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
Y
sia subito dopoX
(non proseguire in caso contrario). - Verifica se
Z
sia anch’esso dopoX
(non proseguire in caso contrario). - Se entrambi i test trovano riscontro considera
X
una 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’èY
prima di esso. - Lookbehind negativo:
(?<!Y)X
, trovaX
, ma solo se non c’è alcunY
prima 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 |