Utilisation de Boost.Regex

ram-0000

Ce tutoriel a pour but de présenter l'utilisation de la bibliothèque Boost.Regex. Ce document est architecturé autour d'un exemple simple de classe C++ permettant d'exploiter et d'utiliser des expressions régulières.

Votre avis et vos suggestions sur ce tutoriel m'intéressent ! Alors après votre lecture, n'hésitez pas : Commentez ♪

- Titre : Utilisation de Boost.Regex

- Auteur : ram-0000

- Parution : 19 novembre 2008

- Mise à jour : 4 mars 2009

- Licence : Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright ® 2008-2010 ram-0000. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Sommaire

- 1. Introduction

- 1-1. Avant-propos

- 1-2. But de ce tutoriel

- 1-3. But des expressions régulières

- 1-4. Environnement de développement

- 1-5. Remerciements

- 2. Installation de Boost

- 2-1. Téléchargement

- 2-2. Décompression

- 2-3. Compilation

- 2-4. Configuration de Visual Studio 2005

- 3. La classe CMyRegex

- 3-1. Fonctionnement général de la classe

- 3-2. Les méthodes et membres de la classe

- 3-1-1. Les fonctions de base de la classe

- 3-1-2. Les fonctions de manipulation de l'expression régulière

- 3-1-3. Les fonctions de manipulation de la dernière erreur

- 3-1-4. Les fonctions de matching d'expression

- 3-1-5. Les autres fonctions

- 3-3. Le fichier header

- 3-4. Le fichier source

- 4. Le test de la classe CMyRegex

- 4-1. Le fichier source de test de la classe CMyRegex

- 4-2. Résultat attendu des tests de la classe CMyRegex

- 5. Conclusions

1 - Introduction

1-1 - Avant-propos

Boost est un ensemble de bibliothèques portables destinées à faciliter la vie du développeur C++. Boost se propose de construire du code de référence pouvant aussi, à terme, être incorporé au standard (STL).

Pour plus d'informations, vous pouvez consulter :

- Les différents tutoriels Boost de développez.com : Tutoriels.

- La F.A.Q Boost de développez.com : F.A.Q.

- Le site officiel de Boost : Boost.

1-2 - But de ce tutoriel

Ce tutoriel a pour but de présenter l'utilisation de la bibliothèque Boost.Regex. Ce document est architecturé autour d'un exemple simple de classe C++ permettant d'exploiter et d'utiliser des expressions régulières.

1-3 - But des expressions régulières

Le but des expressions régulières (certaines littératures parlent aussi d'expressions rationnelles) est de résoudre le problème de l'analyse d'un texte lorsqu'il devient trop complexe pour pouvoir être résolu par analyseur développé "from scratch". Par exemple, savoir si le message de log généré par un serveur Syslog est valide ou non.

Pour rappel, le format d'un message de log Syslog est le suivant :

- La partie PRI : Cette partie est composée de 3, 4 ou 5 caractères. Le premier caractère est le caractère "<" suivi par un nombre représentant la priorité du message et terminé par le caractère ">". La valeur de la priorité est calculée en multipliant la valeur de la facilité du message par 8 et en additionnant la valeur de la sévérité du message.

- La partie HEADER : Cette partie du message Syslog comprend la date du message ainsi que le nom ou l'adresse IP de l'émetteur du message séparés par le caractère espace (" ") et terminée par le caractère espace (" "). La date est au format "Mmm dd hh:mm:ss"

- La partie MSG : Cette partie du message comprend le message texte à transférer.

- Pour plus d'informations sur le protocole Syslog, se référer aux RFC 3164 et RFC 3195.

Exemple d'un message de log Syslog

<165>May 18 14:46:18 192.168.1.1 Un message Syslog

La réalisation d'un analyseur dédié à ce type de message n'est pas triviale, ne serait-ce parce que le nom de l'émetteur du message peut être spécifié par son nom de machine ou bien son adresse IP. Une expression régulière va permettre de résoudre de manière générique cette problématique.

L'expression régulière qui pourrait être utilisée pour cet exemple est la suivante :

Expression régulière d'analyse d'un message de log Syslog

<\d{1,3}>(?:Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec) \d\d \d\d:\d\d:\d\d [^ ]* .*

Un autre problème qui peut être résolu à l'aide des expressions régulières est l'extraction des données d'un message. Toujours en reprenant l'exemple précédent du message de log Syslog, il peut être parfois intéressant d'extraire les différents champs que sont la priorité du message, la date de génération, le nom ou l'adresse IP de l'émetteur ainsi que le texte du message lui-même.

Dans ce cas-là, on utilisera aussi une expression régulière en utilisant la fonctionnalité de capture de chaînes à l'aide des parenthèses :

Expression régulière d'extraction des champs d'un message de log Syslog

<(\d{1,3})>((?:Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec) \d\d \d\d:\d\d:\d\d) ([^ ]*) (.*)

Il existe plusieurs manières d'écrire une expression régulière utilisant des syntaxes différentes :

- La syntaxe Perl. C'est la syntaxe utilisée par le langage de programmation Perl.

- La syntaxe POSIX étendue. C'est la syntaxe supportée par l'API C POSIX des expressions régulières et par les utilitaires Unix egrep et awk.

- La syntaxe POSIX basique. C'est la syntaxe utilisée par les utilitaires Unix sed, grep et emacs.

La manière d'écrire une expression régulière et la syntaxe utilisée ne sont pas l'objet de ce tutoriel, il existe d'excellentes ressources sur Internet pour cela :

- Boost.Regex. Le point d'entrée officiel de la documentation (en anglais) de la bibliothèque Boost.Regex sur le site de Boost.

- Regular Expression Syntax. La documentation (en anglais) de la syntaxe des expressions régulières de la bibliothèque Boost.Regex.

- Initiation aux expressions régulières en PHP. Un tutoriel sur les expressions régulières en PHP.

- Utilisation des expressions régulières en .Net. Un tutoriel sur les expressions régulières en .NET.

- Les Expressions Rationnelles appliquées en VBA Access. Un tutoriel sur les expressions régulières en VBA Access.

- Les expressions régulières en C. Un tutoriel sur les expressions régulières en C.

- Les expressions régulières avec l'API Regex de Java. Un tutoriel sur les expressions régulières en JAVA.

Ce tutoriel est organisé en 3 paragraphes :

- Le paragraphe 2 traite l'installation, la configuration et la compilation de Boost.

- Le paragraphe 3 concerne la spécification de la classe C++ CMyRegex permettant de manipuler des expressions régulières en encapsulant un objet Boost.Regex.

- Le paragraphe 4 décrit un programme de test permettant de valider cette classe C++ et de la tester.

1-4 - Environnement de développement

L'environnement de développement utilisé lors de la rédaction de ce tutoriel est le suivant :

Architecture cible

Microprocesseur x86

Mon poste de travail est un x86

Plateforme cible

Microsoft Windows

C'est la plateforme de développement que j'utilise tous les jours. Ce choix signifie que les fonctions natives de l'API WIN32 seront de préférence utilisées.

Environnement de développement

Visual Studio 2005

C'est l'environnement de développement que j'utilise de manière professionnelle

Langage de développement

C++

La bibliothèque Boost ne peut s'utiliser que dans un environnement C++

Version de Boost

1.38.0

C'est la version courante à la date d'écriture de ce tutoriel

Jeux de caractères supportés

Support des jeux de caractères Unicode et ANSI

Nativement, les std::wstring sont utilisées par la classe CMyRegex de ce tutoriel. Pour des raisons de compatibilité et aussi d'utilisabilité de la classe, les chaînes de caractères ANSI (std::string) sont aussi supportées. Elles sont transformées en interne en sdt:wstring avant d'être utilisées.

1-5 - Remerciements

Je tiens à remercier 3DArchi, Jean-Marc.Bourguet, Luc Hermitte, Alp et Florian Goo pour leurs conseils avisés lors de la relecture de ce tutoriel.

2 - Installation de Boost

2-1 - Téléchargement

La première chose à faire est de créer le répertoire d'installation de Boost. Par choix personnel et arbitraire, celui-ci s'appellera boost sera créé dans le répertoire C:\Program Files\.

La seconde chose à faire est de récupérer l'archive complète de Boost. Cette archive se récupère à partir du site de Boost. En fait, cette archive est hébergée sur le site sourceforge, le fichier récupéré est au format zip (plus par habitude de ce format que par véritable raison). Le fichier téléchargé boost_1_38_0.zip est sauvegardé dans le répertoire C:\Program Files\boost\. La taille de ce fichier est de 56 142 848 octets.

La troisième chose à faire est de télécharger l'utilitaire bjam qui est l'utilitaire nécessaire à la compilation de Boost. Cet utilitaire se récupère aussi à partir du site de Boost. Cet utilitaire est aussi hébergé sur le site sourceforge, le fichier récupéré est boost-jam-3.1.17-1-ntx86.zip. Il permet de compiler le paquetage de Boost sur une architecture de type PC x86. Pour les autres architectures, il conviendra de télécharger la version correspondante de l'utilitaire bjam. Le fichier téléchargé boost-jam-3.1.17-1-ntx86.zip est aussi sauvegardé dans le répertoire C:\Program Files\boost\. La taille de ce fichier est de 117 793 octets.

Récapitulatif :

1. Création du répertoire C:\Program Files\boost\

2. Téléchargement de l'archive Boost 1.38.0 et copie dans le répertoire C:\Program Files\boost\

3. Téléchargement de l'utilitaire bjam 3.1.17.1 pour architecture i386 et copie dans le répertoire C:\Program Files\boost\

2-2 - Décompression

Une fois que le téléchargement de ces 2 fichiers est terminé, il reste à décompresser ces 2 archives.

On commence par décompresser le fichier boost_1_38_0.zip et l'on met tous les fichiers et répertoires contenus dans l'archive sous le répertoire C:\Program Files\boost\boost_1_38_0\. La structure décompressée présente cette forme :

Arborescence décompressée de Boost

C:\program Files\boost\boost_1_38_0\ .................The &#8220;Boost root directory&#8221;
index.htm .........A copy of www.boost.org starts here
boost\ .........................All Boost Header files
lib\ .....................precompiled library binaries
libs\ ............Tests, .cpps, docs, etc., by library
index.html ........Library documentation starts here
algorithm\
any\
array\
&#8230;more libraries&#8230;
status\ .........................Boost-wide test suite
tools\ ...........Utilities, e.g. bjam, quickbook, bcp
more\ ..........................Policy documents, etc.
doc\ ...............A subset of all Boost library docs

Ensuite, on extraira le fichier bjam.exe de l'archive boost-jam-3.1.17-1-ntx86.zip et il sera copié dans le répertoire C:\Program Files\boost\boost_1_38_0\.

Récapitulatif :

1. Décompression de Boost dans le répertoire C:\Program Files\boost\

2. Décompression de bjam.exe et copie du fichier dans le répertoire C:\program Files\boost\boost_1_38_0\

2-3 - Compilation

Boost contient 2 types de bibliothèques :

- les bibliothèques Header only, ces bibliothèques ne nécessitent pas de compilation préalable, tout est contenu dans les en-têtes et elles sont prêtes à être utilisées immédiatement.

- les bibliothèques qui nécessitent une compilation initiale.

Les bibliothèques qui nécessitent une compilation initiale avant leur utilisation sont :

- Boost.Filesystem

- Boost.IOStreams

- Boost.ProgramOptions

- Boost.Python

- Boost.Regex

- Boost.Serialization

- Boost.Signals

- Boost.Thread

- Boost.Wave

Comme le but de ce tutoriel est d'utiliser Boost.Regex, il va donc falloir compiler les bibliothèques pour cela, les commandes à effectuer sont les suivantes :

- Lancer un shell. Pour cela, faire menu Démarrer/Exécuter puis taper la commande cmd

- Se positionner dans le répertoire de Boost. Pour cela, taper la commande cd "C:\program Files\boost\boost_1_38_0\" dans le shell précédemment lancé. Attention aux guillemets dans la commande, ils sont importants.

- Initialiser l'environnement Visual Studio 2005. Pour cela, taper la commande "C:\Program Files\Microsoft Visual Studio 8\VC\vcvarsall.bat" x86 dans le shell précédemment lancé. Cette commande suppose que Visual Studio est installé dans le répertoire C:\Program Files\Microsoft Visual Studio 8\ qui est le répertoire d'installation par défaut. Attention aux guillemets dans la commande, ils sont importants.

- Lancer la compilation. Pour cela taper la commande bjam --toolset=msvc-8.0 --build-type=complete stage

Les options passées à bjam sont les suivantes :

Option

Explication

--toolset=msvc-8.0

Cette option indique à bjam que le compilateur utilisé est le compilateur Microsoft (version 8.0). Les compilateurs supportés sont : Hewlett Packard (acc), Borland (borland), Comeau Computing (como), Metrowerks/FreeScale (cw), Digital Mars (dmc), Apple Computer (darwin), The Gnu Project (gcc), Hewlett Packard (hp_cxx), Intel (intel), Borland (kylix), Microsoft (msvc), QNX Software Systems (qcc), Sun (sun) et IBM (vacpp).

--build-type=complete

Cette option force bjam à construire toutes les variantes des bibliothèques.

stage

Cette option indique à bjam le nom du répertoire dans lequel placer les bibliothèques construites. Dans ce tutoriel, les bibliothèques sont placées dans le répertoire C:\Program Files\boost\boost_1_38_0\stage\lib\.

La compilation peut être longue, soyez donc patients... (2 heures de compilation sur mon PC)

2-4 - Configuration de Visual Studio 2005

La dernière étape consiste à configurer Visual Studio afin qu'il sache dans quels répertoires aller chercher les fichiers d'en-tête et les bibliothèques. Pour cela, il faut lancer Visual Studio 2005, aller dans le menu Outils/Options... et modifier les chemins de recherche comme spécifié sur les deux copies d'écran suivantes :

Ajout du chemin C:\Program Files\boost\boost_1_38_0\ de recherche des fichiers d'en-tête

Ajout du chemin C:\Program Files\boost\boost_1_38_0\stage\lib\ de recherche des bibliothèques

Il est important que les chemins de Boost soient mis en premier dans la liste des répertoires à rechercher.

3 - La classe CMyRegex

Ce paragraphe décrit la classe C++ CMyRegex. Il comprend la description des méthodes de la classe ainsi que le code de cette classe.

Rappel : La classe C++ CMyRegex présentée dans ce tutoriel est un exemple d'implémentation. Son but est de manipuler des expressions régulières tout en encapsulant un objet Boost.Regex. L'interface de cette classe peut ne pas être adaptée à votre besoin. Dans ce cas, il faudra probablement réécrire votre propre classe en reprenant les quelques concepts montrés ici.

3-1 - Fonctionnement général de la classe

Le fonctionnement général de la classe est le suivant :

- Chaque fonction retourne un booléen indiquant si la fonction s'est déroulée correctement ou non.

- Aucune exception n'est levée par la classe. Les exceptions levées par la bibliothèque Boost.Regex sont attrapées et traitées par les différentes fonctions.

- En cas d'erreur, il est possible d'appeler la fonction GetLastError() de la classe pour obtenir plus d'information sur l'erreur.

- Les différentes fonctions attendent des paramètres sous la forme d'une string UNICODE (std::wstring) ou bien d'une ANSI (std::string).

- La forme préférée et optimisée est std::wstring. Pour les string ANSI (std::string), elles sont transformées en interne par la classe en string UNICODE (std::wstring).

- Pour créer une instance de classe, le constructeur peut être appelé en fournissant comme paramètre une expression régulière valide.

- Il est toutefois possible de créer l'instance sans fournir d'expression régulière. Cette expression régulière devra alors être fournie ultérieurement par un appel à la fonction SetRegex().

- Une fois que l'expression régulière est assignée à l'instance de la classe, il est possible de tester des chaînes de caractères avec cette expression régulière. Pour ce faire, c'est la fonction Match() qui doit être appelée.

- La fonction Match() possède 2 formes surchargées. La première forme teste uniquement si la chaîne de caractères passée en paramètre correspond à l'expression régulière de l'instance de la classe.

- La seconde forme de la fonction Match() teste aussi si la chaîne de caractères passée en paramètre correspond à l'expression régulière de l'instance de la classe mais en plus, elle retourne les sous-chaînes extraites par les parenthèses de l'expression régulière.

- Si par exemple, une expression régulière contient 2 blocs de parenthèses, le tableau retourné contiendra alors 3 chaînes de caractères. La chaîne complète (indice 0 du tableau retourné), la chaîne de caractères correspondant au premier bloc de parenthèses (indice 1 du tableau retourné) et la chaîne de caractères correspondant au second bloc de parenthèses (indice 2 du tableau retourné).

- La syntaxe des expressions régulières utilisées est la syntaxe POSIX étendue.

3-2 - Les méthodes et membres de la classe

3-1-1 - Les fonctions de base de la classe

Nom

Visibilité

Rôle

CMyRegex(void)

publique

Le constructeur de la classe. Aucune expression régulière n'est assignée à l'instance de la classe.

explicit CMyRegex(const std::wstring & Regex)

publique

Le constructeur explicite de la classe. Le paramètre est l'expression régulière à utiliser, il s'agit d'une string UNICODE.

explicit CMyRegex(const std::string & Regex)

publique

Le constructeur explicite de la classe. Le paramètre est l'expression régulière à utiliser, il s'agit d'une string ANSI.

void Reset(void)

publique

Cette fonction permet de réinitialiser l'instance de la classe.

3-1-2 - Les fonctions de manipulation de l'expression régulière

Nom

Visibilité

Rôle

bool SetRegex(const std::wstring & Regex)

publique

Cette fonction permet de positionner une expression régulière. Le paramètre est l'expression régulière à utiliser, il s'agit d'une string UNICODE.

bool SetRegex(const std::string & Regex)

publique

Cette fonction permet de positionner une expression régulière. Le paramètre est l'expression régulière à utiliser, il s'agit d'une string ANSI.

const std::wstring & GetRegex(void) const

publique

Cette fonction permet de récupérer l'expression régulière de l'instance de la classe.

std::wstring m_regex_str

privée

Cette variable contient l'expression régulière de l'instance de la classe.

3-1-3 - Les fonctions de manipulation de la dernière erreur

Nom

Visibilité

Rôle

void set_last_error(const CMyRegexError_t Code, const std::string & MoreInfo = "")

privée

Cette fonction permet de positionner le code de la dernière erreur survenue dans l'instance de la classe ainsi qu'éventuellement un complément d'information sur l'erreur.

const std::wstring GetLastError(void) const

publique

Cette fonction permet de récupérer le texte de la dernière erreur survenue dans l'instance de la classe.

CMyRegexError_t m_last_error

privée

Cette variable contient le code de la dernière erreur survenue dans l'instance de la classe.

std::wstring m_last_error_moreinfo

privée

Cette variable contient un éventuel complément d'information sur l'erreur.

3-1-4 - Les fonctions de matching d'expression

Nom

Visibilité

Rôle

bool Match(const std::wstring & Expression)

publique

Cette fonction permet de vérifier si l'expression passée en paramètre sous la forme d'une string UNICODE correspond à l'expression régulière de l'instance de la classe.

bool Match(const std::string & Expression)

publique

Cette fonction permet de vérifier si l'expression passée en paramètre sous la forme d'une string ANSI correspond à l'expression régulière de l'instance de la classe.

bool Match(const std::wstring & Expression, std::vector<std::wstring> & ArrayValue)

publique

Cette fonction permet de vérifier si l'expression passée en paramètre sous la forme d'une string UNICODE correspond à l'expression régulière de l'instance de la classe. Si oui, les éventuelles sous-chaînes sont retournées dans le tableau passé en paramètre. Le tableau passé en paramètre est réinitialisé lors de l'appel à la fonction.

bool Match(const std::string & Expression, std::vector<std::wstring> & ArrayValue)

publique

Cette fonction permet de vérifier si l'expression passée en paramètre sous la forme d'une string ANSI correspond à l'expression régulière de l'instance de la classe. Si oui, les éventuelles sous chaînes sont retournées dans le tableau passé en paramètre. Le tableau passé en paramètre est réinitialisé lors de l'appel à la fonction.

3-1-5 - Les autres fonctions

Nom

Visibilité

Rôle

bool translate(const std::string & AString, std::wstring & WString)

privée

Cette fonction permet de transformer une string ANSI en string UNICODE.

3-3 - Le fichier header

Fichier d'en-tête MyRegex.h de la classe CMyRegex

1. #ifndef MyRegex_INCLUDED

2. #define MyRegex_INCLUDED

4. #include <string>

5. #include <vector>

6. #include <boost/regex.hpp>

8. class CMyRegex

9. {

10. public:

11. typedef enum

12. { CMyRegex_NoError,

13. CMyRegex_InvalidRegex,

14. CMyRegex_EmptyRegex,

15. CMyRegex_NoMatch,

16. CMyRegex_TranslateError,

17. CMyRegex_MemoryError,

18. CMyRegex_SettingError,

19. } CMyRegexError_t;

21. // The constructor with no string

22. inline CMyRegex(void) { }

24. // The explicit constructor with a ANSI string

25. inline explicit CMyRegex(const std::string & Regex) { SetRegex(Regex); }

27. // The explicit constructor with a UNICODE string

28. inline explicit CMyRegex(const std::wstring & Regex) { SetRegex(Regex); }

30. // Reset all parameter of the class

31. void Reset(void);

33. // The functions to manipulate the regular expression

34. bool SetRegex(const std::string & Regex);

35. bool SetRegex(const std::wstring & Regex);

36. inline const std::wstring & GetRegex(void) const { return m_regex_str; }

38. // The functions to check a string with the current regular expression

39. bool Match(const std::string & Expression);

40. bool Match(const std::wstring & Expression);

42. // The functions to check a string with the current regular expression.

43. // This set of function return the sub string

44. bool Match(const std::string & Expression, std::vector<std::wstring> & ArrayValue);

45. bool Match(const std::wstring & Expression, std::vector<std::wstring> & ArrayValue);

47. // The function to get the last error message

48. const std::wstring GetLastError(void) const;

50. private:

51. // The function to translate a ANSI string into a UNICODE string

52. bool translate(const std::string & AString, std::wstring & WString);

54. // The functions to set the last error message

55. void set_last_error(const CMyRegexError_t Code, const std::string & MoreInfo = "");

57. //

58. // The members

59. // -----------

61. // The current regular expression

62. std::wstring m_regex_str;

64. // The boost regex object used to match

65. boost::wregex m_regex_obj;

67. // The last error message

68. CMyRegexError_t m_last_error;

69. std::wstring m_last_error_moreinfo;

70. };

72. #endif // #ifndef MyRegex_INCLUDED

3-4 - Le fichier source

Fichier source MyRegex.cpp de la classe CMyRegex

1. #include "MyRegex.h"

2. #include <Windows.h>

4. // Reset all parameter of the class

5. void CMyRegex::Reset(void)

6. {

7. SetRegex(L"");

8. set_last_error(CMyRegex_NoError);

9. }

11. // Set the regular expression with a ANSI string

12. bool CMyRegex::SetRegex(const std::string & Regex)

13. {

14. // translate the ANSI string into a UNICODE string

15. std::wstring str;

16. if(translate(Regex, str) == false)

17. {

18. // An error while translating

19. return false;

20. }

22. // Set the regular expression (with a UNICODE string)

23. return SetRegex(str);

24. }

26. // Set the regular expression with a UNICODE string

27. bool CMyRegex::SetRegex(const std::wstring & Regex)

28. {

29. try

30. {

31. // Try to set the regular expression

32. m_regex_obj.assign(Regex, boost::regex_constants::extended);

34. // Save the regular expression

35. m_regex_str = Regex;

36. return true;

37. }

38. catch(const std::exception & e)

39. {

40. // An error while setting the regular expression

41. set_last_error(CMyRegex_InvalidRegex, e.what());

42. m_regex_str = L"";

43. return false;

44. }

45. }

47. // Match a ANSI string against the current regular expression

48. bool CMyRegex::Match(const std::string & Expression)

49. {

50. // translate the ANSI string into a UNICODE string

51. std::wstring str;

52. if(translate(Expression, str) == false)

53. {

54. // Translate error

55. return false;

56. }

58. // Match the string

59. return Match(str);

60. }

62. // Match a UNICODE string against the current regular expression

63. bool CMyRegex::Match(const std::wstring & Expression)

64. {

65. // Check if a regex is set

66. if(GetRegex().length() == 0)

67. {

68. // The regex is not set

69. set_last_error(CMyRegex_EmptyRegex);

70. return false;

71. }

73. try

74. {

75. // Match the string against the current regular expression

76. if(regex_match(Expression, m_regex_obj) == false)

77. {

78. // The expression does not match the regex

79. set_last_error(CMyRegex_NoMatch);

80. return false;

81. }

82. return true;

83. }

84. catch(const std::exception & e)

85. {

86. // An exception, it is probably an error

87. set_last_error(CMyRegex_NoMatch, e.what());

88. return false;

89. }

90. }

92. // Match a ANSI string against the current regular expression

93. bool CMyRegex::Match(const std::string & Expression, std::vector<std::wstring> & ArrayValue)

94. {

95. // translate the ANSI string into a UNICODE string

96. std::wstring str;

97. if(translate(Expression, str) == false)

98. {

99. // Translate error

100. return false;

101. }

103. // Match the string

104. return Match(str, ArrayValue);

105. }

107. // Match a UNICODE string against the current regular expression

108. bool CMyRegex::Match(const std::wstring & Expression, std::vector<std::wstring> & ArrayValue)

109. {

110. // Empty the array of values

111. ArrayValue.clear();

113. // Check if a regex is set

114. if(GetRegex().length() == 0)

115. {

116. // The regex is not set

117. set_last_error(CMyRegex_EmptyRegex);

118. return false;

119. }

121. try

122. {

123. // Match the string against the current regular expression

124. boost::wsmatch what;

125. if(regex_match(Expression, what, m_regex_obj) == false)

126. {

127. // The expression does not match the regex

128. set_last_error(CMyRegex_NoMatch);

129. return false;

130. }

132. // Read all values

133. for(unsigned int boucle = 0; boucle != what.size(); boucle++)

134. {

135. // Make a temporary string

136. std::wstring str;

137. str.assign(what[boucle].first, what[boucle].second);

139. // Add the temporary string into the array

140. ArrayValue.push_back(str);

141. }

142. return true;

143. }

144. catch(const std::exception & e)

145. {

146. // An exception, it is probably an error

147. set_last_error(CMyRegex_NoMatch, e.what());

148. return false;

149. }

150. }

152. // translate a ANSI string into a UNICODE string

153. bool CMyRegex::translate(const std::string & AString, std::wstring & WString)

154. {

155. // Try to translate the string (1st try)

156. wchar_t buffer[1024];

157. int ret = ::MultiByteToWideChar(CP_ACP, 0, AString.c_str(), -1, buffer, sizeof(buffer) / sizeof(buffer[0]));

158. if(ret != 0)

159. {

160. // The translate is correct

161. WString = buffer;

162. return true;

163. }

165. // Test the error to check if the buffer is big enougth

166. if(::GetLastError() != ERROR_INSUFFICIENT_BUFFER)

167. {

168. set_last_error(CMyRegex_TranslateError);

169. return false;

170. }

172. // Ask the needed size to translate the string

173. ret = ::MultiByteToWideChar(CP_ACP, 0, AString.c_str(), -1, NULL, 0);

174. if(ret == 0)

175. {

176. // An error while translating

177. set_last_error(CMyRegex_TranslateError);

178. return false;

179. }

181. // Allocate a buffer large enougth and store it in a boost::scoped_array object

182. boost::scoped_array<wchar_t> p_buf(new wchar_t [ret]);

183. if(p_buf.get() == NULL)

184. {

185. // A memory allocation error

186. set_last_error(CMyRegex_MemoryError);

187. return false;

188. }

190. // Perform the translate (2nd try)

191. ret = ::MultiByteToWideChar(CP_ACP, 0, AString.c_str(), -1, p_buf.get(), ret);

192. if(ret == 0)

193. {

194. // An error while translating

195. set_last_error(CMyRegex_TranslateError);

196. return false;

197. }

199. // The translate is correct

200. WString = p_buf.get();

201. return true;

202. }

204. // Set the last error

205. void CMyRegex::set_last_error(const CMyRegexError_t Code, const std::string & MoreInfo /*= ""*/)

206. {

207. // save the last error code

208. m_last_error = Code;

210. // Translate the information into a UNICODE string

211. translate(MoreInfo, m_last_error_moreinfo);

212. }

214. const std::wstring CMyRegex::GetLastError(void) const

215. {

216. std::wstring ret;

217. switch(m_last_error)

218. {

219. default : ret = L"Unknown error"; break;

220. case CMyRegex_NoError : ret = L"No error"; break;

221. case CMyRegex_InvalidRegex : ret = L"Invalid regular expression"; break;

222. case CMyRegex_EmptyRegex : ret = L"Regular expression is empty"; break;

223. case CMyRegex_NoMatch : ret = L"Expression does not match with the regular expression"; break;

224. case CMyRegex_TranslateError : ret = L"Error while translating an ANSI string to a UNICODE string"; break;

225. case CMyRegex_MemoryError : ret = L"Memory allocation error"; break;

226. case CMyRegex_SettingError : ret = L"Error while setting the error code"; break;

227. }

229. // add the more info string if not empty

230. if(m_last_error_moreinfo.length() != 0)

231. ret += L" (" + m_last_error_moreinfo + L")";

233. return ret;

234. }

4 - Le test de la classe CMyRegex

Le code présenté dans ce paragraphe permet de valider le fonctionnement de la classe CMyRegex. Ce code repose uniquement sur la fonction wmain() qui enchaine les différents tests et sur la fonction check() qui affiche les résultats de chacun de ces tests.

4-1 - Le fichier source de test de la classe CMyRegex

Fichier source Main.cpp de test de la classe CMyRegex

1. #include "MyRegex.h"

2. #include <iostream>

4. void check(const std::wstring & TestName, const CMyRegex & Regex, const std::vector<std::wstring> & Array, const bool ret)

5. {

6. // display the test name

7. std::wcout << TestName << L" : ";

8. if(ret == false)

9. {

10. // display the error string and leave the function

11. std::wcout << L"Echec - " << Regex.GetLastError() << std::endl;

12. return;

13. }

15. // display OK

16. std::wcout << L"OK" << std::endl;

18. // display the string returned in the array (if any)

19. for(size_t boucle = 0; boucle != Array.size(); boucle++)

20. {

21. std::wcout << L"\tArray[" << boucle << L"] = " << Array[boucle] << std::endl;

22. }

23. }

25. int wmain(void)

26. {

27. // the variables

28. std::vector<std::wstring> val;

29. CMyRegex regex;

31. // the tests

32. check(L"Regex ANSI invalide   ", regex, val, regex.SetRegex("(t(o).*"));

33. check(L"Regex UNICODE invalide", regex, val, regex.SetRegex(L"t)(o).*"));

34. check(L"Regex ANSI valide     ", regex, val, regex.SetRegex("(t)(o).*"));

35. check(L"Regex UNICODE valide  ", regex, val, regex.SetRegex(L"(t)(o).*"));

37. check(L"ANSI match pas        ", regex, val, regex.Match("otototototo"));

38. check(L"UNICODE match pas     ", regex, val, regex.Match(L"otototototo"));

40. check(L"ANSI match            ", regex, val, regex.Match("tototototo"));

41. check(L"UNICODE match         ", regex, val, regex.Match(L"tototototo"));

43. check(L"ANSI match pas        ", regex, val, regex.Match("otototototo", val));

44. check(L"UNICODE match pas     ", regex, val, regex.Match(L"otototototo", val));

46. check(L"ANSI match            ", regex, val, regex.Match("tototototo", val));

47. check(L"UNICODE match         ", regex, val, regex.Match(L"tototototo", val));

49. // test avec chaine vide

50. check(L"chaine ANSI vide      ", regex, val, regex.Match(L""));

51. check(L"chaine UNICODE vide   ", regex, val, regex.Match(""));

52. check(L"chaine ANSI vide      ", regex, val, regex.Match(L"", val));

53. check(L"chaine UNICODE vide   ", regex, val, regex.Match("", val));

55. // expression régulière vide

56. regex.Reset();

57. check(L"chaine ANSI vide      ", regex, val, regex.Match(L""));

58. check(L"chaine UNICODE vide   ", regex, val, regex.Match(""));

59. check(L"chaine ANSI vide      ", regex, val, regex.Match(L"", val));

60. check(L"chaine UNICODE vide   ", regex, val, regex.Match("", val));

62. // Construction par copie

63. regex.SetRegex(L"(t)(o).*");

64. {

65. CMyRegex regex2(regex);

66. check(L"ANSI match            ", regex2, val, regex2.Match("tototototo"));

67. check(L"UNICODE match         ", regex2, val, regex2.Match(L"tototototo"));

69. check(L"ANSI match pas        ", regex2, val, regex2.Match("otototototo"));

70. check(L"UNICODE match pas     ", regex2, val, regex2.Match(L"otototototo"));

71. }

73. // Construction par affectation

74. {

75. CMyRegex regex2;

76. regex2 = regex;

77. check(L"ANSI match            ", regex2, val, regex2.Match("tototototo"));

78. check(L"UNICODE match         ", regex2, val, regex2.Match(L"tototototo"));

80. check(L"ANSI match pas        ", regex2, val, regex2.Match("otototototo"));

81. check(L"UNICODE match pas     ", regex2, val, regex2.Match(L"otototototo"));

82. }

84. // utilisation du constructeur explicit ANSI

85. {

86. CMyRegex regex2("(t)(o).*");

87. check(L"ANSI match            ", regex2, val, regex2.Match("tototototo"));

88. check(L"UNICODE match         ", regex2, val, regex2.Match(L"tototototo"));

90. check(L"ANSI match pas        ", regex2, val, regex2.Match("otototototo"));

91. check(L"UNICODE match pas     ", regex2, val, regex2.Match(L"otototototo"));

92. }

94. // utilisation du constructeur explicit UNICODE

95. {

96. CMyRegex regex2(L"(t)(o).*");

97. check(L"ANSI match            ", regex2, val, regex2.Match("tototototo"));

98. check(L"UNICODE match         ", regex2, val, regex2.Match(L"tototototo"));

100. check(L"ANSI match pas        ", regex2, val, regex2.Match("otototototo"));

101. check(L"UNICODE match pas     ", regex2, val, regex2.Match(L"otototototo"));

102. }

104. return 0;

105. }

4-2 - Résultat attendu des tests de la classe CMyRegex

Le résultat attendu dans la console est le suivant :

Résultat attendu

1. Regex ANSI invalide    : Echec - Invalid regular expression (Unmatched ( or \()

2. Regex UNICODE invalide : Echec - Invalid regular expression (Unmatched ( or \()

3. Regex ANSI valide      : OK

4. Regex UNICODE valide   : OK

5. ANSI match pas         : Echec - Expression does not match with the regular expression

6. UNICODE match pas      : Echec - Expression does not match with the regular expression

7. ANSI match             : OK

8. UNICODE match          : OK

9. ANSI match pas         : Echec - Expression does not match with the regular expression

10. UNICODE match pas      : Echec - Expression does not match with the regular expression

11. ANSI match             : OK

12. Array[0] = tototototo

13. Array[1] = t

14. Array[2] = o

15. UNICODE match          : OK

16. Array[0] = tototototo

17. Array[1] = t

18. Array[2] = o

19. chaine ANSI vide       : Echec - Expression does not match with the regular expression

20. chaine UNICODE vide    : Echec - Expression does not match with the regular expression

21. chaine ANSI vide       : Echec - Expression does not match with the regular expression

22. chaine UNICODE vide    : Echec - Expression does not match with the regular expression

23. chaine ANSI vide       : Echec - Regular expression is empty

24. chaine UNICODE vide    : Echec - Regular expression is empty

25. chaine ANSI vide       : Echec - Regular expression is empty

26. chaine UNICODE vide    : Echec - Regular expression is empty

27. ANSI match             : OK

28. UNICODE match          : OK

29. ANSI match pas         : Echec - Expression does not match with the regular expression

30. UNICODE match pas      : Echec - Expression does not match with the regular expression

31. ANSI match             : OK

32. UNICODE match          : OK

33. ANSI match pas         : Echec - Expression does not match with the regular expression

34. UNICODE match pas      : Echec - Expression does not match with the regular expression

35. ANSI match             : OK

36. UNICODE match          : OK

37. ANSI match pas         : Echec - Expression does not match with the regular expression

38. UNICODE match pas      : Echec - Expression does not match with the regular expression

39. ANSI match             : OK

40. UNICODE match          : OK

41. ANSI match pas         : Echec - Expression does not match with the regular expression

42. UNICODE match pas      : Echec - Expression does not match with the regular expression

5 - Conclusions

Les expressions régulières permettent de régler les problèmes d'analyse de texte et d'extraction des champs d'une manière simple et élégante. Qui n'a jamais écrit un analyseur pour extraire toutes les parties du nom complet d'un fichier (lecteur, chemin, nom du fichier, extension du fichier) ?

Par contre, il est vrai que la syntaxe d'une expression régulière n'est pas évidente au premier abord. Cela demande un effort d'apprentissage mais cet effort est très vite compensé par la souplesse et la puissance apportées.

Ce tutoriel ne fait qu'effleurer l'utilisation de la bibliothèque Boost.Regex et ne donne qu'un petit aperçu de sa puissance. Pour plus d'informations, il ne faut pas hésiter à se plonger dans la documentation officielle.

Dans le cadre professionnel, j'utilise les expressions régulières dans un programme permettant de recevoir en temps réel les messages de log d'un firewall (1000 par seconde en moyenne) et pour extraire les informations pertinentes (adresses IP source et destination, protocole, ports source et destination, message) afin de les traiter.

J'espère que ce tutoriel vous aura aussi donné l'envie d'utiliser les expressions régulières ainsi que la bibliothèque Boost.Regex.