Dans ce tutorial, vous trouverez une description des expressions régulières(ou rationelles) compatibles PERL, ou plus communement appelées PCRE: Perl Compatible Regular Expressions. Ce tutorial est un condensé de ce qui est le plus important à savoir à propos des expressions régulières il n'est malheureusement pas complet car traiter un tel sujet ne se limite pas à un tutorial, il faudrait un livre entier pour apprécier la puissance et l'art de composer une expression régulière correspondant à ses besoins. J'espère simplement que ce tutorial vous sera utile, et si jamais vous avez des critiques, des suggestions ou autres à me faire parvenir, n'hésitez pas à me le faire savoir sur cette adresse email: qwix@dreamweaver-forum.net .
Les expressions régulières sont une suite d'expressions logiques qui permettent de vérifier le format d'une chaine de caractères devant correspondre à un modèle précis. Issues du monde UNIX, celles-ci ont été très largement utilisées et répandues dans PERL, qui est devenu un des langages de référence pour l'utilisation des expressions régulières. Elles permettent de traiter les chaines d'une façon si puissante qu'il serait très difficile voire impossible de faire la même chose en utilisant simplement les fonctions de chaines standards.
Une expression régulière est composée de deux parties:
Pour différencier la première de la seconde partie, on utilise des séparateurs prédéfinis qui sont soit le slash ‘/’ soit le dièse ‘#’. qui est un caractère que l'on choisit.
Ainsi on peut écrire une expression régulière comme ceci:
$regex = "#le_modèle#options_possibles"; // Ici le séparateur est #
ou encore
$regex = "/le_modèle/options_possibles";// Ici le séparateur est /
Pour ma part je préfère la première notation car elle permet de bien différencier le modèle et les options, la lisibilité d'une expressions régulière étant très importante pour la compréhension et la maintenance, surtout en cas de modèles complexes. Dans tout les cas vous pouvez choisir l’un ou l’autre comme bon vous semble, L'essentiel est que vous soyez capables de vous relire par la suite
L'antislash '\' permet d'échapper des caractères dit spéciaux, c'est à dire des caractères qui ont un sens dans le langage des expressions régulières, il permet de dire au moteur qui traite le modèle que le caractère suivit de '\' a une signification spéciale et qu'il ne faut pas le traiter comme un simple caractère. L'antislash permet aussi d'échapper les slashes '/' qui déterminent la fin du modèle et le début des options de l'expression. En effet un peu plus haut j'ai dit qu'il était possible d'écrire une expression régulière de cette façon:
$regex = \"/le_modèle/options_possibles\" ;
Si jamais le modèle doit contenir un slash, celui-ci doit être échappé avec antislash '\', sinon il risquerait de terminer trop tôt le modèle et vous obtiendriez des erreurs de compilations de votre expression
. Autrement dit, si vous devez utilisez un '/', que vous utilisez cette notation $regex = ”/le_modèle/options_possibles” ; et que ce '/' n'est pas censé terminer votre modèle, échappez le toujours avec un '\' devant.
Certaines combinaisons d'un caractère avec '\' signifie quelque chose de bien particulier dans le langage des expressions régulières, vous trouverez la liste de ceux utilisables pas les PCRE de PHP.
Cette liste de métacaractères permet d'avoir une représentation visuelle un peu plus claire pour certains caractères contenus dans les chaines de caractères. Par exemple seriez-vous capables de reconnaitre la différence entre une tabulation et plusieurs espaces si vous deviez les saisirs tels quels ? A moins d'être équipé d'une vision bionique je voit mal comment ce serait possible, cette liste est faite pour vous aider à en reconnaitre une bonne partie et ce, sans subir de fatigue visuelle importante, même si je vous l'accorde, noyé dans un modèle un peu complexe on se perd facilement.
Vous avez aussi la possibilité de reconnaitre des caractères dont vous connaissez le code hexadécimal. Il suffit de faire suivre l'antislash du caractère x suivi du code hexadécimal du caractère à rechercher. Pour le code octal, il suffit de le faire précéder d'un zéro.
Voici donc, si cela peut vous aider l'équivalence ASCII et octale des métacaractères présentés ci-dessus:
ASCII: <BS> octal: 010ASCII: <ESC> octal: 033ASCII: <FF> octal: 014ASCII: Unix, Dos et Windows: <LF>, MacOS: <CR> octal: Unix, Dos et Windows: 012, MacOS: 015ASCII: <CR>, MacOS:<LF> octal: 015ASCII: <HT> octal: 011Les classes de caractères permet de décrire de façon lisible un groupe de caractère spécifiques, en voici la liste:
Les caractères contenus entre des crochets représentent une classe de caractères, c'est à dire une liste de caractères autorisés que l'on peut trouver dans la chaine cible. La classe de caractères représente, un seul et caractère parmi tous ceux qui sont autorisés dans la classe. Elle permet donc de reconnaitre un caractère parmis une liste de plusieurs possibles.
Prenons une phrase d'exemple:
J'utilise une souris Microsoft
Mais que par erreur je me trompe en tappant Microsoft et que je tape Mikrosoft (Non ce n'est pas le K d'une célèbre marque de bière bon marché
). Comment faire pour retrouver quand même ce mot ? Il suffit de créer une classe de caractère qui autorise le 'c' et le 'k'.
Ainsi je pourrais créer le modèle suivant:
$regex = "#Mi[ckCK]rosoft#";
Ainsi je peux taper ce mot comme je le souhaite c'est à dire: Microsoft ou Mikrosoft ou MiCrosoft ou MiKrosoft puisque que j'ai autorisé les caractères 'c', 'k', 'C' et 'K' j'ai donc traité la possiblité d'avoir les lettres en majuscules. Nous verrons plus tard qu'il est possible d'utiliser une option d'insensibilité à la casse, ce qui nous évite de nous préoccuper des différences entre majuscules et minuscules.
Une méthode efficace pour comprendre -et par la suite rédiger- un modèle d'expression régulière, est de le lire à haute voix. Pour cet exemple on aurait:
"Je cherche un mot qui commence par M puis i, suivi de soit c, soit k, soit C, soit K, puis r, puis o, puis s, puis o, puis f, puis t".
Ainsi le fait de le dire à haute voix vous fait comprendre les choses plus rapidement
Voici par exemple quelques classes de caractères très utiles, mais par contre pas très lisible si elles ne sont pas utilisées avec parcimonie: 1: [-a-zA-Z0-9_] Accepte tout caractères alphanumérique, ainsi que le tiret et le tiret bas, sans ce soucier de la casse du caractère. 2: [0-9] Accepte tout caractère numérique.
Dans la partie pratique, nous verrons comment traiter efficacement les nombres avec ces classes de caractères.
Remarque
Lorsque que vous souhaitez utiliser des classes de caractère sur des caractères accentués comme 'é', 'è'…. la classe [a-zA-Z0-9-_] ne fonctionnera pas, en effet celle-ci ne gère pas les caractères accentués des chaines de caractères, vous pouvez bien sur rajouter le ou les caractères accentués dans la classe mais vous obtiendrez vite un charabia incompréhensible et illisible, surtout si vous voulez gérer la totalité des accents.
Vous pourriez par exemple obtenir ceci:
[éèàöôa-zA-Z0-9] bien sur je n'ai pas mis la totalité des caractères accentués, comme vous pouvez le voir ce n'est pas lisible du tout.
L'astuce consiste donc à utiliser la classe \w qui elle gère les caractères accentués
.
Elles délimitent un sous-motif et peuvent contenir d'autres expressions régulières. Le texte qui est reconnu par le motif entre elles sera capturé et stocké en mémoire, pour être utilisés plus tard grâce aux références arrières dont nous reparlerons un peu plus loin.
Contrairement aux crochets, les parenthèses permettent de stocker une séquence ou une suite figée de caractères, c'est à dire que les caractères doivent apparaitre dans le même ordre que celui cité dans les parenthèses.
Prenons un exemple:
Chacun cherche son chat.
Je souhaite récupérer le mot 'chat'.
Je pourrais me dire “ok chat est composé d'un 'c' d'un 'h' d'un 'a' et d'un 't', je peux donc utiliser la classe de caractères [chat]{4}” ne vous préoccupez pas de '{4}' nous verrons sa signification un peu plus loin
Quel est le résultat obtenu lorsque que l'on applique ce modèle à notre phrase ? On obtient effectivement le mot 'chat', maintenant mélangeons les lettres de ce mot et remplaçons 'chat' par 'hact'. Avec le même modèle nous obtenons le résultat 'hact'. Pourquoi ? Parce que la classe de caractère est une liste de caractères autorisés et ce dans n'importe quel ordre.
Maintenant utilisons les parenthèses avec ce modèle (chat). Avec le mot 'hact' nous n'obtenons rien, par contre avec le mot 'chat' nous avons un résultat.
Pour bien comprendre l'utilité des parenthèses il ne faut pas vous dire, “je cherche le mot chat” mais plutôt, “je cherche la séquence de caractères suivante: 'c' suivi immédiatement de 'h' suivi immédiatement de 'a' suivi immédiatement de 't' ”. Vous comprendrez plus vite
Pour tester par vous même voici le script utilisé:
<?php $chaine = array("chat", "hact", "chat", "hact") ; $pattern = array("#[chat]{4}#", "#[chat]{4}#", "#(chat)#", "#(chat)#"); for($i=0; $i<sizeof($chaine); $i++) { if(preg_match($pattern[$i], "Chacun cherche son ".$chaine[$i], $matches)) { echo "une correspondance à eu lieu sur la chaine ".$chaine[$i]." et pour le modèle ".$pattern[$i]."<br/>\n" ; echo "<pre>" ; print_r($matches[0]) ; echo "</pre>" ; }//fin if else echo "aucune correspondance trouvée sur la chaine ".$chaine[$i]." et pour le modèle ".$pattern[$i]."<br/>\n" ; }//fin for ?>
Il indique le début d'une ligne de texte. Attention ce que je viens de dire est important, l'accent circonflexe indique un début de ligne et non le début d'un mot!! Autrement dit “^chien” se traduit par “Une ligne qui commence par la lettre c, suivie de h….” et pas “une chaine qui commence par chien” ce qui-à mon sens en tout cas- ne veut rien dire.
Utilisé dans une classe de caractères, l'accent circonflexe signifie une négation, par exemple [^0-9] signifie qu'on ne souhaite pas reconnaitre de caractère alphanumérique.
C'est le contraire de l'accent circonflexe, il indique une fin de ligne.
Elle permet de définir une alternative c'est une traduction du mot ou. Un peu comme dans l'expression “thé ou café” la barre fonctionne exactement de la même manière. Par exemple “thé|café|chocolat” peut se traduire oralement par “thé, ou café, ou chocolat”.
Le point permet de reconnaitre n'importe quel caractère, mais attention son comportement peut-être vicieux si vous l'utilisez mal.
Ces métas caractères permettent, comme leur nom l'indique, de définir un nombre (minimum, maximum, ou les deux simultanément) de reconnaissances pour une classe ou un masque. On les qualifie d' avides car ils ne se contentent pas d'un minimum de reconnaissances dans la chaine cible, mais d'un maximum.
Elles permettent de définir une plage de reconnaissances, c'est à dire un minimum et un maximum. Elles s'utilisent comme ceci: {min, max} qui permet de définir un nombre minimum et maximum de reconnaissance sur la chaine cible. {nombre} qui permet de définir un nombre exact de reconnaissances sur la chaine cible. {min, } qui permet de définir un nombre minimum de reconnaissance sur la chaine cible.
Il permet de définir une possibilité, c'est à dire qu'une reconnaissance peut avoir lieu au maximum une fois, ou ne pas avoir lieu. C'est l'équivalent plus lisible de {0,1}.
Elle permet de définir aussi une possibilité étendue, c'est à dire que si la reconnaissance à lieu, il n'y a pas de maximum, par contre il est possible qu'il n'y ait aucune reconnaissance dans la chaine cible. Couplée au méta caractère point '.' c'est un couple efficace mais à utiliser avec attention. C'est l'équivalent plus lisible de {0,}.
Il permet de définir une obligation, c'est à dire que la reconnaissance doit avoir lieu une fois au minimum, et sans limite maximum. C'est l'équivalent plus lisible de {1,}.
La gourmandise est un vilain défaut: Pour obtenir une reconnaissance efficace il est souvent obligatoire d'utiliser un ou plusieurs quantificateurs (+, *, ? , {min, max}) le problème c'est qu'il faut faire attention lorsque l'on les utilise. En effet, ceux-ci sont très 'gourmands' c'est à dire qu'ils vont essayer d'obtenir le plus de reconnaissances possibles, il faut donc faire attention à la combinaison de ces quantificateurs.
Voyons un exemple. Prenons cette phrase:
J'espère que l'année 2004 sera bonne pour vous.
Et utilisont cette expression régulière
#(.*)([0-9]+)(.*)#
Pour ceux qui connaissent déjà les expressions régulières, ils verront que ce pattern est complètement stupide, mais celui-ci explique bien la 'gourmandise' des quantificateurs. En effet si on teste ce pattern avec ce script:
<?php $chaine = "J'espère que l'année 2004 sera bonne pour vous." ; $regex = "#(.*)([0-9]+)(.*)#" ; if(preg_match($regex, $chaine, $matches)) { echo "<pre>" ; print_r($matches) ; echo "</pre>" ; }//fin if ?>
On obtient:
Array
(
[0] => J'espère que l'année 2004 sera bonne pour vous.
[1] => J'espère que l'année 200
[2] => 4
[3] => sera bonne pour vous.
)
La case 0 est un ensemble de toutes les reconnaissance, ne la prenons pas en compte, par contre on voit bien que la case 2 du tableau, c'est à dire la seconde reconnaissance du modèle n'a qu'un seul chiffre, le 4 hors si jamais on ne prends que ce pattern ([0-9]+) on devrait obtenir 2004. Ceci explique bien ce que j'essaie de vous montrer, la partie (.*) prends un maximum de caractères et comme cette partie ([0-9]+) ne se contente d'un caractère numérique elle ne récupère que le 4 vu que la partie d'avant prends tout le reste précédant ce chiffre et que la partie d'après n'est pas constituée de chiffres.
J'espère qu'avec cette démonstration, vous comprenez maintenant qu'il faut faire attention avec certains quantificateurs.
A l'inverse des quantificateurs avides cité ci-dessus, il existe des quantificateur dits 'paresseux', c'est à dire qu'ils se contentent du mimimum de reconnaissances possibles dans le texte cible. Leur signification reste la même que pour les quantificateurs avides, seule la notation change. Vous trouverez ci-dessous la notation avide et paresseuse pour chaque quantificateur:
Les accolades:
Le point d'interrogation:
L'étoile:
Le plus:
Pour voir une utilisation des quantificateur paresseux, vous pouvez comparer ces deux scripts: Avec des quantificateurs avides:
<?php $chaine = "J'espère que l'année 2004 sera bonne pour vous." ; $regex = "#(.*)([0-9]+)(.*)#" ; if(preg_match($regex, $chaine, $matches)) { echo "<pre>" ; print_r($matches) ; echo "</pre>" ; }//fin if ?>
Avec des quantificateurs paresseux:
<?php $chaine = "J'espère que l'année 2004 sera bonne pour vous." ; $regex = "#(.*?)([0-9]+)(.*)#" ; if(preg_match($regex, $chaine, $matches)) { echo "<pre>" ; print_r($matches) ; echo "</pre>" ; }//fin if ?>
Et regardez la différence au niveau de la récupération de l'année 2004
En théorie, on dit que la reconnaissance qui à lieu le plus à gauche dans la chaine cible est toujours celle retenue, même si d'autre reconnaissances -mais qui ont lieu plus loin dans la chaine- ont lieu.
Prenons une phrase d'exemple:
Le portail media box vous souhaites la bienvenue, mais essuyez vos pieds avant d'entrer.
Et maintenant recherchons les mots 'media', 'la', 'pieds', 'entrer'. Nous obtenons ainsi la regex suivante
#entrer|pieds|la|media#
L'ordre est volontairement aléatoire
.
Dans ce cas, le script suivant:
QUOTE
<?php $chaine = "Le portail media box vous souhaite la bienvenue, mais essuyez vos pieds avant d'entrer." ; $regex = "#entrer|pieds|la|media#" ; if(preg_match($regex, $chaine, $matches)) echo $matches[0] ; ?>
media
En effet, même si 'media' est en dernier dans la liste des mots à rechercher, étant donné qu'il se trouve le plus à gauche dans la chaine cible, c'est ce mot qui sera retenu.
Comme je l'ai dit au début de ce tutorial, une expression régulière peut être composée d'un modèle et d'options, c'est ce que nous allons voir. Une option permet d'influer sur l'interprétation du modèle. De plus il est possible de les cumuler ce qui rend parfois les choses beaucoup plus simple. Toutefois si vous souhaitez ne pas utiliser d'options, sachez que par défaut le modèle s'appliquera à une seule ligne, c'est à dire à une chaîne terminée par \n.
Liste des options possibles pour les PCRE
Cette liste et tirée de la documentation PHP, avec quelques modifications personnelles pour certaines options.
Comme nous l'avons vu dans la partie traitant les parenthèses, celles-ci permettent de capturer une reconnaissance dans le texte, elle reste stockée en mémoire pour être ensuite réutilisée.
Voyons le fonctionnement avec un exemple:
#(truc)(machin)(bidule)#
Nous avons donc trois groupes de parenthèses, le texte reconnu dans la chaine cible sera donc stocké dans des variables, disponibles ensuite par la notation \\1, \\2, \\3 c'est ce que l'on apelle les références arrières ainsi (truc) sera capturé dans \\1, (machin) dans \\2 et (bidule) dans \\3.
Il existe une règle concernant ces parenthèses capturantes, elles sont numérotées en comptant les parenthèses ouvrantes depuis la gauche. Ce qui peut apparaitre comme une lapalissade est toutefois important.
Prenons un exemple: Quelles sont les références arrières de:
((truc)(machin)(bidule))
Voilà la solution:
( (truc)(machin)(bidule) ) remplit \\1.(truc) remplit \\2.(machin) remplit \\3.(bidule) remplit \\4.Il existe aussi la notation $1… qui est utilisable dans preg_replace, mais uniquement dans preg_replace,.
Comme nous l'avons déjà vu, les parenthèses permettent de capturer un texte, mais parfois c'est inutile, nous avons juste besoin de reconnaitre la séquence qu'elle contient sans pour autant stocker la reconnaissance en mémoire. Il est possible de faire cela avec les parenthèses non capturantes. Elles se distinguent des parenthèses capturantes par la notation ?:, par exemple (?:truc) permet de dire “Je cherche un séquence de caractères, un 't' suivi immédiatement d'un 'r', suivi immédiatement d'un'u', suivi immédiatement d'un 'c', mais je ne veux pas stocker cette reconnaissance en mémoire, je veux qu'elle ait lieu, c'est tout”.
Une reconnaissance atomique se déroule normalement , mais au moment ou elle se termine, c'est à dire au moment ou elle atteint la parenthèse fermante, le texte reconnu à l'intérieur est figé, il y a alors deux options soit le garder, soit le jeter. Ne sachant pas vraiment comme cette méthode fonctionne, ni même comment on peut l'utiliser dans des cas concrets, je ne me risquerais pas à vous donner un exemple, je me contente juste de vous en donner une définition.
Les tests avants ou arrières ne reconnaissent pas de texte, mais peuvent reconnaitre une une position dans le texte cible. Il est ainsi plus pratique de définir que vous voulez tel caractère à telle position.
Le test avant positif regarde en avance dans le texte cible, c'est à dire qu'il va vérifier sur la droite si la sous-expression dont il fait partie peut réussir. Voici un exemple de test avant positif: Prenons la phrase
Bienvenue sur le site Mediabox
et appliquons le modèle suivant:
#Media(?=box)#
Ce modèle signifie littéralement: “Je souhaite trouver le mot Media uniquement s'il est suivi du mot box” autrement dit “Je veux trouver le mot Media uniquement si le mot box est sur sa droite immédiate”.
Vous pouvez tester ce code pour voir par vous même:
<?php $texte = "Bienvenue sur le site de Mediabox" ; $pattern = "#Media(?=box)#"; if(preg_match($pattern, $texte, $matches)) print_r($matches) ; else echo "rien trouvé" ; ?>
Comme le test avant positif, le test avant négatif va regarder sur la droite dans le texte cible, mais en vérifiant si la sous-expression dont il fait partie ne peut réussir.
Le test arrière positif regarde en arrière dans le texte, c'est à dire qu'il va vérifier sur la gauche si la sous-expression dont il fait partie peut réussir.
Voici un exemple de test arrière positif: Prenons la phrase
On parle de multimedia
et appliquons le modèle suivant
#(?<=multi)media#
Ce modèle signifie littéralement: “Je souhaite trouver le mot media uniquement s'il est précédé du mot muti” autrement dit “Je veux toruver le mot media uniquement si le mot multi est sur sa gauche immédiate”.
Vous pouvez tester ce code pour voir par vous même:
<?php $texte = "On parle de multimedia" ; $pattern = "#(?<=multi)media#" ; if(preg_match($pattern, $texte, $matches)) print_r($matches) ; else echo "rien trouvé" ; ?>
Comme le test arrière positif, le test arrière négatif va regarder sur la gauche dans le texte cible si la sous-expression dont il fait partie ne peut réussir.
Les tests conditionnels: (?(if) then | else) La strucutre conditionnelle permet d'exprimer un “si, alors, sinon” comme en programmation normale, si le test du if réussi on passe à then sinon on passe à else.
Les limites de mot: \b Les limites de mots reconnaissent un emplacement dans une chaine. On apelle une limite de mot tout caractère qui n'est pas adjacent à un autre caractère normal et non spécial comme par exemple, le point, la virgule, l'espace ou la tabulation, le saut de ligne, la nouvelle ligne….
Les modificateurs de codes: (?modificateur)
Avec les PCRE il est possible d'utiliser ce que l'on apelle les modificateurs de codes, ils permettent de de modifier une option de reconnaissance à l'intérieur de l'expression régulière elle même, ainsi vous pouvez par exemple appliquer une insensibilité à la casse pour seulement une partie de votre expressions régulière, ce qui peut-être pratique dans certains cas
.
Voici la liste des modificateurs de codes disponibles:
Vous avez donc les possibilités suivantes:
C'est la fin de la première partie de ce tutorial, dans la seconde partie nous verrons comment comment construire quelques expressions régulières types.
Encore des questions? Besoin d'aide? Venez en discuter sur le forum Flash Programmation Dynamique.