Autour du codeDevelopper toujours mieux
Posté le

Des variables et des conditions dans une regex

Vous connaissez certainement les regexs appelées également expressions rationnelles. Le principe est de décrire un ensemble de chaine de caractère qui ont certain point en commun. Par exemple, la regex T.T correspond à toutes chaines de caractère qui ont un T suivi par n'importe qu'elle caractère suivi d'un autre T. Dans une regex, le point correspond à tout caractère qui n'est pas un saut de ligne. La plus part des langages de programmation supportent les regexs. En python, on utilses le module re et en JS les regexs sont intégrées au langage et s'écrive entre deux slashs ('/'). Pour voir si un motif est présent dans une chaîne de caractère on utilisera la méthode exec en JS et search en python.

match = re.search("T.T", "uuuToTuuu"):
print(match) # <_sre.SRE_Match object; span=(3, 6), match='ToT'>
match = re.search("T.T", "uuuTaTuuu")
print(match) # <_sre.SRE_Match object; span=(3, 6), match='TaT'>
match = re.search("T.T", "uuuBTPuuu")
print(match) # None

match = /T.T/.exec("uuuToTuuu");
console.log(match); // [ 'ToT', index: 3, input: 'uuuToTuuu' ]
match = /T.T/.exec("uuuTaTuuu");
console.log(match); // [ 'TaT', index: 3, input: 'uuuTaTuuu' ]
match = /T.T/.exec("uuuBTPuuu")
console.log(match); // null

Dans une regex à l'instar du point, d'autre caractère ou couple de caractères sont utilisés pour d'écrire un ensemble de caractère possible, on appelle cela une classe.

  • d pour les chiffres.
  • w pour les caractères alphanumériques.
  • s pour les espaces, tabulation et tout autre caractère blanc.

Si l'on souhaite tous les caractères qui ne sont pas dans une classe, on remplace la minuscule par une majuscule. Par exemple D indique tous ce qui n'est pas dans la classe chiffre

Dans le code qui suit, la regex cherche 4 chiffres qui se suivent.

match = re.search("\d\d\d\d", "2016")
print(match) # <_sre.SRE_Match object; span=(0, 4), match='2016'>
match = re.search(r"\w\d", "A4")
print(match) # <_sre.SRE_Match object; span=(0, 2), match='A4'>

match = /\d\d\d\d/.exec(2016);
console.log(match); // [ '2016', index: 0, input: '2016' ]
match = /\w\d/.exec('A4');
console.log(match); // [ 'A4', index: 0, input: 'A4' ]

Définir ses propres classes dans une regex

Vous pouvez définir vos propres classes en plaçant les caractères possibles entre crochets.

  • [aeiouy] - Pour les voyelles en minuscule.
  • [FGh] - Les lettres F, G ou h

Si l'on souhaite le contraire dans un groupe de caractère, on le fait commencer par un accent circonflexe (^) .

[^aeiouy] - Pour le groupe des consonnes.

Il existe aussi une facilité d'écriture lorsque l'on définie un groupe.

[a-m] - Toutes les lettres de 'a' à 'm'.

Et si l'on souhaite que le caractère tiret - fasse partie du groupe, il faut le mettre à gauche du crochet fermant.

[ze-] - Ce groupe contient les caractères. z, e et -

Dans l'exemple ci-dessous, on cherche une chaine qui commence par FO suivi d'une voyelle.

match = re.match("FO[^AEIOUY]", 'FOO')
print(match) # None
match = re.match("FO[^AEIOUY]", 'FOX')
print(match) # <_sre.SRE_Match object; span=(0, 3), match='FOX'>

match = /FO[^AEIOUY]/.exec('FOO');
console.log(match); // null
match = /FO[^AEIOUY]/.exec('FOX');
console.log(match); // [ 'FOX', index: 0, input: 'FOX' ]

Faire de la validation

Comme vu précédemment, test et search cherche le motif dans toute la chaîne, mais on peut modifier ce comportement en mettant un caractère ^ au début du motif ou le caractère $ à la fin du motif.

En utilisant ^ et $ on peut vérifier qu'une chaine de caractère dans sa totalité correspond à la regex.

Par exemple pour vérifier qu'un utilisateur a saisi une date valide on peut utiliser ceci.

var regexDate = /^[0-3]\d\/[01]\d\/\d\d\d\d$/;
match = regexDate.exec("19/12/3020");
console.log(match); // [ '19/12/3020', index: 0, input: '19/12/3020' ]
match = regexDate.exec("X19/12/3020");
console.log(match); // null

Les quantificateurs

Il permet d'indiquer le nombre de fois qu'un caractère doit apparaitre avec la syntaxe suivante: {quantité} ou {quantité-min,quantité-max}.

Exemple:

  • Hi{1,4} - H suivi de 1 à 4 i
  • Hi{4} - H suivi de 4 i
  • Hi{1,} - H suivi de 1 à une infinité de i

Ainsi dans le motif représentant une date peut s'écrire comme ceci:

/^[0-3]\d\/[01]\d\/\d{4}$/

Arrêter la recherche dès que possible

Un des problèmes qui se pose lorsque l'on a une regex qui contient un quantifier infinie et que celui-ci va essayer de prendre le plus de caractère possible alors que parfois, on souhaiterait qu'il s'arreter de prendre des caractères le plus tôt possible

Par exemple, si je veux rechercher les balises html dans un texte et que j'utilise la regex suivante, le résulta risque d'être décevant.

>>> re.search(r'<.*>', '<span>foo</span><p>bar</p>')
<_sre.SRE_Match object; span=(0, 26), match='<span>foo</span><p>bar</p>'>

Comme vous pouvez le voir, toute la ligne est capturée. En fait .* va capturer des caractère tant qu'il le peut même les caractères '<'. On dit que le quantifier est gourmand. Si l'on souhaite qu'il s'arrête le plus-tôt possible on va utiliser le caractère '?' après le quantifier.

>>> re.search(r'<.*?>', '<span>foo</span><p>bar</p>')
<_sre.SRE_Match object; span=(0, 6), match='<span>'>

Si l'on souhaite itérer sur tous les résultats trouvés par une regex, en Python on utilisera la fonction finditer. En JavaScript lorsque l'on souhaite récupérer plusieurs résultas en JS il faut mettre un 'g' à la fin de la regex définie puis utiliser la méthode exec.

regex = r'<.*?>'
for e in re.finditer(regex, '<span>foo</span><p>bar</p>'):
    print(e)

# <_sre.SRE_Match object; span=(0, 6), match='<span>'>
# <_sre.SRE_Match object; span=(9, 16), match='</span>'>
# <_sre.SRE_Match object; span=(16, 19), match='<p>'>
# <_sre.SRE_Match object; span=(22, 26), match='</p>'>

var regex = /<.*?>/g
while ((match = regex.exec('<span>foo</span><p>bar</p>')) !== null) {
    console.log(match);
}

/*
[ '<span>', index: 0, input: '<span>foo</span><p>bar</p>' ]
[ '', index: 9, input: '<span>foo</span><p>bar</p>' ]
[ '<p>', index: 16, input: '<span>foo</span><p>bar</p>' ]
[ '</p>', index: 22, input: '<span>foo</span><p>bar</p>' ]
*/

Capturer des morceaux du motif avec les parenthèses

On peut mettre des parties de la regex entre parenthèse pour définir ce que l'on appelle des groupes. On pourra par la suite récupérer les parties de la chaine de caractères qui correspondent aux groupes

Par exemple prenons une date définie par la regex suivante

^[0-3]\d\/[01]\d\/\d{4}$

On va définir trois groupes comme ceci.

^([0-3]\d)\/([01]\d)\/(\d{4})$

Pour récupérer les éléments de la date saisie par un utilisateur on va faire comme ceci.

regex_date = r'^([0-3]\d)\/([01]\d)\/(\d{4})$'
matches = re.search(regex_date, "11/10/2012")
print(matches.group(1)) # 11
print(matches.group(2)) # 10
print(matches.group(3)) # 2012

var regexDate = /^([0-3]\d)\/([01]\d)\/(\d{4})$/;
var matches = regexDate.exec("11/10/2012");
console.log(matches[1]); // 11
console.log(matches[2]); // 10
console.log(matches[3]); // 2012

Nommer les groupes capturants

Cette fonctionnalité permet de récupérer le contenue d'un groupe en utilisant un nom plutôt qu'un indice. Elle n'ai pas disponible nativement en JS. En python, elle fonctionne comme ceci:

(?Pmotif)

On met ?P après la parenthèse ouvrante du groupe et on inscrit le nom du groupe entre chevrons $lt; $gt; Le motif ci-dessous permet d'utiliser les noms day, month et year pour récupérer les éléments de la date.

>>> matches = re.search(r"^(?P<day>[0-3]\d)/(?P<month>[01]\d)/(?P<year>\d{4})$", "12/12/2004")
>>> matches.group('day')
'12'
>>> matches.groupdict()
{'year': '2004', 'month': '12', 'day': '12'}

Utiliser la capture d'un groupe au sein de la regex

Grace à la syntaxe suivante (?P=nom-du-groupe) on peut dire ce que tu doit matché est ce que le groupe indiqué après '?P=' à capturé.

Par exemple le motif suivant correspond à deux chiffres qui se suivent 11 22 33 44 ...

^(?P\d)(?P=number)$

L'exemple suivant correspond à n'importe quel caractère entouré par une simple quote ou une double quotes.

^(?P['"]).*(?P=quote)$

Avoir une condition dans sa regex

On peut rechercher un motif ou bien un autre uniquement si un group à capturé quelque chose. La syntaxe à utiliser est la suivante:

(?(nom-du-groupe)motif-ok|motif-ko)

Par exemple, en Espagnol Si une phrase commence par un point d'interrogation retourné, elle doit finir par un point d'interrogation sinon elle finit par un point.

regex = r"(?P<ponct>¿)?.*(?(ponct)\?|\.)"
match = re.search(regex, '¿Cómo estás?')
print(match) # <_sre.SRE_Match object; span=(0, 12), match='¿Cómo estás?'>
match = re.search(regex, 'Muy bien.')
print(match) # <_sre.SRE_Match object; span=(0, 9), match='Muy bien.'></ponct>

Rechercher un motif qui ne soit pas précédé par un autre

La syntaxe est la suivante, (?<!motif-1)motif-2, se qui signifie motif-1 ne doit pas précéder motif-2

Dans l'exemple ci-dessous, on capture uniquement les b qui ne sont pas précédés par un a.

>>> re.search("(?<!a)b", "ab cb")
<_sre.SRE_Match object; span=(5, 6), match='b'>

par exemple, si l'on veut récupèrer un texte entre quote mais qu'entre ces quote il peut y avoir des quotes précédé d'un . Comme dans 'l'ours' ou 'l'homme' on va utiliser le motif suivant:

'(.*?)(?<!\\)'

Qui ce lit: Une quote, n'importe qu'elle caractère 0 ou plusieurs fois. Une quote non précédé par un anti-slashe.

for match in re.finditer(r"'(.*?)(?<!\\)'", r"'l\'ours' 'l\'homme'"):
    print(match.group(1))

# l\'ours
# l\'homme

Regarder avant d'y mettre les pieds ou pas

Cette technique se nome lookahead elle consiste à dire je regarde si ce qui suit correspond au motif, mais je n'y vais pas. La syntaxe est la suivante (?=motif), on regarde si motif est présent.

Par exemple la regex foo(?=d) validera la même chose que la regex food mais ne capturera pas ma même chose.

>>> re.search(r"foo(?=\d)", "foo3")
<_sre.SRE_Match object; span=(0, 3), match='foo'>
>>> re.search(r"foo\d", "foo3")
<_sre.SRE_Match object; span=(0, 4), match='foo3'>

Un des cas d'utilisation de lookahead et la vérification de la solidité d'un mot de passe.

Vous regardez s'il contient une lettre, puis s'il contient un chiffre. Puis vous y allez en vérifiant qu'il fait au moins 6 caractères.

re.search(r"(?=.*[0-9].*)(?=.*[a-z].*)(.{6,}$)", "aes320")

Il existe également l'opérateur contraire qui valide si ce qui suit n'est pas le motif. La syntaxe est la suivante (?!motif), on regarde si motif n'est pas présent.

Par exemple si l'on veut les mots qui commencent par foo suivi de n'importe quelle lettre majuscule sauf le B, J, M, T

>>> re.search(r"foo(?![BJMT])[A-Z]", "fooA")
<_sre.SRE_Match object; span=(0, 4), match='fooA'>

Voila, j'espère que les techniques de regex vues dans cet article vous serons utiles. N'hésitez pas à partager vos cas d'usages et d'ici le prochain article à vos IDE et bon code laughing