Espressioni Regolari - I riferimenti
Uso delle sotto-espressioni coi riferimenti
Abbiamo già accennato (in metacaratteri 3) alla esistenza delle backreference, letteralmente "riferimenti all'indietro", che qui chiameremo brevemente riferimenti.
Avendo raggruppato parti di regex in sotto-espressioni con le parentesi tonde, possiamo riferirci ai loro rispettivi risultati, nell'ordine, da sinistra verso destra, con \1, \2, \3, ecc.
E' importante notare che quando scriviamo \1 ci riferiamo al risultato della prima sotto-espressione, non alla sotto-espressione stessa.
Chiariamo con un semplice esempio e osserviamo la regex ([6-9])a\1 .
La sotto-espressione ([6-9]) trova 6, 7, 8 oppure 9 e ne memorizza il valore.
La regex poi ci dice che questo valore deve essere seguito da una "a", quindi otteniamo 6a, 7a, 8a oppure 9a.
Infine nella regex compare \1: è come se dicesse "ora devo trovare di nuovo quello che ho memorizzato prima con ([6-9]) ". Cioè, se ho memorizzato il 6 devo ritrovare il 6, se ho memorizzato il 7 devo ritrovare il 7, e così via.
Quindi la regex considerata può trovare 6a6, 7a7, 8a8 oppure 9a9.
Ora dovrebbe essere chiaro quanto detto sopra, cioè che \1 si riferisce al risultato della prima sotto-espressione, non alla sotto-espressione stessa.
Infatti, se \1 fosse solo una sorta di abbreviazione per [6-9], nei risultati dovrei aspettarmi anche 6a7, 6a8, 6a9, 7a6, 7a8, ecc.
Ma questo non è il significato dei riferimenti.
Se occorre, i riferimenti possono essere ripetuti: ([6-9])a\1\1 troverà:
6a66, 7a77, 8a88 oppure 9a99.
Vediamo un caso più interessante. Supponiamo di voler trovare, in un file HTML, tutti i tag che hanno un'apertura e una chiusura, con il loro eventuale contenuto. Considerando ad esempio il codice HTML della prima frase di questa pagina, vogliamo scrivere una regex in grado di produrre le seguenti parti evidenziate:
Abbiamo già accennato
(in <a href="metacar3.html">metacaratteri 3</a>)
alla esistenza delle
<span class="itc">backreferences</span>,
letteralmente "riferimenti all'indietro", che qui chiameremo brevemente
<span class="itc">riferimenti</span>.
Il primo simbolo da trovare è "< ", seguito da una lettera minuscola e da eventuali caratteri alfanumerici che costituiscono il nome di quel particolare tag.
E' utile memorizzare il nome del tag creando una sotto-espressione, perché ci servirà di nuovo alla fine della regex per scrivere il tag di chiusura.
Quindi l'inizio della nostra regex sarà: <([a-z][a-z0-9]*) .
Ora dobbiamo inserire la possibilità di altri caratteri, per definire eventuali attributi del tag, ed poi il carattere "> "; vi ricordo a questo punto quello che si è detto nel paragrafo "I metacaratteri quantificatori sono greedy" (in metacaratteri 2): dobbiamo far sì che la regex non possa accettare più di un carattere "> ", ma debba accontentarsi del primo che trova. Questa parte di regex sarà allora: [^>]*> .
Tra il tag di apertura e quello di chiusura può esserci del testo qualunque: indichiamo questa eventualità con .*? .
Siamo arrivati al tag di chiusura; esso inizia con < seguito da / e dal nome del tag stesso. Ecco che viene utile averlo memorizzato con l'uso di una sotto-espressione: usiamo semplicemente il suo riferimento \1. Non ci resta che chiudere con un >.
La parte di regex che si occupa del tag di chiusura è quindi </\1> .
In conclusione, mettendo insieme tutti i pezzi, la regex cercata è:
<([a-z][a-z0-9]*)[^>]*>.*?</\1>
La posizione delle parentesi ( )
Bisogna fare attenzione a come si posizionano le parentesi tonde quando si costruisce una sotto-espressione, perché si possono avere brutte sorprese nell'uso dei riferimenti.
Supponiamo per esempio che la regex ([AOMR]+) applicata su un certo testo abbia trovato ROMA. Se faccio la stessa cosa con la regex ([AOMR])+, sempre sullo stesso testo, trovo ancora ROMA.
Però il riferimento \1 nel primo caso è davvero ROMA, mentre nel secondo caso è A .
Non è difficile intuire il perché: nel secondo caso la memorizzazione avviene un carattere alla volta, perché la reiterazione data dal + è fatta dopo il ritrovamento e la memorizzazione di uno di quei caratteri. Dato che questa memorizzazione è fatta ogni volta col risultato della stessa sotto-espressione, viene di volta in volta sovrascritta; cioè prima trova e memorizza R, poi trova e memorizza O cancellando la memoria di R, poi trova e memorizza M cancellando la memoria di O, infine trova e memorizza A cancellando la memoria di M. Ecco perché \1 nel secondo caso vale A .
Svolgi gli esercizi del Gruppo 7