VBJ — n. 76 76 — lugli luglio-a o-agos gosto to 2007 2007
Regular Expression
parte 3
di Vito Vessia In questa ultima parte ci occuperemo della potente implementazione fornita nel framework.NET, vedremo come implementare una tecnica per importare dati forniti sotto forma di stringhe o file di testo, massimizzando il riuso del codice e le logiche applicative e su come effettuare un Replace “intelligente”.
Vito Vessia
E` cofondatore della codeBehind S.r.l. (http://www.codeBehind.it), una software factory di applicazioni enterprise, web e mobile, dove progetta progetta e sviluppa sviluppa applicazioni e framework in .NET, COM(+) e Delphi occupandosi degli aspetti architetturali. E` autore del libro “Programmare il cellulare”, lare”, Hoepli, Hoepli, 2002, sulla programmazione dei telefoni cellulari connessi al PC con protocollo standard AT+
pubblicato su WWW.INFOMEDIA.IT
stampa digitale da Lulu Enterprises Inc. stores.lulu.com / infomedia infomedia I
nfomedia Infomedia Infomedia e` l’impresa editoriale che da quasi venti anni ha raccolto raccolto la voce dei programmatori, programmatori, dei sistemisistemisti, dei professionisti, degli studenti, dei ricercatori e dei professori d’informatica italiani. Sono pi`u di 800 gli autori che hanno realizzato per le testate Computer Programming, Dev, Login, Visual Basic Journal e Java Journal, molte migliaia di articoli tecnici, presentazioni di prodotti, tecnologie, protocolli, strumenti di lavoro, tecniche di sviluppo e semplici trucchi e stratagemmi. Oltre 6 milioni di copie distribuite, trentamila pagine stampate, fanno di questa impresa la pi`u grande ed influente realt`a dell’editoria specializzata nel campo della programmazione e della sistemistica. In tutti questi anni le riviste Infomedia hanno vissuto della passione di quanti vedono nella programmazione non solo la propria professione ma un’attivit`a vitale e un vero divertimento. Nel 2009, Infomedia e` cambiata radicalmente adottando un nuovo modello aziendale ed editoriale e si `e `e organizzata attorno ad una idea di Impresa Sociale di Comunit a, a` , partecipata da programmatori e sistemisti, separando le attivit`a di gestione dell’informazione gestite da un board comunitario professionale e quelle di produzione gestite da una impresa strumentale. Questo assetto `e in linea con le migliori esperienze internazionali e rende Infomedia ancora di pi u` parte della Comunit a` nazionale nazionale degli sviluppatori di software. Infomedia e` media-partner di manifestazioni ed eventi in ambito ambito informatic informatico, o, collabora collabora con molti dei pi `u importanti editori informatici italiani come partner editoriale e fornitore di servizi di localizzazione in italiano di testi in lingua inglese.
L’impaginazione automatica di questa rivista e` realizzata al 100% con strumenti Open Source usando OpenOffice, Emacs, BHL, BHL, LaTeX, Gimp, Inkscape e i linguaggi Lisp, Python e BASH For copyright information about the contents of Visual Basic Journal, please see the section “Copyright” at the end of each article article if exists, exists, otherwise otherwise ask authors. In fomedia contents is © 2007 Infomedia and released as Creative Commons 2.5 BY-NC-ND. Turing Club content is © 2007 Turing Club released as Creative Commons 2.5 BY-ND. Le informazioni di copyright sul contenuto di Visual Basic Journal sono riportate nella sezione “Copyright” alla fine di ciascun articolo o vanno richieste direttamente agli autori. Il contenuto Infomedia e` © 2007 Infomedia e rilasciato con Licenza Creative Commons 2.5 BY-NC ND. Il contenuto Turing Club e` © 2007 Turing Club e rilasciato con Licenza Creative Commons 2.5 BY-ND. Si applicano tutte le norme di tutela dei marchi e dei segni distintivi. ` in ogni caso ammessa la riproduzione parziale o tota E le dei testi e delle immagini per scopo didattico purch´ e vengano vengano integral integralmente mente citati gli autori autori e la completa completa identificazione della testata. Manoscritti e foto originali, anche se non pubblicati, non si restituiscono. restituiscono. Contenuto pubblicitario inferiore al 45%. La biografia biografia dell’autore dell’autore riportata riportata nell’articolo nell’articolo e sul sito www.info www.infomedia media.it .it e` di norma norma quella quella dispon disponibi ibi-le nella nella stampa stampa dell’a dell’arti rticol colo o o aggio aggiorna rnata ta a cura dell’a dell’auto utore re stesso. stesso. Per aggio aggiorna rnarla rla scriver scriveree a info@infom info@infomedia. edia.it it o farlo in autonomia autonomia all’indiri all’indirizzo zzo / moduli / biografia http: // mags.programmers.net mags.programmers.net moduli / biografia
TECNICHE
Regular Expression Terza Parte
In questa ultima parte ci occuperemo della potente implementazione fornita nel framework.NET, vedremo come implementare una tecnica per importare dati forniti sotto forma di stringhe o file di testo, massimizzando il riuso del codice e le logiche applicative e su come effettuare un Replace “intelligente”.
RegEx
di Vito Vessia
Se il primo articolo vi ha fatto comprendere la potenza del meccanismo delle espressioni regolari, pur lasciandovi nel dubbio sulla sua reale applicabilità e il secondo articolo invece vi ha fugato anche quest’ultimo, in questa ultima parte della serie descriveremo lo stato dell’arte delle implementazioni di RegEx: la versione fornita nel.NET Framework. Dopo una breve escursione nel modello ad oggetti per verificare come realizzare, in.NET, quanto visto nell’articolo precedente (che faceva uso della libreria COM di Microsoft), passeremo a verificare le funzionalità avanzate della versione di RegEx in analisi, per implementare sofisticate tecniche di elaborazione e di riuso del codice. E, per concludere, vedremo come realizzare una Replace intelligente. Il namespace System.Text.RegularExpressions
Tutta la libreria di implementazione delle RegEx di .NET è contenuta nel namespaprogetta e sviluppa applicazioni e framework occupandosi degli aspetti architetturali. Scrive da anni per le principali riviste italiane di programmazione ed è autore del libro “Programmare il cellulare”, Hoepli, 2002, sul protocollo standard AT+ dei telefoni cellulari. Può essere contattato tramite e -mail all’indirizzo all’indirizzo
[email protected] [email protected].. Vito Vessia
18
VBJ N. 76 - Luglio/Agosto 2007
ce
System.Text.RegularExpressions. Si tratta di una potente
implementazione ed è considerata, pur coprendo solo una nicchia applicativa, uno dei fiori all’occhiello del framework. L’oggetto radice è System.Text.RegularExpessions.RegEx. Il pattern di parsing, cioè l’espressione regolare che sarà in grado di trattare, è passato nel costruttore. Si tratta di un oggetto immutabile, per cui il pattern non potrà più essere modificato durante il life-time dell’oggetto, ma si dovrà procederà alla creazione di una nuova istanza per gestire una nuova RegEx. Questa apparente rigidità ha consentito di massimizzare le prestazioni di parsing, che in effetti sono note voli, perché l’oggetto RegEx appena istanziato è come se fosse ottimizzato per lavorare su una determinata espressione; in pratica è quasi come se ogni volta ci fosse fornito un parser nativo per l’espressione che si vuole processare. È addirittura possibile sal-
TECNICHE
vare su disco un assembly contenente una versione precompilata della RegEx in modo da poter istanziare direttamente un oggetto RegEx preconfigurato. Nella directory \VBNetRegEx dei sorgenti, liberamente scaricabili dal sito ftp.infomedia.it, è presente il progetto Visual Basic.NET VBNETRegEx.vbproj, nella solution VB.NETRegEx.sln, che contiene un semplice client molto simile a quello realizzato in Visual Basic 6 e Delphi 6, già mostrati nel precedente articolo. Il nostro obiettivo sarà sempre processare il testo contenente i soliti dati di anagrafica di magazzino (Riquadro 1). Avevamo sottoposto la stringa alla seguente espressione regolare IDX(\d{3})-1:(\d+),\d*,”?([\w|.|\s|-]+)”?,”?([\ w|.|\s|-]+)”?,”?([\w|.|\s|-]+)”?,”?([\w|.|\ s|-]+)”?(?:,”?(\w*)”?(?:,(\d{4})-(\d{2})-(\ d{2})|)|)\r\nIDX\1-2:(\d*)(?:,(\d*)|)(?:,(\ d*)|)(?:,(\d*)|)(?:,(\d*)|)
Adesso osserviamo come fare la stessa cosa usando il motore RegEx di.NET usando codice Visual Basic.NET: Imports System.Text.RegularExpressions Dim sPattern As String = “IDX(\d{3})-1:…..” ‘il pattern completo visto in precedenza Dim RegEx As RegExp = New Regex(sPattern)
Il motore RegEx di.NET è considerato uno dei fiori all’occhiello del framework
re riportate anche direttamente nel pattern dell’espressione regolare seguendo la con venzione sintattica descritta in tabella, per cui se ad esempio volessimo creare una RegEx con il pattern visto in precedenza, ma di tipo case insensitive e multiline, useremmo la seguente sintassi: (im)IDX(\d{3})-1:(.....
L’inizio del d el pattern patter n è il token to ken “(im)” che è costituito proprio da “i” (ignore case, cioè case insensitive) e da “m” (multiline). È bene che il pattern con le opzioni si trovi all’inizio all ’inizio dell’espressione, in modo che abbia validità su tutta l’espressione; peraltro alcune opzioni, come descritto in Tabella 1, possono essere poste solo al principio, anche se è possibile metterle in mezzo alla stringa: in tal caso avranno validità solo dal carattere successivo e andranno in override di eventuali opzioni definite in precedenza. È molto utile la possibilità di inserire le opzioni nel pattern perché ciò ci permette di renderle dinamiche. Osserviamo finalmente come estrarre i match. È possibile procedere con l’estrazione di tutti i match facendoci restituire un oggetto Ma-
Esiste una seconda versione del costruttore che permette di passare anche un secondo parametro, necessario a identificare le opzioni di parsing Riquadro 1 utilizzando l’enumerativo RegexOpIDX001-1:045826,1,”ALIM”,”DEPERIB”,”FRUTTA”,”MELE TRENT.”,”KG”,2002-09-29 IDX001-2:45454354,34534534,3453456 tions in bitwise. Le opzioni possiIDX002-1:022342,,”ALIM”,”CONFEZ”,”LATTIC”,”SCAMOR.MASA” bili sono presentaIDX002-2:3243441 te nella Tabella 1. IDX003-1:111134,,ALIM,CONFEZ,LATTIC,LATTE ALICE,,2002-12-29 È interessante rileIDX003-2:232454354,13203456 vare che queste opzioni possono esse-
N. 76 - Luglio/Agosto 2007 VBJ
19
TECNICHE
For Each Match In MatchColl i = 0 For Each Group In Match.Groups i = i + 1 Console.WriteLine(“Group: “ & i & “ = “ & Group.Value) Next Next
Figura 1
Il client.NET in azione
tchCollection, oppure richiedere l’estrazione di un unico Match (il primo trovato). Osser viamo la prima possibilità attraverso il metodo RegExp.Matches: Dim MatchColl As MatchCollection = RegEx.Matches(„IDX001-1:045….“) ‘stringa completa da interpretare Dim Match As Match
Abbiamo utilizzato la collection Groups di Match che contiene proprio i gruppi catturati. Osserviamo quindi il nostro progetto di esempio, nel tab “Espressione Regolare Standard”, che ripropone il comportamento delle applicazioni Visual Basic 6 e Delphi 6 [2] presentate nel precedente articolo. In Figura 1 è possibile osservare come si presenta l’applicazione in esecuzione. Ed ecco il codice di esecuzione della espressione regolare e di estrazione dei gruppi, in versione Visual Basic.NET (Listato 1). E non poteva mancare il codice che permette di accedere in modo puntuale a ciascun gruppo catturato, conoscendone la posizione ed il significato (Listato 2).
I gruppi con nome
For Each Match in MatchColl Console.WriteLine(“Match: “ & Match.Value) Next
Oppure un solo RegExp.Match:
Match
col
metodo
Dim Match As Match = RegEx.Match(“IDX001-1: 045….”) ‘stringa completa da interpretare
Un’interessantissima possibilità offerta dall’implementazione RegEx del framework è la possibilità di catturare i gruppi assegnando loro un nome simbolico, senza la necessità quindi di conoscerne la posizione all’interno del pattern. La sintassi per la cattura di gruppi con nome è la seguente:
Console.WriteLine(“Match: “ & Match.Value) (?
pattern_del_gruppo)
L’ultimo step st ep è accedere ai gruppi grup pi catturati; catturat i; osserviamo come farlo, supponendo di aver un oggetto MatchColl di tipo MatchCollection che contiene già i gruppi catturati dal nostro solito esempio: Dim Match As Match
Si supponga di voler catturare il sottopattern “pattern_del_gruppo” in un gruppo specifico e di volergli attribuire il nome “nome_gruppo”. Vediamo Vediamo come si trasforma il pattern utilizzato nel nostro esempio, associando a ciascun gruppo un nome:
Dim Group As Group Dim i As Integer
(?inm)IDX(?\d{3})-1:(?\d+),\ d*,”?(?[\w|.|\s|-]+)”?,”?(?[\
20
VBJ N. 76 - Luglio/Agosto 2007
TECNICHE
Listato 1
Private MatchColl As MatchCollection Private RegExp As RegEx Private Sub cmdProcess_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles cmdProcess.Click Dim Match As Match Dim Group As Group Dim i As Integer Dim j As Integer RegExp = New Regex(txtPattern.Text, RegexOptions.IgnoreCase And RegexOptions.Multiline) MatchColl = RegExp.Matches(txtText.Text) txtResult.Clear() For Each Match In MatchColl txtResult.Text = txtResult.Text & “Match: “ & Match.Value & vbCrLf & vbCrLf i = 0 For Each Group In Match.Groups i = i + 1 txtResult.Text = txtResult.Text & “Group: “ & i & “ = “ & Group.Value & vbCrLf Next Next End Sub
w|.|\s|-]+)”?,”?(?[\w|.|\ s|-]+)”?,”?(?[\w|.|\s|]+)”?(,”?(?\w*)”?(,(?\d{4})(?\d{2})-(?\d{2})|)|)\r\nIDX\
rettamente ad un gruppo catturato attraverso il suo nome. Si ricorderà come per accedere ad un gruppo in posizione n si proceda come segue attraverso l’oggetto Match:
k-2:(?\d*)(,(?\ d*)|)(,(?\d*)|)(,(?\
Match.Groups(n).Value
d*)|)(,(?\d*)|)
In questo modo ciascuno dei gruppi catturati avrà un nome specifico e significativo; il codice articolo è catturato dal gruppo “codice”, la descrizione dal gruppo “descrizione”, e così via. È interessante notare che anche il backreference [1], cioè la possibilità di far riferimento ad un gruppo precedentemente catturato, diventa più chiara e leggibile. Si osservi infatti la seguente sezione del pattern: IDX\k-2:
che va letta come “ad un certo punto verrà trovata la stringa “IDX” seguita dal testo catturato in precedenza dal gruppo “idx” (\k) seguito poi dalla stringa “-2:”. Aumenta la leggibilità e la comprensibilità del pattern, ma il vantaggio fondamentale è proprio la possibilità di accedere, dal modello ad oggetti, di-
Per l’accesso ad un gruppo con nome, invece, si usano due funzioni. Si osservi il codice seguente: Match.Groups(RegExp.GroupNumberFromName (“codice”)).Value
Il metodo GroupNumberFromName restituisce la posizione (l’indice) del gruppo che ha per nome quello passato come parametro (nell’esempio è “codice”). A questo quest o punto il numero viene passato alla solita collection Groups di Match e il valore è disponibile. Nel nostro progetto di esempio è presente il tab “Regular Expression con gruppi nominali” che propone la nuova versione del pattern, caratterizzato dai gruppi con nome. Osserviamo Osservi amo come è cambiato il metodo che accede in modo puntuale ai gruppi catturati e ne stampa i valori ( Listato 3). N. 76 - Luglio/Agosto 2007 VBJ
21
TECNICHE
Listato 2
Private Sub cmdExtractData_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles cmdExtractData.Click Dim Match As Match Dim Group As Group Dim j As Integer txtResult.Text = “” For Each Match In MatchColl txtResult.Text = txtResult.Text & “Analisi del record: “ & Match.Value & vbCrLf & vbCrLf txtResult.Text = txtResult.Text & “Codice articolo : “ & Match.Groups(2).Val Match.Groups(2).Value ue & vbCrLf txtResult.Text = txtResult.Text & “Descrizione articolo: “ & Match.Groups(6).Value & vbCrLf txtResult.Text = txtResult.Text & “Gruppo merceologico : “ & Match.Groups(3).Value & vbCrLf txtResult.Text = txtResult.Text & “Famiglia merceolog. : “ & Match.Groups(4).Value & vbCrLf If Match.Groups(8).Value <> “” Then txtResult.Text = txtResult.Text & “Data del lotto : “ & _ CDate(Match.Groups(8).Value & “/” & _ Match.Groups(9).Value & “/” & Match.Groups(10).Value) & vbCrLf End If ‘continua… Next End Sub
Si può notare che l’elaborazione non avviene direttamente nel bottone, ma viene richiamato un metodo della classe a cui viene passato il pattern regex e il testo da processare, il quale restituisce il testo formattato della risposta. La ragione del del disaccoppiamento disaccoppiamento sarà chiara nel seguito dell’articolo. Tecniche di polimorfismo per regular expression
È possibile, sfruttando i gruppi con nome, implementare una tecnica di polimorfismo basata sulle Regular Expression. In pratica si supponga di dover gestire un sistema software che importa le anagrafiche articoli come quella vista in precedenza, ma che debba ge-
stire formati di importazione differenti perché provenienti da clienti differenti. Tutti i formati, però, contengono il subset di informazioni necessarie al sistema di importazione. Si tratta chiaramente di una condizione ideale e limite, ma può essere interessante come esempio per comprendere il concetto di polimorfismo per regular expression. Si consideri quindi un secondo tracciato testuale di importazione ( Riquadro 2, prima parte) A prima vista si riescono a scorgere le “informazioni sensibili” già presenti nel precedente layout, ma la loro posizione e struttura è ben differente. Cerchiamo di “leggere” il nuovo tracciato (Riquadro 2, seconda parte):
Riquadro 2 0045826
MELE TRENT.KG
ALIM DEPERIB
FRUTTA20020929454543 FRUTTA20020929454543543453453403453456 543453453403453456
1
0022342
SCAMOR.MASAPZ
ALIM CONFEZ
LATTIC
0
0111134
LATTE ALICE
ALIM CONFEZ
LATTIC20021229232454 LATTIC200212292324543541320345 3541320345
03243441
1
codice |descrizione |UM|gruppo |famiglia|subfa|YYYYMMDD|bcode1 |bcode2 |bcode3 |bcode4 |bcode5 | 0045826
22
MELE TRENT.KG
VBJ N. 76 - Luglio/Agosto 2007
ALIM DEPERIB FRUTTA200209294545 FRUTTA20020929454543543453453403453456 43543453453403453456
1
TECNICHE
[\w|.|\s|-]{6})(?[\ d|s]{4}) (?[\d|\s]{2})(?[\ d|\s]{2})(?[\d|\ s]{8})(?[\d|\s]{8})(?[\
Figura 2
d|\s]{8})(?[\d|\
La Replace in azione
s]{8})(?[\d|\s]{8})
Quindi, se le informazioni sensibili sono le medesime (indipendentemente dal formato di importazione) importazio ne) o perlomeno le differenze sono gestibili da una RegEx, si potranno utilizzare routine comuni di trattamento dei dati. In questi casi, si potrà quindi evitare di riscrivere da zero tutto il codice di trattamento dell’importazione. Per raggiungere questo scopo è sufficiente definire una RegEx completamente nuova per il differente formato di output, ma che sia compatibile con il codice che sfrutta va il risultato della vecchia. Fortunatamente per far questo ci viene incontro proprio propr io la possibilità di definire dei gruppi, cioè di isolare delle sottostringhe dalla stringa da analizzare ed assegnare ad essi un nome. La riuscita del meccanismo sta nel fatto che, dal modello ad oggetti di RegEx, è possibile accedere a questi gruppi per nome e così, avendo due RegEx anche strutturalmente del tutto diverse (ma che producono gli stessi gruppi, con venzionalmente venzionalmente definiti, dai quali estraggono le informazioni sensibili), è possibile accedere agli stessi risultati (i gruppi) senza modificare il codice che ne legge il valore. Così, semplicemente realizzando una espressione regolare che sia in grado di trattare il formato precedente e che sia in grado di estrarre i gruppi in maniera omonima rispetto alla espressione regolare del primo formato di importazione, avremmo la possibilità di riciclare completamente il codice di trattamento del primo formato di importazione! Osserviamo l’espressione regolare che tratta il nuovo formato: (?inm)^(?\d{7}).{3}(?[\ w|.|\s|-]{11})(?[\w|\s]{2}).{4}(?[\ w|. |\s|-]{5})(?[\w|.|\s|]{9})(?
Ed ecco quindi il codice che processa il nuovo formato, facendo uso dell’espressione regolare appena definita: Private Sub cmdExtractDataPolym_Click (ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles cmdExtractDataPolym.Click txtResultPolym.Text = ExtractDataFromGroups (txtPatternPolym.Text, txtTextPolym.Text) End Sub
Si tratta del pannello “Polimorfismo per Regular Expression” del progetto di esempio. Il codice richiama la stessa funzione ExtractDataFromGroups usata per il primo formato di importazione, ottenendo quindi un riuso completo del codice che ha quindi assunto un comportamento polimorfico basato sull’omonimia dei gruppi estratti nelle due RegEx. Occorre ribadire che si tratta di una condizione limite, ma potrebbe avere interessantissimi utilizzi applicativi come mostrato in [3] dove veniva realizzata una “driverizzazione” dei dispositivi proprio basata sul polimorfismo delle RegEx.
La funzione Split Chiunque ha lavorato con Visual Basic non può non aver trovato assolutamente impareggiabile la Split, cioè quella magica funzione che, dati in ingresso una stringa sorgente e un token, restituisce un array contenente tutte le sottostringhe della funzione che vengono separate dal token. In pratica, data una stringa “Telefono<>Strada<>Orologio<>Bastione” dove le sottostringhe sono separate dal token “<>”, la Split restituisce un array composto dagli elementi “Telefono”, “Strada”, ecc…
N. 76 - Luglio/Agosto 2007 VBJ
23
TECNICHE
Listato 3
Private Function ExtractDataFromGroups(ByVal Pattern As String, ByVal Text As String) As String Dim Match As Match Dim Group As Group Dim j As Integer RegExp = New Regex(Pattern) MatchColl = RegExp.Matches(Text) For Each Match In MatchColl ExtractDataFromGroups = ExtractDataFromGroups & “Analisi del record: “ & _ Match.Value & vbCrLf & vbCrLf ExtractDataFromGroups ExtractDataFromGroup s = ExtractDataFromGrou ExtractDataFromGroups ps & “Codice articolo : “ & _ Match.Groups(RegExp.GroupNumberFromName(“codice”)).Value & vbCrLf ExtractDataFromGroups = ExtractDataFromGroups & “Descrizione articolo: “ & _ Match.Groups(RegExp.GroupNumberFromName(“descrizione”)).Value & vbCrLf ExtractDataFromGroups = ExtractDataFromGroups & “Gruppo merceologico : “ & _ Match.Groups(RegExp.GroupNumberFromName(“gruppo”)).Value & vbCrLf ExtractDataFromGroups = ExtractDataFromGroups & “Famiglia merceolog. : “ & _ Match.Groups(RegExp.GroupNumberFromName(“famiglia”)).Value & vbCrLf ExtractDataFromGroups = ExtractDataFromGroups & “Sottofamiglia merc. : “ & _ Match.Groups(RegExp.GroupNumberFromName(“sottofamiglia”)).Value & vbCrLf ExtractDataFromGroups ExtractDataFromGroup s = ExtractDataFromGrou ExtractDataFromGroups ps & “Unità di misura : “ & _ Match.Groups(RegExp.GroupNumberFromName(“UM”)).Value & vbCrLf If Match.Groups(RegExp.GroupNumberFromName(“anno”)).Value.Trim <> “” And _ Match.Groups(RegExp.GroupNumberFromName(“mese”)).Value.Trim <> “” And _ Match.Groups(RegExp.GroupNumberFromName(“giorno”)).Value.Trim <> “” Then ExtractDataFromGroups ExtractDataFromGrou ps = ExtractDataFromGrou ExtractDataFromGroups ps & “Data del lotto : “ & _ CDate(Match.Groups(RegExp.GroupNumberFromName(“anno”)).Value & “/” & _ Match.Groups(RegExp.GroupNumberFromName(“mese”)).Value & “/” & _ Match.Groups(RegExp.GroupNumberFromName(“giorno”)).Value) & vbCrLf End If ExtractDataFromGroups ExtractDataFromGroup s = ExtractDataFromGrou ExtractDataFromGroups ps & “Codice a barre 1 : “ & _ Match.Groups(RegExp.GroupNumberFromName(“barcode1”)).Value & vbCrLf ExtractDataFromGroups ExtractDataFromGroup s = ExtractDataFromGrou ExtractDataFromGroups ps & “Codice a barre 2 : “ & _ Match.Groups(RegExp.GroupNumberFromName(“barcode2”)).Value & vbCrLf ExtractDataFromGroups ExtractDataFromGroup s = ExtractDataFromGrou ExtractDataFromGroups ps & “Codice a barre 3 : “ & _ Match.Groups(RegExp.GroupNumberFromName(“barcode3”)).Value & vbCrLf ExtractDataFromGroups ExtractDataFromGroup s = ExtractDataFromGrou ExtractDataFromGroups ps & “Codice a barre 4 : “ & _ Match.Groups(RegExp.GroupNumberFromName(“barcode4”)).Value & vbCrLf ExtractDataFromGroups ExtractDataFromGroup s = ExtractDataFromGrou ExtractDataFromGroups ps & “Codice a barre 5 : “ & _ Match.Groups(RegExp.GroupNumberFromName(“barcode5”)).Value & vbCrLf Next End Function Private Sub cmdExtractDataWithName_Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles cmdExtractDataWithName.Click txtResultWithName.Text = ExtractDataFromGroups(txtPatternWithName.Text, txtTextWithName.Text) End Sub
A me è parsa tanto utile che, dovendo scri vere codice in Delphi, ho pensato di riscri vermi in questo linguaggio una mia funzione perfettamente identica nella sintassi e nel comportamento! Ebbene, nemmeno in .NET poteva mancare, ma adesso è diventata addirittura una e trina… Ne esiste una versione specifica per Visual Basic.NET mantenuta per retro-compatibilità con VB6, inoltre ce
24
VBJ N. 76 - Luglio/Agosto 2007
ne sono altre due direttamente nel framework standard: la prima è un metodo dell’oggetto System.String, il tipo stringa del Type System. Questa però presenta la limitazione di accettare solo token formati da una sola stringa. Ma l’implementazione più sofisticata è quella offerta dal motore delle espressioni regolari. La split è infatti una metodo statico dell’oggetto RegExp ed è sovraccaricato per ben 5 volte.
TECNICHE
Opzione
Signicato
Descrizione
Nessuna
Nessuna opzione è impostata. Corrisponde all’enumerativo RegexOptions.None.
I
Case insensitive
Effettua la scansione della stringa in modalità case insensitive, cioè non fa differenza tra le maiuscole e le minuscole (“CIRO” = “ciro”). Corrisponde all’enumerativo RegexOptions.IgnoreCase .
M
Multiline
Imposta la scansione in modalità multiline, cioè interpreta i token ^ e $ come indicatori di inizio e ne linea e non inizio e ne stringa. Corrisponde all’enumerativo RegexOptions.Multiline .
N
Cattura es esplicita
Cattura i soli gruppi per i quali è st stato de denito un un no nome ed ed ignora tu tutti gli al altri. Corrisponde all’enumerativo RegexOptions.ExplicitCapture.
C
C o m p i l a to
È una caratteristica dell’implementazione.NET delle Regular Expression: permette cioè di creare e compilare al volo un oggetto.NET in grado di interpretare la RegEx impostata. Se presente, la scansione avviene quindi sempre con un oggetto che è in grado in modo nativo di gestire quel pattern e non in modo interpretato come accade normalemente. Corrisponde all’enumerativo RegexOptions.Compiled .
S
Singleline
Imposta la scansione in modalità singleline: interpreta i token ^ e $ come indicatori di inizio e ne stringa. Corrisponde all’enumerativo RegexOptions.Singleline.
X
Ignora gli spazi bianchi
Ignora gli spazi bianchi presenti nella RegEx. Corrisponde all’enumerativo Regex Options.IgnorePatternWhitespace.
R
RightToLeft
Impone la scansione della stringa da destra verso sinistra. Può essere impostata solo all’inizio del pattern. Corrisponde all’enumerativo RegexOptions.RightToLeft.
ECMAScript
Que Questa opz opziione im impone one la la co compati atibilità co con le le re regol gole de dello st standa ndard ECMAScript. In realtà è da considerarsi come una metaopzione perché non descrive alcun comportamento specico, ma si abbina alle opzioni Ignore Case, Multiline e Compiled rendendole confacenti ad ECMAScript. Corrisponde a RegexOptions.ECMAScript.
Tabella 1
Elenco delle opzioni del motore RegEx del .NET Framework
La versione che al momento ci interessa accetta come parametri proprio la stringa originale e il token di split e restituisce l’array di stringhe delle occorrenze: Dim Item As String For Each Item In RegExp.Split(“Telefono<>Strada< >Orologio<>Bastione”, “<>”) Console.WriteLine(Item)
lo, una delle funzionalità offerte dai motori RegEx è la Replace, cioè la possibilità di sostituire delle occorrenze di stringhe in una stringa di origine sottoposta ad una espressione regolare. Nel progetto di esempio sono presenti ben tre esempi di Replace. La prima ripropone lo stesso comportamento visto in [2] cioè applica il seguente pattern di Replace alla prima versione di RegEx, quella senza gruppi nominali (txtPattern.Text):
Next Gruppo: $3 Famiglia: $4 Sottofamiglia: $5 ($6)
La funzione Replace Come già osservato nel precedente artico-
Viene prodotta una stringa contenente una parte di testo predeteminata (ad esempio la parola “Gruppo: “) fusa però con gruppi estratti
N. 76 - Luglio/Agosto 2007 VBJ
25
TECNICHE
non può essere risolto da un pattern di replace. Si consideri la stringa: Oggi è 12/05/2002; le vacanze inizieranno 27/8/2003. La codeBehind Srl è stata fondata 2/3/2003 da un gruppo di tre programmatori.
Si supponga di voler sostituire tutte le date, che sono espresse nella forma gg/mm/yyyy, con gg ed mm che potrebbero non essere nemmeno formattate con un padding a due cifre (ad esempio Figura 3 Il risultato della replace con funzione di callback 27/8/2003), con le equivalenti date ma espresse nella forma estesa “mercoledì dalla stringa analizzata seconda la RegEx, ed 27 agosto 2003”. Nessuna espressione regolain particolare i gruppi in posizione 3 (“$3”), re sarà in grado di soddisfare soddisfare questa esigenza 4 (“$4”), 5 (“$5”) e 6 (“$6”). Il risultato otteperché non vi è alcun supporto specifico alle nuto è visibile in Figura 2. La presenza dei date. Il framework .NET però offre la possigruppi nominali permette di sofisticare la Rebilità di invocare una versione della Replace place, potendo indicare i gruppi anche con il che accetta come parametro anche il puntaloro nome tra parentesi graffe al posto della tore (il delegate) ad una funzione di callback. posizione: Così, ogni volta che il motore troverà un’occorrenza del pattern di replace, invocherà il Gruppo: ${gruppo} Famiglia: ${famiglia} delegate passandogli il match estratto; la funSottofamiglia: $5 ($6) zione invocata potrà così sostituire il match passatogli come parametro, in qualsiasi altra Il risultato è lo stesso, ma non richiede la co stringa e il nuovo valore andrà a finire nella noscenza delle posizioni dei gruppi, permetstringa risultante dalla replace al posto del tendo così di ottenere un comportamento pomatch corrispondente. corrispondente. limorfico, analogamente a quanto è avvenuPer realizzare la sostituzione di date della to per l’estrazione dei Match attraverso l’uso stringa di esempio, la sottoporremo alla sedella funzione ReplacePattern (come mostraguente espressione regolare: to nel codice di esempio). Esiste però un’altra potentissima possibilità \d{1,2}/\d{1,2}/\d{4} offerta dal framework per la Replace: le funzioni di callback callba ck per la trasformazione. trasformaz ione. L’esemL’esemQuesta estrae tutte le date dalla stringa; si pio di replace presentato in precedenza è molnotino infatti i tre gruppi di cifre che rapto semplice, ma potrebbero esserci casi in cui presentano il giorno, il mese e l’anno sepala sostituzione di occorrenze di stringhe con rati da /, tenendo conto anche dell’eventuaaltre non è così banale, ma richieda un’elaboun’elabole mancato padding a due cifre di giorno e razione più complessa che non potrebbe esmese. A questo punto definiamo la funzione sere soddisfatta da un pattern di replace, per di callback che effettua la corretta trasformaquanto sofisticato. Si pensi all’ipotesi in cui si zione delle date: Private Function ExpandDate(ByVal m As Match) voglia sostituire, in un formato fo rmato testo di imporAs String tazione, un codice articolo con la sua descriReturn Date.Parse(m.Value).ToLongDateString zione, che però va letta dal database. Procediamo però con un esempio più semplice, che End Function
26
VBJ N. 76 - Luglio/Agosto 2007
TECNICHE
Conclusioni Questa riceverà come parametro un match, che poi sarà proprio quello contente la data d ata nel formato numerico originale e, attraverso la potente funzione Parse dell’oggetto Date, la trasformerà in un tipo Date di .NET. A questo punto verrà sottoposta alla trasformazione in formato descrittivo esteso attraverso il metodo ToLongDateString dell’oggetto Date. La sintassi di questa nuova replace sarà quindi: Dim sText As String = “Oggi è 12/05/2002; le vacanze inizieranno 27/8/2003. La codeBehind Srl è
Si conclude così questa serie nel mondo teorico delle espressioni regolari e in quello pratico delle implementazioni reali dei motori RegEx. Molte informazioni sono state omesse e molti altri argomenti avrebbero richiesto un maggior approfondimento, ma l’obiettivo del corso era farvi affacciare in questo mondo, sicuri che non riuscirete più a farne a meno. Certo, lo sforzo iniziale non sarà banale, ma i benefici a medio e lungo termine saranno straordinari. Buon lavoro, dunque…
stata fondata 2/3/2003 da un gruppo di tre pro grammatori.”
Bibliografia
Dim RegExp As RegEx = New Regex(“\d{1,2}/\d{1,2}/ \d{4}”) Console.WriteLine( RegExp.Replace(sText, AddressOf ExpandDate))
L’effetto della del la trasformazione trasfor mazione è mostrato mostra to in Figura 3. 3.
[1] V. Vessia – “Regular Expression – Prima Parte” – Visual Basic &.NET Journal n.73 [2] V. Vessia – “Regular Expression – Seconda Parte” - Visual Basic &.NET Journal n.74 [3] V. Vessia – “Programmare il cellulare” – Hoepli – 2002.
N. 76 - Luglio/Agosto 2007 VBJ
27