Hasta ahora hemos visto como crear expresiones regulares en Javascript, creando objetos
Para ello, hemos utilizado métodos como .test()
o .exec()
, pero sin saber demasiado como funcionan. En esta sección vamos a analizarlos y ver sus características y detalles.
Métodos de un objeto RegExp
Cualquier objeto
Método | Descripción |
---|---|
test(text) | Comprueba si la expresión regular «casa» con el texto text pasado por parámetro. |
exec(text) | Ejecuta una búsqueda en el texto text . Devuelve |
Mientras que el primero, el método .test()
se suele utilizar simplemente para comprobar si la expresión regular detecta algún texto que encaje con el .exec()
es un poco más avanzado, y podemos utilizarlo para capturar coincidencias.
Detectando coincidencias (test)
Veamos, por ejemplo, como utilizar la expresión regular siguiente con el método .test()
para comprobar si encaja con alguna ocurrencia en un texto determinado:
const regexp = /.a.o/i;
regexp.test("gato"); // true
regexp.test("pato"); // true
regexp.test("perro"); // false
regexp.test("DATO"); // true (el flag i ignora mayúsculas/minúsculas)
El método .test()
siempre te devolverá un
Recuerda que aunque test()
espera un .toString()
que existe en todos los objetos de Javascript:
const objeto = { name: "Manz" };
const regexp = /object/g;
objeto.toString(); // "[object Object]"
regexp.test(objeto); // true
Mucho cuidado con esto, ya que de pasar un tipo de dato que no sea
Captura de patrones (exec)
Pero con las expresiones regulares, además de poder realizar búsquedas de patrones, también se puede capturar coincidencias. De hecho, es una de sus características más potentes y versátiles.
Toda expresión regular que utilice la parentización (englobar con paréntesis fragmentos de texto) está realizando implícitamente una captura de texto, con la que es muy útil obtener rápidamente información. Para ello, en lugar de utilizar el método test()
, vamos a utilizar el método exec()
. Funciona exactamente igual, sólo que devuelve un
Antes de empezar a utilizarlo, necesitamos saber detalles sobre la parentización:
Formato | Descripción |
---|---|
(x) | El patrón x incluído dentro de paréntesis se captura y se guarda. |
(?:x) | Si incluímos ?: al inicio del patrón en los paréntesis, no se captura ese patrón. |
x(?=y) | Busca sólo si x está seguido de y . |
x(?!y) | Busca sólo si x no está seguido de y . |
Así pues, veamos algunos ejemplos para entenderlo bien. Observa que hemos creado una expresión regular que parentiza el texto .a.o
, sin embargo, tiene un .
(cualquier carácter, recuerda) fuera del paréntesis, por lo que ese carácter no se capturará (solo se captura el interior del paréntesis):
const text = `Hola Manz,
Soy el otro Manz (el gato) y necesito Whiskas.
El pato del patio sigue haciendo ruido. Te lo digo como dato.
Gracias.`;
const regexp = /(.a.o)./g;
regexp.exec(text); // ["gato)", "gato"]
regexp.exec(text); // ["pato ", "pato"]
regexp.exec(text); // ["dato.", "dato"]
regexp.exec(text); // null
Observa que en cada ejecución del método .exec()
se nos devuelve un resultado diferente. Esto ocurre por usar el flag g
(búsqueda global) y nos devuelve un
Ten en cuenta que también podemos hacer múltiples capturas. Al definir la expresión regular, he incluído varios paréntesis para realizar varias capturas:
const regexp = /(..) (.a.o)/g;
regexp.exec(text); // ["el gato", "el", "gato"]
regexp.exec(text); // ["El pato", "El", "pato"]
regexp.exec(text); // ["mo dato", "mo", "dato"]
regexp.exec(text); // null
En esta ocasión, el
El array devuelto por exec
Ten en cuenta que el .exec()
es un array especial que, a parte de funcionar como un array normal, tiene algunas propiedades extra que nos pueden ser de ayuda:
Propiedad | Descripción |
---|---|
.length | Como array, se puede consultar la longitud (coincidencia completa + capturas) |
.groups | Crea un objeto con los resultados de parentizaciones nombradas (ver más adelante) |
.index | Posición del |
.input | Texto .exec() . |
.indices | Si se usa el flag d , se incluye un |
Veamos el .exec()
en esta ocasión:
const text = "Hola Manz. El formato adecuado es 2022-08-15. Ignoraremos fechas en el formato 15-08-2022.";
const regexp = /([0-9]{4})-([0-9]{2})-([0-9]{2})/gd;
regexp.global; // true (el flag global está activado)
const result = regexp.exec(text); // ["2022-08-15", "2022", "08", "15"] index: 34
regexp.exec(text); // null
Observa que la segunda fecha del texto no tiene el mismo formato en el mismo orden, por lo que no es capturada. Si analizamos el .exec()
y guardado en result
, tendremos algo así:
result // ["2022-08-15", "2022", "08", "15"]
result.length // 4
result.index // 34
result.input === text // true
regexp.hasIndices // true
result.indices // [[34, 44], [34, 38], [39, 41], [42, 44]]
El result.indices
contiene varios result
. Así pues, 34
es la posición inicial de result[0]
, mientras que 44
es la posición final. En el siguiente array, 34
es la posición inicial de result[1]
, mientras que 38
es la posición final. Y así sucesivamente.
Recuerda que para tener esta propiedad .indices
necesitas tener activado el flag d
.
El array
.result.indices
también tiene una propiedad.groups
similar a la que posee.result
y que explicaremos en el siguiente apartado de Parentización nombrada.
Parentizaciones nombradas
Es posible asignarle un nombre a cada parentización realizada, de modo que sea más «humana» la forma de capturar elementos y gestionarlos después. Para ello, solo tenemos que añadir ?<nombre>
al inicio de la parentización, como se puede ver en el siguiente ejemplo:
const text = `Hola Manz. Son las 13:33:02, a las 18:45:00 te avisaré para que inicies stream.`;
const regexp = /(?<hours>[0-9]{2}):(?<mins>[0-9]{2}):(?<secs>[0-9]{2})/gd
const result = regexp.exec(text); // ["13:33:02", "13", "33", "02"]
En este caso, podremos ver que la propiedad .groups
no es undefined
, sino que tiene los textos de las parentizaciones capturadas:
result.groups // { hours: "13", mins: "33", secs: "02" }
result.index // 19
result.indices // [[19, 27], [19, 21], [22, 24], [25, 27]]
result.indices.groups // { hours: [19, 21], mins: [22, 24], secs: [25, 27] }