Ceci est une ancienne révision du document !
Lorsque nous lisons un texte, nous sommes capable de reconnaître la signification (ou sémantique) de certains motifs dans le texte. Par exemple, si on écrit “25/12/2014”, beaucoup de personnes reconnaîtront une date, correspondant au 25 décembre 2014. Si on écrit “18:30”, on reconnaît une heure : dix-huit heure trente. Ou encore, on reconnait que “http://www.google.fr” est une URL internet.
Nous sommes capable de trouver la sémantique d'un chaîne parce que l'on connaît le motif qui caractérise cette chaîne. On a l'habitude d'écrire les dates en indiquant le jour, le mois et l'année (en français). On a l'habitude aussi de voir des URL écrites sous la forme “http:/ /” suivi de plusieurs mots séparés par des points ou des barres obliques.
Les expressions régulières sont un moyen efficace d'écrire de tels motifs. Avec ces motifs, il sera ensuite possible de vérifier qu'un chaîne respecte ce motif ou encore identifier les sous-chaînes qui respectent ce motif.
origine du terme “expression régulière” ?
Les expressions régulières sont une fonctionnalité que l'on trouve dans beaucoup de langages de programmation modernes. En C++, une expression régulière correspond à la classe regex
de la bibliothèque standard (dans le fichier d'en-tête regex
). Il est possible de créer une expression régulière directement à partir d'une littérale chaîne de caractères ou d'une variable chaîne de type string
.
#include <regex> #include <string> int main() { std::regex pattern1 { "bla bla bla" }; // création à partir d'une littérale std::string s { "bla bla bla" }; std::regex pattern2 { s }; // création à partir d'une string }
Utilisation des raw string
Pour bien comprendre les expressions régulières, il faut donner quelques définitions :
Plus concrètement, si l'on prend la chaîne suivante : “La date du 25/12/2014 est un jeudi” et que l'on demande d'écrire une expression régulière pour trouver la date dans cette chaîne, alors la chaîne “La date du 25/15/2014 est un jeudi” est la séquence cible et la correspondance est “25/12/2014”. Le motif est une chaîne qui signifie “trouver une date au format jour/mois/année”. Bien sûr, il n'est pas possible d'écrire un motif de cette façon, il faut utiliser une syntaxe spécifique, qui sera décrite dans la suite de ce chapitre.
Avec une expression régulière, on va donc pouvoir réaliser principalement trois opérations, chaque opération correspondant à une fonction. Ces différentes fonctions seront détaillées par dans les prochains chapitres, la suite de ce chapitre sera consacré à la syntaxe utilisable pour écrire un motif. Mais pour vous permettre de pratiquer et apprendre correctement les expressions régulières, nous allons voir rapidement une syntaxe possible de ces fonctions (il est possible d'utiliser ces fonctions de différentes façon, nous n'en verrons qu'une seule pour le moment).
La première fonctionnalité des expressions régulières est la validation d'une chaîne, c'est-à-dire vérifier qu'une chaîne respecte un motif. La fonction correspondante est la fonction regex_match
. Une version simple de cette fonction prend en arguments la séquence cible et l'expression régulière et retourne une valeur booléenne (vrai si la séquence cible correspond au motif, faux sinon).
#include <iostream> #include <string> #include <regex> int main() { std::regex pattern { "abc" }; // on recherche le motif "abc" std::string target { "abcdef" }; bool result = std::regex_match(target, pattern); std::cout << std::boolalpha << result << std::endl; target = "abc"; result = std::regex_match("abc", pattern); std::cout << std::boolalpha << result << std::endl; }
affiche :
false true
Le motif “abc” correspond donc à la séquence cible “abc”, mais pas à la séquence “abcdef”. Il permet donc de vérifier que la séquence cible correspond exactement à la chaîne “abc”. Ce n'est pas très utile, le but est surtout pour montrer la syntaxe de la fonction.
La deuxième utilité des expressions régulières est de rechercher les sous-chaînes de la séquence cible correspondant au motif. La fonction correspondante est regex_search
. Celle-ci prend les mêmes arguments que la fonction regex_match
et retourne vraie si la fonction trouve au moins une sous-chaîne correspond au motif.
#include <iostream> #include <string> #include <regex> int main() { std::regex pattern { "abc" }; // on recherche le motif "abc" std::string target { "abcdef" }; bool result = std::regex_search(target, pattern); std::cout << std::boolalpha << result << std::endl; target = "abc"; result = std::regex_search("abc", pattern); std::cout << std::boolalpha << result << std::endl; }
affiche :
true true
Le code est le même que précédemment, en remplaçant regex_match
par regex_search
. Par contre, le résultat est très différent : la séquence cible “abcdef” ne correspondait pas au motif lorsque l'on utilise regex_match
, mais il correspond en utilisant regex_search
(il existe bien le motif “abc” dans la séquence “abcdef”).
Pour terminer, la troisième utilisation principales des expressions est le remplacement des sous-chaînes correspondant à un motif par d'autres chaînes. La fonction correspondante est regex_replace
, qui prend en argument la séquence cible, l'expression régulière (comme pour les précédentes fonctions) et la chaîne de remplacement, puis retourne la chaîne après modifications.
#include <iostream> #include <string> #include <regex> int main() { std::regex pattern { "abc" }; // on recherche le motif "abc" std::string target { "abcdefabc" }; std::string replacement { "123" }; std::string result = std::regex_replace(target, pattern, replacement); std::cout << result << std::endl; }
affiche :
123def123
Ce qui correspond bien à la chaîne “abcdefabc”, en remplaçant les sous-chaînes”abc” par “123”.
Maintenant que vous avez une idée générale du fonctionnement des expressions régulières, il va falloir apprendre à écrire des motifs. La syntaxe à utiliser n'est pas très compliquée quand on a l'habitude, mais elle est écrite sous forme assez compacte dans une chaîne, ce qui ne facilite pas la lecture. Il ne faut donc pas hésiter à s'attarder un peu sur ce chapitre et pratiquer un maximum d'exercices, le temps de bien assimiler la syntaxe des expressions régulières.
La classe std::regex
du C++ prend en charge différentes syntaxes, que l'on peut spécifier comme second argument lors de la création d'une expression régulière. Par exemple :
std::regex("abc", std::regex::ECMAScript);
La liste des syntaxes acceptées par std::regex
est disponible dans la documentation de cette classe. Par défaut, c'est la syntaxe ECMAScript qui est utilisée, nous utiliserons donc uniquement cette syntaxe dans ce cours. Mais sachez que d'autres syntaxes sont possibles.
Lorsque l'on débute avec les expressions régulières, il est parfois plus facile de visualiser le motif sous forme graphique. Si vous êtes plus à l'aise pour apprendre de cette façon, il existe des outils qui permettent de générer une représentation graphique à partir d'une expression régulière.
Par exemple, l'expression régulière ”(\d{2})[-/](\d{2})[-/](\d{4})”
, qui permet de valider une date, peut être représentée sous la forme suivante :
Ce graphique se lit de gauche vers la droite, il suffit de suivre les chemins possibles pour lire ce motif. Ce motif est donc constitué de trois groupes séparé par les caractères -
ou /
. Chaque groupe est constitué de chiffres (“digit”) répété 2 fois pour les deux premiers groupes et 4 fois pour le dernier groupe.
L'ensemble des graphiques de ce cours pour les expressions régulières sont générés automatiquement par le site http://www.regexper.com/ et sont sous licence Creative Common (CC BY 3.0).
#include <iostream> #include <string> #include <regex> void search(std::string const& target, std::string const& pattern_str) { std::regex pattern { pattern_str }; std::cout << R"(search ")" << pattern_str << R"(" in ")" << target << R"(" = )" << std::boolalpha << std::regex_search(target, pattern) << std::endl; } void match(std::string const& target, std::string const& pattern_str) { std::regex pattern { pattern_str }; std::cout << R"(")" << pattern_str << R"(" match with ")" << target << R"(" = )" << std::boolalpha << std::regex_match(target, pattern) << std::endl; } int main() { std::cout << "Caractères de base" << std::endl; match("", R"(a)"); match("a", R"(a)"); match("b", R"(a)"); match("ab", R"(a)"); match("ab", R"(ab)"); match("abb", R"(ab)"); std::cout << R"(search ")" << pattern_str << R"(" in ")" << target << R"(" = )" << std::boolalpha << std::regex_search(target, pattern) << std::endl; }
Caractères spéciaux : ^ $ \ . * + ? ( ) [ ] { } | ont un sens spécifique. Pour utiliser le caractère "normal" correspondant à un caractère spécial, précéder de \ : \^ \$
\. \* \+ \? \( \) \[ \] \{ \} \|
#include <iostream> #include <string> #include <regex> void search(std::string const& target, std::string const& pattern_str) { std::regex pattern { pattern_str }; std::cout << R"(search ")" << pattern_str << R"(" in ")" << target << R"(" = )" << std::boolalpha << std::regex_search(target, pattern) << std::endl; } void match(std::string const& target, std::string const& pattern_str) { std::regex pattern { pattern_str }; std::cout << R"(")" << pattern_str << R"(" match with ")" << target << R"(" = )" << std::boolalpha << std::regex_match(target, pattern) << std::endl; } int main() { std::cout << "Caractères de base" << std::endl; match("", R"(a)"); match("a", R"(a)"); match("b", R"(a)"); match("ab", R"(a)"); match("ab", R"(ab)"); match("abb", R"(ab)"); std::cout << std::endl; std::cout << "différence entre match et search" << std::endl; match("ab", R"(a)"); search("ab", R"(a)"); std::cout << std::endl; std::cout << "Caractères générique (wildcard)" << std::endl; match("", R"(.)"); match("a", R"(.)"); match("abc", R"(.)"); std::cout << std::endl; match("abc", R"(abc)"); match("a5c", R"(abc)"); match("abc", R"(a.c)"); match("a5c", R"(a.c)"); std::cout << std::endl; match("a", R"([[:alpha:]])"); match("1", R"([[:alpha:]])"); match("&", R"([[:alpha:]])"); match("a", R"([[:alnum:]])"); // alnum = alphanumeric match("1", R"([[:alnum:]])"); match("&", R"([[:alnum:]])"); std::cout << "Autres : alnum ou w, alpha, blank, cntrl, digit ou d, graph, lower,\n" " print, punct, space ou s, upper, xdigit (digit hexa)" << std::endl; std::cout << std::endl; std::cout << "Ensemble de caractères (character set)" << std::endl; match("", R"([ab])"); match("a", R"([ab])"); match("b", R"([ab])"); match("ab", R"([ab])"); std::cout << std::endl; match("", R"([a-e])"); // range specification match("a", R"([a-e])"); match("e", R"([a-e])"); match("f", R"([a-e])"); match("ab", R"([a-e])"); std::cout << std::endl; match("", R"(ab[c-f])"); match("abc", R"(ab[c-f])"); std::cout << std::endl; // escape notation // \d = [[:d:]] // \D = [^[:d:]] // \s = [[:s:]] // \S = [^[:s:]] // \w = [[:w:]] // \W = [^[:w:]] std::cout << "Répétition" << std::endl; match("", R"(a*)"); // zero or more match("a", R"(a*)"); match("aa", R"(a*)"); match("aaaaaa", R"(a*)"); match("b", R"(a*)"); search("jdsihbhvdsv", R"(a*)"); std::cout << std::endl; match("", R"(a+)"); // one or more match("a", R"(a+)"); match("aa", R"(a+)"); match("aaaaaa", R"(a+)"); match("b", R"(a+)"); std::cout << std::endl; match("", R"(a?)"); // zero or one match("a", R"(a?)"); match("aa", R"(a?)"); std::cout << std::endl; match("", R"(a{3})"); // bounded repeat match("a", R"(a{3})"); match("aaa", R"(a{3})"); match("aaaaa", R"(a{3})"); match("", R"(a{3,})"); match("a", R"(a{3,})"); match("aaa", R"(a{3,})"); match("aaaaa", R"(a{3,})"); match("", R"(a{3,5})"); match("a", R"(a{3,5})"); match("aaa", R"(a{3,5})"); match("aaaaa", R"(a{3,5})"); match("aaaaaaa", R"(a{3,5})"); std::cout << std::endl; // non-greedy (non vorace) search("aaaaa", R"(a{3})"); // -> aaaaa = plus longue correspondance search("aaaaa", R"(a{3}?)"); // -> aaa = plus courte correspondance std::cout << std::endl; std::cout << "Ancres (anchor)" << std::endl; search("", R"(^a)"); // début search("a", R"(^a)"); search("abc", R"(^a)"); search("cba", R"(^a)"); std::cout << std::endl; search("", R"(a$)"); // fin search("a", R"(a$)"); search("abc", R"(a$)"); search("cba", R"(a$)"); std::cout << std::endl; std::cout << "Groupes (group)" << std::endl; std::cout << "Priorité des opérateurs (precedence)" << std::endl; }
affiche :
Caractères de base "a" match with "" = false "a" match with "a" = true "a" match with "b" = false "a" match with "ab" = false "ab" match with "ab" = true "ab" match with "abb" = false différence entre match et search "a" match with "ab" = false search "a" in "ab" = true Caractères générique (wildcard) "." match with "" = false "." match with "a" = true "." match with "abc" = false "abc" match with "abc" = true "abc" match with "a5c" = false "a.c" match with "abc" = true "a.c" match with "a5c" = true "[[:alpha:]]" match with "a" = true "[[:alpha:]]" match with "1" = false "[[:alpha:]]" match with "&" = false "[[:alnum:]]" match with "a" = true "[[:alnum:]]" match with "1" = true "[[:alnum:]]" match with "&" = false Autres : alnum ou w, alpha, blank, cntrl, digit ou d, graph, lower, print, punct, space ou s, upper, xdigit (digit hexa) Ensemble de caractères (character set) "[ab]" match with "" = false "[ab]" match with "a" = true "[ab]" match with "b" = true "[ab]" match with "ab" = false "[a-e]" match with "" = false "[a-e]" match with "a" = true "[a-e]" match with "e" = true "[a-e]" match with "f" = false "[a-e]" match with "ab" = false "ab[c-f]" match with "" = false "ab[c-f]" match with "abc" = true Répétition "a*" match with "" = true "a*" match with "a" = true "a*" match with "aa" = true "a*" match with "aaaaaa" = true "a*" match with "b" = false search "a*" in "jdsihbhvdsv" = true "a+" match with "" = false "a+" match with "a" = true "a+" match with "aa" = true "a+" match with "aaaaaa" = true "a+" match with "b" = false "a?" match with "" = true "a?" match with "a" = true "a?" match with "aa" = false "a{3}" match with "" = false "a{3}" match with "a" = false "a{3}" match with "aaa" = true "a{3}" match with "aaaaa" = false "a{3,}" match with "" = false "a{3,}" match with "a" = false "a{3,}" match with "aaa" = true "a{3,}" match with "aaaaa" = true "a{3,5}" match with "" = false "a{3,5}" match with "a" = false "a{3,5}" match with "aaa" = true "a{3,5}" match with "aaaaa" = true "a{3,5}" match with "aaaaaaa" = false search "a{3}" in "aaaaa" = true search "a{3}?" in "aaaaa" = true Ancres (anchor) search "^a" in "" = false search "^a" in "a" = true search "^a" in "abc" = true search "^a" in "cba" = false search "a$" in "" = false search "a$" in "a" = true search "a$" in "abc" = false search "a$" in "cba" = true Groupes (group) Priorité des opérateurs (precedence)
#include <iostream> #include <string> #include <regex> void search(std::string const& target, std::string const& pattern_str) { std::regex pattern { pattern_str }; std::cout << R"(search ")" << pattern_str << R"(" in ")" << target << R"(" = )" << std::boolalpha << std::regex_search(target, pattern) << std::endl; } void match(std::string const& target, std::string const& pattern_str) { std::regex pattern { pattern_str }; std::cout << R"(")" << pattern_str << R"(" match with ")" << target << R"(" = )" << std::boolalpha << std::regex_match(target, pattern) << std::endl; } std::string tr(std::string const& s, int i) { return std::regex_replace(s, std::regex(R"(\$\d)"), std::to_string(i)); } int main() { std::cout << "Groups" << std::endl; { std::regex pattern("(ab)cd(ef)"); // Find double word. std::string replacement = "le premier groupe est $1 et le second groupe est $2"; std::string target = "abcdef"; std::string output_str = regex_replace(target, pattern, replacement); std::cout << output_str << std::endl; } { std::regex pattern(R"((\d{2})[-/](\d{2})[-/](\d{4}))"); std::smatch match; std::regex_search(std::string("12-03-2014"), match, pattern); for (size_t i = 0; i < match.size(); ++i) { std::cout << i << ": " << match[i].str() << '\n'; } } std::cout << "Traduction" << std::endl; { std::regex pattern("([a-zA-Z]+) \\1"); std::string replacement = "$1"; std::string target = "The cat cat bites the dog dog."; std::string output_str = regex_replace(target, pattern, replacement); std::cout << output_str << std::endl; } std::cout << tr("bla bla $1 bla bla", 123) << std::endl; std::cout << tr("bli bli bli bli $1", 123) << std::endl; std::cout << std::endl; std::cout << "Groupe" << std::endl; match("", R"((ab)*)"); match("a", R"((ab)*)"); match("b", R"((ab)*)"); match("ab", R"((ab)*)"); match("abc", R"((ab)*)"); match("ababab", R"((ab)*)"); std::cout << std::endl; match("cat", R"(c[a-z]*t)"); std::cout << std::endl; std::cout << "Exemples de regex" << std::endl; std::cout << "Date" << std::endl; match("12-03-2014", R"(\d{2}[-/]\d{2}[-/]\d{4})"); std::cout << "Time" << std::endl; match("15:17", R"(\d{2}:\d{2})"); std::cout << std::endl; // vérifier qu'un identifiant C++ est valide // [a-zA-Z_][a-zA-Z_0-9]* // fichier windows //[a-zA-Z_][a-zA-Z_0-9]*\.[a-zA-Z0-9]+ std::string regex_str = "[a-z_][a-z_0-9]*\\.[a-z0-9]+"; std::regex reg1(regex_str, std::regex_constants::icase); std::string str = "File names are readme.txt and my.cmd."; std::sregex_iterator it(str.begin(), str.end(), reg1); std::sregex_iterator it_end; while(it != it_end) { std::cout << it->str() << std::endl; ++it; } }
Groups le premier groupe est ab et le second groupe est ef 0: 12-03-2014 1: 12 2: 03 3: 2014 Traduction The cat bites the dog. bla bla 123 bla bla bli bli bli bli 123 Groupe "(ab)*" match with "" = true "(ab)*" match with "a" = false "(ab)*" match with "b" = false "(ab)*" match with "ab" = true "(ab)*" match with "abc" = false "(ab)*" match with "ababab" = true "c[a-z]*t" match with "cat" = true Exemples de regex Date "\d{2}[-/]\d{2}[-/]\d{4}" match with "12-03-2014" = true Time "\d{2}:\d{2}" match with "15:17" = true readme.txt my.cmd