Espressioni Regolari - approfondimenti
Come rendere non-greedy i quantificatori
In metacaratteri 2 abbiamo visto come i quantificatori ?, +, * e { } siano greedy, perché trovano tutto quello che è possibile in base a quanto specificato nella regex.
A volte però si può desiderare che trovino il meno possibile, rendendoli non-greedy o, come a volte si trova scritto, lazy, cioè pigri.
Questo viene fatto semplicemente aggiungendo un ? subito dopo il quantificatore usato.
Consideriamo la regex (hi)+ e il testo:
cominciò a sghignazzare "hihihihihihi..." orribilmente
Essendo il quantificatore + greedy, essa troverà l'intera stringa hihihihihihi.
Invece con la regex (hi)+? si otterrà solo il primo hi.
Ecco i quantificatori nella loro forma non-greedy:
+?
*?
??
{min,max}?
Vediamo con un esempio come possono venire utili.
Supponiamo di voler individuare in un file HTML le parti di codice racchiuse dai tag <strong> e </strong>.
Se usassimo <strong>.*</strong> , a causa dell'avidità di .* otterremmo a volte risultati non voluti. In una linea di codice così:
CSS, dove la <strong>C</strong> significa Cascading
tutto andrebbe bene:
CSS, dove la <strong>C</strong> significa Cascading
Mentre in:
<strong>C</strong>ascading <strong>S</strong>tyle <strong>S</strong>heets
la nostra regex otterrebbe:
<strong>C</strong>ascading <strong>S</strong>tyle <strong>S</strong>heets
che è veramente troppo.
Se invece la regex è non-greedy, <strong>.*?</strong> si fermerà alla prima coppia di tag <strong> e </strong> trovata:
<strong>C</strong>ascading <strong>S</strong>tyle <strong>S</strong>heets
che è proprio quello che volevamo.
I modificatori
I primi metacaratteri che abbiamo studiato sono stati ^ e $ ed ora, per finire, torniamo a parlarne per approfondirne il significato.
Abbiamo visto che scrivere ^abc significa trovare abc solo se è all'inizio del testo di input, e che scrivere abc$ significa trovarlo solo se è alla fine. Se il testo in input è di una sola linea, come l'abbiamo visto nell'esempio di partenza, non c'è distinzione di significato tra "testo di input" e "linea". Ma se il testo in input è composto da più linee (cioè contiene al suo interno caratteri di nuova linea) che cosa indicano ^ e $ ? Solo l'inizio e la fine di tutto il testo, oppure l'inizio e la fine di ogni linea?
La risposta è che di default essi indicano l'inizio e la fine di tutto il testo. Avendo detto "di default" già capite che c'è modo di modificare questo comportamento.
Abbiamo anche visto (in metacaratteri 2) che il metacarattere punto . può, a seconda delle applicazioni, considerare o no il carattere di fine linea.
Con i modificatori possiamo "forzare" il modo in cui le regex eseguono la ricerca, indipendentemente dal comportamento di default in una particolare applicazione.
Essi possono essere inseriti in una regex usando la seguente notazione:
(?mod) per attivarli,
(?-mod) per disattivarli,
e mettendo al posto di mod uno dei seguenti caratteri: s , m , i , g , x .
Vediamone il significato:
(?s) : E' il single line mode. Ogni stringa viene vista come una sola linea, quindi i caratteri di nuova linea non vengono più considerati nel loro vero significato. Perciò anche il metacarattere punto . , qualunque fosse il suo comportamento di default, non li vede più in modo speciale e li accetta. I metacaratteri ^ e $ con questo modificatore non cambiano il loro comportamento di default e trovano solo l'inizio e la fine di tutto il testo.
Esemplificando, se si usa (?s) nella regex, l'espressione .*$ trova tutto il testo da quel punto fino alla fine del testo stesso.
(?m) : E' il multiple line mode. Il testo considerato viene visto come composto da linee multiple, così ^ e $ trovano rispettivamente l'inizio e la fine di ogni linea. Se, usando questo modificatore, si desidera ancora riferirsi solo all'inizio e alla fine di tutto il testo, si possono usare i caratteri speciali \A e \Z, rispettivamente.
Invece il metacarattere punto . con questo modificatore non cambia il suo comportamento di default. Perciò se desideriamo avere il testo composto da linee multiple e allo stesso tempo essere sicuri che il punto possa accettare ogni carattere, nuova linea compreso, useremo (?ms) .
E così abbiamo visto anche come si possono combinare insieme.
(?i) : E' il case insensitive mode. La ricerca non fa più distinzione tra maiuscole e minuscole. Quindi la regex (?i)pino potrà trovare sia pino che Pino.
(?g) : E' un modificatore che di default è attivo; disattivandolo con (?-g) tutti i quantificatori diventano non-greedy.
(?x) : E' il modificatore che toglie significato ad ogni spazio bianco che non sia escaped o dentro una classe di caratteri. Ciò permette di scrivere una regex su più righe per aumentarne la leggibilità e l'introduzione di commenti, preceduti in ogni riga dal carattere # . Ecco un esempio:
| (?x) | #la seguente è una regex per validare un input alfanumerico |
| ^ | #posizione a inizio linea |
| [a-zA-Z0-9]+ | #una stringa alfanumerica |
| $ | #posizione a fine linea |
Questo modificatore purtroppo è implementato raramente.
Le asserzioni
Vi sono altre due espressioni che possono facilitare molto la scrittura delle regex: in inglese sono dette lookahead (guarda avanti) e lookbehind (guarda indietro); nel loro insieme vengono dette lookaround (guarda intorno). Come vedremo subito, esse non servono tanto a trovare qualcosa ma ad asserire se quel qualcosa può essere o no trovato.
Per questo motivo vengono anche chiamate asserzioni.
Sono estensioni delle Espressioni Regolari del Perl 5 e purtroppo non sono implementate da tutte le applicazioni che permettono l'uso delle regex. E' un vero peccato perché le asserzioni permettono di scrivere regex più semplici e che possono realizzare compiti altrimenti irrealizzabili. Un'applicazione (non freeware) che le implementa è PowerGREP (www.powergrep.com/).
Le asserzioni possono essere positive per vedere se qualcosa può essere trovato, oppure negative per vedere se qualcosa non può essere trovato.
Sintassi del lookahead:
per le asserzioni positive (?=qualcosa) , per quelle negative (?!qualcosa) .
Sintassi del lookbehind:
per le asserzioni positive (?<=qualcosa) , per quelle negative (?<!qualcosa) .
Supponiamo di voler trovare le "q" solo se sono seguite dalla "u" (ma senza trovare la "u").
La soluzione è molto semplice: scrivo il carattere q seguito dal lookahead (?=u), che controlla se dopo questa "q" esiste o no una "u". Se esiste, la "q" (e solo lei) viene selezionata, altrimenti no. La regex dunque è q(?=u) .
Es: Ora questa guerra in Iraq è quasi un incubo.
Se al contrario avessi dovuto trovare solo le "q" non seguite da una "u", avrei scritto q(?!u) .
Es: Ora questa guerra in Iraq è quasi un incubo.
Supponiamo ora di voler trovare le "u" che sono precedute da una "q": usiamo il lookbehind nella sua forma positiva: (?<=q)u .
Es: La causa di tutto quel rumore è quasi un rebus.
Nella forma negativa (?<!q)u trova le "u" che non sono precedute da una "q".
Es: La causa di tutto quel rumore è quasi un rebus.
Svolgi gli esercizi del Gruppo 8