Lorsque nous développons des applications, nous avons souvent besoin d’insérer des fonctionnalités pour filtrer les données venant des utilisateurs, pour rechercher et/ou extraire des éléments dans des chaines de caractères, pour manipuler des données textuelles selon le besoin, etc. Dans la plut part des cas, les utilisateurs doivent s’inscrire, s’enregistrer, souscrire à des services offerts via internet ou non… Et sachant que certains utilisateurs peuvent commettre des erreurs ou fournir des mauvaises informations en replissant les champs les indiqués, on a toujours d’établir quelques restrictions à l’égard des utilisateurs. Par exemple empêcher l’utilisateur d’utiliser autre chose à la place d’une adresse mail, d’un numéro de téléphone ou d’un numéro de carte bancaire, l’exiger à fournir un mot de passe fortifié, l’exiger à remplir ses éléments d’identité sans utiliser les caractères spéciaux, etc.
Pour ce faire, il y a plusieurs façons de procéder. Cependant, les expressions régulières sont les mieux indiquées et les plus utilisées.
1. Qu’est-ce qu’une expression régulière ?
Par simplification alphabétique, une expression régulière est souvent appelée regex, pour dire Regular expression en Anglais. Une expression régulière est une chaine de caractères et de métacaractères permettant de manipuler d’autres chaines de caractères selon les besoins, les règles et les attentes fixés par le développeur. Une expression régulière permet de filtrer, de rechercher et de nettoyer des chaines de caractère conformément aux besoins du programme. Par exemple, l’expression régulière ^(abc) fait allusion à une chaîne de caractères qui doit nécessairement commencer par abc et rien d’autre pour que la chaîne soit acceptée. Nous allons y revenir dans les lignes qui suivent.
2. Pourquoi utiliser les expressions régulières ?
Il existe plusieurs manières d’effectuer des contrôles de saisie, de chercher des chaînes de caractères dans d’autres et d’y effectuer des manipulations impressionnantes, entre autres l’utilisation des expressions régulières. Ces dernières ne sont pas donc le seul moyen existant, mais elles représentent la technique la plus préférée par les développeurs. Quelle est la raison ? Bien qu’elles paraissent complexes et difficiles à maîtriser, les expressions régulières sont plus économiques en termes d’espaces, elles sont plus courtes, simples et concises et offrent des possibilités énormes seulement en quelques lignes de code.
Par exemple, la Regex suivante : r"^(\+243)([ .-]?[1–9]{1}[0–9]{2})([ .-]?[0–9]{3}){2}$"
suffit à une application pour exiger à l’utilisateur de fournir son numéro de téléphone de trois manières seulement : +243 824 856 897 ; +243.824.856.897 ; +243–824–856–897. Comme vous le voyez, tous les numéros commencent par +243, après trois chiffres il faut soit un espace, soit un point, soit un tiret et le chiffre qui vient directement après le +243 doit être différent de 0. En plus, cette Regex montre que taille du numéro demandé ne doit pas aller ni au-delà ni en deçà de 12 chiffres. On peut donc demander à l’application de ne pas accepter tout numéro de téléphone fourni qui ne soit pas écrit sous l’une de ces trois formes. Nous reviendrons sur cette expression plus tard pour voir comment elle fonctionne et par quelle magie elle est capable de telles restrictions. Imaginons maintenant l’instruction qu’il nous faudrait à la place d’une telle Regex si nous résolvons de ne pas utiliser les expressions régulières. Il y aurait évidement la présence des plusieurs instructions conditionnelles, des boucles et d’autres méthodes selon la manière dont chacun peut se le représenter.
3. Dans quels cas utiliser les Expressions régulières ?
Les Regex sont importantes lorsqu’on est devant des cas où la manipulation des chaînes de caractères s’avère complexe avec la programmation standard dans les activités d’administration système, de développement logiciel et de traitement automatique du langage naturel. Elles sont mieux indiquées pour effectuer le filtrage et le nettoyage des données chaînées.
4. Comment utiliser les expressions régulières ?
Premièrement, il faut savoir que les expressions régulières sont utilisables dans tous les langages de programmation. Il est demandé juste de les adapter conformément aux normes du langage que l’on utilise. Dans ce module, tous les exemples sont basés sur le langage Python.
Deuxièmement, la maîtrise parfaite de l’utilisation des Regex n’est jamais une chose évidente. Il faut avoir parcouru et exploité toutes les possibilités dans lesquelles ont peut les utiliser pour en arriver là. C’est pourquoi l’on recommande de recourir toujours à la documentation pour en savoir plus et pour un meilleur usage selon le langage de programmation utilisé. Voici le lien qui conduit directement à la documentation. Cependant, le principe d’utilisation est ce qu’il ne faut jamais perdre des yeux peu importe le langage si l’on souhaite utiliser les expressions régulières.
En python, l’utilisation des Regex va de pair avec le module RE. Sans entrer en détails, nous utiliserons uniquement les fonctions nécessaires pour notre compréhension. Toutefois, si l’on a le désir et le goût de devenir expert en utilisation de module, on peut toujours recourir à la documentation de la bibliothèque Python.
Comment fonctionnent les Regex ? Nous avons vu plus haut qu’une Regex est une chaîne de caractères (alphanumériques) décrivant une ou d’autres chaines conformément à une syntaxe bien précise. Ainsi, dans une expression régulière, chaque caractère à une signification et joue un rôle bien déterminé. Certains caractères sont appelés métacaractères spéciaux en ce sens qu’ils signifient bien au-delà de ce qu’ils représentent comme symbole ou caractère. Il s’agit essentiellement de caractères suivants : . ^ $ * + ? { } [ ] \ | ( )
Tous ces caractères sont bien explicités dans la documentation.
1. Le Point .: il correspond à tous les caractères excepté le caractère de retour à la ligne.
2. Le chapeau ^ : indique le début d’une chaîne.
3. Le $ : indique la fin d’une chaîne
4. L’astérisque * : un caractère de répétition qui va de 0 à n fois. Il agit uniquement sur le caractère ou le groupe de caractères que le précède.
5. Le signe + : un caractère de répétition qui va de 1 à n fois.
6. Le point d’interrogation ?: un caractère de répétition qui va de 0 à 1 fois.
7. Les accolades {} : caractère permettant de personnaliser les répétitions au sein d’une chaîne.
8. Les crochets []: permettent de définir un classe
9. L’antislash \: caractère d’échappement
10. La barre verticale | : présente le OR logique
11. Les parenthèses () : permettent de définir un groupe.
Les exemples qui suivent vont nous permettre de comprendre la signification et le mode d’emploi de tous ces caractères dans la formulation des expressions régulière.
Expression | Correspond à | Ne correspond pas à | |
^ | ^bon | bonjour, bon, bonne, bond | rebond, 2bon |
$ | al$ | Animal, banal, mal | AnimAl, banale, mâle |
* | 08*a | 0a, 08a, 088a, d0a, 9908a | 08, 88a, 998a |
+ | 08+a | 08a, 088a, d08a, 9908a | 0a, 08, 88a, 998a |
{ } | A{2,4} | AA, AAA, AAAA | A, AAAAA |
[ ] | [1234] ou [1-4] | 1, 2, 3, 4 | 12, 1234, 24 |
\ | \+23 | +23 | \+23 |
( ) | (abc) | abc | bca, Abc |
Revenons sur les classes, les groupes, les accolades et l’antislash.
a. Les classes des caractères
On définit une classe en utilisant les crochets []. Une classe correspond à un seul caractère sur l’ensemble ou l’étendue des caractères qu’elle contient. Lorsqu’on veut construire une classe sur un plage, on utilise le trait d’union. Une classe comme [0123456789] peut aussi être écrite [0–9] et correspond à un des dix chiffres constituant la classe. De même, au lieu de reprendre les lettres de a à z pour définir un classe, on peut écrire directement [a-z] dans le cas du minuscule et [A-Z] pour le majuscule. Il est possible d’étendre une classe sur plusieurs plages de caractères. Ainsi, la classe [A-Za-z0–9] correspond à un caractère qui peut être une lettre majuscule, une lettre minuscule ou un chiffre. Les métacaractères repris dans une classe n’ont pas d’autre signification que le caractère ou le symbole qu’ils représentent. On peut donc écrire [0*$+^]. Cette expression correspond exactement à l’un des caractères repris dans la classe. Cependant, lorsque entre les crochets le ^ commence la chaine définissant la classe, il joue le rôle d’une négation. D’après ce principe, la classe [^f5] correspond à tous les caractères sauf f et 5. Certains métacaractères peuvent nous aider à écrire les mêmes classes autrement. Il s’agit notamment de :
\d : Correspond à n’importe quel caractère numérique ; équivalent à la classe [0–9].
\D : Correspond à n’importe caractère non numérique ; équivalent à la classe [^-9].
\s : Correspond à n’importe quel caractère “blanc” ; équivalent à la classe [ \t\n\r\f\v].
\S : Correspond à n’importe caractère autre que “blanc” ; équivalent à la classe [^ \t\n\r\f\v].
\w : Correspond à n’importe caractère alphanumérique ; équivalent à la classe [a-zA-Z0–9_].
\W : Correspond à n’importe caractère non-alphanumérique ; équivalent à la classe [^a-zA-Z0–9_].
Ces métacaractères cités ci-haut gardent leurs signification et leurs rôles lorsqu’ils sont repris entre crochets : la classe [\s.,:] à tous les caractères blancs ou un point. ou une virgule , ou deux points :.
b. Les groupes
Si une classe correspond toujours à l’un des caractères qu’il définit, un groupe, lui, correspond strictement à l’ensemble des caractères constituant le groupe. Un groupe est défini entre (). Exemple : (abcd). Cette expression correspond exactement et uniquement à abcd.
c. Les accolades
Nous avons précisé plus haut que les accolades sont utilisées pour personnaliser les occurrences. Il y a donc plusieurs formes reconnues pour jouer avec les accolades, précisément :
{n} : n fois ; {m,n} : de m à n ; {,n} : de 0 à n ; {m,} : de m à l’infini.
d. L'antislash \
Parfois, il arrive que l’on veuille reprendre l’antislash lui-même ou un caractère d’échappement dans une expression. Pour ce faire, on est toujours obligé de de doubler l’antislash pour permettre au langage de comprendre qu’il ne s’agit pas d’un échappement mais d’un caractère simplement. Par exemple, si on cherche à vérifier la chaîne \255 dans une chaîne, l’expression régulière doit être écrite de la manière suivante : \255. Cela est un travail laborieux et délicat et conduit souvent à des erreurs. Pour éviter ce problème, Python dispose d’un autre format pour représenter les chaines des caractères en plaçant la lettre r devant la chaîne concernée. Au lieu d’écrire “\255” on écrira r”\255".
Pratiquons
Maintenant que nous avons compris comment fonctionnent les expressions régulières, exerçons-nous un peu.
- Supposons que l’on veuille contrôler si les données fournies par un utilisateur est un numéro de téléphone sachant qu’un numéro de téléphone compte 10 chiffres et le premier chiffre est un 0 et le second chiffre est différent de 0.
import re
chaine = input("Entrez un numéro de télépone :")
regex = r"^0[1-9]{1}[0-9]{8}$"
while re.search(regex, chaine) is None:
chaine = input("Veuillez entrer un numéro valide :")
Dans ce petit code, nous commençons d’abord par importer le module RE de la libraire python. C’est grâce à ce module qu’il nous est possible de manipuler les expressions régulières en python. Ensuite, nous avons la variable regex qui contient notre expression régulière. Dans cette expression nous avons une chaîne précédée de r, pour revenir à notre format de représentation des chaînes en python évitant tout soucis avec les caractères d’échappement. Il est vrai que dans cette expression, cette expression, ce format n’est pas nécessaire parce qu’il n’y a aucun caractère problématique. Mais il est temps que nous nous familiarisions avec. Dans la boucle while, nous utilisons la méthode search du module RE pour vérifier notre chaîne. Cette méthode reçoit deux paramètres, l’expression régulière et la chaîne à vérifier. Si la chaine ne correspond pas à la description de l’expression régulière, la méthode renvoie None. Sinon, elle renvoie une instance « match object » contenant les informations relatives à la correspondance : position de départ et de fin, la sous-chaîne qui correspond et d’autres informations.
Revenons à l’expression régulière elle-même. Nous avons d’abord ^0 qui signifie que la chaîne à vérifier doit commencer par 0. Ensuite la classe [1–9] d’occurrence {1}. Cela correspond tout simplement à un chiffre différent de 0 repris une seule fois. Nous avons également la classe [0–9] d’occurrence {8}. Cela correspond à une séquence de 8 chiffres pris dans la plage de 0 à 9. Tous ces chiffres peuvent se ressembler ou non. Enfin nous avons $ qui matérialise la fin de notre chaîne. C’est pour dire que notre chaîne doit finir après la séquence de 8 chiffres qui précèdent. Notez que si notre expression comme par ^ et fin par $, cela signifie que la chaîne à vérifier ne doit pas aller au-delà de l’expression régulière elle-même. L’expression détermine dans ce cas le début et la fin de la chaîne de vérification. Dans notre exemple, si nous avions omis le $, cela donnerait la possibilité d’écrire un numéro qui dépasse dix chiffres. Le programme ne vérifierait que la correspondance par rapport aux dix premiers chiffres pour valider.
- Dans l’exemple précédent, le numéro de téléphone ne peut être fourni que sur dix caractères. Pas d’espace ni de trait entre les chiffres. Perfectionnons-le. Supposons que l’on demande tolérer les espaces ou les traits, spécialement entre le 3ème et le 4ème chiffres, et entre le 6ème et le 7ème chiffres. On aura le code suivant :
import re
chaine = input("Entrez un numéro de télépone :")
regex = r"^0([1-9]{2})([ -]?[0-9]{3})([ -]?[0-9]{4})$"
while re.search(regex, chaine) is None:
chaine = input("Veuillez entrer un numéro valide :")
Comme vous le remarquez, notre expression a changé. Chaque jeu des parenthèses représente un groupe. Mais l’élément étrange pour nous est l’écriture [ -]? que nous retrouvons dans les deux derniers groupes. Cela représente tout simplement un classe correspondant à un espace ou un trait repris 0 ou 1 fois à cause du signe ?. Le premier groupe représente deux chiffres différents de 0, le deuxième groupe représente trois chiffres précédés d’un espace ou d’un trait ou d’aucun d’entre les deux. Le dernier groupe représente les quatre derniers chiffres.
- Perfectionnons notre représentation en obligeant l’utilisateur de fournir un numéro contenant l’indicateur téléphonique de son pays. Un indicateur téléphonique commence par le signe + et ne peut contenir qu’entre 1 et 3 chiffres. La présence de l’indicateur entraîne l’absence du 0 qui commence chaque numéro de téléphone.
import re
chaine = input("Entrez un numéro de télépone :")
regex = r"^(\+[1-9]{1,3})([ -]?[0-9]{1}[0-9]{2})([ -]?[0-9]{3}){2}$"
while re.search(regex, chaine) is None:
chaine = input("Veuillez entrer un numéro valide :")
Il existe donc plusieurs possibilités d’utiliser les expressions régulières. Il suffit d’en connaître le mode d’emploi. Nous pouvons les utiliser pour contrôler les adresses mails, les mots de passe, corriger les fichiers text, html, xml etc. Ce sera notre prochain challenge.
Dans les trois cas, nous avons utilisé uniquement la méthode search pour manipuler les expressions. Il y a beaucoup d’autres méthodes auxquelles nous pouvons recourir selon les cas. En voici quelques-unes :
Methode / Attribut | Fonction |
match() | Détermine si la RE fait correspond dès le début de la chaîne. |
search() | Analyse la chaîne à la recherche d'une position où la RE correspond. |
findall() | Trouve toutes les sous-chaînes qui correspondent à la RE et les renvoie sous la forme d'une liste. |
finditer() | Trouve toutes les sous-chaînes qui correspondent à la RE et les renvoie sous la forme d'un itérateur. |
split() | Découpe la chaîne de caractère en liste, la découpant partout où la RE correspond |
sub() | Recherche toutes les sous-chaînes de caractères où la RE correspond et les substitue par une chaîne de caractères différente |
subn() | Fait la même chose que sub() , mais renvoie la nouvelle chaîne et le nombre de remplacements effectués |
group() | Renvoie la chaîne de caractères correspondant à la RE |
start() | Renvoie la position de début de la correspondance |
end() | Renvoie la position de fin de la correspondance |
span() | Renvoie un n-uplet contenant les positions (début, fin) de la correspondance |
Toutes ces méthodes ont un mode d’emploi aussi simple que la méthode que nous avons utilisée dans les exemples précédents. Une explication plus détaillée concernant l’usage de ces méthodes est disponible dans la documentation python.
Veuillez noter que l’utilisation des expressions régulières est un sujet abondant. La compilation, les groupes nommés, etc. sont d’autres notions qu’il vaille la peine de connaître mais que nous ne développerons pas dans ce module.
Dans la prochaine publication, nous y reviendrons. Elles nous seront utiles pour réaliser un projet de correction de balisage dans des fichiers html.