Les expressions régulières : assertions et sous-masques conditionnelles

applications internet utilitaires

Ce site ne sera plus alimenté de contenu après août 2014. Tous les nouveaux articles seront redigés pour www.waitingforcode.com
Les expressions régulières accompagnent le développement de chaque application web. L'extraction des données, la validation du flux entrant, l'échappement du flux sortant - voici quelques exemples de leur utilisation. Cependant, il ne suffit pas de connaître la construction basique des patterns afin de pouvoir optimiser le code. C'est pourquoi on consacrera cet article à des aspects plus poussés des expressions régulières.

Des tests sur les chaînes dans les RegEx ? Oui, c'est possible grâce à des assertions. La première partie de cette article y sera consacrée. Besoin aussi des instructions if-then-else ? C'est également possible. En occurrence, il s'agit des sous-masques conditionnelles. Elles feront l'objet de la deuxième partie de l'article. La dernière partie sera consacrée à des exemples précis de l'utilisation de ces sous-masques conditionnelles et assertions.

Assertions en expressions régulières
Une assertion est en fait une condition qui permet de vérifier la concordance d'une chaîne de caractères. Il peut s'agir d'un pattern bien connu "^condition$", mais aussi de ceux qui sont moins populaires (celles-ci sont nommées des assertions simples) :
- \A : signifie le début de la chaîne recherchée; n'est pas affecté par le mode multi-lignes (flag m qui permet de matcher un pattern sur plusieurs lignes, si le texte contient par exemple les "\n")
- \b : indique la présence du limite de mot
- \B : indique qu'il n'y a pas de limite de mot
- \G : signifie la position de la première occurrence trouvée
- \z : signifie la fin de la chaîne recherchée; comme \A, indépendant du mode multi-lignes
- \Z : signifie la fin de la chaîne recherchée ou une nouvelle ligne à la fin du texte analysé; également indépendant du mode multi-lignes

Regardons alors comment fonctionnent ces assertions simples sur "Lorem ipsum", une phrase bien connue aux webdesigners et intégrateurs web :
"Le Lorem Ipsum est simplement du fauxtexte employé dans la composition et la mise en page avant impression. Le Lorem Ipsum est le faux texte standard de l'imprimerie depuis les années 1500, quand un peintre anonyme assembla ensemble des morceaux de texte pour réaliser un livre spécimen de polices de texte. Il n'a pas fait que survivre cinq siècles, mais s'est aussi adapté à la bureautique informatique, sans que son contenu n'en soit modifié. Il a été popularisé dans les années 1960 grâce à la vente de feuilles Letraset contenant des passages du Lorem Ipsum, et, plus récemment, par son inclusion dans des applications de mise en page de texte, comme Aldus PageMaker. Contrairement à une opinion répandue, le Lorem Ipsum n'est pas simplement du texte aléatoire. Il trouve ses racines dans une oeuvre de la littérature latine classique datant de 45 av. J.-C., le rendant vieux de 2000 ans. Un professeur du Hampden-Sydney College, en Virginie, s'est intéressé à un des mots latins les plus obscurs, consectetur, extrait d'un passage du Lorem Ipsum, et en étudiant tous les usages de ce mot dans la littérature classique, découvrit la source incontestable du Lorem Ipsum. Il provient en fait des sections 1.10.32 et 1.10.33 du "De Finibus Bonorum et Malorum" (Des Suprêmes Biens et des Suprêmes Maux) de Cicéron. Cet ouvrage, très populaire pendant la Renaissance, est un traité sur la théorie de l'éthique. Les premières lignes du Lorem Ipsum, "Lorem ipsum dolor sit amet...", proviennent de la section 1.10.32. L'extrait standard de Lorem Ipsum utilisé depuis le XVIè siècle est reproduit ci-dessous pour les curieux. Les sections 1.10.32 et 1.10.33 du "De Finibus Bonorum et Malorum" de Cicéron sont aussi reproduites dans leur version originale, accompagnée de la traduction anglaise de H. Rackham (1914). Ce textemployé"

Voici ce qu'on voudra achever en utilisant des assertions simples :
- Retrouver le mot "texte" avec \b :
Si l'on utilise le pattern le plus simple, on recevra 7 résultats, dont 2 incorrects ("fauxtexte" et "textemployé") :

preg_match_all('/texte/i', $txt, $out);


Afin d'éviter ce problème, il nous faut employer l'assertion \b sur le mot "texte", comme ici :

preg_match_all('#\btexte\b#', $txt, $out);


Cinq occurrences trouvées - le résultat est alors correct. On voit bien qu'en mettant le mot recherche entre l'assertion "\b", on a fait comprendre qu'on est intéressé uniquement par ces quelques caractères collés ensemble et délimités par autre chose qu'un caractère alphanumérique.

- Retrouver tous les mots commençants par un "texte" avec \B :
Pour faire ça, on utilisera la flag \B qui signifie l'absence du limite pour la phrase :

preg_match_all('#\btexte\B#', $txt, $out);


Le résultat donnera 1 occurrence, ce qui est dans notre cas correct ("textemployé").
- Vérifier si le premier mot de la chaîne commencer par "Le".
Ici on utilisera l'assertion \G qui traite uniquement la première occurrence trouvée. Dans notre cas, si le tableau $out[0] contiendra un élément, cela voudra dire que notre texte commence par "Le" :

preg_match_all('#\GLe#', $txt, $out);


En affichant le résultat, on voit bien la présence de "Le" sous la clé 0 du tableau $out[0]. Notre texte commence donc bien par "Le".

Les assertions \A, \z et \Z ressemblent beaucoup à des caractères ^ et $. La seule différence réside dans le fait qu'elles ne sont pas influencées par le mode multi-lignes.

Sous-masques conditionnelles en expressions régulières
Les expressions conditionnelles dans RegEx permettent d'effectuer une action en fonction de leur résultat. Ces instructions ressemblent à des instructions conditionnelles dans les langages de programmation if-then-else.

On peut imaginer la situation où l'on veut analyser le contenu d'une facture et de récupérer une liste des choses achetées. Supposons que le format de la facture est le suivant :

----- Magasin test -----
Le 23/03/2012, à Metz Centre

-- Beurre : 3€
-- Baguette : 0.70€
-- Fromage : 2€

-- Total : 5.70€


On pourrait le faire simplement, avec l'expression suivante :

preg_match_all("/\-\- (.*) : /", $txt, $matches);


Cependant, en vérifiant le résultat, on s'aperçoit que le Total a également été sélectionné. Afin de l'éliminer on utilisera une sous-masque conditionnelle dont l'objectif sera de récupérer tout ce qui n'est pas égal au string "Total". La voici :


preg_match_all("/\-\- (?(?!Total)(.*)) : /", $txt, $matches);


Maintenant, en affichant les résultats, on voit bien que le tableau $matches contient nos 3 produits achetés : Beurre, Baguette et Fromage.

La sous-masque conditionnelle commence alors par (?. Elle contient ensuite des conditions entre parenthèses, afin de se terminant par ).

Exemples d'utilisation
La combinaison des assertions et des sous-masques conditionnelles peut trouver son utilité dans le filtrage des données. Imaginons que notre application n'admet pas les balises de formattage du texte : <b>, <em>, <strong> et <u>. Regardons comment une expression régulière peut gérer cette contrainte :


$txt = "<b>This is very important text</b> with an <a href=\"http://www.google.com\">external URL</a> forwarding to <u>Google</u>. <em>See more</em> is not necessary";

preg_match_all("#(?(?!<b>)(?!<em>)(?!<u>)<[a-z][a-z0-9]*[^<>]*>)#", $txt, $matches);
// will show only <a href="http://www.google.com">


On peut aller plus loin et dans notre exemple n'afficher que des balises de formatage du texte (pour éviter par exemple les attaques XSS avec l'injection du JavaScript) :

// $txt from previous exemple
$cleanedText = preg_replace("#(?(?!<(\/|)b>)(?!<(\/|)em>)(?!<(\/|)u>)]*>)#", "", $txt);
// will output <b>This is very important text</b> with an external URL forwarding to <u>Google</u>. <em>See more</em> is not necessary


Comme on a pu voir à travers cet article, les expressions régulières offrent aux développeurs les possibilités quasiment indéfinies du traitement des chaînes de caractères. Qui sait, peut-être un jour elles serviront à établir une nouvelle méthode de la lecture rapide ?
Bartosz KONIECZNY 29-04-2012 16:00 applications web
Moi

Développeur d'applications Internet et journaliste passionné par l'adjectif français. Un aigle polonais orienté vers la progression, volant très haut et écoutant du zouk après les matches du foot français.

Vous appréciez mon travail ?

Pour contribuer au développement de ce site, ou pour remercier pour des articles rédigés, vous pouvez faire un don.

Un conseil Symfony2

Un problème filemtime()

Si pendant le développement de votre projet Symfony2 vous rencontrez un problème avec fonction filemtime, il peut s'agir d'un dysfonctionnement temporaire. Warning: filemtime() [function.filemtime]: stat failed for C:\Program Files (x86)\EasyPHP-5.3.5.0\www\gagu\src\Bun\DleBundle/Resources/views/Bundle/show.html.php in C:\Program Files (x86)\EasyPHP-5.3.5.0\www\appli\app\cache\dev\classes.php line 2064 Pour résoudre ce problème, vous pouvez être amenés à supprimer le cache du répertoire dev (si vous êtes en mode développement).