Articles Les expressions régulières PERL Par Théo aka Thierry ANDRE
Novembre 2001
» Préambule » 1. Ce que sont les expressions régulières » 2. Définition d'une expression régulière PCRE » 3. Options des expressions régulières PCRE » 4. Ce a quoi ressemble une expression régulière » 5. Les Méta-caractères » 6. Les classes de caractères » 7. Les sous-masques » 8. Construction d'une expression régulière » Remerciements
Ceci se veut un condensé non exhaustif des principales options disponibles pour créer des expressions régulières (ou rationnelles) compatibles PERL (librairie PCRE, Perl Compatible Regular Expression) en PHP. Ce petit tutorial a été conçu à l'origine comme une courte notice à usage privé, avant de se voir transformer en FAQ, puis en tutoriel, ce qui explique sa forme peut être parfois plus synthétique qu'explicative.
Néanmoins je pense qu'il peut constituer une bonne introduction et une bonne référence de base pour tout ceux qui s'y intéressent.
Les expressions régulières sont des expressions logiques qui permettent de vérifier qu'une chaîne correspond à un format particulier, défini par l'expression. Elles peuvent permettre également, et par voie de conséquence, d'isoler des motifs particuliers au sein d'une chaîne de caractères. Elles existent sous plusieurs formes, et ont commencé à être développées et utilisées sur des systèmes UNIX avec des logiciels comme grep. Très vite PERL se les est appropriées et en a élargi les capacités. Elles ont été par la suite implémentées en C (libPCRE), puis intégrées dans PHP, apportant un outil très performant pour l'analyse et la transformation syntaxique des chaînes.
Les expressions régulières peuvent s'utiliser dans différentes fonctions PHP : ereg_*, split, etc. En ce qui concerne les expressions régulières PERL, elles s'utilisent exclusivement dans les fonctions de la librairie PCRE, qui commencent toutes par preg_* comme Perl Regular Expression.
Notez également que les expressions régulières PERL sont non seulement plus riches et plus performantes que les expressions de la norme POSIX 1003.2, mais sont également plus rapides.
Une expression régulière PERL est composée de deux parties, l'expression en elle même et les options.
L'expression est une chaîne de caractères délimitée au début et à la fin par un même séparateur. Ce délimiteur ne doit pas être alphanumérique, et ne peut être un antislash.
Si ce caractère délimiteur est amené à être utilisé dans la chaîne de l'expression régulière, il doit être échappé avec un antislash devant.
Exemple correct : /une expression avec des \/ et autre chose/
Exemple incorrect : /une expression avec des / et autre chose/
Une expression régulière peut se terminer par des options qui vont influer sur la manière de l'interpréter. Ces options se situent à la fin de l'expression régulière, après le symbole de fermeture, et sont cumulables.
Notez qu'une expression régulière s'applique par défaut à une ligne, c'est à dire à une chaîne limitée par \n.
Voici une liste non exhaustive des plus couramment employées :
- i - recherche insensible à la casse.
- m - recherche muti-lignes si l'expression est amenée à travailler sur des chaînes comportant des \n.
- F - dans preg_replace permet d'appeler une fonction PHP pour fournir le caractère de remplacement (PHP 4.0.4).
- A - "ancre" le masque, le motif recherché doit correspondre du début à la fin à l'expression régulière.
- S - permet d'optimiser l'expression régulière si elle est appelée plusieurs fois, par exemple lors d'un traitement récursif ou en boucle.
- s – le méta-caractère point (.) prend toutes les valeurs, y compris le saut de ligne.
- U – Limite la gourmandise de toute l'expression rationnelle.
Ces options peuvent être enrichies ou modifiées par des options qui s'appliquent à l'intérieur de masques.
Pour construire une expression régulière vous disposez d'un langage particulier (que je préférerais appeler formalisme), fait d'un assemblage de caractères qui vont permettre de définir le motif à rechercher.
L'expression régulière la plus simple est celle qui sert à isoler une suite de caractères précis (un mot). A ce moment là elle est uniquement définie par cette suite de caractères. Ainsi le mot "php" est en soit un motif d'expression régulière.
Dans une expression régulière chaque caractère se représente lui même, ainsi si je cherche les r dans une phrase, je peux construire l'expression régulière /r/.
Il est bien-sûr évident que ce formalisme très succinct ne peut suffire à isoler des motifs complexes. Les expressions régulières font appel alors à une syntaxe particulière dont nous allons voir tout de suite différents aspects parmi les plus intéressants.
Retenez simplement qu'hormis ce formalisme, une expression régulière se lit simplement de gauche à droite, comme vous pourriez le faire de n'importe quelle phrase, chaque caractère jouant son propre rôle, sauf dans le cas des caractères de contrôle attribués au formalisme PCRE.
Une première série de caractères de contrôle est constituée par les méta-caractères qui constituent l'essentiel des outils de construction. Certains des aspects d'utilisation de ces méta-caractères varient selon le contexte, notamment dans le cas où l'on se trouve dans une classe ou un masque.
Nous verrons tous ces aspects ultérieurement, retenez simplement que le contexte d'utilisation de ces caractères est important.
5.1 - Le backslash \
Nous allons commencer par le plus important le backslash (barre oblique inverse) : \
Il a de nombreux usages, et nous allons en voir les plus intéressants.
Utiliser des caractères ayant une signification particulière, pour leur valeur propre (échappement)
Il peut arriver qu'au sein d'une expression régulière on ait besoin d'utiliser un caractère qui malheureusement a une signification particulière (caractère de contrôle). Comment faire pour que celui-ci ne soit plus interprété par l'expression régulière mais qu'il soit bien reconnu comme un simple caractère ?
Il suffit de faire précéder le caractère en question d'un backslash qui sert justement à échapper des caractères qui par défaut sont des méta-caractères.
Pour échapper le backslash, il suffit de le doubler.
Ce caractère peut se révéler souvent utile, pour par exemple utiliser dans l'expression régulière le séparateur de l'expression.
Exemple : /Une expression valide avec des \/ et autre chose/
Dans cette expression l'expression est définie entre des slash (/) et peut contenir un slash (/), mais pour qu'il soit correctement interprété et pas considéré comme le délimiteur final de l'expression, il a été échappé avec un backslash.
NB : Le rôle particulier du backslash peut amener à le considérer non pas comme un méta-caractère, mais bien comme un simple caractère d'échappement, qui masque la valeur normale d'un méta-caractère. Ces considérations plus philosophiques que pratiques modifient néanmoins quelque peu la perception que l'on peut avoir de son fonctionnement, ce pourquoi je me dois de les souligner au cas où vous les rencontreriez dans d'autres documentations.
Captures de caractères invisibles
Le backslash peut également servir à rechercher des caractères de contrôle invisibles (tabulations, saut de lignes, etc.).
Voici quelques caractères non imprimables qui sont capturables avec le backslash :
- La nouvelle ligne \n
- La tabulation \t
- Le retour chariot \r
- Le formfeed \f
- L'escape \e
De manière générale on peut également capturer des caractères dont on a le code hexadécimal. Il suffit de faire suivre le backslash du caractère x suivi du code hexadécimal du caractère à rechercher. Pour le code octal, il faudra le faire précéder d'un 0 (zéro).
Classes de caractères
On peut également utiliser le backslash pour définir de manière simple un ensemble de caractères. En voici une courte liste :
- \d tout caractère décimal
- \D tout caractère qui n'est pas un caractère décimal
- \S tout caractère blanc (espace, tabulation, saut de ligne, etc.)
- \s tout caractère qui n'est pas un caractère blanc
- \W tout caractère alphanumérique ainsi que le caractère _
- \w tout caractère qui n'est pas un caractère alphanumérique ni un _
On reverra plus loin les classes de caractères mais pour donner une brève définition une classe de caractères permet de définir un ensemble de caractères pour le caractère à remplacer. Il en existe des prédéfinis et on peut en définir d'autres.
Références arrière, rappel de contenus capturés
On verra plus loin ce qu'est exactement une référence arrière. Sachez seulement qu'il est possible dans une expression régulière de rappeler dans l'expression le contenu d'une partie de l'expression qui a déjà été capturée.
Un petit exemple valant mieux qu'un long discours, nous allons vous présenter le problème suivant.
Imaginons une expression régulière qui aurait pour principe de reconnaître un motif encadré par des balises dont le point commun serait un id.
Il faudra dans la même expression être en mesure d'isoler l'id du motif de la balise ouvrante, puis de le recopier dans la même expression à la place de l'id du motif de la balise fermante, et donc faire une référence dynamique à l'id capturé précédemment. C'est ce qu'on appel une référence arrière.
Celle-ci correspondra à peu près à ceci :/debut id='[^']+'>(.*)<\/fin id='\1'>/
Nota bene :
Le [^']+ signifie au moins un caractère différent d'un quote.
(.*) signifie n'importe quel caractère en n'importe quelle quantité.
On a échappé le / de la balise fermante avec un \ pour éviter qu'il soit interprété comme la fin de l'expression et le début des options.
Pour appeler une référence arrière, il suffit de faire suivre le backslash d'une référence numérique qui correspondra au numéro du masque capturant contenant la référence à tester. Un masque capturant étant un motif encadré par des parenthèses, la numérotation des masques se faisant de gauche à droite.
Attention, si il n'y a pas de référence correspondant au numéro, et si le numéro est supérieur à 10, celui-ci sera interprété comme le code hexadécimal d'un caractère et remplacé par sa valeur.
5.2 - Les crochets [ ]
Tous les caractères contenus entre des crochets représentent une classe de caractères, et prennent une signification différente des autre caractères de l'expression régulière.
On le verra par la suite, le sens des caractères suivants étant donné hors d'une classe de caractère.
Une classe de caractères représente par défaut (non utilisation de quantificateurs), un seul et unique caractère parmi tous ceux qui sont définis dans la classe. Cela permet dans un motif de définir un choix de caractères pour un seul dans le motif.
5.3 - L'accent circonflexe ^
Indique le début du motif, ce qui signifie que la ligne ou la chaîne analysée doit obligatoirement commencer par le motif recherché.
L'expression régulière doit donc nécessairement commencer par ^.
Si le ^ ne se situe pas en début d'expression, ou n'est pas échappé par un backslash, il prend sa signification littérale.
5.4 - Le dollar $
Indique la fin du motif, ce qui signifie que la ligne ou la chaîne analysée doit obligatoirement finir par le motif recherché.
L'expression régulière doit donc nécessairement finir par $.
Si le $ ne se situe pas en fin d'expression, ou n'est pas échappé par un backslash, il prend sa signification littérale.
5.5 - Le point .
Remplace n'importe quel caractère sauf le saut de ligne (\n).
Il peut remplacer le saut de ligne si l'option s (dotall) est activée.
5.6 - Les parenthèses ( )
Elles délimitent un sous-motif et peuvent contenir d'autres expressions régulières. Elles permettent de capturer une partie du motif recherché. Cette capture permet de conserver en mémoire la chaîne de caractère correspondant au motif. Cette chaîne sera accessible par le biais des références arrière, et sera utilisable pour la définition du masque.
5.7 - La barre |
Permet de définir une alternative (ou).
Exemples :
/a|b/ se lit "a ou b".
/ab|cd/ se lit "ab ou cd".
5.8 - Les quantificateurs
Ces méta-caractères permettent de définir un nombre d'itérations (de répétitions) pour un sous-masque ou pour une classe.
Lorsque ces expressions sont évaluées, la librairie PCRE commence par défaut à rechercher le nombre maximum d'itération du motif, on dit qu'elle est gourmande. Cela peut avoir quelques effets néfastes sur certaines types de captures.
Pour éviter de rendre "gourmand" un quantificateur (il commencera sa recherche en recherchant le plus petit nombre d'itérations), il faut le faire suivre d'un point d'interrogation.
Une option existe pour limiter la gourmandise à toute l'expression, il s'agit de l'option U.
Les accolades { }
Permettent de définir une quantification en minimum et maximum. Cette écriture est également valable pour les classes de caractères.
Elles peuvent contenir deux nombres séparés par une virgule, ou un nombre et une virgule, ou un nombre.
Deux nombres et une virgule {1,3}
Signifie que l'expression est répétée de 1 (minimum) à 3 (maximum) fois.
Un nombre et une virgule {4,}
Signifie que l'expression est répétée au moins 4 (minimum) fois.
Un nombre {2}
Signifie que l'expression est répétée uniquement 2 fois.
Le point d'interrogation ?
Quantificateur minimum, signifie 0 ou 1 répétition, est un raccourci pour {0,1}.
L'étoile *
Signifie 0 ou plus, est un raccourci pour {0,}.
Le plus +
Signifie au moins une fois, est un raccourci pour {1,}.
Les expressions régulières permettent de définir facilement un ensemble de caractères, qu'on appelle classe. Si je veux par exemple définir l'ensemble des voyelles, je peux définir la classe [aeiouy].
Par exemple si je veux capturer les mots pépé, mémé, dédé, ou bébé dans une phrase, je peux utiliser une classe de caractère qui me simplifiera mon écriture :
Exemple : /(([pdbm]é){2})/
Dans cet exemple ma classe offre l'alternative p,b,m ou d pour le caractère précédant le é. Nous allons voir quelques aspects de la syntaxe de ces masques.
6.1 - Définir un intervalle de caractères
Si nous désirons définir toutes les lettres minuscules de l'alphabet, le masque à créer risque d'être long à écrire, pour cela les classes ont un raccourci qui simplifie l'écriture.
Il suffit de spécifier les limites inférieure et supérieure des caractères désirés, séparées par un tiret. Ainsi dans notre cas, il suffira d'écrire :
Exemple : /[a-z]/
Cette écriture est valable pour les chiffres et les lettres et est sensible à la casse.
6.2 - Utiliser les classes de caractères
On peut également simplifier l'écriture en utilisant les classes de caractères définies avec le backslash (cf. 5.1). Ainsi si je veux définir tout les caractères alphanumériques ainsi que les caractères d'opérations arithmétiques, je peux écrire :
Exemple : /\W\/*+-]/
Vous remarquerez que le caractère - qui a une signification particulière à l'intérieur d'une classe, a été écrit en fin de la classe pour éviter toute mauvaise interprétation. Nous aurions pu aussi l'échapper avec un backslash ce qui serait revenu au même.
A noter que l'écriture avec le backslash de caractère hexadécimaux est également autorisée à l'intérieur d'une classe.
6.3 - Utiliser un crochet fermant dans une classe de caractères
Si on désire utiliser un crochet fermant dans une classe de caractère il suffit de l'échapper avec un backslash. Ainsi si je veux capturer tout les textes délimités par une parenthèse ou un crochet fermant, je peux écrire :
Exemple : /[(\[].+[)\]]/
6.4 - Utiliser la négation
Si on désire définir une classe de caractère par la négation (par exemple tout les caractères sauf les voyelles), il faut faire précéder le contenu de la classe du caractère ^.
Exemple : /[^aeiou]/
Les sous-masques sont utiles pour proposer des alternatives complexes, ou pour permettre la capture de sous-motifs dans le motif recherché (dans le cas de l'utilisation de références arrière par exemple).
Ils sont délimités par des parenthèses et peuvent être imbriqués les uns dans les autres jusqu'à concurrence de 200 sous-masques dans l'expression.
7.1 - Capture :
Un sous-masque permet de capturer le contenu qu'il définit, celui-ci est accessible ensuite via les références arrière. Le numéro de la capture est celui de l'ordre des sous-masques, cette capture peut-être renvoyée en PHP via le dernier argument de la fonction preg_match_all par exemple, et être utilisée dans les fonctions preg_replace en deuxième argument en faisant précéder le numéro de la capture par le signe $.
Exemple :$regexp = "/un joli (pépé|bébé)/";
preg_replace($regexp, $1, "J'ai vu un joli bébé");
Cette exemple remplacera la phrase "J'ai vu un joli bébé", par "J'ai vu bébé".
7.2 - Masque non capturant :
Attention, seulement 99 sous-masques peuvent capturer des chaînes. Pour éviter de capturer des chaînes qui ne présentent aucune utilité, mais qui sont utiles en tant que sous-masque, il faut tout de suite après la parenthèse ouvrante du sous-masque indiquer les caractères ?:.
Exemple : /je cherche (un masque A|un masque B((?: avec peut être ceci)|(?: ou cela))?)/
Cette expression me recherchera :
"je cherche un masque A"
"je cherche un masque B"
"je cherche un masque B avec peut être ceci"
"je cherche un masque B avec peut être cela"
Et me capturera :
"je cherche un masque A"
"je cherche un masque B"
"je cherche un masque B avec peut être ceci"
"je cherche un masque B avec peut être cela"
"un masque A"
"un maque B"
Mais ne me capturera pas :
"avec peut être ceci"
"avec peut être cela"
7.3 - Les assertions positives et négatives, arrière et avant :
Il se peut que dans un sous-masque on veuille vérifier que celui-ci soit suivi ou non, ou précédé ou non, d'une série de caractères particuliers. On appelle cela une assertion, et les PCRE vous permettent de tester tout les types d'assertions, négatives ou positives, arrière ou avant.
Les assertions sont des sous-masques et utilisent la même syntaxe que les sous-masques classiques, simplement ils sont précédés d'une série de caractères particuliers et ne sont pas capturantes. Par ailleurs elles ne déplacent par le pointeur sur l'expression régulière, elles permettent juste de vérifier certaines conditions.
C'est à dire qu'elles n'avancent pas dans la lecture de l'expression régulière.
Elles peuvent être imbriquées et se combiner entre elles.
Les assertions positives
Elles commencent par ( ?= et signifie il y a.
Les assertions négatives
Elles commencent par ( ?! et signifie il n'y a pas.
Les assertions arrière
Elles commencent toutes par (?>= pour les assertions arrière positives (est précédé de ...) et ?>! pour les assertions arrière négatives (n'est pas précédé de ...).
Les assertions avant
Elles commencent toutes par (?<= pour les assertions avant positives (est suivi de ...) et ?<! pour les assertions avant négatives (n'est pas suivi de ...).
7.4 - Les sous-masques conditionnels
Il est possible de choisir un sous-masque par rapport à une condition, l'écriture du sous-masque est semblable alors à celle du test ternaire, et elle est de la forme :
(?(test)masque positif | masque négatif)
Le test peut porter sur le résultat d'une assertion, ou sur l'existence ou non d'une référence arrière.
7.5 – Les options des sous-masques
Un sous-masque peut accepter en début de masque des options qui viendront compléter ou annuler les options générales du masque. Ces options se déclarent au début du masque après un point d'interrogation.
Les options possibles pour un sous-masque sont les suivantes :
- i – Pour l'insensibilité à la casse.
- m – Pour le mode multi-lignes.
- s – Pour que le point remplace également les sauts de ligne.
Pour annuler une option il suffit de la faire précéder d'un signe moins.
La construction d'une expression régulière est complexe, et nécessite d'avoir bien défini le motif recherché.
Nous vous proposons une méthode héritée de nos expériences, mais elle est perfectible, et ne représente pas une panacée, comme dans beaucoup de choses, seul l'expérience vous aidera à concevoir aisément de nouvelles expressions régulières.
La première étape va consister à rechercher les différentes formes valides que peut avoir le motif, ainsi que les formes non valides mais proches et qui peuvent se retrouver dans l'ensemble de la chaîne de recherche.
Nous vous conseillons ensuite d'isoler les caractéristiques les plus flagrantes du motif (symbole à un emplacement particulier, répétition précise d'un même sous-motif, terminaison ou préfixe particulier, succession particulière de sous-motifs, etc.), en conformité au motif recherché, mais aussi par opposition aux motifs proches mais invalides.
Cette dernière démarche s'avère par ailleurs souvent la plus simple.
Vous allez ensuite définir le masque de base de ces motifs en vous aidant des méta-caractères des expressions régulières. Je vous conseille de noter (SM1) à (SMn) les sous-motifs que vous n'avez pas encore clairement définis dans ce premier masque, on les précisera par la suite.
Une fois cette première démarche effectuée, vous recommencez à l'étape 1 pour chacun des sous-motifs (SM1) à (SMn) que vous n'avez pas encore traité. Vous obtenez alors une première expression régulière complète.
Il ne vous reste plus alors qu'à essayer de la simplifier en factorisant certains sous-motifs, ou en simplifiant l'écriture d'autres. Pour les expressions complexes, un masque négatif (ne contient pas, est différent, etc.) s'avère souvent plus simple à concevoir et plus performant qu'un masque positif (celui qu'on a tendance à créer en premier). De manière encore plus générale, il faut vous entraîner à envisager un problème sous différents angles, et à cerner les limites de la définition du motif que vous cherchez à capturer.
Quoiqu'il en soit sachez que la construction d'une expression régulière est une épreuve de patience, de rigueur et d'obstination, mais que c'est outil extrêmement puissant pour analyser des chaînes de caractères et qu'elles valent bien l'effort nécessaire pour les utiliser.
Ont contribué à l'élaboration de ce document par leur relecture, leurs conseils, ou leurs idées et commentaires :
- Thomas BROYER
- Armel FAUVEAU
|