jeudi 2 avril 2020
Ce billet a été vu pour la première fois sur le blog de Synbioz le 02 April 2020 sous licence CC BY-NC-SA.

Regex : le gourmand, le fainéant et le possessif

Ce n’est pas le premier article à traiter de regex sur ce blog. Nous avons déjà eu l’occasion d’aborder la comparaison de chaînes de caractères ou encore le support d’Unicode et des emojis. Cette semaine, je vous propose d’observer dans le détail les nombreuses possibilités que nous offre une notion clé des regex : les quantificateurs, ou quantifiers en anglais.

Que sont les quantificateurs ?

Commençons par un peu de grammaire et de vocabulaire, de quoi parlons-nous ? Et comment cela s’articule-t-il ?

Les quantificateurs permettent de préciser dans une regex le nombre d’occurrences attendues pour un caractère ou un groupe de caractères donné. Pour cela, des caractères ayant une signification spécifique sont mis à notre disposition. Il s’agit du point d’interrogation ?, de l’étoile *, du signe plus +, ainsi que des accolades {}.

Voici leur signification :

Notons que si l’on souhaite utiliser l’un de ces caractères spéciaux dans notre regex pour son sens littéral, il suffit alors de le préfixer par un antislash \. Par exemple, pour rechercher la chaîne compris? et pas compri suivi ou non d’un s, nous écrirons :

/compris\?/

Le gourmand

Par défaut, les quantificateurs sont gourmands (ou greedy en anglais), c’est-à-dire qu’ils maximisent la concordance. Dit autrement, tant qu’un caractère correspond au motif recherché, il sera consommé. Prenons un exemple.

Supposons que nous faisons une recherche dans la chaîne andouillette AAAAA à l’aide de cette regex :

/(.*)A/

Voici ce qui en résulterait :

Concordance complète (0-18) : andouillette AAAAA
Groupe 1             (0-17) : andouillette AAAA

Expliquons-nous. Nous recherchons n’importe quel caractère, un nombre indéterminé de fois .*, c’est là notre groupe 1 ; suivi pour finir du caractère A. Notre quantificateur étant gourmand, il va consommer tous les caractères de la chaîne, sauf le dernier A de façon à ce qu’il puisse y avoir concordance.

Le fainéant

Il est possible d’obtenir le comportement inverse. C’est-à-dire minimiser la concordance. Pour cela, il nous faut suffixer notre quantificateur d’un point d’interrogation *?. On parle alors de quantificateur fainéant ou lazy.

Prenons le même exemple, mais en le comparant cette fois à cette regex :

/(.*?)A/
Concordance complète (0-14) : andouillette A
Groupe 1             (0-13) : andouillette

À présent, sitôt qu’un sous-ensemble de notre chaîne de caractère fait l’affaire, la concordance s’arrête. Notre quantificateur nous empêche d’en parcourir davantage.

Cet exemple peut s’avérer très artificiel. Voyons un cas plus concret. Disons que vous souhaitez récupérer l’ensemble des attributs d’une balise HTML. Par exemple, celle-ci :

<a href="https://code.strigo.cc">Strigo Code</a>

Votre première idée serait peut-être d’écrire un regex comme celle-ci :

/<a (.*)>.*<\/a>`/
Concordance complète (0-48) : <a href="https://code.strigo.cc">Strigo Code</a>
Groupe 1             (3-29) : href="https://code.strigo.cc"

Effectivement, cela fonctionne et le premier groupe contiendra bien la sous-chaîne recherchée href="https://code.strigo.cc". Mais on peut obtenir une regex plus concise grâce à un quantificateur fainéant :

/<a (.*?)>/
Concordance complète (0-33) : <a href="https://code.strigo.cc">
Groupe 1             (3-29) : href="https://code.strigo.cc"

Notons que dans ce cas très précis, l’approche la plus efficace, mais dont l’intention est bien moins déchiffrable, reste l’utilisation d’une classe de caractères négative telle [^>], c’est-à-dire « tout sauf un chevron > » — comme ceci :

/<a ([^>]*)>/
Concordance complète (0-33) : <a href="https://code.strigo.cc">
Groupe 1             (3-29) : href="https://code.strigo.cc"

Il y a toujours plusieurs moyens d’arriver à ses fins à l’aide d’une regex, le tout étant de savoir quelle est votre priorité : la performance ou la compréhensibilité.

Le possessif

Le dernier modificateur dont nous disposons est le possessif (ou possessive en anglais). Le possessif, qui se note *+, est gourmand et empêche tout retour en arrière. Autrement dit, tout caractère consommé ne pourra être relâché pour tenter de trouver une concordance.

Si l’on reprend notre « andouillette AAAAA » comme exemple avec notre nouvelle regex…

/(.*+)A/

…nous n’aurions alors aucune concordance ! Pour bien saisir, il faut comprendre le fonctionnement interne d’une recherche de concordance. Le moteur de regex commencera au début de notre chaîne, consommera tous les caractères .*+ puis recherchera un A. Mais il n’y en a plus, car tout a été consommé et notre quantificateur ne restitue aucun caractère consommé pour aider le moteur de regex à trouver une concordance. Qu’à cela ne tienne, notre petit moteur va commencer sa recherche à partir du deuxième caractère… ce qui aura la même issue malheureuse. Il tentera donc à partir du troisième, et ainsi de suite, nous menant toujours dans une impasse, dans l’impossibilité de donner satisfaction à cette regex.

Alors, à quoi cela sert-il, me direz-vous !? L’intérêt d’un quantificateur possessif réside essentiellement dans la performance : il permet d’échouer plus vite en empêchant le moteur de regex d’effectuer certaines permutations.

Conclusion

Une bonne connaissance des possibilités que nous offrent les regex peut être d’une grande aide au quotidien. Les regex sont tout en nuances et, bien employées, peuvent s’avérer d’excellentes alliées !

Pour aller plus loin, je vous invite à faire un tour sur regex101.com, un excellent outil interactif permettant de passer au crible vos regex, même les plus complexes. Ainsi que regular-expressions.info, une mine d’or (en anglais) à parcourir sans modération !