Escolar Documentos
Profissional Documentos
Cultura Documentos
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.
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.
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.
/[-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.
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.)
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.
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).
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.
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:
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.
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.
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:
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
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.
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?
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.
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:
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.
Com expressões regulares, sua imaginação e criatividade são virtualmente os únicos limites do que você
pode alcançar.
Sobre o autor
Martin Streicher