Regex PHP : lookahead et lookbehind

Accueil Tags Recherche

01 Octobre 2013

Regex PHP : lookahead et lookbehind

Bien le bonjour, amis regexophiles ! Aujourd’hui, je vous explique rapidement comment utiliser les lookahead et les lookbehind dans une regex en PHP.

Le lookahead

Le lookahead, ou assertion avant, est un moyen de vérifier ce qui se trouve derrière votre groupe de capture, avant de décider si vous le capturez ou non. Il existe deux type de lookahead :

  • Le positive lookahead (?=foo) : capture le groupe s’il est suivi par…
  • Le negative lookahead (?!foo) : capture le groupe s’il n’est pas suivi par…

Le symbole $ est un lookahead bien connu, qui vérifie que le groupe de capture n’est suivi par rien d’autre.

Le lookbehind

Le lookbehind, ou assertion arrière, est un moyen de vérifier ce qui se trouve devant votre groupe de capture, avant de décider si vous le capturez ou non. Il existe deux type de lookbehind :

  • Le positive lookbehind (?<=foo) : capture le groupe s’il est précédé par…
  • Le negative lookbehind (?<!foo) : capture le groupe s’il n’est pas précédé par…

Le symbole ^ est un lookbehind bien connu, qui vérifie que le groupe de capture n’est précédé par rien d’autre.

Exemples

Voici ci-dessous quelques exemples

$url = array(
  'root/folder1/',
  'root/folder1/subfolder1/',
  'root/folder1/subfolder2/',
  'root/folder1/subfolder2/action1/',
  'root/folder1/subfolder2/action2/',
  'root/folder2/',
  'root/folder2/subfolder1/',
  'root/folder2/subfolder2/',
);
$reg = array(
  '#/folder1/(?=subfolder2)#', // folder1, suivi de subfolder2
  '#/folder1/(?!subfolder2)#', // folder1, non suivi de subfolder2
  '#(?<=folder1)/subfolder1#', // subfolder1, précédé de folder1
  '#(?<!folder1)/subfolder1#', // subfolder1, non précédé de folder1
);
foreach($reg as $r) {
  echo 'Regex : '.htmlentities($r).'
';
  foreach($url as $u) {
    echo preg_match($r, $u).' - '.$u.'
';
  }
  echo '-----------
';
}
/*
Regex : #/folder1/(?=subfolder2)#
0 - root/folder1/
0 - root/folder1/subfolder1/
1 - root/folder1/subfolder2/
1 - root/folder1/subfolder2/action1/
1 - root/folder1/subfolder2/action2/
0 - root/folder2/
0 - root/folder2/subfolder1/
0 - root/folder2/subfolder2/
-----------
Regex : #/folder1/(?!subfolder2)#
1 - root/folder1/
1 - root/folder1/subfolder1/
0 - root/folder1/subfolder2/
0 - root/folder1/subfolder2/action1/
0 - root/folder1/subfolder2/action2/
0 - root/folder2/
0 - root/folder2/subfolder1/
0 - root/folder2/subfolder2/
-----------
Regex : #(?&lt;=folder1)/subfolder1#
0 - root/folder1/
1 - root/folder1/subfolder1/
0 - root/folder1/subfolder2/
0 - root/folder1/subfolder2/action1/
0 - root/folder1/subfolder2/action2/
0 - root/folder2/
0 - root/folder2/subfolder1/
0 - root/folder2/subfolder2/
-----------
Regex : #(?&lt;!folder1)/subfolder1#
0 - root/folder1/
0 - root/folder1/subfolder1/
0 - root/folder1/subfolder2/
0 - root/folder1/subfolder2/action1/
0 - root/folder1/subfolder2/action2/
0 - root/folder2/
1 - root/folder2/subfolder1/
0 - root/folder2/subfolder2/
-----------
*/

Applications

Les assertions peuvent être utilisées dans de nombreux cas, dont voici quelques exemples :

  • Gérer des exceptions dans vos Regex (comme ci-dessus)
  • Récupérer la dernière occurrence d’un mot : foo(?!.*foo)
  • Valider le niveau de sécurité d’un mot de passe, ici, 8 caractères minimum, dont au moins une majuscule et un chiffre : ^(?=.*[A-Z])(?=.*\d)[\w]{8,}$
  • Récupérer les montants dans un texte (sans récupérer le symbole €) : \d+(?:[.,]\d+)?(?=\s*€)

Limites

Si les assertions vous permettent une plus grande liberté dans l’utilisation de vos regex, il faut être conscient de leurs limites. Elles peuvent en effet devenir assez gourmandes si mal utilisées. Le lookbehind a de plus une particularité, due au fonctionnement des assertions et au déplacement du curseur d’avant en arrière : il doit avoir une taille fixe. Nous avons vu dans les exemples comment trouver la première occurrence d’un mot dans un texte. Si nous adaptions cette regex pour trouver la première occurrence, cela donnerait ce genre de code :

(?<!foo.*)foo

Malheureusement, notre lookbehind n’a pas ici une taille fixe, en résulte une erreur de compilation :

Warning: Compilation failed: lookbehind assertion is not fixed length

Et en Javascript ?

Les lookahead sont aussi supportés en Javascript, mais pas les lookbehind. Il faudra donc parfois retravailler la capture pour supprimer les données inutiles.

Liens

Documentation PHP : PCRE, les assertions SO : Regex lookahead ordering, lien intéressant sur l’endroit où placer une assertion