Talvolta abbiamo bisogno di trovare soltanto quei riscontri per un pattern che sono seguiti o preceduti da un altro pattern.
Esiste a questo scopo una sintassi speciale denominata “lookahead” e “lookbehind”, indicata complessivamente con il termine “lookaround”.
Per cominciare troviamo il prezzo in una stringa come 1 turkey costs 30€. In parole semplici: un numero seguito dal simbolo di valuta €.
Lookahead
La sintassi è: X(?=Y), che significa “cerca X, ma trova la corrispondenza solo se seguita da Y”. Possiamo sostituire X e Y con un pattern qualsiasi.
Per un numero intero seguito da €, la regexp sarà \d+(?=€):
let str = "1 turkey costs 30€";
alert( str.match(/\d+(?=€)/) ); // 30, viene ignorato il numero 1 in quanto non seguito da €
Si noti che la parte lookahead è solo un test e pertanto il contenuto tra parentesi (?=...) non è incluso nel risultato 30.
Quando cerchiamo X(?=Y) l’interprete dell’espressione regolare trova X e successivamente verifica anche la presenza di Y subito dopo di esso. In caso contrario la corrispondenza potenziale viene scartata e la ricerca prosegue.
Sono possibili test più complessi, ad esempio X(?=Y)(?=Z) significa:
- Trova
X. - Verifica se
Ysia subito dopoX(non proseguire in caso contrario). - Verifica se
Zsia anch’esso dopoX(non proseguire in caso contrario). - Se entrambi i test trovano riscontro considera
Xuna corrispondenza, diversamente continua la ricerca.
In altre parole, questo pattern significa che stiamo cercando X seguito sia da Y sia da Z.
Il che è possibile solo se i pattern Y e Z non si escludono a vicenda.
Per esempio, \d+(?=\s)(?=.*30) cerca \d+ seguito da uno spazio (?=\s), e poi c’è 30 da qualche parte dopo di esso (?=.*30):
let str = "1 turkey costs 30€";
alert( str.match(/\d+(?=\s)(?=.*30)/) ); // 1
Nella nostra stringa trova esatta corrispondenza nel numero 1.
Lookahead negativo
Supponiamo invece di volere nella stessa stringa solo la quantità, non il prezzo. Quindi il numero \d+, NON seguito da €.
A questo scopo può essere applicato un lookahead negativo.
La sintassi è: X(?!Y), significa “cerca X, ma solo se non seguito da Y”.
let str = "2 turkeys cost 60€";
alert( str.match(/\d+\b(?!€)/g) ); // 2 (il prezzo non costituisce corrispondenza)
Lookbehind
Lookahead permette di porre una condizione per “quello che segue”.
Lookbehind è simile, ma cerca quello che precede. Consente quindi di trovare una corrispondenza per un pattern solo se c’è qualcosa prima di esso.
La sintassi è:
- Lookbehind positivo:
(?<=Y)X, trovaX, ma solo se c’èYprima di esso. - Lookbehind negativo:
(?<!Y)X, trovaX, ma solo se non c’è alcunYprima di esso.
Cambiamo, ad esempio, il prezzo in dollari USA. Il segno del dollaro è posto di solito prima del numero, per cercare pertanto $30 useremo (?<=\$)\d+ un importo preceduto da $:
let str = "1 turkey costs $30";
// facciamo l'escape al segno del dollaro \$
alert( str.match(/(?<=\$)\d+/) ); // 30 (salta il numero senza segno di valuta)
Se abbiamo bisogno della quantità, il numero, non preceduto da $, allora possiamo usare il lookbehind negativo (?<!\$)\d+:
let str = "2 turkeys cost $60";
alert( str.match(/(?<!\$)\b\d+/g) ); // 2 (il risultato non include il prezzo)
Gruppi di acquisizione
Generalmente il contenuto dentro le parentesi di lookaround non diventa parte del risultato.
Nel pattern \d+(?=€), ad esempio, il segno € non viene acquisito nella corrispondenza. È del tutto normale: stiamo cercando il numero \d+, mentre (?=€) è solo un test che indica che il numero dovrebbe essere seguito da €.
In alcune situazioni, tuttavia, potremmo voler catturare anche l’espressione del lookaround, o una parte di essa. Questo è possibile: è sufficiente racchiudere la parte desiderata all’interno di parentesi aggiuntive.
Nell’esempio sotto, il segno di valuta (€|kr) viene acquisito insieme all’importo:
let str = "1 turkey costs 30€";
let regexp = /\d+(?=(€|kr))/; // parentesi addizionali intorno €|kr
alert( str.match(regexp) ); // 30, €
Stesso discorso per il lookbehind:
let str = "1 turkey costs $30";
let regexp = /(?<=(\$|£))\d+/;
alert( str.match(regexp) ); // 30, $
Riepilogo
Il lookahead e il lookbehind (comunemente denominati con il termine “lookaround”) sono utili quando vogliamo trovare qualcosa in base a ciò viene prima o dopo di esso.
Nel caso di espressioni regolari semplici potremmo ottenere lo stesso risultato manualmente. In altre parole: troviamo ogni riscontro, e quindi filtriamo i risultati in base alla posizione nel ciclo iterativo.
Ricordiamoci che str.match (senza il flag g) e str.matchAll (sempre) restituiscono i risultati in un array con la proprietà index, conosciamo pertanto l’esatta posizione della corrispondenza e possiamo stabilirne il contesto.
Generalmente, però, il lookaround è più efficiente.
Tipi di lookaround:
| Pattern | Tipo | Riscontri |
|---|---|---|
X(?=Y) |
Lookahead positivo | X se seguito da Y |
X(?!Y) |
Lookahead negativo | X se seguito da Y |
(?<=Y)X |
Lookbehind positivo | X se dopo Y |
(?<!Y)X |
Lookbehind negativo | X se dopo Y |
Commenti
<code>, per molte righe – includile nel tag<pre>, per più di 10 righe – utilizza una sandbox (plnkr, jsbin, codepen…)