Expressões Regulares com PHP e Python na prática

Expressões Regulares com PHP e Python

Pessoal, estou pensando em preparar um material bem detalhado sobre apache e mod_rewrite, indispensáveis para um bom trabalho de SEO (Search Engine Optimization) com URL Amigáveis.

Antes de me aprofundar nesse assunto com vocês, é necessário ter uma boa base em expressões regulares para podermos personalizar da maneira que desejarmos nossas URLs.

Edit Atualização: A vídeo aula sobre apache mod_rewrite já foi publicada e pode ser assistida clicando no link: Vídeo Aula – Apache Mod_Rewrite Avançado

Neste artigo introduziremos as principais expressões regulares que usaremos na próxima abordagem prática. Eu escolhi para esse artigo o PHP pois é bastante interessante usá-lo junto com as "URLs Açucaradas" posteriormente e alguns exemplos em Python (3.0.1) pela facilidade em testar as ERs diretamente no terminal.

Primeiramente o que é uma Expressão Regular?

Expressão Regular não são apenas aqueles pequenos conjuntos de caracteres que geralmente resolvem os maiores e mais difíceis problemas do cotidiano no desenvolvimento de software. Como o próprio nome diz, Regular nos vem algo que não foge a regra, regra no caso definida pelas Expressões. Logo Expressões Regulares podem ser definidas com métodos formais para especificar padrões encontrados nesses textos.

Algo que é regular só nos retorna dois valores, verdadeiro ou falso, não existe o padrão meio-termo. Sendo assim podemos fazer verificações utilizando ERs para saber se determinado texto condiz (Casar, do inglês Match) com determinado padrão especificado anteriormente.

Com ERs podemos validar todo dado que segue padrões tal como e-mail que tem como padrão (texto arroba texto ponto texto), tomando texto como sendo não necessariamente literal. Dentre os usos de Expressões Regulares podemos destacar também na validação de CPF, RG, diversos protocolos como http e ftp, tags de marcação, cartões de crédito e etc...

Quais as desvantagens das Expressões Regulares?

ERs parece ser a resposta para todos os seus problemas, não? Errado, ela pode ser o seu problema se usada de maneira errada e/ou de maneira demasiada. O grande poder das Expressões Regulares pode custar muito caro na performance da sua solução pois consome bastante tempo e memória, tornando a navegação lenta, o ideal é evitar o seu uso se existirem alternativas disponíveis.

Outra variável no uso de expressões regulares é o tamanho do seu projeto, em um site por exemplo, que contêm poucas visitas diárias, o uso de expressões regulares não custará tanto, porém um site com várias requisições simultâneas seria prejudicado, pois você terá muito processamento agregado em ERs.

Por exemplo, se for necessário verificar se uma variável contêm @ (arroba) ao invés de usarmos ERs podemos fazer uma verificação simples com strpos(), strstr(),..

 
<?php
 
$texto = "ola@mundo";
$existe = strpos($texto,"@");
 
if($existe === false){
	// não existe arroba no texto
} else {
	// existe arroba no texto, e esta na posição $existe
}
 
?>
 
ApacheBench: Poderiamos usar o programa disponibilizado pelo Apache para fazer benchmarking chamado ab (ApacheBench) encontrado facilmente dentro do diretório bin do seu apache, com ele teríamos uma visão ampla podendo setar centenas de request's com concorrência entre outros atributos interessantes, porém isso vale um outro artigo baseando-se na complexidade do assunto. Vamos fazer o benchmarking com o PHP pois podemos limitar precisamente o local que queremos analisar.

Falando em PHP, uma maneira funcional de calcular a diferença de performance utilizando ERs e uma verificação simples é o uso da função microtime() do PHP, como vemos abaixo.

Calculando o tempo gasto na checagem de padrão manualmente:

 
<?php
ob_start();
 
// atribui o tempo inicial
$inicial = microtime();
 
$texto = "ola@mundo";
strpos($texto,"@");
 
// atribui o tempo final
$final = microtime();
 
/* calcula a diferença entre final e inicial
o valor retornado pela função microtime() é em milissegundos
então multiplicamos por 1000 para obter o valor em segundos */
 
echo "Tempo gasto: ".(($final - $inicial)*1000)." segundos.";
?>
 

O valor retornado poderá variar cada vez que atualizar a página, pois o processador pode estar processando outras threads, o retorno médio no meu computador foi de (0.058 segundos) para encontrar a primeira ocorrência de arroba na variável "ola@mundo".

Calculando o tempo gasto na checagem de padrão usando Expressões Regulares:

 
<?php
ob_start();
 
// atribui o tempo inicial
$inicial = microtime();
 
$texto = "ola@mundo";
 
// Expressão Regular utilizada para checar a presença de arroba
$er = '/(.*)?@(.*)?/';
preg_match($er,$texto);
 
// atribui o tempo final
$final = microtime();
 
/* calcula a diferença entre final e inicial
o valor retornado pela função microtime() é em milisegundos
então multiplicamos por 1000 para obter o valor em segundos */
 
echo "Tempo gasto: ".(($final - $inicial)*1000)." segundos.";
?>
 

O retorno médio no meu computador foi de (0.090 segundos) para encontrar a primeira ocorrência de arroba na variável "ola@mundo".

Podemos ver que o tempo de processamento é quase o dobro, sendo:

Maneira Alternativa: 0.058 segundos
Expressões Regulares: 0.090 segundos

Essa diferença de tempo aumenta de acordo com a dificuldade da operação realiza, se o nosso texto tivesse 400 linhas, a diferença de tempo seria gritante, fica como exercício para vocês fazerem os testes de performance (benchmarking).

Vamos ao estudo das expressões, eu recomendo para estudo a prática das expressões, pois assim como linguagem de programação ERs só se aprende fazendo.

Essas expressões vistas singularmente também podem ser chamadas de metacaracteres.

Como o nosso conhecimento em ERs é bastante limitado até agora, não podemos ainda criar expressões (Composição de Metacatacteres) então iremos nos restringir a exemplos bem simples.

PHP trabalha com dois tipos diferentes de expressão regular sendo POSIX e Perl-Compatible. POSIX é menos poderosa e as vezes mais lenta que as expressões Perl-Compatible porém pode ser mais fácil de utilizar. Nós usaremos Perl-Compatible pois também usaremos esse padrão no artigo de mod_rewrite.

A função para expressões regulares Perl-Compatible no php é preg_match() e para POSIX é ereg()

1) O metacaracter circunflexo ^:

O circunflexo representa o começo da string. Por exemplo, se quisermos saber se determinado texto começa com 'abc' poderemos prosseguir da seguinte maneira:

^abc em PHP:

 
<?php
// Texto alvo
$texto = "abcdefgh";
 
// Ao adicionarmos o modificador i no final dizemos
// que a comparação deve ser case-insensitive  
 
$er = '/^abc/i';
 
if(preg_match($er,$texto))
	echo "Começa com abc";
else
	echo "Não começa com abc";
?>
 

^abc em Python:

 
// Biblioteca para trabalhar com ERs no Python
import re
 
// Texto alvo
texto = 'abcdefgh'
 
// re.match para comparação re.match(Padrao,String,[bandeiras])
 
if(re.match('^abc',texto,re.IGNORECASE)):
	print('Começa com abc ou ABC')
else:
	print('Não começa com abc nem ABC')
 

2) O metacaracter ponto . :
O ponto implica no fato de aceitar qualquer caracter em determinada posição. Por exemplo, se fossemos brincar com a palavra PHP aceitando como válido o H ser qualquer coisa, P(qualquer caracter)P faremos:

P.P em PHP:

 
<?php
$texto = "PUP";
 
$er = '/P.P/i';
 
if(preg_match($er,$texto))
	echo "PUP é válido com o padrão estabelecido pela ERs";
else
	echo "PUP não é válido com o padrão estabelecido pela ERs";
?>
 

P.P em Python:

 
import re
 
texto = 'PUP'
 
// re.match para comparação re.match(Padrao,String,[bandeiras])
 
if(re.match('P.P',texto,re.IGNORECASE)):
	print('PUP é válido com o padrão estabelecido pela ERs')
else:
	print('PUP é não válido com o padrão estabelecido pela ERs')
 

3) O metacaracter Cifrão $:
Semelhante ao circunflexo, porém atua na verificação no final da string, se quisermos saber se além de começar com 'abc' a string termina com 'xyz' poderemos prosseguir da seguinte maneira:

^(abc)(xyz)$ em PHP:

 
<?php
	$texto = "abcxyz";
 
	$er = '/^(abc)(xyz)$/i';
 
	echo (preg_match($er,$texto))? 'válido' : 'inválido';
 
?>
 

^(abc)(xyz)$ em Python:

 
import re
 
texto = 'abcxyz'
 
// re.match para comparação re.match(Padrao,String,[bandeiras])
 
if(re.match('^(abc)(xyz)$',texto,re.IGNORECASE)):
	print('válido')
else:
	print('inválido')
 

4) Os metacaracteres asterisco, mais, interrogação e chaves * + ? {}:
Os quatro metacaracteres são multiplicadores de ocorrências.

I) O multiplicador asterisco * indica que o caractere que esta antes dele poderá ocorrer 0 (zero) ou infinitas vezes.

II) O multiplicador + indica que o caractere anterior poderá ocorrer 1 (uma) ou infinitas vezes (porém necessariamente uma vez).

III) O multiplicador binário ? indica que o caractere anterior poderá ocorrer 1 (uma) ou 0 (zero) vezes, ou seja, poderá existir ou não.

IV) O multiplicador {} funciona de maneira explicita, sendo que podemos ou não, definir o número de repetições. Por exemplo:

Exemplos
a{n} indica que a deve ocorrer exatamente 'n' vezes. Ex: a{3} = aaa
a{n,m} indica que a deve ocorrer pelo menos 'n' vezes e não mais que 'm' vezes. Ex: a{3,6} = aaaaa
a{n,} indica que a deve ocorrer pelo menos n vezes. Ex: a{3,} = aaaaaaa

Sendo assim, agora podemos permitir mais valores entre 'abc' e 'xyz' com a ajuda do metacaracter ponto que aceita qualquer coisa e usando multiplicidade, poderemos prosseguir da seguinte maneira:

^abc(.*)xyz$ em PHP:

 
<?php
	$texto = "abcdefghijkmnopqrstuvwxyz";
 
	// Indicamos que além de começar com abc e
	// terminar com xyz, poderá existir 0 (zero) ou
	// mais caracteres nesse intervalo
 
	$er = '/^abc(.*)xyz$/i';
 
	echo (preg_match($er,$texto))? 'válido' : 'inválido';
 
?>
 

^abc(.*)xyz$ em Python:

 
import re
 
texto = 'abcdefghijkmnopqrstuvwxyz'
 
// re.match para comparação re.match(Padrao,String,[bandeiras])
 
if(re.match('^abc(.*)xyz$',texto,re.IGNORECASE)):
	print('válido')
else:
	print('inválido')
 
Nota: Perceba que (.+)? é semelhante a (.*), sendo que o + (mais) obriga que o . (ponto) aconteça pelo menos uma vez, porém o metacatacter ? indica que isso não necessariamente precisará acontecer.

5) A lista [] e Grupos ():
As listas e grupos permitem que você defina um espaço amostral. Por exemplo, se quisermos saber se um registro começa com vogais poderemos usar a lista junto com o circunflexo, dessa maneira ^[aeiou] onde irá casar com palavras como "arquitetura","e-mail","inteligência" e etc..

Intervalo em Listas: Utilizando um hífen podemos definir intervalos baseando-se na tabela ASCII como ordem oficial, ou seja, [0-9] indica todos os digitos de 0 até 9 [0123456789] o mesmo com letras [a-z] para todas as minusculas [A-Z] para todas as maiusculas e [a-zA-Z] para os dois simultaneamente.

Com o mesmo circunflexo podemos negar a lista, apenas colocando-o dentro da lista [^aeiou] assim, será válido iniciar com qualquer consoante.

Perceba que a lista só retorna 1 (um) catacter, podemos resolver isso utilizando os Grupos () e utilizando o delimitador "Ou" | (http:|https:|ftp:) Assim poderemos validar esses protocolos da seguinte maneira:

Nota: Não é possível negar um grupo utilizando o circunflexo. Para fazer a negação utilize ! (exclamação) !^(http:|https:|ftp:)

^(http:|https:|ftp:) em PHP:

 
<?php
	$texto = "http://www.rafaelcapucho.com";
 
	$er = '/^(http:|https:|ftp:)/i';
 
	echo (preg_match($er,$texto))? 'válido' : 'inválido';
 
?>
 

^(http:|https:|ftp:) em Python:

 
import re
 
texto = 'http://www.rafaelcapucho.com'
 
// re.match para comparação re.match(Padrao,String,[bandeiras])
 
if(re.match('^(http:|https:|ftp:)',texto,re.IGNORECASE)):
	print('válido')
else:
	print('inválido')
 
Nota: Se for preciso utilizar barras nas expressões regulares dentro de funções devemos escapá-las com a contra-barra. \

I) Estudo de Caso - Validando e-mail com Expressão Regulares

Agora que nós já conhecemos os principais metacaracteres podemos começar a brincar com eles, vamos aos estudos de caso, como poderemos proceder para validar um endereço eletrônico? Primeiro devemos analisar, o padrão estabelecido que no caso seria algo como:

texto @ texto ponto texto

Como não podemos saber quantos caracteres tem os textos previamente, devemos utilizar multiplicadores, devemos multiplicar o metacaracter . (ponto) pois ele casará com qualquer coisa.

Usaremos o multiplicador + (mais) pois o nosso texto deve ter pelo menos 1 caractere, sendo assim temos algo como (.*).

Aplicando ao padrão temos algo como: (.+)@(.+).(.+) perceba que não podemos utilizar o . (ponto) para representar um ponto literal pois ele será tratado como metacaracter então escapamos ele com uma contra-barra \. Teremos:

(.+)@(.+)\.(.+) Estamos esperando o que para testar?

(.+)@(.+)\.(.+) em PHP:

 
<?php
	$texto = "palavra@provedor.com";
 
	$er = '/(.+)@(.+)\.(.+)/i';
 
	echo (preg_match($er,$texto))? 'válido' : 'inválido';
 
?>
 

(.+)@(.+)\.(.+) em Python:

 
import re
 
texto = 'palavra@provedor.com'
 
// re.match para comparação re.match(Padrao,String,[bandeiras])
 
if(re.match('(.+)@(.+)\.(.+)',texto,re.IGNORECASE)):
	print('email válido')
else:
	print('email inválido')
 

II) Estudo de Caso - Removendo Tags com Expressão Regulares

Primeiramente devemos analisar o padrão estabelecido pelas Tags, escolhi para o exemplo Tags HTML pela familiaridade que muitos tem com esse padrão, mapeando teremos algo como:

texto <tag> texto </tag> texto

A idéia é remover as tags, todas as tags independente do texto dentro da tag, ou seja, devemos remover tudo que estiver entre <> seja lá o que estiver escrito.

Se não sabemos previamente o que estará dentro das Tags HTML, podemos representar com a multiplicação indefinida de qualquer catactere .*, que deve estar dentro das Tags, algo como:

<.*?>

Observação: Analisando essa expressão chegamos a algumas questão, tal como, porque usar o multiplicador * (asterisco) ao invés do + (mais)? Escolhemos o * (asterisco) porque também queremos remover tags vazias como essa <>, se usássemos o multiplicador + (mais) só removeríamos tags que contêm pelo menos 1 (um) caractere dentro das tags

Removendo Tags com PHP e Expressões Regulares:

 
<?php
	$texto = "texto1<strong>texto2</strong>texto3";
 
	$er = '/<.+?>/i';
 
	// Função preg_replace(pattern, replacement, subject [, limit ]);
 
	echo $novoTexto = preg_replace($er,"",$texto);
 
?>
 

A saída do código acima será: texto1texto2texto3 onde trocamos todas as tags encontradas pelo conteúdo expresso pelo segundo parâmetro da função preg_replace().

Removendo Tags com Python e Expressões Regulares:

 
import re
 
texto = 'texto1<strong>texto2</strong>texto3'
 
// Compila a ER com a flag IGNORECASE
 
padrao = re.compile('<.*?>',re.IGNORECASE)
 
// Baseando no padrao, substitui o primeiro parametro
// no segundo parametro
 
novoTexto = padrao.sub("",texto)
 
print(novoTexto)
 
Performance: Entretanto como analisamos anteriormente devemos evitar usar ER. Poderíamos criar uma lista de tags e mandar substituí-las utilizando str_replace() ao invés de preg_replace(). Assim ganhamos muito com performance.

Com esse artigo já temos uma visão considerável sobre ER para utilizarmos no artigo sobre mod_rewrite e outras aplicações.

Dessa maneira, termino esse artigo sobre Expressões Regulares em caso de dúvidas, entre em contato! Bom proveito!




 
 
 

9 Respostas para “Expressões Regulares com PHP e Python na prática”

  1. Renan J. 31.07.2009 as 16:47
    Firefox 3.0.12Windows XP


    Muito bom o artigo,
    possui aplicações diretas no desenvolvimento de softwares,
    alem de ser de facil intendimento mesmo para quem não tem conhecimentos profundos em programação!
    Eu estava me perguntando como avaliar a eficiencia no tempo de processamento de um codigo, ta respondido!

  2. Vídeo Aula - Apache Mod_Rewrite Avançado | Rafael Capucho 22.08.2009 as 20:11
    WordPress 2.7.1


    [...] estou adicionando a vídeo aula sobre Apache Mod_Rewrite que eu fiz para dar continuidade ao Artigo Expressões Regulares com PHP e Python. É muito importante a leitura desse artigo antes de assistir o vídeo tutorial para não ficar [...]

  3. VAB - Vídeo Aulas Brasil - Vídeo aula de Flash, Vídeo aula de Fireworks, Vídeo aulas de PHP, Vídeo aulas de Tableless e muito mais... 26.08.2009 as 00:01
    WordPress 2.8.1


    [...] É recomendado que antes de assistir o vídeo o usuário que não tem um bom conhecimento sobre expressões regulares (Regular Expression) leia esse artigo: http://blog.rafaelcapucho.com/expressoes-regulares/expressoes-regulares-com-php-e-python-na-pratica…. [...]

  4. Apolo Lira 21.02.2010 as 12:59
    Firefox 3.5.7Windows 7


    É rapaz, muito bom o artigo mto detalhado.. bem feito.. parabéns…

    Metas do Google Analytics + Expressoes Regulares, combinam demais….

    flw abração.

  5. Anderson Ricardo 22.02.2010 as 02:02
    Netscape Navigator 9.0.0.6Windows XP


    Manolo, isto é que é explicação!
    Consegui assimilar tudo cara, wlw!

  6. Ernani Medeiros 6.06.2010 as 08:53
    Firefox 3.6.3Windows Vista


    Prezado Rafael Capucho, em ambos os casos (artigo e video aula), você está de parabéns. Seus exemplos todos funcionaram em meu caso, com uma execeção. Gostaria de saber se há algo errado. Meu sistema (Apache 2.0, Winodows Vista Business 64 e PHP 5.3.1). Bem o Módulo rewrite está habilitado e funciona para qualquer coisa como:
    #RewriteRule ^google$ http://www.youtube.com [R]
    #RewriteRule ^proibido – [F]
    #RewriteRule ^naoexiste – [G,NC]
    #RewriteRule ArquivoQuePassaQuery.php ArquivoQueRecebeQuery.php [QSA]
    #RewriteRule ^velho\.html /mvcfab/novo.php [R,L]
    #RewriteRule ^velho\.html /mvcfab/index.php [R,L]
    #RewriteCond %(REQUEST_FILENAME) !-d
    #RewriteRule ^index\.html /mvcfab/index.php [R=301,L]
    Todos, seus exemplos que fucionaram e fica o meu agradecimento para você sobre isso. mas o exemplo que quero montar é de que qualquer coisa – qualquer coisa mesmo – que seja digitada vá para um index.php. Bem, tentei:
    RewriteBase /mvcfab/
    RewriteCond %(REQUEST_FILENAME) !-d
    RewriteCond %(REQUEST_FILENAME) !-f
    RewriteRule ^.*$ /mvcfab/index.php [NC,L] – Não funcionou
    RewriteRule ^(.*)$ index.php – Não funcionou
    Resposta do Browser: “The requested URL /mvcfab/xpto was not found on this server.” Eu havia digitado xpto no browser. Any ideas a respeito do que pode ser. Coloquei LogLevel de erro no httpd.conf de debug e lá está: File does not exist: J:/ProjetosWeb/mvcfab/xpto. O erro é o mesmo para qualquer coisa que eu digite, mesmo de um arquivo válido.Tentei isso com e sem RewriteBase /mvcfab/.
    Agradeço se houver algo em que possa ajudar. Obrigado.

  7. Rafael Capucho 7.06.2010 as 03:55
    Firefox 3.5.2GNU/Linux


    Olá Ernani, primeiramente muito obrigado pelos elogios em relação ao conteúdo publicidado, fico feliz que seja útil para você para os outros.

    Bom, pelo o que eu vi no teu código, você tentou ^.*$ e ^(.*)$ mas isso não é muito certo, porque o asteristico aceita zero entradas, o que torna a expressão inválida.

    Tente algo dessa maneira:

    RewriteCond %{REQUEST_FILENAME} !-d
    RewriteCond %{REQUEST_FILENAME} !-f
    RewriteRule (.*) index.html?user=$1 [L]

    Assim, qualquer coisa que é digitada, irá levar para index.html processar. passando valores ou não.

    Abraços e boa sorte!

  8. Gabriel 20.10.2010 as 21:54
    Google Chrome 6.0.472.63Windows 7


    PARABÉNS! ótima video aula e o artigo de E.R. foi excelente! mas nao consegui implementar em um sisteminha de busca :(
    Tenho uma página index.php que por sua vez inclui pesquise.php.
    No htaccess configurei: RewriteRule ^pesquise$ index.php?pagina=pesquise para incluir a página e funcionou perfeitamente, mas após digitar um texto e clicar em pesquisar, envio com form action=”pesquise” method=”get” as palavras a serem pesquisadas.

    index.php?pagina=pesquise&busca=teste (funciona normalmente ao digitar direto na barra de endereços)

    Mas não sei passar o 2º parametro (variável busca). tentei várias coisas como:
    RewriteRule ^index\.php?pagina=pesquise&busca=(.*?)$ index.php?pagina=pesquise&busca=$1 [QSA] … nada q tentei funcionou.
    de várias tentativas, no melhor resultado q consegui, reparei q pesquise&?busca=teste (&? apareciam juntos) – deixando somente o & funciona!
    Desde já muito obrigado pela ajuda!!!

  9. Marcelo Leal 19.01.2011 as 13:23
    Firefox 3.6.13Windows 7


    Excelente artigo estava procuranto um conteudo sobre mod-rewrite e encontrei aqui.
    Rafael esta de parabêns e obrigado pela contrubuição a nós internaultas.