Sei sulla pagina 1di 12

VBJ n.

76 luglio-agosto 2007

Regular Expression
di Vito Vessia

parte 3

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 le 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 e sviluppa applicazioni e framework in .NET, COM(+) e Delphi occupandosi degli aspetti architettura` li. E autore del libro Programmare il cellulare, 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
` Infomedia e limpresa editoriale che da quasi venti anni ha raccolto la voce dei programmatori, dei sistemisti, dei professionisti, degli studenti, dei ricercatori e dei professori dinformatica italiani. Sono pi` di 800 gli autori che hanno realizzato per le teu state 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` grande ed u inuente realt` delleditoria specializzata nel campo della a 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 unattivit` vitale e un vero a divertimento. ` Nel 2009, Infomedia e cambiata radicalmente adottando ` un nuovo modello aziendale ed editoriale e si e organizzata attorno ad una idea di Impresa Sociale di Comunit` , a partecipata da programmatori e sistemisti, separando le attivit` di gestione dellinformazione gestite da un board a comunitario professionale e quelle di produzione gesti` te da una impresa strumentale. Questo assetto e in linea con le migliori esperienze internazionali e rende Infomedia ancora di pi` parte della Comunit` nazionale degli u a sviluppatori di software. ` Infomedia e media-partner di manifestazioni ed eventi in ambito informatico, collabora con molti dei pi` imporu tanti editori informatici italiani come partner editoriale e fornitore di servizi di localizzazione in italiano di testi in lingua inglese.

Limpaginazione automatica di questa rivista e realizzata al ` 100% con strumenti Open Source usando OpenOffice, Emacs, 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 if exists, otherwise ask authors. Infomedia 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 ne di ciascun articolo o vanno richieste direttamente agli autori. Il contenuto Infomedia e 2007 Infomedia ` e rilasciato con Licenza Creative Commons 2.5 BY-NCND. 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. ` E in ogni caso ammessa la riproduzione parziale o totale dei testi e delle immagini per scopo didattico purch e vengano integralmente citati gli autori e la completa identicazione della testata. Manoscritti e foto originali, anche se non pubblicati, non si restituiscono. Contenuto pubblicitario inferiore al 45%. La biograa dellautore riportata nellarticolo e sul sito www.infomedia.it e di norma quella disponibi` le nella stampa dellarticolo o aggiornata a cura dellautore stesso. Per aggiornarla scrivere a info@infomedia.it o farlo in autonomia allindirizzo http://mags.programmers.net/moduli/biograa

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.
di Vito Vessia

RegEx

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 questultimo, in questa ultima parte della serie descriveremo lo stato dellarte 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 nellarticolo 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 namespa-

Vito Vessia progetta 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 allindirizzo vvessia@katamail.com.

ce System.Text.RegularExpressions. Si tratta di una potente implementazione ed considerata, pur coprendo solo una nicchia applicativa, uno dei fiori allocchiello del framework. L oggetto radice System.Text.RegularExpessions.RegEx. Il pattern di parsing, cio lespressione 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 delloggetto, 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 notevoli, perch loggetto 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 lespressione che si vuole processare. addirittura possibile sal-

18

VBJ N. 76 - Luglio/Agosto 2007

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*)|)

Il motore RegEx di.NET considerato uno dei fiori allocchiello del framework
re riportate anche direttamente nel pattern dellespressione regolare seguendo la convenzione 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:(.....

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)

Esiste una seconda versione del costruttore che permette di passare anche un secondo parametro, necessario a identificare le opzioni di parsing Riquadro 1 utilizzando lenumerativo 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-

L inizio del pattern il token (im) che costituito proprio da i (ignore case, cio case insensitive) e da m (multiline). bene che il pattern con le opzioni si trovi allinizio dellespressione, in modo che abbia validit su tutta lespressione; 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 lestrazione di tutti i match facendoci restituire un oggetto Ma-

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 lestrazione di un unico Match (il primo trovato). Osserviamo 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 For Each Match in MatchColl Console.WriteLine(Match: & Match.Value) Next

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 lapplicazione 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


Uninteressantissima possibilit offerta dallimplementazione RegEx del framework la possibilit di catturare i gruppi assegnando loro un nome simbolico, senza la necessit quindi di conoscerne la posizione allinterno del pattern. La sintassi per la cattura di gruppi con nome la seguente:
(?<nome_gruppo>pattern_del_gruppo)

Oppure un solo RegExp.Match:

Match

col

metodo

Dim Match As Match = RegEx.Match(IDX001-1: 045.) stringa completa da interpretare Console.WriteLine(Match: & Match.Value)

L ultimo step accedere ai gruppi catturati; 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 Dim Group As Group Dim i As Integer

Si supponga di voler catturare il sottopattern pattern_del_gruppo in un gruppo specifico e di volergli attribuire il nome nome_gruppo. Vediamo come si trasforma il pattern utilizzato nel nostro esempio, associando a ciascun gruppo un nome:
(?inm)IDX(?<idx>\d{3})-1:(?<codice>\d+),\ d*,?(?<gruppo>[\w|.|\s|-]+)?,?(?<famiglia>[\

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|-]+)?,?(?<sottofamiglia>[\w|.|\ s|-]+)?,?(?<descrizione>[\w|.|\s|]+)?(,?(?<UM>\w*)?(,(?<anno>\d{4})(?<mese>\d{2})-(?<giorno>\d{2})|)|)\r\nIDX\ k<idx>-2:(?<barcode1>\d*)(,(?<barcode2>\ d*)|)(,(?<barcode3>\d*)|)(,(?<barcode4>\ d*)|)(,(?<barcode5>\d*)|)

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 loggetto Match:
Match.Groups(n).Value

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<idx>-2:

Per laccesso ad un gruppo con nome, invece, si usano due funzioni. Si osservi il codice seguente:
Match.Groups(RegExp.GroupNumberFromName (codice)).Value

che va letta come ad un certo punto verr trovata la stringa IDX seguita dal testo catturato in precedenza dal gruppo idx (\k<idx>) 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-

Il metodo GroupNumberFromName restituisce la posizione (lindice) del gruppo che ha per nome quello passato come parametro (nellesempio codice). A questo 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 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).Value & 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 lelaborazione 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 disaccoppiamento sar chiara nel seguito dellarticolo.

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 0022342 0111134 MELE TRENT.KG SCAMOR.MASAPZ LATTE ALICE ALIM DEPERIB ALIM CONFEZ ALIM CONFEZ FRUTTA20020929454543543453453403453456 LATTIC 03243441 LATTIC200212292324543541320345 1 0 1

codice |descrizione |UM|gruppo |famiglia|subfa|YYYYMMDD|bcode1 |bcode2 |bcode3 |bcode4 |bcode5 | 0045826 MELE TRENT.KG ALIM DEPERIB FRUTTA20020929454543543453453403453456 1

22

VBJ N. 76 - Luglio/Agosto 2007

TECNICHE

<sottofamiglia>[\w|.|\s|-]{6})(?<anno>[\ d|s]{4}) (?<mese>[\d|\s]{2})(?<giorno>[\ d|\s]{2})(?<barcode1>[\d|\ s]{8})(?<barcode2>[\d|\s]{8})(?<barcode3>[\

Figura 2

La Replace in azione

d|\s]{8})(?<barcode4>[\d|\ s]{8})(?<barcode5>[\d|\s]{8})

Quindi, se le informazioni sensibili sono le medesime (indipendentemente dal formato di importazione) 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 dellimportazione. Per raggiungere questo scopo sufficiente definire una RegEx completamente nuova per il differente formato di output, ma che sia compatibile con il codice che sfruttava il risultato della vecchia. Fortunatamente per far questo ci viene incontro proprio 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, convenzionalmente 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 lespressione regolare che tratta il nuovo formato:
(?inm)^(?<codice>\d{7}).{3}(?<descrizione>[\ w|.|\s|-]{11})(?<UM>[\w|\s]{2}).{4}(?<gruppo>[\ w|. |\s|-]{5})(?<famiglia>[\w|.|\s|]{9})(?

Ed ecco quindi il codice che processa il nuovo formato, facendo uso dellespressione 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 sullomonimia 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 = ExtractDataFromGroups & 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 = ExtractDataFromGroups & 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 = ExtractDataFromGroups & 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 = ExtractDataFromGroups & Codice a barre 1 : & _ Match.Groups(RegExp.GroupNumberFromName(barcode1)).Value & vbCrLf ExtractDataFromGroups = ExtractDataFromGroups & Codice a barre 2 : & _ Match.Groups(RegExp.GroupNumberFromName(barcode2)).Value & vbCrLf ExtractDataFromGroups = ExtractDataFromGroups & Codice a barre 3 : & _ Match.Groups(RegExp.GroupNumberFromName(barcode3)).Value & vbCrLf ExtractDataFromGroups = ExtractDataFromGroups & Codice a barre 4 : & _ Match.Groups(RegExp.GroupNumberFromName(barcode4)).Value & vbCrLf ExtractDataFromGroups = ExtractDataFromGroups & 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 scrivere codice in Delphi, ho pensato di riscrivermi 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

ne sono altre due direttamente nel framework standard: la prima un metodo delloggetto System.String, il tipo stringa del Type System. Questa per presenta la limitazione di accettare solo token formati da una sola stringa. Ma limplementazione pi sofisticata quella offerta dal motore delle espressioni regolari. La split infatti una metodo statico delloggetto RegExp ed sovraccaricato per ben 5 volte.

24

VBJ N. 76 - Luglio/Agosto 2007

TECNICHE
Opzione I Significato Nessuna Case insensitive Descrizione Nessuna opzione impostata. Corrisponde allenumerativo RegexOptions.None. Effettua la scansione della stringa in modalit case insensitive, cio non fa differenza tra le maiuscole e le minuscole (CIRO = ciro). Corrisponde allenumerativo RegexOptions.IgnoreCase. Imposta la scansione in modalit multiline, cio interpreta i token ^ e $ come indicatori di inizio e fine linea e non inizio e fine stringa. Corrisponde allenumerativo RegexOptions.Multiline. Cattura i soli gruppi per i quali stato definito un nome ed ignora tutti gli altri. Corrisponde allenumerativo RegexOptions.ExplicitCapture. una caratteristica dellimplementazione.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 allenumerativo RegexOptions.Compiled. Imposta la scansione in modalit singleline: interpreta i token ^ e $ come indicatori di inizio e fine stringa. Corrisponde allenumerativo RegexOptions.Singleline. Ignora gli spazi bianchi presenti nella RegEx. Corrisponde allenumerativo Regex Options.IgnorePatternWhitespace. Impone la scansione della stringa da destra verso sinistra. Pu essere impostata solo allinizio del pattern. Corrisponde allenumerativo RegexOptions.RightToLeft. Questa opzione impone la compatibilit con le regole dello standard ECMAScript. In realt da considerarsi come una metaopzione perch non descrive alcun comportamento specifico, ma si abbina alle opzioni Ignore Case, Multiline e Compiled rendendole confacenti ad ECMAScript. Corrisponde a RegexOptions.ECMAScript.

Multiline

N C

Cattura esplicita Compilato

Singleline

X R

Ignora gli spazi bianchi RightToLeft 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 larray di stringhe delle occorrenze:
Dim Item As String For Each Item In RegExp.Split(Telefono<>Strada< >Orologio<>Bastione, <>) Console.WriteLine(Item) Next

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):
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 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 unoccorrenza 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 costringa 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. limorfico, analogamente a quanto avvenuPer realizzare la sostituzione di date della to per lestrazione dei Match attraverso luso stringa di esempio, la sottoporremo alla sedella funzione ReplacePattern (come mostraguente espressione regolare: to nel codice di esempio). Esiste per unaltra potentissima possibilit \d{1,2}/\d{1,2}/\d{4} offerta dal framework per la Replace: le funzioni di callback per la trasformazione. L 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 lanno sepala sostituzione di occorrenze di stringhe con rati da /, tenendo conto anche delleventuaaltre non cos banale, ma richieda unelabole 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 allipotesi in cui si zione delle date: voglia sostituire, in un formato testo di imporPrivate Function ExpandDate(ByVal m As Match) tazione, un codice articolo con la sua descriAs String zione, che per va letta dal database. ProceReturn Date.Parse(m.Value).ToLongDateString diamo 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 nel formato numerico originale e, attraverso la potente funzione Parse delloggetto Date, la trasformer in un tipo Date di .NET. A questo punto verr sottoposta alla trasformazione in formato descrittivo esteso attraverso il metodo ToLongDateString delloggetto 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 stata fondata 2/3/2003 da un gruppo di tre pro grammatori. Dim RegExp As RegEx = New Regex(\d{1,2}/\d{1,2}/ \d{4}) Console.WriteLine( RegExp.Replace(sText, AddressOf ExpandDate))

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 lobiettivo 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

Bibliografia
[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.

L effetto della trasformazione mostrato in Figura 3.

N. 76 - Luglio/Agosto 2007 VBJ

27