Assertions, expressions régulières, lookaround, lookahead, lookbehind

Look around avec deux yeux sur fond d'une île perdue dans l'océan

Assertion ?

En français, une assertion est une proposition, de forme affirmative ou négative, qu’on avance et que l’on donne comme vraie. En mathématiques, l’assertion 1 + 1 = 2 est vraie dans l’ensemble des nombres entiers relatifs Ensemble des nombres  relatifs, mais devient fausse dans l’ensemble des nombres entiers relatifs modulo 2 : \mathbb{Z} /2 \mathbb{Z} , c’est alors 1 + 1 = 0 qui devient vraie.

Dans les expressions rationnelles, on utilise fréquemment deux assertions simples : ^ indique le début d’une chaîne, et $ indique la fin. En RegExp, on peut dire qu’une assertion est une condition indiquée à un endroit de la recherche et peut agir sur les éléments qui précèdent ou qui suivent cet endroit, peut être positive (vraie) ou négative (fausse), et ne capture ni ne remplace aucun caractère.

Assertions simples

  • \b indique le début ou la fin d’un mot, une limite de mot,
  • \B Indique ce qui n’est pas une limite de mot,
  • \A indique le début d’une chaîne, comme ^ mais indépendamment du mode multi-ligne,
  • \Z et \z indiquent la fin d’une chaîne independamment du mode multi-ligne. \z indique la toute fin de la chaîne, \Z matche avant les derniers retours à la ligne \n.

Le mode multi-ligne est indiqué par le modificateur /m après l’expression régulière ou (?m) dans le motif. En faisant cela, ^ indiquera le début de chaque ligne, correspondant à la position de début de la chaîne et les positions après chaque retour à la ligne \n.

Un 1er exemple avec l’assertion \b

On considère la chaîne suivante, en recherchant bon ou bons :

C'est bon, les bons mots sont abonbant, bonshommes, rebonsoir, rebondir, bonbons, jambon.

Le motif pourrait être : /bons?/, ? après le s indique que le s peut être présent ou pas. preg_match indiquera le premier bon ou bons sur lequel il tombe. Avec le motif /bons?/, preg_match_all renvoie dans son troisième argument le tableau ci-après :

Si l’on compte bien, les 9 occurences de bon ou bons sont présentes, bonbons matche 2 fois pour bon et bons.

Si l’on veut sortir seulement le mot bon ou bons sans qu’il ressorte par exemple dans rebondir ou rebonsoir, on utilise l’assertion \b de limite de mot, comme cela on pourra sortir de la chaîne l’occurence bon avec la virgule après, bons dans les bons mots. Le motif est alors /\bbons?\b/.

On aurait pu se dire que le motif /\s(bons?)[\s,]/ pouvait convenir. Une parenthèse capturante pour bon ou bons, précédé d’un espace \s, suivi d’un espace \s ou d’une virgule , entre crochets. Et là, ça le fait. Mais la parenthèse capturante utilise des ressources, la sortie $matches est alors :

Si l’on enlève les parenthèses capturantes /\sbons?[\s,]/, l’expression va capturer également la virgule ,, et oblige à énumérer tous les caractères de ponctuation que l’on pourrait trouver à la fin ou au début du mot.

L’assertion \b ne consomme pas de mémoire et est surtout 3 à 4 fois plus rapide que les autres syntaxes. Une assertion simple peut être considérée comme un pointeur qui se trouverait juste entre 2 caractères. Une assertion simple cherche à vérifier une position.

Assertions lookaround

Les assertions lookahead et lookbehind, qui sont toutes deux des assertions lookaround analysent les caractères précédents lookahead, ou suivants lookbehind.

AssertionCodeRésultat
Assertion positive avant
positive lookahead
(?=motif)Vrai si le motif est vérifié
Assertion négative avant
negative lookahead
(?!motif)Vrai si le motif échoue
Assertion positive arrière
positive lookbehind
(?<=motif)Vrai si le motif est vérifié
Assertion négative arrière
negative lookabehind
(?<!motif)Vrai si le motif échoue

Attention au sens

Le pointeur se déplace dans la chaîne de gauche à droite.

Assertion positive avant ?= lookahead le pointeur se déplace vers l’avant, il se déplace donc derrière la recherche en cours.

Assertion positive arrière ?<= lookbehind le pointeur se déplace vers l’arrière, il se déplace donc devant la recherche en cours.

Lookahead et lookbehind ne sont pas aussi similaires que leurs noms l’indiquent. L’assertion lookahead fonctionne exactement de la même manière que s’il s’agissait d’une expression régulière normale, sauf qu’elle est ancrée à la position de correspondance en cours et qu’elle ne capture pas ce qu’elle matche. Lookbehind a un autre fonctionnement : à partir de la position de correspondance en cours, elle recule dans le texte un caractère à la fois, en essayant de faire correspondre son expression à chaque position. Dans les cas où aucune correspondance n’est possible, lookbehind doit aller jusqu’au début du texte un caractère à la fois avant d’abandonner. Comparez cela à lookahead, qui est appliquée exactement une fois. Il s’agit d’une simplification excessive, bien sûr. La manière dont lookbehind est appliqué est fondamentalement différente et beaucoup moins rapide que lookahead. Avec tout cela, le motif d’un lookaround doit être fixe, et cela est souvent obligatoire par le code (php, …) qui pourra émettre un avertissement ou bien une erreur.

Exemples d’assertions lookaround

Les exemples se font sur la chaîne $string. Bien-sûr, pour la démonstration, certains mots sont inexistants en langue française.

C'est bon, les bons mots sont abondant, bonshommes, rebonsoir, rebondir, terrebonnien, rebons, terrebons, jambon.

Assertion positive avant positive lookahead ?=

On cherche à matcher bon ou bons suivis seulement par une autre lettre. On pourra les capturer dans abondant, bonshommes, rebonsoir, rebondir, terrebonnien mais pas dans bon, bons, rebons, terrebons et jambon. Le motif est alors bons?(?=[a-rt-z]). On inclut seulement les lettres minuscules. Pour aussi inclure les lettres majuscules bons?(?=[A-Za-rt-z]).

Assertion négative avant negative lookahead ?!

Avec l’assertion précedente, on exclut l’espace, la virgule ,, le s et le point . : bons?(?![\s,s.]). On aura donc aussi les caractères unicodes tels que les caractères accentués. À noter que le point à l’extérieur des crochets matche n’importe quel caractère. Pour identifier un point, il faut donc l’échapper \.. À l’intérieur des crochets, pour l’énumération des caractères, il matche le point lui-même et ce n’est pas la peine de l’échapper.

Assertion positive arrière positive lookbehind ?<=

On cherche à matcher bon ou bons dans rebonsoir, rebondir, terrebonnien, rebons et terrebons donc il faut qu’il soit précédé de re. Le motif est alors (?<=re)bons?.

Assertion négative arrière negative lookbehind ?<!

On cherche à matcher bon ou bons partout sauf dans rebonsoir, rebondir, rebons donc il ne faut pas qu’il soit précédé de espace + re, on veut aussi le matcher dans terrebonnien et terrebons. Le motif est alors (?<!\sre)bons?.

Exemple pratique

On cherche tous les hooks do_action('hook') dans un template PHP dans le style de WordPress. Prenons un fichier template.php qui contiendrait les lignes suivantes :

On cherche les hooks hook1, hook3 mais pas hook2 car il est commenté entre les balises <?php et ?>. On pense déjà à un negative lookbehind sur les 2 caractères de commentaires en ligne //. On suppose que les commentaires sont inline, c’est à dire sur la même ligne. On écrit donc un motif tel que /^[^\/]*(?<!\/\/)do_action\(\s*'(\S+?)'\s*\).+$/m en multi-ligne (le motif est suivi du modificateur m). Ce n’est peut-être pas la meilleure solution, mais cela fonctionne assez bien sur ce cas de figure, en supposant que les slashs de commentaire ne sont suivis que par des espaces et qu’il n’y a pas d’autres slashs avant sur la même ligne.

Il y a pas mal de caractères d’échappement \ car l’usage est d’utiliser / comme délimiteur d’expression régulière. Avec un autre caractère délimiteur, on peut réduire l’utilisation du caractère d’échappement : ~^[^/]*(?<!//)do_action\(\s*'(\S+?)'\s*\).+$~m. Il faut en plus en rajouter pour les simples quotes en php si l’on met la chaîne entre quote. On peut penser également que le hook risque d’être entre des doubles quotes ", dans ce cas on remplace les quotes ' par ['"].

Les hooks recherchés sont dans le tableau $matches[1].

Et hop, on peut plonger dans les expressions régulières et utiliser les puissantes assertions lookaround.

Soumettre un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Ce site utilise Akismet pour réduire les indésirables.