Cos'è un'espressione regolare?
Mi sembra palese che un'espressione regolare sia... un'espressione regolare! Niente di più di quello che si intende in italiano con questo termine, solo con una sfumatura di stringa: una porzione di testo che, pur non ripetendosi esattamente uguale, è possibile ricondurre ad uno schema specifico. Ad esempio, un indirizzo email può essere ricondotto a questo schema:- una sequenza di due o più caratteri alfanumerici o underscore o punti;
- il simbolo @
- un'altra sequenza di caratteri alfanumerici;
- un punto;
- una sequenza limitata scelta tra un insieme finito di possibilità.
Cos'è un'espressione regolare?
Mi sembra palese che un'espressione regolare sia... un'espressione regolare! Niente di più di quello che si intende in italiano con
questo termine, solo con una sfumatura di stringa: una porzione di testo che, pur non ripetendosi esattamente uguale, è possibile
ricondurre ad uno schema specifico. Ad esempio, un indirizzo email può essere ricondotto a questo schema:
- una sequenza di due o più caratteri alfanumerici o underscore o punti;
- il simbolo @
- un'altra sequenza di caratteri alfanumerici;
- un punto;
- una sequenza limitata scelta tra un insieme finito di possibilità.
Ed ora andiamo un pò più nello specifico...
Descrizione del linguaggio Regular Expression
Le espressioni regolari vengono definite attraverso determinati pattern, scritti usando delle specifiche regole di sintassi e determinati
caratteri "speciali", che svolgono funzioni disparate e interessanti. Si può considerare questo come un linguaggio a parte, che deve anch'esso
essere appreso al fine di sfruttare fino in fondo le potenzialità offerte al programmatore. Ecco un piccolo esempio di pattern:
[aeiou]Questo rappresenta una qualsiasi delle vocali: impiegandolo in un metodo di sostituzione, si potrebbero quindi sostituire tutte le vocali non accentate di un testo con qualcos'altro. In questo caso particolare si è notato come le parentesi quadre svolgano una funzione di raggruppamento di altri caratteri: sono dei caratteri "jolly", che vengono interpretati dal parser come direttive di comportamento. Allo stesso modo, si possono raggruppare tali jolly sotto alcune classificazioni:
- Caratteri di escape : vengono utilizzati per indicare singoli caratteri e referenziare caratteri non stampabili, ossia di controllo. Inoltre possono fornire versioni "normali" dei jolly che assumono particolare significato nelle espressioni. Esempio: \t (tabulazione), \n(a capo), \[ (una parentesi quadra, che non viene considerata come jolly, ma come semplice carattere);
- Classi di caratteri : rappresentano insiemi di caratteri. Nel caso delle parentesi quadre, non è necessario utilizzare sequenze di escape per i jolly tranne che per il carattere ] e -. Esempio: [aei()ou-] (uno qualsiasi tra i caratteri: a, e, i, o, u, (, ) e -)
- Asserzioni atomiche di ampiezza zero : specificano dove debba trovarsi l'espressione da cercare/sostituire. Esempio: ^ac (la stringa "ac" all'inizio di una riga)
- Qualificatori : specificano quante volte una determinata espressione debba apparire all'interno della stringa. Sono distinti in due gruppi: greedy e lazy. I membri del primo confrontano sempre quanti più caratteri possibili; quelli del secondo invece quanti meno possibili. Esempio: \w* (zero o più lettere vicine)
- Costruttori di raggruppamento : servono per raggruppare una o più espressioni assieme; assumono particolare utilità in combinazione con i qualificatori, ma anche nei metodi di ricerca, in quanto possono "marcare" una determinata sottostringa con una chiave che potrà essere usata in seguito per recuperare quell'espressione. Esempio: (?<inizio>^\w+)
- Sostituzioni : indicano di riprendere un dato gruppo di espressioni marcato nel pattern di sostituzione con un costruttore di raggruppamento. Esempio: se il pattern di sostituzione indica di sostituire "(?<inizio>^\w+)" con "Linea: ${inizio}", tutte le parole di almeno una lettere a inizio riga saranno sostituite con la stringa "Linea: " seguita dalla parola
- Costruttori di riferimento all'indietro : permettono di referenziare un particolare gruppo precedentemente definito nell'espressione. Esempio: (?<grp>\s+\w+\s+)ciaok<grp> (una parola separata da spazi, seguita da "ciao", seguita dalla stessa parola di prima)
- Costruttori di alternanza : forniscono un modo per specificare alternative. Esempio : (Ciao|Buongiorno) Totem! (cerca una di queste possibilità "Ciao Totem!" e "Buongiorno Totem!")
La classe Regex
La classe che rappresenta un'espressione regolare è Regex, facente parte del namespace System.Text.RegularExpressions. Ha due costruttori
ed entrambi hanno come primo parametro un pattern. Il secondo paremetro consiste di un enumeratore codificato a bit che indica
le opzioni con le quali debba essere eseguita la ricerca; i valori più utili sono: Compiled (compila l'espressione regolare, rendendola
più veloce, ma impiega più memoria), IgnoreCase (disattiva il case sensitive), IgnorePatternWithSpace (ignora gli spazi bianchi epliciti,
ossia quelli non marcati da un carattere di escape s, e abilita i commenti introdotti da # nel testo da anlizzare), Multiline (la ricerca
è svolta su un testo di più righe: i caratteri speciali $ e ^ cambiano il loro significato), Singleline (la ricerca è svolta su un testo
di una sola riga) e RightToLeft (il testo viene analizzato da destra a sinistra). Le funzioni più importanti in assoluto sono IsMatch, che controlla la validità di
un'espressione regolare nella stringa data e restituisce True o False, Match, che esegue la stessa cosa e restituisce un oggetto Match, e
finine Matches, che restituisce una collezione a tipizzazione forte MatchCollection. Gli altri metodi utili di Regex:
- Escape(S) : sostituisce tutti i caratteri speciali nella stringa con caratteri di escape, quindi restituisce la nuova stringa
- GetGroupNames / GetGroupNumbers : restituisce un array di stringhe o interi che determinano i vari gruppi definiti nel pattern
- GetGroupNameFromNumber / GetGroupNumberFromName : restituisce il nome di un gruppo a partire dall'indice o viceversa
- Replace(T, S) : analizza il testo T con le opzioni definite in precedenza e sostituisce tutte le occorrenze dell'espressione regolare INSERT IGNOREia come pattern nel costruttore con la stringa S, che opzionalmente può contenere Sostituzioni. Alla fine dell'operazione viene restituita la stringa risultante
- Split(S) : lavora come la funzione String.Split, solo che il separatore è costituito dall'espressione regolare
- Unescape(S) : esegue l'opzione inversa a Escape, ossia sostituisce tutti i caratteri di escape con caratteri normali
Le classi Match e MatchCollection
Un oggetto di tipo Match è il risultato della funzione Regex.Match, mentre lo stesso avviene per Regex.Matches con MatchCollection. Un Match
è una corrispondenza dell'espressione trovata all'interno della stringa da anlizzare. Se non viene trovata nessuna corrispondenza, viene
restituito un oggetto Match la cui proprietà Success è impostata a False. Ecco una lista dei membri più usati:
- Groups(N) : restituisce un oggetto di tipo GroupCollection, del quale ogni elemento rappresenta un singolo gruppo racchiuso da parentesi tonde. È possibile prelevare un gruppo usando un argomento N che può essere un indice intero o una stringa nel caso si siano usati dei costruttori di raggruppamento. Ogni oggetto Group ha alcune proprietà: Index restituisce l'indice della sottostringa nella stringa intera, Length la sua lunghezza, Value il suo valore e Success se quel gruppo è presente oppure no
- Index : l'indice del primo carattere che inizia la sottoespressione trovata all'interno della stringa intera
- Length : la lunghezza della sottostringa trovata
- Success : indica se la ricerca ha avuto successo oppure no
- Value : restituisce tutta la sottostringa
Un esempio pratico
Ecco un esempio:
Module Module1 Sub Main() Dim Text As String = _ "Questo è un testo intervallato da alcuni spazi e " & vbCrLf & _ "un a capo. Inoltre, viene supportata anche la punteggiatura." 'Questa espressione ricerca tutti gli insiemi di almeno un 'carattere separati dal resto del testo da spazi bianchi o 'segni di punteggiatura. 'Ergo: cerca tutte le singole parole Dim R As New Regex("w+") Dim Matches As MatchCollection = R.Matches(Text) For Each M As Match In Matches 'chr(34) rappresenta il carattere 34 della tabella ASCII, 'ossia le virgolette Console.WriteLine("All'indice {0}, la sottostringa {1}{2}{1}", _ M.Index, Chr(34), M.Value) Next Console.ReadKey() End Sub End ModuleE un esempio più complesso:
Module Module2 Sub Main() Dim Text As String = String.Format( _ "Sub Prova(){0}" & _ " Dim Int As Int32 = 4{0}" & _ " Dim Str As String = {1}Ciao {1}{0}" & _ " For I As Int32 = Int To 48{0}" & _ " Dim Str2 As String = Str & I{0}" & _ " Console.WriteLine(Str2){0}" & _ " Next{0}" & _ "End Sub", vbCrLf, Chr(34)) 'Questa espressione ricerca tutte le dichiarazioni di 'variabili nel codice sopra Dim R As New Regex( _ "\s*Dim\s+(?<Name>\w+)\s+As\s+(?<Type>\w+)(\s+=\s+(?<Value>[\w"" ]+))?", _ RegexOptions.Multiline) 'Ecco la spiegazione di ogni parte del codice: '\s* : le dichiarazioni possono essere a inizio riga o precedute ' da tabulazioni o spazi. Perciò si deve usare *, che ' indica zero o più ripetizioni ' 'Dim : ovviamente deve essere presenta la keyword Dim ' '(?<Name>\w+) : dopo Dim viene il nome della variabile, ' rappresentato con w+, ossia almeno un carattere o ' underscore. Questo gruppo è chiamato Name, cosicchè ' lo potremo riprendere in seguito ' 'As : la clausola As, separata da almeno uno spazio (s+) ' del nome e dal tipo della variabile ' '(?<Type>\w+) : come Name ' '(...)? : tutto quello che viene ora è posto in una coppia di ' parentesi tonde per poter usare il qualificatore ?. Quindi ' tutta questa espressione può apparire 0 o una volta, ' ossia è opzionale. Si tratta dell'inizializzazione ' della variabile in-line ' '\s+=\s+ : un segno uguale, separato da spazi dal resto ' '(?<Value>[\w" ]+) : il valore della variabile può ' contenere lettere, underscore, spazi bianchi singoli ' o virgolette.Nell'esempio ci sono due virgolette ' poichè ci si trova in una stringa e una ' sola sarebbe interpretata come fine della stringa. ' Due di seguito vengono lette invece come una ' virgoletta nel testo Dim Matches As MatchCollection Matches = R.Matches(Text) For Each M As Match In Matches Console.Write("- Nome: {1}{0} Tipo: {2}{0}", _ vbCrLf, M.Groups("Name").Value, M.Groups("Type").Value) If M.Groups("Value").Success Then Console.WriteLine(" Valore: {0}", M.Groups("Value").Value) End If Next Console.ReadKey() End Sub End ModulePoiché possiamo riconoscere un indirizzo e-mail da queste caratteristiche, possiamo anche creare una espressione regolare che lo rappresenti. Esiste un linguaggio a parte per le espressioni regolari, che è uno standarda e viene implementato allo stesso modo in tutti i linguaggi di programmazione e di scripting esistenti. Per questo motivo, vale la pena spendere qualche capitolo per introdurre tale linguaggio.
Ed ora andiamo un pò più nello specifico...
A cura di: Il Totem