sexta-feira, maio 18, 2007

Expressões Regulares - O início

Diretamente das definições da Sun :P :

Expressões regulares são uma maneira de qualificar um conjunto de strings baseado em características comuns compartilhadas por cada string no conjunto. Elas podem serem usadas para procurar, editar ou manipular texto e dados.

:(

Bem vou tentar desmistificar esse assunto fazendo, claro, um exemplo usando a API java.util.regex do Java.

Expressões regulares variam de complexabilidade mas uma vez que você aprender os conceitos básicos do assunto será capaz de lê-las e escrevê-las sem dificuldades.

Vamos primeiro ver 3 classes chaves para o uso de expressões regulares em Java: Pattern, Matcher e PatternSyntaxExeption.

a) Pattern: É a representação compilada da expressão regular. Não há um construtor público na classe. Para criar um pattern você precisa primeiro invocar um dos métodos compiladores públicos e estáticos da classe que retornarão um objeto Pattern. Estes métodos aceitam como argumento uma expressão regular.

b) Matcher: É o mecanismo de interpretação que interpretará o pattern e confrontará-o contra a string passada para ele. Assim como a classe Pattern, Matcher não define um construtor público. Você obtem um objeto do tipo Matcher invocando o método matcher do objeto Pattern.

c) PatternSyntaxException: Esta é facinha né! :) Nada mais é do que uma exceção que ocorre quando se tem um erro na sintaxe do pattern.

Vamos ao primeiro exemplo: uma classe que procure por ocorrências de um conjunto de caracteres (uma string) em uma sentença qualquer.

ExemploExpressoesRegulares.java

import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class ExemploExpressoesRegulares {
public static void main(String[] args) {
        System.out.println("--- Teste Com Expressões Regulares ---");

        String sentenca = "Post sobre Expressões Regulares (java.util.regex API).";

        String expressao = "Post";

        Pattern regra = Pattern.compile(expressao);

        Matcher confrontador = regra.matcher(sentenca);

        while(confrontador.find()) {
            System.out.printf("Encontrado \"%s\" de %d a %d.%n",
                                           confrontador.group(),
                                           confrontador.start(),
                                            confrontador.end());
        }

        System.out.println("--------------------------------------");
    }
}

Saída:

--- Teste Com Expressões Regulares ---
Encontrado "Post" de 0 a 4.
--------------------------------------

Vamos ver o que fizemos em detalhes... ou em partes, como diria Jack, o estripador :D.
    1)Importamos os dois objetos que precisamos: Pattern e Matcher;
    2)Criamos duas strings, uma com uma sentença qualquer e uma com a nossa expressão a ser pesquisada;
    3)Depois criamos um Pattern com a expressão e um Matcher à partir do Patter passando como parâmetro a nossa sentença;
    4)Em um laço (enquanto forem encontradas ocorrências da nossa regra) imprimimos onde encontramos na sentença a nossa expressão (os índices). Notem que por convenção o índice das ocorrências sempre englobam mais uma posição (coisas da Sun). Faça o teste com a seguinte sentença postpostpost e na expressão use post:

    012345678901 índices
    postpostpost sentença

    Saída:

    --- Teste Com Expressões Regulares ---
    Encontrado "post" de 0 a 4. ??? Aaaaaaaaaaaaaah!
    Encontrado "post" de 4 a 8. ??? Aaaaaaaaaaaaaah!
    Encontrado "post" de 8 a 12. ??? Aaaaaaaaaaaaaah!
    --------------------------------------

Note que o índice de início da ocorrência seguinte é sempre o índice final da ocorrência anterior.

A API também da suporte uma série de caracteres especiais que alteram a forma como são coincididas as sentenças com a expressão.
Vamos testar isso usando agora a sentença: Eu tenho dois carros que trato com carinho e na expressão: carro. Resultado:

--- Teste Com Expressões Regulares ---
Encontrado "carro" de 14 a 19.
--------------------------------------

Agora usaremos de um dos n caracteres especiais suportados, o ponto final (.) em nossa expressão: car. (com ponto final). Resultado:

--- Teste Com Expressões Regulares ---
Encontrado "car." de 14 a 18.
Encontrado "car." de 35 a 39.
--------------------------------------

Note a diferença no resultado. O resultado é referente a carr e cari e não a car apenas (se fosse os índices seriam de 14 a 18 e de 35 a 38!). Para confirmar isso vamos mudar nosso programa. Onde usamos o comando printf vamos colocar ao invés de expressao o comando confrontador.group():

System.out.printf("Encontrado \"%s\" de %d a %d.%n",
                               confrontador.group(),
                               confrontador.start(),
                                confrontador.end());

Agora rodemos novamente o programa e a saída será:

--- Teste Com Expressões Regulares ---
Encontrado "carr" de 14 a 18.
Encontrado "cari" de 35 a 39.
--------------------------------------

Esses caracteres especiais como o ponto final (.) são chamados de meta caracteres – são caracteres com um significado especial para o interpretador.
Eis todos suportados pela API: ([{\^-$|]})?*+..
Alguns desses não podem ser usados sozinhos, pois devem formar uma parte da expressão (visto mais além). Também notem que !, @ e # nunca são meta caracteres.

Meta caracteres podem ser usados para formar-se conjuntos ao qual a sentença deva atender como regra geral.
Por exemplo, poderíamos ver se a sentença contem qualquer palavra com o prefixo pedr:
Expressão: pedr.*
Sentença: pedreiro Ok
Sentença: pedra Ok
Sentença: pedreira Ok
Sentença: pedal Nok

Agora você pergunta, ta mas eu tenho que criar minha regra sempre em maiúsculo e depois em minúsculo?
Claro que não, modificaremos nossa classe novamente:

Pattern regra = Pattern.compile(expressao, Pattern.CASE_INSENSITIVE);

Pronto, se agora passarmos: Pedra, PEDRA, PeDreiRa, etc... irá funcionar!

Com os meta caracteres você pode criar regras mais complexas, unir certas condições, especificas faixas de valor/ocorrências, etc... vejamos:

[a-c] Caracteres entre a e c (inclusive) ou [a-cA-C].
[a-c[e-z]] Qualquer caractere que não seja d.
[a-f&&[cd]] Somente c e d.
[^a-c] Qualquer caracteres que não esteja entre a e c.
[a-f&&[^cd]] Qualquer caracter entre a e f menos c e d.

Assim por diante...

Ainda há com verificar se há dígitos (\\d), caracteres que não sejam dígitos (\\D – o mesmo que [^0-9]) , etc... veja a lista completa na documentação da API.

Agora, na finaleira vamos ver se uma sentença tem apenas números. Para isso mudaremos nossa classe novamente. Onde tem o while vai ter o seguinte if:

if(confrontador.matches()) {
    System.out.printf("Entrada \"%s\" está conforme o padrão %s.%n",
                                                           sentenca,
                                                         expressao);
}
else {
    System.out.printf("Entrada \"%s\" não está conforme o padrão %s.%n",
                                                               sentenca,
                                                             expressao);
}

Agora a nossa expressão fica: \\d* e a nossa sentença: 01234 . Rode o exemplo, depois mude a sentença e assim por diante.

Bom o assunto é muito mais extenso que esse meu post, espero ter aguçado a curiosidade de vocês e que procurem na documentação da API mais informações assim como no Java Tutorial, etc...

Até a próxima pessoal.

Gostou? Não gostou? Achou algum erro? Comenta aí, não leva nem um minuto e me ajudará muito! :D

Aqui tem o post em pdf, para facilitar as coisas :P


http://geocities.yahoo.com.br/sadbuttruebr/blog/
posts_pdf/expressoes_regulares.pdf

Um comentário:

italo disse...

gostei muito! mas infelizmente oq eu procurava naum encontrei, se puder me ajudar:

tenhu q fazer um mini compilador java e estou com dificuldade em gerar uma expressao regular que aceite os identificadores em java, nomes de variaveis.

as regras sao:
naum ter caracter especial
naum comecar com numero
pode ter letra minuscula ou maiuscula

ate ai td bem mas o problema e q naum consigo colocar o caracter "_" sobre-traço na expressao.

segue a expressao:
Pattern ident = Pattern.compile("[a-zA-Z]++[0-9]*+");