Expresiones Regulares – 04

Con los metacaracteres que hemos aprendido hasta el momento, es posible efectuar múltiples tipos de búsquedas y validaciones como ya hemos visto en los ejemplos de las entregas anteriores. Sin embargo, los metacaracteres que veremos en esta entrega son los que probablemente aportan más flexibilidad y funcionalidad a las expresiones regulares: Los caracteres de repetición.

Las llaves "{}"

Comúnmente las llaves son caracteres literales cuando se utilizan por separado en una expresión regular. Para que adquieran su función de metacaracteres es necesario que encierren uno o varios números separados por coma y que estén colocados a la derecha de otra expresión regular de la siguiente forma: "\d{2}" Esta expresión le dice al motor de búsqueda que encuentre dos dígitos contiguos. Utilizando esta formula podríamos convertir el ejemplo "^\d\d/\d\d/\d\d\d\d$" que nos servía para validar un formato de fecha en "^\d{2}/\d{2}/\d{4}$" para una mayor claridad en la lectura de la expresión.

Aunque esta forma de encontrar elementos repetidos es muy útil, algunas veces no sabemos cuantas veces se repite lo que búscamos o su grado de repetición es variable. En estos casos los siguientes metacaracteres no son útiles.

El asterisco "*"

El asterisco nos sirve para encontrar algo que se encuentra repetido 0 o más veces. Osea, utilizando la expresión "[a-zA-Z]\d*" nos servirá para encontrar tanto "H" como "H1", "H01", "H100" y "H1000", osea, una letra seguida de un número indefinido de caractéres. Es necesario tener cuidado con el comportamiento del asterisco, ya que este por defecto trata de encontrar la mayor cantidad posible de caracteres que correspondan con el patrón que buscamos. De esta forma si utilizamos "\(.*\)" para encontrar cualquier cosa que se encuentre entre paréntesis y lo aplicamos sobre el texto "Ver (Fig. 1) y (Fig. 2)" esperaríamos que el motor de búsqueda encuentre los textos "(Fig. 1)" y "(Fig. 2)", sin embargo nos sorprenderemos al ver que en su lugar encontrará el texto "(Fig. 1) y (Fig. 2)". Esto sucede porque el asterísco le dice al motor de búsqueda que llene todos los espacios posibles entre dos paréntesis, y ese es el resultado que obtenemos. Si queremos obtener el resultado deseado debemos utilizar el asterisco en conjunto con el signo de pregunta de la siguiente forma: "\(.*?\)" Esto es equivalente a decirle al motor de búsqueda "Encuentre un paréntesis de apertura y luego encuentre cualquier caracter repetido hasta que encuentre un paréntesis de cierre".

El signo de suma "+"

Nos sirve para encontrar algo que se encuentre repetido 1 o más veces. A diferencia del asterisco, la expresión "[a-zA-Z]\d+" encontrará "H1" pero no encontrará "H". También es posible utilizar este metacaracter en conjunto con el signo de pregunta para limitar hasta donde queremos que se efecúe la repetición.

En la entrega anterior hablamos de que los paréntesis redondos establecen un "punto de referencia" para el motor de búsqueda. Estos puntos se denominan grupos y pueden ser anónimos o nominales. A continuación veremos en detalle como utilizar los grupos.

Grupos anónimos

Los grupos anónimos se establecen cada vez que encerramos una expresión regular en paréntesis redondos, por lo que la expresión "<([a-zA-Z]\w*?)>" define un grupo anónimo que tendrá como resultado que el motor de búsqueda almacenará una referencia al texto que coresponda a la expresión encerrada entre los paréntesis.

Pero como podemos utilizar los grupos que establecemos? La forma más inmediata de utilizar los grupos es dentro de la misma expresión regular, lo cual se realiza utilizando la barra inversa "\" seguida del número del grupo al que queremos hacer referencia de la siguiente forma: "<([a-zA-Z]\w*?)>.*?</\1>" Esta expresión regular nos ayudará a encontrar tanto "<font>Esta</font>" como "<B>prueba</B>" en el texto "<font>Esta</font> es una <B>prueba</B>" a pesar de que la expresión no contiene los literales "font" y "B".

Otra forma de utilizar los grupos es en el lenguaje de programación que estemos utilizando. cada lenguaje tiene una forma de distinta de acceder a los grupos, pero como explicamos al principio de la serie explicaremos con detalle la forma utilizada por el .Net Framework, usando la sintáxis de C# (la cual puede facilmente adaptarse a VB.Net o cualquier otro lenguaje del Framework).

Para utilizar el motor de búsqueda del .Net Framework es necesario en primer lugar hacer referencia al espacio de nombres System.Text.RegularExpressions. Luego es necesario declarar una instancia de la clase Regex de la siguiente forma:

Regex _TagParser = new Regex("<([a-zA-Z]\w*?)>");

Luego asumiendo que el texto que queremos examinar con la expresión regular se encuentra en la variable "sText" podemos recorrer todas las instancias encontradas de la siguiente forma:

foreach(Match CurrentMatch in _TagParser.Matches(sText)){
   // —– Código extra aquí —–
}

Luego podemos utilizar la propiedad Groups de la clase Match para traer el resultado de la búsqueda:

foreach(Match CurrentMatch in _TagParser.Matches(sText)){
  String sTagName = CurrentMatch.Groups[1].Value;
}

Grupos nominales

Los grupos nominales son aquellos a los que les asignamos un nombre, dentro de la expresión regular para poder utilizarlos posteriormente. Esto se hace de forma diferente en los distintos motores de búsqueda, así que una vez más aqui explicaremos como hacerlo en el motor del .Net Framework.

Utilizando el ejemplo anterior podemos convertir "<([a-zA-Z]\w*?)>" en "<(?<TagName>[a-zA-Z]\w*?)>". Nótese el signo de pregunta y el texto "TagName" encerrado entre parentesis triangulares, seguido de este. Para utilizar este ejemplo en el .Net Framework utilizaríamos el siguiente código:

Regex _TagParser = new Regex("<(?<TagName>[a-zA-Z]\w*?)>");
foreach(Match CurrentMatch in _TagParser.Matches(sText)){
  String sTagName = CurrentMatch.Groups["TagName"].Value;
}

Es posible definir tantos grupos como sea necesario, de esta forma podemos hacer algo como: "<(?<TagName>[a-zA-Z]\w*?) ?(?<Attributes>.*?)>" para encontrar no solo el nombre del tag HTML sino tambien sus atributos de la siguiente forma:

Regex _TagParser = new Regex("<(?<TagName>[a-zA-Z]\w*?) ?(?<Attributes>.*?)>");
foreach(Match CurrentMatch in _TagParser.Matches(sText)){
  String sTagName = CurrentMatch.Groups["TagName"].Value;
  String sAttributes = CurrentMatch.Groups["Attributes"].Value;
}

Pero podemos ir mucho más allá:
"<?(?<TagName>[a-zA-Z][\w\r\n]*?) ?(?:(?<Attribute>[\w-\r\n]*?)=’?"?(?<Value>[\w-:;,\./= \r\n]*?)’?"? ?)>"
Esta expresión nos permite encontrar el nombre del tag, el nombre del atributo y su valor.

Sin embargo, una tag html puede tener más de un atributo. Este caso lo resolveríamos utilizando repetición de la siguiente forma:
"<?(?<TagName>[a-zA-Z][\w\r\n]*?) ?(?:(?<Attribute>[\w-\r\n]*?)=’?"?(?<Value>[\w-:;,\./= \r\n]*?)’?"? ?)*?>"

Y en el código la utilizariamos de la siguiente forma:

Regex _TagParser = new Regex("<?(?<TagName>[a-zA-Z][\w\r\n]*?) ?(?:(?<Attribute>[\w-\r\n]*?)=’?"?(?<Value>[\w-:;,\./= \r\n]*?)’?"? ?)*?>");
foreach(Match CurrentMatch in _TagParser.Matches(sText)){
  String sTagName = CurrentMatch.Groups["TagName"].Value;
  foreach(Capture CurrentCapture in CurrentMatch.Groups["Attribute"].Captures){
    AttributesCollection.Add(CurrentCapture.Value)
  }
  foreach(Capture CurrentCapture in CurrentMatch.Groups["value"].Captures){
    ValuesCollection.Add(CurrentCapture.Value)
  }
}

Aún más podríamos profundizar utilizando una expresión como esta:
"<?(?<TagName>[a-zA-Z][\w\r\n]*?) ?(?:(?<Attribute>[\w-\r\n]*?)=’?"?(?<Value>[\w-:;,\./= \r\n]*?)’?"? ?)*?>(?<Content>.*?)</\1>"

La cual nos permitiría encontrar el tag, sus atributos, valores y el contenido de esta, y todo con una sola expresión regular!!!!

Espero que les haya gustado esta cuarta entrega. En la próxima continuaremos estudiendo ejemplos y aplicaciones de las expresiones regulares en casos específicos.

No duden en enviarme sus dudas y comentarios al respecto, estaré dispuesto a ampliar la información dada según sea requerido.

3 pensamientos en “Expresiones Regulares – 04

  1. En primer lugar quiero felicitarte por tu trabajo sobre las expresiones regulares, francamente me parece impresionante.
     
    Por otro lado quiero hacerte una consulta. Es que he probado un monton de posible expresiones pero no sale lo que quiero, a ver si me puedes hechar una mano:
     
    Tengo una cadena de texto en la que aparece un número de 3 a 6 cifrás, pero dicho número no puede venir precedido por la cadena "OF."; estoy probando con:
     
    \\[^OF.]\\s?\\d{3,6}\\s?
     
    y variaciones similares, pero no me sale. El problema le tengo al querer filtrar por los que no tengan "OF."
     
    ¿Alguna sugerencia?.
     
    Muchas gracias por todo

  2. Disculpe por no haber contestado con anterioridad, pero hasta hoy pude ver su comentario.
     
    El problema con las expresiones regulares "negadas" es que muchas veces no nos damos cuenta de que su presencia también ocupa un espacio en la cadena que deseamos encontrar. Osea, cuando buscas:
     
    [^OF.]\\s?\\d{3,6}\\s?
     
    Literalmente estás buscando: Una cadena que empiece con cualquier caracter mientras este no sea ni "O", ni "F", ni ".", seguida opcionalmente por un espacio, seguida por un número de entre 3 a 6 dígitos, seguida opcionalmente por un espacio. Al leer este enunciado puedes ver claramente que esto no es lo que buscas.
     
    No puedo crear una expresión regular "exactamente" ajustada a lo que necesitas hasta tener un archivo donde hacer pruebas, pero prueba utilizando la siguiente expresión regular:
     
    [^(OF\\.|\\d)](\\d{3,6})[^\\d]
     
    Que literalmente es interpretada de la siguiente forma: Una cadena que empiece con cualquier caracter que no sea ni "OF." ni un dígito y que esté seguida de un número de 3 a 6 dígitos y que finalice con cualquier caracter que no sea un dígito. Esta expresión la probé en el siguiente texto:
     
     1 12 123 1234 12345 123456 1234567 12345678  112123123412345123456123456712345678OF.123OF.1234OF.12345OF.123456OF.1234567OF.12345678
     
    Con los siguientes resultados:
     
    Encontró:
     
    123
    1234
    12345
    123456
    123
    12345
     
    Aunque algunos son los resultados que esperabamos y efectivametne se saltó cualquier cosa que empezara con "OF." no encontró algunos resultados que si se deseaban encontrar, y la razón de esto es que despues de la cadena solo seguía el caracter de salto de línea, y como éste cuenta dentro de la búsqueda, el hecho de decirle al motor de búsqueda que la cadena debía finalizar con un caracter que no fuera un dígito y empezar con un caracter que no fuera ni un dígito ni "OF." ambos delimitadores cuentan en la búsqueda, por lo que en el segundo grupo de números se brincó algunos resultados que se deseaban encontrar. Con solo agregar un espacio (o cualquier otro caracter diferente de un dígito) al final de las secuencias del segundo grupo las encontrara sin problema como en el siguiente ejemplo:
     

    1 12 123 1234 12345 123456 1234567 12345678  1a12a123a1234a12345a123456a1234567a12345678aOF.123OF.1234OF.12345OF.123456OF.1234567OF.12345678
     
    Aunque la "a" del final y el salto de linea iniciales estarán incluidos en los resultados por lo que querrá discriminar únicamente el grupo "central" que se encuentra encerrado en paréntesis, ya sea por número o por nombre dependiendo de lo que le permita el motor de búsqueda que esté utilizando. Si esta expresión regular no se adecúa a lo que necesita puede enviarme un archivo de ejemplo con el cual probar los resultados y probablemente logre encontrar una solución adecuada a su problema.
     
    No dude en hacerme preguntas parecidas en el futuro.

  3. Hola que tal, quisiera realizar la siguiente consulta, estoy trabajando con definición de expresiones regulares en una herramienta llamada Spectrum. Como puedo hacer para buscar una cadena cualquiera dentro de otra, por ejemplo Raid Group <name> is deleted, es decir las palabras Raid Group is deleted se deben bucar en otro lugar tal cual son estas palabras, pero <name> va a ser un string cualquiera que el usuario ingrese por teclado, puede ser cualquier palabra, como definiria la expresion regular?. Las expresiones se definen de la siguiente forma -> regexp({v 1}, {S \\"Raid Group — is deleted\\"}))". Donde puse los guiones debería ir cualquier cadena, como seria la exp reg?. Desde ya muchísimas gracias.-

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