Accueil ¦ Espace perso ¦ Espace pro ¦ Préférences ¦ Blog ¦ Webmail ¦ À propos

Les expressions rationnelles, (bb)|[^b]{2} ?

Généralités.

Définition.

Une expression régulière (ou normale) est une chaîne de caractères décrivant un motif.

Usage.

On les utilise pour vérifier des formatages complèxes de chaînes de caractères, et en extraire des chaînes permettant de décrire la forme d'une chaîne.

Pour la suite, je parlerais regex une expression régulière.

Avantages et inconvénients.

  1. Bien !
    • Concision : pour s'en convaincre, il suffit de comparer la transcription en français de l'exemple... et d'imaginer le code nécessaire pour faire le travail équivalent de la regex.
    • Rapidité et fiabilité : il est plus rapide et plus fiable d'écrire une regex plutôt qu'un algorithme dédié.
  2. Pas bien !
    • Complexité : que celui qui a compris du premier coup la blague en entré me jète la première pierre ! (euh... cher ami lecteur, à quoi va donc te servir cette brouette de galets ?)
    • Sensible : c'est à configurer aux petits oignons ! Une erreur, et on perd rapidement l'intérêt de l'outil.

Disponibilité.

La plupart des langages de programmation supportent les regex. L'API POSIX du langage C les propose de manière standard depuis longtemps. Les outils unix awk et grep les utilisent intensément pour faciliter le scripting. Cependant, l'implémentation la plus appréciée est celle de Perl, car performante et très intégrée au langage. Elle est plus connue sous le nom de PCRE.

J'utilise les regex en PHP et Javascript, C++ et PHP. Et surtout dans Vim !

Lors de l'écriture de scripts PHP, elles sont très appréciables pour contôler les champs saisis par l'internaute.

Dans les Javascript, elles permettent d'avertir l'utilisateur qu'il a fait une mauvaise saisie, et ainsi éviter de charger inutilement le serveur... et même éviter à l'utilisateur l'attente de la réponse du serveur !

Règles d'écritures.

Le principe de construction est d'une simplicité enfantine, mais c'est particulièrement illisible lorsqu'on a besoin d'un motif précis. Aussi, suivre je suis le précepte suivant :

Je découpe en sections logiques mes motif ! En plus ça permet de réutiliser plus facilement les morceaux.

Une regex est constituée de branches, séparées par des tuyaux « | ». Ce caractère peut être lu comm un ou logique entre deux chaînes. Une chaîne correspond à une regex si au moins un motif de la regex décrit une portion de la chaîne à contrôler.

On utilise les caractères présentés dans le tableau suivant pour signifier la qualité et la quantité des caractères. Lorsqu'un caractère à une signification particulière, et qu'on veut lui faire perdre son comportement magique, il faut l'échaper à l'aide du caractère « \ »).

Caractères magiques (POSIX).
Caractère Signification
Nature . Représente n'importe quel caractère.
a|b Occurrence de l'une (« a ») ou l'autre (« b ») branche.
[a-c] Interval désignant n'importe quel caractère compris de « a » à « c ».
[abC] Interval désignant n'importe quel caractère étant « a », « c » ou « C ».
[^a-c] Interval désignant tout les caractère en dehors de « a » à « c ».
Encadrement ^ Indique le début de la chaîne. Valable dans le contexte global.
$ Indique la fin de la chaîne.
( ) Les accolades permettent d'encadrer une séquence de caractères.
Périodicité
? Occurrence unique ou nulle de la séquence précédant le caractère
+ Occurence non-nulle.
{n} La séquence précédent l'accolade ouvrante apparaît « n » fois.
{,n} La séquence précédent l'accolade ouvrante apparaît au maximum « n » fois.
{n,} La séquence précédent l'accolade ouvrante apparaît au minimum « n » fois.
{n1,n2} La séquence précédent l'accolade ouvrante apparaît au minimum « n1 » fois, et au maximum « n2 » fois.
Caractères spéciaux.
Caractère Signification
Contrôle \a Cloche système (alarm).
\b Espace en arrière (backspace).
\f Saut de page (form feed).
\n Fin de ligne (line feed).
\r Retour charriot (carriage return).
\t Tabulation horizontale (tabulate).
\v Tabulation verticale (vertical tabulate).
Alias pour les expressions compatibles Perl.
Caractère Équivalent POSIX Signification
Contrôle \d [0-9] Un nombre
\w [a-zA-Z] Caractères alphabétiques.
\s [ ] Espace.
\D [^0-9] Tout ce qui n'est pas un chiffre.
\W [^a-zA-Z] Tout ce qui n'est pas alphabétique.
\S [^ ] Tous les caractères qui ne sont des espaces.

Utilisation.

Exemple de contrôle de saisie d'un courriel.

Il y a normalement trois phase à l'utilisation d'une expression régulière :

  1. Déclarations de la regex.
  2. Compilation de la regex (automatique dans les langages de script).
  3. Exécution (ou utilisation) de la regex.

Dans les exemples qui suivront, on vérifi qu'une chaîne correspond à une adresse de courrier électronique. Le modèle choisi retenu pour l'exemple est : ^[\d\w]+([._-][\d\w]|[\d\w])*@{1}[\d\w]+([._-][\d\w]|[\d\w)*\.[\w\d]+$ Cette expression n'est pas complète, il manque beaucoup de possibilités.

Explications : une adresse de courrier électronique est une chaine formée par deux segments séparés par un arobase.

  1. [\d\w]+ ( [._-][\d\w] | [\d\w] )*
    La première partie désigne un utilisateur de boîte de courrier local au serveur de messagerie.
    Il commence par au moins une lettre ou un chiffre. Ensuite, il peut se répeter une séquence composée de caractère alphanumériques, ou d'un unique caractère point, souligné ou tiret, suivi immédiatement par un caractère alphanumérique.
  2. [\d\w]+ ( [._-]?[\d\w] )* \.[\w\d]+
    La seconde partie est très similaire à la première, sauf qu'on oblige le dommaine à s'achever avec un point suivi de caractères alphanumériques.

On lie et encadre ces deux branches ainsi :
^ [\d\w]+([._-][\d\w]|[\d\w])* @{1} [\d\w]+([._-][\d\w]|[\d\w)*\.[\w\d]+ $

Écrire la regex.

La phase la plus critique est bien celle d'écriture de la regex. En effet, si on l'écrit de manière arbitraire, on risque de passer à côté de chaînes de caractères valides ! Ou pire, accepter des chaînes invalides.

Afin d'avoir l'expression la plus juste possible, il faudra se référer au document spécifiant le format à contrôler. En aucun cas on écrira une regex de manière arbitraire ! Dans notre cas, je me suis référé à la RFC définissant les adresses des courriers électronique. Cette spécification est disponible en français : RFC 2822. La chaîne proposée ici n'est qu'un premier jet. Il faudrait la compléter pour qu'elle accepte toutes les adresses valables. C'est dans ma TODO list ;)

À la source : langage C.

De par son utilité et sa puissance, les regex connaissent un grand succès, et sont le sujets de nombreuses bibliothèques, de la bibliothèque standard à Apache.

Les fonctions POSIX.

L'exemple suivant est accessible ici. Pour compiler l'exemple : cc -o chkemail regex_ex_c_posix.c -Wall

La page décrivant le fonctionnement des regex : man 3 regex

#include <stdio.h>
#include <regex.h>

#define RE_MAIL \
        "^[0-9a-z]+([._-][0-9a-z]|[0-9a-z])*@{1}[0-9a-z]+([._-][0-9a-z]|[0-9a-z])*\\.[0-9a-z]+$"

int main(int argc, const char ** argv)
{
		int c ;
		int regerr ;
		regex_t regbuffer ;

		regerr = regcomp(&regbuffer, RE_MAIL, REG_EXTENDED | REG_ICASE ) ;
		if(regerr)
		{
				puts("Regex fausse : <" RE_MAIL ">") ;
				return !0 ;
		}

		for(c = 1 ; c < argc ; c++) 
		{
				int nomatch ;
				regmatch_t array[1] ;

				nomatch = regexec(&regbuffer, *(argv + c), 1, array, 0) ;
				if(nomatch)
				{
						printf("ko <%s>\n", *(argv + c)) ;
				}
				else
				{
						printf("ok <%s>\n", *(argv + c)) ;
				}
				fflush(NULL) ;
		}

		regfree(&regbuffer) ;

		return 0 ;
}

Les PCRE.

L'exemple suivant est accessible ici.

cc -o chkemail regex_ex_c_pcre.c -Wall -lpcre

Pour consulter la documentation et compiler l'exemple, il faut installer la bibliothèque PCRE. Sous Debian, il suffit d'exécuter la commande suivante : apt-get install libpcre3-dev. Et pour consulter la documentation : man 3 pregapi.

#include <stdio.h>
#include <string.h>
#include <pcre.h>

#define PCRE_MAIL \
		"^[\\d\\w]+([._-][\\d\\w]|[\\d\\w])*@{1}[\\d\\w]+([._-][\\d\\w]|[\\d\\w)])*\\.[\\w\\d]+$"

int main(int argc, const char ** argv)
{
		int c = 0 ;
		int error = 0 ;
		const char * strerror = NULL ;
		pcre * pcre = NULL ;

		pcre = pcre_compile(PCRE_MAIL, 0, &strerror, &error, NULL) ;
		if(!pcre || error)
		{
				puts(PCRE_MAIL) ;
				puts(strerror) ;
				return !0 ;
		}

		for(c = 1 ; c < argc ; c++) 
		{
				int nomatch ;
				int results[1] ;

				nomatch = pcre_exec(pcre, NULL, *(argv + c), strlen(*(argv + c)), 0, 0, results, 1) ;
				if(!nomatch)
				{
						printf("ok <%s>\n", *(argv + c)) ;
				}
				else if(nomatch == PCRE_ERROR_NOMATCH)
				{
						printf("ko <%s>\n", *(argv + c)) ;
				}
				else
				{
						printf("?%d <%s>\n", nomatch, *(argv + c)) ;
				}
				fflush(NULL) ;
		}

		pcre_free(pcre) ;

		return 0 ;
}

Avec Apache

Ce paragraphe sera complété lorsque l'article sur la création de modules Apache sera achevée.

Pour améliorer l'interactivité sur le Web.

Dans un formulaire, pour éviter de faire perdre du temps à l'utilisateur, on peut contrôler les zones de saisie lors de sa validation. En attendant les formulaires Web (XForms), on peut le gérer à l'aide du javascript.

Le code HTML qui va bien :

<form action="regex_exemple.php"
      onsubmit="javascript:return check(this)"
      id="inscription" >
<label>Courriel :</label>
<input name="courriel" />
<button type="submit" >Inscription</button>
</form>
     

Le code Javascript qui va bien :

const RE_MAIL = /^[\d\w]+([._-][\d\w]|[\d\w])*@{1}[\d\w]+([._-][\d\w]|[\d\w)*\.[\w\d]+$/

const MSG_ERR_MAIL = "Vous devez entrer une adresse valide"

private check = function (form) {
  if(!form) {
      return false 
  }

  private courriel = form.elements['courriel']
  private match = courriel.value.match(RE_MAIL)
  
  if(!match) {
    alert(MSG_ERR_MAIL + '.') ;

    private label = document.createElement('label')
    label.setAttribute('class', 'danger') ;

    private text = document.createTextNode( MSG_ERR_MAIL + ' :') 
    label.appendChild(text)
    form.replaceChild(label, form.getElementsByTagName('label')[0]) ;

    return false
  }

  return true
}
     

Pour sécuriser un script PHP.

Contrôler les données dans le navigateur c'est bien, mais ça n'assure pas leur validité. Un client ne suportant pas le Javascvript, ou un petit malin pourraient soumettre des données indésirées.

Il faut donc contrôler la validité des informations transmises. Bien sûr, il est inutile d'utiliser les regex pour vérifier qu'une chaîne contient, par exemple, un nombre entier.

Il existe plusieurs façons de manipuler les chaines à l'aide d'expressions régulière. En utilisant les fonctions...

Je ne peux que conseiller d'utiliser la seconde solution. En effet, les fonctions sont plus performantes, et les extensions d'écriture permettent de simplifier grandement les regex.

define(RE_MAIL, '^\w+[\d\w._-]*@{1}[\d\w]+[\w\d._-]*\.[\w\d]+$') ;
define(MSG_ERR_MAIL, "Vous devez entrer une adresse valide") ;
$courriel = '' ;
$error = '' ;
if(array_key_exists('courriel', $_POST))
{
    $courriel = $_POST['courriel'] ;
    if(!preg_match('/'.RE_MAIL.'/', $courriel))
    {
        $error = MSG_ERR_MAIL ;
    }
}
     

On peut aussi remplacer des motifs à l'aide de ces fonctions. Par exemple, pour afficher les exemples en C, j'ai utilisé les instructions suivantes :

$file = file_get_contents('regex_ex_c_posix.c') ;
if(get_magic_quotes_gpc())
    $file = stripslashes($file) ;
          
echo preg_replace( array('/&/', '/</', '/>/')
                  , array('&amp;', '&lt;', '&gt;')
                  , $file ) ;

À venir...

Boost Regex, MySQL, etc.

Ressources.

Parce que je n'ai rien inventé, voici mes sources :
Haut de la page