Chapitre 10: Expressions rationnelles

À diverses occasions dans les chapitres précédents, nous avons dû jeter un coup d’œil aux structures des valeurs de chaînes. Dans le chapitre 4 nous avons extrait des valeurs de chaînes en notant les positions exactes dans lesquelles on peut trouver les nombres qui indiquent une partie de la date. Plus loin, dans le chapitre 6, nous avons vu des bouts de code assez laids destinés à chercher certains types de caractères dans une chaîne, par exemple ceux qui devaient être échappés en HTML.

Les expressions rationnelles constituent un langage qui décrit les structures des chaînes. Il s’agit d’un petit langage spécifique mais qui est inclus dans le JavaScript (comme dans beaucoup d’autres langages de programmation, d’une façon ou d’une autre). Il n’est pas très lisible ― les expressions rationnelles volumineuses finissent par devenir complètement illisibles. C’est pourtant un outil très utile qui peut vraiment simplifier les programmes qui traitent les chaînes.


Tout comme on écrit les chaînes entre guillemets, les expressions rationnelles sont écrites entre des slash(/). Ce qui implique que des slash à l’intérieur de l’expression devront être échappés.

var slash = /\//;
show("AC/DC".search(slash));

La méthode search ressemble à la méthode indexOf, mais elle cherche une expression rationnelle et non une chaîne. Les structures indiquées dans les expressions rationnelles peuvent effectuer quelques petites choses que les chaînes ne peuvent pas faire. Pour commencer elles permettent à certains de leurs éléments de coïncider sur plus d’un seul caractère. Dans le chapitre 6, quand nous avons extrait les balises pour un document, nous avons eu besoin de trouver le premier astérisque ou la première accolade ouvrante dans une chaîne. Nous pouvions faire ainsi :

var asterisqueOuAccoladeOuvrante = /[\{\*]/;
var histoire =
  "Nous avons remarqué le *paresseux géant*, pendu à une énorme branche.";
show(histoire.search(asterisqueOuAccoladeOuvrante));

Les caractères [ et ] ont une signification particulière dans les expressions rationnelles. Lorsqu’ils encadrent d’autres caractères, ils signifient n’importe lequel de ces caractères. Comme la plupart des caractères non alphanumériques ont une signification particulière dans les expressions rationnelles, c’est une bonne idée de les échapper systématiquement avec un antislash1 pour qu’ils soient compris comme de simples caractères.


Il y a quelques raccourcis pour des ensembles de caractères souvent utilisés. Le point (.) peut être utilisé pour n’importe quel caractère autre que le retour chariot, un « d » échappé (\d) signifie un chiffre, un « w » échappé (\w) correspond à n’importe quel caractère alphanumérique (y compris un souligné, pour certaines raisons) et un « s » échappé (\s) est équivalent aux caractères d’espaces (tabulation, retour chariot, espace).

var chiffreEncadreeParDesEspaces = /\s\d\s/;
show("1a 2 3d".search(chiffreEncadreeParDesEspaces));

Les caractères « d », « w » et « s » échappés peuvent être remplacés par la lettre capitale correspondante pour avoir la signification contraire. Par exemple, \S correspond à n’importe quel caractère qui n’est pas un espace blanc. Lorsqu’on utilise [ et ], un motif peut être inversé en commençant par un caractère ^ :

var pasABC = /[^ABC]/;
show("ABCBACCBBADABC".search(pasABC));

Comme vous pouvez le voir, la façon dont les expressions rationnelles utilisent des caractères pour construire des motifs les rend a) très courts, et b) très difficiles à lire.


Ex. 10.1

Écrivez une expression rationnelle qui retrouve une date au format "XX/XX/XXXX", dans laquelle les X sont des chiffres. Essayez sur cette chaîne "Est né le 15/11/2003 (mère, Spot): Croc Blanc".

var motifDate = /\d\d\/\d\d\/\d\d\d\d/;
show("Est né le 15/11/2003 (mère, Spot): Croc Blanc".search(motifDate));

Vous aurez parfois besoin de vous assurer qu’un motif démarre au début d’une chaîne ou s’achève à son extrémité. Pour cela, on peut utiliser les caractères spéciaux ^ et $. Le premier coïncide avec le début de la chaîne, le deuxième avec la fin.

show(/a+/.test("blah"));
show(/^a+$/.test("blah"));

La première expression rationnelle retrouve toute chaîne qui contient un caractère a, la seconde seulement les chaînes qui sont entièrement constituées de caractères a.

Notez que les expressions rationnelles sont des objets et qu’elles ont des méthodes. Leur méthode test renvoie un booléen qui indique si une chaîne donnée correspond avec l’expression.

Le code \b correspond à une « limite de mot », qui peut être une ponctuation, une espace ou le début ou la fin d’une chaîne de caractères.

show(/cat/.test("concaténer"));
show(/\bcat\b/.test("concaténer"));

On peut autoriser des parties d’un motif à se répéter un certain nombre de fois. Mettre un astérisque (*) après un élément l’autorise à être répété autant de fois qu’on veut, y compris zéro fois. Un plus (+) se comporte de la même façon mais a besoin que le motif apparaisse au moins une fois. Un point d’interrogation (?) rend l’élément facultatif ― il peut apparaître une fois ou aucune.

var texteEntreParentheses = /\(.*\)/;
show("Ses (celles du paresseux) griffes étaient gigantesques!".search(texteEntreParentheses));

Lorsque c’est nécessaire, des accolades peuvent être utilisées pour préciser le nombre de fois où un élément peut apparaître. Un nombre entre accolades ({4}) donne la quantité exacte de fois. Deux nombres séparés par une virgule indiquent que le motif doit apparaître au moins le nombre de fois indiqué par le premier nombre et au maximum le nombre de fois indiqué par le deuxième. De manière similaire, {2,} signifie deux occurrences ou plus tandis que {,4} signifie quatre occurrences ou moins.

var motifDate = /\d{1,2}\/\d\d?\/\d{4}/;
show("Est né le 15/11/2003 (mère, Spot): Croc Blanc".search(motifDate));

Les parties /\d{1,2}/ et /\d\d?/ signifient toutes deux « un ou deux chiffres ».


Ex. 10.2

Écrivez un motif qui correspond avec les adresses électroniques. Pour simplifier, considérez que les parties avant et après le @ peuvent contenir seulement des caractères alphanumériques et des caractères . et - (point et tiret), tandis que la dernière partie de l’adresse, le code du pays après le dernier point, peut contenir des caractères alphanumériques et doit être long de deux ou trois caractères.

var adresseElectronique = /\b[\w\.-]+@[\w\.-]+\.\w{2,3}\b/;

show(adresseElectronique.test("kenny@test.net"));
show(adresseElectronique.test("J’ai envoyé un courriel à kenny@tets.nets, mais ça ne fonctionne pas !"));
show(adresseElectronique.test("le_paresseux_geant@gmail.com"));

Les \b au début et à la fin du motif permettent de s’assurer que la deuxième chaîne de caractères ne correspond pas.


Des parties d’une expression rationnelle peuvent être rassemblées en les mettant entre parenthèses. Ce qui nous permet d’utiliser * et autres sur plus d’un caractère. Par exemple :

var criFaconCartoon = /boo(hoo+)+/i;
show("Il s’exclama alors « Boohoooohoohooo »".search(criFaconCartoon));

D’où vient le i à la fin de cette expression rationnelle ? Après le slash fermant, une option peut être ajoutée à une expression rationnelle. Un i ici signifie que l’expression est insensible à la casse, ce qui permet d’utiliser un B minuscule dans le motif pour correspondre à celui en majuscule dans la chaîne de caractères.

Un caractère barre verticale (|) est utilisé pour permettre à un motif d’avoir le choix entre deux éléments. Par exemple :

var vacheSacree = /(vache|bœuf|taureau) (sacré|sacrée|saint|sainte)/i;
show(vacheSacree.test("Vache sacrée !"));

Souvent, chercher un motif n’est que la première étape dans l’extraction d’un élément dans une chaîne. Dans les chapitres précédents, cette extraction était effectuée en appelant beaucoup les méthodes indexOf et slice de l’objet string. Maintenant que nous sommes conscients de l’existence des expressions rationnelles, nous pouvons utiliser plutôt la méthode match. Quand on teste la correspondance d’une chaîne à une expression rationnelle, le résultat sera null si la correspondance échoue ou un tableau de chaînes si des correspondances sont trouvées.

show("Non".match(/Oui/));
show("… oui".match(/oui/));
show("Grand singe".match(/grand (\w+)/i));

Le premier élément dans le tableau renvoyé est toujours la partie de la chaîne qui correspond au motif. Comme on le voit dans le dernier exemple, lorsqu’il y a des parties du motif entre parenthèses, celles qui correspondent sont également ajoutées au tableau. Souvent, cela facilite grandement l’extraction de fragments de chaînes.

var entreParentheses = prompt("Dites-moi quelque chose", "").match(/\((.*)\)/);
if (entreParentheses != null)
  print("Vous avez mis entre parenthèses '", entreParentheses[1], "'");

Ex. 10.3

Réécrivez la fonction extraireDate que nous avons écrite dans le chapitre 4. Lorsqu’on lui donne une chaîne à traiter, cette fonction cherche quelque chose qui suit le format de date que nous avons vu précédemment. Si elle peut retrouver une telle date, elle met la valeur dans l’objet Date. Sinon, elle lève une exception. Faites en sorte qu’elle accepte les dates dans lesquelles le jour ou le mois sont écrits avec un seul chiffre.

function extraireDate(chaine) {
  var trouves = chaine.match(/(\d\d?)\/(\d\d?)\/(\d{4})/);
  if (trouves == null)
    throw new Error("Aucune date trouvée dans '" + chaine + "'.");
  return new Date(Number(trouves[3]), Number(trouves[2]) - 1,
                  Number(trouves[1]));
}

show(extraireDate("Est né le 5/2/2007 (mère, Kaïra): Johnson Longues Oreilles"));

Cette version est légèrement plus longue que la précédente mais elle a l’avantage de vérifier effectivement ce qui est fait et de faire retentir la sirène d’alarme quand une entrée illogique est faite. C’était beaucoup plus difficile sans expression rationnelle ― cela aurait nécessité beaucoup d’appels à indexOf pour déterminer si les nombres avaient un ou deux chiffres et si les tirets étaient à la bonne place.


La méthode replace des valeurs de chaîne, que nous avons vue dans le chapitre 6, peut être employée comme premier argument d’une expression rationnelle.

print("Borobudur".replace(/[ou]/g, "a"));

Remarquez le caractère g après l’expression rationnelle. Elle signifie « global » et veut dire que toute partie de chaîne qui coïncide avec le motif devrait être remplacée. Quand le g est omis, seul le premier "o" est remplacé.

Il est parfois nécessaire de conserver des parties de chaînes remplacées. Par exemple, nous avons une longue chaîne qui contient des noms de personnes, un nom par ligne, au format «nom, prénom ». Nous voulons inverser l’ordre des informations et supprimer la virgule, pour obtenir un simple format « prénom, nom »

var noms = "Picasso, Pablo\nGauguin, Paul\nVan Gogh, Vincent";
print(noms.replace(/([\w ]+), ([\w ]+)/g, "$2 $1"));

Le $1 et le $2 de la chaîne de remplacement, font référence aux parties entre parenthèses dans le motif. $1 est remplacée par le texte correspondant à la première paire de parenthèses du motif, $2 par la deuxième et ainsi de suite jusqu’à $9.

Si vous avez plus de 9 parties entre parenthèses, cela ne fonctionnera plus. Cependant, il existe un autre moyen de remplacer des parties de chaînes de caractères, qui peut être utile dans certaines situations délicates. Lorsque le second argument donné à la méthode replace est une valeur fonction au lieu d’une chaîne de caractères, cette fonction est appelée à chaque fois qu’une correspondance est trouvée ; le texte correspondant est alors remplacé par ce que la fonction renvoie. Les arguments donnés à la fonction sont les éléments qui correspondent, similaires aux valeurs trouvées dans les tableaux renvoyés par match : le premier est la correspondance complète, puis vient un argument pour chaque partie entre parenthèses du motif.

function mangeUnDeChaque(correspondance, quantite, unite) {
  quantite = Number(quantite) - 1;
  if (quantite == 1) {
    unite = unite.slice(0, unite.length - 1);
  }
  else if (quantite == 0) {
    unite = unite + "s";
    quantite = "aucun";
  }
  return quantite + " " + unite;
}

var stock = "1 citron, 2 carottes, et 101 oeufs";
stock = stock.replace(/(\d+) (\w+)/g, mangeUnDeChaque);

print(stock);

Ex. 10.4

Cette dernière astuce peut être utilisée pour rendre plus efficace la fonction d’échappement HTML vu dans le chapitre 6. Vous vous souvenez peut-être qu’elle ressemblait à cela :

function escapeHTML(texte) {
  var remplacements = [[/&/g, "&"], [/"/g, """],
                      [/</g, "&lt;"], [/>/g, "&gt;"]];
  forEach(remplacements, function(remplacement) {
    texte = texte.replace(remplacement[0], remplacement[1]);
  });
  return texte;
}

Écrivez une nouvelle fonction escapeHTML, qui fasse la même chose, mais qui n’appelle replace qu’une seule fois.

function escapeHTML(texte) {
  var remplacements = {"<": "&lt;", ">": "&gt;",
                      "&": "&amp;", "\"": "&quot;"};
  return texte.replace(/[<>&"]/g, function(caractere) {
    return remplacements[caractere];
  });
}

print(escapeHTML("La balise pour le préformatage s’écrit \"<pre>\"."));

L’objet remplacements est un moyen rapide d’associer chaque caractère à sa version échappée. L’utiliser ainsi ne pose pas de problème (c’est-à-dire qu’un objet Dictionary n’est pas nécessaire) parce que les seules propriétés qui seront utilisées sont celles qui correspondent avec l’expression /[<>&"]/.


Il y a des cas où les motifs avec lesquels doivent correspondre les chaînes ne sont pas connus au moment où le code est écrit. Par exemple, si nous écrivons un filtre à obscénités (simpliste) pour un forum de discussions. Nous voulons autoriser uniquement les messages qui ne contiennent pas de mot obscène. L’administrateur du forum peut spécifier une liste de mots qu’il ou elle considère comme inacceptables.

Le moyen le plus efficace de vérifier un fragment du texte pour un ensemble de mots est d’utiliser une expression rationnelle. Si nous mettons notre liste de mots dans un tableau, nous pourrons construire l’expression rationnelle de la façon suivante :

var motsInterdits = ["primate", "singe", "simien", "gorille", "evolution"];
var motif = new RegExp(motsInterdits.join("|"), "i");
function estAcceptable(texte) {
  return !motif.test(texte);
}

show(estAcceptable("Henry Kissinger a reçu le prix Nobel de la paix en 1973."));
show(estAcceptable("Ça suffit avec ces histoires de singes."));

Nous pourrions ajouter des motifs \b autour des mots, pour que les choses à propos de Henry Kissinger ne soient pas considérées comme irrecevables. Cependant, cela rendrait aussi le deuxième acceptable, ce qui n’est probablement pas correct. Les filtres parentaux sont difficiles à concevoir et à paramétrer (et la plupart du temps sont bien trop agaçant pour être une bonne idée).

Le premier argument pour le constructeur RegExp est une chaîne contenant le motif, le deuxième argument peut être utilisé pour ajouter l’insensibilité à la casse ou la globalité. Quand on élabore une chaîne pour contenir le motif, on doit faire très attention aux antislashes. En effet, en principe, les antislashes sont supprimés quand une chaîne est interprétée, tous les antislashes qui doivent se trouver dans l’expression rationnelle elle-même doivent donc être échappés :

var chiffres = new RegExp("\\d+");
show(chiffres.test("101"));

Le plus important à savoir à propos des expressions rationnelles est qu’elles existent et peuvent augmenter de façon significative la puissance de votre code modificateur de chaînes. Elles sont tellement alambiquées qu’il vous faudra probablement regarder de très près leur détail les dix premières fois où vous voudrez les utiliser. Persévérez et vous écrirez vite sans les mains des expressions qui auront l’air de formules cabalistiques.

(Bande dessinée de Randall Munroe.)

  1. Dans cet exemple, les antislash ne sont pas vraiment nécessaires, car il s’agit de caractères encadrés par [ et ] mais il est plus facile de les échapper tout de même et de ne plus avoir à y penser.