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 subject:1 pavo cuesta 30€. Eso es: un número, seguido por el signo subject:€.
La sintaxis es: pattern:X(?=Y). Esto significa "buscar pattern:X, pero considerarlo una coincidencia solo si es seguido por pattern:Y". Puede haber cualquier patrón en pattern:X y pattern:Y.
Para un número entero seguido de subject:€, la expresión regular será pattern:\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 pattern:(?=...) no es incluido en el resultado match:30.
Cuando buscamos pattern:X(?=Y), el motor de expresión regular encuentra pattern:X y luego verifica si existe pattern: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 pattern:X(?=Y)(?=Z) significa:
- Encuentra
pattern:X. - Verifica si
pattern:Yestá inmediatamente después depattern:X(omite si no es así). - Verifica si
pattern:Zestá también inmediatamente después depattern:X(omite si no es así). - Si ambas verificaciones se cumplen, el
pattern:Xes una coincidencia. De lo contrario continúa buscando.
En otras palabras, dicho patrón significa que estamos buscando por pattern:X seguido de pattern:Y y pattern:Z al mismo tiempo.
Eso es posible solamente si los patrones pattern:Y y pattern:Z no se excluyen mutuamente.
Por ejemplo, pattern:\d+(?=\s)(?=.*30) busca un pattern:\d+ que sea seguido por un espacio pattern:(?=\s) y que también tenga un 30 en algún lugar después de él pattern:(?=.*30):
let str = "1 pavo cuesta 30€";
alert( str.match(/\d+(?=\s)(?=.*30)/) ); // 1En nuestra cadena eso coincide exactamente con el número 1.
Digamos que queremos una cantidad, no un precio de la misma cadena. Eso es el número pattern:\d+ NO seguido por subject:€.
Para eso se puede aplicar un "lookahead negativo".
La sintaxis es: pattern:X(?!Y), que significa "busca pattern:X, pero solo si no es seguido por pattern:Y".
let str = "2 pavos cuestan 60€";
alert( str.match(/\d+\b(?!€)/g) ); // 2 (el precio es omitido)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:
pattern:(?<=Y)X, coincidepattern:X, pero solo si haypattern:Yantes de él. - Lookbehind negativo:
pattern:(?<!Y)X, coincidepattern:X, pero solo si no haypattern:Yantes 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 pattern:(?<=\$)\d+: una cantidad precedida por subject:$:
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 subject:$), podemos usar "lookbehind negativo" pattern:(?<!\$)\d+:
let str = "2 pavos cuestan $60";
alert( str.match(/(?<!\$)\b\d+/g) ); // 2 (el precio es omitido)Generalmente, los contenidos dentro de los paréntesis de "lookaround" (ver alrededor) no se convierten en parte del resultado.
Ejemplo en el patrón pattern:\d+(?=€), el signo pattern:€ no es capturado como parte de la coincidencia. Eso es esperado: buscamos un número pattern:\d+, mientras pattern:(?=€) es solo una prueba que indica que debe ser seguida por subject:€.
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 pattern:(€|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, $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 pattern: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 | pattern:X si está seguido por pattern:Y |
X(?!Y) |
lookahead negativo | pattern:X si no está seguido por pattern:Y |
(?<=Y)X |
lookbehind positivo | pattern:X si está después de pattern:Y |
(?<!Y)X |
lookbehind negativo | pattern:X si no está después de pattern:Y |