A veces necesitamos buscar únicamente aquellas coincidencias donde un patrón es precedido o seguido por otro patrón.
Existe una sintaxis especial para eso llamadas “lookahead” y “lookbehind” (“ver delante” y “ver detrás”), juntas son conocidas como “lookaround” (“ver alrededor”).
Para empezar, busquemos el precio de la cadena siguiente 1 pavo cuesta 30€
. Eso es: un número, seguido por el signo €
.
Lookahead
La sintaxis es: X(?=Y)
. Esto significa “buscar X
, pero considerarlo una coincidencia solo si es seguido por Y
”. Puede haber cualquier patrón en X
y Y
.
Para un número entero seguido de €
, la expresión regular será \d+(?=€)
:
let str = "1 pavo cuesta 30€";
alert( str.match(/\d+(?=€)/) ); // 30, el número 1 es ignorado porque no está seguido de €
Tenga en cuenta que “lookahead” es solamente una prueba, lo contenido en los paréntesis (?=...)
no es incluido en el resultado 30
.
Cuando buscamos X(?=Y)
, el motor de expresión regular encuentra X
y luego verifica si existe Y
inmediatamente después de él. Si no existe, entonces la coincidencia potencial es omitida y la búsqueda continúa.
Es posible realizar pruebas más complejas, por ejemplo X(?=Y)(?=Z)
significa:
- Encuentra
X
. - Verifica si
Y
está inmediatamente después deX
(omite si no es así). - Verifica si
Z
está también inmediatamente después deX
(omite si no es así). - Si ambas verificaciones se cumplen, el
X
es una coincidencia. De lo contrario continúa buscando.
En otras palabras, dicho patrón significa que estamos buscando por X
seguido de Y
y Z
al mismo tiempo.
Eso es posible solamente si los patrones Y
y Z
no se excluyen mutuamente.
Por ejemplo, \d+(?=\s)(?=.*30)
busca un \d+
que sea seguido por un espacio (?=\s)
y que también tenga un 30
en algún lugar después de él (?=.*30)
:
let str = "1 pavo cuesta 30€";
alert( str.match(/\d+(?=\s)(?=.*30)/) ); // 1
En nuestra cadena eso coincide exactamente con el número 1
.
Lookahead negativo
Digamos que queremos una cantidad, no un precio de la misma cadena. Eso es el número \d+
NO seguido por €
.
Para eso se puede aplicar un “lookahead negativo”.
La sintaxis es: X(?!Y)
, que significa “busca X
, pero solo si no es seguido por Y
”.
let str = "2 pavos cuestan 60€";
alert( str.match(/\d+\b(?!€)/g) ); // 2 (el precio es omitido)
Lookbehind
Ten en cuenta: Lookbehind no está soportado en navegadores que no utilizan V8, como Safari, Internet Explorer.
“lookahead” permite agregar una condición para “lo que sigue”.
“Lookbehind” es similar. Permite coincidir un patrón solo si hay algo anterior a él.
La sintaxis es:
- Lookbehind positivo:
(?<=Y)X
, coincideX
, pero solo si hayY
antes de él. - Lookbehind negativo:
(?<!Y)X
, coincideX
, pero solo si no hayY
antes de él.
Por ejemplo, cambiemos el precio a dólares estadounidenses. El signo de dólar usualmente va antes del número, entonces para buscar $30
usaremos (?<=\$)\d+
: una cantidad precedida por $
:
let str = "1 pavo cuesta $30";
// el signo de dólar se ha escapado \$
alert( str.match(/(?<=\$)\d+/) ); // 30 (omite los números aislados)
Y si necesitamos la cantidad (un número no precedida por $
), podemos usar “lookbehind negativo” (?<!\$)\d+
:
let str = "2 pavos cuestan $60";
alert( str.match(/(?<!\$)\b\d+/g) ); // 2 (el precio es omitido)
Atrapando grupos
Generalmente, los contenidos dentro de los paréntesis de “lookaround” (ver alrededor) no se convierten en parte del resultado.
Ejemplo en el patrón \d+(?=€)
, el signo €
no es capturado como parte de la coincidencia. Eso es esperado: buscamos un número \d+
, mientras (?=€)
es solo una prueba que indica que debe ser seguida por €
.
Pero en algunas situaciones nosotros podríamos querer capturar también la expresión en “lookaround”, o parte de ella. Eso es posible: solo hay que rodear esa parte con paréntesis adicionales.
En los ejemplos de abajo el signo de divisa (€|kr)
es capturado junto con la cantidad:
let str = "1 pavo cuesta 30€";
let regexp = /\d+(?=(€|kr))/; // paréntesis extra alrededor de €|kr
alert( str.match(regexp) ); // 30, €
Lo mismo para “lookbehind”:
let str = "1 pavo cuesta $30";
let regexp = /(?<=(\$|£))\d+/;
alert( str.match(regexp) ); // 30, $
Resumen
Lookahead y lookbehind (en conjunto conocidos como “lookaround”) son útiles cuando queremos hacer coincidir algo dependiendo del contexto antes/después.
Para expresiones regulares simples podemos hacer lo mismo manualmente. Esto es: coincidir todo, en cualquier contexto, y luego filtrar por contexto en el bucle.
Recuerda, str.match
(sin el indicador g
) y str.matchAll
(siempre) devuelven las coincidencias como un array con la propiedad index
, así que sabemos exactamente dónde están dentro del texto y podemos comprobar su contexto.
Pero generalmente “lookaround” es más conveniente.
Tipos de “lookaround”:
Patrón | Tipo | Coincidencias |
---|---|---|
X(?=Y) |
lookahead positivo | X si está seguido por Y |
X(?!Y) |
lookahead negativo | X si no está seguido por Y |
(?<=Y)X |
lookbehind positivo | X si está después de Y |
(?<!Y)X |
lookbehind negativo | X si no está después de Y |