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 |