Guida al Visual Basic .NET
Capitolo 94° - Espressioni regolari in azione
Ricerca di parole
La funzione principale delle espressioni regolari è la ricerca di determinate sottostringhe in un testo. Per operare una ricerca si usa solitamente la funzione Regex.Matches, che restituisce tutte le occorrenze, oppure un ciclo nel quale, ad ogni iterazione, si ottiene il match successivo con la funzione Match.NextMatch. Entrambi i metodi eseguono l'operazione nella stessa maniera, poichè anche Matches non viene riempito tutto subito, ma ad ogni iterazione del ciclo For a cui viene sottoposto, cerca e inserisce nel risultato una nuova corrispondenza. Ad esempio, questo codice ricerca nel sorgente di questa pagina tutte le keywords usate per l'indicizzazione dei motori di ricerca: Module Module1 Sub Main() Dim Text As String = IO.File.ReadAllText("74.php") 'Cerca la definizione del tag, ottenendo l'elenco delle 'parole Dim Keywords As New Regex("<meta name=""keywords"" content='(?<Keywords>[\w, ]+)'>", RegexOptions.Multiline) Dim Match As Match Dim Matches As MatchCollection Match = Keywords.Match(Text) 'Se la ricerca ha avuto successo, scandisce ogni parola If Match.Success Then Dim Word As New Regex("\w+") Dim Index As Int32 = 1 'Dal gruppo Keywords, preleva ogni singola parola Matches = Word.Matches(Match.Groups("Keywords").Value) Console.WriteLine("Parole chiave:") For Each M As Match In Matches 'E le scrive a schermo numerandole Console.WriteLine("{0} - {1}", Index, M.Value) Index += 1 Next End If Console.ReadKey() End Sub End Module Quest'altro esempio, invece, è molto più divertente e... cattivello. Cerca in un testo copiato negli appunti tutti gli indirizzi e-mail e li raggruppa in una lista, con la possibilità di salvarli in un file di testo. L'interfaccia è semplice: comprende una listbox e due pulsanti. Ed ecco il codice: Class Form1 Private Sub cmdSearch_Click(ByVal sender As Object, _ ByVal e As EventArgs) Handles cmdSearch.Click 'Clipboard è una proprietà di My.Computer, di tipo 'Microsoft.VisualBasic.MyService.ClipboardProxy: è un 'oggetto singleton che rappresenta gli appunti. Questo 'codice ottiene il testo copiato nella clipboard con 'la funzione Copia Dim Text As String = Clipboard.GetText 'Questa espressione deve ricercare tutti gli indirizzi 'e-mail presenti nel testo, quindi aggiungerli alla lista: '(\w+) : la prima serie di caratteri costituisce ' l'username dell'utente. Ad esempio in ' gianni90@provider.it, è gianni90 ' '\s* : zero o più spazi. Può capitare che per non rendere ' l'indirizzo reperibile, si usi questa sintassi: ' "gianni90 at provider dot it" ' '(@|at|[at]) : uno qualsiasi tra @, at e [at], a seconda ' di come viene scritto l'indirizzo ' '\s*(\w+)\s* : spazi per lo stesso discorso di prima, poi ' una serie di caratteri che indica il provider. ' '(\.|dot|[dot]) : uno qualsiasi tra ., dot e [dot], a ' seconda di come viene scritto l'indirizzo ' '(\w+) : l'ultima serie di caratteri è il dominio Dim Email As New Regex( _ "(\w+)\s*(@|at|[at])\s*(\w+)\s*(\.|dot|[dot])(\w+)", _ RegexOptions.Multiline) For Each M As Match In Email.Matches(Text) 'Aggiungi un elemento alla lista e lo spunta 'Attenzione! Bisogna tenere in conto che: '- il gruppo 0 rappresenta sempre tutta la sottostringa ' catturata e non uno dei raggrupamenti presenti '- anche i costruttori di alternanze sono gruppi, ' poichè racchiusi entro parentesi tonde 'Perciò il risultato sarebbe '0 1 2 3 4 5 ' gianni90[at]provider[dot]it 'Quindi 1 è l'username, 3 il provider e 5 il dominio lstEmail.Items.Add(String.Format( _ "{0}@{1}.{2}", M.Groups(1).Value, M.Groups(3).Value, _ M.Groups(5).Value), True) Next End Sub Private Sub cmdSave_Click(ByVal sender As Object, _ ByVal e As EventArgs) Handles cmdSave.Click 'Salva gli indirizzi spuntati Dim Save As New SaveFileDialog Save.Filter = "File di testo|*.txt" If Save.ShowDialog = Windows.Forms.DialogResult.OK Then Dim Writer As New IO.StreamWriter(Save.FileName) 'CheckedItems è una collezione in sola lettura che 'restituisce tutti gli elementi spuntati For Each Item As String In lstEmail.CheckedItems Writer.WriteLine(Item) Next Writer.Close() End If End Sub End Class Per provare il programma potete copiare questo testo: Salve, ragazzi! Il mio indirizzo è gianni90@email.it, scrivetemi presto, attendo risposte per la mia domanda. Ciao gianni90: ti devo ricordare che sarebbe meglio se non mettessi il tuo indirizzo in chiaro, poichè potrebbe essere più facilmente rintracciato. Prova invece ad usare questa forma: bartolo[at]prov[dot]com. Ok grazie! Anzi, se invece vuoi, puoi usare anche questo: caio at miap dot net. Vedrò di provare anche questo. Ma potrei anche fare delle combinazioni, ad esempio ciack[at]email.fr oppure mark at prov[dot]it... O no? Certo, ottima idea.
Validazione di espressioni
Le espressioni regolari tornano utili anche nella validazione di dati immessi dall'utente: è possibile controllare che i valori abbiano un particolare formato prima di procedere in operazioni che potrebbero produrre degli errori. In questi casi non si usa Matches e di solito si analizza solamente una linea di testo: vengono per lo più usate le funzioni IsMatch e Match. Ad esempio è possibile controllare che un indirizzo e-mail immesso abbia la giusta formattazione: Module Module2 Sub Main() 'Controlla la formattazione dell'indirizzo Dim R As New Regex("(\w+)@(\w+)\.(\w+)") Dim Input As String Do Console.Write("Inserire il proprio indirizzo e-mail: ") Input = Console.ReadLine Loop Until R.IsMatch(Input) Console.WriteLine("Indirizzo accettato!") Console.ReadKey() End Sub End Module Oppure controllare che numeri e date siano immessi correttamente: Module Module3 Sub Main() 'Controlla la formattazione del numero. È uso frequente 'nelle validazioni usare i caratteri ^ e $, che indicano 'inizio e fine della stringa, per controllare che all'interno 'ci sia solo il valore che si vuole e non altre cose Dim R As New Regex("^\d{4}$") Dim Input As String Do Console.Write("Inserire un numero intero tra 1000 e 9999: ") Input = Console.ReadLine Loop Until R.IsMatch(Input) Console.WriteLine("Numero accettato!") Console.ReadKey() End Sub End Module Module Module4 Sub Main() 'Controlla la formattazione del numero. '\d+ : almeno una cifra ' '(...)? : tutto quello che viene dopo è messo tra parentesi ' per poter usare il qualificatore ?. Il numero può ' infatti anche non essere decimale ' '(\.|,) : virgola o punto ' '\d+ : le cifre decimali, almeno una Dim R As New Regex("^\d+((\.|,)\d+)?$") Dim Input As String Do Console.Write("Inserire un numero anche decimale: ") Input = Console.ReadLine Loop Until R.IsMatch(Input) Console.WriteLine("Numero accettato!") Console.ReadKey() End Sub End Module Module Module5 Sub Main() 'Controlla la formattazione della data 'Una o due cifre, seguite da /, - o |, seguite dalla stessa 'cosa e da due o quattro cifre indicanti l'anno 'Ovviamente non viene controllata la coerenza della data Dim R As New Regex("^\d{1,2}[/-\|]\d{1,2}[/-\|](\d{2}|\d{4})$") Dim Input As String Do Console.Write("Inserire una data: ") Input = Console.ReadLine Loop Until R.IsMatch(Input) Console.WriteLine("Data accettata!") Console.ReadKey() End Sub End Module
Parsing di file di dati
Quando l'applicazione non utilizza nè xml nè database nè altri tipi particolari di file per il salvataggio di dati, può impiegare un file di dati, con estensione *.dat (certe volte, il software usa un'estensione proprietaria). All'interno di taluni tipi di file si possono immagazzinare valori formattati a piacere, senza sottostare a nessuna regola di sintassi predefinita. In questi casi è il programmatore che crea da solo le regole per scrivere le informazioni sul supporto. Una grammatica che ha usato in passato per molti programmi è questa: Campo1|Campo2|Campo3|... Dove ogni campo è separato dagli altri da un carattere pipe, e ogni riga rappresenta un oggetto diverso. Ecco un esempio: Module Module6 Sub Main() 'Scandisce una riga del file, ottenendo i vari valori Dim R As New Regex( _ "^(?<FirstName>[\w\s]+)|(?<LastName>[\w\s]+)|(?<BirthDay>.+)$", _ RegexOptions.Multiline) Dim File As String Console.WriteLine("Inserire il nome del file da caricare:") File = Console.ReadLine If Not IO.File.Exists(File) Then Console.WriteLine("File inesistente!") Exit Sub End If 'Classe creata nelle prime lezioni sulle classi Dim P As Person 'Come sopra Dim List As New PersonCollection Dim M As Match Dim Line As String Dim Reader As New IO.StreamReader(File) While Not Reader.EndOfStream 'Legge una linea di testo Line = Reader.ReadLine 'La confronta con l'espressione regolare M = R.Match(Line) 'E se ha successo... If M.Success Then 'Crea un nuovo oggetto Person P = New Person(M.Groups("FirstName").Value, _ M.Groups("LastName").Value, _ Date.Parse(M.Groups("BirthDay").Value)) 'Lo visualizza a schermo Console.WriteLine("{0}, nato il {1}", P.CompleteName, _ P.BirthDay.ToShortDateString) 'E lo aggiunge alla lista List.Persons.Add(P) End If End While Reader.Close() Console.WriteLine("L'età media è {0} ", List.AverageAge) Console.ReadKey() End Sub End Module Un file di esempio: Tizio Caio|Sempronio|21/12/2006 Pinco|Pallino|13/01/2000 Chissoio|Nonso|06/07/1990 Mario|Rossi|19/06/1994
Parsing di codice
È possibile anche analizzare del codice per ottenere informazioni sulle sue varie parti. Ad esempio, si possono contare e reperire tutte le procedure o tutte le variabili, con codice annesso, ottenere le linee di codice e di commenti e così via. Nel programma Source Scanner, ho usato le espressioni regolari per scansionare uno o più sorgenti ed individuare tutte le occorrenze di ogni membro di classe. Questo esempio mostra come fare la stessa cosa con le procedure: Module Module7 Sub Main() 'Scandisce una riga del file, ottenendo i vari valori Dim Argument As String = "(ByVal|ByRef) (?<Arg>\w+) As (?<Type>\w+)" Dim R As New Regex( _ "\s*[\w\s]*\s*Sub\s+(?<Name>\w+)((" & Argument & "\W*)*)\s*$", _ RegexOptions.Multiline) Dim File As String Console.WriteLine("Inserire il nome del file da caricare:") File = Console.ReadLine If Not IO.File.Exists(File) Then Console.WriteLine("File inesistente!") Exit Sub End If For Each M As Match In R.Matches(IO.File.ReadAllText(File)) Console.Write(M.Groups("Name").Value) Console.Write("(") 'Dato che ci possono essere più argomenti, ogni gruppo Arg 'e Type può venire catturato più volte. In questo caso 'la proprietà Captures restituisce ogni singola 'istanza del gruppo For I As Int32 = 0 To M.Groups("Arg").Captures.Count - 1 Console.Write("{0} As {1}", M.Groups("Arg").Captures(I), _ M.Groups("Type").Captures(I)) If I < M.Groups("Arg").Captures.Count - 1 Then Console.Write(", ") End If Next Console.WriteLine(")") Next Console.ReadKey() End Sub End Module
C#, TypeScript, java, php, EcmaScript (JavaScript), Spring, Hibernate, React, SASS/LESS, jade, python, scikit, node.js, redux, postgres, keras, kubernetes, docker, hexo, etc...
|