Les expressions régulières PCRE [ripat]
Par Damien, dimanche 10 avril 2005 à 11:18 :: PHP :: #28 :: rss
1- Remarques préliminaires
Ce tuto suppose une connaissance minimum de la syntaxe de base des regex et des fonctions PHP qui les utilisent. Voir les deux tutoriaux suivants: Les expressions régulières ou celui de Naholyr (format PDF) pour une présentation détaillée de la syntaxe élémentaire.
Ceci dit, les remarques qui vont suivre sur l'implémentation des PCRE par PHP ainsi que les conseils d'optimisation des motifs valent pour tous les langages qui utilisent les regex PCRE (Perl, bien sûr, mais aussi Python, Java, .NET etc...), avec des petites variations mineures. Tous ces langages partagent un même moteur, de type NFA (Nondeterministic Finite Automation). MySQL, awk et egrep, par contre, utilisent un autre type de moteur (DFA - Deterministic Finite Automation) plus simple, moins puissant mais plus rapide pour les cas (très) simples, voir simplistes ("Talking about DFA matching is very boring!" Jeffrey Friedl).
2- Les fonctions d'expressions régulières de PHP
Le support des expressions régulières dans PHP est assuré par deux bibliothèques: POSIX et PCRE.
A première vue, les fonctions PCRE (preg_match...) ne diffèrent pas beaucoup des fonctions POSIX (ereg....). Le prototype des fonctions ainsi que la syntaxe des motifs se ressemblent fort. Mais c'est à première vue seulement!
Exemple : On recherche le mot expressions dans la phrase
Les expressions rationnelles sont plus faciles à écrire qu'à lire!
L'expression régulière POSIX s'écrira:
Tandis que les PCRE s'écriront:
La première différence qui saute aux yeux c'est qu'un motif PCRE doit être encadré par des délimiteurs afin que le moteur regex trouve les options de recherche que l'on place derrière le délimiteur de fin.
Les nombreuses options disponibles modifient le comportement de la fonction PCRE. Ainsi, pour rendre le motif PCRE insensible à la casse, il suffit de lui rajouter l'option i après le délimiteur de fermeture comme ici :
Voir plus loin la liste des options disponibles.
Tout caractère non alpha-numérique (sauf l'anti-slash) peut être utilisé comme délimiteur. Attention cependant au jeu de caractères installés sur le serveur. Ainsi, certains caractères comme § posent parfois problème. Si vous obtenez une erreur du genre 'Delimiter must not be alphanumeric or backslash' ou 'No ending delimiter found', vérifiez ou changez vos délimiteurs. Si un délimiteur doit être utilisé dans le motif de l'expression, il faudra l'échapper.
Pour vous présenter les fonctions PCRE, j'ai fait le choix de mettre en évidence ses avantages par rapport aux fonctions POSIX. Cette manière de faire me permet de passer en revue l'ensemble de l'énorme potentiel des expressions régulières Perl Compatible
3- Pourquoi utiliser les fonctions PCRE plutôt que les fonctions POSIX ?
- L'exécution d'une PCRE est toujours plus rapide (à motif identique) qu'une fonction POSIX.
- Possibilité de rendre un quantificateur non gourmand.
- Possibilité d'utiliser les références arrières (back reference).
- Possibilité de capturer toutes les occurrences d'un motif et pas seulement la première.
- Possibilité de parenthèses non capturantes pour les sous-masques (gain en rapidité).
- Possibilité de remplacement avec "callback" (invocation d'une autre fonction php)
- Il existe un plus grand nombre de classes de caractères.
- Existence d'assertions simples qui ne consomment pas de caractères (comme \b par exemple).
- Possibilité d'utiliser de nombreuses retour menu des options.
- i - insensible à la casse
- s - le point (dot) reconnaît le retour ligne
- m - multilignes
- e - évaluation de code php dans les fonctions de remplacement
- U - ungreedy, pour rendre les quantificateurs non gourmands
- x - Pour commenter les motifs
- autres options A, D, S, X (moins courantes)
- Les assertions positive/négatives avant/arrières
- Utilisation des sous masques conditionnels.
- Les POSIX ne sont pas compatibles avec les données binaires, Les PCRE bien.
- Optimisation et astuces: ou comment accélérer vos regex.
- Utilisez le dot avec parcimonie
- Supprimez les parenthèses capturantes inutiles
- Optimisez les alternatives.
- Scindez vos regex trop complexes
- Supprimez les options inutiles
- Choisissez les fonctions callback appropriées
- Ancrez vos motifs
- Testez et chronométrez
- Quelques liens.
Les quatre premières raisons devraient déjà suffire à vous convaincre.
La rapidité
Exemple on cherche le mot "plus" dans le texte suivant:
La syntaxe des masques utilisés dans ces fonctions ressemble fort à celle de Perl. Les expressions seront entourées de délimiteurs, slash (/), par exemple. N'importe quel caractère peut servir de délimiteur, tant qu'il n'est pas alpha-numérique ou n'est pas un anti-slash (\). Si un délimiteur doit être utilisé dans l'expression, il faudra l'échapper avec un anti-slash. Depuis PHP 4.0.4, vous pouvez utiliser les délimiteurs (), {}, [], et <>, comme en Perl. Voir la syntaxe des masques pour plus d'explications.
La fonction PCRE preg_match('# plus #', $txt, $out) trouvera la première occurrence du motif approximativement 4 fois plus rapidement que son équivalent POSIX ereg(' plus ', $txt, $out).
Les PCRE sont plus rapides. Un point c'est tout! (démo)
Quantificateur non gourmand (non greedy).
Les quantificateurs +, * ou {1,} et {0,} sont toujours gourmands dans les fonctions POSIX mais seulement par défaut dans les PCRE.
Exemple : si on cherche à capturer l'url contenue dans l'ancre suivante
<a href="http://ceci_est_url.com">lien</a>
Dans la fonction POSIX ereg('<a href=(.*)>', $txt, $out), le dot (tout caractère sauf retour ligne) continuera à manger tous les caractères jusqu'au dernier > rencontré (celui de </a>). Voyez plutôt:
<a href="http://ceci_est_url.com">lien</a>
Par contre, dans les PCRE on peut demander au dot de s'arrêter au premier > rencontré grâce au point d'interrogation. Ainsi, la regex suivante preg_match('#<a href=(.*?)>#', $txt, $out) , retournera ceci:
<a href="http://ceci_est_url.com">lien</a>
Oui, oui, je sais qu'ici on aurait pu utiliser utilement une classe négative de caractères telle que '[^>]*' pour obtenir le même le résultat plus rapidement encore (voir discussion plus bas), mais c'est juste pour l'exemple!
Possibilité d'utiliser les références arrières (back reference)
Dans un motif PCRE, on peut insérer une référence arrière. C'est à dire qu'on peut capturer quelque-chose dans un texte et l'utiliser plus loin dans le motif comme référence.
Exemple, dans la chaîne :
<i>Texte italique</i> texte normal <b> et texte gras</b> fin du texte.
Imaginons que nous souhaitions capturer tout ce qui est contenu dans les balises <b>ou <i>. La regex suivante est une des solutions possibles preg_match('#<([ib])>(.*?)</\1>#', $txt, $out)
La première parenthèse capturante mémorisera le i ou le b des balises ouvrantes <b>ou <i> et l'utilisera plus loin dans les balises fermantes </b>ou </i> grâce au motif </\1> où \1, la référence arrière, sera égal à b ou i selon ce qui aura été capturé plus avant. Résultat:
<i>Texte italique</i> texte normal <b> et texte gras</b> fin du texte.
Et le tableau de sortie $out contiendra les éléments suivants:
- Les matches $out[0]=>
- [0]=><i>Texte italique</i>
- [1]=><b> et texte gras</b>
- Capture $out[1]=>
- [0]=>i
- [1]=>b
- Capture $out[2]=>
- [0]=>Texte italique
- [1]=> et texte gras
Capturer toutes les occurrences d'un motif
Les fonctions POSIX ereg(), eregi() ne capturent que la première occurrence rencontrée alors que la PCRE preg_match_all() recherchera toutes les occurrences d'un motif dans un texte. Si on ne veut vérifier que la première concordance de motif avec une fonction PCRE, on pourra toujours utiliser preg_match(), de toute façon, plus rapide que ereg()
Pour le remplacement, c'est l'inverse, la fonction ereg_replace('les', 'LES', $txt) remplacera toutes les occurrences du mot 'les' par 'LES'. Mais, imaginons que vous souhaitiez ne remplacer que les 10 premières occurrences de ce mot, il faudra alors utiliser la fonction PCRE: preg_replace('#les#', 'LES', $txt, 10). Plus de souplesse de programmation donc.
Les parenthèses non capturantes
Les parenthèses sont principalement utilisées pour capturer une série de caractères correspondant à un motif. Dans certains cas, elles seront utilisées pour délimiter les alternatives ou bien une chaîne alternative. Ainsi dans le texte suivant:
Astuce : PHP supporte aussi des expressions rationnelles compatibles Perl, avec l'extension PCRE functions. Ces fonctions supportent des recherches non-gourmandes, des assertions, des sous-masques conditionnels et toute une gamme de fonctionnalités absentes des expressions rationnelles POSIX.
Admettons qu'on cherche à capturer tous les mots précédés de 'des ou 'de', on pourrait faire preg_match_all('#\b(le|les|la|de|des|du)\s(\w+)\b#', $txt, $out) . Ce qui donne:
Astuce : PHP supporte aussi des expressions rationnelles compatibles Perl, avec l'extension PCRE functions. Ces fonctions supportent des recherches non-gourmandes, des assertions, des sous-masques conditionnels et toute une gamme de fonctionnalités absentes des expressions rationnelles POSIX.
Et le tableau de résultat $out retournera
- Matches [0]=>
- [0]=>des expressions
- [1]=>des recherches
- [2]=>des assertions
- [3]=>des sous
- [4]=>de fonctionnalités
- [5]=>des expressions
- Captures [1]=>
- [0]=>des
- [1]=>des
- [2]=>des
- [3]=>des
- [4]=>de
- [5]=>des
- Captures [2]=>
- [0]=>expressions
- [1]=>recherches
- [2]=>assertions
- [3]=>sous
- [4]=>fonctionnalités
- [5]=>expressions
On voit que les parenthèses du groupe de l'alternative ont effectivement capturé tous les déterminants (de, des etc...). Mais seul le deuxième groupe capturant, celui de $out[2], nous intéresse. Par souci de clarté, mais surtout d'efficacité, on peut rendre les premières parenthèses non capturantes en rajoutant simplement ?: après la parenthèse ouvrante: preg_match_all('#\b(?:le|les|la|de|des|du)\s(\w+)\b#', $txt, $out) .
Astuce : PHP supporte aussi des expressions rationnelles compatibles Perl, avec l'extension PCRE functions. Ces fonctions supportent des recherches non-gourmandes, des assertions, des sous-masques conditionnels et toute une gamme de fonctionnalités absentes des expressions rationnelles POSIX.
Le tableau de résultat $out retournera cette fois ceci:
- Matches [0]=>
- [0]=>des expressions
- [1]=>des recherches
- [2]=>des assertions
- [3]=>des sous
- [4]=>de fonctionnalités
- [5]=>des expressions
- Capture [1]=>
- [0]=>expressions
- [1]=>recherches
- [2]=>assertions
- [3]=>sous
- [4]=>fonctionnalités
- [5]=>expressions
"So what", vous allez dire! Eh bien, le tableau de sortie n'est pas encombré d'éléments inutiles et il sera ainsi plus facile à exploiter, de plus, les indices des références arrières (backreference) seront plus facile à gérer et, enfin, la regex consommera moins de mémoire et sera plus rapide! Une nano-seconde par-ci, une micro seconde par-là...
La même remarque vaut pour une chaîne alternative du genre #PHP est un (excellent)? langage de programmation# où le mot excellent est facultatif. Si vous ne devez pas capturer ce mot, il vaut mieux écrire #PHP est un (?:excellent)? langage de programmation#.
Dans les fonctions POSIX, ce n'est pas possible. Toutes les parenthèses sont capturantes.
Les fonctions du type "callback"
Nous abordons ici une des fonctionnalités les plus utiles des fonctions PCRE. Utiles car elles permettent d'utiliser, au sein d'une expression régulière, n'importe quelle fonction php ou perso.
Il y en a deux: preg_replace() avec l'option e ou bien preg_replace_callback()
Callback kesako? On demande simplement à PHP d'exécuter du code PHP ou une fonction au moment du remplacement. Imaginez, par exemple, que vous souhaitiez mettre un mot sur deux en majuscule avec la fonction php strtoupper() et l'autre mot à l'envers avec la fonction strrev() dans le texte suivant:
php supporte aussi des expressions rationnelles compatibles Perl, avec l'extension PCRE functions. Ces fonctions supportent des recherches non-gourmandes, des assertions, des sous-masques conditionnels et toute une gamme de fonctionnalités absentes des expressions POSIX
Le code php suivant:
<?php ?>
retournera le texte ceci:
PHP etroppus AUSSI sed EXPRESSIONS sellennoitar COMPATIBLES lreP, AVEC l'EXTENSION ERCP FUNCTIONS. seC FONCTIONS tnetroppus DES sehcrehcer NON-sednamruog, DES snoitressa, DES suos-MASQUES slennoitidnoc ET etuot UNE emmag DE sétilannoitcnof ABSENTES sed EXPRESSIONS xisop
Vous percevez certainement la puissance de l'option e. Mais, dans le motif de remplacement, il ne faut pas se tromper dans les simples et doubles quotes sinon...
J'avoue que je dois chaque fois réfléchir sur ce coup là. C'est ici que la fonction preg_replace_callback() montre un de ses deux avantages. On obtient exactement le même résultat avec ceci:
<?php function maFonctionTordue($capture){ return $txt; } ?>
C'est plus clair il me semble. Plus de valses de guillemets! Ici, la fonction callback "invoque" une fonction perso et lui passe implicitement les captures, en argument, sous forme de tableau. Ce tableau est récupéré dans la fonction en question et les différentes captures sont alors simplement accessibles par leur indice.
L'autre avantage de preg_replace_callback() est qu'elle est souvent plus rapide à l'exécution. Dans cet exemple, environ 3 fois plus rapide.
Son seul désavantage à mes yeux est qu'on ne peut passer d'autres arguments que les captures. Si, pour les besoins de l'application, il est nécessaire de passer d'autres arguments à la fonction perso, on peut soit déclarer ces arguments en GLOBAL au niveau de la fonction callback, soit il faut en revenir à preg_replace() avec l'option e mais cette fois de manière, disons adaptée...
Voici une illustration du premier cas. Les deux arguments passés à la fonction appelée sont de simples balises <b> et </b>
<?php function maFonctionTordue2($capture){ return $txt; } $autreArgument1 = '<b>'; $autreArgument2 = '</b>'; ?>
Si, pour des raisons de cohésion de portée de variable, on répugne à utiliser GLOBAL, il faut en revenir à preg_replace() avec l'option e mais, cette fois, adaptée pour que la manipulation des captures soit simplifiée:
<?php function maFonctionTordue2($capture1, $capture2, $capture3, $autreArgument1, $autreArgument2){ return $txt; } $autreArgument1 = '<b>'; $autreArgument2 = '</b>'; echo preg_replace('#(\w+)([^\w]*)(\w+)#e','maFonctionTordue2("\1","\2","\3", "$autreArgument1", "$autreArgument2")' , $txt); ?>
Les captures sont passées comme de simples arguments à la fonction perso afin d'y être manipulées plus simplement. Dans cette manière hybride, on gagne en clarté dans la manipulation des captures tout en ayant la possibilité de passer des arguments supplémentaires à la fonction invoquée.
Dans les deux cas, ce code retournera:
PHP etroppus AUSSI sed EXPRESSIONS sellennoitar COMPATIBLES lreP, AVEC l'EXTENSION ERCP FUNCTIONS. seC FONCTIONS tnetroppus DES sehcrehcer NON-sednamruog, DES snoitressa, DES suos-MASQUES slennoitidnoc ET etuot UNE emmag DE sétilannoitcnof ABSENTES sed EXPRESSIONS xisop
Astuce: on peut pour également passer des tableaux en argument comme ceci:
<?php function maFonctionTordue2($capture1, $capture2, $capture3, $autreArgument){ return $txt; } echo preg_replace('#(\w+)([^\w]*)(\w+)#e','maFonctionTordue2("\1","\2","\3", $autreArgument)' , $txt); ?>
Les classes de caractères et types génériques
Comme pour les POSIX, il est possible de définir des classes de caractères. La notion de classe de caractères est bien connue. Quelques rappels:
| Classe | Description |
| [a-p] | défini tout alphabétique compris entre a et p. |
| [^a-p] | tout caractère qui n'est pas compris dans la fourchette a-p |
| [a-p0-6] | toutes les lettres de a à p et chiffres de 0 à 6 |
Les méta classes POSIX [:classe:] sont acceptées au sein de classes PCRE. Mais PCRE ajoute quelques classes supplémentaires appelées types génériques.
| Types génériques PCRE | |
| \d | tout caractère décimal |
| \D | tout caractère qui n'est pas un caractère décimal |
| \s | tout caractère blanc |
| \S | tout caractère qui n'est pas un caractère blanc |
| \w | tout caractère de "mot" [a-z0-9] plus les accentués |
| \W | tout caractère qui n'est pas un caractère de "mot" |
Les méta POSIX comme [:alpha:] ne sont acceptées que si elles sont intégrées dans une classe de caractères comme, par exemple, dans preg_match('#[0-5[:alpha:]]#', $txt, $out) .
Située en dehors d'une classe, le moteur regex renverra l'erreur Compilation failed: POSIX named classes are supported only within a class.
On pourrait remplacer cette classe POSIX par '#[0-5a-z]#' . Mais il y a une différence: selon le setlocale(), la classe méta [:alpha:] comprendra les accentués alors que [a-z] ne les comprend pas. On pourrait mettre le type générique \w (qui comprend les accentués) comme dans: '#[0-5\w]#' mais alors tous les numériques seront également compris. Et le filtrage 0-5 ne marchera pas. A méditer.
Un petit rappel des méta classes POSIX s'impose donc
| Posix | Description | Pcre | Commentaire |
| [:digit:] | décimal | \d | pas de différence |
| [:alnum:] | tout caractère alphanumérique | \w | pas de différence |
| [:word:] | tout caractère de mot | \w | identique à [:alnum:] |
| [:alpha:] | tout caractère alphabétique | [a-zA-Z] | pas d'accentués dans PCRE |
| [:blank:] | espace et TAB | \s | \s comprend également le retour ligne |
| [:punct:] | tout caractère imprimable sauf lettres et chiffres | \W | idem [:punct:] plus l'espace |
| [:print:] | tout caractère imprimable (octal supérieur à 037) | [^\000-\037] | pas de différence |
| [:space:] | espace, H.tab, V.tab, retour ligne | \s | \s ne comprend pas le vertical tab |
| [:upper:] | tout alphabétique majuscule | [A-Z] | |
| [:lower:] | tout alphabétique minuscule | [a-z] | [a-z] ne comprend pas les accentués] |
Un petit mot important, en passant. On a vu plus haut que les classes POSIX [:alpha:] ou PCRE \w comprennent les accentués mais seulement si le bon jeu de caractères est installé sur le serveur, sinon, un setlocale(LC_CTYPE, 'fr_FR.ISO-8859-1') devrait résoudre le problème.
Pour les mordus des specs, voir le détail des différences dans les pages man de la librairie C du moteur PCRE (mise à jour par l'Université de Cambridge). Attention que l'implémentation de cette librairie dans PHP présente parfois de très légères différences.
Les assertions PCRE
La bibliothèque PCRE permet l'utilisation d'assertions simples qui présentent la particularité de ne pas consommer de caractères. Un peu comme les ancrages ^ et $.
| Assertions simples | |
| \b | limite de mot |
| \B | pas limite de mot |
| \A | début de la chaîne sujet (indépendant du mode multi-lignes) |
| \Z | fin de la chaîne sujet ou nouvelle ligne à la fin de la chaîne sujet (indépendant du mode multi-lignes) |
| \z | fin de la chaîne sujet (indépendant du mode multi-lignes) |
Pour bien comprendre la particularité de non-consommation de caractères de ce type d'assertion, voici un exemple détaillé. Imaginons le texte:
Voici simplement un simple exemple, très simple, mais pas simplet sur les assertions PCRE pourtant pas si simples!
Je souhaite en extraire tous les mots simple ou simples. Pour éviter d'extraire le 'simple' de simplet ou de simplement, je décide d'encadrer ce mot recherché avec des espaces comme par exemple dans ce motif: preg_match_all('# simples? #', $txt, $out). La concordance (match), contenue dans le tableau de résultat $out[0] ne renverra qu'un seul 'simple', le seul qui ne soit pas suivi de ponctuation:
Voici simplement un simple exemple, très simple, mais pas simplet sur les assertions PCRE pourtant pas si simples!
Raté! Bon, on va alors rajouter la ponctuation dans une classe de caractères preg_match_all('# simples?[ ,!.;:]#', $txt, $out) pour trouver une concordance sur les simple ou simples suivis d'une ponctuation éventuelle. Essayons:
Voici simplement un simple exemple, très simple, mais pas simplet sur les assertions PCRE pourtant pas si simples!
Voilà qui est mieux. Mais les concordances (matches) sont encadrées soit par des espaces, soit par de la ponctuation. Pas de problème, on va capturer ce qui nous intéresse avec des parenthèses: preg_match_all('# (simples?)[ ,!.;:]#', $txt, $out). Voici ce que ça donne:
Voici simplement un simple exemple, très simple, mais pas simplet sur les assertions PCRE pourtant pas si simples!
On y est presque. Mais est-ce vraiment nécessaire de consommer de la mémoire avec des parenthèses capturantes (voir la discussion sur les parenthèses non capturantes)? C'est ici que l'utilisation de l'assertion simple \b va nous être utile
Essayons ceci: preg_match_all('#\bsimples?\b#', $txt, $out)
Voici simplement un simple exemple, très simple, mais pas simplet sur les assertions PCRE pourtant pas si simples!
Et voilà le travail. Cette dernière version est plus rapide que la version capturante. Et dont le tableau de résultat $out est plus facile à utiliser puisqu'il comporte deux fois moins d'éléments. Voir ici une démo de tout ceci. Essayez aussi l'option "timing" dans le simulateur.
Pensez aux assertions simples comme des indicateurs de position dans la chaîne cible. Cet indice de position ne pointera pas sur un caractère mais bien entre deux caractères.
Les options
Contrairement aux POSIX il est possible de modifier le comportement d'une fonction PCRE grâce aux options. Celles-ci se placent derrière le délimiteur de fin de motif. Voici les options les plus utiles:
- i insensible à la casse
- s le point (dot) reconnaît le retour ligne
- m multilignes
- e évaluation de code php dans le groupe de remplacement
- U ungreedy, pour rendre les quantificateurs non gourmands
- x Pour commenter les motifs
- autres A, D, S, X (moins courantes)
i - Insensible à la casse
C'est l'option la plus facile à comprendre...et à expliquer.
preg_replace('#les?#i', '***', $txt) remplacera toutes les occurrences de 'le', 'les', 'LE' ou 'LES' par '***'. Tout simplement.
Voici l'occasion de présenter une particularité de syntaxe souvent ignorée. Il est possible de d'activer ou de désactiver, à la demande, une option sur une partie de regex. Un exemple vaut mieux qu'un long discours:
Dans la regex : preg_match_all('#concor(?-i)dance#i', $txt, $out), l'option i est activée pour l'ensemble du motif de manière classique, derrière le délimiteur, et désactivée ponctuellement pour une partie du motif grâce à l'utilisation de l'"interrupteur" d'option (?-i). Voici le résultat:
Cette regex doit matcher concordance et CONCORdance, mais pas concorDANCE ou CONCORDAncE
Inversement, on peut bien sûr activer et désactiver une option à la demande. Le motif '#con(?i)cord(?-i)ance#' retournera ceci:
Cette regex doit matcher concordance et conCORDance, mais pas CONcordDANCE ou conCORDANCE
s - Le dot comprend le retour ligne
Le dot remplace n'importe quel caractère. Même s'il est souvent préférable d'utiliser des classes de caractères négatives (voir plus loin), l'utilisation du dot est fort répandue. Mais seulement, le dot ne prendra pas le retour ligne. Et un motif utilisant le dot risque de ne pas retourner le résultat escompté.
Petit exemple pour capturer l'url de ceci: <a href="http://url.com">lien</a>
La regex la plus utilisée pourrait ressembler à ceci: preg_match('#<a href="(.*?)">#', $txt, $out) Le résultat est conforme aux attentes:
Petit exemple pour capturer l'url de ceci: <a href="http://url.com">lien</a>
Mais imaginons qu'il y ait un retour ligne \r\n (windows) ou \n (linux) dans le texte.
Petit exemple pour capturer l'url de ceci: <a href="http://
url.com">lien</a>
Le parser html d'un navigateur n'y verra que du feu, mais pas la regex! Le dot s'arrêtera au retour ligne et n'ira pas plus loin. Elle ne trouvera donc ni le ", ni le > pourtant nécessaires pour qu'il y aie concordance! Elle ne trouvera aucune occurrence du motif et le tableau de résultat $out restera désespérément vide!
Solution : rajouter l'option s derrière le délimiteur preg_match('#<a href="(.*?)">#s', $txt, $out) et l'affaire est dans le sac... et l'occurrence dans le tableau $out[0]!
Mais, il y a un moyen plus simple et plus efficace de contourner le problème du dot et du retour ligne. Simplement en remplaçant le '(.*?)'-->(.*?) par ([^"]*), une classe de négation de caractères qui prendra tout caractère (même le retour ligne) à l'exception du ".(démo)
retour menu des options
m - Multiligne
L'utilisation des ancrages ^ (début de chaîne) et $ (fin de chaîne) accélèrent considérablement le traitement d'une regex (voir partie astuce) mais quand on a une chaîne qui comprend des retours lignes (comme dans un fichier plat de données) l'ancrage de début ^ se trouvera au début de la première ligne de ce fichier et l'ancrage de fin $ après la toute dernière lettre de la dernière ligne. Imaginons que je recherche l'adresse (rue et numéro) de tous les "HADDOCK" du fichier plat suivant (j'y ai rajouté les ancrages afin d'illustrer mon propos):
^000028;Mr;Chevalier;HADDOCK;rue de Rackam Le Rouge;201;
000020;Mr;Marc;DUPONT;Avenue Roosens;253;
000021;Mme;Lucienne;MARTINO;rue Joseph Berger;25;
000022;Mr;Aucoin;DUBOIS;avenue des Genêts;546;
000024;Mme;Marie;DUPOND;rue de Dinant;58;
000025;Mr;Reporter;TINTIN;clos des Sorbiers;111;
000026;Mr;Lechien;MILOU;rue du Chenil;215;
000027;Mr;Typhon;TOURNESOL;boulevard Transgénique;484;
000028;Mr;Archibald;HADDOCK;rue Whiskey;201;
000029;Mr;Marc;DUCHEMIN;clos de la Route;9;
000030;Mr;Philippe;DELAROUTE;chemin d'en Haut;254;$
Je pourrais utiliser le motif '#^\d+;[^;]+;[^;]+;HADDOCK;([^;]+);([^;]+);.*#'. L'ancrage ^ me permet d'écrire ce motif de manière simple, en décomposant tous les champs d'une ligne de fichier, séparés par un point-virgule. En voici le résultat:
000028;Mr;Chevalier;HADDOCK;rue de Rackam Le Rouge;201;
000020;Mr;Marc;DUPONT;Avenue Roosens;253;
000021;Mme;Lucienne;MARTINO;rue Joseph Berger;25;
000022;Mr;Aucoin;DUBOIS;avenue des Genêts;546;
000024;Mme;Marie;DUPOND;rue de Dinant;58;
000025;Mr;Reporter;TINTIN;clos des Sorbiers;111;
000026;Mr;Lechien;MILOU;rue du Chenil;215;
000027;Mr;Typhon;TOURNESOL;boulevard Transgénique;484;
000028;Mr;Archibald;HADDOCK;rue Whiskey;201;
000029;Mr;Marc;DUCHEMIN;clos de la Route;9;
000030;Mr;Philippe;DELAROUTE;chemin d'en Haut;254;
La première ligne a bien été trouvée, mais pas l'autre ligne, plus loin dans le fichier! Tonnerre de Brest!
C'est ici que l'option m intervient.
L'option m (PCRE MULTILINE) demande au moteur regex de considérer chaque retour ligne comme la fin de chaîne et le début de la suivante. Un peu comme si ce moteur voyait le fichier plat comme ceci:
^000028;Mr;Chevalier;HADDOCK;rue de Rackam Le Rouge;201;$
^000020;Mr;Marc;DUPONT;Avenue Roosens;253;$
^000021;Mme;Lucienne;MARTINO;rue Joseph Berger;25;$
^000022;Mr;Aucoin;DUBOIS;avenue des Genêts;546;$
De cette manière, avec l'option m, le motif '#^\d+;[^;]+;[^;]+;HADDOCK;([^;]+);([^;]+);.*#m' donnera:
000028;Mr;Chevalier;HADDOCK;rue de Rackam Le Rouge;201;
000020;Mr;Marc;DUPONT;Avenue Roosens;253;
000021;Mme;Lucienne;MARTINO;rue Joseph Berger;25;
000022;Mr;Aucoin;DUBOIS;avenue des Genêts;546;
000024;Mme;Marie;DUPOND;rue de Dinant;58;
000025;Mr;Reporter;TINTIN;clos des Sorbiers;111;
000026;Mr;Lechien;MILOU;rue du Chenil;215;
000027;Mr;Typhon;TOURNESOL;boulevard Transgénique;484;
000028;Mr;Archibald;HADDOCK;rue Whiskey;201;
000029;Mr;Marc;DUCHEMIN;clos de la Route;9;
000030;Mr;Philippe;DELAROUTE;chemin d'en Haut;254;
Et voilà, toutes les lignes contenant le mot 'HADDOCK' sont trouvées!
Ne vous privez pas d'utiliser les ancrages sur les fichiers plats (avec l'option m s'il y a plusieurs lignes). Ils accélèrent sensiblement le traitement. Le même motif sans l'ancrage ^ donne le résultat escompté mais est plus de 4 fois plus lent sur cet exemple! Alors imaginez sur un fichier de plusieurs milliers de lignes! Démo.
Note: On voit souvent cette option dans des motifs sans ancrage. C'est inutile et sans effet. C'est comme mettre l'option s derrière un motif qui n'a pas de dot ou mettre un parachute pour faire du pédalo!
retour menu des options
e - Evaluation de code PHP
Cette option a été abordée plus haut dans le cadre des fonctions du type "callback" . Elle n'est utilisée que pour la fonction preg_replace(), retour menu des options
U - Option non gourmande
On a vu plus haut comment rendre un ou plusieurs quantificateur non-gourmand dans une regex. L'option U permet de rendre tous les quantificateurs d'une regex non gourmands. Ces deux motifs sont parfaitement identiques :
- '#<a href="(.*?)">(.*?)</a>#'
- '#<a href="(.*)">(.*)</a>#U'
Maintenant, imaginons que vous ayez une loooongue série de quantificateurs et que tous doivent être non-gourmands, sauf un, utilisez l'option globale U pour l'ensemble du motif, et les "interrupteurs" d'options décrits plus haut pour désactiver cette option sur une portion du motif.
retour menu des options
Option x
Le texte de la doc dit:
"Avec cette option, les caractères d'espacement sont ignorés, sauf lorsqu'ils sont échappés, ou à l'intérieur d'une classe de caractères, et tous les caractères entre # non échappés et en dehors d'une classe de caractères, et le prochain caractère de nouvelle ligne sont ignorés. C'est l'équivalent Perl de l'option /x : elle permet l'ajout de commentaires dans les masques compliqués."
Tout est dit. En gros, cette option introduite dans Perl 5 par Larry Wall, lui-même inspiré par une note de Jeffrey Friedl, permet d'écrire des motifs complexes de manière indentée et, éventuellement, commentée. Bref, ça permet d'écrire des motifs de manière plus lisible. Sachant que les regex sont plus faciles à écrire qu'à (re)lire, ce n'est pas un luxe!
Ainsi, ce motif :
<?php $motif = '/^\d+;[^;]+;[^;]+;HADDOCK;([^;]+);([^;]+);.*/m'; ?>
peut s'écrire de manière plus lisible:
<?php $motif = " / # délimiteur (ici je n' ai pas pris #, réservé pour les commentaires) ^ # ancrage de début de chaîne \d+; # tout décimal 1 fois ou plus, suivi par ; [^;]+; # tout caractère sauf ; 1 fois ou plus, suivi par ; [^;]+; # idem HADDOCK; # les caractères HADDOCK suivi par ; ([^;]+); # capture tout caractère sauf ; 1 fois ou plus, suivi par ; ([^;]+); # idem .* # tout caractère sauf retour ligne 0 fois ou plus /xm"; ?>
Attention! Les espaces du motif seront ignorés. Si vous devez y mettre un espace ou un # il FAUT les échapper sinon ils ne seront pas interprétés!
retour menu des options
Autres options
Ces options sont moins utilisées. Je reprends donc la doc in extenso:
- A (PCRE_ANCHORED) Avec cette option, le masque est ancré de force, c'est-à-dire que le masque doit s'appliquer juste au début de la chaîne sujet pour être considéré comme trouvé. Il est possible de réaliser le même effet en ajoutant les méta-caractères adéquats, ce qui est la seule manière de le faire en Perl.
- D (PCRE_DOLLAR_ENDONLY) Avec cette option, le méta-caractère $ ne sera valable qu'à la fin de la chaîne sujet. Sans cette option, $ est aussi valable avant une nouvelle ligne, si cette dernière est le dernier caractère de la chaîne. Cette option est ignorée si l'option m est activée. Il n'y a pas d'équivalent en Perl.
- S Lorsqu'un masque est utilisé plusieurs fois, cela vaut la peine de passer quelques instants de plus pour l'analyser et optimiser le code pour accélérer les traitements ultérieurs. Cette option force cette analyse plus poussée. Actuellement, cette analyse n'est utile que pour les masques non ancrés, qui ne commencent pas par un caractère fixe.
- X (PCRE_EXTRA) Cette option ajoute d'autres fonctionnalités incompatibles avec le PCRE de Perl. Tous les anti-slash suivis d'une lettre qui n'aurait pas de signification particulière cause une erreur, permettant la réservation de ces combinaisons pour des ajouts fonctionnels ultérieurs. Par défaut, comme en Perl, les anti-slash suivis d'une lettre sans signification particulière sont traités comme des valeurs littérales. Actuellement, cette option ne déclenche pas d'autres fonctions.
- u (PCRE8) Cette option désactive les fonctionnalités additionnelles de PCRE qui ne sont pas compatibles avec Perl. Les chaînes sont traitées comme des chaînes UTF-8. Cette option est disponible en PHP 4.1.0 et plus récent sur plate-forme Unix et en PHP 4.2.3 et plus récent sur plate-forme Windows.
Les assertions
On a vu plus haut les assertions simples simples (telle que \b) qui ne consomment pas de caractères. Vous vous rappelez qu'il convenait de les voir comme un pointeur qui se trouverait entre deux caractères et pas sur un caractère. Ils ne cherchent pas une concordance de caractère mais bien de position. Et bien, les assertions font exactement la même chose: elles se placent entre deux caractères et testent les caractères suivants (lookahead) ou précédents (lookbehind). Il y en a 4:
| Types d'assertion | Motif | Succès si le motif dans l'assertion... |
| Les assertions arrières positives (positive lookbehind) | (?<=motif) | ...trouve une concordance à gauche |
| Les assertions arrières négatives (negative lookbehind) | (?<!motif) | ...ne trouve pas de concordance à gauche |
| Les assertions avant positives (positive lookahead) | (?=motif) | ...trouve une concordance à droite |
| Les assertions avant négatives (negative lookahead) | (?!motif) | ...ne trouve pas de concordance à droite |
Bon, c'est très bien, mais à quoi ça peut servir ces trucs là? Imaginons, une fois encore, que l'on veuille extraire du texte suivant le déterminant de, mais uniquement s'il est suivi du mot caractère
L'implémentation des assertions arrières déplace temporairement le pointeur de position vers l'arrière, et cherche à vérifier l'assertion. Si le nombre de caractères est différent, la position ne sera pas correcte, et l'assertion échouera. La combinaison d'assertions arrières avec des sous-masques peut être particulièrement pratique à fin des chaînes. Un exemple est donné à la fin de cette section.
Essayons ceci preg_match_all('#\bde\b(?=\scaractères)#', $txt, $out);. Ce motif placera son pointeur juste après le e du mot de (mais avant le caractère suivant, un espace) et vérifiera si ce qui suit est bien caractère (avec un espace \s).
Résultat:
L'implémentation des assertions arrières déplace temporairement le pointeur de position vers l'arrière, et cherche à vérifier l'assertion. Si le nombre de caractères est différent, la position ne sera pas correcte, et l'assertion échouera. La combinaison d'assertions arrières avec des sous-masques peut être particulièrement pratique à fin des chaînes. Un exemple est donné à la fin de cette section.
On pourrait objecter qu'on aurait pu obtenir la même chose avec preg_match_all('#\b(de)\b\scaractères#', $txt, $out) où le résultat sera capturé dans $out[1][0]. Même résultat en effet mais, plus gourmand (plus lent qu'avec le lookahead! Voyez cette démo).
Mais imaginons maintenant que l'on cherche tous les de SAUF ceux qui sont suivis par le mot caractères. Là on ne s'en tirera pas sans une assertion, négative cette fois.
La fonction suivante php preg_match_all('#\bde\b(?!\scaractères)#', $txt, $out) placera à nouveau son pointeur après le e du mot de et vérifiera, cette fois, si ce qui suit N'EST PAS la chaîne caractère (avec un espace \s).
L'implémentation des assertions arrières déplace temporairement le pointeur de position vers l'arrière, et cherche à vérifier l'assertion. Si le nombre de caractères est différent, la position ne sera pas correcte, et l'assertion échouera. La combinaison d'assertions arrières avec des sous-masques peut être particulièrement pratique à fin des chaînes. Un exemple est donné à la fin de cette section.
Je suppose que vous avez compris l'intérêt des assertions. Les assertions arrières fonctionnent exactement de la même manière mais... dans l'autre direction.
Cherchons dans le même texte, par exemple, toutes les occurrences de assertion ou assertions sauf celles précédées de l'.
La fonction: preg_match_all("#(?<!l')assertions?#", $txt, $out) devrait marcher. Essayez vous-même dans le testeur pour en voir le résultat.
Les masques conditionnels
On aborde ici une structure un peu plus complexe. Qui fait appel, entre-autres, aux assertions (avant ou arrières, positives ou négatives). De plus, la doc PHP en français est assez mal traduite.
Le prototype d'un sous-masque conditionnel (conditional sub-pattern) est le suivant
(?(condition) masque_si_vrai | masque_sinon).
Il s'agit en fait d'une structure de contrôle du type if-then-else. Lorsque nous avons deux masques possibles, l'évaluation de la condition déterminera l'utilisation de l'un ou de l'autre.
La condition est soit une assertion soit un décimal se référant à une référence arrière (capture).
Rien ne vaut un exemple. Nous avons une entête de courriel dans laquelle on souhaite capturer les différents blocs to/from/subject/:
To: destinataire@example.com (commentaire inutile à ne pas capturer)
From: moi@example.net (commentaire encore plus inutile)
Subject: Ces regex commencent sérieusement à me gonfler!
La regex qui tombe sous le sens pourrait ressembler à ceci (avec l'option x pour commenter):
<?php $txt=" To: destinataire@example.com (commentaire inutile) From: me@example.net (commentaire encore plus inutile) Subject: Ces regex commencent sérieusement à me gonfler! "; $motif = '/^(From|To|Subject):\s(.*)/m'; // le même motif avec commentaires (option x) $motif='/ ^(From|To|Subject) # début de chaîne (ligne puisque option m) suivi par From ou To ou Subject :\s # suivi par un : et un espace (.*) # suivi par la capture de tout caractère sauf retour ligne # option m multiligne et x pour les commentaires /xm' ; ?>
Retournera:
To: destinataire@example.com (commentaire inutile)
From: me@example.net (commentaire encore plus inutile)
Subject: Ces regex commencent sérieusement à me gonfler!
Damned! Il a capturé les "commentaires inutiles"! Il faudrait, idéalement, appliquer un masque spécifique pour capturer l'adresse email, et pas plus loin. Par exemple un masque d'email comme "#\w+@\w+\.[a-z]+#" capturera bien toute adresse email. Mais il ne capturera rien dans la ligne Subject car il n'y a aucune adresse email. Comment faire?
Vous aurez deviné que c'est ici que les sous-masques conditionnels interviennent.
<?php $motif = '/^((From|To)|Subject): ((?(2)\w+@\w+\.[a-z]+|.+))/m'; // le même motif, éclaté, avec commentaires (option x) $motif='/ ^ # ancrage début de chaîne (ligne puisque option m) ((From|To)|Subject):\s # soit From ou To (capturé en \2) soit Subject suivi par : et espace ( # paranthèse capturante (capture \3) (?(2) # if (From ou To) (2 est la référence arrière à la capture plus haut) \w+@\w+\.[a-z]+ # then toute chaine alphanum suivie par @ suivie par aplphanum # suivie par un point et une chaîne alpha | # else .+) # tout caractère une ou plusieurs fois ) # fin capture \3 et options m multiline et x pour commentaires /xm' ; ?>
Retournera
To: destinataire@example.com (commentaire inutile)
From: me@example.net (commentaire encore plus inutile)
Subject: Ces regex commencent sérieusement à me gonfler!
Une extraction du tableau $out permettra de réassortir les couples To/From -> adresse email et Subject -> texte sujet.
Les données binaires
Je n'ai jamais dû l'utiliser à ce jour mais la possibilité de comparer des données binaires existe. On peut introduire des caractères binaires dans des motifs de masques. Ces caractères peuvent s'introduire de deux manières:
- Par octal: \ddd où d est un décimal. (attention à la confusion avec les références arrières \1 \2 etc...)
- En hexa : \xhh où hh est le code hexa
Pour plus de détails, il y a une description détaillée sur les binaires dans la section "Non-printing characters" dans les pages man.
Astuces et optimisation
Une regex peut s'écrire de nombreuses manières différentes. Certains motifs seront beaucoup plus efficaces que d'autres. S'il s'agit de valider une courte chaîne, pas de souci. La différence de vitesse d'exécution ne se verra presque pas. Quoique, une mico seconde par-ci, une nano seconde par-là...(chanson connue!). Mais sur des fichiers volumineux, une regex mal écrite pourra même mettre un serveur à genoux!
Le moteur regex interne dispose de puissantes routines d'optimisation qui vont d'abord analyser votre motif avant de se lancer dans le traitement proprement dit, mais on peut toujours lui donner un sérieux coup de main en soignant ses motifs dès le départ.
Comment faire pour les optimiser?
- Utilisez le dot avec parcimonie
- Supprimez les parenthèses capturantes inutiles
- Optimisez les alternatives.
- Scindez vos regex trop complexes
- Supprimez les options inutiles
- Choisissez les fonctions callback appropriées
- Ancrez vos motifs
- Testez et chronométrez
Utilisez le dot avec parcimonie
On est souvent tenté d'utiliser le dot qui prend tout caractère sauf le retour ligne. Ce qui impose au moteur regex un nombre très important de combinaisons possibles (puisque le dot prend tout). On a vu, plus haut, qu'utiliser une classe de négation de caractères en lieu et place du dot était beaucoup plus efficace. Ne vous en privez pas. En plus, ça vous évitera de tomber dans le piège du dot qui s'arrête à un éventuel retour ligne. Enfin, il n'est pas nécessaire de rendre le quantificateur d'une telle classe non-gourmand. Le quantificateur d'une classe de négation de caractères s'arrêtera toujours au premier caractère ("négativé") rencontré.
Par contre, si votre motif commence par un .* (trèèès mauvaise idée ça, surtout s'il n'y a pas d'ancrage ou de lettres fixes en début de motif!), le moteur regex va pédaler dans la choucroute. Voyez:
On recherche une occurrence d'une chaîne se terminant par mot recherché dans un texte de deux lignes:
première ligne suivie par un retour ligne
et mot recherché
Si on cherche le mot cible avec le motif #(.*)mot recherché# impose au moteur regex un grand nombre d'allers-retours dans la chaîne avant de trouver une concordance. Il essayera d'abord toutes les combinaisons possibles sur la première ligne. Il ne trouvera rien. Ensuite seulement, il attaquera la seconde ligne où il trouvera le motif recherché.
Si, maintenant on lui met l'option s #(.*)mot recherché#s, on l'aide en diminuant sensiblement le nombre de backtracking. Il ira directement jusqu'à la fin de la deuxième ligne. Commencera son backtracking et trouvera tout de suite le motif recherché.
Conséquence : le deuxième motif est à peu près 75% plus rapide. Démo.
Donc, attention au dot. Mal utilisé il pourra planter votre regex!
retour menu astuces
Supprimez les parenthèses capturantes inutiles
On a vu plus haut que si vous utilisez les parenthèses pour délimiter une alternative, rendez-les non capturantes si vous n'avez pas besoin de les capturer. Ainsi #(le|la|les|un|une)\s(\w+)# qui cherche à capturer tous les mots ((\w+)) sera 30% plus rapide si vous faites #(?:le|la|les|un|une)\s(\w+)#.
Vous soulagerez le traitement de votre regex et simplifierez également l'utilisation des références arrières et le contenu du tableau de résultats.
retour menu astuces
Optimisez vos alternatives
Tout d'abord, si possible, remplacez les alternatives par des classes. Il est plus efficace d'écrire [akpi] plutôt que (?:a|k|p|i). De la même manière il vaut mieux faire (?:les?|des?|aux?) que (?:le|les|de|des|au|aux)
Enfin, sachez que lorsque le moteur regex aborde une alternative il prendra les alternatives une par une en commençant par la première. Dés qu'il trouve une concordance, il s'arrêtera là et n'analysera pas les autres alternatives. Si vous commencez l'alternative par le choix le plus probable, votre moteur regex à s'arrêtera plus tôt. Exemple:
Pour accélérer le traitement de l'expression régulière, ordonnez vos alternatives !
Le motif non ordonné #(?:le|la|les|de|du|des|au|aux|mes|tes|ses|nos|vos) alternatives# sera plus lent que si vous placiez le choix le plus probable devant: #(?:vos|la|les|de|du|des|au|aux|mes|tes|ses|nos) alternatives# (démo)
retour menu astuces
Scindez vos regex trop complexes
Ne faites pas de regex trop compliquées, avec de nombreux niveaux d'alternatives. Il est souvent plus efficace de faire plusieurs petites regex plutôt qu'une longue. Songez au moteur regex qui doit analyser toutes les combinaisons possibles d'occurrence d'un masque complexe. Et ce nombre de combinaisons augmente de manière exponentielle avec la complexité du motif.
Exemple : vous cherchez à savoir si le nom d'un jour de semaine (Lundi, Mardi etc...) se trouve dans un fichier. Vous serez tenté de faire ceci:
<?php // tous les noms de semaine avec un séparateur de mot (assertion simple \b) $mois = '#\b(?:Lundi|Mardi|Mercredi|Jeudi|Vendredi|Samedi|Dimanche)\b#'; } ?>
C'est propre, c'est net. Mais est-ce efficace? On pourrait essayer de travailler en boucle, en testant chaque nom de jour séparément et en quittant la boucle dès qu'un de ceux-ci est trouvé. Exactement le même mécanisme qu'une alternative regex. Essayons ceci:
<?php // tableau avec un motif par jour de semaine '#\bVendredi\b#', '#\bSamedi\b#', '#\bDimanche\b#'); // boucle sur les jours de la semaine et teste si ce jour se trouve dans le fichier foreach ($mois as $v){ break; } } ?>
C'est un peu plus compliqué que de le faire en une seule passe, mais tellement plus efficace! Près de 10 fois plus rapide!
Et il y a encore certainement moyen d'améliorer la boucle avec une structure de contrôle while() plutôt que la structure foreach() - break que j'ai utilisée pour clarifier l'exemple.
retour menu astuces
Supprimez les options inutiles
Il est inutile de demander au moteur de regex de chercher des possibilités de concordances superflues. Comprenez bien l'utilité des options et supprimez les si elles ne sont pas strictement nécessaires. Pas d'option s s'il n'y a pas de dot dans le motif, ou d'option m s'il n'y a pas d'ancrage. L'impact d'un "nettoyage" d'option sur la performance n'est pas énorme mais ça aide parfois. Et puis ces options inutiles font un peu désordre...
retour menu astuces
Choisissez la fonction callback appropriée
Pour rappel, dans la plupart des cas, la fonction preg_replace_callback() sera plus rapide que preg_replace() avec l'option e. A tester au cas par cas. Voir la discussion sur ces fonctions.
retour menu astuces
Ancrez vos motifs
Si votre motif n'est pas ancré (^ ou $), ou s'il ne commence pas par un caractère fixe, le moteur de regex va devoir stocker toute une série d'états intermédiaires et devoir faire un sérieux nombre de retours sur ces états (backtracking). Si vous fixez le début ou/et la fin d'une chaîne vous diminuerez sensiblement ce backtracking inutile. Vous rappelez-vous l'exemple du Capitaine Haddock plus haut et sa discussion sur l'utilité des ancrages?
L'autre manière de faire est d'utiliser l'option S (majuscule) qui lancera une optimisation du motif avant que le moteur ne commence la recherche proprement dite. Faites l'essai de cette option dans le simulateur-testeur sur le motif 2. Vous verrez qu'il fonctionnera deux fois plus rapidement avec cette option. Le gain en vitesse d'exécution est moins marqué que si vous aviez utilisé les ancrages, mais c'est déjà ça! Démo.
retour menu astuces
Testez!
Après avoir utilisé toutes les astuces ci-dessus, mettez-les à l'épreuve sur un fichier grandeur nature et chronométrez! Utilisez ce petit simulateur de regex en mode timing. Ou bien faites-vous une petite fonction de chronométrage et exécutez votre regex dans une boucle (quelques centaines au maximum) pour lisser les résultats. Allez, en passsant, une petite fonction de chronométrage.
<?php function microtime_float(){ return ((float)$usec + (float)$sec); } // début timing(0) Fin: timing() function timing($v = 1){ static $start; if ($v == 0){ $start = microtime_float(); }else{ $time = (microtime_float() - $start); return $time ; } } timing(0); // // votre code à tester // timing(); ?>
4- Conclusion
Voilà. Il y a encore tellement, tellement à dire sur ce sujet... J'espère que ce petit survol pourra contribuer à une meilleure utilisation des regex dans vos projets.
Les expressions régulières PCRE sont peut-être rébarbatives d'un premier abord mais quelle puissance!
Alors, commencez sur de petites expressions simples et, lancez-vous! Elles vous le rendront bien!
5- Liens et bibliographie
Testeur-simulateur de regex on line
Testeur gratuit à télécharger Regex Coach
Historique des expressions rationnelles Wikipedia
Mastering Regular Expressions Jeffrey Friedl isbn 0-596-00289-0
Tutoriel détaillé sur les fonctions regex de PHP (format PDF par Nicolas Chambrier)
6- A propos de ce tutorial
Tuto et testeur par Jean-Luc Lacroix (ripat) qui accueillera vos commentaires et/ou critiques avec plaisir. Surtout si vous avez de bonnes idées pour illustrer l'une ou l'autre fonctionnalité sympa des PCRE. Merci à Nicolas Chambrier (Naholyr) pour sa relecture et ses conseils.
Dernière mise à jour du tutorial : 10/04/2005

Commentaires
1. Le lundi 2 mai 2005 à 13:19, par Cyrano
2. Le jeudi 5 mai 2005 à 20:46, par Pouille
3. Le vendredi 6 mai 2005 à 08:15, par ripat
4. Le samedi 7 mai 2005 à 15:54, par Bodman
5. Le jeudi 26 mai 2005 à 11:44, par ripat
6. Le dimanche 5 juin 2005 à 13:23, par HyWaN
7. Le dimanche 5 juin 2005 à 23:25, par Damien
8. Le dimanche 17 juillet 2005 à 23:25, par oracle
9. Le lundi 5 décembre 2005 à 18:25, par JF Duflot
10. Le mardi 25 avril 2006 à 11:23, par Tdv
11. Le jeudi 8 juin 2006 à 18:23, par Zaw
12. Le samedi 8 juillet 2006 à 16:11, par seb
13. Le jeudi 11 octobre 2007 à 08:34, par Pierre Ballinger
14. Le jeudi 11 octobre 2007 à 08:43, par Charlie
15. Le vendredi 14 mars 2008 à 08:58, par ahtme.kk@mail.ee
16. Le lundi 24 mars 2008 à 05:32, par lolo888
Ajouter un commentaire