Regex: Cuidado Com os Quantificadores Gananciosos!

Category : Java, Regex

Para um desenvolvedor ou mesmo um adminstrador de sistemas o uso das expressões regulares é uma excelente ferramenta em diversas situações, como:

  • Localizar sequências de texto condicionalmente;
  • filtrar a saida de logs e comandos do sistema;
  • fazer parsing simplificado em arquivos e sequências de texto.

Não pretendo explicar o básico sobre regex aqui, pois existem excelentes tutorias sobre o assunto na Internet, basta dar uma pesquisada no Google. Entretanto vou mostrar uma pequena armadilha que os quantificadores nos fazem cair.

Buscando a URL de uma imagem numa tag HTML.

Suponhamos que você esteja procurando pelo endereço de uma imagem dentro de uma tag IMG do HTML na seguinte sequência de texto:

  • <img src=’http://blog.sobreira.eti.br/image.jpg’ alt=’Imagem’/>

Você poderia utilizar uma regex simples para capturar a URL, como:

  • <img src=’.+’

Mas se surpreenderia ao ver o resultado:

  • src=’http://blog.sobreira.eti.br/image.jpg’ alt=’Imagem

A nossa busca retornou uma sequência muito além da nossa url

O que aconteceu de fato?

Os quantificadores regex ?, + e * sozinhos são chamados de quantificadores gananciosos (greedy), e repetem a sentença antecessora quantas vezes possível. No caso da tag, inicialmente encontramos o literal src=’, em seguida o motor regex é instruído a procurar qualquer caractere nenhuma ou muitas vezes quantas vezes possível .*, o que nos leva até o fim da nossa sequência de texto. O resultado até aqui seria:

  • src=’http://blog.sobreira.eti.br/image.jpg’ alt=’Imagem’>

Mas ainda falta um token, a aspas de fechamento. A busca seria considerada como falha neste ponto, porém o motor regex a partir de agora volta procurando por nossa aspas de fechamento. No primeiro passo ele encontra o sinal > e o descarta, no segundo ele encontra uma aspas e dá a busca como bem sucedida com o seguinte resultado:

  • src=’http://blog.sobreira.eti.br/image.jpg’ alt=’Imagem

Um pouco diferente do que imaginamos a princípio.

Como resolver este problema?

A resposta para este problema é a utilização dos quantificadores relutantes (lazy). Estes basicamente são a representação do operados ganancioso junto de uma interrogação: ??, ?+ e ?*. Diferente dos quantificadores gananciosos, os operadores relutantes repetem a pesquisa o mínimo de vezes possível, no mínimo uma vez. Primeiro encontra o h, em seguida verifica se o próximo caractere é ‘. O que falha, pois o caractere seguinte é t. O motor então continua expandindo a busca por ‘ enquanto a pesquisa falhar.

Então o resultado para:

  • <img src=’.+?’

é:

  • <img src=’http://blog.sobreira.eti.br/image.jpg

Bem próximo do que desejamos.

Não exagere no uso dos operadoes relutantes!

Os operadores relutantes têm baixo desempenho pois ficam indo e voltando (backtracking) a cada iteração. “Não encontrei aspas, então volto e verifico se o próximo caractere é um símbolo qualquer”. É como dar um passo pra frente, um passo pra trás e dois passos pra frente. Uma solução simples e comumente utilizada nestes casos é utilizar a negação junto com o quantificado ganancioso. Por exemplo:

  • <img src=’[^']+

Isto permite o motor seguir em frente enquanto o segundo caractere de apas não é encontrado. Quando encontrado a busca termina sem retornos. A operação falha retornando nossa URL completa. Este tipo de busca oferece excelente desempenho. Mais informações aqui.

Utilizando grupos.

Para isolar as partes da expressão encontradas por nossa expressão regular, podemos utilizar grupos de captura, identificados pelo delimitador ().

  • <img src=’([^.*]+)

A expressão regular anterior isola nossa url num grupo. Em Java os grupos são acessados pelo método group() da classe Matcher (pacote java.util.regex). Os grupos são numerados da esquerda para direita, sendo 0 (zero) a sentença como o todo. Na nossa regex anterior group(1) retornaria a URL integralmente.

No próximo artigo mostrarei como fazer buscas regex em sequência de caracteres (CharSequence) no Java com as classes Pattern e Matcher do pacote java.util.regex. Até lá!

Operadores utilizados nos exemplos:

  • Repete o item anterior zero ou muitas vezes (possessivo): *
  • Repete o ítem anterior uma ou muitas vezes (possessivo): +
  • Marca o ítem anterior como opcional (possessivo): ?
  • Repete o item anterior zero ou muitas vezes (relutante): *?
  • Repete o ítem anterior uma ou muitas vezes (relutante): +?
  • Nega os caracteres dentro da classe: [^]

Referência:

http://www.regular-expressions.info/
http://java.sun.com/javase/6/docs/api/java/util/regex/package-summary.html

Comments (1)

Finalmente postei alguma coisa no blog: http://blog.sobreira.eti.br/?p=68. Um pouquinho sobre expressões regulares.

Post a comment