phpInfo.netLes ArchivesLes éléPHPants

  
  Accueil
  Trucs & Astuces
  Scripts
  Regex
  Annuaire
  Articles

.
      
 Articles   Apprendre à utiliser les Expressions Régulières par l'exemple  Par J-Pierre DEZELUS   Juin 2000  

 »  Avant-propos
 »  Introduction
 »  Règles de base des Expressions Régulières
 »  Valider des valeurs monétaires
 »  Valider les adresses E-mail
 »  Autres utilisations
 »  Quelques exercices
 »  Liens


Avant-propos

Cet article a paru dans les Colums de PHPBuilder en juin 1999 sous le titre Learning to Use Regular Expressions by Example. Il a été écrit par Dario F. Gomes. Je me suis simplement contenté d'en faire la traduction en français.

Je remercie l'auteur ainsi que Tim Perdue, le webmaster de PHPBuilder, de m'avoir autorisé à traduire cet article pour phpInfo.net.

Introduction

Le site sur lequel je travaille dépend fortement de données saisies par les utilisateurs au travers des formulaires. Toutes ces données doivent bien sûr être contrôlées avant d'être stockées dans une base. Je savais que les expressions régulières et PHP pouvaient répondre à ce problème, mais je ne savais pas du tout comment les utiliser.

J'avais donc besoin de quelques exemples - bien sûr, la première chose que j'ai faite a été de consulter le manuel de PHP et la spécification POSIX 1002.3, mais cela n'a pas répondu à toutes mes questions.

J'ai aussi passé beaucoup de temps à chercher sur le Web de la documentation sur le sujet. J'ai finalement pû trouver comment procéder, souvent de façon très empirique. Comme cela ne me paraissait pas représenter un travail énorme, j'ai décidé d'écrire cet article d'initiation aux expressions régulières, dans lequel nous allons construire pas à pas des expressions pour valider des adresses E-mail et des valeurs monétaires.

Règles de base des Expressions Régulières

En premier lieu, nous allons étudier les 2 caractères spéciaux que sont ^ et $. Le premier sert à définir le début d'une chaîne, le second la fin :

  • "^Le": pour définir les chaînes commençant par "Le";
  • "après lui$": pour définir les chaînes se terminant par "après lui";
  • "^abc$": une chaîne qui commence et se termine par "abc" -- c'est "abc" elle-même !
  • "notice": toute chaîne contenant "notice".

Nous voyons dans ce dernier exemple que si l'on n'utilise pas les caractères ^ et $, cela indique que le motif (pattern) peut se trouver n'importe où dans la chaîne.

Nous disposons aussi des symboles *, +, et ? pour exprimer le nombre de fois où un caractère ou une séquence de caractères peut apparaître dans la chaîne. Voici leur signification : * => "zero occurrence ou plus", + => "une occurrence ou plus", et ? => "zéro ou une occurrence.". Quelques exemples :

  • "ab*": les chaînes contenant un a suivi de zéro, un, ou plusieurs b ("a", "ab", "abbb", etc.);
  • "ab+": idem, mais avec au moins 1 b derrière le a ("ab", "abbb", etc.);
  • "ab?": il doit y avoir un b ou non après le a;
  • "a?b+$": éventuellement un a (et un seul) suivi d'un ou plusieurs b, le tout en fin de chaîne.

Les accolades { } vont nous permettre d'indiquer plus précisement le nombre d'occurrences possibles :

  • "ab{2}": les chaînes contenant un a suivi d'exactement 2 b ("abb");
  • "ab{2,}": les chaînes contenant au moins 2 b consécutifs ("abb", "abbbb", etc.);
  • "ab{3,5}": les chaînes pouvant contenir de 3 à 5 b consécutifs ("abbb", "abbbb", or "abbbbb").

Notez que vous devez obligatoirement spécifier la première valeur de l'intervalle (i.e, la syntaxe "{0,2}" est correcte, mais pas "{,2}"). Vous aurez remarqué aussi que les symboles *, +, et ? ont la même signification que les intervalles "{0,}", "{1,}", et "{0,1}".

Pour définir une séquence de caractères, il suffit d'utiliser les parenthèses ( ) :

  • "a(bc)*": les chaînes ayant un a suivi de zéro, une ou plusieurs fois de la chaîne "bc";
  • "a(bc){1,5}": a suivi de 1 à 5 fois de "bc".

Nous disposons aussi du symbole | comme opérateur booléen OU :

  • "hi|hello": les chaines contenant "hi" ou "hello";
  • "(b|cd)ef": les chaines contenant "bef" ou "cdef";
  • "(a|b)*c": les chaînes contenant une série de a et de b puis un c;

Le point . représente un caractère unique :

  • "a.[0-9]": pour un a suivi d'un caractère puis d'un chiffre;
  • "^.{3}$": représente les chaînes d'exactement 3 caractères.

Les crochets [ ] permettent eux de spécifier quels sont les caractères autorisés à un endroit précis de la chaîne :

  • "[ab]": les chaînes contenant un a ou un b (équivalent à "a|b");
  • "[a-d]": les chaînes contenant les caractères minuscules a à d (équivalent à "a|b|c|d" ou "[abcd]");
  • "^[a-zA-Z]": les chaînes commençant par une lettre;
  • "[0-9]%": pour un chiffre suivi du symbole % (pourcentage);
  • ",[a-zA-Z0-9]$": les chaînes se terminant par une virgule suivie d'un caractère alphanumérique.

Vous pouvez aussi indiquer les séquences que vous voulez exclure en utilisant le symbole ^ en premier caractère d'une expression entre crochets [ ] (i.e., "%[^a-zA-Z]%" pour les chaînes contenant un caractère qui n'est pas une lettre entre 2 symboles %).

Pour pouvoir utiliser dans une expression régulière les caractères spéciaux "^.[$()|*+?{\" en tant que caractères 'normaux', il faut les faire précéder d'un antislash \. Comme l'antislash est lui-même un caractère spécial de PHP, il faut aussi le faire précéder d'un antislash ! Ce qui nous donne par exemple pour l'expression régulière suivante "(\$|£)[0-9]+" : ereg("(\\$|£)[0-9]+",$chaine) (d'ailleurs, quel genre de chaînes permet-elle de valider ?).

Toutefois, sachez que les expressions entre crochets ne répondent pas à cette règle. Entre crochets, tous les caractères spéciaux, y compris l'antislash, perdent leur 'pouvoir spécial' (i.e., "[*\+?{}.]" correspond aux caractères *, \, +, etc.). Et comme nous l'indiquent les pages du manuel : "pour inclure le caractère ] dans la liste, placez le en premier (après éventuellement un ^). Pour include le caractère -, placez en première ou dernière position."

Valider des valeurs monétaires

Nous allons maintenant mettre en application ce que nous venons d'apprendre sur un cas pratique : une expression régulière qui permet de vérifier que l'utilisateur saisi un montant. Un montant (au format US) peut s'écrire de 4 manières acceptables : "10000.00", et "10,000.00", et sans les cents, "10000", et "10,000". Nous pouvons commencer par :

^[1-9][0-9]*$

Ce qui nous permet de valider un nombre qui ne commence pas par 0. La chaîne "0" ne passe donc pas le test. Voici la solution :

^(0|[1-9][0-9]*)$

"Seulement un 0, OU tout nombre qui ne commence pas par un 0". Nous pouvons aussi autoriser un signe moins devant le nombre :

^(0|-?[1-9][0-9]*)$

Ce qui signifie : "Seulement un 0, OU un signe moins optionnel puis tout nombre qui ne commence pas par un 0". Bon, soyons moins strict, et laissons l'utilisateur commencer son nombre par un zéro s'il le souhaite. Supprimons aussi le signe moins, puisqu'il s'agit de valider une chaîne représentant une quantité d'argent. Ce que nous pouvons faire par contre, c'est dire qu'il peut y avoir une partie décimale (optionnelle) :

^[0-9]+(\.[0-9]+)?$

Ce qui signifie qu'un point doit toujours être suivi d'au moins un chiffre. Donc, "10." ne sera pas validé, alors que "10" and "10.2" le seront.

^[0-9]+(\.[0-9]{2})?$

Nous demandons maintenant qu'il y ait exactement 2 décimales après le point. Si vous pensez que c'est trop restrictif, utilisez plutôt :

^[0-9]+(\.[0-9]{1,2})?$

Cela permet à l'utilisateur de ne taper qu'une décimale s'il le souhaite. Pour les virgules séparant les milliers, nous pouvons écrire :

^[0-9]{1,3}(,[0-9]{3})*(\.[0-9]{1,2})?$

"Une séquence de 1 à 3 chiffres suivi par zéro, une ou plusieurs séquences de 1 virgule et 3 chiffres". Facile non ?! Rendons les virgules optionnelles :

^([0-9]+|[0-9]{1,3}(,[0-9]{3})*)(\.[0-9]{1,2})?$

Voilà. N'oubliez pas que le + doit être remplacé par une * si vous voulez autoriser la saisie de chaînes vides. Et n'oubliez pas non plus de doubler l'antislash au moment d'appeler une fonction PHP (c'est une erreur courante). Maintenant que la chaîne est validée, nous pouvons supprimer les virgules avec str_replace(",", "", $montant) et convertir (par un cast) la valeur en double afin d'entreprendre les calculs.

Valider les adresses E-mail

Etudions maintenant le cas des adresses e-mail. Une adresse e-mail se décompose en 3 parties : le nom de l'utilisateur POP3 (tout ce qui se trouve à gauche du caractère @), le caractère @, et le nom du serveur (le reste). Le nom de l'utilisateur peut contenir des lettres minuscules ou majuscules, des chiffres, des points (.), des signes moins (-), et des underscores (_). Idem pour le nom du serveur, sauf qu'il ne doit pas contenir de caractères _ (underscore).

D'autre part, le nom de l'utilisateur ne peut pas commencer ni finir par un point. Il en va de même pour le serveur. Il ne peut pas y avoir non plus plusieurs points consécutifs. On doit pouvoir trouver au moins un caractère entre 2 points. Voyons maintenant comment écrire cette expression régulière :

^[_a-zA-Z0-9-]+$

Cela ne permet pas encore d'autoriser le point. Essayons ce qui suit :

^[_a-zA-Z0-9-]+(\.[_a-zA-Z0-9-]+)*$

Cela signifie : "au moins un caractère valide suivi par zéro, une ou plusieurs séquences constituées d'un point suivi de un ou plusieurs caractères valides".

Pour simplifier un peu les choses, nous pouvons utiliser l'expression ci-dessous avec la fonction eregi(), qui n'est pas sensible à la casse (a=A, b=B, ...), contrairement à la fonction ereg(). C'est pourquoi nous n'avons plus besoin de précisier l'intervalle "A-Z" puisque "a-z" lui est équivalent :

^[_a-z0-9-]+(\.[_a-z0-9-]+)*$

Pour le nom du serveur, nous faisons la même chose, sans les underscores :

^[a-z0-9-]+(\.[a-z0-9-]+)*$

Pour terminer, nous pouvons joindre les 2 expressions en les séparant par le caractère @. Par contre dans le deuxième membre nous exigeons la présence d'au moins 1 point suivi de plusieurs caractères, en remplaçant * par + (juste avant le $) :

^[_a-z0-9-]+(\.[_a-z0-9-]+)*@[a-z0-9-]+(\.[a-z0-9-]+)+$

Autres utilisations

Extraire des parties d'une chaîne

ereg() and eregi() possèdent des fonctionnalités particulières permettant d'extraire certains éléments d'une chaîne . Par exemple, pour obtenir le nom de fichier d'un chemin ou d'une URL, nous pouvons écrire :

ereg("([^\\/]*)$", $pathOrUrl, $regs);
echo $regs[1];

Remplacement avancé

ereg_replace() and eregi_replace() sont aussi très utiles : supposons que vous vouliez séparer tous les mots d'une chaîne par une virgule :

ereg_replace("[ \n\r\t]+", ",", trim($str));

Quelques exercices

Voilà maintenant de quoi vous occuper quelques temps :

  • Modifiez l'expression régulière qui nous a permis de valider une adresse e-mail de manière à s'assurer que le nom du serveur est composé d'au moins 2 parties (aide : il n'y a qu'un carctère à changer);
  • Construire une fonction à l'aide de ereg_replace() qui émule la fonction trim();
  • Contruire une autre fonction basée sur ereg_replace() qui place un ~ devant les caractères #, @, &, et %.


Liens

 » Exemples d'expressions régulières
 » RegExplorer et Visual RegExp 2 outils pour tester vos expressions régulières.
 » L'article original (Learning to Use Regular Expressions by Example) par Dario F. Gomes [Juin 1999]
 » Les Colums de PHPBuilder
 » La norme POSIX 1002.3
 » Article sur phpWizard
 » Manuel PHP / Les Expressions Régulières
Synseo