Expresiones regulares en groovy

De ChuWiki

Veamos algunos ejemplos de cómo usar expresiones regulares en Groovy. Puesto que la sintaxis de las expresiones regulares es muy similar en la mayoría de los lenguajes de programación, no vamos a ver aquí el detalle de la sintaxis de dichas expresiones regulares, puedes ver dicha sintaxis en Introducción a las expresiones regulares. En este tutorial veremos los detalles específicos de Groovy.

Cadena con expresión regular[editar]

En Groovy podemos construir la cadena de la expresión regular usando las "" como con cualquier otra cadena. Sin embargo, haciéndolo de esta forma, puede ser engorroso. ya que las expresiones regulares suelen usar bastantes caracteres \ y en una cadena delimitada por "" habría que "escapar" esa \ con otra \. En vez de esto, Groovy nos permite delimitar las cadenas con /, y ahí no es necesario escapar caracteres.

Así, la expresión regular de un dígito \d, se podría poner en Groovy de cualquiera de las dos formas siguientes

def a = "\\d"     // es necesario "escapar" el carácter \ anteponiendo otra \
def b = /\d/      // No es necesario "escapar" el carácter \

Operador find[editar]

Una vez que tenemos la expresión regular en una cadena, delimitada por "" o delimitada por //, el operador find nos permite ver si parte de una cadena cumple la expresión regular. Los siguientes ejemplos, que son equivalentes, devuelven true, ya que la cadena contiene un dígito \d

assert "wsdl2java" =~ "\\d"        // true, "wsdl2java" contiene un dígito \d
assert "wsdl2java" =~ /\d/         // true, "wsdl2java" contiene un dígito \d

Es importante en este operador dejar un espacio en blanco detrás, entre el operador =~ y la cadena con la expresión regular. Como veremos más adelante, una cadena, bien esté delimitada con "", bien esté delimitada con //, precedida inmediatamente de un símbolo ~, nos sirve para definir una instancia de Pattern.

Operador match[editar]

El operador match ==~ nos permite comprobar si una cadena completa cumple con la expresión regular, no sólo un trozo de esa cadena, sino la cadena completa. Por ejemplo, si queremos comprobar que la cadena sólo contiene dígitos, los dos siguientes ejemplos devuelven true y son equivalentes

assert "123" ==~ "\\d+"        // true, "123" contiene uno o más dígitos y nada más.
assert "123" ==~ /\d+/         // true, "wsdl2java" contiene uno o más dígitos y nada más

El espacio detrás de ==~, al igual que con el operador find, es importante.

Extraer coincidencias de la expresión regular[editar]

Imagina que tenemos una fecha en una cadena, estilo "11/3/2017" y queremos extraer los dígitos. La expresión regular de dígitos (uno o más) es /\d+/. Para sacar los dígitos debemos crear un Pattern y llamar al método matcher("11/3/2017") para obtener a su vez un Matcher de esa expresión regular con esa cadena. La instancia de Matcher obtenida nos permite extraer los trozos de cadena (dígitos) que nos interesan. El siguiente código muestra cómo

// Se define un Pattern con la expresión regular.
def digitPattern = ~/\d+/

// Se obtiene el Matcher para la cadena concreta de interés
def dateMatcher = digitPattern.matcher("11/3/2017")

// Basta iterar para obtener cada uno de los números (día, mes y año)
dateMatcher.each {
   println it
}

Vemos aquí lo que indicamos antes, anteponiendo un ~ a la cadena /\d+/, obtenemos una instancia de Pattern para esa expresión regular. Sobre ese Pattern digitPattern, obtenemos el Matcher dateMatcher para la cadena concreta de interés, la fecha "11/3/2017". Y finalmente basta iterar para obtener "11", "3" y "2017". Recuerda que son cadenas de texto, tendrás que hacer la conversión a número si la necesitas.

Groovy, como siempre, nos da catorce o quince formas de hacer la misma cosa. Podemos obtener el Matcher de una forma más rápida, con el siguiente código

def dateMatcher = '11/3/2017' =~ /\d+/
dateMatcher.each {
   println it
}

El operador find =~ devuelve directamente un Matcher, así que ahorramos alguna línea de código.

Extraer partes de la expresión regular[editar]

Puede ser que nos interese extraer sólo algunas partes de la expresión regular. Imagina en el mismo ejemplo de antes que la cadena contiene varias fechas "11/3/2017 4/10/2015 y 22/1/2018" y queremos los dígitos de todas esas fechas. Hacemos una expresión regular que nos permita coger la fecha, algo así como esto /\d{1,2}/\d{1,2}/\d{4}/. Es decir, el día puede ser uno o dos dígitos \d{1,2}, el mes igual y el año son cuatro dígitos \d{4}. Como nos interesan sólo los dígitos y no la cadena con la flecha completa, encerramos entre paréntesis aquellos trozos que nos interesan. La expresión quedaría entonces /(\d{1,2})\/(\d{1,2})\/(\d{4})/. Fíjate que hemos tenido que "escapar" las / de las fechas, puesto que el delimitador de la cadena es justo una /.

El código Groovy sería igual que antes, crear el Pattern, obtener el Matcher y recorrerlo, pero aquí hay una pequeña diferencia. Vemos el código y luego lo explicamos

def matcher = "11/3/2017 4/10/2015 y 22/1/2018" =~ $/(\d{1,2})/(\d{1,2})/(\d{4})/$

matcher.each {
   println "date = "+it[0]
   println "day = "+it[1]
   println "month = "+it[2]
   println "year = "+it[3]
}

La diferencia es que esta vez la variable id no es el trozo de cadena que cumple con la expresión regular, sino un array. El primero elemento del array, el de índice 0, es el trozo de cadena que cumple la expresión regular (la fecha completa). El resto de índices (1,2 y 3) son los tres trozos de cadena que hemos puesto entre paréntesis (hemos abierto y cerrado tres paréntesis, por lo que hay tres elementos en el array, aparte de la fecha completa). El de índice 1 contiene el día, el de índice 2 contiene el mes y el de índice 3 contiene el año. El resultado de ese trozo de código de arriba es

date = 11/3/2017
day = 11
month = 3
year = 2017
date = 4/10/2015
day = 4
month = 10
year = 2015
date = 22/1/2018
day = 22
month = 1
year = 2018

Un pequeño consejo para expresiones regulares[editar]

Aprovechando que Groovy permite "meter" variables dentro de los cadenas delimitadas por "" o incluso delimitadas por //, en vez de una expresión regular compleja, como es hasta cierto punto la que hemos hecho de la fecha, podemos ir haciéndola a base de variables que nos hagan más legible la expresión regular. Por ejemplo, se puede entender mejor de la siguiente forma

def day = /\d{1,2}/
def month = /\d{1,2}/
def year = /\d{4}/
        
matcher = "11/3/2017 4/10/2015 y 22/1/2018" =~ /($day)\/($month)\/($year)/

matcher.each {
   println "date = "+it[0]
   println "day = "+it[1]
   println "month = "+it[2]
   println "year = "+it[3]
}

Hemos definido pequeñas expresiones regulares para día, mes y año, metiéndolas en variables con el nombre adecuado. Luego, la expresión compleja, la de la fecha, la "componemos" usando esas variables, con $day, $month y $year. Esto hace la expresión regular compleja más legible.

Enlaces[editar]