Regex no Java. Usando Pattern e Matcher

Category : Java, Regex

No artigo anterior demonstrei a utilização dos quantificadores regex para buscar um padrão simples de uma tag HTML. Continuarei com o exemplo demonstrando a utilização das expressões regulares na plataforma Java.

Para começar crie uma classe chamada Conexao no pacote ex.regex. Esta classe encapsula um BufferedReader para leitura do stream de conexão com uma URL.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package ex.regex;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URL;

class Conexao {

    private BufferedReader reader;

    Conexao(URL url) throws IOException {
        reader = new BufferedReader(new InputStreamReader(url.openStream()));
    }

    String ler() throws IOException {
        return reader.readLine();
    }

    void fechar() throws IOException {
        reader.close();
    }

    @Override
    protected void finalize() throws Throwable {
        reader.close();
    }
}

Esta classe recebe no construtor um objeto URL e injeta o InputStreamReader criado pelo método openStream() deste objeto no BufferedReader para posterior leitura. Os métodos ler() e fechar() são bem sugestivos.

Agora é hora de criar a classe ExemploRegex no pacote ex.regex. Incialmente apenas com três membros privados. O primeiro será uma String constante que reprensenta nosso regex. O segundo um objeto Pattern e o terceiro um objeto Matcher. As classes Pattern e Matcher fazem parte do pacote java.util.regex.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package ex.regex;

import java.io.IOException;
import java.io.Console;
import java.net.URL;  
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import ex.regex.Conexao;

public class ExemploRegex {
    private final String REGEX = "<img src=['\"]([^'\"]+)";
    private final Pattern pattern = Pattern.compile(REGEX);
    private final Matcher matcher = pattern.matcher("");
}

“Em resumo um objeto Pattern é uma expressão regular compilada que pode ser aplicada a qualquer numero de strings, o objeto Matcher é uma instância individual desta expressão regular sendo aplicada a uma string específica.”(Mastering Regular Expressions, FRIEDL, JEFFREY E. F.)

Através do método matcher(), criamos um novo objeto Matcher. Este método é interessante, pois recebe um CharSequence (String, StringBuffer, etc) como parâmetro. O objeto matcher trabalha como o motor de buscas que confronta a sequência de caracteres com nosso Pattern. A classe Matcher oferece uma rica API para buscar e retornar resultados de buscas com regex.

Até agora tudo o que temos é nossa regex de captura de URL de imagens e uma sequência de caracteres vazia. Implementaremos na classe ExemploRegex o método privado processarURL().

1
2
3
4
5
6
7
8
9
10
11
12
    private void processarURL(String url) throws IOException {
        String s;
        Conexao cn = new Conexao(new URL(url));

        while ((s = cn.ler()) != null) {
            matcher.reset(s);
            while (matcher.find()) {
                System.out.println(matcher.group(1));
            }
        }
        cn.fechar();
    }

O método processarURL() cria um objeto Conexao para a URL desejada e percorre linha a linha o Reader.

A chamada do método reset() da classe Matcher descarta todas as informações de estado do nosso Matcher informando um novo CharSequence como parâmetro para reiniciarmos nossa pesquisa. O novo CharSequence é a próxima linha obtida do stream.

O método find() tenta encontrar na sequência um padrão equivalente ao Pattern usado para construir este Matcher. Quando encontrado, o primeiro grupo da expressão é impresso, através da chamada a group().

Capturando Grupos

Em regex, os grupos de captura são definidos por parênteses e numerados sequencialmente da esquerda para direita, sendo que o grupo zero representa o Pattern inteiro.

Grupos podem causar um pouco de sobrecarga nas expressões regulares mais complexas pois criam uma backreference para a expressão dentro do grupo, o que pode prejudicar o desempenho das buscas. Sempre sinalize os grupos que não deseja capturar da seguinte maneira com (?:).

Por exemplo: num Matcher criado por um Pattern com expressão regular “(?:Guarda)-(roupas|chuva)”, a chamada do método group(1) retornaria a palavra roupas ou chuva. Grupos marcados com (?:) são ignorados (não são armazenados e não criam backreferences).

Voltando a classe ExemploRegex, agora precisamos de um método main que nos permita entrar com uma URL para ser processada.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
    public static void main(String[] args) throws IOException {

        Console console = System.console();
        if (console == null) {
            System.err.println("O console não pôde ser encontrado.");
            System.exit(1);
        }

        ExemploRegex exr = new ExemploRegex();
        String url = "";

        while (!url.equalsIgnoreCase("sair")) {
            url = console.readLine("Entre com a URL do documento (CTRL+C ou 'sair' para terminar): ");
            exr.processarURL(url);
        }
    }

Verificamos o console e em seguida iniciamos uma iteração que aguarda a entrada da URL do documento para processar. Execute a classe e experimente entrar com a URL de um site como http://www.gamespot.com/pc e veja o resultado.

Considerações Finais

A utilização de expressões regulares combinando as classes Pattern e Matcher do pacote java.util.regex abre um novo leque para o desenvolvedor que precisa encontrar padrões numa sequência de texto com formato não padronizado.

Com elas pode-se encontrar padrões específicos de maneira elegante e simples. Veja quantos operadores condicionais foram necessários para encontrar o que queríamos.

O exemplo apresentado é bem simples e você pode expandí-lo para uso próprio.

Links recomendados:

http://aurelio.net/er/
http://www.regular-expressions.info/brackets.html
http://java.sun.com/javase/6/docs/api/java/util/regex/package-summary.html
http://java.sun.com/docs/books/tutorial/essential/regex/index.html

Livros Recomendados

Mastering Regular Expressions
FRIEDL, JEFFREY E. F.
OREILLY & ASSOC

Real World Regular Expressions With Java 1.4
HABIBI, MEHRAN
APRESS

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