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.