Auteur: Ludovic PATEY

Publié le 8 février 2008

Modifié le: 8 février 2008

Page d'accueil

Prendre en compte les échappements dans son expression régulière

Vous êtes ici : Accueil / Articles / Formations globales

Il peut vous arriver d’avoir à parser des éléments délimités par des caractères définis. Par exemple : un une balise encadrée par des < >, ou une chaine de caractères délimitée par des guillemets...

Expression du problème

Prenons le dernier cas et faisons-en une étude détaillée. Il existe une expression régulière assez simple pour en extraire le contenu :

/"([^"]*)"/

L’expression précédente sélectionne une chaine qui commence par un guillemet, se termine par un guillemet et contient 0 ou plusieurs caractères qui ne sont pas des guillemets.

A première vue, cette expression régulière répond aux besoins. Cependant, il arrivera un moment où vous vous trouverez confronté au texte suivant à parser :

"Je vais afficher \"Hello World\" "

Avec l’expression régulière précédente, nous aurons 2 captures : "Je vais afficher \" et " "

Un premier essai de solution

La première réaction est de modifier l’expression régulière précédente pour remplacer le "0 ou plusieurs caractères qui ne sont pas des guillemets" par "0 ou plusieurs fois ( un guillemet précédé d’un antislash ou un caractère qui n’est pas un guillemet )"

/"((?:\\"|[^"])*)"/

Remarque 1 : l’ajout de ? : après une parenthèse permet de la transformer en parenthèse non capturante.

Remarque 2 : Nous avons recours à 2 antislash à la suite car ce caractère étant également un caractère d’échappement pour le langage des expressions régulières, il faut échapper le caractère d’échappement.

Remarque 3 : Attention ! Le code /"(( ? :[^"]|\\")*)"/ ( interversion entre [^"] et \\" de l’expression régulière précédente ) ne rendrait pas l’effet escompté. En effet, comme l’antislash est un caractère qui n’est pas un guillemet, il est pris en compte par le [^"]. En présence d’une syntaxe d’alternative ( ? : | | | ), lorsqu’une des conditions est vérifiée, le curseur se déplace directement à la fin de l’alternative sans en analyser les autres élément. Analysons donc pas à pas l’évolution du curseur dans la phrase "Je vais afficher \"Hello World\" " :

Le curseur commence par se positionner au début de l’expression régulière et voit qu’il lui faut un guillemet. Il en trouve un au début de la phrase. Ensuite, il se positionne au début de de l’alternative, qu’il doit répéter 0 ou plusieurs fois. ( à cause de l’astérisque * ). Il trouve successivement J e v a i s a f f i c h e r \, en se contentant d’analyser la première condition. Le curseur se trouve donc au niveau du guillemet avant le H de Hello. La première condition [^"] est fausse. Il regarde la seconde condition : \\". Comme le curseur se trouve juste devant le guillemet, cette condition vaut également false. Elle aurait été bonne si le curseur s’était trouvé un caractère avant sa position actuelle.

L’expression régulière précédente était bonne car l’expression \\" étant évaluée en premier, le curseur l’évaluait 1 caractère avant le guillemet, la trouvait bonne et se décalait de 2 caractères ce qui avait pour effet de se retrouver après le guillemet.

La remarque 3 est essentielle pour comprendre la solution de cette article. Sa compréhension est l’élément clé du parsage des caractères d’échappement.

La solution finale

Nous en étions donc au parsage de "Je vais afficher \"Hello World\" " qui semble avoir été résolu par l’expression régulière /"(( ? :\\"|[^"])*)"/

Cependant viendra le jour où la chaîne à parser sera

"Un antislash est le caractère \\" "Une apostrophe est ’ "

L’expression régulière précédente va capturer "Un antislash est le caractère \\" "

Et là, on réalise que le problème est plus complexe : il ne faut prendre en compte le guillemet si le nombre d’apostrophes est pair.

Nous allons pour cela nous appuyer toujours sur le même principe que le précédent : jouer sur l’ordre de vérification des conditions d’une alternative avec le déplacement du curseur.

Pour savoir si le nombre d’antislashes est pair ou impair, il suffit de décaler le curseur de 2 antislashes jusqu’à ce qu’il se trouve devant un antislash unique ou devant le guillemet. Dans le premier cas, il faut qu’il ne tienne pas compte du guillemet. Dans le second, si.

Voyons maintenant comment nous allons l’implémenter en expressions régulières. Comme nous l’avons vu, l’antislash ayant un sens au niveau du langage d’expressions régulières, nous devons l’échapper. Ainsi, un antislash en tant que caractère doit être représenté par 2 antislashes.

/"((?:\\\\|\\"|[^"])*)"/

De l’expression régulière précédente, seule la partie centrale nous intéresse : l’alternative. Nous savons maintenant que l’alternative se lit de gauche à droit et que le curseur se décale du nombre de caractères que définit l’alternative.

Prenons le texte suivant qui présente l’avantage de regrouper les différents cas :

"L’ \"antislash\" est codé \\"

Le curseur va lire le guillemet du départ, puis se retrouver face à l’alternative qu’il doit trouver en 0 ou plusieurs occurences. Jusqu’au premier antislash, seule la troisième alternative est vérifiée. La troisième alternative ne définissant qu’un caractère, le curseur ne se décalera que d’un caractère dans la chaîne. Puis il va se trouver face à \" . On voit clairement qu’il s’agit de la seconde alternative. Il va donc l’accepter et se décaler de 2 caractères, puis va continuer à lire caractère par caractère sachant que les caractères suivants ne sont vérifiés que par la dernière alternative. Enfin, il va se retrouver devans \\" . La première alternative étant vérifiée, il va se décaler de 2 caractères et se retrouver devant le guillemet simple. Aucune des alternatives n’est vérifiée. C’est donc la fin de la chaîne.

Conclusion

Cette solution présentée dans le cas concret des caractères d’échappements est en vérité une solution générale à tout système de congruence par les expressions régulières. Nous avons étudié le cas d’un nombre d’occurences congru à 0 ou 1 modulo 2.

Commentaires

Auteur :

Message :