XML não é a resposta 2: parsing

Em meu último post sobre esse assunto, expliquei porque prefiro, na maioria dos casos, usar um formato de descrição de dados como JSON ao invés de XML. Infelizmente, parece que nem todo mundo concorda comigo, e há uma porção de dados úteis disponíveis apenas em XML. O que não é um problema, certo? Do que eu estou reclamando? Os dados estão lá, disponíveis publicamente, muitos de graça, e eu aqui reclamando do formato?

Vamos dar um jeito e ler XML!

A maneira óbvia parece ser usar um parser de XML, e é o que eu faço na maioria dos casos. Toda linguagem e ambiente de desenvolvimento hoje possuem bons parsers de XML para você escolher. Mas nem sempre o parser de XML é a melhor solução.

Vamos tomar como exemplo o retorno do rssWeather.com:

http://www.rssweather.com/wx/br/sao+paulo+aeropor-to/rss.php

A resposta é um RSS com uma tag content:encoded, contendo uma seção CDATA com um trecho de HTML. Você vai ter que fazer duplo parsing se quiser as informações de dentro desse HTML. Ou pode adotar uma solução assim:

import urllib2,re
url='http://www.rssweather.com/wx/br/sao+paulo+aeropor-to/rss.php'
xml=urllib2.urlopen(url).read()
data=dict(re.findall('<dt.*>(.*)</dt><dd.*> ?(.*)</dd>',
        xml.replace(':','').replace('&#176;','\xc2\xb0')))

Cinco linhas de código. Dê uma olhada no retorno disso:

>>> data
{'Barometer': '1028 mb', 'Wind Speed': '10 KMH', 'Dewpoint': '15\xc2\xb0C', 'Wind Direction': 'SSE (160\xc2\xb0)', 'Visibility': '6 km', 'Humidity': '93%', 'Wind Chill': '15\xc2\xb0C', 'Heat Index': '16\xc2\xb0C'}
>>> for i in data.iteritems(): print '%s => %s' % i
... 
Barometer => 1028 mb
Wind Speed => 10 KMH
Dewpoint => 15°C
Wind Direction => SSE (160°)
Visibility => 6 km
Humidity => 93%
Wind Chill => 15°C
Heat Index => 16°C

É importante entender os riscos que estamos assumindo ao adotar essa solução. Se o formato desse HTML for ligeiramente modificado, nosso código pode parar de funcionar. Neste caso, como os dados estão em HTML dentro do XML, isso não é uma desvantagem, porque você teria o mesmo tipo de problema usando um parser SGML.

Será que conseguimos a mesma simplicidade lendo HTML?

import urllib2
url='http://www.bancocentral.gov.br/'
html=urllib2.urlopen(url).read()
dados=html.replace(',','.').split('')[0].split('\r\n')[-7:]
dados=map(float,(dados[0].strip(),dados[3].strip()))

Veja o retorno:

>>> print 'Compra: %.4f, Venda: %.4f' % tuple(dados)
Compra: 1.7784, Venda: 1.7792

Dicas de shell: find

Um dos comandos mais úteis no gerenciamento de arquivos no terminal do Linux[bb] é o comando find. Ele permite realizar e automatizar tarefas que na interface gráfica levariam muito tempo. Executar o comando find sem argumentos faz com que ele liste todos os arquivos do diretório atual e de seus subdiretórios.

Para procurar dentro de um diretório específico, informe o caminho ao find:

find caminho/onde/procurar

O find recebe uma série de parâmetros úteis. É bom lembrar que o diretório de procura precisa ser sempre o primeiro parâmetro. Assim, para procurar por todos os arquivos de extensão bak no diretório atual, faça:

find -name "*.bak"

E para procurar dentro de um diretório específico, use:

find caminho/onde/procurar -name "*.bak"

O find permite a você construir uma série de expressões de procura e operação com os arquivos, algumas bastante complicadas (e poderosas). Vamos dar uma olhada em algumas bem simples. Para apagar os arquivos, use -delete. Então, para apagar todos os arquivos bak no diretório atual e em seus subdiretórios, faça:

find -name "*.bak" -delete

Você também pode passar parâmetros numéricos, que funcionam segundo a seguinte lógica:

  • Um número sozinho significa exatamente aquele número, por exemplo: 3
  • Com um sinal de mais, significa maior do que aquele número: +3
  • Com um sinal de menos, significa menor do que aquele número: -3

Para ver uma lista dos arquivos modificados há menos de três dias, faça:

find -mtime -3

Para ver os que foram modificados a mais de três dias, inverta o sinal:

find -mtime +3

As expressões mais comuns para uso com tempo são:

  • -amin – quando o arquivo foi acessado, em minutos.
  • -atime – quando o arquivo foi acessado, em dias.
  • -cmin – quando o arquivo foi criado, em minutos.
  • -ctime – quando o arquivo foi criado, em dias.
  • -mmin – quando o arquivo foi modificado, em minutos.
  • -mtime – quando o arquivo foi modificado, em dias.

Outras expressões bastante úteis são:

  • -regex: funciona como -name, mas recebe uma expressão regular[bb].
  • -size: filtra pelo tamanho do arquivo. É um número seguido de uma letra. As mais comuns são: c – bytes, k – kilobytes, M – megabytes e G – gigabytes.

Por fim, você também pode mandar o find executar operações nos arquivos. Foi o que fizemos no início, quando usamos -delete para apagar arquivos. Dentre as operações, minha predileta é a -exec.

Quando você coloca -exec numa linha de comando do find, tudo o que vem depois até o próximo “;” é entendido pelo find como um comando, que ele vai executar para cada um dos arquivos encontrados. É obrigatório colocar “;” para terminar o comando, se você se esquecer vai receber o seguinte erro:

$ find -exec ls
find: faltando argumento para `-exec'

Onde você quiser passar para seu comando o nome do arquivo, use {}. Por exemplo, para tirar a permissões de execução de todos os arquivos TXT na busca, você pode fazer:

find -name "*.txt" -exec chmod -x {} ";"

Isso vai executar, para cada um dos arquivos encontrados:

chmod -x ARQUIVO ;

O último truque que eu gostaria que você guardasse na manga é que você pode combinar o find com o excelente comando grep. Grep faz buscas dentro de um arquivo. Com o parâmetro -l o grep apenas lista o nome do arquivo se a expressão regular de busca for encontrada. Então, para encontrar todos os arquivos python[bb] que contém o texto “urllib” você pode fazer:

find -name *.py -exec grep -l urllib {} ";"

O find, é claro, tem muito mais recursos e opções. Se quiser mesmo se aprofundar, man find.

Acentuação em Português e Expressões Regulares Python

Ao utilizar expressões regulares em Python[bb], por padrão, seu texto é interpretado como uma seqüência de caracteres ASCII comum. Assim, caracteres acentuados são considerados sinais gráficos especiais, e não são capturados como letras. Veja este exemplo:


>>> import re
>>> print re.sub(r"\b","|","era uma criança")
|era| |uma| |crian|ç|a|

Como você pode ver, o ce-cedilha não é considerado uma letra, “quebrando” a palavra. Resolver isso é muito fácil, basta compilar a expressão regular passando a flag L, para que ela siga o locale de sua máquina, ou a flag U, para que ela trabalhe com unicode. No meu caso, em que o locale da máquina é unicode, tanto faz. Veja como funciona:


>>> import re
>>> c=re.compile(r"\b",re.U)
>>> print c.sub("|",u"era uma criança")
|era| |uma| |criança|

Só não se esqueça de trabalhar com strings unicode.

Dirty PHP Include, plugin para WordPress que faz include de arquivos PHP em seus posts

Para atender a um cliente, escrevi agora um pequeno plugin para WordPress[bb]. Você pode fazer download do plugin aqui:
http://elcio.com.br/download/dirtyinclude.zip (497 bytes)

Depois, basta descompactá-lo na pasta wp-content/plugins em seu diretório do WordPress. Tendo feito isso, acesse o administrador do WordPress, clique em “Plugins” (“Extensões” na versão em português) e ative o plugin. Pronto, está funcionando.

Para incluir um arquivo em um post basta, ao editar o post, escrever: [[[include:/caminho/ate/seu/arquivo.php]]] e o arquivo será incluído. O caminho é relativo ao diretório base do WordPress. Funciona tanto com o editor visual quanto com o editor de código. Atenção! Se você tem usuários não confiáveis, por exemplo, se permite que seus usuários se cadastrem e postem, não deve usar esse plugin. É perigoso…

Naturalmente, o plugin acaba de ser escrito e não foi exaustivamente testado. Se você tiver sugestões de melhorias ou achar bugs, por favor, avise.

Meus comentários:

  1. WordPress é fantástico! Essa API para a criação de plugins é fabulosa. É muito fácil escrever um plugin que faz bem seu trabalho sem atrapalhar todo o resto.
  2. Continuo não gostando de PHP, mas dá para trabalhar com isso. Se metade do código mundo a fora tivesse a qualidade do WordPress, já ajudaria muito.
  3. Vou dizer de novo: se você não sabe expressões regulares[bb], precisa aprender!

Aprenda expressões regulares!

Exemplos de expressões regulares PHP. Expressões prontas para uma porção de coisas.

Seguem também uns exemplos brasileiros bem comuns:

CPF='\b([0-9]{3}\.?){3}-[0-9]{2}\b'
CNPJ='\b[0-9]{2,3}\.?([0-9]{3}\.?){2}\/[0-9]{4}-[0-9]{2}\b'
CEP='\b[0-9]{2}\.?[0-9]{3}-[0-9]{3}\b'

Expessões regulares permitem fazer com uma linha de código coisas que, de outra forma, levariam um bocado de código e tempo. Veja, por exemplo, este trecho de Javascript:

// Exibe o texto da página, removendo todas as tags
t=document.body.innerHTML
t=t.replace(/<.*?>/g,"")
t=t.replace(/[ \t]+/g," ")
t=t.replace(/(\n ?)+/g,"\n")

Pode dar um pouco de trabalho aprender expressões regulares[bb], mas eu garanto que o tempo que você investir nisso se paga muito rápido.