Forums Développement Multimédia

Les formations Mediabox
Les formations Mediabox

Les expressions régulières compatibles PERL en PHP, partie 2

Compatible PHP. Cliquer pour en savoir plus sur les compatibilités.Par qwix, le 13 août 2005

Attention: Les exemples dans le fichier joint sont écrits en utilisant le modificateur x dans les options, celui-ci n'étant pas disponible pour les versions de php inférieures à 4.3.2, vous trouverez aussi le modèle en non commenté, compatible pour toutes les versions.

introduction

Dans la seconde partie de ce tutorial, nous allons voir comment rédiger des expressions régulières, cependant ne comptez pas être à l'aise avec leur notation à la fin de ce tutorial car il faut beaucoup de pratique pour pouvoir les lire 'comme un journal' et dans le cas de certains modèles compliqués, il vaut mieux expliquer leur fonctionnement dans un commentaire à part sinon la personne qui va relire votre script ne sera pas capable d'effectuer des modifications et des corrections de bug(he oui le bug zéro n'existe pas).

Comment poser le problème ?

Lorsque l'on doit résoudre un problème en utilisant les expressions régulières, la première chose à ne pas faire et de se lancer 'tête baissée' dans la rédaction du modèle, mais plutôt d'essayer de réfléchir à ce que l'on veut exactement, en appliquant une méthode que j'apellerais la méthode entonnoir, c'est à dire que l'on part d'un cas général pour restreindre de plus en plus les possibilité, autrement dit lorsque l'on réfléchit au problème on part d'un large champ d'investigation pour ensuite penser aux plus de cas particuliers possibles. Mais avec cette méthode il ne faut pas tomber dans l'extrémisme non plus car à force de vouloir traiter trop de cas particuliers ou être vraiment trop précis, on s'écarte du sujet et de plus on aboutit généralement à une expression illisible. Je me rapelle qu'une fois, j'avais voulu être plus royaliste que le roi en faisant une vérification d'email avec une expression régulière, et en voulant traiter le plus de cas particulier possible, j'ai fait n'importe quoi et mon modèle ne reconnaissait même plus le '@' des emails hi hi le boulet . Donc ne faites pas comme moi essayez d'établir un bon rapport entre efficacité et lisibilité ;) .

Faire le bon rapport entre lisibilité et efficacité Comme cité ci-dessus, il est fréquent de devoir choisir entre lisibilité et efficacité, car:

  1. on peut obtenir des expressions illisibles et fausses
  2. on peut avoir un ralentissement du à une expression trop chargée
  3. un cumul des deux problème que je viens de citer

De toute façon le modèle idéal et efficace à 100% existe rarement. En effet la façon d'écrire de chaque personne est tellement différente que l'on oublie toujours un cas particulier où alors son traitement est assez superflu, bref le modèle idéal n'exite pas souvent à mon avis. Mais ce n'est pas pour autant qu'il vous faut renoncer aux expressions régulières car elles vous permettent de faire tellement de choses tellement rapidement :).

Techniques pratiques

A mon avis la meilleurs technique pour énoncer un problème de façon clair est de le dire à hauter voix, par exemple pour un nom de fichier, on peut se dire: “Je cherche une chaine qui soit composée d'aux moins deux parties, une pour le nom du fichier, l'autre ou les autres pour l'extension, la séparation entre chaque devant être un point”. On arrive ainsi à énoncer le problème rapidement et la solution vient très vite :)

Exemples applicatifs:

Passons maintenant à la résolution de quelques cas concrets qui j'espère vous seront utiles:

Reconnaitre un email:

C'est le problème de beaucoup de monde cet email, il faut d'abord que vous sachiez que le modèle que vous allez voir n'est pas infaillible, comme la plupart de ceux que j'ai pu voir sur le net, chacun à sa technique propre, mais il faut savoir que les adresses emails doivent en théorie respecter une norme que l'on apelle norme RFC822, et pour vérifier qu'une adresse email respecte totalement cette norme, l'expression régulière qu'il faudrait utiliser est disponible ici, comme vous pouvez le voir c'est absolument illisible et incompréhensible(attention je ne remet pas en doute son efficacité, je serais bien incapable de rédiger une telle chose alors delà à critiquer….).

Essayons plutôt de rédiger notre propre modèle. Un email est généralement constitué de cette façon:

utilisateur@machine

Ce qui simplifie déjà le problème, il nous suffit de créer un modèle pour l'utilisateur et un modèle pour la machine, le tout séparé par le caractère '@'.

Réglons son compte à l'utilisateur: Un nom d'utilisateur est toujours composé d'au moins 1 caractère alphanumérique(c'est à dire soit un chiffre, soit une lettre) mais peut aussi contenir un ou plusieurs tirets, un ou plusieurs points, ou un plusieurs tirets bas. Nous pouvons donc en déduire ceci:

^[-a-z0-9]+

Mais il y a un problème, si le nom d'utilisateur commence par un caractère qui n'est pas alphanumérique, notre modèle ne va pas s'en rendre comte et accepter l'email, il nous faut donc régler ce problème. Nous pouvons le régler en faisant tout simplement:

^[-a-z0-9.]*

Ainsi, il est obligatoire de saisir un caractère alphanumérique en premier, nous ajoutons à cela la possibilité d'avoir autant de caractères alphanumériques, de tirets et de points par la suite, ce qui nous donne une vérification acceptable du nom d'utilisateur .

Réglons son compte au nom de machine: Un nom de machine est toujours constitué d'au moins un caractère suivi d'un point et d'une extension qui peut être multiple, contenir des tirets et des tirets bas. Dans ce cas nous pourrions régler le problème comme ceci:

[-a-z0-9]+\.[-a-z0-9_.]+$

Comme vous pouvez le voir, je n'ai pas utilisé \w car celui-ci accepte les lettres accentuées qui n'ont rien à faire dans un nom de machine ;). De plus nous ne voulons pas non plus traiter les caractères en majuscules donc nous les mettons pas dans les classes de caractères, de toute façon, nous pouvons toujours nous débarasser de ce problème soit en utilisant la fonction strtolower() ou en utilisant l'option 'i' qui permet une reconnaissance insensible à la casse ;), nous allons opter pour cette solution.

Nous avons donc le modèle complet suivant:

#^[-a-z0-9.]*@[-a-z0-9]+\.[-a-z0-9_.]+$#i

Qui est à mon avis assez puissant pour reconnaitre une adresse email fausse syntaxiquement ;) Vous pouvez tester cette expression régulière avec le script suivant:

<?php
$texte = "qwix@dreamweaver-forum.net" ;
$pattern = "#^[a-z0-9][-a-z0-9.]*@[-a-z0-9]+\.[-a-z0-9_.]+$#i" ;
if(preg_match($pattern, $texte))
    echo "mail correct\n" ;
else
    echo "mail incorrect\n" ;
?>

J'ajouterais que pour cette vérification d'email, il n'est pas vraiment utile(à mon sens en tout cas, je ne prétends pas détenir la science infuse) d'utiliser les parenthèses si ce n'est pour capturer une partie ou l'email complètement. En effet dans la première partie de ce tutorial, j'avais expliqué que les parenthèses servaient à capturer du texte mais aussi à décrire une séquence de texte, c'est à dire une suite de caractère dans un ordre précis, mais dans le cas de l'email, les caractères sont les mêmes d'un côté ou de l'autre de l'arobase '@', il est possible d'avoir un mélange de caractère alhanumériques, de tirets et de points, d'un côté ou de l'autre de l'arobase, et ce dans n'importe quel ordre, c'est pourquoi je n'ai pas utilisé de parenthèses dans cette expression, mais vous pouvez bien sur les mettre si cela vous est utile ;).

Reconnaitre une URL:

Il est toujours utile de pouvoir reconnaitre une URL dans un texte, on peut ainsi facilement créer des liens hypertextes à la volée. La contrainte pour trouver une url est que l'URL doit être de la forme: http://www.site.com OU www.site.com OU http://site.com

En effet il est assez difficile de savoir si la chaine 'site.com' est une URL lorsqu'il est tapé dans une phrase.

Une URL peut aussi contenir des caractères tels que:

  • le tiret '-'
  • l'esperluette '&'
  • le slash '/'

En gros nous avons le plus généralement le modèle suivant:

http://machine/chemin

ou alors pour être plus précis:

protocole://machine/chemin

Il faut en tout premier lieu vérifier si le protocole est présent, si c'est le cas alors le www. n'est pas obligatoire sinon le www. est obligatoire. On peut traduire cette contrainte comme ceci:

SI la chaine http: OU https: est présente ALORS la séquence www. devient facultative SINON la séquence www. devient obligatoire FIN SI

C'est une bonne occasion de voir la notion de tests conditionnels (?(if) then | else) ;) Voici le modèle à appliquer:

"#^(((?:http?)://)?(?(2)(www\.)?|(www\.){1})[-a-z0-9_]{2,}\.[-a-z0-9.]{2,}[-a-z0-9\/&\?=.]{2,})$#i"

Avant toute chose, je tiens à préciser que ce modèle n'est pas infaillible, mais après avoir comparé celui-ci à d'autres que j'ai pu trouver dans des livres parlant des expressions régulières, j'en déduit qu'il y a forcément des failles, et que pour obtenir une vérification parfaite d'une URL il faudrait avoir un modèle excessivement lourd et compliqué. Celui-ci offre un bon rapport entre la lisibilité et l'efficacité, c'est pourquoi je me permet de vous le présenter, si toutefois vous avez des suggestions ou des améliorations, n'hésitez pas à me les faire parvenir par mail: qwix@dreamweaver-forum.net pour que je puisse les étudier.

Je n'expliquerais pas en détail ce modèle car il est disponible en version commentée dans le fichier joint en fin de tutorial, cependant je crois qu'il est bon que je découpe ce modèle afin que vous puissiez voir l'instruction conditionnelle.

((?:http?)://)? 
(?(2)
 (www\.)?
 |
 (www\.){1}
)

Tout d'abord la ligne

((?:http?)://)?

me permet de savoir si une séquence du type http: ou https: est présente.

L'instruction conditionnelle vient ensuite avec ceci (?(2)(www\.)?|(www\.){1}), souvenez vous de sa structure elle est toujours comme ceci: (?(if)condition vraie|condition fausse) dans ce cas si la condition dans notre modèle est 2 présent dans (?(2) ….), je vous doit quelques explications sur la nature de ce 2. Lorsque la partie IF est un nombre entre parenthèses, elle prend la valeur vraie si la paire correspondante de parenthèses capturantes a participé à la reconnaissance. Etant donné que la partie

((?:http?)://)

correspond à la deuxième parenthèse capturante, la première étant celle qui englobe tout le modèle il est normal que l'on retrouve ce 2 dans la condition du IF, pour faire simple c'est un peu comme une référence arrière à laquelle on a retiré les \\. La séquence (www\.)? correspond à la partie then, c'est à dire ce qui est interprété si la condition est vraie, dans notre cas la séquence www. est facultative si la chaine cible possède un protocole de la liste citée ci-dessus. La séquence (www\.){1} correspond à la partie else, c'est à dire ce qui est interprété si la condition est fausse, dans notre cas la séquence www. doit être obligatoirement présent si la chaine cible ne possède pas un protocole cité ci-dessus. Le reste est assez bien commenté dans le fichier joint, je pense que c'est assez clair.

Reconnaitre une adresse IP:

Essayons en premier lieu d'établir des contraintes :

  • une adresse IP doit toujours être constituée de chiffres
  • elle est toujours de la forme xxx.xxx.xxx.xxx
  • les chiffres vont de 0 à 255

Il est assez facile de dire à notre modèle de ne reconnaitre que des caractères numériques, il nous suffit d'utiliser le métacaractère \d. Par contre pour traiter des chiffres qui vont de 0 à 255, \d ne convient pas car il peut accepter des chiffres comme 999 qui n'existent pas dans les adresses IP.

La première solution qui vient à l'esprit lorsque l'on veut contrôler des adresses IP est de créer le modèle suivant:

#\d{1,3}.\d{1,3}.\d{1,3}.\d{1,3}#

Mais le problème c'est que \d va ainsi accepter des chiffres comme 999, en bref tout chiffre supérieur à 255. Comment faire alors ? Essayons déjà de traiter le problème des chiffres qui vont de 200 à 255. Il serait stupide de taper un séquence du genre (200|201…….|255), ce qui est trop long, trop fastidieux et trop lent. Dans la première partie de ce tutorial, j'avais expliqué la notion de métas classes qui permet de définir des caractère autorisés, c'est exactement ce qu'il nous faut, ainsi pour traiter des chiffres à centaines il nous suffit de faire comme ceci: Par exemple pour ne reconnaitre que des chiffres compris entre 100 et 199:

#1[0-9][0-9]#
ou plus court:
#1\d\d#

Appliquons maintenant cette technique aux adresses IP. Le problème réel est de reconnaitre un chiffre supérieur ou égal à 200 mais inférieur ou égal à 255, nous pouvons trouver la solution avec ce modèle:

#2[0-4][0-9]|25[0-5]#
ou plus clair:
#2[0-4]\d|25[0-5]#

Dans ce modèle nous avons deux possibilités:

  1. accepter un chiffre qui commence par 2, suivi d'un chiffre entre 0 et 4, suivi d'un chiffre entre 0 et 9, en clair nous acceptons un chiffre qui va de 200 à 249.
  2. accepter un chiffre qui commence par 25, suivi d'un chiffre compris entre 0 et 5, en clair nous acceptons un chiffre qui va de 250 à 255.

Pour reconnaitre les autres chiffres, on peut réfléchir de cette façon: les chiffres vont de 0 à 199 on a donc soit 1 soit 2 soit 3 chiffres au maximum, autrement dit, on peut traduire cette phrase par le modèle suivant:

#[01]?\d\d?#

qui signifie, un chiffre qui est soit 0 soit 1 mais facultatif, un chiffre compris entre 0 et 9 obligatoire, un chiffre compris entre 0 et 9 facultatif. Ce qui signifie que l'on accepte les chiffres compris entre 0 et 199.

Pour vérifier notre adresse IP nous n'avons plus qu'à combiner les modèles vus ci-dessus ce qui nous donne:

(?\d\d?|2[0-4]\d|25[0-5])

pour chaque triplet de chiffre dans l'adresse IP.

Voici le modèle final:

#^(?\d\d?|2[0-4]\d|25[0-5]).(?\d\d?|2[0-4]\d|25[0-5]).(?\d\d?|2[0-4]\d|25[0-5]).(?\d\d?|2[0-4]\d|25[0-5])$#

Reconnaitre des doublons:

Cet exercice va nous faire pratiquer les références arrières de façon simple. Lorsque l'on tape un texte, il est fréquent de taper deux fois de suite le même mot, comme par exemple 'le le four', 'la la serrure'…. Nous allons essayer de voir comment les trouver et ainsi les élimininer

Contrainte: Un doublon ressemble toujours à ceci: chaineX chaineX chaineY Autrement dit le doublons sont séparés par un espace. On pourrait dire qu'il peut ne pas y avoir d'espace entre eux mais dans ce cas, comment reconnaitre les doublons dans Lili(qui est un prénom) ou alors par exemple dans une chanson tralalalalalalalalala. C'est impossible, il faut le faire à la main, c'est pour ça que je pars du principe que des doublons sont toujours séparés par un espace.

Voici donc le modèle qui nous permet de les repérer:

#\s?(\w+)\s+(?=(\s?\\1\s+))#i

Je vous invite à lire les commentaires sur ce modèle dans le fichier joint, ce n'est pas très compliqué ;)

La seconde partie de ce tutorial est terminée, la troisième partie est plus une partie culturelle qui décrit brièvement le fonctionnement du moteur qui traite les expressions régulières. Si vous avez des questions n'hésitez pas à les poser dans ce forum.

:)


Tutorial de Qwix