samedi 20 août 2016

LES EXPRESSIONS RÉGULIÈRES (PARTIE 2/2)


Voici donc la suite (et fin) de notre aventure avec les expressions régulières.
Le mot d'ordre de ce chapitre est pratiquer. Hormis quelques points que nous allons aborder au début, vous connaissez l'essentiel sur les regex mais il vous manque le plus important : la pratique !
Dans la seconde moitié de ce chapitre, nous allons donc en construire ensemble, pour que vous voyiez comment il faut procéder pour arriver enfin à écrire ces $%@#$#% de regex !
Écrire un bout de regex comme on l'a fait jusqu'ici, c'est une chose, mais créer une regex complète, vous allez voir que c'est une toute autre paire de manches !

Une histoire de métacaractères

Pour commencer, et avant d'aller plus loin, il me semble important de porter à votre connaissance une nouvelle notion : les métacaractères.
Ce n'est pas une insulte de programmeur, mais un mot qui signifie tout simplement « caractères spéciaux ». Ce sont des caractères pas comme les autres qui ont un rôle ou un sens particulier.

Alerte mon Général ! Les métacaractères s'échappent !

Dans le langage PCRE (des regex), les métacaractères qu'il faut connaître sont les suivants :
# ! ^ $ ( ) [ ] { } ? + * . \ |
Il faut bien les retenir. Pour la plupart d'entre eux, vous les connaissez déjà.
Ainsi, le dollar « $ » est un caractère spécial parce qu'il permet d'indiquer une fin de chaîne.
De même pour l'accent circonflexe, le dièse, les parenthèses, les crochets, les accolades et les symboles « ? + * » : nous les avons tous utilisés dans le chapitre précédent, souvenez-vous.
Pour le point « . » et l'antislash « \ », vous ne les connaissez pas mais vous n'allez pas tarder à les apprendre.
Bon, ce sont des caractères spéciaux et chacun d'eux signifie quelque chose de précis. Et alors ?
Et alors, le problème vous tombe dessus le jour où vous voulez chercher par exemple « Quoi ? » dans une chaîne.
Comment écririez-vous la regex ? Comme ça ?
#Quoi ?#
Eh non, surtout pas ! Le point d'interrogation, vous le savez, sert à dire que la lettre juste avant est facultative (elle peut apparaître 0 ou 1 fois). Ici, l'espace devant le point d'interrogation serait donc facultatif, mais ce n'est pas ce qu'on veut faire !
Alors, comment faire pour faire comprendre qu'on recherche « Quoi ? » alors que le point d'interrogation a déjà une signification ?
Il va falloir l'échapper. Cela signifie que vous devez placer en fait un antislash « \ » devant un caractère spécial. Ainsi, la bonne regex serait :
#Quoi \?#
Ici, l'antislash sert à dire que le point d'interrogation juste après n'est pas un symbole spécial, mais bel et bien une lettre comme une autre !
Bien tordu tout ça, non ?
Pourtant, ce que vous devez retenir est simple : si vous voulez utiliser un caractère spécial dans votre recherche, il faut placer un antislash devant. Point barre.
Je vous donne quelques exemples d'utilisation, ça devrait bien vous faire rentrer ça dans la tête :
Chaîne
regex
Résultat
Je suis impatient !
#impatient \!#
VRAI
Je suis (très) fatigué
#\(très\) fatigué#
VRAI
J'ai sommeil…
#sommeil\.\.\.#
VRAI
Le smiley :-\
#:-\\#
VRAI

Le cas des classes

Il reste une dernière petite chose à voir (encore un cas particulier), et cela concerne les classes de caractères.
Jusqu'ici, vous avez mis des lettres et des chiffres entre les crochets ; par exemple :
#[a-z0-9]#
Oui mais, vous vous en doutez, vous avez le droit de mettre d'autres caractères, comme les accents (mais dans ce cas, il faut les énumérer un à un). Par exemple : [a-zéèàêâùïüë] et ainsi de suite.
Jusqu'ici, tout va bien. Mais si vous voulez lister aussi des caractères spéciaux, hum ? Par exemple un point d'interrogation (au hasard). Eh bien là, ça ne compte pas ! Pas besoin de l'échapper : à l'intérieur de crochets les métacaractères… ne comptent plus !
Ainsi, cette regex marche très bien :
#[a-z?+*{}]#
Elle signifie qu'on a le droit de mettre une lettre, un point d'interrogation, un signe +, etc.
3 cas particuliers, cependant.
  • « # » (dièse) : il sert toujours à indiquer la fin de la regex. Pour l'utiliser, vous DEVEZ mettre un antislash devant, même dans une classe de caractères.
  • « ] » (crochet fermant) : normalement, le crochet fermant indique la fin de la classe. Si vous voulez vous en servir comme d'un caractère que vous recherchez, il faut là aussi mettre un antislash devant.
  • « - » (tiret) : encore un cas un peu particulier. Le tiret – vous le savez – sert à définir un intervalle de classe (comme [a-z]). Et si vous voulez ajouter le tiret dans la liste des caractères possibles ? Eh bien il suffit de le mettre soit au début de la classe, soit à la fin. Par exemple : [a-z0-9-] permet de chercher une lettre, un chiffre ou un tiret.

Les classes abrégées

La bonne nouvelle, c'est que vous êtes maintenant prêts à réaliser quasiment toutes les regex que vous voulez.
La mauvaise, c'est que je viens de dire « quasiment ».
Oh rassurez-vous, ça ne sera pas long et vous ne sentirez aucune douleur (à ce stade, on ne ressent plus la douleur de toute façon).
Je souhaite juste vous montrer ce qu'on appelle les classes abrégées, et que moi j'appelle les raccourcis.
Certains de ces raccourcis ne vous seront pas indispensables, mais comme vous risquez de les rencontrer un jour ou l'autre, je ne voudrais pas que vous soyez surpris et que vous croyiez que je vous ai caché des choses.
Voici ce qu'il faut retenir :
Raccourci
Signification
\d
Indique un chiffre.
Ça revient exactement à taper [0-9]
\D
Indique ce qui n'est PAS un chiffre.
Ça revient à taper [^0-9]
\w
Indique un caractère alphanumérique ou un tiret de soulignement.
Cela correspond à [a-zA-Z0-9_]
\W
Indique ce qui n'est PAS un mot.
Si vous avez suivi, ça revient à taper [^a-zA-Z0-9_]
\t
Indique une tabulation
\n
Indique une nouvelle ligne
\r
Indique un retour chariot
\s
Indique un espace blanc
\S
Indique ce qui n'est PAS un espace blanc (\t \n \r)
.
Indique n'importe quel caractère.
Il autorise donc tout !
Il s'agit de lettres normales, mais quand on place un antislash devant, on leur donne une signification spéciale.
C'est l'inverse de ce qu'on faisait tout à l'heure : on utilisait un antislash devant les métacaractères pour leurenlever leur signification spéciale.
Allez, cette fois vous en savez suffisamment, on va pouvoir passer à la pratique !

Construire une regex complète

Vous allez enfin comprendre pourquoi vous en avez bavé tout le long !
Cette fois, nous allons toucher du concret à travers des exemples qui vous seront sûrement utiles. Nous allons construire de grosses regex ensemble, pour que vous compreniez la méthode. Ensuite, vous serez tout à fait capables d'inventer vos regex et de vous en servir pour vos scripts PHP !

Un numéro de téléphone

Pour cette première vraie regex, nous allons essayer de voir si une variable (entrée par un visiteur via un formulaire, par exemple) correspond bien à un numéro de téléphone.
Je vais me baser sur les numéros de téléphone français, il faudra donc m'excuser si vous n'êtes pas français et que vous ne connaissez pas. L'avantage, c'est que vous pourrez ensuite vous exercer à écrire cette regex pour les numéros de téléphone de votre pays.
Pour rappel (et pour ceux qui ne savent pas, donc), un numéro de téléphone français comporte 10 chiffres. Par exemple : « 01 53 78 99 99 ». Il faut respecter les règles suivantes :
  • le premier chiffre est TOUJOURS un 0 ;
  • le second chiffre va de 1 à 6 (1 pour la région parisienne… 6 pour les téléphones portables), mais il y a aussi le 8 (ce sont des numéros spéciaux). À noter que le 7 et le 9 commencent à être utilisés mais que nous ne les prendrons pas en compte dans nos exemples ;
  • ensuite viennent les 8 chiffres restants (ils peuvent aller de 0 à 9 sans problème).
Pour commencer, et pour faire simple, on va supposer que l'utilisateur entre le numéro de téléphone sans mettre d'espace ni quoi que ce soit (mais on complique juste après, et vous verrez que c'est là le véritable intérêt des regex).
Ainsi, le numéro de téléphone doit ressembler à ça : « 0153789999 ». Comment écrire une regex qui corresponde à un numéro de téléphone comme celui-ci ?
Voici comment je procède, dans l'ordre, pour construire cette regex.
  1. Primo, on veut qu'il y ait UNIQUEMENT le numéro de téléphone. On va donc commencer par mettre les symboles ^ et $ pour indiquer un début et une fin de chaîne : #^$# 
  2. Continuons. On sait que le premier caractère est forcément un 0. On tape donc :
    #^0$# 
  3. Le 0 est suivi d'un nombre allant de 1 à 6, sans oublier le 8 pour les numéros spéciaux. Il faut donc utiliser la classe [1-68], qui signifie « Un nombre de 1 à 6 OU le 8 » :
    #^0[1-68]$# 
  4. Ensuite, viennent les 8 chiffres restants, pouvant aller de 0 à 9. Il nous suffit donc d'écrire [0-9]{8}pour indiquer que l'on veut 8 chiffres. Au final, ça nous donne cette regex :
    #^0[1-68][0-9]{8}$#
Et c'est tout !
Bon, je vois que vous êtes en forme, alors ne nous arrêtons pas en si bon chemin et améliorons cette regex. 
Maintenant, on va supposer que la personne peut taper un espace tous les deux chiffres (comme c'est courant de le faire en France), mais aussi un point ou un tiret. Notre regex devra donc accepter les numéros de téléphone suivants :
  • 0153789999
  • 01 53 78 99 99
  • 01-53-78-99-99
  • 01.53.78.99.99
  • 0153 78 99 99
  • 0153.78 99-99
  • etc.
Et c'est là qu'est toute la puissance des regex !
Les possibilités sont très nombreuses, et pourtant vous avez juste besoin d'écrire la regex correspondante.
On reprend donc la création de notre regex.
  1. Primo, le 0 puis le chiffre de 1 à 6 sans oublier le 8. Ça, ça ne change pas : #^0[1-68]$#
  2. Après ces deux premiers chiffres, il peut y avoir soit un espace, soit un tiret, soit un point, soit rien du tout (si les chiffres sont attachés). On va donc utiliser la classe [-. ] (tiret, point, espace). 

    Mais comment faire pour dire que le point (ou le tiret, ou l'espace) n'est pas obligatoire ? Avec le point d'interrogation, bien sûr ! Ça nous donne : #^0[1-68][-. ]?$#
  3. Après le premier tiret (ou point, ou espace, ou rien), on a les deux chiffres suivants. On doit donc rajouter [0-9]{2} à notre regex.
    #^0[1-68][-. ]?[0-9]{2}$#
  4. Et maintenant, réfléchissez. Il y a moyen de terminer rapidement : on a juste besoin de dire que « [-. ]?[0-9]{2} » doit être répété quatre fois, et notre regex est terminée ! On va se servir des parenthèses pour entourer le tout, et placer un {4} juste après pour indiquer que tout ça doit se répéter quatre fois. Ce qui nous donne finalement :
    #^0[1-68]([-. ]?[0-9]{2}){4}$#
Vous pouvez l'encadrer en gros en poster dans votre chambre : c'est votre première VRAIE regex !
#^0[1-68]([-. ]?[0-9]{2}){4}$#
Voici un petit script que j'ai fait rapidement, pour que vous puissiez tester toute la puissance des regex :
<p>
<?php
if (isset($_POST['telephone']))
{
$_POST['telephone'] = htmlspecialchars($_POST['telephone']); // On rend inoffensives les balises HTML que le visiteur a pu entrer
if (preg_match("#^0[1-68]([-. ]?[0-9]{2}){4}$#", $_POST['telephone']))
{
echo 'Le ' . $_POST['telephone'] . ' est un numéro <strong>valide</strong> !';
}
else
{
echo 'Le ' . $_POST['telephone'] . ' n\'est pas valide, recommencez !';
}
}
?>
</p>
<form method="post">
<p>
<label for="telephone">Votre téléphone ?</label> <input id="telephone" name="telephone" /><br />
<input type="submit" value="Vérifier le numéro" />
</p>
</form>
Vous pouvez essayer tous les numéros de téléphone que vous voulez, avec des espaces au milieu ou non si ça vous chante : la regex gère tous les cas.

Une adresse e-mail

Ça serait dommage de s'arrêter sur une si bonne lancée.
Je vais donc vous présenter un deuxième exemple qui vous sera certainement utile : tester si l'adresse e-mail est valide.
Alors, avant de commencer quoi que ce soit, et pour qu'on soit bien d'accord, je vais rappeler comment est construite une adresse e-mail.
  1. On a tout d'abord le pseudonyme (au minimum une lettre, mais c'est plutôt rare). Il peut y avoir des lettres minuscules (pas de majuscules), des chiffres, des points, des tirets et des underscores « _ ».
  2. Il y a ensuite une arobase : @.
  3. Ensuite il y a le nom de domaine. Pour ce nom, même règle que pour le pseudonyme : que des minuscules, des chiffres, des tirets, des points et des underscores. La seule différence – vous ne pouviez pas forcément deviner – c'est qu'il y a au moins deux caractères (par exemple, « a.com » n'existe pas, mais « aa.com » oui).
  4. Enfin, il y a l'extension (comme « .fr »). Cette extension comporte un point, suivi de deux à quatre lettres (minuscules). En effet, il y a « .es », « .de », mais aussi « .com », « .net », « .org », « .info », etc.
L'adresse e-mail peut donc ressembler à j.dupont_2@orange.fr.
Construisons la regex.
  1. Primo, comme tout à l'heure, on ne veut QUE l'adresse e-mail ; on va donc demander à ce que ça soit un début et une fin de chaîne : #^$# 
  2. Ensuite, on a des lettres, chiffres, tirets, points, underscores, au moins une fois. On utilise donc la classe[a-z0-9._-] à la suite de laquelle on rajoute le signe + pour demander à ce qu'il y en ait au moins un :
    #^[a-z0-9._-]+$# 
  3. Vient ensuite l'arobase (là ce n'est pas compliqué, on a juste à taper le caractère) :
    #^[a-z0-9._-]+@$# 
  4. Puis encore une suite de lettres, chiffres, points, tirets, au moins deux fois. On tape donc {2,} pour dire « deux fois ou plus » :
    #^[a-z0-9._-]+@[a-z0-9._-]{2,}$# 
  5. Ensuite vient le point (de « .fr » par exemple). Comme je vous l'ai dit plus haut, c'est un caractère spécial qui sert à indiquer « n'importe quel caractère » (même des accents). Or, ici, on veut enlever sa signification au point pour dire que l'on veut le symbole point dans notre regex. On va donc mettre un antislash devant :
    #^[a-z0-9._-]+@[a-z0-9._-]{2,}\.$# 
  6. Enfin, pour terminer, il nous faut deux à quatre lettres. Ce sont forcément des lettres minuscules, et cette fois pas de chiffre ou de tiret, etc. On écrit donc :
    #^[a-z0-9._-]+@[a-z0-9._-]{2,}\.[a-z]{2,4}$# 
Et voilà encore une nouvelle regex de bouclée !
#^[a-z0-9._-]+@[a-z0-9._-]{2,}\.[a-z]{2,4}$#
Vous sentez que vous commencez à parler chinois, non ? ;-)
Allez, je suis en forme et de bonne humeur, je vous donne le script PHP pour tester cette regex :
<p>
<?php
if (isset($_POST['mail']))
{
$_POST['mail'] = htmlspecialchars($_POST['mail']); // On rend inoffensives les balises HTML que le visiteur a pu rentrer
if (preg_match("#^[a-z0-9._-]+@[a-z0-9._-]{2,}\.[a-z]{2,4}$#", $_POST['mail']))
{
echo 'L\'adresse ' . $_POST['mail'] . ' est <strong>valide</strong> !';
}
else
{
echo 'L\'adresse ' . $_POST['mail'] . ' n\'est pas valide, recommencez !';
}
}
?>
</p>
<form method="post">
<p>
<label for="mail">Votre mail ?</label> <input id="mail" name="mail" /><br />
<input type="submit" value="Vérifier le mail" />
</p>
</form>
Testez donc des adresses comme :
  • the_cypher@hotmail.com ;
  • business_consultants@free4work.info ;
  • mega-killer.le-retour@super-site.fr.st ;
  • etc., etc.
Alors, ça vous plaît ?
Je reconnais que ça paraît être très complexe quand on lit une regex la première fois. J'imagine la tête que vous avez dû faire lorsque je vous en ai montré une dans l'introduction du chapitre précédent. ;-)
Mais bon, vous voyez le progrès ? On vient ensemble d'écrire un de ces fameux trucs imbuvables, et je ne pense pas que beaucoup d'entre vous pensaient y arriver en lisant le chapitre précédent !
Pourtant nous y voilà : nous avons réussi à écrire deux regex complètes. Je ne vais pas vous faire travailler sur une troisième, vous avez – je pense – compris le principe et vous savez vous débrouiller comme des grands.
Je veux juste vous montrer une dernière petite chose avant de passer à la dernière notion importante que nous aborderons (capture et remplacement).

Des regex… avec MySQL !

Comme quoi, vous allez vraiment être heureux d'en avoir un peu bavé pour arriver jusqu'ici.
Eh oui, grrrande nouvelle : MySQL comprend les regex !
Et ça, bah c'est tout bénef' pour vous : vous venez d'apprendre à écrire des regex, vous n'avez presque rien à savoir de plus pour vous en servir avec MySQL.
Il faut savoir cependant que MySQL ne comprend que les regex en langage POSIX, et pas PCRE comme on a appris.
Vous avez juste besoin de retenir ce qui suit pour faire une regex POSIX :
  • il n'y a pas de délimiteur ni d'options. Votre regex n'est donc pas entourée de dièses ;
  • il n'y a pas de classes abrégées comme on l'a vu plus haut, donc pas de \d, etc. En revanche, vous pouvez toujours utiliser le point pour dire : « n'importe quel caractère ».
Le mieux, bien entendu, c'est toujours un bon exemple. Supposons que vous ayez stocké les IP de vos visiteurs dans une table visiteurs et que vous vouliez les noms des visiteurs dont l'IP commence par « 84.254 » :
SELECT nom FROM visiteurs WHERE ip REGEXP '^84\.254(\.[0-9]{1,3}){2}$'
Cela signifie : Sélectionne tous les noms de la table visiteurs dont l'IP commence par « 84.254 » et se termine par deux autres nombres de un à trois chiffre(s) (ex. : 84.254.6.177).
Toute la puissance des regex dans une requête MySQL pour faire une recherche très précise… Ça ne se refuse pas. ;-)
Je ne m'étends pas plus là-dessus, je sais que vous saurez vous débrouiller si jamais cela vous est utile.
Passons maintenant à la dernière notion importante avec les regex : « capture et remplacement » !

Capture et remplacement

Je vous avais dit au début de ces deux chapitres consacrés aux regex qu'elles servaient à faire une recherche puissante (ça, on vient de le voir, à travers les exemples du téléphone et du mail), mais aussi à faire une recherche et un remplacement.
Cela va nous permettre par exemple de faire la chose suivante :
  1. chercher s'il y a des adresses e-mail dans un message laissé par un visiteur ;
  2. modifier automatiquement son message pour mettre un lien
<a href="mailto:blabla@truc.com"> devant chaque adresse, ce qui rendra les adresses e-mail cliquables !
Avec cette technique, on peut faire pareil pour rendre les liens http:// automatiquement cliquables eux aussi. On peut également, vous allez voir, créer notre propre langage simplifié pour le visiteur, comme le fameux bbCode utilisé sur la plupart des forums ([b][/b] pour mettre en gras, ça vous dit quelque chose ?).

Les parenthèses capturantes

Tout ce que nous allons voir maintenant tourne autour des parenthèses. Vous vous êtes déjà servi d'elles pour entourer une partie de votre regex et dire qu'elle devait se répéter quatre fois par exemple (comme on l'a fait pour le numéro de téléphone).
Eh bien ça, c'est la première utilité des parenthèses, mais elles peuvent aussi servir à autre chose.
À partir de maintenant, nous allons travailler avec la fonction preg_replace.
C'est avec cette fonction que nous allons pouvoir réaliser ce qu'on appelle une « capture » de chaîne.
Ce qu'il faut savoir, c'est qu'à chaque fois que vous utilisez des parenthèses, cela crée une « variable » contenant ce qu'elles entourent.
Je m'explique avec une regex :
#\[b\](.+)\[/b\]#
Vous ne devriez pas avoir trop de mal à la déchiffrer : elle signifie « Chercher dans la chaîne un [b], suivi d'un ou plusieurs caractère(s) (le point permet de dire « n'importe lesquels »), suivi(s) d'un [/b] ».
Normalement, si vous réfléchissez deux secondes, vous devez vous dire qu'ici les parenthèses ne sont pas obligatoires. Et c'est vrai que pour faire juste une recherche, les parenthèses sont effectivement inutiles. Mais pour faire un remplacement, cela va être très pratique !
En effet, retenez bien ceci : à chaque fois qu'il y a une parenthèse, cela crée une variable appelée $1 (pour la première parenthèse), $2 pour la seconde, etc.
On va ensuite se servir de ces variables pour modifier la chaîne (faire un remplacement).
Sur la regex que je vous ai montrée plus haut, il y a une seule parenthèse, vous êtes d'accord ? Donc, il y aura juste une variable $1, qui contiendra ce qui se trouve entre le [b] et le [/b]. Et grâce à ça, on sait ce qu'on va mettre en gras.
Bon, la théorie de tout ça est délicate à expliquer, alors je vais vous montrer de suite comment on fait pour mettre en gras tous les mots compris entre des [b][/b] :
<?php
$texte = preg_replace('#\[b\](.+)\[/b\]#i', '<strong>$1</strong>', $texte);
?>
Voici comment s'utilise la fonction preg_replace.
  1. On lui donne en premier paramètre la regex. Rien de particulier, comme vous pouvez le constater, à part qu'il faut bien garder en tête que chaque parenthèse va créer une variable ($1$2, etc.).   

    Ici, j'ai rajouté l'option « i » pour que le code fonctionne aussi avec des majuscules ([B][/B]).
  2. Ensuite, et c'est là qu'est la nouveauté, on indique le texte de remplacement : « <strong>$1</strong> » (je vous rappelle que <strong> permet de mettre en gras en HTML). 
    Entre les balises HTML, j'ai mis $1. Cela signifie que ce qui se trouve dans la parenthèse capturante (entre [b] et [/b]) sera en fait entouré des balises <strong> !
  3. Enfin, dernier paramètre, c'est le texte dans lequel on fait notre recherche / remplacement (ça, vous connaissez déjà).
La fonction preg_replace renvoie le résultat après avoir fait les remplacements.
Si je schématise le fonctionnement, ça donne la figure suivante.
Fonctionnement de la fonction preg_replace
Fonctionnement de la fonction preg_replace
Il y a quelques règles à respecter que vous allez devoir apprendre.
    • Si vous avez plusieurs parenthèses, pour savoir le numéro de l'une d'elles il suffit de les compter dans l'ordre de gauche à droite.
Par exemple : #(anti)co(nsti)(tu(tion)nelle)ment# Il y a quatre parenthèses dans cette regex (donc $1$2$3 et $4). La parenthèse numéro 3 ($3) contient « tutionnelle », et la parenthèse $4contient « tion ».
    • Vous pouvez utiliser jusqu'à 99 parenthèses capturantes dans une regex (ça vous laisse de la marge). Ça va donc jusqu'à $99.
    • Une variable $0 est toujours créée ; elle contient toute la regex. Sur le même exemple que tout à l'heure :
#(anti)co(nsti)(tu(tion)nelle)ment# … $0 contient « anticonstitutionnellement ».
    • Si, par hasard, vous ne voulez pas qu'une parenthèse soit capturante (pour vous faciliter les comptes, ou parce que vous avez beaucoup beaucoup de parenthèses), il faut qu'elle commence par un point d'interrogation suivi d'un deux points « : ». Par exemple :
#(anti)co(?:nsti)(tu(tion)nelle)ment# La seconde parenthèse n'est pas capturante. Il ne nous reste que trois variables (quatre si on compte $0) :
  1. $0 : anticonstitutionnellement
  2. $1 : anti
  3. $2 : tutionnelle
  4. $3 : tion
Voilà : si vous avez compris ça, vous avez tout compris, bravo ! ;-)

Créez votre bbCode

On peut maintenant passer à la pratique et apprendre à se servir des parenthèses capturantes.
Nous allons réaliser ce qu'on appelle un parser (prononcez « parseur »).
Le parser va servir à transformer le texte rédigé par un visiteur (pour un message sur un forum, ou sur votre livre d'or, ou même sur votre mini-chat !) en un texte inoffensif (sans balises HTML grâce àhtmlspecialchars) mais qui accepte aussi du bbCode !
On ne va pas faire tous les bbCode qui existent (trop long), mais pour s'entraîner, ceux-ci suffiront déjà :
  • [b][/b] : pour mettre du texte en gras ;
  • [i][/i] : pour mettre du texte en italique ;
  • [color=red][/color] : pour colorer le texte (il faudra laisser le choix entre plusieurs couleurs).
Et nous ferons en sorte de remplacer aussi automatiquement les URL (http://) par des liens cliquables.
Commençons par [b] et [i] (c'est la même chose).
Vous avez déjà vu le code pour [b], et c'est en effet presque le bon. Il y a un problème toutefois : il manque des options. Pour que ça marche, on va avoir besoin d'utiliser trois options :
    • i : pour accepter les majuscules comme les minuscules ([B] et [b]) ;
    • s : pour que le « point » fonctionne aussi pour les retours à la ligne (pour que le texte puisse être en gras sur plusieurs lignes) ;
    • U : le U majuscule est une option que vous ne connaissez pas et qui signifie « Ungreedy » (« pas gourmand »). Je vous passe les explications un peu complexes sur son fonctionnement, mais sachez que, grosso modo, ça ne marcherait pas correctement s'il y avait plusieurs [b] dans votre texte. Exemple :
« Ce texte est [b]important[/b], il faut me [b]comprendre[/b] ! » … sans activer l'option Ungreedy, la regex aurait voulu mettre en gras tout ce qu'il y a entre le premier [b] et le dernier [/b](c'est-à-dire « important[/b], il faut me [b]comprendre »). En utilisant l'option « U », la regex s'arrêtera au premier [/b], et c'est ce qu'on veut.
Voici donc le code correct pour mettre en gras et en italique avec le bbCode :
<?php
$texte = preg_replace('#\[b\](.+)\[/b\]#isU', '<strong>$1</strong>', $texte);
$texte = preg_replace('#\[i\](.+)\[/i\]#isU', '<em>$1</em>', $texte);
?>
Comme vous pouvez le voir, c'est quasiment pareil pour [b] et [i] (à part que la balise HTML qu'on utilise est <em>).
Donc, si vous avez suivi jusqu'ici, ça ne doit pas trop vous surprendre.
Passons maintenant à un cas un peu plus complexe : celui de la balise [color=truc]. On va laisser le choix entre plusieurs couleurs avec le symbole « | » (OU), et on va utiliser deux parenthèses capturantes :
  1. la première pour récupérer le nom de la couleur qui a été choisie (en anglais, comme ça on n'aura pas besoin de le changer pour le code HTML) ;
  2. la seconde pour récupérer le texte entre [color=truc] et [/color] (pareil que pour gras et italique).
Voici le résultat :
<?php
$texte = preg_replace('#\[color=(red|green|blue|yellow|purple|olive)\](.+)\[/color\]#isU', '<span style="color:$1">$2</span>', $texte);
?>
Ainsi, si on tape [color=blue]texte[/color], ça écrira texte en bleu. Vous pouvez essayer avec les autres couleurs aussi !
Allez, dernière étape, et après je vous laisse essayer.
Je veux que les liens http:// soient automatiquement transformés en liens cliquables. Essayez d'écrire la regex, vous en êtes tout à fait capables !
Voici la solution :
<?php
$texte = preg_replace('#http://[a-z0-9._/-]+#i', '<a href="$0">$0</a>', $texte);
?>
Vous remarquerez que j'ai fait simple pour cette regex. C'est vrai, j'aurais pu la faire plus complexe et plus précise, mais je n'ai pas envie de vous embrouiller avec ça : je veux surtout que vous l'amélioriez vous-mêmes.
En effet, la regex marche très bien pour http://www.siteduzero.com/images/super_image2.jpg, mais elle ne fonctionne pas s'il y a des variables en paramètres dans l'URL, comme par exemple :
http://www.siteduzero.com/index.php?page=3&skin=blue
Je vous laisse le soin d'améliorer la regex, ça vous fera un peu de travail.
Résumons maintenant notre parser bbCode au complet :
<?php
if (isset($_POST['texte']))
{
$texte = stripslashes($_POST['texte']); // On enlève les slashs qui se seraient ajoutés automatiquement
$texte = htmlspecialchars($texte); // On rend inoffensives les balises HTML que le visiteur a pu rentrer
$texte = nl2br($texte); // On crée des <br /> pour conserver les retours à la ligne
// On fait passer notre texte à la moulinette des regex
$texte = preg_replace('#\[b\](.+)\[/b\]#isU', '<strong>$1</strong>', $texte);
$texte = preg_replace('#\[i\](.+)\[/i\]#isU', '<em>$1</em>', $texte);
$texte = preg_replace('#\[color=(red|green|blue|yellow|purple|olive)\](.+)\[/color\]#isU', '<span style="color:$1">$2</span>', $texte);
$texte = preg_replace('#http://[a-z0-9._/-]+#i', '<a href="$0">$0</a>', $texte);
// Et on affiche le résultat. Admirez !
echo $texte . '<br /><hr />';
}
?>
<p>
Bienvenue dans le parser du Site du Zéro !<br />
Nous avons écrit ce parser ensemble, j'espère que vous saurez apprécier de voir que tout ce que vous avez appris va vous être très utile !
</p>
<p>Amusez-vous à utiliser du bbCode. Tapez par exemple :</p>
<blockquote style="font-size:0.8em">
<p>
Je suis un gros [b]Zéro[/b], et pourtant j'ai [i]tout appris[/i] sur http://www.siteduzero.com<br />
Je vous [b][color=green]recommande[/color][/b] d'aller sur ce site, vous pourrez apprendre à faire ça [i][color=purple]vous aussi[/color][/i] !
</p>
</blockquote>
<form method="post">
<p>
<label for="texte">Votre message ?</label><br />
<textarea id="texte" name="texte" cols="50" rows="8"></textarea><br />
<input type="submit" value="Montre-moi toute la puissance des regex" />
</p>
</form>
Test du parser bbCode 
Test du parser bbCode
Pfiou !
Eh bah si avec ça vous me pondez pas un super site, je ne peux plus rien pour vous.
Avant de terminer, comme j'ai peur que vous vous ennuyiez, je vous donne ci-dessous quelques idées de regex que vous pourriez rajouter au parser.
    • Je vous l'ai déjà dit plus haut, mais il serait très appréciable que les URL cliquables fonctionnent aussi pour des URL avec des variables comme :
http://www.siteduzero.com/index.php?page=3&skin=blue.
    • Vous devriez aussi parser les adresses e-mail en faisant un lien mailto: dessus !
    • Il serait bien de compléter le bbCode avec [u][img], etc.
Mais puisqu'on y est, pourquoi refaire du bbCode ? Après tout, si vous êtes allergiques aux crochets, que pour vous [b] ne veut rien dire, vous n'avez qu'à inventer le code : {gras} {/gras}.
    • Et si faire des regex vous plaît, je peux vous proposer un dernier défi qui devrait vous occuper un petit moment : écrire une fonction qui colore automatiquement le code HTML !
Vous donnez à la fonction le code HTML, elle en fait un htmlspecialchars, puis elle rajoute des <span style="color:…"> pour colorer par exemple en bleu les noms des balises, en vert les attributs, en rouge ce qui est entre guillemets, etc.
Bon courage ! Vous en aurez besoin !

En résumé

  • Certains caractères sont spéciaux au sein d'une expression régulière : on parle de métacaractères. Si on souhaite les rechercher dans une chaîne, il faut les échapper en plaçant un symbole antislash devant. Par exemple : \[.
  • Il existe des classes abrégées, c'est-à-dire des classes toutes prêtes, comme par exemple \d qui revient à écrire [0-9].
  • La fonction preg_replace permet d'effectuer des remplacements dans une chaîne de texte.
  • Dans le cas d'un remplacement, les parenthèses au sein d'une expression régulière permettent de capturer une portion de texte pour la réutiliser dans une autre chaîne.
Source: OpenClassrooms

Aucun commentaire:

Enregistrer un commentaire