24 août 2021

Éléments précédents et éléments suivants

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

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

There’s a string of integer numbers.

Create a regexp that looks for only non-negative ones (zero is allowed).

An example of use:

let regexp = /your regexp/g;

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

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

The regexp for an integer number is \d+.

We can exclude negatives by prepending it with the negative lookbehind: (?<!-)\d+.

Although, if we try it now, we may notice one more “extra” result:

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

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

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

As you can see, it matches 8, from -18. To exclude it, we need to ensure that the regexp starts matching a number not from the middle of another (non-matching) number.

We can do it by specifying another negative lookbehind: (?<!-)(?<!\d)\d+. Now (?<!\d) ensures that a match does not start after another digit, just what we need.

We can also join them into a single lookbehind here:

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

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

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

We have a string with an HTML Document.

Write a regular expression that inserts <h1>Hello</h1> immediately after <body> tag. The tag may have attributes.

For instance:

let regexp = /your regular expression/;

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

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

After that the value of str should be:

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

In order to insert after the <body> tag, we must first find it. We can use the regular expression pattern <body.*?> for that.

In this task we don’t need to modify the <body> tag. We only need to add the text after it.

Here’s how we can do it:

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

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

In the replacement string $& means the match itself, that is, the part of the source text that corresponds to <body.*?>. It gets replaced by itself plus <h1>Hello</h1>.

An alternative is to use lookbehind:

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

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

As you can see, there’s only lookbehind part in this regexp.

It works like this:

  • At every position in the text.
  • Check if it’s preceeded by <body.*?>.
  • If it’s so then we have the match.

The tag <body.*?> won’t be returned. The result of this regexp is literally an empty string, but it matches only at positions preceeded by <body.*?>.

So it replaces the “empty line”, preceeded by <body.*?>, with <h1>Hello</h1>. That’s the insertion after <body>.

P.S. Regexp flags, such as s and i can also be useful: /<body.*?>/si. The s flag makes the dot . match a newline character, and i flag makes <body> also match <BODY> case-insensitively.

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…)