II. Syntaxe▲
Nous allons commencer par des regex très simples afin de nous familiariser avec la bestiole. Pour cela, nous allons écrire des expressions qui permettront de faire la même chose que certaines fonctions déjà mises à disposition par PHP. Bien entendu, ces fonctions sont à utiliser de préférence : nous ne les remplaçons ici qu'à des fins didactiques.
II-A. La base : trouver une sous-chaîne dans une chaîne▲
En PHP, la fonction strpos() permet de savoir si une chaîne se trouve dans une autre chaîne :
<?php
$pattern
=
"rossolini"
;
$subjects
=
array
();
$subjects
[]
=
"ftp://ftp.developpez.com/g-rossolini/"
;
$subjects
[]
=
"http://g-rossolini.developpez.com/"
;
$subjects
[]
=
"http://www.developpez.com/"
;
$subjects
[]
=
"http://php.developpez.com/"
;
$subjects
[]
=
"Guillaume Rossolini"
;
$subjects
[]
=
"0123456789"
;
$subjects
[]
=
"01 23 45 67 89"
;
echo "Le masque <strong>
$pattern
</strong> correspond-il à :<ul>"
;
foreach
($subjects
as
$subject
)
{
echo "<li><strong>
$subject
</strong> ?<br />"
;
if
(strpos($subject
,
$pattern
) !==
FALSE
)
{
echo "Oui"
;
}
else
{
echo "Non"
;
}
echo "</li>"
;
}
echo "</ul>"
;
?>
N. B. En cas de succès, la fonction strpos() retourne la position de la sous-chaîne dans la chaîne, mais cela ne nous est pas nécessaire : nous cherchons simplement à déterminer son existence.
Du moment que nous ne souhaitons pas récupérer cette valeur, nous pouvons simuler strpos() au moyen d'une regex. Pour cela, nous utiliserons la fonction preg_match() :
<?php
$pattern
=
"/rossolini/"
;
$subjects
=
array
();
$subjects
[]
=
"ftp://ftp.developpez.com/g-rossolini/"
;
$subjects
[]
=
"http://g-rossolini.developpez.com/"
;
$subjects
[]
=
"http://www.developpez.com/"
;
$subjects
[]
=
"http://php.developpez.com/"
;
$subjects
[]
=
"Guillaume Rossolini"
;
$subjects
[]
=
"0123456789"
;
$subjects
[]
=
"01 23 45 67 89"
;
echo "Le masque <strong>
$pattern
</strong> correspond-il à :<ul>"
;
foreach
($subjects
as
$subject
)
{
echo "<li><strong>
$subject
</strong> ?<br />"
;
if
(preg_match($pattern
,
$subject
))
{
echo "Oui"
;
}
else
{
echo "Non"
;
}
echo "</li>"
;
}
echo "</ul>"
;
?>
Décomposition de la regex : [?]
/rossolini/ => La chaîne "rossolini" telle quelle.
Évidemment, ce premier exemple n'est pas très intéressant. Il nous permet à peine d'introduire les délimiteurs.
Les délimiteurs sont, ici, les deux barres obliques encadrant la chaîne à chercher. En réalité, il est possible d'utiliser une grande variété de délimiteurs. J'aurais pu écrire #rossolini#, `rossolini`, [rossolini], <rossolini>, etc. Reportez-vous au manuel PHP pour en avoir la liste complète. Je ne recommande pas l'utilisation des parenthèses, crochets et accolades, car cela peut compliquer les choses dans l'expression (cf. plus loin).
L'une des différences majeures entre les expressions POSIX et les PCRE est l'existence de ces délimiteurs ; ils permettent de spécifier des modificateurs à la suite de l'expression, par exemple :
<?php
$pattern
=
"/ROSSOLINI/i"
;
$subjects
=
array
();
$subjects
[]
=
"ftp://ftp.developpez.com/g-rossolini/"
;
$subjects
[]
=
"http://g-rossolini.developpez.com/"
;
$subjects
[]
=
"http://www.developpez.com/"
;
$subjects
[]
=
"http://php.developpez.com/"
;
$subjects
[]
=
"Guillaume Rossolini"
;
$subjects
[]
=
"0123456789"
;
$subjects
[]
=
"01 23 45 67 89"
;
echo "Le masque <strong>
$pattern
</strong> correspond-il à :<ul>"
;
foreach
($subjects
as
$subject
)
{
echo "<li><strong>
$subject
</strong> ?<br />"
;
if
(preg_match($pattern
,
$subject
))
{
echo "Oui"
;
}
else
{
echo "Non"
;
}
echo "</li>"
;
}
echo "</ul>"
;
?>
Décomposition de la regex : [?]
/ROSSOLINI/i => La chaîne "rossolini" quelle que soit la casse.
Ici, j'ai ajouté le modificateur i permettant de rendre l'expression insensible à la casse, c'est-à-dire de chercher toutes les variantes majuscules/minuscules de "rossolini". Dans l'exemple ci-dessus, c'est équivalent à invoquer la fonction stripos().
II-B. Les alternatives (barre verticale |)▲
L'un des intérêts des regex est de permettre de donner des alternatives. Ainsi, nous pouvons savoir si une chaîne contient au moins l'une de plusieurs sous-chaînes, cela sans à avoir à appeler plusieurs fois la fonction à la suite :
<?php
$pattern_1
=
"rossolini"
;
$pattern_2
=
"www"
;
$subjects
=
array
();
$subjects
[]
=
"ftp://ftp.developpez.com/g-rossolini/"
;
$subjects
[]
=
"http://g-rossolini.developpez.com/"
;
$subjects
[]
=
"http://www.developpez.com/"
;
$subjects
[]
=
"http://php.developpez.com/"
;
$subjects
[]
=
"Guillaume Rossolini"
;
$subjects
[]
=
"0123456789"
;
$subjects
[]
=
"01 23 45 67 89"
;
echo "Le masque <strong>
$pattern_1
</strong> ou <strong>
$pattern_2
</strong> correspond-il à :<ul>"
;
foreach
($subjects
as
$subject
)
{
echo "<li><strong>
$subject
</strong> ?<br />"
;
if
(strpos($subject
,
$pattern_1
) !==
FALSE
or
strpos($subject
,
$pattern_2
) !==
FALSE
)
{
echo "Oui"
;
}
else
{
echo "Non"
;
}
echo "</li>"
;
}
echo "</ul>"
;
?>
<?php
$pattern
=
"/rossolini|www/"
;
$subjects
=
array
();
$subjects
[]
=
"ftp://ftp.developpez.com/g-rossolini/"
;
$subjects
[]
=
"http://g-rossolini.developpez.com/"
;
$subjects
[]
=
"http://www.developpez.com/"
;
$subjects
[]
=
"http://php.developpez.com/"
;
$subjects
[]
=
"Guillaume Rossolini"
;
$subjects
[]
=
"0123456789"
;
$subjects
[]
=
"01 23 45 67 89"
;
echo "Le masque <strong>
$pattern
</strong> correspond-il à :<ul>"
;
foreach
($subjects
as
$subject
)
{
echo "<li><strong>
$subject
</strong> ?<br />"
;
if
(preg_match($pattern
,
$subject
))
{
echo "Oui"
;
}
else
{
echo "Non"
;
}
echo "</li>"
;
}
echo "</ul>"
;
?>
Décomposition de la regex : [?]
/rossolini|www/ => Soit la chaîne "rossolini", soit "www".
Cela dit, s'il s'agit de trouver deux sous-chaînes dans une chaîne, alors il est préférable de lancer deux regex plutôt que d'essayer de le faire en une fois : cela pourrait s'avérer bien complexe, illisible et très long à exécuter.
II-C. L'ancrage (accent circonflexe ^ et signe dollar $)▲
Il est possible d'ancrer une regex, de manière à ne trouver de correspondances qu'au début de la chaîne, à la fin ou les deux (chaîne entière). Cela se fait au moyen des symboles ^ et $.
<?php
$patterns
=
array
();
$patterns
[
0
][
0
]
=
"#^http$#"
;
$patterns
[
0
][
1
]
=
"#http$#"
;
$patterns
[
0
][
2
]
=
"#^http#"
;
$patterns
[
1
][
0
]
=
"#^.com/$#"
;
$patterns
[
1
][
1
]
=
"#.com/$#"
;
$patterns
[
1
][
2
]
=
"#^.com/#"
;
$patterns
[
2
][
0
]
=
"#^Guillaume Rossolini$#"
;
$patterns
[
2
][
1
]
=
"#Guillaume Rossolini$#"
;
$patterns
[
2
][
2
]
=
"#^Guillaume Rossolini#"
;
$subjects
=
array
();
$subjects
[]
=
"ftp://ftp.developpez.com/g-rossolini/"
;
$subjects
[]
=
"http://g-rossolini.developpez.com/"
;
$subjects
[]
=
"http://www.developpez.com/"
;
$subjects
[]
=
"http://php.developpez.com/"
;
$subjects
[]
=
"Guillaume Rossolini"
;
$subjects
[]
=
"0123456789"
;
$subjects
[]
=
"01 23 45 67 89"
;
foreach
($patterns
as
$pattern
)
{
echo "Où se trouve <strong>"
.
trim($pattern
[
0
],
'#^$'
).
"</strong> ?<br /><ul>"
;
foreach
($subjects
as
$subject
)
{
echo "<li>dans <strong>
$subject
</strong> ?<br />"
;
if
(preg_match($pattern
[
0
],
$subject
))
{
echo "C'est la chaîne complète"
;
}
elseif
(preg_match($pattern
[
1
],
$subject
))
{
echo "À la fin"
;
}
elseif
(preg_match($pattern
[
2
],
$subject
))
{
echo "Au début"
;
}
else
{
echo "Nulle part"
;
}
echo "</li>"
;
}
echo "</ul>"
;
}
?>
Décomposition de la regex : [?]
#^http$# => La chaîne "http".
#http$# => La chaîne "http" en fin de chaîne.
#^http# => La chaîne "http" en début de chaîne.
J'ai pris soin d'utiliser un délimiteur différent de la barre oblique, dans la mesure où elle se trouve dans le masque ($pattern). Sans cela, il aurait fallu échapper la barre oblique, or je préfère simplifier la lecture de la regex.
Il y a bien sûr des équivalences tant que nous cherchons une sous-chaîne fixe :
<?php
$pattern
=
"http"
;
$subjects
=
array
();
$subjects
[]
=
"ftp://ftp.developpez.com/g-rossolini/"
;
$subjects
[]
=
"http://g-rossolini.developpez.com/"
;
$subjects
[]
=
"http://www.developpez.com/"
;
$subjects
[]
=
"http://php.developpez.com/"
;
$subjects
[]
=
"Guillaume Rossolini"
;
$subjects
[]
=
"0123456789"
;
$subjects
[]
=
"01 23 45 67 89"
;
echo "Le masque <strong>
$pattern
</strong> est-il au début :<br /><ul>"
;
foreach
($subjects
as
$subject
)
{
echo "<li>dans <strong>
$subject
</strong> ?<br />"
;
if
(strpos($subject
,
$pattern
) ===
0
)
{
echo "Oui"
;
}
else
{
echo "Non"
;
}
echo "</li>"
;
}
echo "</ul>"
;
?>
<?php
$pattern
=
".com/"
;
$subjects
=
array
();
$subjects
[]
=
"ftp://ftp.developpez.com/g-rossolini/"
;
$subjects
[]
=
"http://g-rossolini.developpez.com/"
;
$subjects
[]
=
"http://www.developpez.com/"
;
$subjects
[]
=
"http://php.developpez.com/"
;
$subjects
[]
=
"Guillaume Rossolini"
;
$subjects
[]
=
"0123456789"
;
$subjects
[]
=
"01 23 45 67 89"
;
echo "Le masque <strong>
$pattern
</strong> est-il à la fin :<br /><ul>"
;
foreach
($subjects
as
$subject
)
{
echo "<li>dans <strong>
$subject
</strong> ?<br />"
;
if
(strpos($subject
,
$pattern
) ===
(strlen($subject
) -
strlen($pattern
)))
{
echo "Oui"
;
}
else
{
echo "Non"
;
}
echo "</li>"
;
}
echo "</ul>"
;
?>
<?php
$pattern
=
"Guillaume Rossolini"
;
$subjects
=
array
();
$subjects
[]
=
"ftp://ftp.developpez.com/g-rossolini/"
;
$subjects
[]
=
"http://g-rossolini.developpez.com/"
;
$subjects
[]
=
"http://www.developpez.com/"
;
$subjects
[]
=
"http://php.developpez.com/"
;
$subjects
[]
=
"Guillaume Rossolini"
;
$subjects
[]
=
"0123456789"
;
$subjects
[]
=
"01 23 45 67 89"
;
echo "Le masque <strong>
$pattern
</strong> est-il équivalent à :<br /><ul>"
;
foreach
($subjects
as
$subject
)
{
echo "<li><strong>
$subject
</strong> ?<br />"
;
if
($subject
==
$pattern
)
{
echo "Oui"
;
}
else
{
echo "Non"
;
}
echo "</li>"
;
}
echo "</ul>"
;
?>
Des recommandations de sécurité sont recensées en fin d'article lorsque l'ancrage de fin de chaîne est utilisé.
II-D. Les classes▲
Les regex permettent une chose intéressante : savoir si une chaîne contient une liste de caractères (plutôt qu'une sous-chaîne précise).
<?php
$pattern
=
"/[a-z]/"
;
$subjects
=
array
();
$subjects
[]
=
"ftp://ftp.developpez.com/g-rossolini/"
;
$subjects
[]
=
"http://g-rossolini.developpez.com/"
;
$subjects
[]
=
"http://www.developpez.com/"
;
$subjects
[]
=
"http://php.developpez.com/"
;
$subjects
[]
=
"Guillaume Rossolini"
;
$subjects
[]
=
"0123456789"
;
$subjects
[]
=
"01 23 45 67 89"
;
echo "<strong>
$pattern
</strong> correspond-il à :<br /><ul>"
;
foreach
($subjects
as
$subject
)
{
echo "<li><strong>
$subject
</strong> ?<br />"
;
if
(preg_match($pattern
,
$subject
))
{
echo "Oui"
;
}
else
{
echo "Non"
;
}
echo "</li>"
;
}
echo "</ul>"
;
?>
Décomposition de la regex : [?]
/[a-z]/ => Il suffit d'une lettre minuscule dans la chaîne.
Dans cet exemple, la regex regarde s'il y a au moins une lettre dans la chaîne. Le trait d'union permet d'indiquer une série : "[a-z]" comprend l'alphabet entier, "[0-9]" tous les chiffres, "[4-7]" uniquement les chiffres de 4 à 7 inclus. Nous pourrions indiquer "[4567]" à la place de "[4-7]", mais bon… Idem pour les lettres, il est possible d'écrire tout l'alphabet dans les crochets. Le trait d'union permet de simplifier l'expression.
Précisons que ce qui se trouve à l'intérieur d'une classe (à l'exception des délimiteurs, de la barre oblique arrière \ et du trait d'union -) n'a pas besoin d'être échappé. Ici, par exemple, le point est pris en tant que tel et non comme joker :
<?php
$pattern
=
"#^[a-z:/.-]+$#"
;
$subjects
=
array
();
$subjects
[]
=
"ftp://ftp.developpez.com/g-rossolini/"
;
$subjects
[]
=
"http://g-rossolini.developpez.com/"
;
$subjects
[]
=
"http://www.developpez.com/"
;
$subjects
[]
=
"http://php.developpez.com/"
;
$subjects
[]
=
"Guillaume Rossolini"
;
$subjects
[]
=
"0123456789"
;
$subjects
[]
=
"01 23 45 67 89"
;
echo "Le masque <strong>
$pattern
</strong> correspond-il à :<br /><ul>"
;
foreach
($subjects
as
$subject
)
{
echo "<li><strong>
$subject
</strong> ?<br />"
;
if
(preg_match($pattern
,
$subject
))
{
echo "Oui"
;
}
else
{
echo "Non"
;
}
echo "</li>"
;
}
echo "</ul>"
;
?>
Décomposition de la regex : [?]
#^[a-z:/.-]+$# => Un ou plusieurs caractères parmi les lettres de l'alphabet, les deux points, la barre oblique, le point et le trait d'union.
Enfin, une note sur le .* : bien que pratique, il est souvent peu judicieux de l'utiliser. Il est généralement préférable d'utiliser une classe dite négative :
<?php
$pattern
=
"#^[^ ]+$#"
;
$subjects
=
array
();
$subjects
[]
=
"ftp://ftp.developpez.com/g-rossolini/"
;
$subjects
[]
=
"http://g-rossolini.developpez.com/"
;
$subjects
[]
=
"http://www.developpez.com/"
;
$subjects
[]
=
"http://php.developpez.com/"
;
$subjects
[]
=
"Guillaume Rossolini"
;
$subjects
[]
=
"0123456789"
;
$subjects
[]
=
"01 23 45 67 89"
;
echo "Le masque <strong>
$pattern
</strong> correspond-il à :<br /><ul>"
;
foreach
($subjects
as
$subject
)
{
echo "<li><strong>
$subject
</strong> ?<br />"
;
if
(preg_match($pattern
,
$subject
))
{
echo "Oui"
;
}
else
{
echo "Non"
;
}
echo "</li>"
;
}
echo "</ul>"
;
?>
Décomposition de la regex : [?]
#^[^ ]+$# => N'importe quel caractère, mais pas une seule espace dans la chaîne.
Voici certaines des classes prédéfinies et leurs équivalents :
- Caractère décimal
[[:digit:]] ou [0-9] ou \d
Équivalent PHP pour is_int($subject) : preg_match('/^\d+$/', $subject)
Équivalent PHP approximatif pour is_numeric($subject) : preg_match('/^\d+(\.\d*)?$/', $subject) - Caractère alphabétique
[[:alpha:]] ou [a-zA-Z]
Équivalent PHP approximatif pour ctype_alpha($subject) : preg_match('/^[a-z]+$/i', $subject) - Caractère alphanumérique
[[:alnum:]] ou [a-zA-Z0-9] - Caractère blanc (espace, tabulation, saut de ligne)
[[:space:]] ou [\040\r\t] ou \s
Équivalent C : isspace() - Limite de mot
\b
Les classes du genre [[::]] sont héritées de la norme POSIX. Pour une liste plus complète, je vous laisse vous rendre vers l'article de mon collègue Hugo Étiévant.
II-E. Les quantifieurs▲
Il est possible de dire combien de fois le dernier caractère (ou la dernière classe) peut/doit être répété. C'est l'objectif des quantifieurs.
Liste des quantifieurs :
- "Zéro ou une fois" : point d'interrogation ?
- "Zéro, une ou plusieurs fois" : étoile *
- "Au moins une fois" : signe plus +
- "Deux fois" : {2}
- "De deux à quatre fois" : {2,4}
- "Au moins deux fois" : {2,}
À noter que :
- ? est équivalent à {0,1}
- * est équivalent à {0,}
- + est équivalent à {1,}
Les regex pouvant être très rapidement compliquées, il est préférable de les simplifier tant que possible. Conseil d'ami.
Il est évidemment possible de mettre n'importe quel nombre dans les accolades (pourvu que le premier nombre soit inférieur au second).
N'oublions pas le fameux joker : le point. Il permet de remplacer n'importe quel caractère.
<?php
$pattern
=
"#http://[a-z.-]+
\.
[a-z]{2,4}/#"
;
$subjects
=
array
();
$subjects
[]
=
"ftp://ftp.developpez.com/g-rossolini/"
;
$subjects
[]
=
"http://g-rossolini.developpez.com/"
;
$subjects
[]
=
"http://www.developpez.com/"
;
$subjects
[]
=
"http://php.developpez.com/"
;
$subjects
[]
=
"Guillaume Rossolini"
;
$subjects
[]
=
"0123456789"
;
$subjects
[]
=
"01 23 45 67 89"
;
echo "Le masque <strong>
$pattern
</strong> correspond-il à :<br /><ul>"
;
foreach
($subjects
as
$subject
)
{
echo "<li><strong>
$subject
</strong> ?<br />"
;
if
(preg_match($pattern
,
$subject
))
{
echo "Oui"
;
}
else
{
echo "Non"
;
}
echo "</li>"
;
}
echo "</ul>"
;
?>
Décomposition de la regex : [?]
# http://[a-z.-]+\.[a-z]{2,4}/ #
Détail :
- http://
- [a-z.-]+ : au moins un caractère parmi les lettres de l'alphabet, le point et le trait d'union
- \.[a-z]{2,4} : un point suivi de deux à quatre lettres de l'alphabet
II-F. Les parenthèses▲
Les regex permettent une autre chose intéressante, à savoir de capturer des portions de la chaîne parcourue. Cela se fait au moyen des parenthèses.
Pour récupérer une capture, il faut spécifier le paramètre optionnel de preg_match(). Cette variable contiendra un tableau des résultats : l'index zéro contiendra la chaîne complète correspondant au masque puis chaque index suivant sera rempli par une capture dans l'ordre où elles sont définies.
Les parenthèses capturantes▲
<?php
$pattern
=
"#http://([a-z-]+)
\.
developpez.com/#"
;
$subjects
=
array
();
$subjects
[]
=
"ftp://ftp.developpez.com/g-rossolini/"
;
$subjects
[]
=
"http://g-rossolini.developpez.com/"
;
$subjects
[]
=
"http://www.developpez.com/"
;
$subjects
[]
=
"http://php.developpez.com/"
;
$subjects
[]
=
"Guillaume Rossolini"
;
$subjects
[]
=
"0123456789"
;
$subjects
[]
=
"01 23 45 67 89"
;
echo "Le masque <strong>
$pattern
</strong> correspond-il à :<br /><ul>"
;
foreach
($subjects
as
$subject
)
{
$match
=
array
();
echo "<li><strong>
$subject
</strong> ?<br />"
;
if
(preg_match($pattern
,
$subject
,
$match
))
{
echo "Oui :<br /><pre>"
;
print_r($match
);
echo "</pre>"
;
}
else
{
echo "Non<br /><br />"
;
}
echo "</li>"
;
}
echo "</ul>"
;
?>
Décomposition de la regex : [?]
# http://([a-z-]+)\.developpez.com/ #
Détail :
- http://
- ([a-z-]+) : un ou plusieurs caractères parmi les lettres et le trait d'union
- \.developpez.com/
<?php
$pattern
=
"#http://([a-z-]+)
\.
([a-z]+)
\.
([a-z]+)/#"
;
$subjects
=
array
();
$subjects
[]
=
"ftp://ftp.developpez.com/g-rossolini/"
;
$subjects
[]
=
"http://g-rossolini.developpez.com/"
;
$subjects
[]
=
"http://www.developpez.com/"
;
$subjects
[]
=
"http://php.developpez.com/"
;
$subjects
[]
=
"Guillaume Rossolini"
;
$subjects
[]
=
"0123456789"
;
$subjects
[]
=
"01 23 45 67 89"
;
echo "Le masque <strong>
$pattern
</strong> correspond-il à :<br /><ul>"
;
foreach
($subjects
as
$subject
)
{
$match
=
array
();
echo "<li><strong>
$subject
</strong> ?<br />"
;
if
(preg_match($pattern
,
$subject
,
$match
))
{
echo "Oui :<br /><pre>"
;
print_r($match
);
echo "</pre>"
;
}
else
{
echo "Non<br /><br />"
;
}
echo "</li>"
;
}
echo "</ul>"
;
?>
Décomposition de la regex : [?]
# http://([a-z-]+)\.([a-z]+)\.([a-z]+)/ #
Détail :
- http://
- \.([a-z]+) : un point suivi d'une ou plusieurs lettres.
- /
Une version plus intéressante (car moins redondante) serait : [?]
# http://([a-z-]+)(?:\.([a-z]+)){2}/ #
Détail :
- http://
- (?:\.([a-z]+)){2} : un point suivi d'un ou plusieurs caractères parmi les lettres et le trait d'union. Ce bloc est répété une fois.
- /
Pour récupérer plusieurs fois un masque dans une même chaîne, nous pouvons utiliser la fonction preg_match_all() :
<?php
$pattern
=
"#[./]([a-z-]+)#"
;
$subjects
=
array
();
$subjects
[]
=
"ftp://ftp.developpez.com/g-rossolini/"
;
$subjects
[]
=
"http://g-rossolini.developpez.com/"
;
$subjects
[]
=
"http://www.developpez.com/"
;
$subjects
[]
=
"http://php.developpez.com/"
;
$subjects
[]
=
"Guillaume Rossolini"
;
$subjects
[]
=
"0123456789"
;
$subjects
[]
=
"01 23 45 67 89"
;
echo "Le masque <strong>
$pattern
</strong> correspond-il à :<br /><ul>"
;
foreach
($subjects
as
$subject
)
{
$matches
=
array
();
echo "<li><strong>
$subject
</strong> ?<br />"
;
if
(preg_match_all($pattern
,
$subject
,
$matches
,
PREG_SET_ORDER))
{
echo "Oui :<br /><pre>"
;
print_r($matches
);
echo "</pre>"
;
}
else
{
echo "Non<br /><br />"
;
}
echo "</li>"
;
}
echo "</ul>"
;
?>
Décomposition de la regex : [?]
#[./]([a-z-]+)# => Un point ou une barre oblique, puis au moins une lettre de l'alphabet.
Une autre utilisation des captures est la référence arrière :
<?php
$pattern
=
"#http://([a-z-]+)
\.
(developpez
\.
com)/#"
;
$subjects
=
array
();
$subjects
[]
=
"ftp://ftp.developpez.com/g-rossolini/"
;
$subjects
[]
=
"http://g-rossolini.developpez.com/"
;
$subjects
[]
=
"http://www.developpez.com/"
;
$subjects
[]
=
"http://php.developpez.com/"
;
$subjects
[]
=
"Guillaume Rossolini"
;
$subjects
[]
=
"0123456789"
;
$subjects
[]
=
"01 23 45 67 89"
;
echo "Le masque <strong>
$pattern
</strong> correspond-il à :<br /><ul>"
;
foreach
($subjects
as
$subject
)
{
echo "<li><strong>
$subject
</strong> ?<br />"
;
if
(preg_match($pattern
,
$subject
))
{
echo preg_replace(
$pattern
,
"Le sous-domaine est '
\1
', le domaine est '
\2
'"
,
$subject
);
echo "<br />"
;
echo preg_replace(
$pattern
,
"Le sous-domaine est '
$1
', le domaine est '
$2
'"
,
$subject
);
}
echo "</li>"
;
}
echo "</ul>"
;
?>
Décomposition de la regex : [?]
# http://([a-z-]+)\.(developpez\.com)/ #
Détail :
- http://
- ([a-z-]+)\. : une ou plusieurs lettres de l'alphabet (ou traits d'union) suivies d'un point
- (developpez\.com)/ : la chaîne "developpez.com"
Ici, j'ai utilisé les deux manières d'appeler une référence arrière : au moyen de la barre oblique inversée \ ou au moyen du signe dollar $, suivies du numéro de la référence (en commençant à 1, non à 0). Les deux méthodes sont équivalentes. Faites attention si vous utilisez plus de neuf références, car la convention de nommage peut poser problème. Reportez-vous à la documentation PHP pour toute information complémentaire.
Les parenthèses non capturantes▲
Il est possible d'utiliser des parenthèses sans pour autant définir une capture. Cela peut servir dans certaines situations, notamment lorsque nous voulons donner une alternative :
<?php
$pattern
=
"/[a-z]+
\s
(?:[a-z]+['
\s
])?([a-z]+(?:es|ons|ez|ent|e))/i"
;
$subjects
=
array
();
$subjects
[]
=
"Je m'appelle, tu t'appelles, il s'appelle,"
.
" nous nous appelons, vous vous appelez, ils s'appellent"
;
$subjects
[]
=
"Je programme, tu programmes, il programme,"
.
" nous programmons, vous programmez, ils programment"
;
echo "Le masque <strong>
$pattern
</strong> correspond-il à :<br /><ul>"
;
foreach
($subjects
as
$subject
)
{
$matches
=
array
();
echo "<li><strong>
$subject
</strong> ?<br />"
;
if
(preg_match_all($pattern
,
$subject
,
$matches
,
PREG_SET_ORDER))
{
echo "Oui : <br /><pre>"
;
print_r($matches
);
echo "</pre>"
;
}
else
{
echo "Non"
;
}
echo "</li>"
;
}
echo "</ul>"
;
?>
Décomposition de la regex : [?]
/ [a-z]+\s(?:[a-z]+['\s])?([a-z]+(?:es|ons|ez|ent|e)) / i
Détail :
- [a-z]+\s : une ou plusieurs lettres de l'alphabet suivies d'une espace
- (?:[a-z]+['\s])? : une ou plusieurs lettres de l'alphabet suivies soit d'une espace soit d'une apostrophe (ce bloc est facultatif)
- ([a-z]+(?:es|ons|ez|ent|e)) : tout mot terminant par "e", "es", "ons", "ez" ou "ent" (on capture le mot entier)
Dans cet exemple, ce qui nous intéresse n'est pas uniquement la terminaison du verbe, mais le verbe entier. Il y a donc un couple de parenthèses capturantes autour du verbe et un couple de parenthèses non capturantes autour de la terminaison.
II-G. Les modificateurs▲
Les modificateurs sont un apport très consistant des PCRE depuis la norme POSIX. Ils permettent de modifier la manière dont le moteur de regex va parcourir la chaîne.
Voici mes trois modificateurs favoris.
- U
"Ungreedy", c'est-à-dire non gourmand. Cela signifie que l'expression trouvera des résultats aussi petits que possible.
Essayez l'expression ci-dessous avec et sans le modificateur U. Dans certains cas, la regex pourrait même retourner depuis le premier <b> jusqu'au dernier </b> (cf. exemple ci-dessous) ! - s
Permet de demander au point de contenir également les sauts de ligne. Par défaut, ce n'est pas le cas. - i
Permet de ne pas tenir compte de la casse. Ainsi, les masques [a-z], [A-Z] et toutes leurs variantes sont équivalents. Il est inutile de préciser [a-zA-Z], ce qui peut être pratique dans de nombreux cas.
Essayez ces exemples avec et sans leur modificateur :
<?php
$patterns
=
array
();
$patterns
[]
=
"#<strong>(.*)</strong>#"
;
$patterns
[]
=
"#<strong>(.*)</strong>#U"
;
$subjects
=
array
();
$subjects
[]
=
"Je m'appelle <strong>Guillaume</strong>,"
.
" mon site est <strong>http://g-rossolini.developpez.com/</strong>."
;
$subjects
[]
=
"Il s'appelle <strong>Pierre-Baptiste</strong>,"
.
" son site est <strong>http://pbnaigeon.developpez.com/</strong>."
;
foreach
($patterns
as
$pattern
)
{
echo "Le masque <strong>
$pattern
</strong> correspond-il à :<br /><ul>"
;
foreach
($subjects
as
$subject
)
{
$matches
=
array
();
echo "<li><strong>
$subject
</strong> ?<br />"
;
if
(preg_match_all($pattern
,
$subject
,
$matches
,
PREG_SET_ORDER))
{
echo "Oui : <br /><pre>"
;
print_r($matches
);
echo "</pre>"
;
}
else
{
echo "Non"
;
}
echo "</li>"
;
}
echo "</ul>"
;
}
?>
Décomposition de la regex : [?]
#<b>(.*)</b>#U => N'importe quelle suite de caractères encadrée d'une balise de mise en gras.
<?php
$patterns
=
array
();
$patterns
[]
=
"#<strong>(.*)</strong>#"
;
$patterns
[]
=
"#<strong>(.*)</strong>#s"
;
$subjects
=
array
();
$subjects
[]
=
"Je m'appelle <strong>Guillaume</strong>,
\n
"
.
" mon site est <strong>http://g-rossolini.developpez.com/</strong>. (avec saut de ligne)"
;
$subjects
[]
=
"Il s'appelle <strong>Pierre-Baptiste</strong>,"
.
" son site est <strong>http://pbnaigeon.developpez.com/</strong>. (sans saut de ligne)"
;
foreach
($patterns
as
$pattern
)
{
echo "Le masque <strong>
$pattern
</strong> correspond-il à :<br /><ul>"
;
foreach
($subjects
as
$subject
)
{
$matches
=
array
();
echo "<li><strong>
$subject
</strong> ?<br />"
;
if
(preg_match_all($pattern
,
$subject
,
$matches
,
PREG_SET_ORDER))
{
echo "Oui : <br /><pre>"
;
print_r($matches
);
echo "</pre>"
;
}
else
{
echo "Non"
;
}
echo "</li>"
;
}
echo "</ul>"
;
}
?>
Décomposition de la regex : [?]
#<i>(.*)</i>#s => N'importe quelle suite de caractères encadrée d'une balise de mise en italique.
II-H. Les assertions▲
Les assertions permettent de déterminer la présence ou l'absence de caractères avant ou après la position du curseur dans la chaîne. Pour plus de détails sur ce curseur, veuillez vous reporter à la documentation officielle.
Il existe des assertions simples telles que \b, décrit succinctement plus haut, ainsi que des assertions plus complexes. Nous allons nous attarder sur ces dernières.
Je ne traiterai pas des assertions positives, car elles me semblent totalement inutiles : autant mettre la chaîne sans assertion, cela revient au même que faire une assertion positive. Pour les curieux, il s'agit de (?=) et (?<=). Je présenterai donc uniquement les assertions négatives : (?!) et (?<!).
N. B. Les assertions ont besoin de parenthèses, mais elles ne sont pas capturantes.
Les assertions négatives avant▲
Elles permettent de déterminer si une chaîne est suivie d'une autre chaîne (par son absence).
<?php
$patterns
=
array
();
$patterns
[]
=
"#http://(?!www)#"
;
$patterns
[]
=
"#http://(?!php)#"
;
$subjects
=
array
();
$subjects
[]
=
"ftp://ftp.developpez.com/g-rossolini/"
;
$subjects
[]
=
"http://g-rossolini.developpez.com/"
;
$subjects
[]
=
"http://www.developpez.com/"
;
$subjects
[]
=
"http://php.developpez.com/"
;
foreach
($patterns
as
$pattern
){
echo "Le masque <strong>
$pattern
</strong> correspond-il à :<br /><ul>"
;
foreach
($subjects
as
$subject
)
{
echo "<li><strong>
$subject
</strong> ?<br />"
;
if
(preg_match($pattern
,
$subject
))
{
echo "Oui"
;
}
else
{
echo "Non"
;
}
echo "</li>"
;
}
echo "</ul>"
;
}
?>
Décomposition des regex : [?]
#http://(?!www)# => La chaîne "http://" non suivie de "www" #http://(?!php)# => La chaîne "http://" non suivie de "php"
Les assertions négatives arrière▲
Elles permettent de déterminer si une chaîne est précédée d'une autre chaîne (par son absence).
<?php
$patterns
=
array
();
$patterns
[]
=
"#(?<!www)
\.
developpez#"
;
$patterns
[]
=
"#(?<!php)
\.
developpez#"
;
$subjects
=
array
();
$subjects
[]
=
"ftp://ftp.developpez.com/g-rossolini/"
;
$subjects
[]
=
"http://g-rossolini.developpez.com/"
;
$subjects
[]
=
"http://www.developpez.com/"
;
$subjects
[]
=
"http://php.developpez.com/"
;
foreach
($patterns
as
$pattern
)
{
echo "Le masque <strong>
$pattern
</strong> correspond-il à :<br /><ul>"
;
foreach
($subjects
as
$subject
)
{
echo "<li><strong>
$subject
</strong> ?<br />"
;
if
(preg_match($pattern
,
$subject
))
{
echo "Oui"
;
}
else
{
echo "Non"
;
}
echo "</li>"
;
}
echo "</ul>"
;
}
?>
Décomposition des regex : [?]
#(?<!www)\.developpez# = La chaîne ".developpez" non précédée de "www" #(?<!php)\.developpez# = La chaîne ".developpez" non précédée de "php"