Questo sito utilizza cookies solo per scopi di autenticazione sul sito e nient'altro. Nessuna informazione personale viene tracciata. Leggi l'informativa sui cookies.
Username: Password: oppure
Guida al Visual Basic .NET - Espressioni regolari

Guida al Visual Basic .NET

Capitolo 93° - Espressioni regolari

<< Precedente Prossimo >>

 

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à.
Poiché 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...


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!")
Ecco una lista di tutte le espressioni jolly più importanti: Caratteri di escape
\a : campanello \b : tra parentesi quadre e nelle sostituzioni, rappresenta il backspace, altrimenti il limite di una parola \t : tabulazione \r: ritorno carrello \v : tabulazione verticale \f : avanzamento pagina \n : a capo \e : escape 00 : un carattere ASCII espresso in notazione ottale. Deve sempre avere tre cifre. Ad esempio 40 rappresenta uno spazio (32 in decimale) x00 : un carattere ASCII espresso in notazione esadecimale. Lo spazio, ad esempio, è x20 * : quando il backslash è seguito da un carattere che sarebbe un jolly, rappresenta quel carattere normalmente. * rappresenta un asterisco (e non ha più quindi la funzione di qualificatore)Classi di caratteri . : qualsiasi carattere eccetto l'a capo [abcde] : uno qualsiasi dei caratteri specificati all'interno delle parentesi [a-z] : il trattino rappresenta una serie di caratteri consecutivi. In questo caso, tutte le lettere minuscole da a a z [a-z-[aeiuo]] : quando una classe di caratteri appare nidificata all'interno di un'altra e preceduta da un segno meno, allora si considera la serie prima espressa escludendo i caratteri specificati nella seconda coppia di parentesi. In questo caso, tutte le consonanti minuscole \w : un carattere alfanumerico o un underscore (comprende anche caratteri accentati) \W : negazione di w, ossia tutti i caratteri non alfanumerici, inclusi quelli accentati \s : uno spazio bianco. Rappresenta contemporaneamente uno qualsiasi tra uno spazio normale, \t, \r, \v, \f e $ \S : negazione di s, ossia tutti i caratteri che non sono uno spazio bianco \d : una cifra decimale \D : negazione di dAsserzioni atomiche di ampiezza zero ^ : inizio di una riga $ : fine di una riga (se l'opzione Multiline è attiva, come si vedrà in seguito) o fine della stringa \A : inizio della stringa (sempre, anche se Multiline è attivo) \Z : fine della stringa (sempre, anche se Multiline è attivo); nel caso ci sia un carattere di a capo, rappresenta la posizione immediatamente precedente \z : come \Z, solo che comprende anche l'a capo \b : limite di una stringa, ossia il primo o l'ultimo carattere di una parola delimitata da spazi bianchi o altri caratteri di punteggiatura \B : negazione di \bQualificatori * : zero o più corrispondenze. Ad esempio s*Public indica la parola Public preceduta da zero o più caratteri di spazio + : una o più corrispondenze ? : zero o una corrispondenza {n} : esattamente n corrispondenze. Ad esempio (w+){6} indica sei parole consecutive di almeno un carattere {n,} : almeno n corrispondenze. Ad esempio *{1,} è uguale a *+ e indica una serie di asterischi {n,m} : da n a m corrispondenze *? : la prima corrispondenza con il minor numero di ripetizioni +? : la prima corrisponde con il minor numero di ripetizioni, ma almeno una ?? : se possibile zero, altrimenti una corrispondenza {n}? {n,}? {n,m}? : come sopraCostruttori di raggruppamento (stringa) : una stringa o un'espressione. Le parentesi vengono numerate: la prima ha indice 1. Ci si può riferire ad esse anche con l'indice (?<nome>expr) : cattura l'espressione "expr" e le assegna il nome "nome" (?=expr) : continua il confronto solo se l'espressione a destra corrisponde a quella data. Ad esempio w+(?=,) indica una parola seguita da una virgola, ma senza comprendere la virgola (?!expr) : continua il confronto solo se l'espressione a destra non corrisponde a quella data (?<=expr) : continua il confronto solo se l'espressione a sinistra corrisponde a quella data. Ad esempio (?<=,)w+ rappresenta una parola che segue una virgola, ma senza comprendere la virgola (?<!expr) : continua il confronto solo se l'espresisone a sinistra non corrisponde a quella dataSostituzioni $n : sostituisce la sottostringa rappresentata dall'espressione n-esima (si contano solo quelle tra parentesi tonde). $0 indica tutta la stringa ${nome} : sostituisce la sottostringa rappresentata da un'espressione (?<nome>) $& : analogo a $0 $$ : simbolo del dollaro (solo nelle sostituzioni)Costruttori di riferimento all'indietro \N: si riferisce all'n-esimo gruppo di caratteri all'indietro a partire da. Ad esempio (\w)\1 rappresenta un carattere ripetuto (è come se fosse \w\w) k<nome> : si riferisce a un gruppo denominato "nome"Costruttori di alternanza | : indica un'alternativa tra due o più espressioni. Ad esempio (.net|.com|.it) rappresenta una qualsiasi delle stringhe ".net", ".com" e ".it" (?(expr)s|n) : rappresenta la parte s se l'espressione corrisponde a expr, altrimenti la parte n

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 Module 
E 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 Module 
Poiché 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...
<< Precedente Prossimo >>
A proposito dell'autore

C#, TypeScript, java, php, EcmaScript (JavaScript), Spring, Hibernate, React, SASS/LESS, jade, python, scikit, node.js, redux, postgres, keras, kubernetes, docker, hexo, etc...