Expresiones Regulares en Java
Veamos aquí algunos detalles de cómo usar expresiones regulares en java. No vamos a explicar con detalle las expresiones regulares, ya que en todos los lenguajes de programación son muy similares y usan sintaxis muy parecidas.
introducción a las expresiones regulares java[editar]
A veces es necesario que nuestro código analice una cadena de caracteres para buscar algo o bien para comprobar que cumple un determinado patrón. Por ejemplo, si pedimos por teclado una fecha dd/mm/yy, necesitamos comprobar si la cadena leída cumple ese patrón: dos cifras, una barra, dos cifras, otra barra y otras dos cifras.
Las expresiones regulares de java nos ayudan a hacer estos análisis. No vamos a dar aquí una lista detallada de todas las posibilidades, pero sí ver lo básico y unos ejemplos de cómo usarlas en Java en ciertos casos de interés.
Lo básico de las expresiones regulares en Java[editar]
Qué es una expresión regular[editar]
Una expresión regular no es más que un String Java. Se usa para saber si otra cadena cumple un determinado patrón. En el ejemplo más trivial, la expresión regular es exactamente el String. Por ejemplo, la cadena "ABC" cumple el patrón (expresión regular) "ABC". Poco más que un equals()
.
Sin embargo, una expresión regular es más potente. Admite caracteres especiales en el String para darnos un poco más de juego. Por ejemplo, y aunque sea adelantar acontecimientos, el punto . hace de comodín. Si la expresión regular es "A.B", las cadenas "ABC", "A1C" y "A#C" cumplen con el patrón. La expresión regular "A.B" es una A seguida de cualquier carácter y luego una B.
Vamos a ver detalles de todo esto.
Saber si se cumple la expresión regular java: Pattern.matches() y String.matches()[editar]
Para saber si una cadena cumple una expresión regular, podemos usar cualquiera de los dos métodos siguientes
Pattern.matches(regex, string);
string.matches(regex);
Podemos usar el método Pattern.matches()
pasando como parámetros un String con la expresión regular regex
y el string que queremos saber si cumple dicha expresión regular.
La otra alternativa es usar el método matches()
de la clase String. Teniendo cualquier cadena de texto string
, llamamos a matches
pasando como parámetro un string con la expresión regular regex
.
Los métodos anteriores solo devuelven true
si la cadena completa cumple la expresión regular. Si queremos saber si una parte de la cadena cumple con una expresión regular, usaremos el siguiente método
Pattern.compile(regex).matcher(string).find()
Por ejemplo, para saber si una cadena tiene una A mayúscula
Pattern.compile("A").matcher("ABC").find()
Pattern.matches("A", "ABC");
La expresión regular es simplemente "A" y la cadena es "ABC". En el primer caso, la llamada devuelve true
porque "ABC" contiene una "A". En el segundo caso devuelve false
porque "ABC" no es "A".
Comodines en la expresión regular[editar]
Un punto es el equivalente a cualquier carácter. Las siguientes expresiones devuelven todas true
Pattern.matches("A.C", "ABC")
Pattern.matches("A.C", "A2C")
Pero el caracter tiene que estar y ser solo uno. Las siguientes devuleven false
Pattern.matches("A.C", "AC")
Pattern.matches("A.C", "A12C")
Repetir n caracteres en la expresión regular[editar]
Si en una expresión regular queremos buscar un número determinado de caracteres iguales, podemos poner detrás del caracter la expresión {n}
si queremos que se repita exactamente n veces, o {n,m}
donde n es el número mínimo de veces que deseamos que aparezca el carácter y m el número máximo. Por ejemplo, A{2,4}
indica que nos valen "AA", "AAA" y "AAAA". En los siguientes ejemplos
Pattern.matches("A{2,4}", "AA")
Pattern.matches("A{2,4}", "AAA")
Pattern.matches("A{2,4}", "AAAA")
Pattern.matches("A{2,4}", "AAAAA")
las tres primeras devuelven true
, la cuarta devuelve false
.
Podemos usar el comodín, por ejemplo .{2,4}
indica cualquier conjunto de caracteres entre 2 y 4 y no es necesario que sean el mismo.
En {n,m}
podemos poner 0 en n, así que valdría que no hubiera ningún carácter. Podemos dejar m sin rellenar, es decir {n,}
, con lo que valdría cualquier número de caracteres desde n hasta infinitos.
Para las combinaciones {n.m}
más habituales tenemos caracteres especiales que nos lo abrevian.
- + equivale a {1,}, es decir, uno o más.
- * equivale a {0,}, es decir, cero o más.
Escapar caracteres especiales de las expresiones regulares[editar]
Vemos que hay caracteres que una expresión regular interpreta, hasta ahora el punto ., el asterisco *, el más +, las llaves { } y más que iremos viendo. Si queremos que sea un punto en la cadena o un asterisco en vez de cualquier número de veces el carácter anterior, debemos "escaparlo" en la expresión regular poniendo una \ delante. Como java es especial con la \ dentro de una cadena de texto, porque la interpreta, debemos poner doble \\
Pattern.matches("A\\.C", "A.C")
Pattern.matches("A\\.C", "ABC")
En los ejemplos anteriores, el primero devuelve true
porque tiene un punto en entre la A y la B, mientras que el segundo devuelve false
porque no hay un punto entre la A y la B.
Conjunto de caracteres en las expresiones regulares[editar]
Imagina que queremos saber si una cadena contiene solo dígitos. Nos vale cualquiera entre 0 y 9. Podemos indicar un conjunto de caracteres poniendo todos ellos entre corchetes. [0123456789]
equivale a decir cualquier dígito. Los siguientes ejemplos dan todos true
Pattern.matches("[0123456789]", "1")
Pattern.matches("[0123456789]", "4")
Podemos añadir detrás cuántas veces queremos que se repita con {n.m}
o cualquiera de los caracteres que hemos visto para las repeticiones habituales. Por ejemplo, [0123456789]+
es uno o más dígitos. Lo siguiente daría true
Pattern.matches("[0123456789]+", "215")
Si los caracteres van seguidos, podemos poner el primero y último separados por un guión -. [0-9]
es equivalente a [0123456789]
. Idem con letras, es válido [a-z]
, [A-Z]
o incluso juntando ambos [a-zA-Z]
para mayúsculas y minúsculas.
Podemos poner ^ delante para decir que queremos cualquier caracter menos los de ese conjunto. [^0-9]
es cualquier carácter que no sea un dígito.
Para los conjuntos habituales, tenemos también abreviaturas
- \d equivale a [0-9]
- \D equivale a [^0-9]
- \s equivale a cualquier carácter en blanco (espacio, tabulador, retorno de carro, etc)
- \S equivale a cualquier carácter que no sea un carácter en blanco.
- \w equivale a cualquier carácter que sea letra mayúscula, minúscula, dígito a guion bajo _. Es el típico para ver si se cumplen patrones como este "Texto_1".
- \W equivale a cualquier caracter que no sea letra mayúscula, minúscula, dígito ni guion bajo _
Algunos ejemplos de expresiones regulares java[editar]
Veamos algunos ejemplos concretos, algunos de los patrones más utilizados en nuestro código java
Expresión regular para fecha[editar]
Imaginemos la fecha en formato dd/mm/yyyy. Son grupos de dos cifras separadas por barras. En una expresión regular \d
representa una cifra. El día pueden ser una o dos cifras, es decir \d{1,2}
, el mes igual y el año vamos a obligar que sean cuatro cifras exactamente \d{4}
Si queremos comprobar que una cadena leída por teclado cumple ese patrón, podemos usar la clase Pattern
. A la clase Pattern
le decimos el patrón que queremos que cumpla nuestra cadena y nos dice si la cumple o no.
El siguiente ejemplo comprueba si la cadena cumple con la expresión regular. Ten en cuenta que cuando en java metemos un caracter \ dentro de una cadena delimitada por ""
, debemos "escapar" esta \ con otra \, por ello todas nuestras \ en la expresión regular, se convierten en \\ en nuestro código java.
String regexp = "\\d{1,2}/\\d{1,2}/\\d{4}";
// Lo siguiente devuelve true
System.out.println(Pattern.matches(regexp, "11/12/2014"));
System.out.println(Pattern.matches(regexp, "1/12/2014"));
System.out.println(Pattern.matches(regexp, "11/2/2014"));
// Los siguientes devuelven false
System.out.println(Pattern.matches(regexp, "11/12/14")); // El año no tiene cuatro cifras
System.out.println(Pattern.matches(regexp, "11//2014")); // el mes no tiene una o dos cifras
System.out.println(Pattern.matches(regexp, "11/12/14perico")); // Sobra "perico"
Supongamos que queremos que el mes se exprese como "ene", "feb", "mar", ... en vez de como un número. Cuando hay varias posibles cadenas válidas, en la expresión regular se ponen entre paréntesis y separadas por |
. Es decir, algo como esto (ene|feb|mar|abr|may|jun|jul|ago|sep|oct|nov|dic)
. Si además nos da igual mayúsculas o minúsculas, justo delante ponemos el flag de case insensitive (?i)
(la 'i' es de ignore case)
El siguiente código muestra un ejemplo completo de esto.
String literalMonthRegexp = "\\d{1,2}/(?i)(ene|feb|mar|abr|may|jun|jul|ago|sep|oct|nov|dic)/\\d{4}";
// Lo siguiente devuelve true
System.out.println(Pattern.matches(literalMonthRegexp, "11/dic/2014"));
System.out.println(Pattern.matches(literalMonthRegexp, "1/nov/2014"));
System.out.println(Pattern.matches(literalMonthRegexp, "1/AGO/2014")); // Mes en mayúsculas
System.out.println(Pattern.matches(literalMonthRegexp, "21/Oct/2014")); // Primera letra del mes en mayúsculas.
// Los siguientes devuelven false
System.out.println(Pattern.matches(literalMonthRegexp, "11/abc/2014")); // abc no es un mes
System.out.println(Pattern.matches(literalMonthRegexp, "11//2014")); // falta el mes
System.out.println(Pattern.matches(literalMonthRegexp, "11/jul/2014perico")); // sobra perico
Expresión regular java para DNI[editar]
En España existe el DNI (Documento Nacional de Identidad), últimamente también llamado NIF (Número de identificación fiscal), que lleva un número único y sirve para identificar a la persona. Este número son 8 cifras seguidas de una letra, que normalmente se escribe en mayúscula. Esta letra es una especie de checksum de las cifras anteriores, por lo que hay un algoritmo para validar que el número completo es correcto. Quedan excluidas las letras 'I', 'O' y 'U'. Las dos primeras por poder confundirse con uno y cero respectivamente. La tercera por algún extraño motivo.
Una expresión regular no va a realizar este checksum, pero sí nos puede ayudar a hacer una primera comprobación: 8 cifras y una letra mayúscula. La expresión regular puede ser así \d{8}[A-HJ-NP-TV-Z]
El siguiente código muestra un ejemplo completo de la expresión regular de DNI
String dniRegexp = "\\d{8}[A-HJ-NP-TV-Z]";
// Lo siguiente devuelve true
System.out.println(Pattern.matches(dniRegexp, "01234567C"));
// Lo siguiente devuelve faslse
System.out.println(Pattern.matches(dniRegexp, "01234567U")); // La U no es válida
System.out.println(Pattern.matches(dniRegexp, "0123567X")); // No tiene 8 cifras
Expresión regular java para email[editar]
Antes de nada, no existe una expresión regular para email que sea 100% fiable, puesto que hay muchos formatos válidos de email y muy complejos. Aquí vamos a usar una expresión regular más o menos sencilla extraída de ese enlace : [^@]+@[^@]+\.[a-zA-Z]{2,}
. Significa lo siguiente, un email válido está compuesto de :
[^@]+
cualquier caracter que no sea @ una o más veces seguido de@
una @ seguido de[^@]+
cualquier caracter que no sea @ una o más veces seguido de\.
un punto seguido de[a-zA-Z]{2,}
dos o más letras minúsculas o mayúsculas
Un ejemplo en código java de esta expresión regular
String emailRegexp = "[^@]+@[^@]+\\.[a-zA-Z]{2,}";
// Lo siguiente devuelve true
System.out.println(Pattern.matches(emailRegexp, "a@b.com"));
System.out.println(Pattern.matches(emailRegexp, "+++@+++.com"));
// Lo siguiente devuelve faslse
System.out.println(Pattern.matches(emailRegexp, "@b.com")); // Falta el nombre
System.out.println(Pattern.matches(emailRegexp, "a@b.c")); // El dominio final debe tener al menos dos letras
Extraer partes de una cadena con una expresión regular[editar]
Una vez que vemos la forma de ver si una cadena cumple el patrón, podemos querer extraer parte de ese patrón, por ejemplo, las cifras de la fecha (día, mes y año). Nuevamente las expresiones regulares de java nos ayudan. Cambiemos el ejemplo. Queremos extraer los sumandos y el resultado de una cadena así "xxxx+yyyy=zzzzz" donde x, y y z representan dígitos y pueden ser en cualquier número.
Con \d+
indicamos uno o más dígitos. La expresión regular para ver si una cadena cumple ese patrón puede ser \d+\+\d+=\d+
". Puesto que el +
tiene un sentido especial en los patrones -indica uno o más-, para ver si hay un "+" en la cadena, tenemos que "escaparlo", por eso el \ delante.
Las partes que queramos extraer, debemos meterlas entre paréntesis. Así, la expresión regular quedaría "(\d+)\+(\d+)=(\d+)
".
El código para extraer los sumandos y el resultado puede ser así:
// La cadena a analizar
String cadena = "23+12=35";
// Obtenemos un Pattern con la expresión regular, y de él
// un Matcher, para extraer los trozos de interés.
Pattern patron = Pattern.compile("(\\d+)\\+(\\d+)=(\\d+)");
Matcher matcher = patron.matcher(cadena);
// Hace que Matcher busque los trozos.
matcher.find();
// Va devolviendo los trozos. El primer paréntesis es el 1,
// el segundo el 2 y el tercero el 3
System.out.println(matcher.group(1));
System.out.println(matcher.group(2));
System.out.println(matcher.group(3));
// La salida de este programa es
// 23
// 12
// 35
Buscar con una expresión regular a lo largo de la cadena[editar]
Supongamos la siguiente cadena de texto <a>uno</a><b>dos</b><c>tres</c>
y que queremos extraer usando expresiones regulares los trozos que hay entre los tags <a>, <b> y <c>
, es decir, "uno", "dos" y "tres".
No es necesario que la cadena coincida exactamente con el patrón en su longitud total. Es posible tener una cadena larga, por ejemplo <a>uno</a><b>dos</b><c>tres</c>
y un patrón que sólo coincida con parte de la cadena, por ejemplo, <[^>]*>([^<]*)</[^>]*>
. Es decir
<[^>]*>
Este trozo busca los tags encerrados entre los símbolos mayor y menor. El [^>] indica cualquier caracter que no sea >. El * detrás indica que puede estar 0 o más veces.([^<]*)
Busca lo que hay entre tags, es decir, cualquier caracter que no sea "menor que". Como es lo que queremos extraer de la cadena, lo ponemos entre paréntesis, de forma que el método find() será lo que nos vaya devolviendo en sucesivas llamadas.</[^>]*>
Buscamos la finalización del tag, es decir, un menor qué, seguido de una barra / y todos los caracteres que no sean "mayor qué".
Esa expresión regular extraería de la cadena el "uno" en una primera llamada a find()
. Tendríamos que hacer un bucle para repetir tantas veces como sea necesario. El bucle sería como el siguiente
String cadena = "<a>uno</a><b>dos</b><c>tres</c>";
Pattern pattern1 = Pattern.compile("<[^>]*>([^<]*)</[^>]*>");
Matcher matcher1 = pattern1.matcher(cadena);
for (int i = 0; i < 1; i++) {
while (matcher1.find()) {
System.out.println(matcher1.group(1));
}
}
Es decir, una vez construído el Matcher
, vamos haciendo sucesivas llamadas a find()
para obtener el contenido de cada uno de los tags. Como en nuestro patrón sólo hay un paréntesis, el group()
siempre será el 1.
Expresión regular para extraer direcciones de email[editar]
El siguiente ejemplo extrae las direcciones de email existentes en un String, usando otra expresión regular de email distinta de la anterior (más restrictiva), pero extraída de la misma página.
package com.chuidiang.ejemplos;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class ExtractorEmails {
public static void main(String[] args) {
String entrada = "<p>hola@pedro.com</p><br>\n";
entrada += "kk@tres.tris///pepe@eso.es";
Pattern limpiar = Pattern
.compile("([a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\\.[a-zA-Z0-9-]+)*)");
Matcher buscar = limpiar.matcher(entrada);
while (buscar.find())
System.out.println(buscar.group(1));
}
}
La salida de este código será
hola@pedro.com kk@tres.tris pepe@eso.es
Veamos el significado de la expresión regular, trozo por trozo
[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+
debe empezar por alguno de los caracteres entre los corchetes ( _ - mayúscula, minúscula o número, extraños símbolos), una o más veces (el +)@
este es fácil, una @[a-zA-Z0-9-]+
Una o más letras, mayúsculas, minúsculas, dígitos o el guión.(?:\\.[a-zA-Z0-9-]+)*)
esta no es tan fácil, quiere decir que puede haber después 0 o más veces un punto seguido de una o más letras, algo como dominio.com.es. Veamos los trocitos\\.
un punto[a-zA-Z0-9-]+
minúsculas, mayúsculas, dígitos o guión una o más veces.(?: )*
los dos trocitos anteriores (el punto y las una o más letras), se encierran entre paréntesis y se repiten 0 o más veces (el asterico del final). El?:
se pone para indicar que NO queremos extraer en nuestro código el trozo que está entre estos paréntesis. Recuerda que poner algo entre paréntesis en nuestra expresión regular es que vamos a querer extraer ese trozo en nuestro código. Si lo encerramos entre(?: )
queremos decir que ese trozo concreto no nos interesa y que lo ignore. Necesitamos los paréntesis solo para poner el*
detrás.
Greedy, reluctant y possesive en expresiones regulares[editar]
Cuando java intenta encajar un patrón en una cadena, puede haber varias posibles coincidencias. Por ejemplo, si la cadena es "aaaaa" y el patrón es a*
, podría considerarse que el patrón se cumple 5 veces (es decir, el * se tomaría como 1 ocurrencia y a* se interpretaría como "a"), o bien podría interpretarse que el patrón sólo se cumple una vez, es decir, el * sería 5 ocurrencias y "a*" sería "aaaaa". Java nos permite elegir cómo queremos que se haga.
Una forma de resolver el ejemplo de extraer el texto dentro de los tags html, sería con el patrón <.*>(.*)</.*>
, es decir, buscamos
<.*>
Una apertura de tag(.*)
Lo que hay entre tags</.*>
El cierre de tag
Y el código java sería
String cadena = "<a>uno</a><b>dos</b><c>tres</tres>";
Pattern pattern1 = Pattern.compile("<.*>(.*)</.*>");
Matcher matcher1 = pattern1.matcher(cadena);
while (matcher1.find()) {
System.out.println("1 " + matcher1.group(1));
}
Estamos buscando cualquier cosas entre <.*>
y </.*>
, es decir, entre cualquier tag delimintado por <>
, tenga lo que tenga dentro y </ >
. Nos quedamos con lo que hay entre ellos (el (.*)
). Metiendo el Matcher
correspondiente en un bucle con find()
, esperamos encontrar las tres cadenas buscadas.
Pero esto no funciona, sólo nos da la última. ¿Por qué?. La búsqueda empieza con un <.*>
, es decir, busca un < (el que está al principio de la cadena) y luego va saltando caracteres, todos los que puede, hasta que encuentre algo que pueda casar con el resto del matcher
. Y lo que encuentra es que el primero <.*>
casa perfectamente con <a>uno</a><b>dos</b><c>
. Luego el (.*)
casa con el "tres" (y es lo que nos va a devolver la llamada al group(1)
y el último </.*>
casa con </c>
.
Este comportamiento, que no es el que queremos, se conocd como "greedy" (glotón), en el que cada uno de los trozos del patrón que ponemos trata de coger lo máximo posible de la cadena. El primer trozo del patrón <.*>
casa perfectamente con el primero <a>
, pero también con <a>uno</a>
, con <a>uno</a><b>
, con .... y con la cadena completa <a>uno</a><b>dos</b><c>tres</c>
. El comportamiento greedy trata de coger lo máximo posible, pero siempre intentando que el patrón completo se cumpla. Por ello, el primero <.*>
coge <a>uno</a><b>dos</b><c>
, que es lo máximo que puede coger haciendo que el patrón completo se cumpla.
Otro posible comportamiento es "reluctant" o perezoso. Este comportamiento es el contrario de greedy. Tratará de coger lo menos posible, pero siempre intentando que se cumpla el patrón. Para este comportamiento, añadimos un ? detrás. Así, el siguiente código
Pattern pattern2 = Pattern.compile("<.*?>(.*?)</.*?>");
Matcher matcher2 = pattern2.matcher(cadena);
while (matcher2.find()) {
System.out.println("2 " + matcher2.group(1));
}
funcionará según lo esperado, ya que <.*?>
intentará coger lo menos posible que cumpla el patrón, es decir, la <a>
. El (.*?)
hará lo mismo, es decir el "uno" (si no pusiéramos el interrogante, este trozo de patrón cogería todo hasta el </c></nowwiki></code> final, excluyéndolo. Por último, el último <code><nowiki></.*?>
casará sólo con el </a>
y si no pusiéramos el interrogante, casaría con todo el resto de la cadena.
Finalmente, existe otra forma llamada possesive o posesiva. Funciona exactamente igual que greedy (es decir, trata de coger lo máximo posible), pero a diferencia de greedy no se preocupa de hacer que se cumpla el patrón. Para este modo se pone un más en vez de un interrogante y el patrón quedaría <.*+>(.*+)</.*+>
. No funcionaría nunca ni encontraría nada, porque el primer < casaría con el primer < de la cadena y el .*+ se "comería" el resto de la cadena hasta el final. La forma correcta de usar este cuantificador para este tipo de cadena podría ser <[^>]*+>([^<]*+)</[^>]*+>
de forma que
<[^>]*+>
Busca el "menor que", luego todo lo que no sea "mayor que" y finalmente coge el "mayor que", es decir, lo que sería un tag con su apertura < y cierre >([^<]*+)
Va leyendo todo lo que no sea "menor que", es decir, lo que hay entre la apertura del tag encontrado antes y el principio del siguiente tag, presumiblemente el de cierre. Esta es la parte que nos interesa, por lo que la metemos entre paréntesis.</[^>]*+>
El "menor que", la /, todo lo que haya que no sea "mayor que" y el "mayor que". Es decir, otro cierre de tag.
De esta forma, los possessive se irían "comiendo" todo hasta encontrar un "mayor que" o "menor que", según el caso. En cualquier caso, se puede podría hacer exactamente lo mismo con greedy.
¿Para qué se usa este modo entonces?. Únicamente por motivos de eficiencia. Si en la cadena hay un trozo que queramos quitar y que podamos distinguir con una expresión regular, podemos ponerlo con este modo possesive. De esta forma, el possesive se comerá directamente ese trozo de cadena y no perderá el tiempo tratando de hacer casar ese trozo con el patrón de alguna u otra forma. En el ejemplo anterior, si la cadena fuera larga y no hubiera ningún "mayor que", el primer trozo de [^>]*+
se "comería" toda la cadena dando fallo en la búsqueda directamente, mientras que los otros dos cuantificadores (greedy y reluctant), tras ver el fallo, tratarían de retroceder en la cadena a ver si "comiendo" más o menos caracteres pueden hacerla "casar" de alguna forma.
Enlaces[editar]
- En la api de la clase Pattern tienes todos los posibles caracteres que se pueden usar en expresiones regularas de Java, con una pequeña explicación de cada uno de ellos.
- En esta página puedes probar online tus expresiones regulares en java, muy útil para hacer pruebas antes de ponerse a codificar
- Por supuesto la documentación oficial de Oracle para expresiones regulares en java.