Desenvolvimento - C#

Expressões Regulares em C# - Resolvendo problemas de identificação de textos

Veja neste artigo as Expressões Regulares em C# e como resolver problemas de identificação de textos.

por Marcio Paulo Mello Martins



Introdução


A maioria de nós, desenvolvedores, já encontramos situações onde precisamos extrair dados e informações de trechos de textos dispostos em algum tipo de padrão.
Neste artigo vou demonstrar a utilização de Expressões Regulares em C# para facilitar a vida de quem trabalha com strings que contêm dados a serem extraídos.

Um caso comum - String posicional


Quem trabalha ou já trabalhou com Mainframes sabe que programas de alta plataforma geralmente trabalham com dados em formato texto - as chamadas “strings posicionais”. Elas recebem este nome devido à sua característica: armazenam dados em posições definidas. Por exemplo:

A056BJOSE DA SILVA 1409197858162860568

Esta é uma string posicional que contém dados. Olhando em um primeiro momento, tudo parece meio sem sentido: uma sopa de letrinhas com um nome no meio. Mas se aplicarmos as regras de posição desta string posicional, obtemos os seguintes dados:

A056B - Identificação da string (5 caracteres iniciando da posição 1)
JOSE DA SILVA - Nome do consultado (30 posições iniciando da posição 6)
14091978 - Data de nascimento do consultado (8 posições iniciando da posição 36, formato ddmmaaaa)
58162860568 - CPF do consultado (11 posições iniciando da posição 44)
Imagine que você recebeu a missão de criar um Parse para esta string, ou seja, uma classe que recebe esta string posicional e preenche as propriedades desta classe baseada nos dados da string. Como você faria para ler estes dados? Provavelmente, usaria o método Substring da classe String, não é mesmo?

Listagem 1 - Classe Posicional, que contém as propriedades da string posicional.

public class Posicional
{
    public string ID { get; set; }
    public string Nome { get; set; }
    public DateTime DataNascimento { get; set; }
    public string Cpf { get; set; }
}

A implementação do método Parse ficaria provavelmente assim:
Listagem 2 - Método Parse com Substring
public void ParseSubstring(string stringPosicional)
{
    ID = stringPosicional.Substring(0, 5);
    Nome = stringPosicional.Substring(5, 30);
    DataNascimento = DateTime.ParseExact(stringPosicional.Substring(35, 8), 
                                         "ddMMyyyy", new CultureInfo("pt-BR"));
    Cpf = stringPosicional.Substring(43, 11);
}

Esta implementação funciona muito bem. Porém, existe uma outra forma de fazer esta extração de dados de forma mais eficiente, utilizando as classes Regex e Match para comparar o texto e extrair os dados utilizando uma expressão regular :
Listagem 3 - Método Parse com Expressão Regular
public void ParseExpressaoRegular(string stringPosicional)
{
    Regex regex = new Regex(@"(?<ID>[A-Z\d]{5})(?<Nome>.{30})(?<DataNascimento>\d{8})(?<Cpf>\d{11})");

    Match match = regex.Match(stringPosicional);

    if (match.Success)
    {
        ID = match.Groups["ID"].Value;
        Nome = match.Groups["Nome"].Value;
        DataNascimento = DateTime.ParseExact(match.Groups["DataNascimento"].Value, "ddMMyyyy", new CultureInfo("pt-BR"));
        Cpf = match.Groups["Cpf"].Value;
    }
}

Vamos comparar as duas aproximações para o problema. Na primeira, usamos Substring quatro vezes para retirar trechos do texto, passando os índices iniciais e o tamanho de cada trecho.
Já no segundo caso, utilizamos uma única expressão regular para obter trechos da string. Veja como está transcrita esta expressão regular:

(?[A-Z\d]{5})(?.{30})(?\d{8})(?\d{11})

Quem recebe esta expressão regular é o construtor da classe Regex, cuja instância possui um método Match que recebe a string a ser analisada (no caso, a string posicional) e que devolve um objeto da classe Match que por sua vez contém os dados desta análise, à qual chamamos de “combinação”.

Uma das vantagens é que podemos nomear os trechos (chamados de Groups) que desejamos extrair da string posicional. Além do mais, podemos realizar a validação de dados específicos. Por exemplo, a data de nascimento deve estar no formato ddmmaaaa (\d{8}), o nome do consultado pode conter quaisquer caracteres por 30 posições (.{30}), a assim por diante.

Analisando a fórmula utilizada, vamos separar seus elementos para entender melhor seus componentes. Se separarmos nossa expressão regular pelos identificadores, teremos quatro partes distintas, uma para cada propriedade da string posicional:

· (?[A-Z\d]{5}) - Identifica o grupo “ID”, que pode conter caracteres de A a Z, e números, por cinco posições;
o (?) - Identificação do grupo;
o [ ] - Identificação da sequência a ser repetida;
o {5} - Quantidade de vezes que a sequência deverá ser repetida;
o A-Z - Representa um caractere entre as letras A e Z, inclusive;
o \d - Representa um caractere numérico (também pode ser representado na forma 0-9);
· (?.{30}) - Identifica o grupo “Nome”, que pode conter quaisquer caracteres por 30 posições;
o (? ) - Identificação do grupo;
o . - Representa qualquer caractere (excluídos os pulos de linha e tabulações);
o {30} - Quantidade de vezes que a sequência deverá ser repetida;
· (?\d{8}) - Identifica o grupo “DataNascimento”, que pode conter 8 caracteres numéricos;
o (? ) - Identificação do grupo;
o \d - Representa um caractere numérico (também pode ser representado na forma 0-9);
o {8} - Quantidade de vezes que a sequência deverá ser repetida;
· (?\d{11}) - Identifica o grupo “Cpf”, que pode conter 11 caracteres numéricos;
o (? ) - Identificação do grupo;
o \d - Representa um caractere numérico (também pode ser representado na forma 0-9);
o {11} - Quantidade de vezes que a sequência deverá ser repetida.

Podemos perceber que existe uma validação bem consistente na forma de obter os dados em uma string através de expressões regulares.
Porém, as expressões regulares oferecem uma flexibilidade muito maior em relação ao uso de Substring. Vamos supor que o campo nome não exija um número fixo de 30 posições, mas sim que ele possua um tamanho máximo de 50 posições. Neste caso, os espaços remanescentes podem ser omitidos. Veja:

A056BJOSE DA SILVA1409197858162860568 Neste caso específico, o Substring não funcionaria, pois não sabemos a medida exata que o campo Nome pode conter. Mas se modificarmos nossa expressão regular, podemos capturar os dados com a mesma facilidade de antes. Vejamos:

(?[A-Z\d]{5})(?.{0,50})(?\d{8})(?\d{11}) No trecho em destaque da expressão regular, alteramos a quantidade de vezes que a sequência deverá ser repetida. Veja que, ao invés de um único número, temos agora dois números separados por vírgula que indicam, respectivamente, a menor (zero) e a maior (cinquenta) quantidade de vezes que a sequência pode ser repetida. Neste caso, assim que a expressão regular encontrar um número, ela entende que este pode fazer parte do próximo grupo (“DataNascimento”) e já inicia a verificação, possibilitando assim que o nome possua de zero a cinquenta caracteres sem prejudicar a validação da string.

Assim sendo, com esta nova expressão regular, podemos usar a string posicional proposta acima que seus dados serão corretamente extraídos, o mesmo não acontecendo se utilizarmos o método Parse que se utiliza de Substring.

Podemos também alterar o método para que dispare uma exceção caso a string posicional de entrada não esteja de acordo com as regras de validação:

Listagem 4 - Método Parse com Expressão Regular e tratamento de formato public void ParseExpressaoRegular(string stringPosicional) { Regex regex = new Regex(@"(?[A-Z\d]{5})(?.{0,50})(?\d{8})(?\d{11})"); Match match = regex.Match(stringPosicional); if (match.Success) { ID = match.Groups["ID"].Value; Nome = match.Groups["Nome"].Value; DataNascimento = DateTime.ParseExact(match.Groups["DataNascimento"].Value, "ddMMyyyy", new CultureInfo("pt-BR")); Cpf = match.Groups["Cpf"].Value; } else { throw new FormatException("A string posicional não está corretamente formatada."); } } A propriedade Success do objeto da classe Match indica se a string de estrada combinou corretamente com a expressão regular, possibilitando a verificação da string para um correto tratamento da execução do programa. Caso Success seja false, podemos disparar uma exceção informando sobre não conformidades da string posicional.

Conclusão


Nossa classe de exemplo pode ser codificada da seguinte forma:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Globalization;
using System.Text.RegularExpressions;

namespace ExpressoesRegulares
{
    public class Posicional
    {
        public string ID { get; set; }
        public string Nome { get; set; }
        public DateTime DataNascimento { get; set; }
        public string Cpf { get; set; }

        public void ParseSubstring(string stringPosicional)
        {
            ID = stringPosicional.Substring(0, 5);
            Nome = stringPosicional.Substring(5, 30);
            DataNascimento = DateTime.ParseExact(stringPosicional.Substring(35, 8), "ddMMyyyy", new CultureInfo("pt-BR"));
            Cpf = stringPosicional.Substring(43, 11);
        }

        public void ParseExpressaoRegular(string stringPosicional)
        {
            Regex regex = new Regex(@"(?<ID>[A-Z\d]{5})(?<Nome>.{0,50})(?<DataNascimento>\d{8})(?<Cpf>\d{11})");

            Match match = regex.Match(stringPosicional);

            if (match.Success)
            {
                ID = match.Groups["ID"].Value;
                Nome = match.Groups["Nome"].Value;
                DataNascimento = DateTime.ParseExact(match.Groups["DataNascimento"].Value, "ddMMyyyy", new CultureInfo("pt-BR"));
                Cpf = match.Groups["Cpf"].Value;
            }
            else
            {
                throw new FormatException("A string posicional não está corretamente formatada.");
            }
        }
    }
}

Expressões Regulares são um universo à parte no mundo da programação. Existem diversos livros e sites especializados no assunto, mas poucos abordam diretamente o assunto com foco específico nas classes do .NET Framework.
Vou procurar, a partir deste artigo, manter regularmente alguns exemplos sobre C# e Expressões Regulares, trazendo exemplos úteis para problemas do dia-a-dia.
Em breve estarei lançando um livro exclusivamente sobre Expressões Regulares e C#. Aguardem!
Marcio Paulo Mello Martins

Marcio Paulo Mello Martins - Bacharel em Ciência da Computação pela FASP; MCP, MCAD, MCSD, MCTS, MCPD e MCT. Atua há mais de 10 anos com desenvolvimento de software e treinamento em tecnologias Microsoft, trabalhando hoje como Analista Desenvolvedor na F|Camara (http://www.fcamara.com.br), além de ser proprietário da Logical Docs (http://www.logicaldocs.com.br), empresa do ramo de gerenciamento eletrônico de documentos. Quando sobra um tempinho, é pianista e toca em uma banda de Jazz.