C# - Regex : Localizando uma ocorrência específica


Hoje veremos como usar expressões regulares para localizar uma ocorrência específica em uma string.

Uma expressão regular ou regex para simplificar, é um modelo que descreve uma certa quantidade de texto. A expressão regular mais simples consiste de um único caractere. Ex: a.

Este padrão irá coincidir com a primeira ocorrência do caractere em uma string. Se a string for "Isto é apenas um teste"  a primeira ocorrência será a letra a antes da letra p (de apenas).

A classe Regex representa o mecanismo para tratar as expressões regulares na plataforma .NET . Ela pode ser usado para analisar rapidamente grandes quantidades de texto para encontrar padrões de caracteres específicos; para extrair, editar, substituir ou excluir substrings de texto, ou para adicionar as strings extraídas a uma coleção para gerar um relatório.

Para usar as expressões regulares, definimos o padrão desejado para identificar em um fluxo de texto usando a sintaxe documentada nos Elementos de linguagem das expressões regulares. A seguir instanciamos um objeto Regex. Finalmente, executamos alguma operação, como a substituição de texto que corresponda ao padrão de expressão regular, ou a identificação de um padrão coincidente.

Cenário:

Você precisa encontrar uma ocorrência específica de uma correspondência dentro de uma string. Por exemplo, você deseja encontrar a terceira ocorrência de uma palavra ou a segunda ocorrência de um número de um documento como CPF, RG, etc.

Você também pode precisar encontrar cada terceira ocorrência de uma palavra em uma string.

Para concluir você tem que retornar o número de ocorrências de um padrão em um texto.

Usando Regex

Vamos criar um projeto Console (.NET 5.0) chamado CShp_Regex1.

Uma abordagem bem simples e elegante é criar métodos de extensões para string usando a classe Regex.

Crie a classe ExtensionMethods no projeto e a seguir inclua o código abaixo:

using System;
using System.Collections.Generic;
using System.Text.RegularExpressions;

namespace CShp_Regex1
{
    public static class ExtensionMethods
    {
        public static Match LocalizaOcorrencia(this string fonte, string padrao, int ocorrencia)
        {
            if (ocorrencia < 1)
            {
                throw (new ArgumentException("Não pode ser menor que 1", nameof(ocorrencia)));
            }
            // torna ocorrencia zero-based.
            --ocorrencia;
            // Executa o regex uma vez na fonte
            Regex RE = new Regex(padrao, RegexOptions.Multiline);

            MatchCollection correspondencias = RE.Matches(fonte);

            if (ocorrencia >= correspondencias.Count)
            {
                return (null);
            }
            else
            {
                return (correspondencias[ocorrencia]);
            }
        }

        public static List<Match> LocalizaCadaOcorrencia(this string fonte, string padrao, int ocorrencia)
        {
            if (ocorrencia < 1)
            {
                throw (new ArgumentException("Não pode ser menor que 1", nameof(ocorrencia)));
            }

            List<Match> ocorrencias = new List<Match>();
           

            Regex RE = new Regex(padrao, RegexOptions.Multiline);

            MatchCollection correspondencias = RE.Matches(fonte);

            for (int index = (ocorrencia - 1); index < correspondencias.Count; index++)
            {
                ocorrencias.Add(correspondencias[index]);
            }
            return (ocorrencias);
        }

        public static int NumeroOcorrencias(this string fonte, string padrao)
        {
            List<Match> ocorrencias = new List<Match>();

            Regex RE = new Regex(padrao, RegexOptions.Multiline);

            MatchCollection correspondencias = RE.Matches(fonte);

            if (correspondencias.Count > 0 )
            {
                return correspondencias.Count;
            }
            else
            {
                return 0;
            }
        }
    }
}

Criamos três métodos de extensão :

  1. LocalizaOcorrencia(this string fonte, string padrao, int ocorrencia)
  2. LocalizaCadaOcorrencia(this string fonte, string padrao, int ocorrencia)
  3. NumeroOcorrencias(this string fonte, string padrao)

O primeiro método retorna uma ocorrência particular de uma correspondência da expressão regular. A ocorrência que você deseja encontrar é passada para este método por meio do parâmetro de ocorrencia.

Se a ocorrência particular da correspondência não existir, um valor null será é retornado. Por isso, você deve verificar se o objeto retornado deste método é não nulo antes de usar esse objeto. Se a ocorrência existir, o objeto Match que contém as informações de correspondência para que a ocorrência seja retornado.

O segundo método funciona de forma semelhante ao primeiro exceto que continua a encontrar uma ocorrência particular de um
correspondência de expressão regular até que o final da string seja alcançado e retorna uma lista das ocorrências encontradas (List<Match>).

O terceiro vai retornar a quantidade de ocorrências do padrão no texto.

O método Regex.Matches procura em um texto por todas as ocorrências de uma expressão regular e retorna as ocorrências.

A classe MatchCollection representa o conjunto ou coleção de ocorrências bem sucedidas encontradas pela aplicação interativa de um expressão regular em um texto. A coleção é imutável (somente-leitura) contendo zero ou mais objetos Match.

Se a consulta for feita com sucesso a coleção será preenchida com um objeto Match para cada correspondência encontrada no texto. Se a consulta falhar a coleção não irá conter objetos Match e a propriedade Count será igual a zero.

A enumeração RegexOptions fornece valores enumerados para usar para definir opções de expressões regulares.  O modo Multiline altera o significado de ^ e $ para que eles correspondam ao início e final, respectivamente, de qualquer linha, e não apenas  o início e o fim de toda a cadeia.

Testando a solução

Para testar a solução vamos definir o código abaixo no método Main da classe Program:

using System;
using System.Collections.Generic;
using System.IO;
using System.Text.RegularExpressions;
using CShp_Regex1;

            Console.WriteLine("Usando Regex para localizar ocorrências em um texto\n");
            Console.WriteLine("Fonte : Poema de Fernando Pessoa : Poema em linha reta\n");

            Console.ReadLine();
            string arquivo = @"C:\dados\txt\PoemaLinhaReta.txt";
            string poema = File.ReadAllText(arquivo);

            Console.WriteLine("Localizando o texto 'vezes' na terceira ocorrência\n");

            Match resultado = poema.LocalizaOcorrencia("vezes", 3);
            int numOcorrencias1 = poema.NumeroOcorrencias("vezes");

            Console.WriteLine($"Foram encontradas {numOcorrencias1} ocorrências de 'vezes \n");
            Console.WriteLine($"A ocorrência 3 foi encontrada na posicao : {resultado?.Index}\n");
            Console.WriteLine();

            Console.WriteLine("Localizando o texto 'sido' a partir da terceira ocorrência\n");

            int numOcorrencias2 = poema.NumeroOcorrencias("sido");
            List<Match> resultados = poema.LocalizaCadaOcorrencia("sido", 4);

            Console.WriteLine($"Foram encontradas {numOcorrencias2} ocorrências de 'sido' \n");
            Console.WriteLine("As ocorrências foram encontradas nas posições :");

            foreach (Match m in resultados)
                Console.WriteLine($"\t{m.Index}");

            Console.ReadLine();

No código acima estou usando o novo recurso Top Level Statements do C# 9.0.

Estamos acessando o arquivo texto e lendo o arquivo na memória. A seguir estamos usando cada um dos métodos de extensão para localizar textos em uma string e exibindo o resultado.

Executando o projeto teremos o seguinte resultado:

Vamos agora substituir o código usado no exemplo anterior pelo código abaixo:

Pegue o código do projeto aqui:  CShp_Regex1.zip

"Brame o mar e a sua plenitude; o mundo, e os que nele habitam.
Os rios batam as palmas; regozijem-se também as montanhas,
Perante a face do Senhor, porque vem a julgar a terra; com justiça julgará o mundo, e o povo com eqüidade."
Salmos 98:7-9

Referências:


José Carlos Macoratti