IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)

JavaScript Éloquent

Une introduction moderne à la programmation
Image non disponible


précédentsommairesuivant

X. Expressions régulières

À 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 régulières 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 régulières 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 régulières sont écrites entre des slash(/). Ce qui implique que des slashes à l'intérieur de l'expression devront être échappés.

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

La méthode search ressemble à la méthode indexOf, mais elle cherche une expression régulière et non une chaîne. Les structures indiquées dans les expressions régulières 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 :

 
Sélectionnez
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 régulières. 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 régulières, c'est une bonne idée de les échapper systématiquement avec un antislash(17) 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).

 
Sélectionnez
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. Lorsqu'on utilise [ et ], un motif peut être inversé en commençant par un caractère ^ :

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

Comme vous pouvez le voir, la façon dont les expressions régulières 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 régulière 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".

 
Sélectionnez
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.

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

La première expression régulière 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 régulières 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.

 
Sélectionnez
show(/cat/.test("concatener"));
show(/\bcat\b/.test("concatener"));

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.

 
Sélectionnez
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.

 
Sélectionnez
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 e-mail. 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.

 
Sélectionnez
var adresseEmail = /\b[\w\.-]+@[\w\.-]+\.\w{2,3}\b/;
 
show(adresseEmail.test("kenny@test.net"));
show(adresseEmail.test("J'ai envoyé un mail à kenny@tets.nets, mais ça ne marche pas !"));
show(adresseEmail.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 régulière 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 :

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

D'où vient le i à la fin de cette expression régulière ? Après le slash fermant, une option peut être ajoutée à une expression régulière. 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 :

 
Sélectionnez
var vacheSacree = /(vache|boeuf|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 régulières, nous pouvons utiliser plutôt la méthode match. Quand on teste la correspondance d'une chaîne à une expression régulière, le résultat sera null si la correspondance échoue ou un tableau de chaînes si des correspondances sont trouvées.

 
Sélectionnez
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.

 
Sélectionnez
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.

 
Sélectionnez
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 régulière -: 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 régulière.

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

Remarquez le caractère g après l'expression régulière. 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" doit être 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 »

 
Sélectionnez
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é 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 neuf 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.

 
Sélectionnez
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 vue dans le chapitre 6. Vous vous souvenez peut-être qu'elle ressemblait à cela :

 
Sélectionnez
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 fait la même chose, mais qui n'appelle replace qu'une seule fois.

 
Sélectionnez
function escapeHTML(texte) {
  var remplacements = {"<": "&lt;", ">": "&gt;",
                      "&": "&amp;", "\"": "&quot;"};
  return texte.replace(/[<>&"]/g, function(caractere) {
    return remplacements[caractere];
  });
}
 
print(escapeHTML("Le tag 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 aucun objet Dictionary n'est nécessaire) parce que les seules propriétés qui seront utilisées sont celles qui correspondent avec l'expression /[<>&"]/.

Il y 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 régulière. Si nous mettons notre liste de mots dans un tableau, nous pourrons construire l'expression régulière de la façon suivante :

 
Sélectionnez
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çants 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 régulière elle-même doivent donc être échappés :

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

Le plus important à savoir à propos des expressions régulières est qu'elles existent et peuvent augmenter de façon significative la puissance de votre code altérateur 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.

Image non disponible

(Bande dessinée de Randall Munroe.)


précédentsommairesuivant
Dans cet exemple, les antislashes 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.

Licence Creative Commons
Le contenu de cet article est rédigé par Marijn Haverbeke et est mis à disposition selon les termes de la Licence Creative Commons Attribution 3.0 non transposé.
Les logos Developpez.com, en-tête, pied de page, css, et look & feel de l'article sont Copyright © 2013 Developpez.com.