View Single Post
Old 15-06-2010, 22:46   #1
MaxArt
Senior Member
 
L'Avatar di MaxArt
 
Iscritto dal: Apr 2004
Città: Livorno
Messaggi: 6611
[Tutorial] Regular Expressions

Regular Expressions
Questi oscuri garbugli di caratteri...



Sommario

Prima parte
Il classico problema
___Le regular expressions, come e quando
Le basi ed i primi esempi
___Sequenze speciali
___Quantificatori
___Alternanze e raggruppamenti
___Posizionamenti
___Fare a pezzi la stringa trovata!
___Backreferences
Modificatori (flags)
Sostituzioni
Dove posso provare le mie espressioni regolari?

Seconda parte
Espressioni regolari avanzate
___Lookarounds
___Quantificatori avidi, possisivi e svogliati
___Espressioni atomiche
___Catastrofe!
___Trucchi e funzionalità vari
Differenze con le RE tra i linguaggi
Links e riferimenti


Il classico problema
Vi sarà capitato, programmando, di dover effetuare delle ricerche all'interno di una stringa di una particolare sottostringa. Pratica comunissima, tanto che pressoché tutti i linguaggi di programmazione mettono a disposizione gli strumenti per farlo in maniera semplice e concisa.
Tuttavia, potrebbe anche esservi capitato di non dover ricercare una stringa precisa, ma qualcosa di più vago. Potrebbe anche essere semplicemente che si voglia cercare "pippo" all'interno di una stringa, ma che possa anche andare bene "Pippo", "PIPPO" o "pIpPo": normalmente, si converte tutta la stringa in minuscolo (o in maiuscolo) con le apposite funzioni e poi si cerca solo "pippo" (o "PIPPO").
Le cose si complicano se si vuole invece cercare una sottostringa tipo "Pippo ha XX anni", dove XX può essere un qualsiasi numero non negativo, nel qual caso si dovrebbe, nell'ordine,
  1. cercare la stringa "Pippo ha ";
  2. verificare che i caratteri successivi prima dello spazio successivo siano effettivamente cifre decimali;
  3. verificare che siano seguite dalla stringa " anni".
Le cose, allora, diventano complesse perché si vogliono evitare che casi come "Pippo ha corso fino a casa" o "Pippo ha 3 gatti" risultino in un "falso positivo". Il codice da scrivere si complica di conseguenza.

Non parliamo, poi, di dover andare a cercare cose come "Pippo", "PIPPO" ma non "pIPPO", oppure una data, o peggio un codice fiscale, se non, addirittura, un tag X/HTML ().
Ecco allora che vengono in nostro soccorso le Regular Expressions, che possono fare tutto questo e molto, molto di più.


Le regular expressions, come e quando
Le regular expressions (in italiano espressioni regolari, e spesso abbreviato in molti linguaggi come RegExp o RegEx) sono un concetto matematico sviluppato già negli anni '40, e sebbene la mia indole di matematico frema per illustrarvene i concetti, devo astenermi perché, a livello di programmazione, hanno utilità prossima allo zero Per chi si intende un po' di logica e di teoria degli insiemi la voce della Wikipedia relativa offre qualche spunto: http://it.wikipedia.org/wiki/Espressione_regolare
Le espressioni regolari (da ora in poi, RE) sono un aiuto importante per mantenere snello e compatto il proprio codice, riducendo ad una sola riga quel che normalmente avremmo fatto magari in decine di righe. I più comuni linguaggi di programmazione offrono pacchetti spesso molto completi e di facile reperibilità per le RE, quando non proprio forniti con le più comuni distribuzioni; alcuni linguaggi hanno addirittura supporto nativo alle RE (Javascript, Perl, PHP, Actionscript 3).
Non solo: spesso, molti editor di testo nonché ambienti di sviluppo offrono la possibilità di ricerca e sostituzione del testo tramite RE, e questo può essere uno strumento estremamente utile per risparmiarsi laboriose azioni magari fatte a mano.
Ma, se da una parte abbiamo uno strumento potente e flessibile subito a disposizione, dall'altra ci sono da pagare due scotti principali:
  1. condensando in pochi caratteri diverse righe di codice, il programma può risentirne parecchio in leggibilità e capacità di poterci intervenire per modifiche future (non è infatti raro che, con la prospettiva di dover modificare una RE, si opti - e a ragione - per riscriverla daccapo);
  2. le RE nascondono dietro di sé un codice nativo ma molto complesso, cioè quello che avremmo dovuto scrivere in sua vede: in poche parole, le RE possono rivelarsi lente, soprattutto se mal progettate.
Insomma, questo è per dirvi: non abusatene. Spesso un codice brillante può risultare più chiaro e veloce.
Ricordo, inoltre, che le RE sono implementate sì in svariati linguaggi ed ambienti, ma, complice il fatto che non sono mai state standardizzate, il supporto alle varie caratteristiche delle RE potrebbe essere più o meno completo, così come il comportamento (che si distingue in text-driven e regex-driven). Ogni volta che usare le RE con un linguaggio nuovo, consultate la parte relativa al modo in cui agiscono.


Le basi ed i primi esempi
I vari linguaggi di programmazione hanno diversi modi di rappresentare le RE. Spesso si tratta di stringhe che poi devono essere interpretate o semicompilate dal motore interno (Java, VB, C...), o nei casi di supporto nativo hanno una rappresentazione tutta propria, come nel caso di Javascript e Perl che le racchiudono tra due slash (/). In questa guida verranno rappresentate semplicemente con caratteri a larghezza fissa, senza apici di contorno, come questa: Pippo ha \d+ anni.

Sequenze speciali
Una RE sarà quindi composta da parti di stringhe, come Pippo ha e anni dell'esempio precedente, e da particolari sequenze di caratteri che vengono poi interpretate. Ad esempio, la sequenza \d+ significa "qualunque ripetizione di una o più cifre decimali". Vediamo dunque quali sono queste sequenze di caratteri speciali.
  • [...] Questa sequenza è in realtà un contenitore, che sta a significare "uno qualsiasi dei caratteri specificati qui dentro". Ad esempio, [abc] significa che viene ricercato il carattere 'a', oppure 'b', oppure 'c', mentre p[aeiou]zzo trova "pazzo", "pezzo", "pizzo", "pozzo" e "puzzo". Se vogliamo includere tutto l'alfabeto o una buona parte, si può anche scrivere [f-t] per includere tutti i caratteri compresi tra 'f' e 't', e lo stesso si può fare per i numeri, indicando ad esempio [0-4]. Naturalmente lo stesso si può fare con le lettere maiuscole, ed inoltre si possono combinare più intervalli di caratteri, come in [a-z0-9]. Se tra i caratteri si vuole ricercare anche il segno -, oppure le parentesi quadre, lo si deve far precedere da una backslash, così: [abc\-\[]. L'uso dell'escape con la barra rovesciata è prassi normale con le RE per ricercare tutti i caratteri considerati speciali compresa la backslash stessa.

    In alternativa, la sequenza [...]*si può usare anche per cercare tutti i caratteri che NON sono tra quelli specificati, facendo seguire alla [ il simbolo ^: ad esempio, [^abc] cerca tutti i caratteri che NON sono 'a', oppure 'b', oppure 'c'.
  • \d, \w, \s Indicano rispettivamente una cifra qualsiasi, un carattere alfanumerico qualsiasi più l'underscore, un carattere di spaziatura qualsiasi (spazio, tab, interruzioni di riga). \d è un'abbreviazione per [0-9], mentre \w ha lo stesso significato di [a-zA-Z0-9_], ma a differenza di queste espressioni le sequenze suddette si possono usare a loro volta all'interno delle parentesi quadre: [\d\s] è corretta, [[a-z][0-9]] no (insomma, le parentesi quadre non possono essere nidificate).
  • \D, \W, \S Indicano rispettivamente ogni carattere che NON sia una cifra, un carattere alfanumerico o underscore, una spaziatura.
  • \n, \r, \t Indicano rispettivamente il carattere di line feed (LF), carriage return (CR) e tabulazione.
  • \a, \e, \f, \v Indicano rispettivamente il carattere del beep (BELL), di escape (ESC), di form feed (FF) e vertical tab (VT). Li userete poco...
  • \xXX Serve ad indicare un carattere qualsiasi nella sua rappresentazione esadecimale. Ad esempio, \x41 rappresenta la 'A'.
  • \uXXXX Come sopra, ma questa volta XXXX è la rappresentazione esadecimale di un carattere Unicode. Alcuni linguaggi non supportano questa sintassi, oppure supportano l'alternativa \x{XXXX} (come il Perl).
  • . Serve a specificare la ricerca di un carattere qualsiasi, ad eccezione delle interruzioni di riga (o anche quelle se specificato dai flag).
Detto questo, è facile crearsi una RE che, ad esempio, ci consenta di trovare un codice fiscale all'interno di una stringa: per quanto visto, basta usare [A-Z][A-Z][A-Z][A-Z][A-Z][A-Z]\d\d[A-EHLMPR-T]\d\d[A-Z]\d\d\d[A-Z]. Semplice, no? Ma si può fare di meglio...
(Per ulteriori informazioni sulla sintassi dei codici fiscali, c'è sempre la fida Wikipedia: http://it.wikipedia.org/wiki/Codice_fiscale)


Quantificatori
Innanzitutto, tutte quelle ripetizioni di [A-Z] e \d si possono evitare. A questo proposito ci vengono incontro dei comodi quantificatori:
  • {...} Specifica che il carattere o gruppo di caratteri precedente dev'essere ripetuto un certo tot di volte. Ad esempio, \d{5} indica che si deve cercare una sequenza di esattamente 5 cifre decimali. È anche possibile specificare un numero minimo ed anche massimo di volte: \d{1,3} indica una sequenza da 1 a 3 cifre, mentre \d{2,} indica qualunque sequenza di almeno due cifre. Si badi che, con l'ultima sintassi, il match dell'espressione regolare si estende finché è possibile: dunque l'esempio riportato trova all'interno della stringa "Pippo è del 1990" tutta la sequenza "1990" e non solo "19". Per ottenere un comportamento come quest'ultimo si veda la parte relativa ai quantificatori greedy (di default), possessive e lazy.
  • ?, *, + Sono abbreviazioni rispettivamente per {0,1}, {0,} e {1,} (cioè, trovano rispettivamente una o nessuna ripetizione, un numero qualsiasi e almeno una ripetizione). Ad esempio, RegExp? trova sia "RegExp" sia "RegEx", mentre a+rgh! trova "argh!" ma anche "aaargh!", però non "rgh!".
A questo punto possiamo ridurre ulteriormente la nostra RE per i codici fiscali (oltretutto rendendone più veloce l'esecuzione), che diventa così [A-Z]{6}\d\d[A-EHLMPR-T]\d\d[A-Z]\d{3}[A-Z]. Poi possiamo sbizzarrirci come più vogliamo, ad esempio imponendo che il primo carattere sia per forza una consonante, ma per ora abbiamo raggiunto lo scopo (la validità del codice fiscale, verificando ad esempio la correttezza della data o del codice comunale, si può fare molto più semplicemente scrivendo poche righe di codice, ora che si sa che la sottostringa ha la forma di un codice fiscale).

Nota: fate attenzione ad usare correttamente i quantificatori! Un loro uso improprio, per quanto possa portare a risultati corretti, potrebbe avere conseguenze nefaste sulle performance del programma.


Alternanze e raggruppamenti
Ma se invece volessimo cercare due o più espressioni diverse tra loro? Ad esempio, ci interessano le stringhe del tipo "Pippo" ma anche "Pluto": dovremmo procedere a due ricerche? No, basta usare il carattere di alternanza, cioè la pipe (|), e creare un'espressione del tipo Pippo|Pluto. In questo modo, viene cercato "Pippo" oppure "Pluto", come volevamo.
Se invece fosse solo un pezzo di stringa a differire? Nel caso citato, "Pippo" e "Pluto" hanno le stesse lettere iniziali e finali. In questo caso sarebbe meglio utilizzare i raggruppamenti: essi non sono altro che un modo per intendere una sequenza di caratteri come un'unica entità dall'interprete delle RE. Per creare un gruppo basta includere le sequenze desiderate all'interno di parentesi tonde, ad esempio così: P(ipp|lut)o. In questo modo, l'interprete di RE cercherà ogni stringa che comincia per 'P', cui seguirà un gruppo che potrà essere "ipp" o "lut", cui segue una 'o'. Insomma, troverà "Pippo" e "Pluto", per l'appunto.
Le alternanze possono anche essere più di due, ad esempio avremmo potuto cercare P(ipp|lut|aperin)o, per includere anche "Paperino" nella nostra ricerca. Ma, cosa anche più importante, i raggruppamenti possono essere nidificati: la RE abc(def|ghi(123|456))ABC troverà tutte le seguenti stringhe: "abcdefABC", "abcghi123ABC" e "abcghi456ABC". Questo è utile, ad esempio, se volessimo cercare una data il cui anno può essere indicato da 2 o 4 cifre: \d\d-\d\d-(\d\d|\d{4}) è l'espressione regolare che fa al caso nostro, capace di trovare sequenze come "12-05-2004" o anche "22-04-85". (Ma anche "32-13-7865": come già detto, il controllo della correttezza della data può essere fatto tramite codice e non con le RE, anche se è possibile farlo. Io stesso ho creato una RE per trovare tutte le date comprese tra l'1-1-1901 e il 31-12-2099, senza bisogno di ulteriori controlli, e con una piccola modifica potrebbe essere usato per qualsiasi data, anche avanti Cristo. Se vi interessa, ditelo.)
Un altro grosso vantaggio dei raggruppamenti è la loro possibilità di essere usati in combinazione a quantificatori, in modo da ricercare una, nessuna o più ripetizioni dello stesso gruppo. Ad esempio, ba(na)+ troverà "bana", ma anche "banana", "bananana", "bananananana" e così via.


Posizionamenti
Cosa si può fare se vogliamo trovare delle espressioni solo in alcuni punti particolari del testo? Ad esempio, se volessimo trovare "Pippo" solo quando è all'inizio o alla fine di una stringa, oppure "ente" ma solo quando è alla fine di una parola? In questo caso intervengono delle sequenze di caratteri speciali che non indicano nessun carattere, ma servono per il posizionamento all'interno della stringa. Vediamoli.
  • ^ e $ Indicano rispettivamente l'inizio e la fine della stringa (o di una riga della stringa, nel caso di modalità multilinea - vedere più avanti la parte relativa ai flag). Ad esempio ^Pippo troverà "Pippo" quando è all'inizio della stringa. Oppure, ^\d+$ darà esito positivo solo quando la stringa è interamente composta da cifre decimali.
  • \A e \Z Sono simili a ^ e $, per non dire uguali, tranne nel caso in cui si usi la modalità multilinea, nel qual caso ^ e $ corrisponderanno all'inizio ed alla fine di ogni riga di testo, mentre \A e \Z indicheranno sempre l'inizio e la fine della stringa. L'unica eccezione (sia per \Z sia per $) si ha quando la stringa termina con un'andata a capo: in quel caso, $ e \Z indicheranno la posizione immediatamente prima di essa.
  • \b Indica la fine o l'inizio di una sequenza di caratteri alfanumerici (più l'underscore, come definito nella sequenza \w). In questo modo ente\b troverà "ente" in "dolcemente" ma non in "sentenza", perché dopo "ente" la parola continua.
  • \B Indica una qualsiasi posizione che NON sia l'inizio o la fine di una sequenza di caratteri alfanumerici. Dunque \Bente\B troverà "ente" in "sentenza", ma non in "dolcemente" o in "enteroclisma", così come \B-\B troverà '-' nella stringa "$$-$$" ma non in "foo-bar".
Si noti che le sequenze di posizionamento possono essere incluse anche all'interno di gruppi ed essere alternate ad altre sequenze. Ad esempio, (^|Topolino e )Pippo troverà "Pippo" quando è all'inizio della stringa, oppure "Topolino e Pippo" in qualsiasi parte del testo.


Fare a pezzi la stringa trovata!
Ebbene, abbiamo visto che \d\d-\d\d-\d{4} trova le date nel formato gg-mm-aaaa, mentre [A-Z][a-z]+ ha \d+ anni trova "Pippo ha 20 anni" ma anche "Paperone ha 75 anni". A questo punto possiamo procedere a recuperare i "pezzi" che ci servono, cioè i numeri indicanti giorno, mese e anno per la data, oppure nome ed età nel secondo esempio. Dovrebbe risultare piuttosto semplice, ma a volte può essere dispendioso, barboso e pure inutilmente complicato mettersi a fare ciò che la RE ha, in sostanza, già fatto. Ecco che in nostro soccorso arrivano i riferimenti.
In realtà, i riferimenti li abbiamo già creati negli esempi visti finora: vengono infatti ottenuti dai raggruppamenti tra parentesi tonde. Questi riferimenti tengono in memoria ("catturano") la sottostringa che ha un match con il gruppo. In questo modo, ([A-Z][a-z]+) ha (\d+) anni troverà sì "Pippo ha 20 anni", ma ci terrà comodamente in memoria le sottostringhe "Pippo" e "20" pronte per la nostra elaborazione!
I riferimenti vengono di solito salvati dai linguaggi in particolari oggetti, che di solito contengono sia tutta la stringa trovata, sia un elenco (solitamente un array) di stringhe contenenti i singoli riferimenti, e magari anche le loro posizioni iniziali e finali all'interno della stringa. I riferimenti sono numerati in ordine crescente a partire dal primo che viene trovato da sinistra, così "Pippo" sarà il primo riferimento, mentre "20" sarà il secondo. Il modo per reperire i riferimenti varia da linguaggio a linguaggio: in Java, ad esempio, un oggetto della classe Matcher consentirà, con il metodo find(), di trovare le corrispondenze e, una volta trovate, il metodo group(n) restituirà il riferimento nella posizione n, mentre start(n) e end(n) restituiranno la posizione del riferimento all'interno della stringa. In JavaScript, più semplicemente il metodo delle stringhe match(regex) restituirà un array il cui primo elemento è l'intera stringa trovata, cui seguiranno i diversi riferimenti catturati (non c'è indicazione della loro posizione, solo l'indicazione di inizio e fine della stringa trovata nell'oggetto globale RegExp o nell'array restituito da match).
Ma i gruppi sono stati usati anche per altre cose, ad esempio le alternanze: creeranno dei riferimenti anche in quel caso? La risposta è sì, anche se non vorremmo. Il modo per usare raggruppamenti senza creare riferimenti è quello di usare la sequenza (?:...) al posto di (...). Così, Il programma (\w+) contiene (\d+) bac(?:o|hi) troverà "Il programma helloWorld ha 1 baco" oppure "Il programma fooBar ha 7 bachi", ma i riferimenti saranno solo due: quello al nome del programma e quello al numero di bachi.
Perché evitare di creare riferimenti? La risposta dovrebbe essere ovvia: occupano memoria. Finché le espressioni sono così semplici non c'è problema, ma quando le cose si complicano sono dolori...
Un altro dubbio può essere: nel caso già visto di ba(na)+, ad esempio, viene creato un riferimento per ogni ripetizione di "na"? No. Il riferimento sarà sempre e solo uno, identificato da un numero assegnato in partenza, e conterrà solo l'ultima sottostringa "na" trovata.

Nota: che succede se una cattura viene fallita? Questo può capitare, ad esempio, nel caso di gruppi di cattura indicati in un'alternanza, come in (Pippo)|(Pluto) (sì, l'esempio è scemo). Nella stringa "Ciao Pippo!" la RE troverà "Pippo" e lo memorizzerà nella cattura con numero 1, ma non troverà "Pluto". La cattura 2 che valore avrà? Solitamente sarà un valore nullo, come null, nil o che altro in tanti linguaggi. In JavaScript e gli altri dialetti ECMAScript, invece, avrà valore "" (stringa vuota), così come una cattura del tipo (). Tenetelo a mente.


Backreferences
Il nostro problema ora diventi quello di voler cercare un semplice tag XML, aperto da una sequenza tipo <tag>, contenente un po' di testo ed infine chiuso dalla sequenza </tag>, ad esempio "<ciao>Pippo</ciao>". Una RE del tipo <\w+>(?:[^<]|<[^/])*</\w+> sembrerebbe che possa andare bene (esercizio: si presti attenzione alla parte compresa tra (?:...)* e si cerchi di capire cosa vuol dire). Tuttavia, è facile accorgersi che in caso di XML non ben formato, come ad esempio "<ciao>Pippo</saluto>", questa RE dapprima trova "<ciao>", poi trova "Pippo" e poi trova "</saluto>". Noi invece avremmo voluto che la corrispondenza fallisse, perché "</saluto>" non chiude il tag "<ciao>", e ci saremmo risparmiati un "falso positivo".
I gruppi di cattura ci possono dare una mano: creando dei riferimenti, li possiamo usare per indicare di star cercando qualcosa che è stato trovato in precedenza e catturato. Come già detto, ai gruppi di cattura viene assegnato un numero progressivo da 1 in poi, a partire da quello indicato più a sinistra: per farvi riferimento si usa una backreference, che si indica dalla sequenza \#, dove # è il numero assegnato alla cattura (può anche essere di due cifre, fino a 99). La nostra RE dovrà quindi catturare il nome del tag e riutilizzarlo dopo per indicare il tag di chiusura, in questo modo: <(\w+)>(?:[^<]|<[^/])*</\1>. Così siamo sicuri che l'eventuale tag di chiusura corrisponda a quello di apertura.

Note:
  1. Come detto, i riferimenti possono anche essere a due cifre. Attenzione, quindi, se dopo un riferimento ad una cifra si vuol far seguire un altro numero: se volessimo, ad esempio, cercare sequenze tipo "zerozero7" sbaglieremmo ad usare la RE (\w+)\17, perché faremmo un riferimento alla cattura 17, che non esiste. In questo caso si può inserire una sequenza vuota nel mezzo, tipo (?:) o .{0}, ottenendo (\w+)\1(?:)7. Macchinoso ma efficace.
  2. Alcuni linguaggi supportano i riferimenti alla catture posteriori al riferimento stesso: nell'esempio precedente, avremmo potuto scrivere anche \1(\w+)7, risparmiandoci la tediosa sequenza vuota. Ma in realtà, i "riferimenti a priori" hanno il loro vero senso solo all'interno nel caso di gruppi ripetuti.
  3. Alcuni linguaggi in più supportano anche i riferimenti nidificati, cioè riferimenti dentro al gruppo stesso cui fanno riferimento. Anche in questo caso, il vero senso si ha in caso di gruppi ripetuti: ad esempio (zero|\1sette)+ catturerà "zerozerosette" (ma anche "zerozerosettezerosettesette" o "zerozerozerosette": ricordare il dubbio alla fine del sottoparagrafo precedente).


Modificatori (flags)
Con una semplice opzione è anche possibile cambiare il modo in cui il motore applica le RE. Ad esempio, gli si può dire di ignorare le differenze tra maiuscole e minuscole. I modificatori vengono solitamente abbreviati con una singola lettera minuscola. Vediamoli:
  • i ignore-case. Semplicemente la RE non fa distinzione tra lettere maiuscole e minuscole.
  • m multi-line. In questa modalità, i caratteri speciali ^ e $ corrisponderanno all'inizio e alla fine di ogni riga piuttosto che di tutta la stringa.
  • s single-line o dot-all. In questo caso, invece, il carattele speciale . (il punto) corrisponderà anche alle andate a capo.
  • x free-space. Utile per poter scrivere RE in maniera più comprensibile, questa flag indica al motore RE di ignorare ogni spazio bianco (comprese le andate a capi) e sarà possibile addirittura inserire commenti all'interno della RE, facendoli precedere da un simbolo #.
  • g global. La RE andrà a trovare tutti i match possibili all'interno della stringa e non si fermerà alla prima. Non sempre questa modalità ha un senso, tant'è vero che alcuni linguaggi neanche la supportano. Il suo maggiore utilizzo è principalmente nelle sostituzioni (si veda dopo).
Si noti che i modificatori non sono esclusivi, e non lo sono neppure multi-line e single-line: un modificatore sm, semplicemente, farà corrispondere ^ e $ ad inizio e fine di ogni riga, e al punto anche le andate a capo.
La modalità free-space può essere molto comoda per le RE più complesse ed usate di comune. Quella per la data, ad esempio, si può scrivere così:
Codice:
( \d \d? )    # Trova e cattura il giorno
[ \- \/ ]     # Trova il carattere di separazione
( \d \d? )    # Trova e cattura il mese
[ \- \/ ]     # Altro separatore
( \d{4} )     # Trova e cattura l'anno
Questa RE avrà lo stesso effetto della più oscura (\d\d?)[\-\/](\d\d?)[\-\/](\d{4}). Per indicare gli spazi, se non si vuole usare \s si possono usare i costrutti [ ] (aperta quadra, spazio, chiusa quadra) o \ (backslash seguita da uno spazio).


Sostituzioni
Ora che abbiamo i riferimenti (e magari anche le loro posizioni) per noi diventa facile manipolare i dati. Un altro annoso problema che potremmo incontrare è quello della sostituzione di sottostringhe all'interno di una stringa. Solitamente abbiamo da parte la posizione iniziale e finale della stringa trovata, per cui il problema si semplifica... ma può essere ulteriormente semplificato grazie ai comuni metodi di sostituzione che vengono offerti dai vari linguaggi ed ambienti di sviluppo.
Supponiamo, ad esempio, di voler trasformare una data nel formato gg-mm-aa nel corrispondente gg/mm/19aa. Normalmente, quello che faremmo è, una volta che la RE ha trovato la corrispondenza, prendere la stringa iniziale, prendere la sottostringa dall'inizio fino all'inizio della corrispondenza, concatenarla con la stringa che abbiamo creato con la data nel formato che vogliamo, ed infine concatenarla con il resto della stringa. Noioso, vero?
Invece è comune che i linguaggi mettano a disposizione i metodi replace(string, string) (o qualcosa di simile), ma che abbiano un overload anche nel metodo replace(regex, string), in modo che la sostituzione avvenga con i match trovati con le RE. In questo caso, la stringa di sostituzione può contenere le indicazioni ai riferimenti trovati (backreferences), che vengono solitamente rappresentate da sequenze come "$X" oppure "\X", dove X indica il numero del riferimento trovato. (Nei linguaggi ho trovato più comune l'uso di "$X", mentre negli editor mi è parso più comune "\X". Consultate l'help relativo, comunque.)
Dunque, per ottenere il nostro scopo possiamo procedere con replace() usando (\d\d)-(\d\d)-\(\d\d) come RE, e "$1/$2/19$3" come stringa da sostituire. In questo modo, una data come "12-08-82" viene trasformata subito in "12/08/1982". Si noti che i riferimenti, nella stringa di sostituzione, vengono spesso numerati a partire da 1 e non da 0 come ci si potrebbe aspettare da prassi comune nei linguaggi di programmazione.
In alcuni linguaggi, soprattutto quelli che non supportano un metodo replaceAll() o simile (tipo il JavaScript), può tornare utile usare RE con flag global, in modo da ottenere una sorta di "sostituisci tutto".


Dove posso provare le mie espressioni regolari?
Solitamente, un editor di testo che le supporti potrebbe fare a caso vostro. Se volete sapere quali supportano le RE, questa pagina di Wikipedia vi può aiutare:
http://en.wikipedia.org/wiki/Compari...Basic_features
Personalmente, uso Notepad++. Se invece volete qualcosa di graficamente più carino e che dia subito un colpo d'occhio dei risultati trovati, forse questo sito ha quello che cercate: http://www.gskinner.com/RegExr/


**********************
Il contenuto di questo post è rilasciato con licenza Creative Commons Attribution-Noncommercial-Share Alike 2.5
Creative Commons Attribution-Noncommercial-Share Alike 2.5
__________________
HWU Rugby Group :'( - FAQ Processori - Aurea Sectio - CogitoWeb: idee varie sviluppando nel web

Ultima modifica di MaxArt : 15-06-2010 alle 22:50.
MaxArt è offline   Rispondi citando il messaggio o parte di esso