Des regex qui ont la classe !
Parlons regex ! Expressions rationnelles. Alors oui, les plus assidus me feront remarquer qu’on a déjà abordé ce thème à plusieurs reprises ; on a abordé le sujet des quantificateurs, des groupes de capture et on a même joué avec les emojis ! Mais il reste encore des aspects de ce fabuleux outil que sont les regex à aborder, et aujourd’hui je vous propose de nous pencher sur les classes de caractères.
Classes de caractères
Une classe de caractère, c’est tout simplement un ensemble de caractères manipulé comme un tout ; généralement parce qu’ils partagent un trait commun. Au sein d’une expression rationnelle, on note une classe de caractère entre crochets, comme ceci :
[0123456789]
une simple liste de caractères ;[0-9]
un intervalle ;[A-Za-z0-9_]
un mix de tout ça ;\w
ou[[:word:]]
une classe prédéfinie ;[^0-9]
une négation (caractérisée par la présence d’un circonflexe en tête de liste).
Moteur !
Il existe différentes implémentations de moteurs de regex, dits moteurs NFA piloté par le motif ou DFA piloté par l’entrée. Suivant le langage et l’environnement dans lequel vous évoluez, de petites différences pourront être constatées. En l’occurrence, tous les moteurs ne supportent pas les mêmes classes de caractères prédéfinies.
Le moteur le plus complet à ce jour étant celui de Perl. Celui-ci permet, par exemple, de rechercher le mot le plus long d’une chaîne de caractères à l’aide de boundaries, possessive quantifiers, positive lookahead, negative lookahead, positive lookbehind… on a sorti tout l’attirail !
/\b(\w++)(?=(.*))(?!(.*\W)\b((?<=(?=(?=\1\2$)(?:(?=\w*+\3(\5?+\w))\w)++\b|(?4)).)))/
Aïe, ça pique !
Un manque de standardisation
Les classes prédéfinies sont fort utiles, seulement elles sont peu portables du fait du manque de standardisation entre les différents moteurs de regex. Voici un petit aperçu.
Vim | JS | Ruby, Elixir, PHP | ASCII | Description |
---|---|---|---|---|
[[:ascii:]] |
[\x00-\x7F] |
Caractères ASCII | ||
[[:alnum:]] |
A-Za-z0-9 |
Caractères alphanumériques | ||
\w |
\w |
\w ou [[:word:]] |
A-Za-z0-9_ |
Caractères alphanumériques, et « _ » |
\W |
\W |
\W ou [^[:word:]] |
^A-Za-z0-9_ |
Caractères ne composant pas les mots |
\a |
[[:alpha:]] |
A-Za-z |
Caractères alphabétiques | |
\s |
[[:blank:]] |
\t |
Espace et tabulation | |
\\< \\> |
\b |
\b ou [[:<:]] [[:>:]] |
(?<=\W)(?=\w)│(?<=\w)(?=\W) |
Positions de début et fin de mots |
\B |
\B ou [^[:<:]] [^[:>:]] |
(?<=\W)(?=\W)│(?<=\w)(?=\w) |
Positions ni en début ni en fin de mot | |
[[:cnrtl:]] |
\x00-\x1F\x7F |
Caractères de contrôle | ||
\d |
\d |
\d ou [[:digit:]] |
0-9 |
Chiffres décimaux |
\D |
\D |
\D ou [^[:digit:]] |
^0-9 |
Autre qu’un chiffre décimal |
[[:graph:]] |
\x21-\x7E |
Caractères visibles | ||
\l |
[[:lower:]] |
a-z |
Lettres en minuscule | |
\p |
[[:print:]] |
\x20-\x7E |
Caractères imprimables | |
[[:punct:]] |
][!"#$%&'()\*+,./:;<=>?@\^_{│}~- |
Caractères de ponctuation | ||
\_s |
\s |
\s ou [[:space:]] |
\t\r\n\v\f |
Caractères d’espacement |
\S |
\S |
\S ou [^[:space:]] |
^ \t\r\n\v\f |
Autre qu’un caractère d’espacement |
\v |
\v |
Caractère d’espacement vertical | ||
\V |
Autre qu’un caractère d’espacement vertical | |||
\u |
[[:upper:]] |
A-Z |
Lettres capitales | |
\x |
\x |
\h ou [[:xdigit:]] |
A-Fa-f0-9 |
Chiffres hexadécimaux |
\H ou [^[:xdigit:]] |
Autre qu’un chiffre hexadécimal | |||
\A |
\A |
Début de chaîne de caractère | ||
\z |
\z |
Fin de chaîne de caractère |
Propriétés Unicode
Mais les classes de caractères ne se limitent pas à celles listées ci-dessus. Il est en effet possible de tirer profit des propriétés Unicode. Voici quelques exemples pour toucher du doigt le potentiel de ces classes.
Jeux de caractères
Il est par exemple possible de rechercher n’importe quel caractère grec :
/\p{Greek}/
2ΠR valent mieux qu'un caillou
^
Symboles monétaires
Ou encore, de retrouver les symboles monétaires dans une chaîne :
/\p{Sc}/g
Vous pouvez payer en €uro en £ivre ou en ¥en. Et même en ₿itcoin !
^ ^ ^ ^
Tirets de ponctuation
Voire de détecter n’importe quel tiret de ponctuation :
/\p{Pd}/g
Vous êtes plutôt trait d'union (‐ U+2010), signe moins (- U+002D), tiret demi-cadratin (– U+2013), tiret cadratin (— U+2014) ou tiret numérique (‒ U+2012) ?
^ ^ ^ ^ ^
Exclusion
Il est même possible d’exclure une sous-classe. Par exemple, voici comment retrouver tous les caractères de ponctuation et symboles, excepté les tirets :
/(?!\p{Pd})[\p{P}\p{S}]/g
schöner co-operative two_words!@#$%^
^ ^^^^^^
Il existe près de 40 de ces propriétés Unicode ! Si la curiosité vous pique, je vous invite à aller faire un tour du côté de la documentation d’Erlang traitant de ce sujet.
À suivre…
Dans un prochain article, je vous présenterai un cas concret de non portabilité d’une expression rationnelle et comment j’ai dû ruser pour arriver à mes fins !