Usando expressões regulares em C 

O presente artigo procura explicar o uso de expressões regulares em conjunto com a linguagem C, desmistificando funções e ensinando a extrair o máximo delas. Adianto que a leitura não é recomendado para elite programmers ou iniciantes, mas pessoas com conhecimento prévio em expressões regulares.

Definir regras e encontrar padrões demandam códigos extensos e de difícil manutenção. As expressões regulares aparecem é nesse cenário, facilitando e transformando o raciocínio lógico do programador em uma composição de símbolos recheada de significados.

É normal que programadores novatos ou pessoas que tiveram os primeiros contatos com expressões regulares em Perl, Python ou até mesmo PHP, sejam assombradas quando escultam o termo regex e C na mesma frase. Ocorre que as linguagens destacas anteriormente, com exceção de C, além de serem interpretadas, nasceram prontas para trabalhar com o melhor que concerne a expressões regulares.

 

O PRIMEIRO CÓDIGO

O código ulterior compila a expressão e testa se a string casa com um padrão.

#include <stdio.h>
#include <regex.h>

/*
 *  Written by: fm4lloc <fm4lloc@gmail.com>
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 */

int main ()
{
	regex_t preg;
	reg_errcode_t err;
	const char regex[]  = "^(([^:/?#]+):)?"       \
						  "(//([^/?#]*))?"        \
						  "([^?#]*)(\\?([^#]*))?" \
						  "(#(.*))?"; 

	const char string[]	= "https://fm4lloc.wordpress.com/";

	if ( ( err = regcomp (&preg, regex, REG_EXTENDED|REG_NOSUB) ) != REG_NOERROR)
		return err;

	if ( (err = regexec (&preg, string, 0 , NULL, 0 ) ) != REG_NOERROR )
		printf ("No match!");
	else
		printf ("Match!");

	regfree (&preg);
	return 0;
}

A função regcomp () compila a expressão.
Esta é a declaração de regcomp ():

int regcomp(regex_t *preg, const char *regex, int cflags);

*preg é um ponteiro para pattern buffer, ele aponta para o bloco de memória contento o padrão de pesquisa preparado por regcomp () através dos parâmetros fornecidos.

*regex aponta para um vetor de char contento a expressão regular, enquanto cflags é utilizado para determinar o tipo de compilação através de flags.

As flags são:

REG_EXTENDED Usa o padrão POSIX Expressão Regulares Estendidas na interpretação da regex. Quando ela é usada os metacaracteres não precisam ser escapados com a barra invertida (\) para serem identificados. O escape é usado apenas para transformar metacaractere em literal.
O uso da flag permite isso:

^(a.*)$

Quando omitida, a expressão é tratada através do padrão POSIX Expressões Regulares Básicas. Esse padrão necessita do uso de barras invertidas para a identificação dos metacaracteres.

^\\(a.*\\)$

Recomendo estes links para aprofundar no tema:
Link1
Link2

REG_ICASE Não diferencia letras maiúsculas de minúsculas.
REG_NOSUB Ignora suporte aos parâmetros nmatch e pmatch usados pela função regexec().Esta flag é fixada quando se deseja apenas testar a entrada de dados com um padrão sem coletar informações adicionais.
REG_NEWLINE Trata a entrada de dados com várias linhas individualmente.
Exemplo:

char string[] = “Olá\nMundo”;

const char regex[] = "^O(.*)$";

Como a entrada possui quebra de linha, a palavra “Olá” e “Mundo” são tratadas individualmente.

Se omitida, a entrada será tratada como um todo, resultando em “Olá\\nMundo”.

Voltando a explicação do código, a função regexec() avalia e busca os dados correspondentes a expressão.

A declaração é:

int regexec(const regex_t *preg, const char *string,
		size_t nmatch, regmatch_t pmatch[], int eflags);
*preg Ponteiro para pattern buffer.
*string Ponteiro para os dados de entrada.
nmatch Número de matchs, isto é, quantidade casada.
*pmach[] Ponteiro para um array de regmatch_t.
eflags
REG_NOTBOL Ignora o metacaractere circunflexo ‘^’ (inicio de linha).
REG_NOTEOL Ignora o metacaractere cifrão ‘$’ (final da linha)

A estrutura de *pmach[] guarda em seus elementos informações sobre o casamento.

A declaração de regmatch_t em regex.h é:

typedef struct
{
	/* Posição do primeiro byte da sub-string casada
	 */
	regoff_t rm_so;

	/* Posição do último byte da sub-string casada
	 */
	regoff_t rm_eo;
} regmatch_t;

A última função presente no código em analise é regfree (), que liberar o pattern buffer.

Declaração:

void regfree(regex_t *preg);

 

EXIBINDO AS SUB-STRINGS ENCONTRADAS

Leia os comentários para entender o código, recomento que compile e faça alterações se achar valido.

#include <stdio.h>
#include <stdlib.h>
#include <regex.h>

/*
 *  Written by: fm4lloc <fm4lloc@gmail.com>
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 3 of the License, or
 *  (at your option) any later version.
 *
 */

int main ()
{
	regex_t preg;
	reg_errcode_t err;
	regmatch_t *pmatch = NULL;
	size_t nmatch, i;

	const char regex[]  =	"^(([^:/?#]+):)?"       \
							"(//([^/?#]*))?"        \
							"([^?#]*)(\\?([^#]*))?" \
							"(#(.*))?";

	const char string[]	= "https://fm4lloc.wordpress.com/";

	if( ( err = regcomp (&preg, regex, REG_EXTENDED) ) != REG_NOERROR)
		return err;

	/*
	 * Pega o número de matchs e aloca uma lista de regmatch_t.
	 * ======================================================================
	 * Soma-se 1 para obter a quantidade total de matchs, pois a
	 * contagem inicia em zero.
	 *
	 * Os elementos contidos em pmatch[0] guardam a posição do primeiro
	 * e último byte da string, os demais registros correspondem
	 * aos grupos representados pelo metacaratere (parênteses).
	 *
	 * Cada sub-string é capturada por um grupo.
	 */
	nmatch = preg.re_nsub + 1;

	/* aloca pmatch */
	pmatch = (regmatch_t *) malloc (sizeof(regmatch_t) * nmatch);

	if ( (err = regexec (&preg, string, nmatch , pmatch, 0 ) ) != REG_NOERROR )
	{
		free (pmatch);
		return err;
	}

	/* mostra as sub-strings */
	for(i = 0; i < nmatch; i++)
	{
		printf ("pmatch[%ld] (%d : %d)\t=> %.*s\n",
			i,
			pmatch[i].rm_so,pmatch[i].rm_eo,
			pmatch[i].rm_eo - pmatch[i].rm_so,
			string + pmatch[i].rm_so);
	}

	free (pmatch);
	regfree (&preg);
	return 0;
}

Retorno:

pmatch[0] (0 : 29) => https://fm4lloc.wordpress.com/
pmatch[1] (0 : 5) => http:
pmatch[2] (0 : 4) => http
pmatch[3] (5 : 28) => //fm4lloc.wordpress.com
pmatch[4] (7 : 28) => fm4lloc.wordpress.com
pmatch[5] (28 : 29) => /
pmatch[6] (-1 : -1) =>
pmatch[7] (-1 : -1) =>
pmatch[8] (-1 : -1) =>
pmatch[9] (-1 : -1) =>

As sub-strings inexistentes recebem (-1, -1).

Grupos:

^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?
_12____________3__4__________5_______6__7________8_9____

 

ENCONTRANDO NA STRING TODAS AS OCORRÊNCIAS DO PADRÃO.

No começo do artigo comentei que Perl nasceu pronto para trabalhar com o melhor que concerne a expressões regulares, não mentei, vou provar no próximo código.

Para encontrar todas as ocorrências do padrão em Perl:

#!/usr/bin/perl

use warnings;
use strict;

my $string	=	"1991/04;".
				"1992/12;".
				"1992/05;".
				"1994/03;".
				"1995/09;";

for ($string =~ m/([0-9]+)\/([0-9]+)/g)
{
	print "$_\n";
}
Saída
1991
04
1992
12
1992
05
1994
03
1995
09

Tudo é simplificado, bastou o uso da opção “g” ao final da expressão. Em C as coisas ficam um pouco mais complexas.

A mesma ideia em C:

#include <stdio.h>
#include <stdlib.h>
#include <regex.h>
#include <string.h>
/*
 *  Written by: fm4lloc <fm4lloc@gmail.com>
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 */

int main ()
{
	regex_t preg;
	reg_errcode_t err;
	regmatch_t *pmatch = NULL;
	size_t	offset = 0, nmatch, i, j;

	const char regex[]  =	"([0-9]+)/([0-9]+)";

	const char string[]	=	"1991/04;" \
							"1992/12;" \
							"1992/05;" \
							"1994/03;" \
							"1995/09;";

	if( ( err = regcomp (&preg, regex, REG_EXTENDED) ) != REG_NOERROR)
		return err;

	nmatch = preg.re_nsub + 1;
	/*
	 * Define o modo de impressão das sub-strings.
	 * Só imprime sub-strings do índice zero se não
	 * existir grupos.
	 */
	if (nmatch == 1)
		j = 0;
	else
		j = 1;

	pmatch = (regmatch_t *) malloc (sizeof(regmatch_t) * nmatch);

	/* Tenta casar enquanto não for retornado erro.
	 *
	 * O endereço de string é incrementado com offset para evitar
	 * buscas no mesmo trecho.
	 */
	while ((err = regexec (&preg, string + offset,
		nmatch ,pmatch, 0 )) == REG_NOERROR)
	{
		/* mostra as sub-strings.
		 */
		for(i = j; i < nmatch; i++)
			printf ("%.*s\n", pmatch[i].rm_eo - pmatch[i].rm_so,
				string + pmatch[i].rm_so + offset);

		/* offset: variável auxiliar – usada para pular fragmentos já
		 * analisados.
		 *
		 * Ela recebe o valor referente a posição
		 * do último byte do pedaço casado anteriormente.
		 */
		if (pmatch[0].rm_eo)
			offset += pmatch[0].rm_eo;
		else
			break;
	}

	free (pmatch);
	regfree (&preg);

	return 0;
}

Os códigos escritos no artigo são feitos para facilitar o entendimento, podem ser otimizados ao extremo.

Exemplo com outras expressões e strings:

Exemplo 1:

Regex “^([0-9]+)/([0-9]+);$”
String “1991/04;\n” \
“1992/12;\n” \
“1992/05;\n” \
“1994/03;\n” \
“1995/09;\n”;
regcomp() flags REG_EXTENDED | REG_NEWLINE
Saída 1991
04
1992
12
1992
05
1994
03
1995
09

Exemplo 2:

regex “^[0-9]+/[0-9]+;”
string “1991/04;” \
“1992/12;” \
“1992/05;” \
“1994/03;” \
“1995/09;”;
regcomp() flags REG_EXTENDED
Saída 1991/04;
1992/12;
1992/05;
1994/03;
1995/09;

Exemplo 3:

regex “^[0-9]+/[0-9]+;”
string “1991/04;\n” \
“1992/12;\n” \
“1992/05;\n” \
“1994/03;\n” \
“1995/09;\n”;
regcomp() flags REG_EXTENDED
Saída 1991/04;\n

 

EXIBINDO ERROS

As funções regcomp () e regexec () retornam um inteiro indicando o ocorrido ao final de cada execução, o retorno é chamado de error code e é declarado no header regex.h assim:

typedef enum
{
#if defined _XOPEN_SOURCE || defined __USE_XOPEN2K
  REG_ENOSYS = -1,	/* This will never happen for this implementation.  */
#endif

  REG_NOERROR = 0,	/* Success.  */
  REG_NOMATCH,		/* Didn't find a match (for regexec).  */

  /* POSIX regcomp return error codes.  (In the order listed in the
     standard.)  */
  REG_BADPAT,		/* Invalid pattern.  */
  REG_ECOLLATE,		/* Inalid collating element.  */
  REG_ECTYPE,		/* Invalid character class name.  */
  REG_EESCAPE,		/* Trailing backslash.  */
  REG_ESUBREG,		/* Invalid back reference.  */
  REG_EBRACK,		/* Unmatched left bracket.  */
  REG_EPAREN,		/* Parenthesis imbalance.  */
  REG_EBRACE,		/* Unmatched \{.  */
  REG_BADBR,		/* Invalid contents of \{\}.  */
  REG_ERANGE,		/* Invalid range end.  */
  REG_ESPACE,		/* Ran out of memory.  */
  REG_BADRPT,		/* No preceding re for repetition op.  */

  /* Error codes we've added.  */
  REG_EEND,		/* Premature end.  */
  REG_ESIZE,		/* Compiled pattern bigger than 2^16 bytes.  */
  REG_ERPAREN		/* Unmatched ) or \); not returned from regcomp.  */
} reg_errcode_t;

Pequena rotina que exibe a mensagem correspondente ao error code.

Observe, regerror () aparece duas vezes na rotina, na primeira ela é chamada para determinar o comprimento da mensagem, enquanto na segunda é invocada com o intuito de copiar a mensagem para *errbuff.

/*
 *  Written by: fm4lloc <fm4lloc@gmail.com>
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 3 of the License, or
 *  (at your option) any later version.
 *
 */

void c_regerror (reg_errcode_t err, const regex_t *preg)
{
	size_t errbuf_size;
	char *errbuf = NULL;

	errbuf_size  = regerror (err, preg, NULL, 0);
	errbuf = (char *) calloc (errbuf_size, sizeof(char));

	regerror (err, preg, errbuf, errbuf_size);

	fprintf (stderr, "%s\n", errbuf);
	free (errbuf);
}

Declaração de regerror ():

size_t regerror(int errcode, const regex_t *preg, char *errbuf,
                       size_t errbuf_size);
errcode O error code retornado por regcomp () ou regexec ()
*preg Aponta para o pattern buffer
*errbuf Ponteiro para a mensagem de erro
errbuf_size Número de bytes que serão copiados para *errbuff
Anúncios