Nessun risultato. Prova con un altro termine.
Guide
Notizie
Software
Tutorial

Espressioni regolari in C++11, introduzione

Validare l'input, effettuare ricerche all'interno del testo o implementare la funzione di trova e sostituisci, con le espressioni regolari di C++11
Validare l'input, effettuare ricerche all'interno del testo o implementare la funzione di trova e sostituisci, con le espressioni regolari di C++11
Link copiato negli appunti

C++11 è lo standard per il linguaggio C++ approvato dopo un lungo percorso il 12 Agosto 2012. Tra le numerose funzionalità introdotte c'è il supporto nativo alle espressioni regolari.

Perché l'introduzione delle Espressioni Regolari (chiamate per brevità regex) è una novità così importante? Tra gli impieghi più frequenti delle regex in programmazione c'è la validazione dell'input, ovvero quel controllo che verifica, ad esempio, se ciò che l'utente ha inserito in un campo numerico sia un numero valido oppure no.

Prima del rilascio del nuovo standard, validare l'input significava scrivere le proprie funzioni per la verifica dei dati inseriti e per la gestione di eventuali eccezioni, oppure utilizzare librerie specializzate (come boost).

In questo articolo vedremo allora come utilizzare l'implementazione delle regex all'interno di C++11.

Compatibilità dei compilatori

L'elaborazione delle espressioni regolari è già supportata da alcuni dei maggiori compilatori (fra i quali Clang++ e Visual C++ 2010 e seguenti) tuttavia, nel momento in cui scrivo, G++ 4.6 ha ancora un supporto parziale alle espressioni regolari: i sorgenti vengono compilati ma non eseguiti. Quindi per eseguire il codice di esempio di questo tutorial è bene che vi muniate di alternative valide.

La sintassi delle regex in C++11

Con le espressioni regolari è possibile validare un input (e molto altro) in modo semplice a patto di imparare la loro sintassi. Le regex sono infatti un modo di descrivere la struttura di una stringa (un pattern), permettendo così al computer di identificarla all'interno di un testo (matching) oppure dire se una stringa in ingresso sia dotata o no della stessa struttura.

Ad esempio ogni numero intero può cominciare opzionalmente con il simbolo "meno" (-) e segue con una serie di cifre. Con una espressione regolare è possibile descrivere questa struttura e riconoscere immediatamente la stringa "-304" come valida e scartare, ad esempio, la stringa "35op90".

Nelle espressioni regolari in C++ è possibile utilizzare diversi tipi di sintassi semplicemente passando un opzione come parametro delle funzioni. L'implementazione di default prevede però la sintassi ECMAScript ed è proprio di questa che elenchiamo gli elementi fondamentali in una piccola tabella:

Simbolo Significato
. (punto) Qualunque carattere ad esclusione dei terminatori di riga
[:alpha:] Qualunque carattere alfabetico
[:alnum:] Qualunque carattere alfanumerico
[:digit:] Qualunque cifra compresa fra 0 e 9
s Spazio
[^:class:] Nega la classe. Ad esempio [^:digit:] significa: qualsiasi carattere che non sia una cifra
* (asterisco) Zero o più ripetizioni della struttura precedente
+ (più) Una o più ripetizioni della struttura precedente
? (interrogativo) La struttura precedente è opzionale
{int} La struttura precedente viene ripetuta esattamente int volte
$ La struttura è collocata alla fine della frase
^ La struttura è collocata all'inizio della frase
| (pipe) Separa una serie di possibilità. Ad esempio a | b significa che può comparire il carattere a oppure il carattere b

La struttura di <regex>

L'implementazione delle espressioni regolari in C++ è costituita principalmente da:

  • 3 funzioni (in particolare sono template di funzioni) per l'esecuzione delle tre più comuni operazioni con le espressioni regolari:
    • matching (regex_match)
    • ricerca (regex_search)
    • sostituzione (regex_replace)

    Parleremo con sufficiente dettaglio di queste funzioni negli esempi successivi.

  • basic_regex, una classe per memorizzare e definire un'espressione regolare. Questa classe può a sua volta essere istanziata come regex (espressioni regolari su stringhe) oppure come wregex (espressioni regolari per stringhe wide).
  • match_result, una classe per memorizzare i risultati di una ricerca o di un matching. .
  • regex_error, classe per la gestione delle eccezioni.
  • regex_iterator, classe per gestire l'iterazione fra le diverse strutture identificate in una stringa.
  • Infine il namespace regex_constants che contiene tutta una serie di costanti e bitmask per passare parametri alle funzioni del modulo regex.

Costruire un validatore numerico

Iniziamo a lavorare concretamente con le espressioni regolari e vediamo come scrivere un programma C++ in grado di riconoscere se una stringa è un numero valido.

Cominciamo includendo le librerie necessarie:

#include <iostream>
#include <regex>
#include <string>

Le librerie iostream e string sono le classiche librerie C++ che ci permettono di avere accesso all'input dell'utente e alla gestione delle stringhe. L'header regex invece contiene tutte le funzioni che ci permettono di usufruire delle espressioni regolari.

Il nostro programma continua così:

using namespace std;
int main()
{
	string input;
	regex number("ESPRESSIONE REGOLARE"); // Da definire!
	while(true)
	{
		cout<<"Scrivi un numero: "<<endl;
		cin>>input;
		//Se la stringa è "q" esci dal programma.
		if(input=="q")
			break;
		if(regex_match(input,number))
			cout<<"Numero Valido"<<endl;
		else
		{
			cout<<"Input Invalido"<<endl;
		}
	}
}

Tutto qui! È un loop estremamente semplice e si basa su due punti:

  • Definire un oggetto regex che prende come parametro l'espressione regolare desiderata (cosa che presto andremo a fare).
  • Utilizzare la funzione regex_match, che prende come parametro una stringa e l'espressione regolare per verificare se la stringa data abbia la stessa struttura definita nella regex.

Non ci resta che definire l'espressione regolare per identificare un numero valido. La sintassi implementata in C++ è la stessa utilizzata dalle espressioni regolari in JavaScript, grep e molti altri famosissimi linguaggi e strumenti di sistema.

Iniziamo da una semplice regex in grado di identificare una cifra fra 0 e 9.

regex number("[[:digit:]]");

Poiché i numeri sono composti da più di una cifra aggiungiamo un + alla fine. Il segno "più" indica che quello che lo precede può essere ripetuto all'infinito ma almeno una volta.

regex number("[[:digit:]]+");

I numeri, inoltre, possono essere preceduti dal segno "meno" o dal segno "più". Aggiungiamo anche questa proprietà alla nostra espressione regolare.

regex number("(+|-)?[[:digit:]]+");

Il punto interrogativo (?) indica che la struttura che lo precede è opzionale. Il simbolo "pipe" (x|y) invece che andrà bene x oppure y. Notate inoltre che poiché il "più" è un simbolo appartenente alle "istruzioni" delle espressioni regolari è necessario precederlo con il doppio backslash .

A questo punto è possibile compilare ed eseguire il programma e cominciare a giocarci su. Per compilarlo con Visual Studio non dovrete fare nulla di particolare (F5), se invece utilizzate Clang il comando è

clang++ -std=c++0x -stdlib=libc++ regex_example.cpp -o regex_example

Un esempio dell'esecuzione dell'applicazione è mostrato nell'immagine seguente:

E se vogliamo identificare anche i numeri decimali? Ci basta modificare la stringa dell'espressione regolare.

regex number("((+|-)?[[:digit:]]+)(.(([[:digit:]]+)?))?");

La stringa è ovviamente più complessa ma la sostanza non cambia.

regex_search: la ricerca con le espressioni regolari

Oltre alla validazione delle stringhe le espressioni regolari possono essere convenientemente utilizzate per la ricerca di strutture caratteristiche (o pattern) all'interno di un documento. Ad esempio potremmo voler cercare tutti i numeri di telefono o i codici fiscali presenti in una pagina Web. Oppure possiamo voler eliminare tutti i tag HTML da un documento e lasciare solamente il testo.

Per compiere operazioni di questo tipo in modo efficace possiamo utilizzare il comando regex_search

regex_search(input, match_results, regex)

Questa funzione prende in ingresso tre parametri: il primo è la stringa sulla quale operare la ricerca, il secondo è una struttura di tipo match_result, il terzo è l'espressione regolare da ricercare.

Il valore di ritorno sarà true se il pattern cercato esiste, false altrimenti.

È giusto spendere qualche parola in più riguardo la struttura in cui viene salvato il risultato della ricerca. La variabile in questione è un istanza della classe template match_result. Per comodità sono presenti 4 scorciatoie per la sua definizione:

Comando Descrizione
cmatch Invoca la creazione di un oggetto match_result per stringhe letterali (ovvero espresse come const char*, come nel C)
smatch Invoca la creazione di un oggetto match_result per oggetti stringhe (come istanza della classe string)
wcmatch Invoca la creazione di un oggetto match_result per stringhe letterali di caratteri wide.
wsmatch Invoca la creazione di un oggetto match_result per oggetti stringhe di caratteri wide
Campo1 Descrizione1

Questa struttura non solo immagazzina la prima stringa corrispondente al pattern dato ma ricorda anche le "sotto-espressioni" (le parti racchiuse fra parentesi in un espressione regolare). Quest'ultimo comportamento in particolare ci sarà molto utile nell'ultima parte dell'articolo.

Facciamo anche qui un esempio. Supponiamo di voler estrarre il numero di telefono 06-000023 dalla frase "Spero di riuscire a trovare il numero 06-000023 presente in questa stringa." Il numero di telefono è così composto: 2 cifre, un trattino e altre 6 cifre. L'espressione regolare che rappresenta questa struttura è quindi:

regex telephone_number("[[:digit:]]{2}-[[:digit:]]{6}");

Il programma che estrae questo numero sarà semplicemente:

using namespace std;
int main()
{
	string input = "Spero di riuscire a trovare il numero 06-000023 presente in questa stringa.";
  regex telephone_number("[[:digit:]]{2}-[[:digit:]]{6}");
  smatch result;
  regex_search(input,result,telephone_number);
  cout << result[0].str() << endl;
  getchar(); // Se non usate Visual Studio questa riga non serve.
}

Una volta avviato il programma dovreste vedere il numero di telefono correttamente estratto.

regex_replace: trova e sostituisci

L'ultima grande funzionalità delle espressioni regolari è il cosiddetto trova e sostituisci. Anche per questo esiste un semplice comando: regex_replace

regex_replace(input, regex, replacement, option)

In questo caso la funzione prende quattro parametri

  1. La stringa in ingresso.
  2. L'espressione regolare da ricercare.
  3. La stringa da sostituire ad ogni occorrenza della regex in input
  4. Un parametro di formattazione solitamente impostato a regex_constants::format_default
  5. La funzione restituirà come valore di ritorno proprio la stringa input in cui ad ogni occorrenza di "regex" è stato sostituito "replacement".

Un esempio di questa funzione è dato dal seguente programma:

using namespace std;
int main()
{
	string input = "Voglio eliminare 90 tutti i 894 numeri da 39 questa 3513 stringa";
  string replacement = "";
  regex number("[[:digit:]]+");
  string result = regex_replace(input,number,replacement,regex_constants::format_default);
  cout << result << endl;
  getchar();
}

Questo programma ha l'unico scopo di eliminare tutti i numeri presenti nella frase di input. Ovviamente possiamo usare espressioni regolari arbitrariamente complesse per effettuare compiti meno banali del rimuovere i numeri da una stringa.

Espressioni regolari, un esempio complesso

Una volta compresi i fondamentali dell'utilizzo delle regex, possiamo addentrarci un po' e realizzare una applicaizione che svolga un compito più complesso: sfrutteremo le funzioni che abbiamo appena visto (in particolare regex_search) per creare un programma che converta una stringa formattata in stile Markdown in una stringa che contenga i giusti tag HTML.

Per chi non ne fosse a conoscenza la sintassi Markdown è un metodo molto semplice e diffuso per indicare la formattazione nei documenti di testo. Per non complicare troppo le cose mostreremo l'implementazione di un piccolo sotto-insieme della sintassi Markdown che chiameremo "microMarkDown" e che si limita a indicare parole in grassetto o corsivo:

  • Una parola circondata da doppi asterischi diventa una parola in grassetto. Ovvero **parola** diventa <strong>parola</strong>.
  • Una parola circondata solo da una coppia di asterischi diventa una parola in corsivo. Ovvero *parola* diventa <em>parola</em>.

Iniziamo dal principio:

string input = "Abbiamo due **parole** in **grassetto** e una in *corsivo*.";
 regex italic("(\*)([[:alpha:]]+)(\*)");
 regex bold("(\*\*)([[:alpha:]]+)(\*\*)");
 smatch result;
 string final_sentence = "";

Innanzitutto inseriamo la stringa di test. Possiamo configurare il nostro programma in modo che tale scritta venga richiesta dall'utente oppure venga letta da un file.

Dopodiché dobbiamo definire le espressioni regolari per il grassetto ed il corsivo. Le espressioni che vedete nell'esempio non sono le migliori possibili e possono essere migliorate (ad esempio permettendo spazi o numeri all'interno della parola da evidenziare) ma per il momento accontentiamoci di questa versione semplice. Fate bene attenzione che ogni espressione regolare è stata suddivisa in tre sotto espressioni (gli asterischi iniziali, il contenuto e gli asterischi che chiudono il grassetto o il corsivo). Questo ci servirà per estrarre il contenuto della frase.

Infine definiamo due variabili. La prima è la classica match_results mentre la seconda è una stringa (inizialmente vuota) che conterrà la stringa elaborata.

/** Gestisci Grassetto **/
while( regex_search(input, result, bold)) {
  final_sentence += result.prefix().str() + result.format("$2");
  input = result.suffix().str();
}
final_sentence += input;
/** ----- **/

A questo punto cominciamo con la prima elaborazione del grassetto. Attenzione! È importantissimo partire dal grassetto poiché la regex per il corsivo è un sottoinsieme di quella del grassetto. Notate infatti che il pattern descritto nel corsivo (asterisco-parola-asterisco) è contenuto all'interno della struttura della parola in grassetto (asterisco-asterisco-parola-asterisco-asterisco). Se facessimo il contrario, invece di trasformare la parola **grassetto** in <strong>grassetto</strong> ci ritroveremmo con una stringa *<em>grassetto</em>*. È quindi importante identificare casi simili quando elaborate sulla stessa frase una serie di espressioni regolari.

Vediamo il codice. In primo luogo c'è un ciclo che continua fintanto ci sono pattern validi nella stringa. All'interno facciamo i seguenti passi:

  • Usiamo il risultato di regex_search per costruire un primo pezzo della frase elaborata.
  • Impostiamo input come "result.suffix()" ovvero togliamo da input la parte che abbiamo appena elaborato.

Questo piccolo pezzo di codice contiene tre nuovi comandi. I primi due sono le funzioni suffix() e prefix() della classe match_results. Queste due funzioni ritornano rispettivamente il pezzo di stringa dopo e prima del pattern appena trovato.

Per esempio nella frase "Amo **veramente** le espressioni regolari", dopo la ricerca della regex bold, suffix() ritornerà " le espressioni regolari" mentre prefix() restituirà "Amo ".

Il secondo importantissimo comando è format. Questo comando genera una stringa utilizzando i pezzi dell'espressione regolare che abbiamo appena trovato. In particolare:

  • Viene sostituito a $0 l'intera stringa trovata. Ad esempio "**veramente**".
  • Viene sostituito a $N l'N-esima sotto-struttura. Nel caso della nostra espressione regolare $1 conterrà "**", $2 conterrà "veramente" e $3 avrà di nuovo "**".

Poiché la parola da trasformare in grassetto è identificata dalla seconda sotto-struttura della regex utilizziamo $2 per accedervi. La funzione format piazza quindi la parola circondata da doppi asterischi in mezzo a <strong> e </strong>.

Alla fine del ciclo (ovvero quando non ci sono più stringhe valide nel testo) non ci resta che attaccare ciò che rimane in fondo alla frase.

A questo punto la nostra frase originale sarà diventata: "Abbiamo due <strong>parole</strong> in <strong>grassetto</strong> e una in *corsivo*."

Dobbiamo quindi andare ad elaborare la parola in corsivo. Prima di fare ciò dobbiamo "resettare le variabili".

input = final_sentence;
  final_sentence = "";

Primo: la nostra nuova frase di input diventerà la frase parzialmente elaborata. Secondo: svuotiamo la variabile che conterrà la frase elaborata.

/** Gestisci Corsivo **/
  while( regex_search(input, result, italic)) {
    final_sentence += result.prefix().str() + result.format("<em>$2</em>");
    input = result.suffix().str();
  }
  final_sentence += input;
  /** ----- **/

A questo punto il ciclo per il corsivo procede in modo del tutto analogo a quello per il grassetto (con la ovvia differenza di usare il tag <em> invece del tag <strong>).

Una volta terminato non ci resta che stampare il risultato (o farne ciò che volete).

cout << final_sentence << endl;

Partendo da questo semplice esempio potete complicare la sintassi del nostro microMarkdown aggiungendo altri elementi. Ad esempio considerando come intestazioni <h1> le righe che circondate con un cancelletto (#), oppure trasformare una stringa del tipo "(questo è un link)[www.example.com]" in un corretto link HTML. Possiamo fare questo e molto altro in modo non dissimile da come abbiamo gestito grassetto e corsivo.

Altri Riferimenti

  • C++11 Regex Tutorial - (inglese) Una piccola serie di tutorial sulle espressioni regolari da cui è stata tratta la prima parte di questo articolo. Continua con un esempio di uso delle espressioni regolari applicate ai file.
  • Espressioni Regolari di Marco Beri - Un ottimo libro in italiano per addentrarsi nel mondo delle espressioni regolari.
  • Professional C++ - Un buon libro (in inglese) sul C++ che descrive le nuove funzionalità dello standard C++11.
  • Il modulo <regex> su CppReference -- Documentazione tecnica sulle espressioni regolari in c++.

Ti consigliamo anche