Questo sito utilizza cookies, anche di terze parti, per mostrare pubblicità e servizi in linea con il tuo account. Leggi l'informativa sui cookies.
Username: Password: oppure
Guida al Visual Basic .NET - Espressioni regolari in azione

Guida al Visual Basic .NET

Capitolo 94° - Espressioni regolari in azione

<< Precedente Prossimo >>
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 
<< Precedente Prossimo >>
A proposito dell'autore

Programmatore e analista .NET 2005/2008/2010 (in particolare C# e VB.NET), anche nell'implementazione Mono per Linux. Conoscenze approfondite di Pascal, PHP, XML, HTML 4.01/5, CSS 2.1/3, Javascript (e jQuery). Conoscenze buone di C, LUA, GML, Ruby, XNA, AJAX e Assembly 68000. Competenze basilari di C++, SQL, Hlsl, Java.