Inicio > .NET Framework, C#, Cuestionario > Cuestionario de expresiones regulares: 1. manipulación desde .NET

Cuestionario de expresiones regulares: 1. manipulación desde .NET


Qué ondas gente, heme aquí de nuevo, listo para comenzar a bloguear, pero ya no a la una de la mañana. Ahora que ya han pasado los festejos por el día de las madres, es momento de volver a nuestro mundo de programación. En estos días estuve pensando en las expresiones regulares. ¿Cuánto código no se encuentra uno, que hace un string.Replace o string.IndexOf para buscar texto dentro de otras cadenas? ¡Cuando una expresión regular simplificaría mucho el código!

La verdad es que las expresiones regulares son un tema difícil. Son difíciles de crear pero más difíciles de leer (si no fuiste el creador). Es un lenguaje críptico y muy propenso a errores, por lo que hay que tener muchísimo cuidado al emplearlo: un mal uso y podemos hasta generar brechas de seguridad en nuestra aplicación…

El tema ciertamente es muy extenso y he visto que hasta hay libros enteros al respecto. Y la verdad es que intentar abarcar todo en una entrada pues no se puede. Más bien habría que hacer un tutorial, pero como ahorita ando con mi tutorial de LINQ, habré de posponerlo un poco.

Sin embargo, no sufran mis estimados colegas, porque he preparado un cuestionario, una guía de preguntas y respuestas sobre expresiones regulares, su uso en .NET y el meta-lenguaje que utiliza. Este cuestionario está, por razones de espacio y tiempo, dividido en dos: el primero trata sobre cómo manipular expresiones regulares desde .NET, mientras que el segundo trata sobre cómo formar los patrones de las expresiones. Sin más preámbulos, comencemos.

 

¿Qué es una expresión regular?

Una expresión regular es un conjunto de caracteres que definen un patrón, el cual puede ser comparado contra una cadena de texto cualquiera en aras de determinar si dicha cadena cumple con los requerimientos de formato. Si la cadena cumple, se puede extraer la porción de texto que concuerda en uno o varios grupos.

¿Cuáles son los elementos de una expresión?

Al texto que funge como patrón se le denomina expresión. Una expresión puede estar constituida por los siguientes elementos.

  • Caracteres de escape. Ciertos caracteres son especiales y tienen un significado concreto. Por ejemplo, \n representa una nueva línea y \t representa un tab.
  • Clases de caracteres. Representa un conjunto de caracteres, como letras, palabras, espacio en blanco e incluso rangos entre letras.
  • Grupos. Es posible agrupar  una expresión en componentes más pequeños, a los que incluso se les puede dar un nombre para referenciarlo posteriormente.
  • Cuantificadores. Especifican las instancias de algún elemento previo (caracteres, grupos, clases de caracteres) que deben estar presentes para que una cadena concuerde con el patrón.
  • Referencias previas. Es posible que una subexpresión ya evaluada pueda ser referenciada subsecuentemente.
  • Construcciones alternas. Permite construir expresiones que permitan acertar una o más posibilidades, de tal suerte que los valores permitidos sean fijos. Por ejemplo, se puede crear un patrón que limite los valores a (sí | no).
  • Substituciones. Una vez que se encuentra un valor que concuerda con el patrón, se puede reemplazar por alguna otra cadena.
  • Misceláneos. Permite añadir comentarios y opciones de pre-procesamiento.
  •  

    ¿Cómo puedo evaluar una expresión regular en .NET Framework?

    Dentro del espacio de nombres System.Text.RegularExpressions existe la clase Regex. Ésta es la que se encarga de hacer las evaluaciones. Para ello, crea una nueva instancia de esta clase y pásale como parámetro tu expresión regular. Luego, utiliza el método IsMatch para evaluar el patrón contra la cadena que se pase como parámetro. Por ejemplo:

    Regex regex = new Regex(@"^\d{5}$");
    bool v1 = regex.IsMatch("fernando");
    bool v2 = regex.IsMatch("99901");
    

    La expresión que pasamos representa una cadena de texto con exactamente cinco caracteres que sean dígitos. Así, v1 será falso y v2 será verdadero.

     

    ¿Cómo puedo extraer la cadena evaluada de una expresión regular?

    Lo primero que hay que hacer es crear la expresión regular. Luego, ejecutamos el método Match de nuestro objeto Regex. Éste método nos devolverá un objeto tipo Match. Si se encontró alguna correspondencia entre la expresión y la cadena a evaluar (pasada como parámetro a Match) entonces la propiedad Match.Success será verdadera, y Match.Value contendrá el primer extracto de la cadena evaluada que concuerda con la expresión regular. Por ejemplo:

    Regex regex = new Regex(@"\d{5}");
    Match match = regex.Match("fer12345nando99901gomez");
    if (match.Success)
    {
      string val = match.Value;
      Console.WriteLine(val);
    }
    

La expresión regular que le pasamos intenta ubicar cualquier secuencia de cinco dígitos. Así, en el ejemplo anterior tenemos que la cadena a evaluar contiene dos secuencias: 12345 y 99901. El objeto Match que obtenemos hace referencia a la primera secuencia encontrada, imprimiendo por ende “12345” en la consola.

 

Pero ¿y si la cadena evaluada tiene más de una concordancia?

El objeto Match que nos regresa Regex.Match contiene un método llamado NextMatch, que lo que hará es regresar otro objeto Match, el cual hará referencia a la siguiente concordancia. Así, podemos llamar a NextMatch hasta que nos devuelva un objeto cuya propiedad Success nos devuelva un false. El siguiente código hace esto mismo a través de un bucle for.

Regex regex = new Regex(@"\d{5}");
string eval = "fer12345nando99901gomez54321flores";
for (Match match = regex.Match(eval); match.Success; match = match.NextMatch())
{
  Console.WriteLine(match.Value);
}

Al ejecutar este código, se imprime en consola “12345 99901 54321”.

 

¿Existe alguna forma de obtener todas las concordancias de una sola vez?

Pues sí, la hay. En lugar de ejecutar el método Match, puedes ejecutar el método Matches, que te regresará un MatchCollection, sobre el cual puedes iterar (digamos, usando un foreach).

Regex regex = new Regex(@"\d{5}");
string input = "fer12345nando99901gomez54321flores";
MatchCollection matches = regex.Matches(input);
foreach (Match match in matches)
{
  Console.Write("{0} ", match.Value);
}

 

¿Qué es un grupo?

Un grupo permite delinear subexpresiones de una expresión regular y capturar las subcadenas de una cadena a evaluar. Los grupos se utilizan para:

  • Hacer que una subexpresión que se repite, concuerde.
  • Aplicar cuantificadores a subexpresiones que tienen múltiples elementos de expresiones regulares.
  • Poder asignar un nombre a alguna subexpresión, y referenciarla posteriormente.
  • Obtener subexpresiones indifivuales.

Una subexpresión  es cualquier patrón contenido dentro de unos paréntesis. Por ejemplo, si a \d{5} lo escribimos como (\d{5}), entonces tendríamos una subexpresión. A cada una de estas se le asigna un número automáticamente, y la clase Match contiene la propiedad Groups, que devuelve un GroupCollection, donde se encuentra cada una de las subexpresiones encontradas, así como sus respectivos valores. Adicionalmente, es posible darle un nombre explícito a un grupo, poniendo el patrón entre “?<” y “>”. Por ejemplo, al grupo (<?CincoDigitos>\d{5}) le hemos dado el nombre “CincoDigitos”.

Regex regex = new Regex(@"(?<CincoDigitos>\d{5})");
string input = "fer12345nando99901gomez54321flores";
MatchCollection matches = regex.Matches(input);
foreach (Match match in matches)
{
  Group group = match.Groups["CincoDigitos"];
  Console.WriteLine(group.Value);
}

 

¿Y para qué cuernos quiero crear y nombrar grupos?

Bueno, dada una expresión regular sencilla como las que hemos manejado, quizás no te interese mucho. Pero son muy útiles cuando tratas con expresiones más complejas. Imagínate que tienes que validar que una cadena de texto tenga un formato de correo electrónico. Entonces tu código luciría de forma semejante al siguiente.

string[] emails = { 
  "fer@e-people.com.mx", 
  "fernando.a.gomez.f@gmail.com", 
  "fer(arroba)algo.com", 
  "fernando.gomez@matematicas.net", 
  "fer@otrodominio"
};
string mailPattern =
  @"^(?("")("".+?""@)|(([0-9a-zA-Z]((\.(?!\.))|[-!#\$%&'\*\+/=\?\^`\{\}\|~\w])*)" +
  @"(?<=[0-9a-zA-Z])@))(?(\[)(\[(\d{1,3}\.){3}\d{1,3}\])|(([0-9a-zA-Z][-\w]*[0-9a-zA-Z]\.)" +
  @"+[a-zA-Z]{2,6}))$";

Regex regex = new Regex(mailPattern);
foreach (string email in emails)
{
  if (regex.IsMatch(email))
    Console.WriteLine("{0} es un correo válido. ", email);
  else
    Console.WriteLine("{0} es inválido. ", email);
}

Esa sí que es una expresión enorme e ilegible, y eso que es para algo que nos parecería trivial, como un correo electrónico (¿cuántas aplicaciones no buscan simplemente la arroba y el punto? *sigh*).

Este código funciona (al ejecutarlo, la tercera y quinta dirección aparecen como inválidas), pero ahora imagínate que tu jefe te pide que identifiques cuál es el dominio del correo y cuál es el nombre de usuario. Menudo lío tío, ¿qué le modificarías? Por supuesto, la mejor opción es darle un nombre al grupo que represente a ambos y sanseacabó…

string[] emails = { 
  "fer@e-people.com.mx", 
  "fernando.a.gomez.f@gmail.com", 
  "fer(arroba)algo.com", 
  "fernando.gomez@matematicas.net", 
  "fer@otrodominio"
};
string mailPattern =
  @"^(?("")("".+?""@)|((?<Usuario>[0-9a-zA-Z]((\.(?!\.))|[-!#\$%&'\*\+/=\?\^`\{\}\|~\w])*)" +
  @"(?<=[0-9a-zA-Z])@))(?<Dominio>(\[)(\[(\d{1,3}\.){3}\d{1,3}\])|(([0-9a-zA-Z][-\w]*[0-9a-zA-Z]\.)" +
  @"+[a-zA-Z]{2,6}))$";

Regex regex = new Regex(mailPattern);
foreach (string email in emails)
{
  Match match = regex.Match(email);
  if (match.Success)
  {
    Console.WriteLine("{0} es válido...", email);

    Group userGroup = match.Groups["Usuario"];
    Group domainGroup = match.Groups["Dominio"];

    Console.WriteLine("+ El usuario es {0}", userGroup.Value);
    Console.WriteLine("+ El dominio es {0}", domainGroup.Value);
  }
  else
  {
    Console.WriteLine("{0} es inválido. ", email);
  }
  Console.WriteLine();
}

Al ejecutar este código, se imprime lo siguiente en la consola.

fer@e-people.com.mx es válido...
+ El usuario es fer
+ El dominio es e-people.com.mx

fernando.a.gomez.f@gmail.com es válido...
+ El usuario es fernando.a.gomez.f
+ El dominio es gmail.com

fer(arroba)algo.com es inválido.

fernando.gomez@matematicas.net es válido...
+ El usuario es fernando.gomez
+ El dominio es matematicas.net

fer@otrodominio es inválido.

Mucho mejor, ¿no?

¿Qué tan eficiente es usar expresiones regulares?

Ah, una pregunta sensata. Las expresiones regulares, a final de cuentas, hacen un análisis sintáctico-semántico del patrón y la cadena a evaluar. Tras bambalinas, se genera un árbol de expresiones, y para efectos prácticos se comporta igual que un compilador, solo que en tiempo de ejecución. Te imaginarás que hacer lo anterior es, efectivamente, costo.

Cuando hacemos una validación sobre una sola cadena el tiempo es despreciable. Pero puede haber ocasiones en las que tengamos que evaluar muchas cadenas (por ejemplo, unas mil direcciones de correo electrónico). En estos casos sí que sentiríamos el tiempo empleado por la expresión regular.

Por suerte, contamos con un mecanismo para optimizar un poco el asunto. Uno de los constructores de la clase Regex nos permite especificar algunas opciones de comportamiento a través de la enumeración RegexOptions. Una de estas opciones es RegexOptions.Compiled. Si especificamos este valor, cuando se cree por primera vez el Regex se compilará a un ensamblado temporal, que será llamado para cada evaluación subsiguiente. Esto, por supuesto, hace que no se genere el árbol de expresiones, reduciendo el costo en recursos. Sin embargo, la primera vez que se evalúe tardará un poco más de lo normal, ya que tendrá que generar el ensamblado. Por eso, esta opción solo deberá emplearse en caso de que una expresión sea evaluada en un número considerable de ocasiones.

Un ejemplo a continuación.

Regex regex = new Regex(@"\d{5}", RegexOptions.Compiled);
string input = "fer12345nando99901gomez54321flores";
MatchCollection matches = regex.Matches(input);
foreach (Match match in matches)
{
  Console.Write("{0} ", match.Value);
}

 

Esta historia continuará…

Bueno, hemos llegado a la primera parte de este cuestionario. Hasta ahorita hemos visto como utilizar expresiones regulares, pero no hemos visto como crearlas. En la segunda parte de este cuestionario trataré cómo formar las expresiones básicas.

Pero ahora ya me tengo que poner a trabajar, así que nos vemos al rato o mañana. Auf wiedersehen!

Anuncios
Categorías:.NET Framework, C#, Cuestionario Etiquetas:
  1. Danny
    agosto 3, 2011 en 7:34 pm

    Que buen aporta man!!! te lo agradezco

  1. mayo 15, 2010 en 2:15 am

Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión / Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión / Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión / Cambiar )

Google+ photo

Estás comentando usando tu cuenta de Google+. Cerrar sesión / Cambiar )

Conectando a %s