Fermer

février 8, 2019

Nouvelles fonctionnalités de JavaScript qui vont changer votre façon d'écrire Regex


À propos de l'auteur

Faraz est une développeur, consultante et rédactrice frontale professionnelle qui se passionne pour le développement du Web et la promotion de modèles et d'idées qui…
Plus d'informations sur Faraz

Si vous avez déjà effectué du traitement de texte sophistiqué et des manipulations en JavaScript, vous apprécierez les nouvelles fonctionnalités introduites dans ES2018. Dans cet article, nous examinons en détail comment la neuvième édition de la norme améliore la capacité de traitement de texte de JavaScript.

Il existe une bonne raison pour que la majorité des langages de programmation prennent en charge les expressions régulières: ce sont des outils extrêmement puissants pour manipuler du texte. Les tâches de traitement de texte nécessitant des dizaines de lignes de code peuvent souvent être accomplies avec une seule ligne de code d'expression régulière. Bien que les fonctions intégrées dans la plupart des langues suffisent généralement pour effectuer des opérations de recherche et de remplacement sur les chaînes, des opérations plus complexes, telles que la validation des entrées de texte, nécessitent souvent l'utilisation d'expressions régulières.

Les expressions régulières font partie du code JavaScript. depuis la troisième édition de la norme ECMAScript, introduite en 1999. ECMAScript 2018 (ou ES2018 en abrégé) est la neuvième édition de la norme et améliore encore la capacité de traitement de texte de JavaScript en introduisant quatre nouvelles fonctionnalités:

. les nouvelles fonctionnalités sont expliquées en détail dans les sous-sections qui suivent.

Débogage de JavaScript

console.log peut vous en dire beaucoup sur votre application, mais elle ne peut pas réellement déboguer votre code. Pour cela, vous avez besoin d’un débogueur JavaScript complet. En savoir plus →

Assertions Lookbehind

La possibilité de faire correspondre une séquence de caractères en fonction de ce qui précède ou précédent permet de supprimer des correspondances potentiellement indésirables. . Ceci est particulièrement important lorsque vous devez traiter une chaîne de grande taille et que le risque de correspondances indésirables est élevé. Heureusement, la plupart des types d’expression classiques fournissent les assertions d’avant-garde et d’avant-garde.

Avant ES2018, seules les assertions d’avant-garde étaient disponibles en JavaScript. Un lookahead vous permet d'affirmer qu'un motif est immédiatement suivi d'un autre motif.

Il existe deux versions des assertions d'anticipation: positive et négative. La syntaxe pour une anticipation positive est (? = ...) . Par exemple, l'expression régulière / Item (? = 10) / ne correspond à Item que si elle est suivie, avec un espace intermédiaire, du numéro 10:

 const re = / Item (? = 10) /;

console.log (re.exec ('Item'));
// → null

console.log (re.exec ('Item5'));
// → null

console.log (re.exec ('Item 5'));
// → null

console.log (re.exec ('Item 10'));
// → ["Item", index: 0, input: "Item 10", groups: undefined]

Ce code utilise la méthode exec () pour rechercher une correspondance dans une chaîne. Si une correspondance est trouvée, exec () renvoie un tableau dont le premier élément est la chaîne correspondante. La propriété index du tableau contient l'index de la chaîne correspondante et la propriété input contient l'intégralité de la chaîne sur laquelle la recherche a été effectuée. Enfin, si des groupes de capture nommés sont utilisés dans l'expression régulière, ils sont placés sur la propriété groups . Dans ce cas, les groupes ont une valeur de indéfinie car il n'y a pas de groupe de capture nommé.

La construction d'un préfixe négatif est (?! ...) . Une perspective négative indique qu'un motif n'est pas suivi d'un motif spécifique. Par exemple, le modèle / Red (?! Head) / ne correspond à Red que s'il n'est pas suivi de head :

 const re = / Red ( ?!tête)/;

console.log (re.exec ('Redhead'));
// → null

console.log (re.exec ('Redberry'));
// → ["Red", index: 0, input: "Redberry", groups: undefined]

console.log (re.exec ('Redjay'));
// → ["Red", index: 0, input: "Redjay", groups: undefined]

console.log (re.exec ('Red'));
// → ["Red", index: 0, input: "Red", groups: undefined]

ES2018 complète les affirmations d'anticipation en intégrant les affirmations d'anticipation à JavaScript. Notée par (? <= ...) une assertion d'analyse rétrospective ne vous permet de faire correspondre un modèle que s'il est précédé d'un autre modèle.

Supposons que vous deviez récupérer le prix d'un produit dans euro sans saisir le symbole de l’euro. En regardant en arrière, cette tâche devient beaucoup plus simple:

 const re = /(?<==)d+(.d*)?/;

console.log (re.exec ('199'));
// → null

console.log (re.exec ('199 $'));
// → null

console.log (re.exec ('199 €'));
// → ["199", undefined, index: 1, input: "€199", groups: undefined]

Note : Les assertions d'anticipation et d'atténuation du regard sont souvent désignées par le terme de «anomalies».

La version négative de l'observation est désignée par (? <! .. .) et vous permet de faire correspondre un modèle qui n'est pas précédé du modèle spécifié dans la recherche. Par exemple, l'expression régulière / (? <! D {3}) mètres / correspond au mot "mètres" si trois chiffres ne se trouvent pas avant elle:

 const re = / (? < !  d {3}) metres /;

console.log (re.exec ('10 mètres '));
// → [" meters", index: 2, input: "10 meters", groups: undefined]

console.log (re.exec ('100 mètres'));
// → null

Comme pour les antécédents, vous pouvez utiliser plusieurs antécédents (négatifs ou positifs) pour créer un motif plus complexe. Voici un exemple:

 const re = / (? <=  D {2}) (? <! 35) mètres /;

console.log (re.exec ('35 mètres '));
// → null

console.log (re.exec ('mètres'));
// → null

console.log (re.exec ('4 mètres'));
// → null

console.log (re.exec ('14 mètres '));
// → ["meters", index: 2, input: "14 meters", groups: undefined]

Cette expression rationnelle ne fait correspondre une chaîne contenant des mètres que si elle est immédiatement précédée de deux chiffres autres que 35. La recherche positive garantit que le motif est précédé de deux chiffres, puis la recherche négative garantit que les chiffres ne sont pas 35.

Groupes de capture nommés

Vous pouvez grouper une partie d'une expression régulière en encapsulant les caractères entre parenthèses. Cela vous permet de limiter l'alternance à une partie du motif ou d'appliquer un quantificateur à l'ensemble du groupe. De plus, vous pouvez extraire la valeur correspondante entre parenthèses pour un traitement ultérieur.

Le code suivant donne un exemple de recherche d'un nom de fichier avec l'extension .jpg dans une chaîne, puis extrait le nom du fichier:

 const re = /(w+).jpg/;
const str = 'Nom du fichier: cat.jpg';
const match = re.exec (str);
const nomFichier = match [1];

// Le deuxième élément du tableau résultant contient la partie de la chaîne qui correspond aux parenthèses
console.log (match);
// → ["cat.jpg", "cat", index: 11, input: "File name: cat.jpg", groups: undefined]

console.log (NomFichier);
// → chat

Dans des modèles plus complexes, le fait de référencer un groupe à l'aide d'un nombre ne fait que compliquer davantage la syntaxe des expressions rationnelles, déjà cryptée. Par exemple, supposons que vous souhaitiez faire correspondre une date. Comme la position du jour et du mois est permutée dans certaines régions, il est difficile de savoir quel groupe se réfère au mois et quel groupe se rapporte au jour:

 const re = / ( d {4}) - ( d {2 }) - ( d {2}) /;
const match = re.exec ('2020-03-04');

console.log (match [0]); // → 2020-03-04
console.log (match [1]); // → 2020
console.log (match [2]); // → 03
console.log (match [3]); // → 04

La solution de ES2018 à ce problème est appelée groupes de capture, qui utilisent une syntaxe plus expressive sous la forme de (? ...) :

 const re = / (?   d {4}) - (?   d {2}) - (?   d {2}) /;
const match = re.exec ('2020-03-04');

console.log (match.groups); // → {année: "2020", mois: "03", jour: "04"}
console.log (match.groups.year); // → 2020
console.log (match.groups.month); // → 03
console.log (match.groups.day); // → 04

Etant donné que l'objet résultant peut contenir une propriété portant le même nom qu'un groupe nommé, tous les groupes nommés sont définis sous un objet séparé appelé groupes .

Une construction similaire existe dans de nombreux nouveaux et traditionnels langages de programmation. Python, par exemple, utilise la syntaxe (? P ) pour les groupes nommés. Sans surprise, Perl prend en charge les groupes nommés avec une syntaxe identique à JavaScript (JavaScript a imité sa syntaxe d'expression régulière de Perl). Java utilise également la même syntaxe que Perl.

En plus de pouvoir accéder à un groupe nommé via l'objet groups vous pouvez accéder à un groupe à l'aide d'une référence numérotée, similaire à un groupe de capture classique:

 const re = / (?   d {4}) - (?   d {2}) - (?   d {2}) /;
const match = re.exec ('2020-03-04');

console.log (match [0]); // → 2020-03-04
console.log (match [1]); // → 2020
console.log (match [2]); // → 03
console.log (match [3]); // → 04

La nouvelle syntaxe fonctionne également bien avec la tâche de déstructuration:

 const re = / (?   d {4}) - (?   d {2}) - (?   d {2}) /;
const [match, year, month, day] = re.exec ('2020-03-04');

console.log (match); // → 2020-03-04
console.log (année); // → 2020
console.log (mois); // → 03
console.log (jour); // → 04

L'objet groups est toujours créé, même si aucun groupe nommé n'existe dans une expression régulière:

 const re = /  d + /;
const match = re.exec ('123');

console.log ('groupes' en correspondance); // → true

Si un groupe nommé facultatif ne participe pas au match, l'objet groups aura toujours une propriété pour ce groupe mais la propriété aura une valeur de undefined : [19659016] const re = / d + (? st | nd | rd | th)? /;

let match = re.exec ('2nd');

console.log ('ordinal' dans match.groups); // → true
console.log (match.groups.ordinal); // → nd

match = re.exec ('2');

console.log ('ordinal' dans match.groups); // → true
console.log (match.groups.ordinal); // → non défini

Vous pouvez faire référence à un groupe capturé régulier plus tard dans le modèle avec une référence arrière sous la forme de 1 . Par exemple, le code suivant utilise un groupe de capture qui correspond à deux lettres dans une ligne, puis le rappelle ultérieurement dans le modèle:

 console.log (/ ( w  w)  1 / .test ('abab') ) // → true

// si les deux dernières lettres ne sont pas identiques
// comme les deux premiers, le match échouera
console.log (/ ( w  w)  1 / .test ('abcd')); // → faux

Pour rappeler un groupe de capture nommé plus tard dans le modèle, vous pouvez utiliser la syntaxe / k / . Voici un exemple:

 const re = /  b (?   w +)  s +  k   b /;

const match = re.exec ("Je ne suis pas paresseux, je suis en mode d'économie d'énergie");

console.log (match.index); // → 18
console.log (match [0]); // → on on

Cette expression régulière trouve des mots dupliqués consécutifs dans une phrase. Si vous préférez, vous pouvez également rappeler un groupe de capture nommé en utilisant une référence numérotée:

 const re = /  b (?   w +)  s +  1  b /;

const match = re.exec ("Je ne suis pas paresseux, je suis en mode d'économie d'énergie");

console.log (match.index); // → 18
console.log (match [0]); // → on on

Il est également possible d’utiliser à la fois une référence numérotée et une référence nommée:

 const re = / (?   d):  1:  k  /;

correspondance constante = re.exec ('5: 5: 5');

console.log (match [0]); // → 5: 5: 5

Comme pour les groupes de capture numérotés, des groupes de capture nommés peuvent être insérés dans la valeur de remplacement de la méthode replace () [). Pour ce faire, vous devrez utiliser la construction $ . Par exemple:

 const str = 'War & Peace';

console.log (str.replace (/ (Guerre) et (Paix) /, '2 $ & 1 $'));
// → Paix et guerre

console.log (str.replace (/ (?  War) & (?  Peace) / / '$  & $ ')));
// → Paix et guerre

Si vous souhaitez utiliser une fonction pour effectuer le remplacement, vous pouvez référencer les groupes nommés de la même manière que vous référeriez des groupes numérotés. La valeur du premier groupe de capture sera disponible en tant que deuxième argument de la fonction et la valeur du deuxième groupe de capture sera disponible en tant que troisième argument:

 const str = 'War & Peace';

const result = str.replace (/ (?  War) & (?  Peace) /, fonction (match, groupe1, groupe2, offset, chaîne) {
    return group2 + '&' + group1;
});

console.log (résultat); // → Paix et guerre

s ( dotAll ) Indicateur

Par défaut, le métacaractère point (. ) dans un motif regex correspond à tout caractère à l'exception des caractères de saut de ligne, incluant saut de ligne ( n ) et retour chariot ( ):

 console.log (/./. test (' n')); // → faux
console.log (/./. test (' r')); // → faux

Malgré cette lacune, les développeurs JavaScript pouvaient toujours faire correspondre tous les caractères en utilisant deux classes de caractères abrégées opposées, telles que [wW]qui indique au moteur des expressions rationnelles de correspondre à un caractère qui est un caractère mot ( w ) un caractère autre qu'un mot ( W ):

 console.log (/ [wW] /. test (' n')); // → true
console.log (/ [wW] /. test (' r')); // → true

ES2018 vise à résoudre ce problème en introduisant le drapeau s ( dotAll ). Lorsque cet indicateur est défini, le comportement du métacaractère point (. ) est modifié pour correspondre également aux caractères de fin de ligne:

 console.log (/./ s.test (' n') ) // → true
console.log (/./ s.test (' r')); // → true

Le drapeau s peut être utilisé sur une base régulière et ne rompt donc pas les schémas existants qui reposent sur l'ancien comportement du métacaractère à points. Outre JavaScript, le drapeau s est disponible dans un certain nombre de langues telles que Perl et PHP.

Lectures recommandées : Introduction au dessin animé abrégé Introduction à WebAssembly

Unicode Property Escapes

Parmi les nouvelles fonctionnalités introduites dans ES2015, citons la prise de conscience Unicode. Cependant, les classes de caractères abrégées ne pouvaient toujours pas correspondre aux caractères Unicode, même si l'indicateur u était activé.

Considérez l'exemple suivant:

 const str = '';

console.log (/  d / .test (str)); // → faux
console.log (/  d / u.test (str)); // → faux

? est considéré comme un chiffre, mais d ne peut correspondre qu'à ASCII [0-9]. Le test de () renvoie false . Parce que changer le comportement des classes de caractères abrégées briserait les modèles d'expression régulière existants, il a été décidé d'introduire un nouveau type de séquence d'échappement.

Dans ES2018, la propriété Unicode s'échappe, indiquée par p {...} [psont disponibles dans les expressions régulières lorsque le drapeau u est activé. Maintenant, pour faire correspondre un numéro Unicode, vous pouvez simplement utiliser p {Number} comme indiqué ci-dessous:

 const str = '';
console.log (/  p {Number} /u.test (str)); // → true

Et pour faire correspondre un caractère alphabétique Unicode, vous pouvez utiliser p {Alphabétique} :

 const str = '';

console.log (/  p {Alphabétique} /u.test (str)); // → true

// le raccourci  w ne peut pas correspondre à
console.log (/  w / u.test (str)); // → faux

P {...} est la version annulée de p {...} et correspond à tout caractère qui p {...} ne le fait pas:

 console.log (/  P {Number} /u.test ('?')); // → faux
console.log (/  P {Number} /u.test ('漢')); // → true

console.log (/  P {Alphabétique} /u.test ('?')); // → true
console.log (/  P {Alphabétique} /u.test ('漢')); // → faux

Une liste complète des propriétés prises en charge est disponible dans la proposition de spécification actuelle .

Notez que l'utilisation d'une propriété non prise en charge provoque une SyntaxError :

. ] console.log (/  p {indéfini} /u.test ('漢')); // → SyntaxError

Tableau de compatibilité

Navigateurs de bureau

Chrome Firefox Safari Edge
Les affirmations Lookbehind 62 X X X
Groupes de capture nommés 64 X 11.1 X
s ( dotAll ) drapeau 62 X 11.1 X
Evasions de propriété Unicode 64 X 11.1 X

Navigateurs mobiles

ChromeFor Android FirefoxFor Android Firefox Safari Edge Mobile Samsung Internet Android Webview
Les assertions de regard 62 X X X 8.2 62
Groupes de capture nommés 64 X 11.3 X X 64
s ( dotAll ) Indicateur 62 X X 19659092] 11.3 X 8.2 62
Unicode Pro perty Escapes 64 X 11.3 X X 64

Node.js

  • 8.3.0 (exige de harmonie drapeau d'exécution]
  • 8.10.0 (prise en charge de s ( dotAll ) indicateur et vérification des assertions)
  • 10.0.0 (Support complet) )

Wraping Up

ES2018 poursuit les travaux des éditions précédentes d’ECMAScript en rendant les expressions régulières plus utiles. Les nouvelles fonctionnalités incluent l'affirmation lookbehind, les groupes de capture nommés, le drapeau ( dotAll ) et les propriétés Unicode qui s'échappent. L'assertion Lookbehind vous permet de faire correspondre un modèle uniquement s'il est précédé par un autre modèle. Les groupes de capture nommés utilisent une syntaxe plus expressive que les groupes de capture classiques. Le drapeau s ( dotAll ) modifie le comportement du métacaractère point (. ) pour correspondre aux caractères de saut de ligne. Enfin, les échappements de propriétés Unicode fournissent un nouveau type de séquence d'échappement dans les expressions régulières.

Lors de la création de modèles complexes, il est souvent utile d'utiliser un testeur d'expressions régulières. Un bon testeur fournit une interface pour tester une expression régulière par rapport à une chaîne et affiche chaque étape effectuée par le moteur, ce qui peut s'avérer particulièrement utile lorsque vous essayez de comprendre les modèles écrits par d'autres. Il peut également détecter les erreurs de syntaxe pouvant survenir dans votre motif regex. Regex101 et RegexBuddy sont deux testeurs de regex populaires à vérifier.

Avez-vous d'autres outils à recommander? Partagez-les dans les commentaires!

 Éditorial éclatant (dm, il)




Source link