Você está na página 1de 8

Dominando Expressões Regulares em PHP, Parte 2: Como

Processar Texto em PHP


Dominar expressões regulares em PHP é fácil — simplesmente aplique
ganância, preguiça e cobiça

Martin Streicher (martin.streicher@gmail.com) 08/Jan/2008


Software Developer
#####

Aqui, na Parte 2 da série " Dominando Expressões Regulares em PHP", aprenda como solucionar
diversos problemas de processamento de texto difíceis com alguns operadores de expressões regulares
(regex) avançados.

Visualizar mais conteúdo nesta série

Apesar de os termos dados e informações serem usados um pelo outro, há uma grande diferença entre
os dois. Dados são reais. Dados são uma lista de temperaturas, uma litania de vendas recentes ou um
inventário de peças disponíveis. Informações são uma perspectiva. Informações são uma previsão do tempo,
uma declaração de lucro e perda e uma tendência de vendas. Dados são registrados como uns e zeros.
Informações são unidas por sinapses.

Entre dados e informações está o aplicativo de software: o mecanismo que transforma o primeiro no
segundo e vice-versa. Por exemplo, se você comprar um livro on-line, uma requisição de compra permuta
suas informações — o título do livro, sua identidade, as informações de sua conta bancária — em dados,
como um número de pedido, um preço de venda, detalhes da transação de cartão de crédito e um ajuste do
inventário disponível. De forma semelhante, a requisição de compra transforma os dados em um pedido de
seleção para o armazém e em uma etiqueta de remessa e um número de acompanhamento — informações
necessárias para efetuar a venda.

Na verdade, a complexidade da criação de um aplicativo é diretamente proporcional às transformações que


afeta. O livro de convidados de um Web site, que converte um nome e endereço em campos de um banco de
dados, é simples. Por outro lado, uma loja on-line, que converte muitos tipos de informações no modelo de
dados do negócio e converte os dados em informações para inspirar tomadas de decisões, é bem elaborada.
A arte de programação é a manipulação competente de dados e informações — uma qualificação semelhante
à captura de luz na técnica de pintura claro-escuro.

© Copyright IBM Corporation 2008. Todos os direitos reservados. Marcas Registradas


Dominando Expressões Regulares em PHP, Parte 2: Como Processar Página 1 de 8
Texto em PHP
developerWorks® ibm.com/developerWorks/br/

Conforme introduzido na Parte 1, regexes são uma das ferramentas mais poderosas para manipulação
de dados. Usando uma taquigrafia concisa, regexes descrevem o formato dos dados e decompõem os
mesmos. Por exemplo, você poderia usar a regex a seguir para processar qualquer temperatura em Celsius ou
Fahrenheit: /^([+-]?[0-9]+)([CF])$/.

A regex corresponde ao início da linha (representado pelo circunflexo ^), seguida pelo sinal de positivo,
um sinal de negativo ou nenhum dos dois ([+-]? ), seguindo por um número inteiro ([0-9]+), um qualificador
de escala — Celsius ou Fahrenheit ([CF]) — e terminada pelo final de linha (representado pelo símbolo de
dólar, $).

Na regex de temperatura, os operadores de início de linha e de fim e linha são dois exemplos de asserções de
largura zero ou correspondências que são posicionais, não literais. Os parênteses não são literais, também.
Em vez disso, colocar um padrão entre parênteses captura o texto que corresponde ao padrão. Assim, se o
texto corresponder a todo o padrão, o primeiro conjunto de parênteses produziria uma string representando
um número inteiro positivo ou negativo, como +49. O segundo conjunto de parênteses produziria a letra C
ou F.

A Parte 1 introduz a noção da regex e as funções de PHP disponíveis para comparar texto a padrões e
extrair correspondências. Agora, aprofundo-me mais nas regexes e dou uma olhada nos diversos operadores
avançados e receitas.

Parênteses para a Salvação (Novamente)


Na maioria das vezes, você usa parênteses para definir um subpadrão e para capturar o texto que
corresponde ao subpadrão. No entanto, os parênteses não precisam capturar o subpadrão. Como em uma
fórmula aritmética complexa, é possível usar parênteses simplesmente para agrupar termos.

Segue um exemplo. Você pode dizer a que tipo de dados corresponde?

/[-a-z0-9]+(?:\.[-a-z0-9]+)*\.(?:com|edu|info)/i

Essa regex corresponde a nomes de host (apesar de estar somente nos domínios .com, .edu e .info),
como pode ter previsto. A diferença é a inclusão de ?:. O qualificador de subpadrão ?: desativa a captura,
deixando que os parênteses esclareçam a precedência da operação. Aqui, por exemplo, a frase (?:\.[-a-z0-9]+)*
corresponde a zero ou mais instâncias de uma string, como ".ibm". De forma semelhante, a frase \.(?:com|edu|
info) expressa um período literal seguido por qualquer uma das strings com, edu ou info.

A desativação da captura pode parecer infundada, até você perceber que a captura requer processamento
extra. Se seu código processar muitos dados, omitir a captura pode ser vantajoso. Além disso, se sua regex
for especialmente complexa, desativar a captura em determinados subpadrões pode facilitar a extração dos
subpadrões nos quais está realmente interessado.

Nota: O modificador i no final da regex faz com que todas as correspondências no padrão não façam
distinção entre maiúsculas e minúsculas. O subconjunto a-z, portanto, corresponde todas as letras,
independentemente de maiúsculas e minúsculas.

Dominando Expressões Regulares em PHP, Parte 2: Como Processar Página 2 de 8


Texto em PHP
ibm.com/developerWorks/br/ developerWorks®

O PHP oferece outros modificadores de subpadrão. Usando a regex test jig fornecida na Parte 1 (repetida
aqui na Lista 1), corresponda a regex ((?i)edu) a strings candidatas "EDU", "edu" e "Edu". Se você iniciar
um subpadrão com o modificador (?i), a correspondência no subpadrão faz não distinção entre maiúsculas
e minúsculas. A distinção entre maiúsculas e minúsculas é reativada assim que o subpadrão terminar.
(Compare isso ao modificador /.../i acima, que se aplica a todo o padrão.)

Lista 1. Um Utilitário de Teste de Regex Simples


<?php
//
// divida a lista separada por vírgula em palavras individuais
// o terceiro parâmetro, -1, permite um número ilimitado de correspondências
// o quarto parâmetro, PREG_SPLIT_NO_EMPTY, ignora correspondências vazias
//
$words = preg_split( '/,/', $_REQUEST[ 'words' ], -1, PREG_SPLIT_NO_EMPTY );
//
// remova os espaços à esquerda e à direita de cada elemento
//
foreach ( $words as $key => $value ) {
$words[ $key ] = trim( $value );
}
//
// localize as palavras que correspondem à expressão regular
//
$matches = preg_grep( "/${_REQUEST[ 'regex' ]}/", $words );
print_r( $_REQUEST['regex' ] );
echo( '<br /><br />' );

print_r( $words );
echo( '<br /><br />' );

print_r( $matches );

exit; ?>

Outro modificador de subpadrão útil é (?x). Permite integrar espaços em branco a um subpadrão, facilitando
a leitura da regex. Assim, o subpadrão ((?x) edu | com | info) (observe os espaços entre os operadores de
alternação incluídos para legibilidade) é o mesmo que (edu|com|info). Você pode usar o modificador global /.../
x para integrar espaços em branco e comentários em toda a regex, conforme mostrado abaixo.

Lista 2. Embarcar Espaço em Branco e Comentários


$matches = preg_grep(
"/
[- a-z 0-9]+ # nome da máquina
(?: \. [- a-z 0-9]+)* # subdomínios
\. (?: com | edu | info)# domínio
/xi", $words );

Como pode ver, também é possível combinar os modificadores conforme necessário. Além disso, se precisar
corresponder um espaço literal ao usar (?x), digamos, use o metacaractere \s para corresponder qualquer
caractere de espaço em branco ou \ (a barra invertida seguida por um espaço) para corresponder a um único
espaço, como em ((?x) hello \ there).

Dominando Expressões Regulares em PHP, Parte 2: Como Processar Página 3 de 8


Texto em PHP
developerWorks® ibm.com/developerWorks/br/

Dando uma Olhada ao Redor


A grande maioria de uso de regex valida ou decompõe entrada em pequenas partes individuais armazenadas
como dados em um repositório ou trabalhada imediatamente pelo aplicativo. O processamento de campo em
um formulário, a análise de código XML e a interpretação de um protocolo são usos canônicos.

Outro uso de regex é formatar, normalizar ou melhorar a capacidade de leitura de dados. Em vez de
usar regex para localizar e extrair texto, a formatação usa regex para localizar e inserir texto na posição
apropriada.

Segue uma aplicação útil de formatação. Suponhamos que um formulário da Web envie um salário em
dólares sem cents a seu aplicativo. Como você armazena o salário como um número inteiro, seu aplicativo
deve remover a pontuação dos dados postados antes de serem persistidos. No entanto, quando os dados são
recuperados do repositório, você deseja reformatá-lo para que fique legível, usando vírgulas. Uma chamada
de PHP simples para converter o valor de dólar em um número é mostrada abaixo.

Lista 3. Converter Valores de Dólares em um Número


$salary = preg_replace( "/[\$\s,]/", '', $_REQUEST[ 'salary' ] );
if ( is_numeric( $salary ) ) {
// persista os dados } else {
// erro }

A chamada à função preg_replace() substitui o símbolo de dólar, qualquer espaço em branco e cada vírgula
por uma string vazia, produzindo o que deve ser um número inteiro. Se a chamada a is_numeric() validar a
entrada, os dados podem ser armazenados.

Em seguida, vamos reverter a operação para emitir o número com um símbolo de moeda e vírgulas para
separar centenas, milhares e milhões. Você poderia escrever código pata localizar essas unidades ou pode
usar procurar à frente e procurar para trás para inserir vírgulas na posição apropriada. O modificador de
subpadrão ?<= denota procurar para trás (ou seja, procurar à esquerda) da posição atual. O modificador ?=
significa procurar à frente (procurar à direita) da posição atual.

Portanto, qual é a posição apropriada? Qualquer local da string onde houver pelo menos um dígito à
esquerda e um ou mais grupos de três dígitos à direita, excluindo o ponto decimal e o número de cents.
Considerando essa regra e os dois modificadores para olhar ao redor, ambos os quais são asserções de
largura zero, esta instrução faz o necessário:

$pretty_print = preg_replace( "/(?<=\d)(?=\d\d\d)+$)/", ',', $salary );

Como a última regex funciona? Começando pelo início da string e prosseguindo por cada posição, a regex
pergunta: "Há pelo menos um dígito à esquerda e um ou mais grupos de três dígitos para a direita?" Se
houver, uma vírgula "substitui" a asserção de largura zero.

Muitas correspondências complexas podem ser dispensadas usando facilmente uma estratégia semelhante a
acima. Por exemplo, segue outro uso de procurar à frente que soluciona rapidamente um dilema comum.

Dominando Expressões Regulares em PHP, Parte 2: Como Processar Página 4 de 8


Texto em PHP
ibm.com/developerWorks/br/ developerWorks®

Lista 4. Exemplo de Procurar à Frente


$tab_data = preg_replace( '/
, # procure uma vírgula
(?= # em seguida, procure à frente
(?:[^"]*$) # uma string sem aspas e eol
| # -ou-
(?:[^"]*"[^"]*"[^"]*)*$ # uma string com aspas equilibradas
) #
/x', "\t", $csv_data );

Essa instrução preg_replace() transforma uma linha de dados separados por vírgula em uma linha de dados
separados por tabulação. Sabiamente, não substitui uma vírgula encontrada em uma string entre aspas.

A regex faz uma asserção em toda ocorrência de uma vírgula (a vírgula no início da regex): "Não há aspas à
frente ou há um número par de aspas à frente?" Se a asserção for verdadeira, a vírgula pode ser substituída
por uma tabulação (\t).

Se você não gostar dos operadores de procurar ao redor ou se estiver trabalhando em uma linguagem que
não fornece os mesmos, é possível integrar vírgulas em um número usando uma regex tradicional, apesar de
fazer isso exigir muitas iterações a serem realizadas. Segue uma possível solução.

Lista 5. Integrando Vírgulas


$pretty_print = preg_replace( "/[\$\s,]/", '', $_REQUEST[ 'salary' ] );

do {
$old = $pretty_print;
$pretty_print = preg_replace( "/(\d)(\d\d\d\b)/", "$1,$2", $pretty_print );
} while ( $old != $pretty_print );

Vamos percorrer o código. Primeiro, um parâmetro salary tem sua pontuação removida para simular a leitura
de um número inteiro a partir do banco de dados. Em seguida, o loop é repetido, localizando posições onde
um único dígito ((\d) é seguido por três dígitos ((\d\d\d\) terminado imediatamente em um limite de palavra,
designado por \b. Um limite de palavra é outra asserção de largura zero e é definido como:

• Antes do primeiro caractere da string, se o primeiro caractere for um caractere de palavra.


• Após o último caractere da string, se o último caractere for um caractere de palavra.
• Entre um caractere de palavra e um caractere não de palavra imediatamente após o caractere de palavra.
• Entre um caractere não de palavra e um caractere de palavra imediatamente após o caractere não de
palavra.
Assim, um espaço em branco, um ponto e uma vírgula são cada um limite de palavra válida.

Devido ao loop externo, a regex avança essencialmente da direita para a esquerda procurando um dígito
seguido por três dígitos e um limite de palavra. Se uma correspondência for localizada, uma vírgula é
inserida entre os dois subpadrões. Desde que preg_replace() localize uma correspondência, o loop deve
continuar, o que explica a condição $old != $pretty_print.

Ganância e Preguiça
Regexes são bem poderosas. Às vezes, um tanto quanto muito poderosas. Por exemplo, considere o que
ocorre quando a regex ".*" é aplicada à string "The author of 'Wicked' also wrote 'Mirror, Mirror.'" Enquanto

Dominando Expressões Regulares em PHP, Parte 2: Como Processar Página 5 de 8


Texto em PHP
developerWorks® ibm.com/developerWorks/br/

você pode esperar preg_match() para retornar duas correspondências, pode se surpreender em localizar apenas
um único resultado: 'Wicked' also wrote 'Mirror, Mirror.'

A causa? A menos que você especifique o contrário, operadores, como * (nenhum ou mais) e + (um ou
mais) são gananciosos. Se um padrão puder continuar a correspondência, irá, produzindo o maior resultado
possível. Para manter o mínimo de correspondências, você deve forçar determinados operadores a serem
preguiçosos. As operações preguiçosas encontram as correspondências mais curtas, em seguida, param. Para
tornar um operador preguiçoso, inclua um sufixo de ponto de interrogação. A Lista 6 mostra um exemplo.

Lista 6. Incluindo um Sufixo de Ponto de Interrogação


$text = 'The author of "Wicked" also wrote "Mirror, Mirror."';
if ( preg_match_all( '/".*?"/', $text, $matches ) ) {
print_r( $matches[0] );
}

O fragmento de código acima produz:

Array ( [0] => "Wicked" [1] => "Mirror, Mirror." )

A regex ".*?" converte " para corresponder as aspas, seguidas por caracteres apenas suficientes , seguidos por
aspas.

Às vezes, no entanto, o operador * pode ser muito preguiçoso. Considere o fragmento de código a seguir, por
exemplo. O que ele produz?

Lista 7. Um Utilitário de Teste de Regex Simples


if (preg_match( "/([0-9]*)/", "-123", $matches ) ) {
print_r( $matches ); }

O que você achou? "123"? "1"? Nenhuma saída? Na verdade, a saída é Array ( [0] => [1] => ), o que significa
que uma correspondência foi feita, mas nada foi capturado. Por quê? Lembre-se de que o operador * pode
corresponder zero ou mais vezes. Aqui, a expressão [0-9]* corresponde zero vezes com relação ao início da
string e o processamento para.

Para corrigir o problema, inclua uma asserção de largura zero para ancorar a correspondência, que força o
mecanismo regex a continuar a corresponder; /([0-9]*\b/ é suficiente.

Mais Dicas e Truques


Regexes podem solucionar problemas de processamento de texto simples ou difíceis. Comece com um
punhado de operadores e expanda seu vocabulário à medida que sua experiência crescer. Para dar início a
seu empenho, segue um punhado de dicas e truques.

Tornar suas Regexes Portáteis com Classes de Caracteres


Você viu metacaracteres, como \s, que correspondam a quaisquer caracteres de espaço em branco. Além
disso, muitas implementações regex suportam classes predefinidas de caracteres que são mais fáceis de usar

Dominando Expressões Regulares em PHP, Parte 2: Como Processar Página 6 de 8


Texto em PHP
ibm.com/developerWorks/br/ developerWorks®

e portáteis para diversas linguagens de escrita. Por exemplo, a classe de caractere [:punct:] representa todos
os caracteres de pontuação no código do idioma atual. É possível usar [:digit:] no lugar de [0-9] e [:alpha:] é
uma substituição mais portátil para [-a-zA-Z0-9_]. Por exemplo, você pode remover toda a pontuação de uma
string, usando a instrução:

$clean = preg_replace( "/[[:punct:]]/", '', $string );

A classe de caractere é mais sucinta do que escrever por extenso todos os caracteres de pontuação. Consulte
a documentação para sua versão de PHP para obter uma lista completa de classes de caracteres.

Excluir o que Você Não Está Procurando


Conforme mostrado no exemplo de dados delimitados por valores separados por vírgula (CSV) para
delimitados por tabulação, às vezes é mais fácil e mais preciso listar o que você não deseja corresponder.
Um conjunto iniciado por um circunflexo (^) corresponde qualquer caractere não incluído no conjunto. Por
exemplo, você pode usar a regex /[2-9][0-9]{2}[2-9][0-9]{2}[0-9]{4}/ para validar números de telefone nos EUA.
Usando um conjunto de exclusão, você poderia escrever a regex de forma mais explícita /[^01][0-9]{2}[^01]
[0-9]{2}[0-9]{4}/. Ambas as regexes funcionam, apesar de a última ser discutivelmente, mais simples em sua
intenção.

Ignorar a Nova Linha


Se sua entrada se estender por diversas linhas, uma regex típica não será suficiente, pois a varredura termina
em uma nova linha, denotada por $. No entanto, se você usar o modificador s ou m , o mecanismo da regex
trata a entrada de forma diferente. O primeira trata uma string como uma linha única, forçando o ponto a
corresponder a uma nova linha (geralmente não corresponde). O segundo trata a string como diversas linhas,
onde ^ e $ correspondem ao início e o fim de qualquer linha, respectivamente. Segue um exemplo: Se você
configurar $string = "Hello,\nthere";, a instrução preg_match( "/.*/s", $string, $matches) configuraria $matches[0] para
Hello,\nthere. (Remover o s produz Hello.)

Com expressões regulares, sua imaginação e criatividade são virtualmente os únicos limites do que você
pode alcançar.

Dominando Expressões Regulares em PHP, Parte 2: Como Processar Página 7 de 8


Texto em PHP
developerWorks® ibm.com/developerWorks/br/

Sobre o autor
Martin Streicher

Martin Streicher é chefe executivo em tecnologia da McClatchy Interactive, editor chefe da


Linux Magazine, um desenvolvedor da Web e um colaborador regular do developerWorks.
Ele possui mestrado em ciência da computação pela Purdue University e programa sistemas
semelhantes ao UNIX desde 1986.

© Copyright IBM Corporation 2008. Todos os direitos reservados.


(www.ibm.com/legal/copytrade.shtml)
Marcas Registradas
(www.ibm.com/developerworks/br/ibm/trademarks/)

Dominando Expressões Regulares em PHP, Parte 2: Como Processar Página 8 de 8


Texto em PHP

Você também pode gostar