Espressioni regolariIntroduzione alle espressioni regolariIn questo articolo tratteremo le Espressioni Regolari, ma desidero chiarire preliminarmente che lo farò alla maniera di un programmatore PHP, con tutti i limiti che questo comporta. Confido quindi nella clemenza degli appassionati ed esperti di PERL che potrebbero avere da ridire su questo scritto. Le Espressioni Regolari servono per trovare corrispondenze di modelli (pattern) su stringhe e costituiscono uno strumento tanto difficile ed ostico (soprattutto all'inizio) quanto potente ed utile. Un primissimo esempio, per aiutare a capire di cosa stiamo discutendo chi non ne avesse mai sentito parlare, potrebbe essere quello di un campo di un modulo destinato a contenere un importo in Euro: come fare a verificare che quanto digitato dall'utente sia corretto ? Se il contenuto del field dovrà essere inserito, ad esempio, in un campo di un Database (MySQL) di tipo DECIMAL(10,2), qualora non venissero rispettate queste caratteristiche, il valore verrebbe corrotto al momento dell'inserimento e quindi torniamo al problema iniziale: come fare a verificare la correttezza formale dell'input dell'utente ? Chiaro che non possiamo fare riferimento su funzioni PHP tipo is_numeric(), occorre invece costruire un modello, la cui corrispondenza con il contenuto del campo del modulo andrà poi verificata. Per la cronaca il modello del nostro esempio potrebbe assomigliare a questo (non spaventatevi, alla fine della lettura, vi risulterà chiarissimo): ^[0-9]{1,10}\.[0-9]{2}$ PHP conosce due classi di espressioni regolari, le POSIX e quelle compatibili con PERL. Le differenze riguardano essenzialmente la sintassi, ma è un argomento avanzato di cui non ci occuperemo, e in ogni caso qui verranno trattate soltanto le espressioni regolari del primo tipo (con le relative funzioni). I metacaratteriIl modello più semplice è ovviamente quello costituito da una parola o, più in generale, da una sequenza di caratteri. È chiaro che in questo caso non è conveniente usare una delle funzioni per le espressioni regolari perchè queste sono molto "dispendiose" in termini di risorse impiegate, per cui il loro utilizzo dovrebbe essere limitato ai casi in cui non sia possibile utilizzare un'altra funzione (ad esempio, strpos(), o str_replace()). Nella costruzione del modello, la cui corrispondenza andrà verificata, ci sono naturalmente una serie di regole da seguire, prima fra tutte quella che riguarda i cosìddetti "metacaratteri", ossia dei caratteri speciali, nel senso che, all'interno di un'espressione regolare, assumono un preciso significato. Questi caratteri sono:
Caratteristica comune a tutti questi metacaratteri è che se vogliono essere usati per il loro "valore letterale" (e non per quello speciale), devono essere preceduti da un backslash (\), per cui, ad esempio, l'esistenza di un punto interrogativo all'interno di una stringa, potrebbe essere accertata con questa espressione: \?. Vediamo adesso di esaminare più nel dettaglio i metacaratteri, cominciando dalle varie parentesi. Le parentesi quadre [], come si è accennato, racchiudono una "classe di caratteri". Questo vuol dire che il modello può o deve contenere alcuni o tutti i caratteri in esse contenute; chiariamo il concetto con degli esempi: [abc] [a-z] [0-9] [a-z0-9\?] [^a-z] Naturalmente una classe di caratteri può essere seguita (e normalmente lo è) da uno dei metacaratteri che indicano il numero di volte in cui uno dei caratteri in essa contenuti, deve essere presente, riprendendo l'ultimo modello: [a-z0-9\?]? [a-z0-9\?]* [a-z0-9\?]{3} [a-z0-9\?]{1,3} [a-z0-9\?]{3,} [a-z0-9\?]{,3} Le parentesiLe parentesi graffe, come abbiamo già visto, indicano il numero esatto, minimo, massimo o l'intervallo di volte in cui una un'esatta sequenza o una classe di caratteri, devono essere presenti in una stringa:
Le parentesi tonde, invece, fanno riferimento ad una sottostringa, o una parte di stringa se preferite, che viene assunta per il suo esatto valore letterale. Quindi ad esempio (abc) si riferisce all'esatta sequenza di caratteri abc, a differenza, come abbiamo visto, di [abc] che si riferisce invece ad uno dei tre caratteri. Ovviamente anche le parentesi tonde, possono essere usate con quei metacaratteri che indicano il numero di volte in cui la sottostringa deve ripetersi, per cui l'espressione (casa)? indica la presenza opzionale della parola casa (o, il che è lo stesso, che la parola deve essere presente zero o una volta). Ma le parentesi tonde sono molto importanti anche e soprattutto perche le parti di stringa (o le espressioni) in esse contenute, possono essere "utilizzate" per vari scopi (un replace per dirne uno), ma lo vedremo più avanti quando faremo un cenno alle funzioni PHP sulle Espressioni Regolari. Descriviamo adesso brevemente, gli altri metacaratteri. Partiamo dal punto che sta per qualsiasi carattere escluso un acapo, per cui, ad esempio, l'espressione (.)+ indica qualsiasi carattere ripetuto una o più volte (nella pratica è difficile che questo modello non trovi corrispondenza...). Dei caratteri *,? e + abbiamo già detto in relazioni alle classi e alle sottostringhe. Il carattere | (pipe) indica l'operatore OR e consente, quindi, di presentare più alternative per un'espressione; ad esempio (bello|normale|brutto) va letta come "bello o normale o brutto" ed è quindi soddisfatta quando solo una delle tre parole viene trovata nella stringa analizzata. Sul carattere ^ vale la pena di soffermarsi un attimo perchè, come accennato, esso assume una duplice valenza, a seconda del punto in cui si trovi all'interno dell'Espressione Regolare ed occorre quindi porre molta attenzione nel suo uso. Se posto all'inizio del modello, tale carattere indica l'inizio esatto dello stesso: ^(ciao) indica infatti che la stringa deve iniziare con la parola ciao. Ma l'accento circonflesso, se posto all'interno di una classe di caratteri, nega la stessa: [^0-9] indica qualsiasi carattere che non sia un numero. Infine, il carattere $ indica la fine di una stringa, per cui se viene usato in combinazione con ^, è possibile costruire un'Espressione Regolare che indichi un modello esattamente contenuto in una stringa, ad esempio ^Ciao come stai \?$, o che indichi l'esatto inizio e l'esatta fine di una stringa, ad esempio ^(Ciao) [a-zA-Z]+ (come stai \?)$ Le Espressioni Regolari, conoscono anche, per così dire, delle abbreviazioni per ottenere ciò che si desidera, in relazione, ad esempio, alle classi di caratteri usate più di frequente. Eccone di seguito un breve schema riepilogativo:
Analogamente, esistono delle classi di caratteri predefinite:
Le funzioni eregi ed ereg_replaceChe ci crediate o no, le poche regole appena esplicate (che non esauriscono l'argomento, comunque) sono sufficienti a permetterci di lavorare con le Espressioni Regolari e a costruire, quindi, dei validi modelli per gli scopi che ci proponiamo, mi preme solo aggiungerne una dettata dalla mia esperienza personale: prima di accingervi a costruire l'Espressione, è fondamentale che abbiate in mente l'esatto modello che volete riprodurre, le parti di cui esso si compone, in altre parole, che sappiate esattamente ciò che volete cercare delimitandone correttamente i confini. Le funzioni che PHP ci mette a disposizione (ribadisco che faccio riferimento alle POSIX extended e non alle Perl-Compatible) per lavorare con le Reg. Exp., sono le seguenti (sul manuale ufficiale on-line, le trovate a questo link (http://www.php.net/manual/it/ref.regex.php)): ereg() (e l'omologa eregi() che si differenzia solo per il fatto di essere insensibile alla differenza tra lettere maiuscole e minuscole) ereg_replace() (e l'omologa eregi_replace() che si differenzia solo per il fatto di essere insensibile alla differenza tra lettere maiuscole e minuscole) split() (e l'omologa spliti() che si differenzia solo per il fatto di essere insensibile alla differenza tra lettere maiuscole e minuscole) La funzione ereg(), serve a trovare la corrispondenza di un modello (il primo argomento della funzione) all'interno di una stringa (il secondo argomento della funzione); essa si presenta, pertanto, nella seguente forma: ereg( string epressione_regolare, string stringa [, array regs]) La funzione restituisce TRUE o FALSE a seconda se venga trovata o meno la corrispondenza (o se si verifichino degli errori). Il terzo argomento, opzionale, se inserito, restituirà un array che conterrà tanti elementi quante sono le parti del modello poste tra parentesi tonde ritrovate nella stringa più uno che sarà costituito dall'intera stringa ritrovata, e a questo array si potrà naturalmente fare riferimento per "utilizzare" quelle parti di testo ritrovate. Un esempio servirà a chiarire meglio quanto appena detto. Il nostro scopo è estrarre il tag <title> da una pagina remota e dobbiamo quindi costruire un modello che riesca ad individuarlo e, successivamente, dobbiamo estrarlo e stamparlo a video. Il codice per fare tutto ciò, potrebbe essere il seguente:
<?php Il modello è estremamente semplice, ciò che mi preme è far rilevare come, passando alla funzione il terzo argomento (l'array $regs), siamo riusciti ad utilizzare (stampandolo a video, in questo caso) ciò che era contenuto fra parentesi tonde, cioè il tag <title> della pagina remota che costituisce quindi il secondo elemento di $regs ($regs[1], il primo elemento $regs[0], contiene l'intera stringa ritrovata e quindi <title>Titolo della pagina</title>). La funzione ereg_replace() è usata per sostituire, all'interno della stringa passata come terzo argomento, la parte di testo che corrisponda all'Espressione Regolare passata come primo argomento, con il testo passato come secondo argomento; essa si presenta, pertanto, nella seguente forma: ereg_replace ( string espressione_regolare, string testo_sostitutivo, string stringa) la funzione restituisce la stringa originaria (eventualmente) modificata dalla sostituzione. Analogamente a quanto accade con la funzione ereg(), anche qui è possibile "utilizzare" quelle parti di testo ritrovate nella stringa che corrispondevano alle sottostringhe dell'Espressione Regolare poste fra parentesi tonde; per far riferimento a queste parti di testo, si usa la notazione \\n, dove n è il numero progressivo (partendo da \\1, \\0 si riferisce, invece all'intera stringa) che fa riferimento alle sottostringhe comprese tra parentesi tonde e che si incrementa di pari passo (\\1 farà riferimento alla prima sottostringa, \\2 alla seconda, e così via). Anche qui un esempio chiarirà meglio quanto detto, in questo caso ci proponiamo di rendere cliccabile, di trasformare quindi in link, un indirizzo email, e di stamparlo quindi a video, il codice per far tutto ciò, potrebbe somigliare a questo:
<?php Assegnato alla variabile $mail l'indirizzo email, riusciamo agevolmente a trasformarlo in link, grazie all'Espressione Regolare passata come primo argomento (che non mi soffermerò a spiegare, perchè ritengo che, arrivati a questo punto, dovreste già essere in grado di capirla) ed alle opportune sostituzioni operate nel secondo parametro della funzione. Se ci fate caso, nella Reg. Exp. abbiamo 3 sottostringhe comprese fra parentesi tonde, la prima corrisponde a tutto ciò che c'è prima del segno @, (quindi a mail nel nostro caso), la seconda corrisponde a tutto ciò che è compreso tra il segno @ e il punto prima del suffisso (quindi a sito nel nostro caso), e la terza infine, corrisponde al punto più il suffisso (quindi a .it nel nostro caso). A queste 3 sottostringhe facciamo riferimento nel testo sostitutivo con le notazioni \\1, \\2 e \\3 ed il gioco è fatto. La funzione splitLa funzione split(), infine, serve per suddividere una stringa (il secondo argomento della funzione) in più parti in base al carattere passato come primo argomento; svolge quindi lo stesso lavoro della funzione explode() ma, a differenza di questa, accetta un'Espressione Regolare come primo argomento. È possibile quindi "splittare" la stringa non sulla base di un singolo carattere, ma di un'espressione. Importante rilevare come split() restituisca un array contenente tanti elementi quanti sono le parti della stringa risultanti dalla divisione; la funzione accetta anche un terzo parametro facoltativo (un numero intero) che, se passato, limita il numero di elementi dell'array. Essa si presenta, quindi, nella seguente forma: split ( string espressione_regolare, string stringa [, int limite]) Anche qui facciamo un esempio per renderci conto di come lavori split(). Supponiamo di ricevere da un modulo una data in formato italiano (giorno mese anno) e di volerla trasformare in formato inglese (mese giorno anno), con un problema però, che non sappiamo se la data verrà passata come gg/mm/aaaa o come gg-mm-aaaa. In questo caso la funzione split() fa al caso nostro perchè possiamo passare, come primo parametro, un'Espressione Regolare che comprenda sia il carattere / che il carattere -. Ecco il codice che possiamo utilizzare: <?php // data in formato italiano $data = "09-11-2002"; if(ereg("([/-])+", $data, $regs)) { $separatore = $regs[1]; } $split_data = split("[/-]", $data); // data in formato inglese echo $split_data[1] . $separatore . $split_data[0] . $separatore . $split_data[2]; ?> Poco da dire, voglio solo far rilevare come utilizziamo la funzione ereg() per tirar fuori il carattere che separa giorno, mese ed anno (memorizzato nella variabile $separatore), per riproporre lo stesso anche come separatore della data in formato inglese. A conclusione di scritto, voglio proporvi due esempi concreti (sotto forma di funzioni) di applicazione di
espressioni regolari, che potranno tornarvi utili (almeno spero) nei vostri script.
<?php
function is_email($email)
{
if(eregi("^([a-z0-9_\.-])+@(([a-z0-9_-])+\\.)+[a-z]{2,6}$", trim($email)))
return 1;
else
return 0;
}
// esempio di utilizzo, si suppone che $_POST['Email']
// contenga l'input dell'utente
if(is_email($_POST['Email']))
{
echo "Email corretta";
} else {
echo "Email non corretta";
}
?>
L'altro esempio, infine, ha per oggetto la "trasformazione" di un URL del tipo http://www.sito.it (ma anche in altre forme), in un link, cliccabile quindi da una pagina web:
<?php
function CodeUrl($link)
{
$Search = "^(https?|ftp)://([^<>[:space:]]+)$";
$Replace = "<a href=\"\\1://\\2\" target=\"_blank\">\\1://\\2</a>";
return eregi_replace($Search, $Replace, $link);
}
// esempio di utilizzo
echo CodeUrl("http://www.html.it");
?>
|