23 octobre 2022

Lookahead et Lookbehind

Parfois nous avons juste besoin de trouver les motifs précédents ou suivant un autre motif.

Il existe pour cela des syntaxes spéciales, appelées “lookahead” et “lookbehind”, ensemble désignées par “lookaround”.

Pour commencer, trouvons le prix à partir d’une chaîne de caractères comme sujet:1 dindes coûte 30€.C’est un nombre suivi par le signe sujet:€

Lookahead

La syntaxe est: X(?=Y), cela veut dire "recherche X, mais renvoie une correspondance seulement si il est suivi de Y". Tous les motifs peuvent être utilisés au lieu de X et Y.

Pour un nombre entier suivi de sujet:€, l’expression régulière sera \d+(?=€):

let str = "1 dinde coûte 30€";

alert( str.match(/\d+(?=€)/) ); // 30, le nombre 1 est ignoré, vu qu'il n'est pas suivi de €

NB: Le lookahead est seulement un test, le contenu de la parenthèse (?=...) n’est pas include dans le resultat 30.

Quand nous recherchons X(?=Y), le moteur d’expressions régulières trouve X et verifie s’il y a Y immediatemment après. Si ce n’est pas le cas, la correspondqnce possible est ignoré, et la recherche continue.

Des tests plus complexes sont possibles, ex: X(?=Y)(?=Z) signifie:

  1. TrouveX.
  2. Verifier si Y est immédiatement après X (ignorer sinon).
  3. Verifier si Z se situe aussi immédiatement après X (ignorer sinon)…
  4. Si les deux tests sont réussis, alors le motif X correspond, sinon continuer à chercher.

En d’autres mots, ce genre de motif signifie que nous recherchons X suivi de Y et Z en meme temps

C’est possible seulement si Y et Z ne s’excluent pas mututellement.

Par exemple, \d+(?=\s)(?=.*30) recherche \d+ suivi du motif (?=\s), et il y a 30 quelque part apres lui (?=.*30):

let str = "1 dinde coute 30€";

alert( str.match(/\d+(?=\s)(?=.*30)/) ); // 1

Dans notre chaîne de caractères cela correspond exactement au nombre 1.

Lookahead negatif

Supposons que nous recherchons plutôt une quantité, non un prix, a partir de la même chaîne de caractères.

Pour cela, le loopahead negatif peut etre utilisé.

La syntaxe est: X(?!Y), cela veut dire X, mais seulement si il n’est pas suivi de Y".

let str = "2 turkeys cost 60€";

alert( str.match(/\d+\b(?!€)/g) ); // 2 (le prix ne correspond pas au motif)

Lookbehind

Compatibilité des navigateurs pour Lookbehind

Veuillez noter : Lookbehind n’est pas pris en charge dans les navigateurs non-V8, tels que Safari, Internet Explorer.

Lookahead permet d’ajouter une condition sur “ce qui suit”.

Lookbehind est similaire a loopahead, mais il regarde derrière.Ça veut dire qu’il établit une correspondance seulement si il y a quelquechose avant lui,

La syntaxe est:

  • Lookbehind positif: (?<=Y)X, correspond à X, mais seulement si il y a Y avant lui.
  • Lookbehind negatif: (?<=Y)X, correspond à X, mais seulement si il n’y a pas Y avant lui.

Pqr exemple,changeons le prix en dollars US. Le signe dollar est généralement placé avant le chiffre,donc pour recupérer $30 nous utiliserons(?<=\$)\d+ – Une quantité précédé de $:

let str = "1 turkey costs $30";

// le signe dollar est echappé \$
alert( str.match(/(?<=\$)\d+/) ); // 30 (ignore le nombre sans dollar)

Et si nous avons besoin d’une quantité – un nombre non précédé de $, alors nous pouvons utiliser un lookbehind négatif (?<!\$)\d+:

let str = "2 dndes coûte $60";

alert( str.match(/(?<!\$)\b\d+/g) ); // 2 (le prix ne correspond pas )

Groupes capturants

Généralement, le contenu d’une parenthese de lookaround ne fait partie des resultats.

Par exmple dans le motif \d+(?=€), le signe n’est pas capture comme une partie de la corredpondance. C’est naturel: nous recherchons un nombre \d+, tandis que (?=€) iest juste un test qui doit etre suivi de .

Mais dans certains cas, nous voulons capturer l’expression du lookaround aussi, comme une partie de la correspondance.C’est possible.Il suffit juste de le l’entourer d’une parenthese supplementaire.

Dans l’exemple suivant, le signe de la monnaie est capture, en meme temps aue la quqntite.

let str = "1 turkey costs 30€";
let regexp = /\d+(?=(€|kr))/; // parentheses supplemetaires autour de  €|kr

alert( str.match(regexp) ); // 30, €

Et voila le meme chose pour lookbehind:

let str = "1 turkey costs $30";
let regexp = /(?<=(\$|£))\d+/;

alert( str.match(regexp) ); // 30, $

Summary

Lookahead et lookbehind (ensemble désignés sous le nom de looparound) sont utiles quand nous voulons identifier quelquechose selon le contexte avant/après lui.

Pour les expressions regulières simple, nous pouvons une chose similaire manuellement: considerer tous les elemets, dans tous les contextes et alors filtrer par contexte en boucle

Vous vous souvenez que str.match (sans le drapeau g) et str.matchAll retournent (toujours) les correspondances comme des tableaux avec une propriete index, et donc nous connaissons où exactement il est et nous pouvons verifier le contexte

Mais generalement lookaround est plus adapté.

Types de lookaround:

motif type correspondances
X(?=Y) Lookahead positif X si il est suivi de Y
X(?!Y) Lookahead négatif X si il n’est pas suivi deY
(?<=Y)X Lookbehind positif X s’il suit Y
(?<!Y)X Lookbehind négatif X s’il ne suit pas Y

Exercices

Il y a une chaîne de nombres entiers.

Créez une expression régulière qui ne recherche que les expressions non négatives (zéro est autorisé).

Un exemple d’utilisation :

let regexp = /your regexp/g;

let str = "0 12 -5 123 -18";

alert( str.match(regexp) ); // 0, 12, 123

L’expression régulière pour un nombre entier est \d+.

Nous pouvons exclure les négatifs en les faisant précéder du lookbehind négatif : (?<!-)\d+.

Bien que, si nous l’essayons maintenant, nous remarquerons peut-être un autre résultat “supplémentaire”:

let regexp = /(?<!-)\d+/g;

let str = "0 12 -5 123 -18";

console.log( str.match(regexp) ); // 0, 12, 123, 8

Comme vous pouvez le voir, il correspond à 8, à partir de -18. Pour l’exclure, nous devons nous assurer que l’expression régulière commence à correspondre à un nombre qui ne se trouve pas au milieu d’un autre nombre (non correspondant).

Nous pouvons le faire en spécifiant un autre lookbehind négatif : (?<!-)(?<!\d)\d+. Maintenant, (?<!\d) garantit qu’une correspondance ne commence pas après un autre chiffre, juste ce dont nous avons besoin.

Nous pouvons également les joindre en un seul lookbehind ici:

let regexp = /(?<![-\d])\d+/g;

let str = "0 12 -5 123 -18";

alert( str.match(regexp) ); // 0, 12, 123

Nous avons une chaîne avec un document HTML.

Écrivez une expression régulière qui insère <h1>Hello</h1> immédiatement après la balise <body>. La balise peut avoir des attributs.

Par exemple:

let regexp = /your regular expression/;

let str = `
<html>
  <body style="height: 200px">
  ...
  </body>
</html>
`;

str = str.replace(regexp, `<h1>Hello</h1>`);

Après cela, la valeur de str devrait être :

<html>
  <body style="height: 200px"><h1>Hello</h1>
  ...
  </body>
</html>

Pour insérer après la balise <body>, nous devons d’abord la trouver. Nous pouvons utiliser le modèle d’expression régulière <body.*?> pour cela.

Dans cette tâche, nous n’avons pas besoin de modifier la balise <body>. Nous n’avons qu’à ajouter le texte après.

Voici comment nous pouvons le faire :

let str = '...<body style="...">...';
str = str.replace(/<body.*?>/, '$&<h1>Hello</h1>');

alert(str); // ...<body style="..."><h1>Hello</h1>...

Dans la chaîne de remplacement, $& signifie la correspondance elle-même, c’est-à-dire la partie du texte source qui correspond à <body.*?>. Il est remplacé par lui-même suivi de <h1>Hello</h1>.

Une alternative consiste à utiliser lookbehind :

let str = '...<body style="...">...';
str = str.replace(/(?<=<body.*?>)/, `<h1>Hello</h1>`);

alert(str); // ...<body style="..."><h1>Hello</h1>...

Comme vous pouvez le voir, il n’y a qu’une partie lookbehind dans cette expression régulière.

Cela fonctionne comme ceci :

  • À chaque position dans le texte.
  • Vérifiez s’il est précédé de <body.*?>.
  • Si c’est le cas, nous avons le match.

La balise <body.*?> ne sera pas renvoyée. Le résultat de cette expression régulière est littéralement une chaîne vide, mais elle ne correspond qu’aux positions précédées de <body.*?>.

Il remplace donc la “ligne vide”, précédée de <body.*?>, par <h1>Hello</h1>. C’est l’insertion après <body>.

PS Les drapeaux d’expression régulière, tels que s et i peuvent également être utiles : /<body.*?>/si. Le drapeau s fait correspondre le point . à un caractère de retour à la ligne, et le drapeau i fait que <body> correspond également à <BODY> insensible à la casse.

Carte du tutoriel

Commentaires

lire ceci avant de commenter…
  • Si vous avez des améliorations à suggérer, merci de soumettre une issue GitHub ou une pull request au lieu de commenter.
  • Si vous ne comprenez pas quelque chose dans l'article, merci de préciser.
  • Pour insérer quelques bouts de code, utilisez la balise <code>, pour plusieurs lignes – enveloppez-les avec la balise <pre>, pour plus de 10 lignes - utilisez une sandbox (plnkr, jsbin, codepen…)