Você está na página 1de 465

Sbastien Aperghis-Tramoni Philippe Bruhat Damien Krotkine Jrme Quelin

LE GUIDE DE SURVIE

Perl

moderne
LESSENTIEL DES PRATIQUES ACTUELLES

Perl moderne
Sbastien Aperghis-Tramoni Damien Krotkine Jrme Quelin
avec la contribution de Philippe Bruhat

Pearson Education France a apport le plus grand soin la ralisation de ce livre an de vous fournir une information complte et able. Cependant, Pearson Education France nassume de responsabilits, ni pour son utilisation, ni pour les contrefaons de brevets ou atteintes aux droits de tierces personnes qui pourraient rsulter de cette utilisation. Les exemples ou les programmes prsents dans cet ouvrage sont fournis pour illustrer les descriptions thoriques. Ils ne sont en aucun cas destins une utilisation commerciale ou professionnelle. Pearson Education France ne pourra en aucun cas tre tenu pour responsable des prjudices ou dommages de quelque nature que ce soit pouvant rsulter de lutilisation de ces exemples ou programmes. Tous les noms de produits ou autres marques cits dans ce livre sont des marques dposes par leurs propritaires respectifs. Publi par Pearson Education France 47 bis, rue des Vinaigriers 75010 PARIS Tl : 01 72 74 90 00 www.pearson.fr Ralisation PAO : euklides.fr Collaboration ditoriale : Jean-Philippe Moreux

ISBN : 978-2-7440-4164-8 Copyright 2010 Pearson Education France Tous droits rservs

Aucune reprsentation ou reproduction, mme partielle, autre que celles prvues larticle L. 122-5 2 et 3 a) du code de la proprit intellectuelle ne peut tre faite sans lautorisation expresse de Pearson Education France ou, le cas chant, sans le respect des modalits prvues larticle L. 122-10 dudit code.

Table des matires


Les auteurs Avant-propos
XVI XVII 1 1 2 3 4 5 6 6 7 9 10 10 11

1 Dmarrer avec Perl


Vrier la version de Perl Excuter les exemples Excuter perl sur un chier diter les programmes Installer Perl sous Linux Installer Perl sous Windows Installer Perl sous Mac OS X

2 Installer un module Perl


Chercher un module avec cpan Installer un module avec cpan Mettre jour les modules Installer un module avec cpanm

Partie I Langage et structures de donnes


3 lments du langage
Excuter perl en ligne de commande Excuter perl sur un chier Crer un chier excutable Excuter en mode dboggage Rgles gnrales du langage Les types de donnes Initialiser une variable scalaire
13 13 15 15 15 17 18 19

III

IV

Perl moderne

Dclarer une variable scalaire Afcher un scalaire Crer une chane de caractres La notion de contexte Travailler sur les nombres Travailler sur les chanes Tester si un scalaire est dni Dclarer une fonction Passer des paramtres Renvoyer une valeur de retour Utiliser des variables dans une fonction Les oprateurs de test Tester une expression Effectuer une opration conditionnelle Effectuer une opration si un test est faux Tester ngativement une expression Effectuer des tests avec and et or Boucler sur les lments dune liste Boucler tant quun test est vrai Crer une rfrence sur scalaire Drfrencer une rfrence sur scalaire Accder une variable rfrence Passer un paramtre par rfrence Utiliser des rfrences sur fonctions Rcuprer les arguments de la ligne de commande Excuter des commandes systme Terminer abruptement un programme Crer un module Perl Utiliser un module Perl

19 20 20 22 24 25 26 26 27 28 29 29 31 32 33 34 34 35 36 37 38 38 39 40 41 42 42 43 44 47 47 48 49

4 Structures de donnes
Crer une liste Crer une liste avec un intervalle Crer une liste de mots

Table des matires

Crer un tableau une dimension Accder aux lments dun tableau Affecter un lment dun tableau Obtenir le premier lment dun tableau Obtenir le dernier lment dun tableau Obtenir la taille dun tableau Assigner un lment en dehors du tableau Tester les lments dun tableau Manipuler la n dun tableau Manipuler le dbut dun tableau Manipuler le milieu dun tableau Supprimer un lment dun tableau Inverser une liste ou un tableau Aplatir listes et tableaux Manipuler une tranche de tableau Boucler sur les lments dun tableau Crer un tableau plusieurs dimensions Rfrencer un tableau Drfrencer un tableau Crer des rfrences dans un tableau Accder un tableau de tableaux Modier un tableau de tableaux Dumper un tableau Utiliser les tables de hachage Crer une table de hachage Accder aux lments dune table de hachage Supprimer un lment dune table de hachage Tester lexistence et la dnition dun lment Utiliser des tranches de hashs Obtenir la liste des cls Obtenir la liste des valeurs dune table de hachage Dumper un hash Boucler sur un hash avec foreach Boucler sur un hash avec each

50 51 52 53 53 54 55 55 56 58 58 60 60 61 62 65 67 67 68 69 70 71 71 74 75 76 78 78 79 80 81 82 82 83

VI

Perl moderne

Rfrencer un hash Drfrencer un hash Crer des structures hybrides Transformer tous les lments dun tableau ou dune liste avec map Filtrer un tableau ou une liste avec grep Renvoyer le premier lment dune liste avec List::Util Trouver le plus grand lment avec List::Util Trouver le plus petit lment avec List::Util Rduire une liste avec List::Util Mlanger une liste avec List::Util Faire la somme dune liste avec List::Util Savoir si un lment vrie un test avec List::MoreUtils Savoir si aucun lment ne vrie un test avec List::MoreUtils Appliquer du code sur deux tableaux avec List::MoreUtils Tricoter un tableau avec List::MoreUtils Enlever les doublons avec List::MoreUtils

84 84 85 86 88 89 90 90 91 92 92 92 93 93 93 94 95 96 97 98 99 100 100 101 101 102 104 106 107 108

5 Expressions rgulires
Effectuer une recherche Rechercher et remplacer Stocker une expression rgulire Rechercher sans prendre en compte la casse Rechercher dans une chane multiligne Rechercher dans une chane simple Neutraliser les caractres espace Contrler la recherche globale de correspondances Distinguer caractres normaux et mtacaractres tablir une correspondance parmi plusieurs caractres Ancrer une expression rgulire en dbut de ligne Ancrer une expression rgulire en n de ligne Utiliser une ancre de dbut de chane

Table des matires

VII

Utiliser une ancre de n de chane Utiliser une ancre de frontire de mot Utiliser une ancre par prxe de recherche Utiliser une ancre de correspondance globale Quantieur * Quantieur + Quantieur ? Quantieur {n} Quantieur {n,m} Quantieurs non avides Quantieurs possessifs Capturer du texte avec les groupes capturants Grouper sans capturer avec les groupes non capturants Dnir des alternatives Dcouper une chane avec split Utiliser Regexp::Common Utiliser Regexp::Assemble Utiliser Text::Match::FastAlternatives Utiliser YAPE::Regex::Explain

108 108 109 109 111 112 112 113 113 114 115 116 119 121 122 124 126 128 128

Partie II Objet moderne


6 Concepts objet en Perl
Crer un objet Connatre la classe dun objet Appeler une mthode Dnir une mthode Dnir un constructeur
131 132 132 133 133 135 137 137 138 140 141

7 Moose
Dclarer une classe Dclarer un attribut Accder aux objets Modier le nom des accesseurs

VIII

Perl moderne

Mthodes de prdicat et de suppression Rendre un attribut obligatoire Vrier le type dun attribut Donner une valeur par dfaut Construire un attribut Rendre un attribut paresseux Spcier un dclencheur Drfrencer un attribut Affaiblir un attribut rfrence Chaner les attributs Simplier les dclarations dattributs tendre une classe parente Surcharger une mthode Modier des attributs hrits Crer un rle Consommer un rle Requrir une mthode Construire et dtruire des objets Modier les paramtres du constructeur Interagir avec un objet nouvellement cr Interagir lors de la destruction dun objet

143 144 145 145 147 148 150 151 152 153 153 154 155 156 156 157 158 159 159 160 161 163 163 165 165 166 167 167 168 171 171 172

8 Le typage dans Moose


Utiliser les types de base Crer une bibliothque de types personnaliss Dnir un sous-type Dnir un nouveau type Dnir une numration Dnir une union de types Transtyper une valeur

9 Moose et les mthodes


Modier des mthodes Intercaler un prtraitement

Table des matires

IX

Intercaler un post-traitement Sintercaler autour dune mthode Modier plusieurs mthodes Appeler la mthode parente Augmenter une mthode Dlguer une mthode un attribut Dlguer des mthodes une structure Perl

173 174 175 176 177 179 180

Partie III Manipulation de donnes


10 Fichiers et rpertoires
Ouvrir des chiers Utiliser un descripteur de chier en lecture Utiliser un descripteur de chier en criture Fermer un descripteur de chier Manipuler des chemins avec Path::Class Pointer un chier ou un rpertoire Pointer un objet relatif Pointer un objet parent Obtenir des informations Crer ou supprimer un rpertoire Lister un rpertoire Ouvrir un chier Supprimer un chier Parcourir un rpertoire rcursivement Crer un chier temporaire Crer un rpertoire temporaire Identier les rpertoires personnels Connatre le rpertoire courant Changer de rpertoire
183 183 185 187 188 189 190 191 191 192 193 193 194 196 196 197 198 199 201 201 203 205 208

11 Bases de donnes SQL


Se connecter Tester la connexion

Perl moderne

Se dconnecter Prparer une requte SQL Lier une requte SQL Excuter une requte SQL Rcuprer les donnes de retour dune requte SQL Combiner les tapes dexcution dune requte SQL Grer les erreurs Tracer lexcution Proler lexcution

209 209 211 211 212 214 216 217 219 223 223 232 234 237 238 239 240 242 243 244 247 249 250 250 251 252 253 254 254 255 256 257 258

12 Abstraction du SQL, ORM et bases non-SQL


Utiliser Data::Phrasebook::SQL ORM avec DBIx::Class Crer un schma DBIx::Class Utiliser un schma DBIx::Class Stocker des objets avec KiokuDB Se connecter une base KiokuDB Stocker et rcuprer des objets Administrer une base KiokuDB Utiliser une base oriente paires de cl-valeur Utiliser une base oriente documents

13 Dates et heures
Utiliser le module Date::Parse Lire une date avec Date::Parse Interprter une date avec Date::Parse Changer la langue avec Date::Language Grer les intervalles de temps avec Time::Duration Interprter une dure avec Time::Duration Obtenir la dure partir de maintenant Rduire lafchage Changer la langue avec Time::Duration::fr Utiliser les modules DateTime Construire une instance DateTime arbitraire Choisir un fuseau horaire

Table des matires

XI

Obtenir linstant prsent Obtenir la date du jour Obtenir lanne Obtenir le mois Obtenir le nom du mois Obtenir le jour du mois Obtenir le jour de la semaine Obtenir le nom du jour Obtenir lheure Obtenir les minutes Obtenir les secondes Obtenir les nanosecondes Obtenir des dures de temps Dcaler une date dans le futur Ajouter une dure Dcaler une date dans le pass Soustraire une dure Calculer un intervalle de temps Gnrer une reprsentation textuelle dune date Interprter une date

258 259 259 260 260 260 260 261 261 261 261 262 263 264 265 265 266 267 268 270

Partie IV Formats structurs


14 XML
Charger un document XML avec XML::LibXML Parcourir un arbre DOM Utiliser XPath Utiliser SAX Crer un objet XML::Twig Charger du contenu XML avec XML::Twig Crer des handlers avec XML::Twig Produire le contenu XML de sortie Ignorer le contenu XML de sortie
273 275 277 280 281 285 286 287 289 289

XII

Perl moderne

Accder au nom dun lment Changer le nom dun lment Obtenir le contenu texte dun lment Changer le contenu XML dun lment Interagir avec les attributs dun lment Interagir avec les lments environnants Effectuer un copier-coller

290 291 292 292 293 294 297 301 301 307 310 316 319 320

15 Srialisation de donnes
Srialiser avec Data::Dumper Srialiser avec Storable Srialiser avec JSON Srialiser avec YAML

16 Fichiers de conguration
Fichiers .INI

Partie V Programmation vnementielle


17 Principes gnraux de POE
POE vnements Sessions Le noyau POE
325 326 327 328 329 331 331 332 333 335 336 339 340

18 POE en pratique
Crer une session POE Envoyer un vnement Passer des paramtres Utiliser des variables prives Communiquer avec une autre session Envoyer un message diffr Envoyer un message lheure dite

Table des matires

XIII

Terminer le programme Couper les longs traitements Bannir les entres-sorties bloquantes Composants de haut niveau Bote outils de niveau intermdiaire Fonctions de bas niveau POE Exemple dutilisation : le composant DBI

341 341 345 346 347 348 348 357 358 359

19 POE distribu
Crer un serveur IKC Crer un client IKC

Partie VI Web
20 Analyse de documents HTML
Analyser avec les expressions rgulires Utiliser lanalyseur vnementiel HTML::Parser Instancier un analyseur HTML::Parser Crer un gestionnaire dvnements Lancer lanalyse HTML Terminer lanalyse du contenu Dtecter un nouveau document Dtecter une balise Dtecter un commentaire Dtecter un dbut de balise Dtecter du texte brut Dtecter la n dune balise Dtecter la n du document Dtecter des instructions de traitement Capturer les autres vnements Extraire du texte dun document Produire une table des matires Crer une instance HTML::TokeParser
363 364 365 365 366 368 369 369 370 370 371 371 372 372 372 373 373 374 377

XIV

Perl moderne

Rcuprer des tokens Obtenir des balises Obtenir du texte Obtenir du texte nettoy Extraire le texte dun document avec HTML::Parser Produire une table des matires avec HTML::Parser Analyse par arbre avec HTML::TreeBuilder Crer un arbre Rechercher un lment Extraire du texte dun document avec HTML::TreeBuilder Produire une table des matires avec HTML::TreeBuilder Extraire le titre dun document avec HTML::TreeBuilder

378 379 380 381 381 382 383 384 385 387 387 388 389 390 391 392 393

21 HTTP et le Web
Adresses Messages Requtes Rponses

22 LWP

395 Utiliser LWP::Simple 395 Faire une requte GET sur une URL 395 Enregistrer le contenu de la rponse 396 Faire une requte HEAD sur une URL 396 Utiliser LWP::UserAgent 397 Crer un agent LWP::UserAgent 397 Grer les rponses 398 Faire une requte GET sur une URL avec LWP::UserAgent 399 Enregistrer le contenu de la rponse 400

Faire une requte HEAD sur une URL avec LWP::UserAgent Faire une requte POST sur une URL avec LWP::UserAgent

401 402

Table des matires

XV

Envoyer des requtes Diffrences entre LWP::UserAgent et un vrai navigateur

403 403 407 407 408 410 411 412 413 415 415 417 417 417 418 418 420 420 420 421 421 421 422 422 422 423 423 424 429 431

23 Navigation complexe
Traiter les erreurs Authentier Grer les cookies Crer un objet HTML::Form Slectionner et modier des champs de formulaire Valider un formulaire

24 WWW::Mechanize
Crer un objet WWW::Mechanize Lancer une requte GET Lancer une requte POST Revenir en arrire Recharger une page Suivre des liens Traiter les erreurs Authentier Grer les cookies Grer les formulaires Slectionner un formulaire par son rang Slectionner un formulaire par son nom Slectionner un formulaire par son identiant Slectionner un formulaire par ses champs Remplir le formulaire slectionn Valider le formulaire slectionn Slectionner, remplir et valider un formulaire Exemple dapplication

A Tableau rcapitulatif des oprateurs Index

Les auteurs
Les auteurs, activement impliqus dans la communaut Perl franaise, sont membres de lassociation Les mongueurs de Perl (http://www.mongueurs.net). Sbastien Aperghis-Tramoni travaille actuellement chez Orange France comme ingnieur systme et dveloppeur Perl. Utilisateur de ce langage depuis 15 ans, il contribue rgulirement Perl, au CPAN (une quarantaine de modules), et dautres logiciels libres. Il participe de nombreuses confrences en France et en Europe depuis 2003. Il est lauteur de plusieurs articles parus dans GNU/Linux Magazine France. Damien Krotkine, ingnieur, a travaill notamment chez Mandriva (diteur Linux), et Venda (leader eCommerce). Auteur de sept modules CPAN, il est galement coauteur de Linux le guide complet (Micro Application), et auteur darticles dans GNU/Linux Magazine France. Ancien dveloppeur Gentoo Linux et ancien contributeur Mandriva, il participe rgulirement aux confrences French Perl Workshop et OSDC.fr. Jrme Quelin utilise le langage Perl depuis 1995. Il est lauteur dune quarantaine de modules sur CPAN et contribue de nombreux autres modules et projets opensource. Il maintient aussi un certain nombre de packages pour Mandriva (et Mageia maintenant). Philippe Bruhat vit Lyon et travaille pour Booking. com. Utilisateur de Perl depuis 1998, il est lauteur de vingt-cinq modules sur CPAN et darticles dans GNU/Linux Magazine France ; il est aussi cotraducteur de Programming Perl et Perl Best Practices, contributeur Perl Hacks et relecteur pour OReilly. Il a particip (et organis) de nombreuses confrences Perl depuis 2000.

Avant-propos
Le langage Perl est en plein essor. Plus de vingt ans ont pass depuis sa cration, et pourtant il a su sadapter pour rester un langage pertinent. Car depuis mi-2008, cest une petite rvolution de fond qui a lieu. Sinspirant des concepts de la version 6 venir, elle mme sinspirant dautres langages, Perl 5 sest radicalement modernis. Tout dabord, depuis la version 5.10, linterprteur perl est mis jour rgulirement, avec une version majeure tous les ans apportant de nouvelles fonctionnalits et des mises jour mineures tous les trimestres. Ce cycle rgulier est garant dun langage toujours stable et jour des dernires nouveauts. Ensuite, le langage lui-mme a subi de profondes modications, avec notamment un nouveau systme de programmation oriente objet, Moose, et de nouveaux motscls. Le dpt de modules CPAN voit galement son rythme de croissance sacclrer, malgr sa taille dj confortable (21 000 distributions et 86 000 modules). Aucun autre langage dynamique ne possde autant de bibliothques externes centralises. Enn, un nombre croissant de logiciels Perl de haut niveau et utilisant les dernires technologies voient le jour, dans tous les secteurs. Dans le domaine du Web notamment, Perl amorce son retour sur le devant de la scne, avec des frameworks complets comme Catalyst, ou plus lgers et agiles comme Dancer.

XVIII Perl moderne

Bien sr, toutes ces volutions changent la manire dont Perl est utilis : un programme crit en lan 2000, mme sil reste compatible, ne ressemble plus du tout au mme programme crit au got du jour, en 2010. Cest donc tout lintrt de ce livre, vritable guide de survie dans le monde Perl contemporain : les auteurs, fortement impliqus dans lvolution de ce langage, vous apprennent lutiliser dans ses dimensions les plus actuelles. Un monde qui vous tonnera par sa richesse et ses possibilits. . . mais surtout par son incomparable modernit !

1
Dmarrer avec Perl
Cet ouvrage contient un nombre important dexemples et dextraits de code. La plupart constituent des petits programmes part entire, quil est possible dexcuter directement, dautres sont des extraits non fonctionnels tels quels, mais qui sont sufsamment simples pour tre intgrs dans des programmes existants. Ce petit chapitre a pour but de dmarrer rapidement avec cet ouvrage, pour pouvoir excuter les exemples de code immdiatement et exprimenter le langage. En rgle gnrale, Perl est disponible sous Linux et Mac OS X. Sous Windows toutefois, il faut commencer par linstaller. Pour des dtails quant linstallation de Perl, voir page 5.

Vrier la version de Perl


Il est trs important de sassurer de disposer dune version rcente de Perl. En effet, cet ouvrage se focalise sur les versions modernes de Perl, cest--dire dont la version est suprieure ou gale 5.10.

CHAPITRE 1 Dmarrer avec Perl

perl -v

Cette commande renvoie quatre paragraphes de description et dinformation sur Perl. La premire ligne ressemble cela :
This is perl, v5.10.0 built for darwin-2level

Elle indique quil sagit de la version 5.10 de Perl. Si la version tait infrieure 5.10, il faudrait mettre jour Perl (pour installer ou mettre jour Perl, voir page 5).

Excuter les exemples


Le moyen le plus simple de tester les concepts dcrits dans cet ouvrage est dexcuter les exemples de code. Pour cela, il est conseill de les reproduire dans un chier, puis dutiliser linterprteur Perl en lui donnant le chier en argument. La premire tape consiste crer un chier (qui sera nomm test.pl1 ). Ce chier doit contenir les lignes suivantes, au dbut :
use s t r i c t ; use w a r n i n g s ; use 5 . 0 1 0 ;

Ces lignes prcisent linterprteur Perl quil doit passer en mode strict et activer tous les avertissements, et quil requiert une version suprieure ou gale 5.10, dont il active toutes les fonctionnalits supplmentaires. En dessous de ces lignes, le code Perl proprement parler peut enn tre insr.
1. Lextension de chier .pl est standard pour les programmes autonomes Perl, galement appels scripts. Les modules Perl, qui doivent tre chargs, sont stocks dans des chiers avec lextension .pm.

Excuter perl sur un chier

Voici un exemple dun tel chier, pour excuter lextrait de code sur foreach (voir page 66) :
# ## f i c h i e r t e s t . pl ### use s t r i c t ; use w a r n i n g s ; use 5 . 0 1 0 ; # tout l alphabet my @ a l p h a b e t = ( a .. z ) ; my $ c o u n t = 0; f o r e a c h my $ l e t t r e ( @ a l p h a b e t ) { say la . ++ $ c o u n t . e l e t t r e de l \ a l p h a b e t est : . $lettre ; }

Excuter perl sur un chier


Il ne reste plus qu lancer perl en lui passant le chier test.pl en argument :
perl test.pl

Et la console afche le rsultat de lexcution, dans ce cas prcis :


la 1e lettre de lalphabet est : a la 2e lettre de lalphabet est : b la 3e lettre de lalphabet est : c ... la 25e lettre de lalphabet est : y la 26e lettre de lalphabet est : z

CHAPITRE 1 Dmarrer avec Perl

Attention
Il est trs important dajouter les trois lignes :
use s t r i c t ; use w a r n i n g s ; use 5 . 0 1 0 ;

au dbut du chier. Faute de quoi linterprteur ne prendra pas en compte les nouvelles fonctionnalits de Perl 5.10, notamment la fonction say().

diter les programmes


Pour diter les programmes Perl, il est ncessaire dutiliser un diteur de texte puissant et si possible orient programmation, qui prenne en charge les spcicits de Perl. Lenvironnement de dveloppement intgr (IDE) Eclipse peut tre utilis avec le greffon (plugin) EPIC. Le site web www.epic-ide.org contient les instructions (trs simples) qui permettent dinstaller EPIC dans Eclipse. Bien sr, le prrequis est davoir une version rcente dEclipse, qui peut tre tlcharge sur www.eclipse.org. Cette solution EPIC-Eclipse est multi-plateforme, et fonctionne trs bien sous Linux, Windows ou Mac OS X. Padre (http://padre.perlide.org) est un nouvel IDE crit en Perl, par des dveloppeurs Perl, pour les dveloppeurs Perl. En trs peu de temps, cet IDE a acquis une renomme certaine, et cest probablement lIDE Perl de demain. Il fonctionne sous Windows, Linux, et Mac OS X2 . Pour Windows, il y a mme un paquetage qui intgre Strawberry Perl et Padre en une seule installation, qui permet dobtenir un environnement Perl oprationnel en quelques minutes et sans aucune conguration.
2. Mme si linstallation sur ce systme est moins aise que sur les deux premiers.

Installer Perl sous Linux

Pour les utilisateurs qui nutilisent pas dIDE, voici une petite liste trs classique de recommandations :
q

Sous Unix, Emacs et vim sont incontournables. Dautres diteurs de texte (comme SciTE) sont galement couramment utiliss. Sous Windows, notepad++ et Editplus sont des diteurs qui proposent une prise en charge basique de Perl. Vim est galement disponible sous forme graphique, gvim. Sous Mac OS X, il y a une version Emacs de trs bonne qualit, ainsi que des diteurs spciques cette plateforme, comme TextMate.

Installer Perl sous Linux


Pratiquement toutes les distributions Linux sont fournies avec Perl en standard. Toutes les distributions majeures (Ubuntu, Debian, Red Hat, Fedora, Mandriva, OpenSuSE. . .) permettent dutiliser Perl directement sans avoir installer quoi que ce soit. Cependant il convient de vrier la version installe, avec :
perl -v

qui permet de renvoyer des informations sur la version de linterprteur perl, dont la version doit tre au moins 5.10. Si ce nest pas le cas, il convient de mettre jour Perl. Pour mettre jour Perl sous Linux, il suft dutiliser le gestionnaire de paquetage livr avec la distribution.
# Ubuntu / Debian aptitude update && aptitude install perl5 # Mandriva urpmi perl # Red Hat / Fedora yum install perl

CHAPITRE 1 Dmarrer avec Perl

Installer Perl sous Windows


Perl nest pas livr en standard avec Windows. Il existe plusieurs distribution Perl pour Windows, mais la meilleure ce jour est Strawberry Perl. En se connectant sur http://strawberryperl.com, il est ais de tlcharger la dernire version. Strawberry Perl sinstalle trs facilement3 , il possde un programme dinstallation MSI qui guide lutilisateur tout au long de la conguration. Une fois install, le rpertoire contenant linterprteur perl est ajout la variable denvironnement PATH, et perl.exe est normalement utilisable depuis nimporte quel rpertoire dans la console Windows. Il peut galement tre utilis travers des IDE, comme Padre ou Eclipse.

Installer Perl sous Mac OS X


Perl est fourni en standard avec Mac OS X. Cependant, il est prfrable de mettre jour Perl. Pour cela, la solution la plus simple est dutiliser le projet MacPorts (http:// www.macports.org), qui est un systme de paquetage similaire ceux disponibles sous Linux. Il faut tout dabord installer le logiciel MacPorts sur le Mac, puis mettre jour Perl. MacPorts est une grande bibliothque de logiciel, et contient plusieurs paquetages (ports dans la dnomination MacPorts) de Perl : perl5, perl5.8, perl5.10, perl5.12, et probablement dautres plus jour. Il est conseill dinstaller la dernire version, ou perl5.12 au minimum. Voici la ligne de commande entrer pour installer perl5.12 :
port install perl5.12

Linterprteur pourra tre lanc de /opt/local/bin/perl.


3. Par dfaut sur C:/strawberry, congurable pendant linstallation.

2
Installer un module Perl
CPAN (Comprehensive Perl Archive Network) est la plus vaste bibliothque logicielle Perl. En pratique, cest un ensemble de sites web qui donnent accs en ligne la formidable bibliothque des modules Perl ainsi qu leur documentation , modules qui permettent dlargir les possibilits du langage Perl. Attention
Il ne faut pas confondre :
q

CPAN : le nom de la bibliothque logicielle mise disposition du public sur Internet ;

cpan : le nom du programme lancer en ligne de commande


pour pouvoir chercher et installer des modules depuis CPAN.

De mme, il ne faut pas confondre :


q q

Perl (avec une majuscule) : le langage de programmation ;

perl (sans majuscule) : le nom de linterprteur perl, cest-dire le programme qui excute les programmes.

CHAPITRE 2 Installer un module Perl

CPAN
CPAN est un lment capital du succs de Perl. Aucun autre langage ne possde une telle bibliothque, vaste, bien organise, bien gre, compatible, et soutenue par une communaut aussi performante. En particulier, le site Search CPAN (http://search.cpan.org) est la pierre angulaire pour trouver des modules, accder leur documentation en ligne, ainsi qu des commentaires et ajouts faits la documentation, au code source des modules, aux archives des distributions, aux tickets ouverts, aux apprciations des utilisateurs, aux rapports des tests automatiques (trs utiles pour savoir sur quelles versions de Perl et sur quelles plateformes le module fonctionne) et dautres outils annexes encore.

Installer un module CPAN est une opration qui consiste trouver le module recherch dans la bibliothque CPAN, le tlcharger, le congurer, trouver sil dpend dautres modules non installs, et enn installer le module et ses ventuelles dpendances. Heureusement, tout ce processus est trs largement automatis grce cpan. Ce programme se lance simplement depuis la ligne de commande :
cpan

Il est galement possible dexcuter cette ligne :


perl -MCPAN -e shell

Attention
Sous Linux et Mac OS X, il sera sans doute indispensable davoir les droits dadministrateur pour pouvoir installer un module Perl.

Chercher un module avec cpan

Il convient donc de passer en mode root avant de lancer cpan, ou bien dutiliser la commande sudo.

Sous Windows, avec Strawberry Perl, cpan est automatiquement congur. Cependant, sous Linux et Mac OS X, au premier lancement de cpan, plusieurs questions sont poses lutilisateur. Une rponse par dfaut est toujours propose, de sorte que la conguration initiale est trs simple. Une fois lance, la commande cpan donne accs un environnement de commande trs simple. Les commandes sont entres sur une ligne, et valides avec la touche Entre. Il est facile davoir la liste des commandes et leurs options en tapant help :
cpan[1]> help

Voici cependant les commandes les plus utilises.

Chercher un module avec cpan


i /requete/

La commande cpan i permet de rechercher le nom exact dun module1 Perl en partant dun fragment de son nom ou dun de ses chiers. Par exemple, il est possible davoir une liste de tous les modules Perl qui mentionnent XML en tapant :
cpan[1]> i /XML/

1. Ou dun auteur, dun bundle, dune distribution.

10

CHAPITRE 2 Installer un module Perl

Installer un module avec cpan


install Un::Module

Une fois le nom du module trouv, il suft dutiliser la commande install pour linstaller :
install Modern::Perl

cpan va alors tlcharger et installer le module. Sil dpend

dautre modules, lutilisateur devra conrmer linstallation de ces dpendances. Il existe une mthode plus directe dinstaller un module. Au lieu de lancer depuis la console :
cpan

puis dentrer :
install Un::Module

Il est possible dentrer directement depuis la console :


cpan Un::Module

Cela va directement installer le module en question.

Mettre jour les modules


r upgrade

La commande r permet de lister tous les modules dont il existe une mise jour.
upgrade permet de mettre jour un module, plusieurs, ou tous. Sans argument, upgrade met jour tous les modules.

Si un argument est donn, il servira de ltre.

Installer un module avec cpanm

11

Installer un module avec cpanm


cpanm Un::Module

Une nouvelle mthode pour installer un module Perl est apparue avec larrive du programme cpanm, dont le module dinstallation sappelle App::cpanminus. cpanm permet dinstaller trs rapidement un module Perl, sans aucune interaction. Pour linstaller, il suft dutiliser cpan :
cpan App::cpanminus

ou encore plus simplement, de tlcharger directement le programme :


wget http://xrl.us/cpanm chmod +x cpanm

La commande cpanm est alors disponible et peut tre utilise en lieu et place de cpan. Pour installer un module, il suft de taper :
cpanm Un::Module

cpanm na pas besoin dtre congur et nafche pas din-

formation tant que linstallation se passe bien. Les modules dpendants sont automatiquement installs, ce qui fait que lutilisateur na pas interagir avec cpanm.

3
lments du langage
Linterprteur perl sexcute traditionnellement soit depuis la console, soit travers un IDE.

Partie I Langage et structures de donnes

Excuter perl en ligne de commande


Il est possible de donner des instructions directement perl sur la ligne de commande :
perl -E say "hello world";

Astuce
Sous Windows, la console par dfaut nest pas trs puissante, et ne comprend pas les guillemets simples . On peut crire la ligne de cette faon : perl -E "say hello world;" Ou bien utiliser une console de meilleure qualit, comme Tera Term1 .

1. Disponible sur http://ttssh2.sourceforge.jp.

14

CHAPITRE 3 lments du langage

Cette manire dexcuter du code Perl est trs utile pour effectuer des petites tches, ou bien pour tester quelques instructions :
perl -E my $variable = 40; $variable += 2; say $variable

Cette commande afche :


42

qui est bien le rsultat de 40 + 2. Info


Loption -E permet dexcuter du code Perl fournit en argument de la ligne de commande. Cette option est identique -e sauf quelle active toutes les fonctions des dernires versions de Perl. Il est donc recommand de toujours utiliser -E, et non -e.

Linterprteur supporte beaucoup dautres options. Elles sont documentes dans le manuel perlrun2 , mais voici les plus importantes dentre elles : q -h : afche un rsum des options de linterprteur.
q

-w : active lafchage des messages dalertes (warnings). Cette option est conseille car un message de warnings qui apparat est souvent synonyme de bug potentiel. -d : lance lexcution en mode dboggage. Trs utile pour trouver la source dun problme dans un programme (voir page 15).

Excuter perl sur un chier


perl chier.pl

2. Accessible avec la commande man perlrun

Excuter en mode dboggage

15

Il est plus pratique et prenne de sauvegarder un programme Perl dans un chier et dexcuter linterprteur perl sur ce chier :
perl chier.pl

Info
Lextension de chiers des programmes Perl est .pl. Lextension de chiers des modules Perl (voir page 43) est .pm.

Crer un chier excutable


Sous Unix (Linux, Mac OS X), il est possible de crer un chier qui sexcutera directement avec perl en faisant commencer le chier par :
# !/ usr / bin / p e r l my $ v a r = 40; p r i n t $ v a r + 2;

Il est galement ncessaire de rendre le chier excutable avec la commande chmod :


chmod u+x chier.pl

Le chier est maintenant excutable :


./chier.pl

Excuter en mode dboggage


perl -d chier.pl perl -d -E ..

16

CHAPITRE 3 lments du langage

Il est possible de lancer linterprteur perl en mode dboggage. Dans ce mode, linterprteur peut excuter les instructions une par une, et lutilisateur a la possibilit de vrier la valeur des variables, de changer leur contenu, et dexcuter du code arbitraire. Astuce
Les IDE (voir page 4) permettent de dbogguer un programme Perl de manire visuelle, interactive, dans linterface graphique, sans avoir recours au mode console expliqu ci-dessous. Eclipse, avec le mode EPIC, et Padre permettent ainsi lutilisateur de placer visuellement des points darrt (breakpoint), dexcuter pas pas les programmes, et dafcher le contenu des variables.

Une fois perl lanc en mode dboggage, linvite de commande afche ceci :
$ perl -d -E my $var = 40; ... main::(-e:1): my $var = 40; DB<1>

Voici une slection des commandes quil est possible dentrer pour interagir avec le mode dboggage : q h : permet dafcher un rcapitulatif des commandes.
q

l : permet dafcher le code source lendroit de lins-

truction en cours dexcution.


q

- : permet dafcher les lignes prcdant linstruction en

cours.
q

n : permet dexcuter linstruction en cours, sans ren-

trer dans le dtail. Ainsi, si linstruction est un appel de fonction, celle-ci sera excute entirement avant que le mode dboggage sarrte.

Rgles gnrales du langage

17

s : contrairement n, s excute linstruction en cours, mais sarrte la prochaine sous-instruction. r : excute toutes les instructions jusqu la prochaine

instruction return.
q q

c : continue lexcution du programme sans sarrter. b : place un point darrt sur la ligne en cours. Il est

possible de donner un numro de ligne, un nom de fonction complet ou une condition en paramtre.
q

q : quitte le mode dboggage.

Rgles gnrales du langage


Un programme Perl est une suite dinstructions, spares par des point-virgules3 . Les instructions sont sensibles la casse, donc Fonction nest pas la mme chose que fonction. Les noms de variables commencent par un sigil, cest-dire un caractre spcial qui permet de reconnatre son type. Les chanes de caractres sont gnralement entoures des caractres ou (voir page 20). Le langage propose des fonctions de base4 . Il est bien sur possible de crer des fonctions, pour modulariser le code. En plus des fonctionnalits de base, il existe des centaines de modules additionnels5 qui permettent denrichir le langage. Il est bien sr possible dcrire ses propres modules,
3. Il est possible domettre le point-virgule lors dune n de bloc, ou une n de chier. 4. La liste complte des fonctions de base est accessible dans la documentation perlfunc, accessible sous Unix avec man perlfunc. 5. Les modules additionnels sont disponibles sur CPAN (http://cpan. org), et installables laide de la commande cpan (voir Chapitre 2).

18

CHAPITRE 3 lments du langage

pour rassembler les fonctionnalits semblables dun logiciel et les diffuser. Astuce
Il est possible de passer la ligne avant la n dune instruction. Un retour chariot ne signie pas une n dinstruction. Seule un point-virgule, une n de bloc ou une n de chier permet de signier une n dinstruction. Cette fonctionnalit est trs utile pour amliorer la lisibilit dun code source :
my @ t a b l e a u = ( sur , une , l i g n e ) ; my @ t a b l e a u 2 = ( sur , plusieurs , lignes );

Les types de donnes


Perl propose de base trois types de donnes : les scalaires, les tableaux et les tables de hachage. Ces types de donnes sont expliqus en dtail au Chapitre 5 (voir page 47), cependant en voici une introduction sommaire. Les scalaires sont un type de donnes qui regroupe les nombres (entiers ou ottants), les chanes de caractres, et les rfrences (voir page 37). Voici quelques exemples de scalaires : 42, -3, 0.01, 2e36 , une chane de caractres. Les variables contenant un scalaire sont identies par le sigil $. Par exemple, $variable, $chaine. Les tableaux sont des regroupements de scalaires. Ils sont taille variable, et leurs indices commencent 0. Il nest pas ncessaire dinitialiser un tableau (voir page 47 pour plus
6. Correspond 2103 , donc 2 000.

Dclarer une variable scalaire

19

de dtails). Les variables contenant un tableau sont identies par le sigil @. Par exemple, @tableau, @elements. Aussi appeles tableaux associatifs, ou simplement hash, les tables de hachage sont des regroupements dassociations cl-valeur. Les cls sont des chanes de caractres, et les valeurs sont des scalaires (voir page 47 pour plus de dtails). Les variables contenant une table de hachage sont identies par le sigil %. Par exemple, %hash.

Initialiser une variable scalaire


my $variable = ..

Dclarer une variable scalaire est trs simple, il suft dutiliser loprateur daffectation = :
my $ n o m b r e = 42; my $ t e s t = " C e c i est une c h a n e " ;

Le mot-cl my permet de spcier que la variable est locale, et non globale.

Dclarer une variable scalaire


my $variable

Une variable peut galement tre dclare sans tre initialise :


my $ v a r i a b l e ;

Dans ce cas, $variable prend une valeur spciale, undef, qui signie que la variable nest pas initialise.

20

CHAPITRE 3 lments du langage

Afcher un scalaire
print() say()

Pour afcher un scalaire, il est possible dutiliser print. Le code suivant afche le nombre 5 sur la console :
my $ v a r i a b l e = 5; print ( $variable );

Cependant, pour faire de mme mais avec un retour chariot la n de la ligne, say est plus pratique :
my $ t e x t e = " B o n j o u r " ; say ( $ t e x t e ) ;

say est une nouveaut de Perl 5.10, et se comporte comme print, sauf quun retour chariot est ajout la n.

Crer une chane de caractres


chaine, chaine, q(chaine), qq(chaine)

Les chanes de caractres appartiennent la famille des scalaires. Il y a plusieurs mthodes pour crer une chane de caractres :
q

laide des guillemets doubles : les guillemets doubles permettent de crer une chane de caractres avec interpolation. Info
Il est important de ne pas faire trop de diffrence entre un entier, un ottant et une chane de caractres. En Perl, ils sont tous regroups sous la bannire des scalaires.

Crer une chane de caractres

21

my $ c h a i n e = C e c i est une c h a i n e ; # c o n t i e n t : C e c i est une c h a i n e ; my $ v a r = 42; my $ c h a i n e = " Le r e s u l t a t est $ v a r " ; # c o n t i e n t : Le r e s u l t a t est 42;

Les caractres \n, \r etc. sont interprts. Ainsi, un certain nombre de squences dchappement sont reconnues et interprtes (\n, \r, etc.). Le caractre dchappement est lanti-slash (\).
print " Retour \ nChariot "

afche :
Retour Chariot

Pour insrer le caractre dchappement ou des sigils, il suft de les protger :


p r i n t " \ $ v a r R e t o u r \\ n C h a r i o t " ; # affiche $var Retour \ nChariot
q

laide des guillemets simples : les guillemets simples permettent de crer une chane de caractres sans interpolation. Ainsi, un nom de variable ne sera pas remplac par sa valeur ; \n, \r etc. ne seront pas interprts.
my $ c h a i n e = C e c i est une c h a i n e ; # c o n t i e n t : C e c i est une c h a i n e ; my $ v a r = 42; my $ c h a i n e = Le r e s u l t a t est $ v a r \ n ; # c o n t i e n t : Le r e s u l t a t est $ v a r \ n ;

laide de loprateur q() : loprateur q() permet de crer une chane de la mme faon que les guillemets

22

CHAPITRE 3 lments du langage

simples , mais la limite de la chane correspond la parenthse fermante balance :


print q ( chaine avec , ", $var ) # affiche : chaine avec , " , $var p r i n t q ( c h a i n e (1 , 3) ) # a f f i c h e : c h a i n e (1 , 3)
q

laide de loprateur qq() : de manire similaire, loprateur qq() permet de crer une chane avec interpolation.

Astuce
Il est possible dutiliser q() et qq() avec dautres caractres, par exemple [] ou {}. Cela permet de crer des chanes qui contiennent des parenthses non fermes et des guillemets :
p r i n t q [ une d e r r e u r ("] . $erreur . q [") est a p p a r u e ];

La notion de contexte
Une spcicit de Perl est la notion de contexte : Le comportement des oprateurs et lvaluation des expressions dpendent du contexte dans lequel ils sont interprts. Les contextes les plus souvent utiliss sont : q Le contexte scalaire numrique. Une expression value dans ce contexte est considre comme un scalaire numrique, cest--dire un nombre. Dans ce contexte, un oprateur aura un comportement adapt aux nombres. Par exemple, en contexte numrique, une chane 03.20 sera interprte comme le nombre 3.2. Attention, en contexte numrique, une liste ou un tableau sera interprt comme un nombre correspondant

La notion de contexte

23

au nombre dlments. En contexte scalaire numrique, la valeur undef renvoie 0.


q

Le contexte scalaire de chane. Dans ce contexte, toute expression sera considre comme une chane de caractres. Ainsi, un nombre sera interprt comme une suite de caractres. 3.2 sera interprt comme 3.2. En contexte scalaire de chane, la valeur undef renvoie la chane vide . Le contexte de liste. Cest le contexte qui permet de travailler avec des listes et des tableaux. En contexte de liste, un tableau renvoie sa liste dlment. En contexte scalaire, un tableau renvoie un nombre (donc un scalaire) correspondant aux nombre dlments quil contient. Attention, en contexte de liste, undef renvoie une liste dun lment contenant la valeur undef.

Info
Beaucoup doprateurs forcent le contexte. Une liste des oprateurs qui forcent le contexte scalaire numrique ou bien de chane est prsente ci-aprs (voir page 24). Dautres oprateurs ne forcent pas de contexte. Cest le cas de loprateur daffectation = qui fonctionne avec des scalaires numriques, des chanes de caractres ou des listes, sans forcer de contexte. Dans ces cas-l, cest le type des oprandes qui va inuencer le contexte. Voici des exemples :
" 5.00 " + 3

Ici, loprateur + force le contexte numrique, et lexpression renvoie la valeur ottante 8.


my @ t a b l e a u 2 = @ t a b l e a u 1

Ici, la liste gauche force lvaluation de @tableau1 en contexte de liste. @tableau2 contient les lments de @tableau1.
my $ t a i l l e = @ l i s t e

24

CHAPITRE 3 lments du langage

Le scalaire gauche force lvaluation de @liste en contexte scalaire, ce qui renvoie la taille de la liste. Il est possible de forcer un contexte de liste en utilisant des parenthses (..), et un contexte scalaire en utilisant scalar().
my ( $ e l e m e n t 1 ) = @ l i s t e ;

Ici les parenthses forcent le contexte de liste. $element contient donc le premier lment de @liste.
my @ t a b l e a u 2 = s c a l a r ( @ t a b l e a u 1 ) ;

scalar force lvaluation de @tableau1 en contexte scalaire, ce qui renvoie sa taille. Cette dernire est alors stocke en tant que premier lment dans @tableau2.

Travailler sur les nombres


+ - / * % ...

Perl propose les oprateurs mathmatiques classiques : +, -, *, /, sin, cos, %. ** est disponible pour lever un nombre une puissance. Il existe des oprateurs raccourcis : ++, , +=, -=.
my $ v a r $ v a r ++; $var - -; $ v a r += $ v a r -= $ v a r *= = 5; # var # var # var # var # var vaut vaut vaut vaut vaut 6 5 10 5 10

5; 5; 2;

Tous ces oprateurs forcent le contexte scalaire numrique.

Travailler sur les chanes

25

Astuce
$var++ renvoie la valeur de $var, puis incrmente $var, alors que ++$var incrmente dabord la variable, puis renvoie sa valeur. Ainsi, return($var++) est diffrent de return(++$var).

La notion de prcdence
Les oprateurs de Perl nont pas tous le mme poids, ils sont plus ou moins prioritaires . On parle de leur prcdence. Par exemple, loprateur multiplication * a une prcdence plus forte que loprateur addition +. Il est bien sr possible de forcer lapplication dun oprateur avant un autre par lusage de parenthses : my $resultat = 3 * (2 + 4); # $resultat vaut bien 18 Il est utile de connatre la prcdence des oprateurs pour viter les parenthses superues, qui nuisent la lisibilit (voir en annexe, page 429).

Travailler sur les chanes


. length chomp split join reverse substr index rindex

Loprateur . permet de concatner deux chanes de caractres. chop retire le dernier caractre dune chane. chomp effectue la mme opration mais seulement si le dernier caractre est un retour chariot. split permet de sparer une chane par rapport un motif (une expression rgulire, voir page 122). join permet

26

CHAPITRE 3 lments du langage

lopration inverse :
my $ c h a i n e = " b c d a o f i a z z " ; say ( j o i n ( " | " , s p l i t (/ a / , $ c h a i n e ) ) ) ; # a f f i c h e bcd | ofi | zz

substr permet de remplacer une portion de chane de caractre par une autre. index et rindex permettent de chercher une portion de chane de caractres dans une autre en partant respectivement du dbut ou de la n :
my $ c h a i n e = " Un l a p i n " ; substr ( $chaine , 3, 0, " grand " ); # $ c h a i n e v a u t " Un g r a n d l a p i n " s u b s t r ( $ c h a i n e , 0 , 2 , " Ce " ) ; # $ c h a i n e v a u t " Ce g r a n d l a p i n " my $ v a r = i n d e x ( $ c h a i n e , " l a p i n " ) ; # $var vaut 9

Tous ces oprateurs forcent le contexte de chane.

Tester si un scalaire est dni


dened

Pour tester si un scalaire contient une valeur, on doit utiliser dened, qui renvoie vrai si la variable est dnie.

Dclarer une fonction


sub fonction { }

Une fonction se dclare grce au mot-cl sub, suivi dun nom et dun bloc de code :

Passer des paramtres

27

m a _ f o n c t i o n () ; sub m a _ f o n c t i o n { say " D a n s ma f o n c t i o n " } m a _ f o n c t i o n () ;

Cet extrait de code afche deux fois le texte Dans ma fonction. Appeler une fonction se fait simplement en utilisant son nom, suivi de parenthses pour passer des paramtres. Une fonction peut tre utilise avant ou aprs sa dclaration.

Passer des paramtres


fonction($param)

Lors dun appel de fonction, les paramtres sont passs entre parenthses la suite du nom de la fonction, spars par des virgules :
m a _ f o n c t i o n (12 , " une c h a n e " , -50) ;

Cest en fait une liste de scalaires qui est passe en paramtres. Pour rcuprer les arguments de la fonction, voici le code quil faut insrer au dbut de la dclaration de la fonction :
sub m a _ f o n c t i o n { my ( $ p a r a m 1 , $ p a r a m 2 , $ p a r a m 3 ) = @_ ; say " d e u x i m e p a r a m t r e : $ p a r a m 2 " ; }

@_ est une variable spciale. Cest un tableau qui contient

les paramtres reus par la fonction. La premire ligne de la fonction rcupre les lments du tableau @_, et stocke

28

CHAPITRE 3 lments du langage

les trois premiers dans les trois variables $param1, $param2, $param3. Attention
Il est important de ne pas oublier les parenthses autour de la liste des variables utilises pour stocker les paramtres, et ce mme sil ny a quun paramtre rcuprer :
my ( $ p a r a m 1 ) = @_ ;

Renvoyer une valeur de retour


return $valeur

Une fonction Perl peut bien sr renvoyer une valeur de retour, en utilisant return :
sub f o i s _ d i x { my ( $ p a r a m ) = @_ ; r e t u r n $ p a r a m * 10; } my $ r e s u l t a t = f o i s _ d i x (2) ; say $ r e s u l t a t ;

Le mot-cl return termine lexcution de la fonction en cours, et renvoie la ou les valeurs passs en paramtres. Lappelant peut rcuprer la valeur de retour en lassignant une variable, ou bien en lutilisant directement. Astuce
Une fonction peut renvoyer plus dune valeur, il suft de renvoyer une liste :
my ( $mois , $ a n n e e ) = f o n c t i o n () ; sub f o n c t i o n {

Les oprateurs de test

29

r e t u r n (12 , 1 9 5 7 ) ; }

Dans cet exemple, la variable $mois aura pour valeur 12, et $annee 1957.

Utiliser des variables dans une fonction


my $variable

Lorsquune variable locale est dclare dans une fonction, elle est invisible depuis lextrieur :
my $ a n n e e = 1 9 0 0 ; say f o n c t i o n () ; # a f f i c h e 1 8 0 0 say $ a n n e e ; # affiche 1900 sub f o n c t i o n { my $ a n n e e = 1 8 0 0 ; return $annee ; }

Dans la fonction, la porte de la variable $annee est limite, et sa valeur nest donc pas propage au-del de fonction.

Les oprateurs de test


== != eq ne

Il existe un nombre relativement grand doprateurs de test en Perl. Ces oprateurs sont diviss en deux catgories, ceux qui forcent un contexte scalaire numrique, et ceux qui forcent un contexte scalaire de chane.

30

CHAPITRE 3 lments du langage

Les oprateurs ==, !=, <, >, <=, >= permettent de tester respectivement lgalit, la diffrence, linfriorit, la supriorit, sur des valeurs numriques. Pour ce faire, ces oprateurs forcent le contexte scalaire numrique. Vrai et faux en Perl
Toute expression est soit vraie ou fausse, selon sa valeur. En Perl, comme la valeur dune expression peut dpendre du contexte en cours, voici la liste des cas o une expression est vraie ou fausse.
q q

Nombres : un nombre est vrai sil est diffrent de zro. Chanes de caractres : une chane de caractres est fausse si elle est vide, cest--dire quelle ne contient pas de caractre. Cependant, la chane de caractres 0 est galement fausse. Dans les autres cas, une chane de caractres est une expression vraie. Listes, tableaux : une liste ou un tableau sont vrais sils contiennent au moins un lment. Une liste vide () ou un tableau sans lment sont faux. Tables de hachage : cest une trs mauvaise ide de tester si une table de hachage est vrai ou faux. Il faut toujours utiliser la fonction keys qui renvoie la liste des cls, ce qui permet de se replacer dans le cas dune liste (voir page 80). undef : en contexte scalaire numrique, undef est valu comme 0, donc faux. En contexte scalaire de chane, undef est valu comme chane vide , donc faux. En contexte de liste, undef est valu comme une liste contenant un lment non dnie, donc une liste de taille 1, donc vrai.

Il existe des oprateurs quivalents en contexte de chanes, qui permettent de tester lgalit, la diffrence, linfrio-

Tester une expression

31

rit, la supriorit de deux chanes entre elles : eq, ne, lt, gt, le et ge. Ces oprateurs forcent le contexte scalaire de chane. La liste complte des oprateurs est disponible en annexe (voir page 429).

Tester une expression


if(. . .) {. . .} elsif {. . .} else {. . .}

Tester une expression se fait simplement avec loprateur if. Voici la forme traditionnelle, dite forme prxe .
if ( $ n u m b e r > 5 ) { say " le n o m b r e est p l u s g r a n d que 5 " ; }

Il est possible dajouter un bloc else, qui est excut si le test est faux :
if ( $ n u m b e r > 5 ) { say " le n o m b r e est p l u s g r a n d que 5 " ; } else { say " le n o m b r e est p l u s p e t i t ou g a l 5 " ; }

Il est galement pratique de chaner les tests, avec elsif :


if ( $ n u m b e r > 5 say " le n o m b r e } elsif ( $number say " le n o m b r e } else { say " le n o m b r e } ) { est p l u s g r a n d que 5 " ; < 5 ) { est p l u s p e t i t que 5 " ; est g a l 5 " ;

32

CHAPITRE 3 lments du langage

Quelques remarques sur cette forme de loprateur if : q Les parenthses autour de lexpression tester sont obligatoires. Les accolades sont galement obligatoires autour des blocs de code, et ce mme si le bloc ne contient quune ligne. Bien videmment, lexpression tester peut tre trs complexe, comporter des appels de fonctions, etc. Loprateur if interprte lexpression en contexte scalaire, comme lillustre lexemple suivant :
q

# une l i s t e c o n t e n a n t un l m e n t , # l entier zro my @ l i s t = (0) ; if ( @ l i s t ) { say " la l i s t e est non v i d e " ; }

Lexemple de code prcdent afche bien lcran la liste est non vide. En effet, la liste @list contient un lment, lentier 0. Cependant, en contexte scalaire, une liste renvoie le nombre dlments quelle contient. Donc @list en contexte scalaire renvoie 1, qui est vrai, et donc le test est vrai.

Effectuer une opration conditionnelle


do_something() if $expression

Il existe une autre forme de loprateur if, appele forme inxe . Lexpression tester est place la droite du motcl if, et le code excuter si le test est valid, gauche du if. Cet ordre peut paratre incongru, mais correspond en fait lordre logique linguistique.

Effectuer une opration si un test est faux

33

En effet il est courant dentendre en franais : Ouvre la fentre sil fait trop chaud . Ce qui peut scrire en Perl :
o p e n _ w i n d o w () if t o o _ h o t () ;

Les intrts de cette forme sont notamment :


q

Parenthses optionnelles. Les parenthses autour de lexpression tester ne sont pas obligatoires. Pas daccolades. Le code excuter ne doit pas tre entour daccolades ;

Lisibilit. La structure du code ressemble plus une grammaire linguistique. Cependant, cette forme est probablement moins facile apprhender par des personnes peu exprimentes. En sus, cette forme ne permet pas lutilisation de else.
q

Effectuer une opration si un test est faux


do_something unless $expression

Linverse du if inxe est loprateur unless inxe, qui peut tre interprt comme sauf :
say " $ n u m b e r est p o s i t i f " u n l e s s $ n u m b e r <= 0;

Un autre exemple utilisant loprateur modulo % :


say " le n o m b r e est p a i r " u n l e s s $ n u m b e r % 2;

Ce programme afche le nombre est pair sauf si $number % 2 renvoie vrai, ce qui est le cas si $number est impair.

34

CHAPITRE 3 lments du langage

Cet exemple illustre le fait que lutilisation de unless peut rendre la comprhension du code difcile. unless est quivalent if ! :
say " le n o m b r e est p a i r " if ! ( $ n u m b e r % 2) ;

Tester ngativement une expression


unless(. . .) {. . .}

Loprateur unless existe galement sous forme prxe . L aussi, il est quivalent if ! :
u n l e s s ( $ n u m b e r % 2) { say " $ n u m b e r est p a i r " ; d o _ s o m e t h i n g () ; }

Effectuer des tests avec and et or


and or

En Perl, il est trs important de connatre la prcdence des oprateurs principaux, et les bonnes pratiques veulent que les parenthses inutiles soient vites. Il existe un couple doprateur intressant : and et or. and et or sont identiques && et ||, mais ont une trs faible prcdence. Ils sont interprts en bout de chane

Boucler sur les lments dune liste

35

de lvaluation dune expression, et de ce fait, sont utiliss comme charnires entre les morceaux de code excuter. Ainsi, les oprateurs and et or sont utiliss la place de if et unless. Voici un exemple dutilisation de or :
d o _ s o m e t h i n g () or die " e r r e u r : $ ! " ;

Ce code peut tre dcompos en deux blocs, de part et dautre de or. Le bloc_1 : do_something() Le bloc_2 : die "erreur : $!" Linterprteur de Perl va valuer la ligne comme :
b l o c _ 1 or b l o c _ 2

Si bloc_1 renvoie vrai, alors lensemble de lexpression est vrai, donc bloc_2 ne sera pas excut. Si bloc_1 renvoie faux, alors il faut valuer bloc_2. Donc, si lappel de fonction do_something() renvoie faux, le programme sarrte et afche lerreur. De manire similaire, on peut voir ce type de code :
d o _ s o m e t h i n g () and say " s u c c e s s ! " ;

Ce programme afchera success! si do_something() renvoie vrai.

Boucler sur les lments dune liste


foreach(. . .) {. . .}

36

CHAPITRE 3 lments du langage

Il existe deux types principaux de boucles. La boucle foreach permet deffectuer des oprations pour chaque lments dune liste donne, et la boucle while sexcute tant quun test est vrai (voir section suivante).
f o r e a c h my $ e l e m e n t (1 , 2 , " h e l l o " ) { say $ e l e m e n t ; }

Cet exemple de code utilisant foreach afche successivement les lments passs la liste foreach. Il est possible domettre la variable temporaire (ici $element). Dans ce cas, llment en cours est stock dans la variable spciale $_ :
f o r e a c h (1 , 2 , " h e l l o " ) { say $_ ; }

Le mot-cl last permet de sortir prmaturment dune boucle en cours. next quant lui, oblige la boucle passer la prochaine itration immdiatement.

Boucler tant quun test est vrai


while(. . .) {. . .}

Les boucles while value la valeur de lexpression passe en paramtre ; tant quelle est vraie, le bloc de code est excut.
my $ c h a i n e = " a n i m a l " ; w h i l e ( my $ c a r = c h o p $ c h a i n e ) { say $ c a r ; }

Crer une rfrence sur scalaire

37

Cet extrait de code afche toutes les lettres de la chane animal, mais en partant de la n. En effet, chop enlve le dernier caractre de $chaine, et le renvoie. $car va donc contenir les caractres de la chane un un, en partant de la n. Tant que $chaine contient au moins un caractre, la valeur de lexpression my $car = chop $chaine est celle de $car, qui est une chane non vide, donc vrai. Quand $chaine est vide, chop renvoie une chane vide, qui est galement stocke dans $car. Dans ce cas, lvaluation de lexpression my $car = chop $chaine est la chane vide, qui est faux. La boucle while sarrte alors immdiatement.

Crer une rfrence sur scalaire


\$var

Voici comment crer une rfrence sur un scalaire :


my $ v a r i a b l e = 5; my $ r e f = \ $ v a r i a b l e ;

La variable $ref est maintenant une rfrence sur $variable. Info


En plus des nombres et des chanes de caractres, il existe un autre type de scalaire : les rfrences. Une rfrence est une variable qui pointe sur une autre variable. Cela permet notamment de passer une variable par rfrence. Il est possible de crer une rfrence sur un scalaire, sur une liste, ou sur une table de hachage. Ces deux derniers types de rf-

38

CHAPITRE 3 lments du langage

rences sont abords dans le chapitre sur les structures de donnes (voir pages 67 et 84).

Drfrencer une rfrence sur scalaire


$$var

Drfrencer une rfrence se fait en ajoutant le sigil $ devant la variable rfrence :


my $ v a r i a b l e = 5; my $ r e f = \ $ v a r i a b l e ; my $ v a r i a b l e 2 = $ $ r e f ; # $variable2 vaut 5

Accder une variable rfrence


$$ref

Le double sigil $$ devant la rfrence permet daccder la valeur de la variable rfrence directement, pour rcuprer sa valeur, ou la mettre jour.
my $ v a r i a b l e = 5; my $ r e f = \ $ v a r i a b l e ; $ $ r e f = 6; say $ v a r i a b l e ; # affiche 6

Passer un paramtre par rfrence

39

Passer un paramtre par rfrence


Lors dun appel de fonction, il nest gnralement pas utile de passer des scalaires par rfrence. En effet, il suft de les renvoyer par valeur de retour de la fonction :
my $ v a r 1 = 5; my $ v a r 2 = 2; my ( $var1 , $ v a r 2 ) = m o d i f _ v a l e u r s ( $var1 , $var2 ) ; say " r s u l t a t : $var1 , $ v a r 2 " ; sub m o d i f _ v a l e u r s { my ( $var1 , $ v a r 2 ) = @_ ; $ v a r 1 ++; $var2 - -; r e t u r n ( $var1 , $ v a r 2 ) ; }

Cet exemple afchera :


rsultat : 6, 1

Cependant, il est galement possible de passer un scalaire par rfrence :


my $ v a r 1 = 5; my $ v a r 2 = 2; m o d i f _ v a l e u r s (\ $var1 , \ $ v a r 2 ) ; say " r s u l t a t : $var1 , $ v a r 2 " ; sub m o d i f _ v a l e u r s { my ( $ r e f _ v a r 1 , $ r e f _ v a r 2 ) = @_ ; $ $ r e f _ v a r 1 ++; $ $ r e f _ v a r 2 - -; }

Cet extrait de code a exactement le mme effet, sauf quil nest pas ncessaire de renvoyer les deux variables en valeurs de retour de la fonction modif_valeurs. En effet,

40

CHAPITRE 3 lments du langage

leur rfrences sont passes en paramtres, et la valeur des variables pointes par les rfrences sont modies directement, avec $$ref_var1++.

Utiliser des rfrences sur fonctions


Il est possible de crer des rfrences sur fonctions anonymes. Ce mcanisme est trs puissant, car il permet de passer des portions de code en paramtres, puis de les excuter. Crer une rfrence sur fonction se fait ainsi :
my $ r e f _ t r a n s f o r m = sub { my ( $ v a r ) = @_ ; $ v a r ++; r e t u r n $ v a r ; };

Attention
Le caractre point-virgule ; est obligatoire la n de la dnition de sub. La rfrence sur fonction dnie est stocke dans la variable $ref_transform, cest donc une allocation de variable, qui doit se terminer par un point-virgule.

prsent, la variable $ref_transform contient une rfrence sur une fonction anonyme. Il est possible de la manipuler de diverses manires. Elle peut par exemple tre passe une autre fonction comme un simple scalaire. Pour excuter la fonction rfrence, il faut utiliser cette notation :
$ r e f _ t r a n s f o r m - >(42) ;

Loprateur che -> permet de drfrencer et dexcuter la fonction, en passant 42 en paramtre.

Rcuprer les arguments de la ligne de commande

41

Voici un exemple dutilisation :


my $ r e f _ t r a n s f o r m = sub { my ( $ v a r ) = @_ ; $ v a r ++; r e t u r n $ v a r ; }; say a p p l i q _ t r a n s f o r m (12 , $ r e f _ t r a n s f o r m ) ; sub a p p l i q _ t r a n s f o r m { my ( $var , $ r e f _ f o n c t i o n ) = @_ ; my $ r e s u l t = $ r e f _ f o n c t i o n - >( $ v a r ) ; return $result ; }

Cet exemple renvoie 13. Lextrait de code peut paratre inutile, cependant lintrt majeur est que la fonction appliq_transform ne sait rien de la fonction quelle reoit par rfrence. Il est possible de changer le comportement de appliq_transform en changeant juste $ref_transform. Ce mcanisme est un aspect de la programmation fonctionnelle. Perl permet de manipuler non seulement des donnes, mais aussi des morceaux de programmes.

Rcuprer les arguments de la ligne de commande


@ARGV

Lorsquun script Perl est lanc depuis la ligne de commande, il est possible de lui passer des paramtres :
./ s c r i p t . pl a r g u m e n t 1 a r g u m e n t 2

Pour pouvoir rcuprer ces paramtres lintrieur du script, il suft dutiliser la variable spciale @ARGV :
my ( $ p a r a m 1 , $ p a r a m 2 ) = @ A R G V ;

42

CHAPITRE 3 lments du langage

@ARGV est un tableau dont les lments sont les valeurs des

paramtres de la ligne de commande. Info


Plutt que dessayer dinterprter les paramtres de lignes de commandes complexes tels que le-name /tmp/le ag port=5432, il est plus judicieux dutiliser lun des nombreux modules dinterprtation des paramtres de ligne de commande, tel que Getopt::Long.

Excuter des commandes systme


system(), ` . . .`

La commande system permet dexcuter une commande externe. Les back-quotes ` . . .` permettent dexcuter une commande et de rcuprer sa sortie standard sous forme de chane de caractres dans une variable.
system ( " echo exemple " ); my $ c o n t e n u = ls / tmp ;

Terminer abruptement un programme


die

La fonction die permet darrter le programme instantanment, et permet dafcher un message derreur pass en paramtre de die sur la console.

Crer un module Perl

43

o p e n my $f , " / tmp / f i c h i e r " or die " e r r e u r d o u v e r t u r e " ;

Crer un module Perl


package

Les modules Perl sont des extensions du langage, qui enrichissent les fonctionnalits de Perl (voir le Chapitre 2, ddi linstallation des modules). Un module Perl est un chier avec une extension .pm. Il contient linstruction spciale package, qui permet dnir un espace de nommage (namespace) pour ce module7 .
p a c k a g e Mon :: M o d u l e sub t e s t { say " c h a n e de t e s t " ; } 1;

Lexemple prcdent est stocker dans un chier Mon/Module.pm. En effet, il y a une correspondance entre le nom du module et son nom de chier. Les doubles deuxpoints :: correspondent un rpertoire. Ainsi, Mon::Module correspond au chier Mon/Module.pm. Un module ou package Perl doit se terminer par une expression vraie, qui signie que le module sest correctement charg. Traditionnellement, cela se fait en ajoutant la ligne 1; la n du chier.
7. Par abus de langage, comme la plupart des modules ne contiennent souvent quun seul espace de noms, les termes module et package sont souvent utiliss de manire interchangeable.

44

CHAPITRE 3 lments du langage

Toute fonction dnie dans un module peut tre appele depuis un script Perl (chier en extension .pl) pour peu que le module soit charg dans le script.

Utiliser un module Perl


use Mon::Module;

Dans un script Perl, pour utiliser un module Perl, il suft dutiliser use, qui va charger le module dans lenvironnement du script.
use Mon :: M o d u l e ; Mon :: M o d u l e :: t e s t () ; # a f f i c h e " c h a i n e de t e s t "

Concrtement, use transforme le nom du module en nom de chier. Ici Mon::Module est transform en Mon/Module.pm. Ce chier est recherch dans une liste de rpertoires8 . Si le chier est trouv, il est charg, sinon, une erreur survient. Il est possible dajouter un rpertoire la liste des rpertoire de recherche de modules grce use lib. Voici un exemple de code qui illustre cette fonctionalit. Ici, le module Mon::Module est stock dans le chier /home/user/perl/Mon/Module.pm. Linstruction use lib /home/user/perl lajoute la liste des rpertoires de recherche.

8. Cette liste de rpertoire est accessible, elle est stoke dans la variable spciale @INC : perl -E say for @INC renvoie la liste des rpertoires do peuvent tre chargs les modules.

Utiliser un module Perl

45

use lib " / h o m e / u s e r / p e r l " ; use Mon :: M o d u l e Mon :: M o d u l e :: t e s t () ; # a f f i c h e " c h a i n e de t e s t "

Les fonctions du module peuvent tre appeles en accolant leur noms au nom du module. Les modules Perl offrent normment de fonctionnalits, mais il est plus pertinent dutiliser la programmation oriente objet, qui permet dutiliser classes, mthodes et instances, au lieu de modules et fonctions (voir la partie II consacre la programmation oriente objet).

4
Structures de donnes
Nous allons aborder dans ce chapitre les structures de donnes dites simples : listes, tableaux et tableaux associatifs (tables de hachage). Ces types de donnes compltent les scalaires dans lutilisation basique de Perl, et sont indispensables la programmation plus avance, ainsi qu lutilisation des modules externes CPAN. Nous prsenterons galement une slection de modules permettant dtendre les oprateurs de base du langage, List::Util et List::MoreUtils).

Crer une liste


(1, 2, 3)

Une liste est, pour simplier, un ensemble ni de scalaires. Voici quelques exemples de listes :
(1 , 2 , 3) ; ( -25 , 42 , 3 . 1 4 1 5 9 2 ) ; ( une h i r o n d e l l e , 3 , un l a p i n , -.5) ;

48

CHAPITRE 4 Structures de donnes

Une liste peut contenir des entiers, ottants, chanes de caractres, et mme dautres types telles par exemple les rfrences, voir pages 67 et 84 , du moment que ce sont des scalaires. Les lments dune liste sont ordonns : (1, 2, 3) est diffrent de (1, 3, 2), bien que ces listes contiennent les mmes lments.

Crer une liste avec un intervalle


..

Tant quil y a peu dlments, une liste est facile crer :


(1 , 2 , 3 , 4 , 5) ; ( a , b , c , d );

Cependant, il existe loprateur intervalle, constitu de deux points successifs .., qui permet de crer facilement une liste : en donnant ses bornes, loprateur cre la liste correspondante, en interpolant les lments manquants. Pour les nombres entiers, cela donne :
(1..5) ; # r e n v o i t (1 , 2 , 3 , 4 , 5) ;

Loprateur intervalle ne fonctionne que dans lordre croissant, donc lexemple suivant renvoie une liste vide :
(5..1) ; # r e n v o i t une l i s t e v i d e

Crer une liste de mots

49

Comment crer une liste de nombres dcroissants ? Nous verrons comment faire cela lorsque sera aborde la fonction reverse (voir page 60). Loprateur intervalle fonctionne galement sur les chanes de caractres :
( a .. z ) ; # t o u t e s les l e t t r e s de l a l p h a b e t ( aa .. bb ) ; # est q u i v a l e n t ( aa , ab , ac , ag , ah , ai , am , an , ao , as , at , au , ay , az , ba ,

: ad , ae , aj , ak , ap , aq , av , aw , bb ) ;

af , al , ar , ax ,

Crer une liste de mots


qw()

Il est souvent trs utile de crer des listes de mots, comme par exemple :
( avancer , reculer , tourner , d m a r r e r ) ;

Les listes de mots sont tellement courantes quil existe un oprateur ddi, qui facilite leur criture : loprateur qw. Son nom vient de labrviation de Quoted Word , et il permet dviter dcrire les guillemets autour des mots, et les virgules entre eux :
qw ( a v a n c e r r e c u l e r t o u r n e r d m a r r e r ) ;

50

CHAPITRE 4 Structures de donnes

On pourra donc crire :


f o r e a c h my $ a c t i o n ( qw ( a v a n c e r r e c u l e r t o u r n e r d m a r r e r ) ) { # ... }

Astuce
Loprateur qw sutilise gnralement avec des parenthses, mais il est possible dutiliser dautres dlimiteurs. Les lignes suivantes sont quivalentes :
qw ( a v a n c e r qw [ a v a n c e r qw / a v a n c e r qw ! a v a n c e r reculer reculer reculer reculer tourner tourner tourner tourner dmarrer ) dmarrer ] dmarrer / dmarrer !

Lintrt est vident lorsquil faut crer des listes de caractres non alphanumriques :
qw [ . ( ) ] # r e n v o i e ( . , ( , ) ) qw /[ ] ( ) / # r e n v o i e ( [ , ] , ( , ) )

Crer un tableau une dimension


@array = . . .

Pour faire simple, un tableau est une variable qui contient une liste. Le sigil dun tableau est @ :
my @ a r r a y ;

Accder aux lments dun tableau

51

Pour initialiser un tableau, une liste peut lui tre assigne :


my @ a r r a y = ( f r a i s e , 12 , f r a m b o i s e ) ; my @ a r r a y 2 = ( 1 . . 1 0 ) ; my @ a r r a y 3 = qw ( p o m m e f r a i s e framboise poire );

Si un tableau nest pas initialis, il contient par dfaut la liste vide ().
my @ a r r a y ; # quivalent : my @ a r r a y = () ;

Accder aux lments dun tableau


$array[n]

Accder un lment dun tableau se fait en utilisant lindice de llment, entre crochets :
my @ a r r a y = (121 , 122 , 1 2 3 ) ; my $ e l e m e n t = $ a r r a y [ 0 ] ; # $element contient 121; $element = $array [1]; # $element contient 122; $ e l e m e n t = $ a r r a y [ 1 + 1 ]; # $element contient 123;

Attention
Les indices des lments commencent 0. $array[0] est le premier lment, $array[1] est le deuxime lment, etc.

52

CHAPITRE 4 Structures de donnes

la lecture de cet exemple, il est possible de formuler plusieurs remarques :


q

Pour accder un lment dun tableau, cest le sigil $1 qui est utilis. Ainsi il faut crire $array[1] pour accder au deuxime lment, et non pas @array[1]. Un indice nest pas obligatoirement un nombre, il peut tre une expression, qui sera value en contexte scalaire en tant quentier, et le rsultat sera utilis comme indice.

En application directe, voici comment afcher un lment dun tableau :


say " le 3 e l m e n t est : $ a r r a y [2] " ; # quivalent say le 3 e l m e n t est : . $ a r r a y [ 2 ] ;

Affecter un lment dun tableau


$array[n] = . . .

Pour changer un lment, il suft dassigner une valeur llment dun tableau. Il est bien sr possible de faire des calculs directement avec des lments de tableaux :
my @ a r r a y = (10 , 20 , 30) ; $ a r r a y [1] = 15; # le t a b l e a u v a u t p r s e n t (10 , 15 , 30) ; $ a r r a y [2] = $ a r r a y [0] * 2 . 5 ; # le t a b l e a u v a u t p r s e n t (10 , 15 , 25) ;

1. Un bon moyen mnmotechnique est de considrer ce qui est obtenu lorsque lexpression est value. Ici, cest llment du tableau qui est dsir, donc un scalaire. Par consquent, lexpression doit commencer par le sigil $.

Obtenir le dernier lment dun tableau

53

$ a r r a y [0] -= 5; # le t a b l e a u v a u t m a i n t e n a n t (5 , 15 , 25) ; $ a r r a y [ $ a r r a y [0] ] = 12; # le t a b l e a u v a u t m a i n t e n a n t # (5 , 15 , 25 , undef , undef , 12) ;

Obtenir le premier lment dun tableau


$array[0]

Comme la numrotation des indices commence zro, array[0] correspond au premier lment du tableau.

Obtenir le dernier lment dun tableau


$array[-1]

Perl permet dutiliser des entiers ngatifs comme indices, qui permettent de parcourir les lments dun tableau en partant de la n. Ainsi lindice -1 correspond au dernier lment dun tableau, et ainsi de suite :
my @ a r r a y = (10 , 20 , 30 , 40) ; say $ a r r a y [ -1]; # A f f i c h e 40 say $ a r r a y [ -2]; # A f f i c h e 30 $ a r r a y [ -2] -= $ a r r a y [ -3]; # le t a b l e a u v a u t (10 , 20 , 10 , 40) ;

54

CHAPITRE 4 Structures de donnes

Obtenir la taille dun tableau


$size = @array

La taille dun tableau est le nombre dlments quil contient. En Perl, il ny a pas de fonction particulire pour obtenir la taille dun tableau, il suft de lvaluer en contexte scalaire. Sous cette description en apparence complique, se cache une opration trs simple. Soit le code suivant :
my $ s i z e = @ a r r a y ;

Attention
Une erreur classique est dessayer dutiliser la fonction length pour calculer la taille dun tableau : cela ne fonctionne pas. En effet, length calcule la longueur dune chane de caractres uniquement. length attend donc un scalaire en paramtre. Si on donne un tableau en argument, length va renvoyer la longueur de la reprsentation textuelle du dernier lment du tableau.

gauche, un scalaire, $size ; droite, un tableau. Le fait que lexpression de gauche soit un scalaire oblige lexpression de droite tre value en contexte scalaire. Le tableau de droite, en contexte scalaire, renvoie le nombre dlments quil contient. Cest en effet le seul rsultat utile qui peut tre stock dans un unique scalaire. Astuce
On peut forcer le contexte scalaire grce la fonction scalar :
my $ s i z e = s c a l a r ( @ a r r a y ) ;

En cas de doute sur le contexte dans lequel est valu le tableau ou la liste, il faut utiliser scalar() pour en obtenir la taille.

Tester les lments dun tableau

55

Assigner un lment en dehors du tableau


Assigner un lment en dehors des limites dun tableau est parfaitement valide. Le tableau va tre tendu dautant dlments que ncessaire pour avoir la bonne taille. Les lments crs en plus seront indnis.
my @ a r r a y = (10 , p o i r e , 30 , 40) ; $ a r r a y [4] = 50; # @array vaut maintenant # (10 , p o i r e , 30 , 40 , 50) $ a r r a y [7] = 80; # @array vaut maintenant # (10 , p o i r e , 30 , 40 , 50 , # undef , undef , 80 )

Tester les lments dun tableau


exists(..) dened(..)

Chaque lment dun tableau tant un scalaire, tous les tests sur les scalaires sappliquent aux lments dun tableau :
my @ a r r a y = (10 , p o i r e , 30 , 40) ; if ( $ a r r a y [1] eq a b r i c o t ) { # ... } if ( $ a r r a y [0] > 5) { # ... }

56

CHAPITRE 4 Structures de donnes

Mais il est aussi possible de tester la prsence et la dnition dun lment. La fonction exists renvoie vrai si llment existe ; dened teste si un lment est dni. Lexemple suivant explicite la nuance entre les deux concepts :
my @ a r r a y = (10 , 20 , 30) ; $ a r r a y [4] = 50; # @array vaut maintenant # (10 , 20 , 30 , undef , 50) # ce t e s t est v r a i if ( e x i s t s $ a r r a y [ 4 ] ) { # ... } # ce t e s t est f a u x if ( d e f i n e d $ a r r a y [ 4 ] ) { # ... }

Le test utilisant exists renvoie vrai car il y a un lment lindice 4. Cependant, cet lment nest autre que undef, et donc le second test choue.

Manipuler la n dun tableau


push(..) pop()

Il est courant de vouloir ajouter des lments un tableau : par exemple, calculer des rsultats, et les ajouter un tableau, puis renvoyer le tableau, comme liste de rsultats. Bien sr, il est possible dajouter un lment la n dun tableau en dduisant le dernier indice de sa taille :
my @ a r r a y = (10 , f r a i s e , 30) ; my $ s i z e = @ a r r a y ; # r e n v o i e 3

Manipuler le dbut dun tableau

57

$ a r r a y [ $ s i z e ] = 40; # @ a r r a y v a u t (10 , f r a i s e , 30 , 40)

Mais cest un peu rbarbatif. Il est beaucoup plus rapide dutiliser push, qui permet dajouter un lment un tableau :
my @ a r r a y = (10 , f r a i s e , 30) ; p u s h @array , 40; # @ a r r a y v a u t (10 , f r a i s e , 30 , 40)

Astuce
En fait, push permet dajouter plus dun lment un tableau. Ainsi, push est capable dajouter une liste la n dun tableau.
my @ a r r a y = (10 , f r a i s e , 30) ; p u s h @array , 40 , 50; # @ a r r a y v a u t (10 , f r a i s e , 30 , 40 , 50)

Linverse de push est pop, qui permet denlever un lment de la n dun tableau. pop ne permet pas denlever plus dun lment la fois. Llment qui est retir du tableau est renvoy. Donc, la valeur de retour de pop correspond au dernier lment du tableau :
my @ a r r a y = (10 , f r a i s e , 30) ; my $ l a s t _ e l e m e n t = pop @ a r r a y ; # @ a r r a y v a u t m a i n t e n a n t (10 , f r a i s e ) # et $ l a s t _ e l e m e n t v a u t 30

Manipuler le dbut dun tableau


shift(..) unshift(..)

58

CHAPITRE 4 Structures de donnes

push et pop sont utiliss pour manipuler la n dun ta-

bleau, il existe lquivalent pour le dbut : shift retire le premier lment dun tableau et le renvoie, et unshift permet dajouter une liste dlments au dbut du tableau.
my @ a r r a y = (10 , f r a i s e , 30) ; my $ f i r s t _ e l e m e n t = s h i f t @ a r r a y ; # @ a r r a y v a u t m a i n t e n a n t ( f r a i s e , 30) # et $ f i r s t _ e l e m e n t v a u t 10 u n s h i f t @array , $ f i r s t _ e l e m e n t , 42; # @ a r r a y v a u t (10 , 42 , f r a i s e , 30)

Info
Avec unshift, les lments sont ajouts tous ensemble et non un par un, leur ordre originel est donc respect.

Manipuler le milieu dun tableau


splice(..)

Voici comment manipuler une zone arbitraire contige dun tableau. La fonction splice permet de slectionner des lments dun tableau, pour les supprimer ou les remplacer par dautres. splice prend en argument le tableau, un indice partir duquel commencer la slection, un entier reprsentant la taille de la slection, et une liste dlments qui remplaceront la slection le cas chant. Lexemple suivant remplace les deuxime et troisime lments par un seul lment :

Manipuler le milieu dun tableau

59

my @ a r r a y = (10 , f r a i s e , 30 , 40) ; s p l i c e @array , 1 , 2 , p o m m e ; # @ a r r a y v a u t m a i n t e n a n t (10 , p o m m e , 40)

Il est galement possible que la liste de remplacement soit plus grande que la slection :
my @ a r r a y = (10 , f r a i s e , 30 , 40) ; s p l i c e @array , 1 , 2 , p o m m e , p o i r e , a b r i c o t ; # @array vaut maintenant # (10 , p o m m e , p o i r e , a b r i c o t , 40)

Si la liste de remplacement est omise, splice va ter la slection du tableau uniquement.


my @ a r r a y = (10 , f r a i s e , 30 , 40) ; s p l i c e @array , 1 , 2; # @ a r r a y v a u t m a i n t e n a n t (10 , 40)

Si la longueur de la slection est omise, splice va tronquer le tableau partir de lindice de dpart.
my @ a r r a y = (10 , f r a i s e , 30 , 40) ; s p l i c e @array , 2; # @ a r r a y v a u t m a i n t e n a n t (10 , f r a i s e )

Astuce
Lindice de dpart peut tre ngatif, pour slectionner depuis la n du tableau. La longueur de slection peut galement tre ngative : splice( @array, 3, -2) enlvera tous les lments partir du 4e, sauf les deux derniers du tableau.

60

CHAPITRE 4 Structures de donnes

Supprimer un lment dun tableau


splice(..)

Il ne faut pas confondre rinitialiser un lment et supprimer un lment . Rinitialiser un lment, cest le mettre undef, ce qui seffectue trs simplement comme suit :
my @ a r r a y = (10 , f r a i s e , 30) ; $ a r r a y [1] = u n d e f ; # @ a r r a y v a u t m a i n t e n a n t (10 , undef , 30)

Pour supprimer un lment dun tableau, il faut enlever llment en question du tableau. Pour cela il est possible dutiliser splice :
my @ a r r a y = (10 , f r a i s e , 30) ; s p l i c e @array , 1 , 1; # @ a r r a y v a u t m a i n t e n a n t (10 , 30) ;

Inverser une liste ou un tableau


reverse(..)

Les listes et tableaux sont ordonns. Il est possible de les inverser, cest--dire de les retourner, de manire ce que les lments soient dans lordre oppos, en utilisant reverse :
my @ a r r a y = (10 , f r a i s e , 30 , 40) ; @array = reverse @array ; # @ a r r a y v a u t (40 , 30 , f r a i s e , 10) ;

Aplatir listes et tableaux

61

Il est possible de faire la mme chose sur les listes directement :


my @ a r r a y = r e v e r s e (10 , f r a i s e , 30 , 40) ; # @ a r r a y v a u t (40 , 30 , f r a i s e , 10) ;

Astuce
Prcdemment, il a t dit quil tait impossible de dnir un intervalle dcroissant en utilisant ... Cependant, grce reverse, cest possible et facile :
( a .. z ) ; # r e n v o i e t o u t e s les l e t t r e s # dans l ordre alphabtique r e v e r s e ( a .. z ) # r e n v o i e t o u t e s les l e t t r e s de l a l p h a b e t # de z v e r s a reverse (1..3) # est q u i v a l e n t (3 , 2 , 1)

Aplatir listes et tableaux


Laplatissement des listes est un lment important du langage Perl et il est primordial de bien comprendre ce mcanisme. Examinons les listes suivantes :
( ( ( ( 1, 2, a , b ) 1, 2, ( a , b ) ) 1 , (2 , a ) , b ) ( 1, 2, a ) , b )

62

CHAPITRE 4 Structures de donnes

Contre toute attente, ces listes sont quivalentes entre elles, et sont aplaties . Ce principe est valable galement pour les tableaux :
my @ a r r a y 1 = (10 , f r a i s e , 30 , 40) ; my @ a r r a y 2 = ( a , b , c ) ; my @ a r r a y 3 = ( @ a r r a y 1 , @ a r r a y 2 ) ; # @ a r r a y 3 v a u t (10 , f r a i s e , 30 , 40 , a , b , c ) ; @ a r r a y 3 = ( @ a r r a y 3 , 1 , 2) ; # @array3 vaut maintenant # (10 , f r a i s e , 30 , 40 , a , b , c , 1 , 2) ;

Il est donc impossible de crer des tableaux de tableaux en essayant de regrouper une liste de tableaux ensemble. Pour crer des tableaux n dimensions voir page 67.

Manipuler une tranche de tableau


@array[a..b]

En plus de splice, Perl permet de travailler avec des tranches de tableau (slice en anglais). Une tranche de tableau est une slection dindices dun tableau. Une tranche permet de rcuprer ou dassigner une partie dun tableau. Il est intressant de noter quune tranche nest pas ncessairement contigu. Les exemples suivants permettent de se familiariser avec ces slices :
# r c u p r e r une p a r t i e c o n t i g u d un t a b l e a u my @ a r r a y = (10 , f r a i s e , 30 , 40 , p o m m e ) ; my @ t r a n c h e = @ a r r a y [ 1 , 2 ]; # @ t r a n c h e v a u t ( f r a i s e , 30)

Manipuler une tranche de tableau

63

@ t r a n c h e = @ a r r a y [ 1 . . 3 ]; # @ t r a n c h e v a u t ( f r a i s e , 30 , 40 ) ;

Attention
Lors dune utilisation dune tranche de tableau, le sigil du tableau reste @.

Il est possible dutiliser une tranche contige pour remplacer des lments :
# r e m p l a c e r des l m e n t s c o n t i g u s my @ a r r a y = (10 , f r a i s e , 30 , 40 , p o m m e ) ; @array [ 1, 2 ] = ( poire , citron ) ; # @array vaut maintenant # (10 , p o i r e , c i t r o n , 40 , p o m m e )

Attention
Contrairement splice, il nest pas possible de changer la taille dune tranche. Les lments en trop seront ignors, et ceux manquants seront remplacs par undef.
my @ a r r a y = (10 , f r a i s e , 30 , 40 , p o m m e ) ; @ a r r a y [1 , 2] = ( p o i r e ) ; # @array vaut maintenant # (10 , p o i r e , undef , 40 , p o m m e ) @ a r r a y [1 , 2] = ( p o i r e , c i t r o n , p c h e ) ; # @array vaut maintenant # (10 , p o i r e , c i t r o n , 40 , p o m m e )

Cette proprit peut tre utilise pour rinitialiser (mettre undef) une partie de tableau :
my @ a r r a y = (10 , f r a i s e , 30 , 40 , p o m m e ) ; @ a r r a y [ 1 , 2 ] = () ; # @array vaut maintenant # (10 , undef , undef , 40 , p o m m e )

64

CHAPITRE 4 Structures de donnes

En plus des listes, les tableaux peuvent tre utiliss pour spcier une tranche :
my @ i n d i c e s = (2 , 3) ; my @ a r r a y = (10 , f r a i s e , 30 , 40 , p o m m e ) ; @array [ @indices ] = ( abricot , citron ) ; # @array vaut maintenant : # (10 , f r a i s e , a b r i c o t , c i t r o n , p o m m e )

L o les tranches deviennent trs puissantes, cest quelles peuvent tre discontinues. Voici un exemple qui travaille avec les nombres pairs et impairs :
my @ p a i r s = (0 , 2 , 4 , 6) ; my @ i m p a i r s = (1 , 3 , 5 , 7) ; my @ a r r a y = ( 0 . . 7 ) ; @array [ @pairs ] = ( reverse (4..1) ); @array [ @impairs ] = ( reverse (8..5) ); # @array vaut maintenant : # (4 , 8 , 3 , 7 , 2 , 6 , 1 , 5)

Loprateur virgule
Contrairement ce quil parat, et contrairement dautres langages, ce ne sont pas les parenthses qui caractrisent une liste, mais cest la virgule. 1, 2 est une liste valide. La virgule , est loprateur qui cre la liste2 . Cependant, une liste est pratiquement toujours crite avec des parenthses autour des lments, car loprateur virgule a une trs faible prcdence (voir le tableau de prcdence des oprateurs, page 429).

2. En fait, loprateur virgule change de comportement suivant le contexte. En contexte de liste, la virgule cre une liste. En contexte scalaire, le dernier lment est renvoy.

Boucler sur les lments dun tableau

65

a = 12, 13; # est quivalent (a = 12), 13; Le tableau @a contiendra un unique lment (12). Pour connatre lordre de prcdence, voir la liste des oprateurs page 429. On y voit que loprateur virgule est presque tout en bas de la liste, alors que loprateur = est au-dessus. En conclusion : toujours utiliser des parenthses autour des listes, mais savoir que cest la virgule qui cre la liste.

Boucler sur les lments dun tableau


foreach (. . .) {. . .}

foreach permet deffectuer des oprations en boucle. Il

existe plusieurs syntaxes possibles, mais la syntaxe moderne est la suivante :


f o r e a c h my $ v a r i a b l e ( e x p r e s s i o n ) { # c o r p s de la b o u c l e u t i l i s a n t $ v a r i a b l e }

Lexpression est interprte en contexte de liste, et pour chaque lment de lexpression, le corps de la boucle est interprt, $variable valant llment en question3 . Lexemple
3. En fait, $variable. na pas juste la valeur de llment en cours, cest un alias dessus. Changer la valeur de $variable change llment, du moins si llment peut tre modi. Ainsi foreach my $v (1, 2) { $v++ } renverra une erreur, mais foreach my $v (@j = (1,2)) { $v++ } say @j afchera 2 3. Cependant, utiliser ce mcanisme nest pas recommand, car laffectation, trop peu visible, est source de nombreux bogues.

66

CHAPITRE 4 Structures de donnes

naf suivant afche une liste au format HTML4 :


say < ul > ; # f o r e a c h a v e c une l i s t e f o r e a c h my $ f r u i t ( p o m m e , f r a i s e , f r a m b o i s e ) { say " < li > $fruit </ li > " ; } say </ ul > ;

Le code prcdent utilise une simple liste de chanes de caractres, qui sont afchs, encadrs par <li> et </li>. foreach fonctionne aussi sur les tableaux :
# tout l alphabet my @ a l p h a b e t = ( a .. z ) ; my $ c o u n t = 0; f o r e a c h my $ l e t t r e ( @ a l p h a b e t ) { say la . ++ $ c o u n t . e l e t t r e de l \ a l p h a b e t est : . $lettre ; }

Bien sr, il est possible dvaluer nimporte quelle expression, et le rsultat sera utilis pour boucler :
my @ e n t i e r s = ( 0 . . 1 0 0 ) ; my @ a l p h a b e t = ( a .. z ) ; f o r e a c h my $e ( @ e n t i e r s [ 7 . . 1 1 ] , @alphabet [7..11]) { p r i n t " $e - " ; } # a f f i c h e 7 -8 -9 -10 -11 - h - i - j - k - l -

4. Ce nest pas une mthode recommande pour gnrer du HTML de manire propre. Pour cela, utilisez par exemple le module Template::Toolkit.

Rfrencer un tableau

67

Dans le code prcdent, il faut se souvenir du concept daplatissement de liste, qui fait que @entiers[7..11], @alphabet[7..11] est vu comme une seule liste des entiers de 7 11, et des lettres de h l (voir page 61).

Crer un tableau plusieurs dimensions


Les lments dune liste ou dun tableau sont des scalaires. Il nest donc pas possible de stocker un tableau (car ce nest pas un scalaire) en tant qulment : le concept daplatissement sapplique (voir page 61). Cependant, il y a un moyen trs simple (et de nombreux outils syntaxiques) pour construire des tableaux n dimensions, et dautres structures plus compliques, en utilisant des rfrences.

Rfrencer un tableau
[1, 2, 3] \@array

Une rfrence est un scalaire spcial, qui pointe sur autre chose, que ce soit un autre scalaire, un tableau, une table de hachage. Pour obtenir une rfrence, il est possible dutiliser \ sur un tableau, qui renvoie une rfrence pointant dessus. [ ] peut galement tre utilis, cela permet de crer une rfrence sur une liste directement, ou sur un tableau dj existant. Ces mthodes basiques sont rsumes dans le code suivant :

68

CHAPITRE 4 Structures de donnes

my @ a r r a y = (1 , 2 , 3) ; my $ r e f _ a r r a y = \ @ a r r a y ; # $ r e f _ a r r a y est une r f r e n c e sur @ a r r a y my $ r e f _ a r r a y = [1 , 2 , 3]; # $ r e f _ a r r a y est une r f r e n c e sur (1 , 2 , 3)

Dans lexemple prcdent, [1, 2, 3] est appel une rfrence vers un tableau anonyme.

Drfrencer un tableau
@{$ref_array}

Pour accder un tableau rfrenc, il faut utiliser @{} :


my $ r e f _ a r r a y = [1 , 2 , 3]; my @ a r r a y = @ { $ r e f _ a r r a y };

Loprateur @{} value lexpression donne, et drfrence le rsultat en tableau. Lorsquil ny a pas dambigut, les accolades peuvent tre omises :
my $ r e f _ a r r a y = [1 , 2 , 3]; my @ a r r a y = @ $ r e f _ a r r a y ;

Il est possible daccder directement un lment dun tableau en partant de sa rfrence, grce loprateur che -> :
my $ r e f _ a r r a y = [1 , 2 , 3]; my $ v a l u e = $ r e f _ a r r a y - >[1]; # $value vaut 2

Crer des rfrences dans un tableau

69

Crer des rfrences dans un tableau


Les rfrences sont trs utiles pour crer des tableaux deux dimensions ou plus. Au lieu dessayer de stocker des tableaux dans des tableaux, il suft de stocker des rfrences dans un tableau simple. Pour illustrer lexplication, voici un exemple, dans lequel il sagit de crer un tableau deux dimensions. Soit 3 tudiants, pour lesquels il faut stocker leur moyenne gnrale pour les 3 premiers mois de lanne. La premire dimension du tableau sera donc les 3 premiers mois de lanne, de 0 2. Dans la seconde dimension, donc pour chaque mois, seront stockes les moyennes gnrales des lves. Voici les donnes disponibles : le premier lve a eu 10 en janvier, 13 en fvrier et 12.5 en mars ; le second lve a obtenu 12 le premier mois de lanne, puis 10, et enn 7 en mars ; la moyenne du dernier lve tait de 15 en janvier, 17 en fvrier et 18 en mars. Voici un moyen de stocker ces valeurs dans un tableau deux dimensions :
# c r a t i o n des my $ r e f _ m o n t h _ 1 my $ r e f _ m o n t h _ 2 my $ r e f _ m o n t h _ 3 r f r e n c e s sur t a b l e a u x = [10 , 12 , 1 5 ] ; = [13 , 10 , 1 7 ] ; = [12.5 , 7 , 1 8 ] ;

# e n s u i t e , s t o c k a g e d a n s un t a b l e a u g n r a l my @ s t u d e n t s _ a v e r a g e s = ( $ r e f _ m o n t h _ 1 , $ref_month_2 , $ref_month_3 );

Cette manire de faire a lavantage dtre simple et explicite, mais elle nest pas trs puissante. Nous aurions pu crire directement le tableau nal comme suit :

70

CHAPITRE 4 Structures de donnes

# d f i n i t i o n et i n i t i a l i s a t i o n # du t a b l e a u f i n a l d i r e c t e m e n t my @ s t u d e n t s _ a v e r a g e s = ( [10 , 12 , 15] , [13 , 10 , 17] , [12.5 , 7 , 18] , );

Accder un tableau de tableaux


Pour cette section, le tableau @students_averages prcdemment cr va tre rutilis. Un tableau n dimensions est un tableau normal, dans lequel des rfrences sont stockes. Logiquement, il est possible daccder un de ses lments en utilisant son indice. La valeur ainsi rcupre sera une rfrence, quil faudra drfrencer pour pouvoir lexploiter. Par exemple, pour rcuprer la moyenne du deuxime lve au mois de mars :
my $ r e f _ m a r c h = $ s t u d e n t s _ a v e r a g e s [ 2 ] ; my @ m a r c h = @ $ r e f _ m a r c h ; my $ a v e r a g e _ i n _ m a r c h = @ m a r c h [ 1 ] ;

Cependant, Perl permet dutiliser le raccourci ->. Cet oprateur permet de drfrencer, puis daccder un lment directement :
my $ a v e r a g e _ i n _ m a r c h = $ s t u d e n t s _ a v e r a g e s [2] - >[1];

Une autre syntaxe est possible, en faisant disparatre purement et simplement la che :
my $ a v e r a g e _ i n _ m a r c h = $students_averages [2][1];

Dumper un tableau

71

Info
Certains dveloppeurs prfrent garder loprateur -> pour indiquer plus clairement quil sagit dun drfrencement, dautres prfrent la notation plus courte qui est similaire dautres langages.

Modier un tableau de tableaux


Comme dans le cas dun tableau plat, il suft daffecter une valeur pour modier le tableau :
# le 3 e l v e a eu 11 en j a n v i e r , et non 15 $ s t u d e n t s _ a v e r a g e s [0] - >[2] = 11; # a j o u t du m o i s d a v r i l $ s t u d e n t s _ a v e r a g e s [3] = [14 , 5 , 1 6 ] ; # idem , m a i s c e t t e f o i s en d r f r e n a n t # gauche @ { $ s t u d e n t s _ a v e r a g e s [ 3 ] } = (14 , 5 , 16) ;

Dumper un tableau
Dumper est un anglicisme, qui vient du verbe dump, utilis en anglais pour dnir une action de vidage mmoire vers un priphrique de sortie, gnralement des ns danalyse, effectue suite une exception ou erreur 5 . En pratique, dumper une structure permet de rcuprer une chane de caractres qui la dcrit, pour pouvoir lafcher. Le moyen le plus simple est dutiliser un module standard fourni avec Perl, Data::Dumper. Il prend en argument la

5. http://fr.wikipedia.org/wiki/Dump.

72

CHAPITRE 4 Structures de donnes

structure afcher, et renvoie une chane de caractres. Il est prfrable de lui donner la rfrence sur la structure.
# C h a r g e le m o d u l e use D a t a :: D u m p e r ; # o f f r e un a f f i c h a g e p l u s c o n c i s $ D a t a :: D u m p e r :: I n d e n t = 0; my @ a r r a y = (1 , 2 , 3) ; say D u m p e r (\ @ a r r a y ) ; # affiche l cran : # $ V A R 1 = [1 ,2 ,3];

Info
$Data::Dumper::Indent = 0 met la variable $Indent du module Data::Dumper 0. Cette variable gre le niveau dindentation de sortie de la mthode Dumper. Lorsque cette variable est
initialise zro, le texte produit est trs concis.

Dans cet exemple, lintrt est minime : il nest pas trs intressant de vrier que le tableau contient bien 1, 2, 3 car cest comme cela quil a t initialis. Mais Data::Dumper est dune efcacit redoutable pour vrier le contenu dune structure construite dynamiquement.
use D a t a :: D u m p e r ; $ D a t a :: D u m p e r :: I n d e n t = 0; # c o n s t r u c t i o n de la t a b l e XOR # des 3 p r e m i e r s e n t i e r s . my @ t a b l e _ d e _ x o r ; f o r e a c h my $i ( 0 . . 2 ) { f o r e a c h my $j ( 0 . . 2 ) { $ t a b l e _ d e _ x o r [ $i ][ $j ] = $i xor $j ; } }

Dumper un tableau

73

say D u m p e r (\ @ a r r a y ) ; # affiche l cran : # $ V A R 1 = [[0 ,0 ,0] ,[1 ,1 ,1] ,[2 ,2 ,2]];

Le rsultat afch nest pas bon, ce nest pas le rsultat de lopration XOR sur les entiers entre eux. Lerreur vient du fait que lexemple de code utilise loprateur xor alors que cest loprateur ^ qui effectue un XOR sur les entiers bits bits. Le code correct est le suivant :
use D a t a :: D u m p e r ; # affichage plus concis $ D a t a :: D u m p e r :: I n d e n t = 0; # c o n s t r u c t i o n de la t a b l e XOR # des 3 p r e m i e r s e n t i e r s my @ t a b l e _ d e _ x o r ; f o r e a c h my $i ( 0 . . 2 ) { f o r e a c h my $j ( 0 . . 2 ) { $ t a b l e _ d e _ x o r [ $i ][ $j ] = $i ^ $j ; } } say D u m p e r (\ @ t a b l e _ d e _ x o r ) ; # affiche l cran : # $ V A R 1 = [[0 ,1 ,2] ,[1 ,0 ,3] ,[2 ,3 ,0]];

Cet exemple montre lutilit principale de Data::Dumper : vrier quune structure de donnes est bien conforme au rsultat escompt. Cet outil est trs important lors du dboggage des programmes.

74

CHAPITRE 4 Structures de donnes

Utiliser les tables de hachage


Une table de hachage est une structure qui permet dassocier un scalaire (gnralement une chane de caractres), qui sappelle alors cl, un autre scalaire (gnralement une autre chane, un nombre, ou une rfrence sur dautres structures) : cest la valeur. Il est trs courant en franais dutiliser simplement le mot hash , comme contraction de table de hachage . Les tables de hachage sont plus connues dans dautres langages sous le nom de tableaux associatifs ; quen Perl, ils sont souvent plus souples. Le sigil dune table de hachage est % :
my % h a s h ;

Loprateur che double


Avant de dcrire les fonctionnalits des tables de hachage, il est important de sattarder sur loprateur che double . Loprateur => est en fait un remplaant de loprateur virgule, mais il permet dconomiser lutilisation des guillemets, et rend le code source plus lisible. Voici deux extraits de codes, qui sont quivalents, utiliss dans ce cas pour construire un tableau. # sans lutilisation de => my @array = (pomme,poire,fraise,framboise); # avec lutilisation de => my array = (pomme => poire, fraise => framboise); Loprateur => est quivalent loprateur virgule, sauf quil force largument gauche tre une chane. On dit quil value le paramtre de gauche en contexte de chane .

Crer une table de hachage

75

Crer une table de hachage


%hash = (. . .)

La manire la plus courante de crer une table de hachage est de le faire partir dune liste :
my % h a s h = ( m i c h e l , 25 , j e a n , 42 , c h r i s t i n e , 37 , );

Cette table de hachage associe un prnom un ge. Michel a 25 ans, Jean 42, et Christine 37. Nous pouvons crire la mme table de hachage en utilisant loprateur => :
my % h a s h = ( m i c h e l = > 25 , j e a n = > 42 , c h r i s t i n e = > 37 , );

Il est indniable que cette deuxime forme est plus concise et claire : lconomie des guillemets nest pas ngligeable, et la relation dassociativit y est mieux reprsente grce la che, mme si le code source nest pas indent proprement :
my % h a s h = ( m i c h e l = > 25 , j e a n = > 42 , c h r i s t i n e = > 37 ) ; # est p l u s c l a i r e que : my % h a s h = ( m i c h e l , 25 , j e a n , 42 , c h r i s t i n e , 37 ) ;

76

CHAPITRE 4 Structures de donnes

Une table de hachage est ainsi une table dassociation, qui permet de relier deux types de valeurs. Le seul impratif est que ces valeurs soient des scalaires. Mais comme une rfrence est un scalaire, il est possible et facile dassocier une cl des structures. La table de hachage ci-dessous illustre le fait quune cl peut tre une chane ou un nombre (qui sera interprt comme une chane6 ), et les valeurs peuvent tre nimporte quel scalaire, tel que nombre, chane ou rfrence (sur tableau ou sur table de hachage).
my % h a s h = ( n o m b r e = > 12 , c h a n e = > " c e c i est une c h a n e " , pairs => [ 0, 2, 4, 6, 8 ], impairs => [ 1, 3, 5, 7, 9 ], t a b l e a u = > \ @array , f r u i t s = > [ qw ( pomme , poire , citron , fraise ) ], 42 => " rponse " , );

Accder aux lments dune table de hachage


$hash{cl}

Crer une table de hachage nest utile que sil est ais de manipuler ses lments. Une manire daccder un lment dune table de hachage est de connatre sa cl :
6. En fait nimporte quel scalaire peut tre utilis, mais en dehors dun nombre ou dune chane, cela donnera des rsultats surprenants et probablement inutilisables.

Accder aux lments dune table de hachage

77

$hash{cl}. Il nest pas ncessaire de mettre des guille-

ments autour de la cl sil ny a pas dambigut7 . Un lment dune table de hachage peut tre utilis pour rcuprer une valeur ou pour lassigner, et donc ajouter et modier des valeurs :
my % h a s h = ( j e a n = > 25) ; # a j o u t e r une a s s o c i a t i o n $ h a s h { m i c h e l } = 34; # m o d i f i e r une a s s o c i a t i o n $ h a s h { j e a n } = 26; # a f f i c h e r une v a l e u r say " j e a n a " . $ h a s h { j e a n } . " ans . " ; say " m i c h e l a " . $ h a s h { m i c h e l } . " ans . " ; # Ici , les g u i l l e m e t s s o n t o b l i g a t o i r e s $ h a s h { marie - c l a i r e } = 16; say Marie - c l a i r e . ( $ h a s h { marie - c l a i r e } > 17 ? est : " n est pas " ) . majeure ;

Cette manire daccder un lment permet de le rinitialiser, cest--dire de le mettre undef :


my % h a s h = ( j e a n = > 25) ; # a j o u t e r une a s s o c i a t i o n $ h a s h { m i c h e l } = 34; # e f f a c e r une a s s o c i a t i o n $hash { michel } = undef ; # peut s crire galement u n d e f $ h a s h { m i c h e l };

7. Cest--dire si la cl ne contient pas de caractre espace ou un oprateur, et si elle ne commence pas par un chiffre. Par exemple, test, chat_1, r0b1n3t sont valides. Mais Marie Claire, jean-jacques sont invalides.

78

CHAPITRE 4 Structures de donnes

Attention
La table de hash de lexemple prcdent contient toujours deux associations : jean et michel. La valeur de lassociation michel est undef, mais la table de hachage contient bien deux entres.

Supprimer un lment dune table de hachage


delete $hash{cl}

delete permet denlever une association dune table de hachage, et sutilise trs simplement :
my % h a s h = ( j e a n = > 25) ; delete $hash { jean } # le h a s h ne c o n t i e n t p l u s r i e n

Tester lexistence et la dnition dun lment


exists $hash{cl} dened $hash{cl}

exists permet de savoir si, pour une cl donne, il existe une association dans la table de hachage en question. dened renvoie vrai si la valeur de lassociation est dnie, faux dans les autres cas.

Utiliser des tranches de hashs

79

Attention
Il ne faut pas confondre existence dune association, et le fait que la valeur soit dnie. Voici un exemple de code qui illustre les deux concepts :
my % h a s h = ( j e a n = > 25 , j u l i e = > undef , ); e x i s t s $ h a s h { j e a n }; # r e n v o i e v r a i d e f i n e d $ h a s h { j e a n }; # r e n v o i e v r a i e x i s t s $ h a s h { j u l i e }; # r e n v o i e v r a i d e f i n e d $ h a s h { j u l i e }; # r e n v o i e f a u x e x i s t s $ h a s h { m a r c }; # r e n v o i e f a u x d e f i n e d $ h a s h { m a r c }; # r e n v o i e f a u x

exists reprsente lexistence dune association (mme vers une valeur non dnie), alors que dened correspond au fait que la
valeur pointe par la cl soit dnie.

Utiliser des tranches de hashs


hash{a..b}

Comme pour les tableaux, il est possible de manipuler des tranches (slice en anglais) de tableaux de hachage. Il suft pour cela dutiliser non pas une cl, mais une liste8 de cls pour spcier plusieurs valeurs, qui sont alors renvoyes sous forme de liste. Attention
Lors dune utilisation dune tranche de hash, le sigil du hash se transforme de % en @.

8. Ou un tableau.

80

CHAPITRE 4 Structures de donnes

my % h a s h = ( j e a n = > 25 , j u l i e = > undef , m i c h e l = > 12 , ); my @ a g e s = @ h a s h { j e a n , m i c h e l }; # @ a g e s c o n t i e n t (25 , 12) my @ a g e s = @ h a s h { j u l i e , m i c h e l }; # @ a g e s c o n t i e n t ( undef , 12)

Bien sr, il est possible dassigner plusieurs valeurs la fois en utilisant une tranche :
my % h a s h = ( j e a n = > 25 ) ; @ h a s h { j u l i e , m i c h e l } = (42 , 12) ; # % hash vaut maintenant : # j e a n = > 25 , # j u l i e = > 42 , # m i c h e l = > 12

Comme les cls sont spcies grce une liste ou un tableau, il est possible dutiliser loprateur qw() :
my % h a s h = ( j e a n = > 25 ) ; @ h a s h { qw ( j u l i e m i c h e l ) } = (42 , 12) ;

Il est bien sr possible de crer un tableau de cls et de lutiliser pour dnir une tranche :
my @ c l e s = qw ( j u l i e m i c h e l ) ; my % h a s h = ( j e a n = > 25 ) ; @ h a s h { @ c l e s } = (42 , 12) ;

Obtenir la liste des cls


keys %hash

Obtenir la liste des valeurs dune table de hachage

81

keys renvoie la liste des cls du hash :


my % h a s h = ( j e a n = > 25 , j u l i e = > undef , m i c h e l = > 12 , ); my @ c l e s = k e y s % h a s h # @ c l e s v a u t ( j e a n , j u l i e , m i c h e l )

Attention
Lordre des cls renvoyes est alatoire, mais cest le mme que lordre des valeurs renvoyes par values.

Obtenir la liste des valeurs dune table de hachage


values %hash

values renvoie la liste des valeurs du hash :


my % h a s h = ( j e a n = > 25 , j u l i e = > undef , m i c h e l = > 12 , ); my @ v a l e u r s = v a l u e s % h a s h ; # @ v a l e u r s v a u t (25 , undef , 12)

Attention
Lordre des valeurs renvoyes est alatoire, mais cest le mme que lordre des cls renvoyes par keys.

82

CHAPITRE 4 Structures de donnes

Dumper un hash
De la mme manire que Data::Dumper peut tre utilis pour dumper un tableau, ce module permet galement dexplorer un hash, ou tout autre structure de donnes complexe.
use D a t a :: D u m p e r ; $ D a t a :: D u m p e r :: I n d e n t = 0; my % h a s h = ( nom = > j e a n , age = > 12 , n o t e s = > [ 10 , 13.5 , 18 ] ); say D u m p e r (\% h a s h ) ; # affiche l cran : # $ V A R 1 = { nom = > j e a n , n o t e s = > [10 , 1 3 . 5 ,18] , age = > 1 2 } ;

Il est important de se rendre compte quune table de hachage ne stocke pas lordre des couples cl-valeur, pour des raisons de performances9 .

Boucler sur un hash avec foreach


foreach (. . .) {. . .}

La premire mthode classique pour parcourir une table de hachage est dutiliser foreach sur les cls du hash, et de rcuprer la valeur pour chaque cl :
9. Il existe des modules qui proposent des tables de hachage qui gardent lordre, ou mme qui trient la vole leur contenu, comme par exemple Tie::Hash::Sorted.

Boucler sur un hash avec each

83

my % h a s h = ( j e a n = > 25 , m i c h e l = > 12 , ); f o r e a c h my $ k e y ( k e y s % h a s h ) { my $ v a l u e = $ h a s h { $ k e y }; say " l age de $ k e y est : $ v a l u e " ; }

Cette mthode a lavantage dtre explicite et simple comprendre.

Boucler sur un hash avec each


each

Il existe cependant un autre moyen de boucler la fois sur les cls et valeurs dune table de hachage, en utilisant la fonction each :
my % h a s h = ( j e a n = > 25 , m i c h e l = > 12 , ); w h i l e ( my ( $key , $ v a l u e ) = e a c h % h a s h ) { say " l age de $ k e y est : $ v a l u e " ; }

La fonction each renvoie les couples cl-valeur du hash un un. Lorsque tous les couples ont t renvoys, each renvoie une liste vide (cest ce qui fait que la boucle while sarrte), puis recommence au dbut du hash. Attention
Lordre des couples cl-valeur renvoys est alatoire, mais cest le mme que lordre des cls renvoyes par keys ou celui des valeurs renvoyes par values.

84

CHAPITRE 4 Structures de donnes

Rfrencer un hash
{ cl => valeur } \%hash

Comme pour les tableaux, il est ais de crer une rfrence sur un hash. Pour obtenir une rfrence, il est possible dutiliser \ sur une table de hachage, qui renvoie une rfrence pointant dessus. Pour crer une rfrence sur un ensemble de clvaleur directement, il existe galement { } :
my % h a s h = ( nom = > " H u g o " , p r e n o m = > " V i c t o r " ) ; my $ r e f _ h a s h = \% h a s h ; # $ r e f _ h a s h est une r f r e n c e sur @ h a s h my $ r e f _ h a s h = { nom = > " H u g o " , p r e n o m = > " V i c t o r " }; # $ r e f _ h a s h est une r f r e n c e

Drfrencer un hash
%{$ref_array}

Pour accder un hash rfrenc, il faut utiliser %{} :


my $ r e f _ h a s h = { nom = > " H u g o " , p r e n o m = > " V i c t o r " }; my % h a s h = %{ $ r e f _ h a s h };

Loprateur %{} value lexpression donne, et drfrence le rsultat en hash. Lorsquil ny a pas dambigut, les accolades peuvent tre omises :

Crer des structures hybrides

85

my $ r e f _ h a s h = { nom = > " H u g o " , p r e n o m = > " V i c t o r " }; my % h a s h = % $ r e f _ h a s h ;

Pour accder directement un lment dun hash partir dune rfrence, il faut utiliser loprateur che -> :
my $ r e f _ h a s h = { nom = > " H u g o " , p r e n o m = > " V i c t o r " }; my $ p r e n o m = $ r e f _ h a s h - >{ p r e n o m }; # $prenom vaut " Victor "

Crer des structures hybrides


Il est facile de crer des structures plus complexes, en combinant les tableaux, hash, listes et en utilisant des rfrences. Pour crer un hash de tableaux, il suft dutiliser des rfrences de tableaux, et de les stocker dans une table de hachage. Ainsi, il est facile de crer une structure de donnes pour reprsenter un lve et ses notes sur les trois premiers mois :
my % e l e v e 1 = ( nom = > D u p o n t , prenom => Jean , n o t e s = > { j a n v i e r = > [ 12 , 9 , 14 , 10 ] , f e v r i e r = > [ 13 , 15 , 8 , 9 ] , m a r s = > [ 12 , 8 , 11 , 1 2 . 5 ] } );

Accder la troisime note du mois de fvrier de cet lve se fait ainsi :


my $ n o t e = $ e l e v e 1 { n o t e s } - >{ f v r i e r } - >[2]; # note vaut 8

86

CHAPITRE 4 Structures de donnes

Ce code peut galement scrire comme suit :


my $ n o t e = $ e l e v e 1 { n o t e s }{ f v r i e r } [ 2 ] ;

Transformer tous les lments dun tableau ou dune liste avec map
map {. . .} @tableau

Les oprations de base sur les tableaux et listes consistent en gnral manipuler un ou plusieurs lments. Mais Perl propose de manipuler des listes en entier grce map et grep. map permet dappliquer des transformations sur tous les lments dun tableau ou dune liste. Il prend en argument :
q q

un bloc de code10 excuter ; une liste ou un tableau.

Le bloc de code sera excut autant de fois quil y a dlments dans la liste ou le tableau, et pour chaque itration, la variable spciale $_ prendra la valeur de llment.
# e x e m p l e a v e c un t a b l e a u my @ t a b l e a u = ( 1 . . 3 ) ; @ t a b l e a u 2 = map { $_ * 2 } @ t a b l e a u 1 ; # @tableau2 contient ( 2, 4, 6 ) # m m e e x e m p l e a v e c une l i s t e @ t a b l e a u = map { $_ * 2 } ( 1 . . 3 ) ; # @tableau contient ( 2, 4, 6 )

10. Il est possible de donner une expression comme premier argument, mais cette manire dutiliser map est dconseille.

Transformer tous les lments dun tableau ou dune liste avec map

87

Il est possible dcrire lquivalent dun code utilisant map avec un morceau de code utilisant foreach, mais gnralement cela prend plus de lignes. Voici un exemple qui illustre trs bien cela. Il sagit dcrire une fonction qui reoit en argument une liste de chanes, et qui renvoie une nouvelle liste contenant les chanes mises en majuscules.
# exemple avec foreach sub m a j u s c u l e s { my @ c h a i n e s = @_ ; my @ r e s u l t a t ; f o r e a c h my $ c h a i n e ( @ c h a i n e s ) { p u s h @ r e s u l t a t , uc ( $ c h a i n e ) ; } return @resultat ; } # e x e m p l e a v e c map sub m a j u s c u l e s { my @ c h a i n e s = @_ ; r e t u r n map { uc } @ c h a i n e s ; }

Il convient dexpliquer ici que : q La fonction uc prend en argument une chane de caractres et la met en majuscule. Dans argument, elle utilise la variable spciale $_. q map renvoie la liste rsultat (sans modier la liste originelle). Or il ne sert rien de stocker le rsultat dans un tableau intermdiaire, autant directement le renvoyer avec return. Il faut bien comprendre que map construit une nouvelle liste avec les rsultats du bloc de code excut. Le bloc de code nest donc pas oblig de renvoyer toujours un rsultat, et il peut galement en renvoyer plus dun. Voici un exemple qui supprime les nombres infrieurs 4 dune liste, et pour tout autre nombre, il insre son double la suite.

88

CHAPITRE 4 Structures de donnes

my @ l i s t = map { $_ >= 4 # si le n o m b r e est > 4 ? ( $_ , 2* $_ ) # le n o m b r e et son d o u b l e : () # sinon rien } (1..5) ; # @ l i s t e c o n t i e n t (4 , 8 , 5 , 10)

Astuce
map est principalement utilis pour crer des tableaux, mais il
peut galement tre utilis pour crer des hashs. En effet, un hash sinitialise grce une liste, comme dans le code suivant :
my % h a s h = ( nom = > " J e a n " ) ; # quivalent my % h a s h = ( " nom " , " J e a n " ) ;

Dans cet exemple, le hash %hash est initialis grce une liste de deux chanes, la premire correspondant au nom de la cl, la deuxime la valeur. Cette liste peut parfaitement tre la valeur de retour dune fonction, et donc map peut tre utilis pour crer cette liste :
my $ c o u n t = 0; my % h a s h = map { " e l e v e _ " . $ c o u n t ++ = > $_ ; } ( qw ( J e a n M a r c e l ) ) ; # % hash contient : # eleve_0 => " Jean " # eleve_1 => " Marcel "

Filtrer un tableau ou une liste avec grep


grep {. . .} @tableau

Renvoyer le premier lment dune liste avec List::Util

89

Cest le compagnon de map. grep permet de ltrer un tableau ou une liste, et renvoie une liste rsultat ne contenant que les lments qui passent un test donn. En exemple, voici comment ltrer les nombres dune liste pour ne garder que ceux infrieurs 10 :
my @ r e s u l t a t = g r e p { $_ < 10 } @ l i s t e ;

Voici un extrait de code qui limine tous les nombres impairs dune liste de nombres :
my @ l i s t e = ( 1 . . 5 ) ; my @ r e s u l t a t = g r e p { ! $_ % 2 } @ l i s t e

Loprateur % calcule le modulo de deux nombres. $_ % 2 renverra donc zro si $_ est pair. Pour garder les nombres pairs, il faut que le test soit vrai ; il donc faut inverser le test avec !. Info
En plus des oprations classiques sur les listes, Perl propose un assortiment de fonctions plus puissantes, qui ont t elles-mme compltes par un nombre important de modules disponibles sur CPAN. Seule une slection de deux modules prouvs et stables est prsente ici, List::Util et List::MoreUtils.

Renvoyer le premier lment dune liste avec List::Util


rst(..)

Le module List::Util (disponible en standard avec Perl) contient quelques mthodes pour travailler sur les listes, an dtendre les oprateurs de base du langage.

90

CHAPITRE 4 Structures de donnes

rst renvoie le premier lment dune liste ou dun ta-

bleau.
use L i s t :: U t i l qw ( f i r s t ) ; my $ r e s u l t a t = f i r s t ( 1 . . 3 ) ; # $resultat contient 1

Trouver le plus grand lment avec List::Util


max(..) maxstr(..)

max renvoie le plus grand lment, en les comparant nu-

mriquement.
use L i s t :: U t i l qw ( max ) ; my $ r e s u l t a t = max (1 , 3 , 2 , 5 , -2) ; # $resultat contient 5

maxstr renvoie llment qui a la plus grande valeur alpha-

btique.
use L i s t :: U t i l qw ( m a x s t r ) ; my $ r e s u l t a t = m a x s t r ( q u e l q u e s , c h a i n e s ) ; # $ r e s u l t a t c o n t i e n t q u e l q u e s ;

Trouver le plus petit lment avec List::Util


min(..) minstr(..)

Rduire une liste avec List::Util

91

min renvoie le plus petit lment dune liste, en les com-

parant numriquement.
use L i s t :: U t i l qw ( min ) ; my $ r e s u l t a t = min (1 , 3 , 2 , 5 , -2) ; # $ r e s u l t a t c o n t i e n t -2

minstr, quant elle, renvoie llment qui a la plus petite

valeur alphabtique.
use L i s t :: U t i l qw ( m a x s t r ) ; my $ r e s u l t a t = m a x s t r ( q u e l q u e s , c h a i n e s ) ; # $ r e s u l t a t c o n t i e n t c h a i n e s ;

Rduire une liste avec List::Util


reduce {. . .} (. . .)

Cette mthode rduit une liste, en appliquant un bloc de code qui reoit deux argument $a et $b. Lors de la premire valuation, ces deux variables sont initialises aux deux premiers lments de la liste. Lors des valuations ultrieurs, $a vaut le rsultat de lvaluation prcdente, et $b a comme valeur le prochain lment de la liste.
use L i s t :: U t i l qw ( r e d u c e ) ; $ r e s u l t a t = r e d u c e { $a * $b } ( 1 . . 1 0 ) # $ r e s u l t a t c o n t i e n t le p r o d u i t # des n o m b r e s de 1 10

92

CHAPITRE 4 Structures de donnes

Mlanger une liste avec List::Util


shufe(..)

Il est ais de mlanger une liste de manire pseudo-alatoire avec cette fonction, qui sutilise trs facilement :
use L i s t :: U t i l qw ( r e d u c e ) ; my @ t a b l e a u = ( 1 . . 1 0 ) ; my @ m e l a n g e = s h u f f l e ( @ t a b l e a u ) ;

Faire la somme dune liste avec List::Util


sum(..)

Comme son nom lindique, sum sert simplement faire la somme de tous les lments dune liste :
use L i s t :: U t i l qw ( sum ) ; my $ s o m m e = ( 1 . . 1 0 0 ) ; # somme vaut 5050

Savoir si un lment vrie un test avec List::MoreUtils


any {. . .} @tableau

La fonction any renvoie vrai si au moins un des lments dune liste vrie un test donn.

Tricoter un tableau avec List::MoreUtils

93

Savoir si aucun lment ne vrie un test avec List::MoreUtils


none {. . .} @tableau

La fonction none renvoie vrai si aucun des lments dune liste ne vrie un test donn.

Appliquer du code sur deux tableaux avec List::MoreUtils


pairwise {. . .} (@tableau1, @tableau2)

Cette fonction trs utile permet dexcuter un bloc de code sur deux tableaux la fois : dans le bloc de code, les variables $a et $b prennent successivement les premiers des lments des deux tableaux respectivement, plus les deuximes lments, etc. jusqu la n de la liste. pairwise renvoie la liste de rsultats.

Tricoter un tableau avec List::MoreUtils


zip(@tableau1, @tableau2, . . .)

zip prend en argument un nombre quelconque de ta-

bleaux, et renvoie une liste constitue du premier lment de chaque tableau, puis du deuxime lment de chaque tableau, etc., jusqu la n de tous les tableaux.

94

CHAPITRE 4 Structures de donnes

Enlever les doublons avec List::MoreUtils


uniq(@tableau)

Cette fonction renvoie une liste contenant les lments du tableau pass en paramtre, mais sans les doublons. Info
Lordre initial des lments est respect dans la liste rsultat.

5
Expressions rgulires
Lune des forces traditionnelles de Perl rside dans ses capacits danalyse, avec en premier lieu ce vritable langage dans le langage que sont les expressions rgulires. Cellesci permettent de comparer une chane un motif, pour vrier si elle correspond et ventuellement en extraire des parties intressantes. Si leur syntaxe apparat assez cryptique de prime abord, cest parce quil sagit avant tout dun langage trs spcialis, ddi une tche particulire, la recherche de correspondance de motifs. Larry Wall, le concepteur de Perl, samusait dailleurs que bien que les expressions rgulires soient en partie lorigine de la rputation de mauvaise lisibilit de Perl, les autres langages de programmation se sont empresss de les emprunter pour eux-mmes. Quest-ce quune expression rgulire ?
Tout dabord, un petit peu de thorie et dhistoire. Les expressions rgulires (certains prfrent parler dexpressions rationnelles) sont une grammaire sinscrivant dans le cadre de la thorie des langages formels, qui dcrit comment analyser et

96

CHAPITRE 5 Expressions rgulires

excuter de tels langages au travers dautomates tats nis. Ces derniers se distinguent en automates tats nis dterministes ou DFA (Deterministic Finite Automaton), sil est possible de calculer si lautomate sarrtera et quel moment, et non dterministes ou NFA (Non-deterministic Finite Automaton) dans le cas contraire. Ken Thompson, lun des pres du systme Unix, crivit les premires implmentations dexpressions rgulires, de type DFA, dans les diteurs de lignes QED et ed, puis dans lutilitaire grep en 1973. La syntaxe dorigine (dite SRE) fut par la suite augmente et normalise au sein de POSIX sous forme de deux syntaxes diffrentes (BRE et ERE). Une implmentation de ces expressions rgulires POSIX, crite par Henry Spencer, servit de base pour celle de Perl, qui tendit et surtout rationalisa de manire trs importante la grammaire sous-jacente. Cest elle qui fait maintenant ofce de quasi-standard, au travers de sa rimplmentation PCRE (Perl Compatible Regular Expressions) crite par Philip Hazel pour le serveur de courriels Exim, et qui est maintenant utilise par de trs nombreux logiciels.

Effectuer une recherche


m//

m// est loprateur de recherche (match), qui prend comme argument une expression rgulire : m/REGEXP/. Le dlimiteur par dfaut est la barre oblique (/), mais il peut tre

chang par nimporte quel caractre (hormis lespace et le saut de ligne, pour dvidentes raisons de lisibilit), et en particulier par les couples de caractres suivants : m(..),

Rechercher et remplacer

97

m[..], m{..}, m<..>. Ces derniers sont trs pratiques car

Perl reconnat correctement les imbrications des caractres identiques faisant partie de la syntaxe des expressions, et ils permettent gnralement une meilleure prsentation pour les expressions complexes sur plusieurs lignes. Quand le dlimiteur est celui par dfaut, le m initial peut tre omis, do la forme abrge // qui va tre utilise trs souvent dans ce chapitre. Par dfaut, m// travaille sur la variable $_, mais il suft dutiliser loprateur de liaison = pour travailler sur une autre variable :
$ s t r i n g =~ / ab /; # t e s t de $ s t r i n g c o n t r e le m o t i f " ab "

Le motif peut contenir des variables, qui seront interpoles (et le motif recompil) chaque valuation. En contexte scalaire, m// renvoie vrai si le texte correspond au motif, faux sinon. En contexte de liste, il renvoie la liste des groupes capturants ($1, $2, $3, etc.) en cas de succs, et la liste vide sinon.

Rechercher et remplacer
s///

s/// est loprateur de recherche et remplacement, qui

prend comme arguments une expression rgulire et une chane de remplacement : s/REGEXP/REMPLACEMENT/. Comme pour m//, les dlimiteurs peuvent tre changs, avec le petit rafnement supplmentaire quil est possible dutiliser des paires diffrentes pour les deux parties de loprateur : s{..}<..>.

98

CHAPITRE 5 Expressions rgulires

s/// travaille lui aussi par dfaut sur la variable $_, et

comme prcdemment il suft dutiliser loprateur de liaison = pour travailler sur une autre variable :
# c h e r c h e la c h a n e " ab " et la r e m p l a c e # par " ba " $ s t r i n g =~ s / ab / ba /;

L encore, le motif interpole les ventuelles variables quil contient. La chane de remplacement fonctionne vritablement comme une chane normale, et supporte donc sans problme linterpolation des variables, y compris celles qui contiennent les valeurs des groupes capturants ($1, $2, $3, etc.). En plus des modicateurs dcrits ci-aprs, reconnus tant par m// que par s///, ce dernier en supporte un spcique, /e, qui value la chane de remplacement avec la fonction eval. Ce nest pas trs performant, mais cela permet de raliser simplement certaines oprations, par exemple le petit bout de code suivant permet de remplacer tous les caractres encods dans les URL par leur vraie valeur :
$ s t r i n g =~ s / % ( [ A - Fa - f0 - 9 ] { 2 } ) / chr ( hex ( $1 ) ) / eg ;

Il est mme possible dvaluer le rsultat obtenu en ajoutant un autre modicateur /e.
s/// renvoie le nombre de remplacements effectus sil y

en a eu, faux sinon.

Stocker une expression rgulire


qr//

Rechercher sans prendre en compte la casse

99

Une expression rgulire peut tre stocke dans une variable scalaire en utilisant la syntaxe qr// :
my $re = qr / ab /;

Comme tous les q-oprateurs, il est possible de changer le caractre dlimitant la chane de loprateur. Les expressions suivantes dnissent la mme expression rgulire que ci-dessus :
my $re = qr { ab }; my $re = qr ! ab !;

Il est alors possible de tenter une correspondance avec un texte avec loprateur de liaison = vu page 96 :
$ t e x t =~ $re ;

Rechercher sans prendre en compte la casse


/i

Info
Une recherche de correspondance peut tre afne laide de modicateurs, qui scrivent sous la forme de simples lettres en n doprateur. Plusieurs modicateurs peuvent se combiner sans problme dincompatibilit.

Par dfaut, la recherche seffectue en respectant la casse (la diffrence entre majuscules et minuscules). Quand ce modicateur est activ, la recherche devient insensible la casse :

100

CHAPITRE 5 Expressions rgulires

" BAM " =~ / bam /; " BAM " =~ / bam / i ;

# ne c o r r e s p o n d pas # correspond

Rechercher dans une chane multiligne


/m

Ce modicateur fait considrer la chane comme contenant plusieurs lignes. En ralit, cela change le comportement des mtacaractres ^ et $ (voir page 102) pour quils correspondent respectivement au dbut et n des lignes au sein de la chane.

Rechercher dans une chane simple


/s

Ce modicateur fait considrer la chane comme ne comprenant quune seule ligne. En ralit, cela change le comportement du mtacaractre point . pour quil corresponde aussi au saut de ligne. Malgr leur description qui semble contradictoire, les modicateurs /m et /s peuvent se combiner ensemble sans problme : quand /ms est utilis, cela modie le mtacaractre point . pour quil corresponde tout caractre, y compris le saut de ligne, tout en autorisant les mtacaractres ^ et $ correspondre aprs et avant les sauts de lignes embarqus.

Contrler la recherche globale de correspondances

101

Neutraliser les caractres espace


/x

Le modicateur de syntaxe retire toute signication aux caractres despace, qui doivent alors tre explicitement crits sous forme de mtacaractres pour correspondre. Cela permet dcrire les expressions rgulires sur plusieurs lignes, avec des commentaires, ce qui est extrmement pratique pour les motifs longs et complexes :
$ i n p u t =~ m { ^ # d b u t de l i g n e ( ( ? : ( ? : ( ? : dis | en ) abl | pag ) e | s t a t u s ) ) # c o m m a n d e ( $1 ) ( \ + \ d +) ? # le d c a l a g e ( $2 ) \s+ # des e s p a c e s (\ S +?) \ . ( \ S +) # s e r v e u r . s o n d e ( $3 , $4 ) \s+ # des e s p a c e s (.*) $ # d e r n i e r s a r g u m e n t s ( $5 ) } sx ; Exemple tir dun module CPAN qui permet lanalyse des messages au protocole Big Brother/Hobbit.

Contrler la recherche globale de correspondances


/g, /c, /gc

Le modicateur de correspondance globale /g active la recherche globale de correspondances, cest--dire que le motif va parcourir la chane au fur et mesure des corres-

102

CHAPITRE 5 Expressions rgulires

pondances russies. Pour ce faire, la position courante de travail est normalement remise zro aprs chec, sauf si le modicateur /c est activ.

Distinguer caractres normaux et mtacaractres


Un motif est constitu dlments appels atomes dans la terminologie reconnue. Les plus simples atomes dun motif sont les caractres normaux, qui tablissent une correspondance avec eux-mmes.
" H e l l o w o r l d " =~ / W o r l d /; # c o r r e s p o n d " H e l l o w o r l d " =~ / lo Wo /; # c o r r e s p o n d a u s s i

Il est trs important de noter quun motif va toujours sarrter la premire correspondance trouve :
# le " hat " de " T h a t " c o r r e s p o n d " T h a t hat is red " =~ / hat /;

Attention
Cela surprend souvent les dbutants, mais le langage des expressions rgulires ne permet pas naturellement de rcuprer la n-ime occurrence dune correspondance. Bien sr, cela peut se contourner, de manire interne pour les cas simples, ou de manire externe dans les cas plus complexes.

Par dfaut, la casse (la distinction majuscule-minuscule) est prise en compte, et les espaces (espace normale, tabulation, retour chariot) sont considres comme des caractres normaux. Plus prcisment, est considr comme caractre normal tout caractre qui nest pas un mtacaractre, ces derniers

Distinguer caractres normaux et mtacaractres

103

tant utiliss pour la syntaxe des expressions rgulires : { } [ ] ( ) ^ $ . | *? + \ Tout mtacaractre redevient un caractre normal en le prxant par une barre oblique inverse \ (antislash ou backslash en anglais), ainsi :
" 2 + 2 = 4 " =~ / 2 + 2 / ; " 2 + 2 = 4 " =~ / 2 \ + 2 / ; # ne c o r r e s p o n d pas # ok , c o r r e s p o n d

Bien sr, le caractre utilis pour dlimiter lexpression rgulire doit lui-mme tre protg an de pouvoir tre utilis, do lintrt de pouvoir changer le dlimiteur, an dviter le syndrome de la barre oblique :
" / usr / bin / p e r l " =~ /\/ usr \/ bin \/ p e r l /; " / usr / bin / p e r l " =~ m {/ usr / bin / p e r l };

Inversement, un caractre normal peut devenir un mtacaractre en le prxant par un antislash. Certains introduisent des squences dchappement, cest--dire quils prennent en argument une squence de caractres normaux. Pour une part, il sagit en fait simplement de mtacaractres dj bien connus : \t pour la tabulation, \n pour une nouvelle ligne, \xHH pour obtenir un caractre par son code point en hexadcimal, etc.
# c o r r e s p o n d a u s s i ( m m e si c est b i z a r r e ) " cat " =~ / \ 1 4 3 \ x61 \ x74 /;

Les autres caractres, qui ne sont pas normaux, font partie des classes de caractres (voir la section suivante).

104

CHAPITRE 5 Expressions rgulires

tablir une correspondance parmi plusieurs caractres


Une classe de caractres est un atome de longueur un qui permet dtablir une correspondance parmi plusieurs caractres possibles. Elle est note par des crochets [. . .] qui encadrent les valeurs possibles. Ainsi /[bcr]at/ peut correspondre aux chanes bat , cat et rat . Comme il serait fastidieux dcrire lensemble des lettres, il est possible de dnir des intervalles de caractres en mettant un tiret - entre les deux bornes. Ainsi [0-9] est quivalent [0123456789], et [b-n] [bcdefghijklmn]. Lexpression /[0-9a-fA-F] permet donc de chercher un caractre hexadcimal. Dernier point de la syntaxe des classes de caractres, la ngation dune classe, qui sactive en prxant par laccent circonexe ^. /[^0-9]/ correspond tout caractre qui nest pas un chiffre. Comme certaines classes sont trs courantes, elles sont prdnies dans les expressions rgulires. La plus simple est le mtacaractre point . qui correspond tout caractre sauf le saut de ligne :
" a =3 " =~ / . . . / ; # correspond

Il est bon de se souvenir que cela peut se modier avec le modicateur /s :


" k r a c k \ n b o u m " =~ / k r a c k . b o u m /; # ne c o r r . pas " k r a c k \ n b o u m " =~ / k r a c k . b o u m / s ; # c o r r e s p o n d

Il existe deux syntaxes pour utiliser les autres classes prdnies. Lune, introduite par Perl, est courte et correspond aux mtacaractres qui ont t laisss en suspens, de la

tablir une correspondance parmi plusieurs caractres

105

forme antislash plus un caractre minuscule, avec la forme majuscule qui correspond sa ngation :
q

\w (comme word character) correspond un caractre de

mot , cest--dire tout alphanumrique plus lespace souligne (_), et \W tout caractre qui nest pas un mot.
q

\d (comme digit) correspond un chiffre, et \D tout ce qui nest pas un chiffre :


/\ d \ d :\ d \ d :\ d \ d /; # f o r m a t d h e u r e hh : mm : ss

\s (comme space) correspond tout caractre considr comme un espace blanc (fondamentalement, la tabulation, lespace usuelle et les sauts de ligne et de page), et \S tout caractre qui nest pas considr comme un espace blanc. Lautre syntaxe, dite POSIX, est de la forme [:classe:], utiliser au sein dune classe, dont la syntaxe complte est [[:classe:]]. Perl permet de plus de crer les ngations de ces classes par [^[:classe:]]. La liste des classes reconnues est : q [[:alpha:]] correspond tout caractre alphabtique, par dfaut [A-Za-z] ;
q q

[[:alnum:]] correspond tout caractre alphanumrique, par dfaut [A-Za-z0-9] ; [[:ascii:]] correspond tout caractre dans le jeu de

caractres ASCII ;
q

[[:blank:]] une extension GNU qui correspond les-

pace usuelle et la tabulation ;


q q

[[:cntrl:]] correspond tout caractre de contrle ; [[:digit:]] correspond tout caractre considr comme un chiffre, quivalent \d ; [[:graph:]] correspond tout caractre imprimable,

lexclusion des espaces ;

106

CHAPITRE 5 Expressions rgulires

[[:lower:]] correspond tout caractres minuscule, par dfaut [a-z] ; [[:print:]] correspond tout caractre imprimable, y

compris les espaces ;


q

[[:punct:]] correspond tout caractre de ponctua-

tion, cest--dire tout caractre imprimable lexclusion des caractres de mot ;


q

[[:space:]] correspond tout caractre despace, quivalent \s plus la tabulation verticale \cK ; [[:upper:]] correspond tout caractre majuscule, par dfaut [A-Z] ; [[:word:]] est une extension Perl quivalente \w ; [[:xdigit:]] correspond tout caractre hexadcimal.

q q

Il faut noter que, au sein dune classe de caractres, la plupart des mtacaractres de la syntaxe des expressions rgulires perdent leur signication, hormis ceux qui correspondent des caractres (comme \t et \n) et ceux qui correspondent des classes (comme \d).

Ancrer une expression rgulire en dbut de ligne


^

Info
Les ancres sont des assertions de longueur nulle qui permettent, comme leur nom lindique, dancrer la recherche en certains points de la chane, an dimposer des contraintes au motif pour, par exemple, limiter ou au contraire tendre sa porte dexcution.

Ancrer une expression rgulire en n de ligne

107

Lancre ^ permet daccrocher le motif en dbut de ligne :


" b e a u s o l e i l " =~ /^ s o l e i l /; # ne c o r r e s p . pas " b e a u s o l e i l " =~ /^ b e a u /; # correspond

Par dfaut, ^ garantit de ne correspondre quen dbut de chane, y compris si celle-ci comporte des sauts de ligne internes. Pour que cette ancre corresponde en plus aprs chaque saut de ligne, il suft dutiliser le modicateur /m :
" un \ n d e u x \ n t r o i s " =~ /^ d e u x / m ; # correspond

Ancrer une expression rgulire en n de ligne


$

Lancre $ permet daccrocher le motif en n de ligne :


" b e a u s o l e i l " =~ / b e a u $ /; # ne c o r r e s p . pas " b e a u s o l e i l " =~ / s o l e i l $ /; # c o r r e s p o n d

Comme pour ^, $ garantit par dfaut de ne correspondre quen n de chane, et il faut utiliser le modicateur /m pour quelle corresponde en plus avant chaque saut de ligne interne :
" un \ n d e u x \ n t r o i s " =~ / d e u x $ / m ; # correspond

108

CHAPITRE 5 Expressions rgulires

Utiliser une ancre de dbut de chane


\A

\A est comme ^ une ancre de dbut de chane, sauf quelle

nest pas affecte par le modicateur /m.

Utiliser une ancre de n de chane


\Z, \z

\Z est comme $ une ancre de n de chane, sauf quelle nest pas affecte par le modicateur /m. Comme \Z peut tout de mme correspondre juste avant un saut de ligne nal, il existe aussi \z qui garantit de ne correspondre qu la n physique de la chane.

Utiliser une ancre de frontire de mot


\b

Lancre \b (boundary) est conue pour correspondre la frontire dun mot, qui intervient entre un \w et un \W.
" b e a u s o l e i l " =~ /\ b b e a u /; # correspond " b e a u s o l e i l " =~ /\ b b e a u \ b /; # ne c o r r e s . pas " b e a u s o l e i l " =~ /\ b s o l e i l /; # ne c o r r e s . pas

Utiliser une ancre de correspondance globale

109

Utiliser une ancre par prxe de recherche


\K

Lancre \K (comme keep), introduite avec Perl 5.10 et auparavant par le module CPAN Regexp::Keep, permet de positionner la recherche de correspondance en un point prcis de la chane examine. Plus prcisment la partie gauche de cette assertion est un motif de recherche pure, utilis seulement pour le positionnement. La principale utilit de cette assertion est doffrir une solution plus efcace un cas courant dutilisation de s///, leffacement dun morceau de texte repr par un prxe particulier qui est conserver :
$ t e x t =~ s /( p r f i x e ) t e x t e s u p p r i m e r / $1 /;

se rcrit :
$ t e x t =~ s / p r f i x e \ K t e x t e s u p p r i m e r //;

qui est un peu plus clair, mais est surtout deux fois plus rapide sexcuter.

Utiliser une ancre de correspondance globale


\G pos()

Lancre \G ne correspond qu la position courante du motif dans la chane, qui peut sobtenir et se modier par la fonction pos().

110

CHAPITRE 5 Expressions rgulires

$str = " abcdef " ; $ s t r =~ / cd / g ; say pos ( $ s t r ) ;

# affiche 4

Cette ancre sutilise typiquement sous la forme idiomatique m/\G. . ./gc pour crire des analyseurs bas niveau de type lex. Il nest pas utile de sattarder davantage sur ce point car il existe des modules CPAN permettant de raliser des analyses syntaxiques et grammaticales de manire plus aise. Par exemple Parse::Yapp (un peu vieux mais toujours utile) qui offre un fonctionnement de type yacc, et Marpa qui supporte des grammaires de type EBNF. galement les deux modules de Damian Conway, Parse::RecDescent et le rcent Regexp::Grammars qui rend obsolte le premier et permet dcrire des grammaires arbitrairement complexes. Unicode et locales
Il nest pas possible daborder ici en dtail les problmes lis Unicode car il faudrait un livre entier ddi ce sujet pour le couvrir de manire correcte ; voici cependant un rappel des bases. Unicode est une norme qui constitue un catalogue des centaines de milliers de caractres des critures du monde entier, de leurs proprits associes et de leurs rgles dutilisation. Au-del de notre alphabet romain si familier, et mme des alphabets proches comme le grec ou le cyrillique, il existe de nombreux systmes dcritures qui obissent des rgles totalement diffrentes. Par exemple les systmes hbreu et arabe scrivent de droite gauche. Les caractres arabes ont diffrentes variantes en fonction de leur position dans un mot. Les syllabaires japonais et les idogrammes chinois nont pas de

Quantieur *

111

notion de casse. Plusieurs systmes dcritures possdent leurs propres dnitions de chiffres. Sans compter les centaines de symboles techniques et mathmatiques. Le moteur dexpressions rgulires de Perl supporte sans problme lanalyse de chanes en Unicode (dans le format interne de Perl qui est une variation de UTF-8), mais ce support implique des comportements, spcis dans la norme Unicode, qui peuvent surprendre lutilisateur non averti. Ceux-ci sactivent typiquement par le chargement des locales qui changent certaines dnitions comme les caractres qui constituent un mot (\w), les caractres considrs comme des chiffres (\d), et mme les caractres considrs comme des espaces (\s). Ainsi, si la locale franaise va inclure les caractres diacritiques (, , , etc.) dans les caractres de mot (\w), ce nest pas le cas dune locale anglaise. En rsum, devant un comportement curieux avec une expression rgulire, il est souvent utile de vrier lenvironnement et le type des donnes sur lesquelles lexpression est teste.

Quantieur *
x*

Info
Les quantieurs permettent la rptition dun sous-motif, pour que celui-ci tablisse une correspondance multiple de ses atomes avec la chane examine.

Le quantieur * tablit une correspondance de zro ou plusieurs fois :

112

CHAPITRE 5 Expressions rgulires

" krack " " kraaack " " krck "

=~ / kra * ck /; =~ / kra * ck /; =~ / kra * ck /;

# correspond # correspond # correspond

Le dernier exemple montre bien que le sous-motif peut russir en correspondant zro fois.

Quantieur +
x+

Le quantieur + tablit une correspondance de une ou plusieurs fois :


" krack " " kraaack " " krck " " item04 " " Kevin68 " " srv - h t t p " =~ / kra + ck /; =~ / kra + ck /; =~ / kra + ck /; =~ /\ w +/; =~ /\ w +/; =~ /\ w +/; # correspond # correspond # ne c o r r e s . pas # correspond # correspond # ne c o r r e s . pas

# r e c h e r c h e s i m p l i s t e d une a d r e s s e I P v 4 /\ d + \ . \ d + \ . \ d + \ . \ d +/; # r e c h e r c h e s i m p l i s t e d une a d r e s s e m a i l / <[ -.\ w ]+\ @ [ -.\ w ]+ >/;

Quantieur ?
x?

Le quantieur ? tablit une correspondance de zro ou une fois, cest--dire quil rend le sous-motif optionnel :

Quantieur {n,m}

113

" krack " " kraaack " " krck "

=~ / kra ? ck /; =~ / kra ? ck /; =~ / kra ? ck /;

# correspond # ne c o r r e s . pas # correspond

Quantieur {n}
x{n}

Le quantieur {n} tablit une correspondance dexactement n fois :


" krack " =~ / kra {2} ck /; " k r a a c k " =~ / kra {2} ck /; " k r a a a c k " =~ / kra {2} ck /; # ne c o r r e s p . pas # correspond # ne c o r r e s p . pas

Quantieur {n,m}
x{n,m}

Le quantieur {n,m} tablit une correspondance entre n et m fois :


" krack " =~ / kra {2 ,4} ck /; # ne c o r r . pas " k r a a a c k " =~ / kra {2 ,4} ck /; # c o r r e s p o n d " k r a a a a a c k " =~ / kra {2 ,4} ck /; # ne c o r r . pas

La borne suprieure m peut ne pas tre prcise, auquel cas la correspondance essayera de stablir au moins n fois :
" kraaack " =~ / kra {5 ,} ck /; # ne c o r r e s . pas " k r a a a a a c k " =~ / kra {5 ,} ck /; # c o r r e s p o n d

114

CHAPITRE 5 Expressions rgulires

Quantieurs non avides


x*? x+? x*?? x{n}? x{n,m}?

Un point important noter est que les quantieurs prcdents sont dits avides et tentent dtablir la correspondance la plus grande possible avec les caractres de la chane cible. Voici un exemple typique des dbutants :
" < a h r e f = " ... " > < img src = " ... " > </ a > " =~ / <.+ >/;

Tout dabord, essayer danalyser du HTML (ou du XML) avec des expressions rgulires, cest mal et a ne marche pas . Dans ce cas-ci, lutilisateur voulait capturer le premier lment <a>, mais le comportement avide de lexpression fait que cest en ralit lensemble des caractres jusquau > du </a> qui sont pris en compte. Il existe donc une variation des quantieurs qui permet de les rendre non avides, pour quils tentent dtablir une correspondance au plus tt :
q q q q q q

*? zro ou plusieurs fois, au plus tt ; +? une ou plusieurs fois, au plus tt ; ?? zro ou une fois, au plus tt ; {n}? exactement n fois, au plus tt ; {n,}? au moins n fois, au plus tt ; {n,m}? entre n et m fois, au plus tt.

Lexemple prcdent, corrig pour devenir non avide, devient /<.+?>/. Il est aussi possible dutiliser une classe qui exclut le caractre terminal, donc ici /<[^>]+>/. Il faut se souvenir que l, plus encore quavant, le motif va terminer ds quune correspondance est trouve.

Quantieurs possessifs

115

" aaaa " =~ / a + ? / ; # correspond avec " a" " a a a a b b b b " =~ / a +? b * ? / ; # c o r r e s p o n d a v e c " a " " a a a a b b b b " =~ / a +? b + ? / ; # c o r r e s p o n d a v e c # " aaaab "

Quantieurs possessifs
x*? x+? x*?? x{n}? x{n,m}?

Pour rpondre des cas assez spciques, il existe encore une autre variation des quantieurs qui fonctionnent de manire semblable aux quantieurs avides mais neffectuent jamais de retour arrire dans la recherche de correspondance. On parle de quantieurs possessifs, ou encore de groupement atomique :
q q q q q q

*+ zro ou plusieurs fois, et ne rend jamais, ++ une ou plusieurs fois, et ne rend jamais, ?+ zro ou une fois, et ne rend jamais, {n}+ exactement n fois, et ne rend jamais, {n,}+ au moins n fois, et ne rend jamais, {n,m}+ entre n et m fois, et ne rend jamais.

Voici un petit exemple qui permet de comprendre les diffrences entre les diffrents types de quantieurs :
# a v i d e : / a +/ c o r r e s p o n d a v e c " aaa " " a a a a " =~ / a + a /; # non a v i d e : / a +?/ c o r r e s p o n d a v e c " a " " a a a a " =~ / a +? a /; # p o s s e s s i f : / a ++/ ne c o r r e s p o n d pas " a a a a " =~ / a ++ a /;

116

CHAPITRE 5 Expressions rgulires

/a++/ ne correspond pas car le motif essaye dabord de raliser une correspondance avec les quatre caractres a,

mais le dernier est ncessaire pour correspondre avec le second caractre atome a du motif. Do chec du motif, et comme il sagit dun quantieur possessif, il ny a pas de tentative de retour arrire, comme le fait le quantieur avide qui revient dune position en arrire, essaye de correspondre avec trois caractres a et russit.

Capturer du texte avec les groupes capturants


(. . .)

Les motifs permettent de rechercher des donnes au sein dun texte. Cest un bon dbut, mais en ltat, difcile den faire quelque chose de vritablement utile. . . En particulier, il manque un moyen pour rcuprer les donnes intressantes dans une variable, voire dans plusieurs variables diffrentes quand le motif permet dextraire la structure des donnes du texte. Les groupes capturants rpondent ce problme. Leur principale fonction est de pouvoir crer des lments complexes partir de plusieurs atomes. Leur seconde fonction est, par dfaut, de capturer le texte qui a correspondu au sous-motif du groupe. La syntaxe est trs simple, il suft de mettre un sous-motif entre parenthses (. . .) pour crer un groupe capturant.
" a b c d e f g h " =~ / b ( cd ) e /; # c a p t u r e " cd " " a b c d e f g h " =~ /\ w (\ w +) \ w /; # c a p t u r e " b c d e f g "

Pour se rfrer au texte captur, il suft dutiliser les formes \1, \2, \3. . ., nommes rfrences arrires au sein mme de

Capturer du texte avec les groupes capturants

117

lexpression, et les variables $1, $2, $3, etc. lextrieur.


# r e c h e r c h e d un mot r p t if ( $ s t r i n g =~ /(\ w +) + \ 1 / ) { p r i n t " mot r p t : $1 " ; }

Noter que ces dernires ne sont dnies que dans le bloc courant et sont videmment rinitialises ds lvaluation suivante de la mme expression ou dune autre expression. La dure de vie de ces variables tant donc assez courte, il est prfrable de rapidement copier leurs valeurs dans des variables soi.
# e x e m p l e de l e c t u r e d un f i c h i e r # de c o n f i g u r a t i o n w h i l e ( $ l i n e = < $fh >) { if ( $ l i n e =~ / ^ ( \ w +) \ s *=\ s * ( . * ) $ /) { $ p a r a m { $1 } = $2 ; } }

Quand cest possible, il est prfrable daffecter directement des variables destination par une assignation de liste :
w h i l e ( $ l i n e = < $fh >) { if ( my ( $name , $ v a l u e ) = $ l i n e =~ / ^ ( \ w +) \ s *=\ s * ( . * ) $ /) { $param { $name } = $value ; } }

La numrotation des captures se fait logiquement de gauche droite, en comptant les parenthses ouvrantes, y compris pour les groupes imbriqus :
" a b c d e f g h " =~ /( b ( cd ) e ) .+( g ) /; p r i n t " $1 : $2 : $3 " ; # a f f i c h e " b c d e : cd : g "

118

CHAPITRE 5 Expressions rgulires

Comme lutilisation des rfrences arrires numrotes posait des problmes de syntaxe, tant au niveau de la cohrence que de la lisibilit, une nouvelle syntaxe a t introduite avec Perl 5.10, plus souple et sans ambigut : \g{N}, o N peut tre un nombre positif, auquel cas cela correspond au numro habituel (absolu) de la capture, ou ngatif, qui permet un rfrencement arrire relatif. Car cest un des gros problme de la syntaxe classique des rfrences arrires, elles sont absolues, ce qui complique la construction dexpressions rgulires complexes partir de morceaux spars, si ceux-ci veulent utiliser des rfrences arrires, puisquil faut alors connatre le nombre global de captures. La nouvelle syntaxe permet de rcuprer le texte captur de manire relative, permettant chaque morceau de rester indpendant. Lexemple prcdent de recherche de mot rpt devient alors :
# r e c h e r c h e d un mot r p t if ( $ s t r i n g =~ /(\ w +) +\ g { -1}/) { p r i n t " mot r p t : $1 " ; }

Mieux, il est aussi possible de nommer les captures, avec la syntaxe (?<nom>motif) et de sy rfrer avec \g{nom} ou \k<nom>. Les syntaxes PCRE et Python sont aussi supportes pour rendre plus aise la rutilisation dexpressions rgulires venant dhorizons varis. Ceci facilite encore la modularisation sous forme de morceaux contenant des motifs spcialiss :
my $ f i n d _ d u p = qr { (? < d u p _ w o r d >\ w +) \ s + \ g { dup_word } }x;

Pour rcuprer les valeurs correspondantes en dehors de lexpression, il faut utiliser les variables lexicales %+ et %- : $+{nom} fournit la mme valeur que \g{nom}, et $-{nom} la rfrence vers un tableau de toutes les captures de ce

Grouper sans capturer avec les groupes non capturants

119

nom. Les noms de ces hashs, certes courts mais aussi peu explicites, peuvent tre remplacs par des noms plus clairs, grce au module Tie::Hash::NamedCapture :
use Tie :: H a s h :: N a m e d C a p t u r e ; tie my % l a s t _ m a t c h , " Tie :: H a s h :: N a m e d Capture "; # % l a s t _ m a t c h se c o m p o r t e m a i n t e n a n t # c o m m e %+ tie my % r e g _ b u f f e r , " Tie :: H a s h :: N a m e d C a p t u r e " , all = > 1; # % r e g _ b u f f e r se c o m p o r t e m a i n t e n a n t # comme %-

Bien sr, les captures nommes restent aussi accessibles par leur numro.

Grouper sans capturer avec les groupes non capturants


(?:. . .)

Lutilisation de groupes capturants est trs pratique, mais impose aussi un cot non ngligeable tant donn quil faut ncessairement mettre en place une mcanique interne pour rcuprer et stocker le texte qui a correspondu. Cest pourquoi il est aussi possible de crer des groupes non capturants avec la syntaxe (?:. . .). Cet exemple illustre lintrt de pouvoir grouper sans capturer :
# r e c h e r c h e s i m p l e d une a d r e s s e I P v 4 /( ( ? : \ d + \ . ) {3} \ d + ) / x ;

120

CHAPITRE 5 Expressions rgulires

Le groupe non capturant permet de crer un sous-motif attendant plusieurs chiffres suivi dun point, sous-motif rpt trois fois an de correspondre aux trois premires composantes de ladresse IP, suivi enn de la dernire composante. Les composantes individuelles ne sont pas intressantes ici, car cest ladresse IPv4 dans son entier qui est voulue, do le groupe capturant qui englobe le tout. Point intressant, il est possible de positionner des modicateurs locaux au seul groupe :
# d s a c t i v a t i o n l o c a l e de / x m{ ... # e x p r e s s i o n r g u l i r e c o m p l e x e ... # qui ne s e r a pas d t a i l l e ici (? - x : . . . ) # une l i g n e o / x # n est pas d s i r ... # et ici , r e t o u r au m o d e / x }x;

Il est aussi possible de dsactiver un modicateur en le faisant prcder par le signe moins (-), ainsi que de grouper plusieurs modicateurs :
# a c t i v a t i o n l o c a l e de / s et d s a c t i v a t i o n # de / i m /(? s - i : . . . ) ../ i ;

Il ne faut donc pas hsiter utiliser les groupes non capturants, leur cot lexcution tant ngligeable.

Dnir des alternatives


(..|..) (? |..|..)

Dnir des alternatives

121

Dernier point important de la syntaxe des expressions qui na pas encore t abord, les alternatives, qui consistent crer des branches de correspondance possible au sein de lexpression en les sparant avec le caractre barre verticale |. Dtail qui a son importance, les branches stendent le plus possible, jusquaux bords de lexpression rgulire ou du groupe qui lenglobe. Ainsi /^krack|clonk$/ doit se comprendre comme chercher krack en dbut de ligne, ou clonk en n de ligne . Pour obtenir un comportement du type chercher un mot unique sur la ligne qui est soit krack, soit clonk , il faut placer lalternative dans un groupe : /^(?:krack|clonk)$/ Par exemple, une expression pour commencer un dbut de support des arguments de la commande Linux ip serait :
/^( a d d r (?: ess ) ?| r o u t e | r u l e | t a b l e ) +( add | del | l i s t ) /

lexcution, les alternatives sont essayes dans lordre, de gauche droite. La premire qui russit valide lalternative. Un problme survient lors de lutilisation des alternatives quand les branches elles-mmes contiennent des captures. Comme expliqu dans la partie sur les captures, cellesci sont numrotes en comptant la parenthse ouvrante de chaque groupe, en partant de la gauche. Pour des cas un peu complexes, cela peut donner un rsultat assez curieux :
/ ( a ) # 1 (?: x ( y ) z | ( p ( q ) r ) | ( t ) u ( v ) ) 2 3 4 5 6 (z) /x 7

122

CHAPITRE 5 Expressions rgulires

Suivant la branche qui aura correspondu, il faudra regarder des variables diffrentes : $2 pour la premire, $3 et $4 pour la seconde, $5 et $6 pour la troisime. Pas forcment trs pratique. Cest pourquoi une nouveaut a t introduite en Perl 5.10, la remise zro de branche (branch reset), un nouveau type de groupe non capturant qui se dclare avec la syntaxe (?|..). Les captures de chaque branche de lalternative sont alors numrotes comme sil ny avait quune seule branche :
# avant / ( a ) # 1 - - - r e m i s e z r o de b r a n c h e - (?| x ( y ) z | ( p ( q ) r ) | ( t ) u ( v ) ) 2 2 3 2 3 aprs (z) /x 4

Lintrt est quil suft maintenant de regarder la valeur dune seule variable, $2, pour savoir quelle branche a correspondu. Bien sr, il est aussi possible de rsoudre ce genre de problmes avec des captures nommes la place, mais avec linconvnient dune expression plus verbeuse. Cest mme probablement prfrable dans de nombreux cas tant donn que des champs nomms sont plus faciles comprendre quand il sagit de relire le code que des champs numrots.

Dcouper une chane avec split


split(/MOTIF/, CHAINE, LIMITE)

La fonction split permet de sparer une chane en plusieurs lments, dlimits par le motif fourni en premier argument. Ainsi, pour rcuprer les champs des lignes du chier /etc/passwd, spars par des caractres deux-points :

Dcouper une chane avec split

123

my @ f i e l d s = s p l i t /:/ , $ l i n e ;

Les ventuels champs vides intermdiaires sont conservs, mais les derniers sont limins. Si la limite est spcie (sous forme dun nombre positif), cela reprsente le nombre maximum dlments dans lesquels se dcoupe la chane :
# les 4 p r e m i e r s champs , p u i s le r e s t e # de la l i g n e my @ f i e l d s = s p l i t /:/ , $line , 5;

Plus intressant, split fonctionne de manire naturelle :


my ( $login , $ p a s s w o r d ) = s p l i t /:/ , $ l i n e ;

Dans un tel cas, inutile dindiquer la limite car split dtecte quil sagit dune assignation une liste de variables, et positionne automatiquement la limite au nombre de variables plus un, an de jeter le reste non utilis. Si le comportement contraire est souhait, il suft de spcier la limite :
my ( $login , $ p a s s w o r d , $ r e s t ) = s p l i t /:/ , $line , 3;

Le motif peut bien sr faire plus dun caractre de long :


# m o y e n t r s r a p i d e de l i r e un f i c h i e r de # c o n f i g u r a t i o n de t y p e cl = v a l e u r et de # s t o c k e r ses p a r a m t r e s d a n s un h a s h c h o m p ( my @ l i n e s = g r e p { !/^ $ |^\ s * # / } < $ c o n f i g _ f h >) ; my % c o n f i g = map { s p l i t /\ s *=\ s */ , $_ , 2 } @lines ;

124

CHAPITRE 5 Expressions rgulires

Si le motif contient des captures, des champs supplmentaires seront crs pour chacune, avec la valeur en cas de correspondance, undef sinon. Il est possible de fournir la chane (une seule espace) la place du motif, qui se comporte comme le motif /\s+/ sauf que les ventuels lments vides en dbut de la liste rsultat sont supprims, an dmuler le comportement par dfaut de awk. Si aucune chane nest fournie en argument, split travaillera sur $_. Si ni la chane, ni le motif ne sont fournis, donc si on appelle split sans aucun argument, cest quivalent split( , $_).

Utiliser Regexp::Common
use Regexp::Common

Regexp::Common est un des module CPAN assistant les uti-

lisateurs dans la manipulation dexpressions rgulires. Originellement crit par Damian Conway, il fait ofce de bibliothque dexpressions rgulires rpondant des besoins courants. Son interface principale est un peu baroque et passe principalement par un hash magique %RE. Ainsi $RE{num}{real} est lexpression pour tester si un nombre est un rel, et $RE{net}{IPv4} celle pour tester si la chane contient bien une adresse IPv4. Les expressions sont implmentes sous forme de modules, ce qui permet de facilement tendre Regexp::Common avec ses propres expressions. Ce qui rend Regexp::Common plus intressant est que ces expressions sont paramtrables et acceptent des options. Deux sont communes toutes, -keep pour raliser des

Utiliser Regexp::Common

125

captures et -i pour travailler de manire insensible la casse.


use R e g e x p :: C o m m o n ; if ( $ s t r i n g =~ $RE { num }{ int }{ - k e e p }) { $ n u m b e r = $1 ; $sign = $2 ; }

Les autres options sont spciques chaque expression. Par exemple le module number supporte une option -base pour indiquer la base numrique des nombres :
# n o m b r e s r e l s en b i n a i r e $RE { num }{ r e a l }{ - b a s e = > 2} # n o m b r e s e n t i e r en b a s e 5 $RE { num }{ int }{ - b a s e = > 5}

et il supporte mme les nombres romains avec $RE{num}{roman}. Lintrt de Regexp::Common est dune part de fournir des expressions rgulires correctes pour des besoins courants :
given ( $host ) { when (" localhost " ) { say " a d r e s s e l o c a l e " } w h e n (/ $RE { net }{ I P v 4 }/ ) { say " a d r e s s e I P v 4 " } w h e n (/ $RE { net }{ d o m a i n }/) { say " nom d h t e " } d e f a u l t { say " t y p e d a r g u m e n t i n c o n n u " } }

mais aussi des expressions pour des besoins moins courants et sur lesquels il est assez facile de se tromper, comme lextraction de chanes encadres par des guillemets. Le dernier point noter avec Regexp::Common est que sil peut sembler trop gros charger pour certains usages, il est parfaitement possible de sauver lexpression rgulire dsire quelque part pour sen servir de manire autonome.

126

CHAPITRE 5 Expressions rgulires

Utiliser Regexp::Assemble
use Regexp::Assemble

Regexp::Assemble est un module crit par David Land-

gren pour raliser lassemblage de sous-motifs an de crer une grande expression rgulire partiellement optimise. Il lavait crit lorigine pour assembler des adresses dmetteurs de pourriel et fournir ainsi une seule grosse expression Postx, qui supporte les expressions rgulires grce PCRE. Ainsi, partir dun grand nombre dadresses comme celles-ci,
64-80-231-201.bertel.com.ar host-67-84-230-24.midco.net host-89-229-2-176.torun.mm.pl host-213-213-233-44.brutele.be ppp-58.10.219.243.revip2.asianet.co.th 68-67-200-36.atlaga.adelphia.net

il est facile den dduire des motifs simples qui y correspondent, ainsi quaux adresses voisines :
\ d + -\ d + -\ d + -\ d +\. f i b e r t e l \. com \. ar host -\ d + -\ d + -\ d + -\ d +\. m i d c o \. net host -\ d + -\ d + -\ d + -\ d +\. t o r u n \. mm \. pl host -\ d + -\ d + -\ d + -\ d +\. b r u t e l e \. be ppp -\ d + \ . \ d + \ . \ d + \ . \ d +\. r e v i p \ d +\. a s i a n e t \. co \. th \ d + -\ d + -\ d + -\ d +\. a t l a g a \. a d e l p h i a \. net

Regexp::Assemble permet de runir tous ces motifs, mais

surtout le fait de manire intelligente en ralisant des groupements de prxes partout o cest possible :

Utiliser Regexp::Assemble

127

(?: host -\ d + -\ d + -\ d + -\ d +\. (?: t o r u n \. mm \. pl | b r u t e l e \. be | m i d c o \. net ) |\ d + -\ d + -\ d + -\ d +\. (?: a t l a g a \. a d e l p h i a \. net | f i b e r t e l \. com \. ar ) | ppp -\ d + \ . \ d + \ . \ d + \ . \ d +\. r e v i p \ d +\. a s i a n e t \. co \. th )

Pour prendre un exemple plus court et plus simple :


use R e g e x p :: A s s e m b l e ; my $ra = R e g e x p :: A s s e m b l e - > new ; $ra - > add ( " cra + ck " , " cru + nch " , " clu + n + ck " ) ; p r i n t $ra - > re ; # a f f i c h e "(? - x i s m : c (?: r (?: u + nch | a + ck ) | lu + n + ck ) ) "

Il faut noter que Regexp::Assemble ne peut vritablement assembler que des motifs comprenant des atomes simples, donc sans groupes internes. Mais mme ainsi, il permet des gains sensibles de performance, et propose aussi de trs intressantes options pour par exemple suivre les branches qui tablissent les correspondances, ce qui constitue une outil de dboggage trs utile.

128

CHAPITRE 5 Expressions rgulires

Utiliser Text::Match::FastAlternatives
use Text::Match::FastAlternatives

Text::Match::FastAlternatives est un module crit par

Aaron Crane alors quil travaillait pour le site The Register, o il maintenait un outil danalyse de logs de plusieurs dizaines de gigaoctets, do des besoins de performances assez importants. Ce module est fondamentalement un moyen davoir lquivalent dun idiome assez courant dans ce cas, qui est de crer une liste dalternatives ainsi :
my $re = j o i n " | " , @ w o r d s ;

Cest facile crire, mais peu performant, et de plus cela nutilise pas vraiment les expressions rgulires. Text::Match::FastAlternatives fournit un moyen de chercher la prsence dune sous-chane dans un texte, et ce bien plus efcacement puisquil est environ 90 fois plus rapide que lexpression rgulire quivalente.

Utiliser YAPE::Regex::Explain
use YAPE::Regex::Explain

YAPE::Regex::Explain peut savrer utile pour qui a en-

core un peu de mal avec la syntaxe des expressions rgulires, car ce module permet dexpliquer une expression en termes clairs, malheureusement seulement en anglais. Ainsi le code suivant :

Utiliser YAPE::Regex::Explain

129

use Y A P E :: R e g e x :: E x p l a i n ; my $re = qr / ^ ( \ w +) \ s *=\ s * ( . * ) $ /; my $ex = Y A P E :: R e g e x :: E x p l a i n - > new ( $re ) ; p r i n t $ex - > e x p l a i n ;

afche cette explication, assez verbeuse :


The regular expression: (?-imsx:^(\w+)\s*=\s*(.*)$) matches as follows: NODE EXPLANATION --------------------------------------------------------(?-imsx: group, but do not capture (case-sensitive) (with ^ and $ matching normally) (with . not matching \n) (matching whitespace and # normally): --------------------------------------------------------^ the beginning of the string --------------------------------------------------------( group and capture to \1: --------------------------------------------------------\w+ word characters (a-z, A-Z, 0-9, _) (1 or more times (matching the most amount possible)) --------------------------------------------------------) end of \1 --------------------------------------------------------\s* whitespace (\n, \r, \t, \f, and " ") (0 or more times (matching the most amount possible)) --------------------------------------------------------= = --------------------------------------------------------\s* whitespace (\n, \r, \t, \f, and " ") (0 or more times (matching the most amount possible)) --------------------------------------------------------( group and capture to \2: --------------------------------------------------------.* any character except \n (0 or more times (matching the most amount possible)) --------------------------------------------------------) end of \2 ---------------------------------------------------------

130

CHAPITRE 5 Expressions rgulires

before an optional \n, and the end of the string --------------------------------------------------------) end of grouping ---------------------------------------------------------

Il ne gre par ailleurs quune partie rduite de la syntaxe, et les ajouts rcents comme les quantieurs possessifs ne sont pas pris en compte. Autres rfrences
Seules les bases des expressions rgulires ont t abordes ici. Si celles-ci sont relativement simples apprhender, elles constituent pourtant un domaine vritablement complexe matriser dans ses dtails. Le lecteur dsireux de mieux approfondir ses connaissances pourra se rfrer la page du manuel Perl consacre aux expressions rgulires, perlre, lisible en ligne ladresse http://perldoc.perl.org/perlre.html, en anglais et un peu aride, mais jour avec les dernires nouveauts. Par ailleurs, le livre Mastering Regular Expression, 2nd Ed de Jeffrey Friedl (OReilly 2002, ISBN 0-596-00289-0) reste une rfrence en la matire, abordant lensemble du domaine li aux expressions rgulires, et dcrivant les diffrentes implmentations, celle de Perl bien sr mais aussi celle de PCRE, utilise par de trs nombreux autres langages et programmes, ou encore celle de Java. Dans la mme collection que le prsent ouvrage, existe galement le Guide de Survie Expressions rgulires de Bernard Desgraupes (Pearson, ISBN 978-2-7440-2253-1).

6
Concepts objet en Perl
Perl propose un grand nombre de possibilits pour crire du code orient objet. De la syntaxe de base faisant partie intgrante du langage aux nombreux modules plus volus disponibles sur CPAN, il ntait pas forcment facile de savoir quelle voie suivre. . . Survint alors Perl 6, qui a commenc poser les bases de la programmation objet que supporterait le langage, en ne prenant que le meilleur des diffrents langages de programmation existants. Ce nouvel ensemble forme un tout cohrent, qui a t port sous forme de module Perl 5 : Moose tait n. Il sest depuis impos comme la manire moderne de programmer en objet avec Perl. Avant de plonger dans Moose, il est toutefois bon de poser quelques principes sur le concept dobjet en Perl. En effet, Moose sappuie sur les mcanismes de base prsents dans le langage pour les enrichir. Ce chapitre mentionnera uniquement les concepts et syntaxes de base. Il ne parlera pas des notions dobjet bas niveau Perl qui ont t supplants par Moose.

Partie II Objet moderne

132

CHAPITRE 6 Concepts objet en Perl

Crer un objet
Package->new( . . .)

Les classes fournissent un constructeur, souvent appel new()1 . Un constructeur est simplement une mthode renvoyant une variable lie donc un objet :
use P e r s o n n e ; my $ o b j = P e r s o n n e - > new ;

Dans cet exemple, $obj est un objet de classe Personne. Bien sr, il ne faut pas oublier de charger la classe avant de lutiliser. Quest-ce quun objet ?
En Perl, un objet est tout simplement une variable scalaire contenant une rfrence, avec une particularit : cette rfrence sait quelle est lie2 un package donn.

Connatre la classe dun objet


ref( . . .)

La fonction ref() utilise avec un objet permet de rcuprer le package li cet objet :
1. Voir page 135. 2. La liaison avec le package se fait en interne avec la fonction bless, qui ne sera pas aborde ici.

Dnir une mthode

133

my $ o b j = P e r s o n n e - > new ; say ref ( $ o b j ) ; # affiche " Personne "

La fonction ref() renverra dans ce cas la chane Personne.

Appeler une mthode


->

Les mthodes sont appeles en utilisant loprateur -> sur lobjet, suivi de lappel de la mthode avec ses paramtres :
$obj - > s a l u e r ( m a t i n ) ;

Classes et mthodes
Comme un objet est un scalaire li un package, une classe est tout simplement ledit package associ. Cest un package standard. Les fonctions dnies dans ce package deviennent des mthodes.

Dnir une mthode


sub { }

Une mthode tant simplement une fonction du package, elle se dnit avec le mot-cl sub, dj vu dans le chapitre lments du langage page 26.

134

CHAPITRE 6 Concepts objet en Perl

La mthode va recevoir lobjet en premier paramtre. Les paramtres passs lors de lappel de la mthode viennent aprs.
sub s a l u e r { # le p r e m i e r p a r a m t r e est l o b j e t my $ s e l f = s h i f t ; # les p a r a m t r e s d a p p e l v i e n n e n t aprs say " bon @_ " ; }

Dans le cas de mthodes statiques (appeles sur la classe au lieu dun objet), le premier paramtre est le nom de la classe elle-mme :
package Personne ; sub s t a t i c _ m e t h o d { my $ c l a s s = s h i f t ; say $ c l a s s ; } Personne -> static_method ; # affiche " Personne "

Certaines mthodes peuvent faire sens en tant que mthode statique ou mthode objet. Dans ce cas, il faudra diffrencier suivant le type du premier paramtre : un objet sera une rfrence, tandis que le nom de la classe sera juste une chane.
sub m e t h o d { my $ s e l f = s h i f t ; if ( ref $ s e l f ) { # le p a r a m t r e est une rf . : mthode objet ... } else {

Dnir un constructeur

135

# mthode statique ... } }

Dnir un constructeur
Une mthode spciale du package renvoie une rfrence sur un nouvel objet. Cette mthode est le constructeur de la classe. Par convention, elle est souvent appele new(). Cependant, cette mthode doit tre crite par le programmeur : pour Perl, cette mthode na rien de spcial. Il est dailleurs possible de crer plusieurs constructeurs, renvoyant chacun un objet : pour Perl, cela reste des mthodes comme les autres. Classes et attributs
Un objet Perl est souvent une rfrence de hash lie un package. De ce fait, les attributs sont souvent un couple cl/valeur de cette rfrence. sub method { my $self = shift; say $self->{attribut}; } Cependant, Perl est trs souple et permet dutiliser nimporte quelle rfrence comme objet. Moose permet de saffranchir de ces dtails dimplmentation, pour se concentrer sur le paradigme de la programmation oriente objet.

7
Moose
Perl fournit donc toutes les briques de base pour la programmation oriente objet : constructeur, attributs, mthodes, hritage, etc. Cependant, ces briques sont vraiment bas niveau. Un programmeur souhaitant utiliser un paradigme objet doit soit plonger dans ces dtails, soit mixer un ensemble de modules objet qui fournissent chacun une surcouche au systme objet de Perl. . . Surcouches qui ne sont pas toujours compatibles entre elles, encore moins conues comme un tout cohrent. Moose a donc t crit pour devenir ce tout cohrent qui manquait au monde objet de Perl. Il fournit un ensemble de mots-cls et de concepts, cachant toute la complexit des dtails dimplmentation, en laissant les auteurs se concentrer sur le problme rsoudre.

Dclarer une classe


Une classe Moose est un package, qui charge le module Moose.
package Personne ; use M o o s e ; # m a i n t e n a n t c est une c l a s s e M o o s e ! 1;

138

CHAPITRE 7 Moose

Le simple fait de charger le module Moose change lhritage de la classe : elle devient une classe drive de Moose::Object. Celle-ci fournit entre autre un constructeur new(). Moose va aussi activer automatiquement le mode strict et le mode warnings, pour encourager lcriture de code propre. Enn, parmi les autres changements visibles, le chargement de Moose va importer un certain nombre de fonctions dans le package courant, qui sont dtailles dans ce chapitre. Astuce
Lutilisation de MooseX::Singleton au lieu de Moose dans une classe change le constructeur an quil renvoie tout le temps la mme instance un singleton, autrement dit.

Dclarer un attribut
has

Moose permet bien sr de dclarer les attributs dune classe facilement avec le mot-cl has1 :
package Personne ; use M o o s e ; has nom = > ( is = > rw ) ;

Info
Les objets possdent la plupart du temps des attributs. Un attribut est une variable prive que tous les membres de la classe

1. Has veut dire possde en anglais.

Dclarer un attribut

139

possdent. Par exemple, tous les objets dune classe Personne auront un nom et une date de naissance.

Les options passes la fonction has dnissent les proprits de lattribut. Moose propose un panel complet doptions pour personnaliser lattribut selon son but. Le seul paramtre obligatoire est is, qui permet de dnir si lattribut sera accessible en lecture seule (valeur ro du paramtre) ou pourra tre modi durant la vie de lobjet (rw). Ainsi, dans lexemple prcdent, tout objet Personne aura un attribut nom optionnel qui pourra tre modi au cours de la vie de lobjet. Astuce
Si une classe dclare plusieurs attributs identiques (mis part le nom, bien sr), il est possible de les dclarer tous dun coup :
package Couleur ; use M o o s e ; has [ r o u g e , v e r t , b l e u ] = > ( is = > rw , isa = > Int ) ;

Il est possible de spcier la valeur dun attribut lors de linstanciation dun objet, en passant le nom de lattribut et sa valeur comme paramtres lors de lappel du constructeur :
my $p = P e r s o n n e - > new ( nom = > J e r o m e Q u e l i n ) ;

Et si la classe Personne avait dni deux attributs nom et prenom :


my $p = P e r s o n n e - > new ( p r e n o m = > J e r o m e , nom = > Q u e l i n );

Lordre de passage des paramtres na pas dimportance.

140

CHAPITRE 7 Moose

Accder aux objets


Ds quun attribut est dclar avec has, Moose va gnrer un accesseur pour cet attribut, du nom de cet attribut :
package Personne ; use M o o s e ; has nom = > ( is = > rw ) ; my $p = P e r s o n n e - > new ( nom = > J e r o m e ) ; say $p - > nom ; # affiche " Jerome " $p - > nom ( J e r o m e Q u e l i n ) ; say $p - > nom ; # affiche " Jerome Quelin "

La mthode est donc un mutateur, agissant comme un accesseur (getter) ou un modicateur (setter) suivant si des paramtres lui sont passs. Si lobjet est dclar en lecture seule, la mthode ne pourra pas tre utilise pour modier la valeur de lattribut :
package Personne ; use M o o s e ; has nom = > ( is = > ro ) ; my $p = P e r s o n n e - > new ; $p - > nom ( J e r o m e Q u e l i n ) ;

# invalide

Tenter dexcuter ce programme conduira lerreur :


C a n n o t a s s i g n a v a l u e to a read - o n l y a c c e s s o r at ./ t e s t l i n e 13

Modier le nom des accesseurs


reader writer

Modier le nom des accesseurs

141

Moose permet de modier le nom des accesseurs laide des paramtres reader et writer, qui changent respective-

ment le nom de laccesseur et celui du modicateur. Changer le nom dun accesseur peut se rvler utile pour changer lAPI de la classe. Par exemple, pour rendre un attribut publiquement lisible, mais uniquement modiable de faon prive :
package Personne ; use M o o s e ; has g p s _ c o o r d s = > ( is = > rw , writer => _set_gps_coords );

Info
La convention Perl consiste prxer par un _ les attributs et mthodes privs. Cela nempche pas les classes extrieures dutiliser ces attributs et mthodes, mais cest leur risque !

Cela est utile dans le cas dattributs calculs, mis jour par des mthodes de la classe. Ainsi, la classe Personne cidessus peut dnir une mthode move() qui va modier la localisation de la personne chaque appel, permettant de masquer la complexit du changement de lieu tout en permettant lutilisateur de la classe de savoir o se trouve cette personne via $p->gps_coords, Certains prfrent un style de programmation avec des mthodes spares pour les accesseurs et les modicateurs, ce qui peut aussi se faire avec le changement de nom des accesseurs :
package Personne ; use M o o s e ; has nom = > (

142

CHAPITRE 7 Moose

is = > rw , reader => get_nom , writer => set_nom , ); my $p = P e r s o n n e - > new ; $p - > s e t _ n o m ( J e r o m e Q u e l i n ) ; say $p - > g e t _ n o m ; # affiche " Jerome Quelin "

Bien sr, cela devient vite fastidieux sil faut le faire pour chaque attribut. . . Le module Moose::FollowPBP2 permet dautomatiser cette pratique :
package Personne ; use M o o s e ; use M o o s e X :: F o l l o w P B P ; has nom = > ( is = > rw ) ; my $p = P e r s o n n e - > new ; $p - > s e t _ n o m ( J e r o m e Q u e l i n ) ; say $p - > g e t _ n o m ; # affiche " Jerome Quelin "

Une autre extension de Moose, MooseX::SemiAffordanceAccessor, permet davoir les accesseurs du nom de lattribut et les modicateurs prcds de set_ :
package Personne ; use M o o s e ; use M o o s e X :: S e m i A f f o r d a n c e A c c e s s o r ; has nom = > ( is = > rw ) ; my $p = P e r s o n n e - > new ; $p - > s e t _ n o m ( J e r o m e Q u e l i n ) ; say $p - > nom ; # affiche " Jerome Quelin "

2. PBP comme Perl Best Practices, du nom du livre de Damian Conway, publi aux ditions OReilly.

Mthodes de prdicat et de suppression

143

Mthodes de prdicat et de suppression


predicate clearer

Moose permet de faire la distinction entre une valeur fausse

(au sens boolen du terme) et une valeur non dnie. Cette distinction se fait grce la dnition de mthodes de prdicat, qui vont renvoyer vrai si une valeur a bien t spcie pour cet attribut et ce, mme si la valeur spcie est fausse. Dnir une mthode de suppression permet deffacer la valeur dun attribut. Cette action est diffrente de celle qui consiste rinitialiser lattribut undef : elle change aussi la valeur de retour de la mthode prdicat.
package Personne ; use M o o s e ; has n a i s s a n c e = > ( is = > rw , # v r i f i c a t i o n de l e x i s t e n c e predicate => has_naissance , # s u p p r e s s i o n de la v a l e u r clearer => clear_naissance ); my $p = P e r s o n n e - > new ; $p - > n a i s s a n c e ; $p - > h a s _ n a i s s a n c e ; $p - > n a i s s a n c e ( u n d e f ) ; $p - > n a i s s a n c e ; $p - > h a s _ n a i s s a n c e ; $p - > c l e a r _ n a i s s a n c e ; $p - > n a i s s a n c e ; $p - > h a s _ n a i s s a n c e ;

# undef # faux

# undef # vrai

# undef # faux

144

CHAPITRE 7 Moose

$p - > n a i s s a n c e ( 1 9 7 6 / 1 0 / 1 8 ) ; $p - > n a i s s a n c e ; # "1976/10/18" $p - > h a s _ n a i s s a n c e ; # vrai my $ e p o c h = P e r s o n n e - > new ( n a i s s a n c e = > 1 9 7 0 / 0 1 / 0 1 ) ; $epoch - > h a s _ n a i s s a n c e ; # vrai

Ce besoin nest cependant pas trs rpandu, Moose ne cre donc pas ces mthodes automatiquement. Il faudra spcier les paramtres predicate et clearer lors de lappel de has pour que Moose gnre ces mthodes.

Rendre un attribut obligatoire


required

Par dfaut, les attributs sont optionnels : ils ne sont pas obligatoirement fournis lors de lappel au constructeur.
Moose peut cependant rendre un attribut obligatoire grce au paramtre required lors de lappel de has :
package Personne ; use M o o s e ; has nom = > ( is = > ro , r e q u i r e d = > 1 ) ; my $p1 = P e r s o n n e - > new ; # erreur my $p2 = P e r s o n n e - > new ( nom = > J e r o m e Quelin ); # valide

Attention
Passer une valeur undef lors du constructeur est tout fait possible. De mme, spcier une mthode de suppression (cf. cidessus) pour cet attribut est aussi valide, mme si cela na gure de sens. . .

Donner une valeur par dfaut

145

Vrier le type dun attribut


isa

Heureusement, Moose possde un solide systme de typage (voir Chapitre 8) permettant de spcier les valeurs quun attribut peut prendre. Le type dun attribut se dnit via le paramtre isa3 .
package Personne ; use M o o s e ; has nom = > ( is = > ro , r e q u i r e d = > 1 , isa = > Str ) ;

Ainsi, il nest plus possible de passer une valeur undef pour le nom lors de lappel du constructeur : Moose lancera une exception qui arrtera le programme si elle nest pas traite.

Donner une valeur par dfaut


default

Il est courant de proposer des valeurs par dfaut aux attributs, ce qui est support par Moose :
package Personne ; use M o o s e ; has p a y s = > ( is = > rw , isa = > Str , r e q u i r e d = > 1, default => France );

3. Se lisant is a, ou est un.

146

CHAPITRE 7 Moose

# valide , m a l g r le r e q u i r e d my $p = P e r s o n n e - > new ; # affiche " France " say $p - > p a y s ;

Si lappel au constructeur ne contient pas dattribut pays, celui-ci sera initialis France. Si lattribut avait un prdicat, celui-ci renverra vrai (cest--dire que lattribut a une valeur). Ainsi, il nest plus ncessaire de spcier largument required pour les attributs proposant une valeur par dfaut. Il est aussi possible de fournir une rfrence une fonction comme valeur par dfaut. Dans ce cas, elle sera appele par Moose comme une mthode sur lobjet nouvellement cr :
package Personne ; use M o o s e ; has n u m e r o _ f e t i c h e = > ( is = > rw , isa = > Str , d e f a u l t = > sub { my @ n b s = ( 1 . . 9 ) ; r e t u r n $ n b s [ r a n d @ n b s ]; }, );

Utiliser une fonction de rappel est aussi ncessaire pour fournir une valeur par dfaut aux rfrences. En effet, sans cette prcaution, Perl ninitialiserait quune seule rfrence et lutiliserait comme valeur par dfaut pour tous les objets. . . Et ainsi tous les attributs des objets pointeraient vers la mme valeur ! Moose dtecte donc ce problme et interdit lutilisation dune rfrence comme valeur par dfaut, en forant lutilisation dune fonction de rappel :

Construire un attribut

147

package Personne ; use M o o s e ; has l i s t e _ c o u r s e s = > ( is = > rw , isa = > A r r a y R e f , # c r a t i o n d une l i s t e u n i q u e d e f a u l t = > sub { [] } , );

Ce nest pas trs lgant, mais cest d la smantique Perl elle-mme.

Construire un attribut
builder

Plutt quutiliser une fonction de rappel, Moose permet dutiliser une mthode constructeur dattribut, grce au paramtre builder :
package Personne ; use M o o s e ; has n u m e r o _ f e t i c h e = > ( is = > rw , isa = > Str , builder => _build_numero_fetiche , ); sub _ b u i l d _ n u m e r o _ f e t i c h e { my @ n b s = ( 1 . . 9 ) ; r e t u r n $ n b s [ r a n d @ n b s ]; }

Outre le gain vident de lisibilit d la diffrentiation des dclarations et du code, cette notation a deux grands avantages. Tout dabord, elle permet facilement aux classes lles de modier cette valeur par dfaut, en surchargeant cette mthode (voir la section sur lhritage page 154) :

148

CHAPITRE 7 Moose

package Enfant ; use M o o s e ; extends Personne ; sub _ b u i l d _ n u m e r o _ f e t i c h e { 7 }

Ensuite, cette mthode permet dutiliser des rles. Un rle peut donc fournir un attribut, mais requrir que la classe fournisse le constructeur (voir page 156).

Rendre un attribut paresseux


lazy

Une classe peut dnir un grand nombre dattributs, chacun avec son constructeur. Cependant, si certains attributs sont rarement utiliss, cela implique que de la mmoire sera mobilise pour eux aussi, jusqu la destruction de lobjet. Sans compter le temps processeur utilis pour gnrer ces attributs qui, mme sil est drisoire, peut sadditionner dans le cas de millions dobjets avec des dizaines dattributs.
Moose permet donc de dnir des attributs paresseux ,

dont le constructeur ne sera appel que lorsque laccesseur sera appel. Cela se fait grce la proprit lazy :
package Personne ; use M o o s e ; has n o _ s e c u r i t e _ s o c i a l e = > ( is = > rw , lazy => 1, builder => _build_no_securite_sociale , );

Rendre un attribut paresseux

149

Les attributs paresseux ont aussi une autre utilit : si un attribut a une valeur par dfaut dpendant dautres attributs, cet attribut doit tre paresseux. En effet, Moose ne garantit pas lordre de gnration des valeurs par dfaut : il nest donc pas possible de compter sur la valeur dun autre attribut. . . sauf utiliser un attribut paresseux :
package Personne ; use M o o s e ; has p a y s = > ( is = > rw , isa = > Str , default => France ); has b o i s s o n = > ( is = > rw , isa = > Str , l a z y = > 1 , # p e r m e t de d i f f r e r l a p p e l builder => _build_boisson , ); sub _ b u i l d _ b o i s s o n { my $ s e l f = s h i f t ; r e t u r n $self - > p a y s eq F r a n c e ? vin : bire ; } my $p = P e r s o n n e - > new ; say $p - > b o i s s o n ; # a f f i c h e vin

Astuce
Il est recommand de rendre paresseux tous les attributs ayant un constructeur (ou une valeur par dfaut gnre de manire un tant soit peu complexe). Cela est dailleurs facilit par Moose avec le paramtre lazy_build :
has b o i s s o n = > ( is = > rw , l a z y _ b u i l d = >1 ) ; # quivalent : # has b o i s s o n = > ( # is = > rw , # lazy => 1, # builder => _build_boisson , # clearer => clear_boisson , # predicate => has_boisson , # );

150

CHAPITRE 7 Moose

Pour les attributs dits privs4 , les mthodes gnres le seront aussi :
has _ b o i s s o n = > ( is = > rw , l a z y _ b u i l d = >1 ) ; # quivalent : # has b o i s s o n = > ( # is = > rw , # lazy => 1, # builder => _build__boisson , # clearer => _clear_boisson , # predicate => _has_boisson , # );

Notez le double _ _ dans le nom du builder.

Spcier un dclencheur
trigger

Les dclencheurs (ou triggers en anglais) comptent parmi les fonctionnalits avances de Moose. Ils permettent de spcier une fonction de rappel qui sera appele lorsquun attribut est modi :
package Personne ; use M o o s e ; has p a y s = > ( is = > rw , t r i g g e r = > \& _ s e t _ p a y s , ); sub _ s e t _ p a y s { my ( $self , $new , $ o l d ) = @_ ; my $ m s g = " d m n a g e m e n t " ; $ m s g .= " de $ o l d " if @_ > 2; $ m s g .= " v e r s $ n e w " ;

4. Commenant par un _.

Drfrencer un attribut

151

say $ m s g ; } my $p = P e r s o n n e - > new ; $p - > p a y s ( F r a n c e ) ; # affiche " dmnagement vers France " $p - > p a y s ( USA ) ; # a f f i c h e " d m n a g e m e n t de F r a n c e v e r s USA "

Le dclencheur est appel comme une mthode de lobjet courant. Il reoit aussi la nouvelle valeur et lancienne (si celle-ci existait). Cela permet de diffrencier les cas o lancienne valeur nexistait pas de ceux o elle valait undef.

Drfrencer un attribut
auto_deref

Les attributs de type rfrence peuvent utiliser la proprit auto_deref pour que Moose drfrence automatiquement la valeur lors dun appel laccesseur :
package Personne ; use M o o s e ; has l i s t e _ c o u r s e s = > ( is = > rw , isa = > A r r a y R e f , d e f a u l t = > sub { [] } , ); my $p = P e r s o n n e - > new ; my @ l i s t e = $p - > l i s t e _ c o u r s e s ; # r e n v o i e une v r a i e l i s t e !

Ce mcanisme est pratique, car les attributs de type liste ou hash sont obligatoirement des rfrences. Le paramtre auto_deref permet donc de saffranchir des rfrences pour manipuler directement le type Perl.

152

CHAPITRE 7 Moose

Affaiblir un attribut rfrence


weak_ref

Il est possible de marquer un attribut de type rfrence comme tant une rfrence dite faible . Ces rfrences ne vont pas augmenter le compteur de rfrences de la variable5 et permettront donc Perl de librer la mmoire ds que possible :
package Personne ; use M o o s e ; has p a r e n t = > ( is = > rw , isa = > P e r s o n n e , w e a k _ r e f = >1 ); my $ c h i l d = P e r s o n n e - > new ; { my $ p a r e n t = P e r s o n n e - > new ; $child - > p a r e n t ( $ p a r e n t ) ; } # fin de p o r t e : $ p a r e n t est l i b r !

la sortie de la porte ci-dessus, $child->parent est vide, car $parent a t rclam par Perl. En effet, la rfrence stocke dans $child->parent tait faible et ne sufsait pas pour maintenir $parent en vie. Ce mcanisme est trs commode pour crer des structure de donnes circulaires, telles que graphes ou autres listes chanes.

Chaner les attributs


traits => [ Chained ]

5. Perl nutilise pas de ramasse-miettes en interne, mais des compteurs de rfrences pour savoir si une valeur est toujours utilise.

Simplier les dclarations dattributs

153

Lextension MooseX::ChainedAccessor permet de remanier les modicateurs pour quils renvoient lobjet. Cela permet de chaner les attributs :
package Test ; use M o o s e ; has d e b u g = > ( is = > rw , isa => Bool , traits => [ Chained ], ); sub run { ... } my $ t e s t = Test - > new ; $test - > d e b u g (1) - > run () ;

Cest particulirement intressant pour les objets ayant un grand nombre dattributs qui seront modis durant la vie de lobjet.

Simplier les dclarations dattributs


use MooseX::Has::Sugar

Lextension MooseX::Has::Sugar exporte un certain nombre de fonctions permettant de dclarer des attributs plus facilement. La dclaration suivante :
has a t t r = > ( is = > rw , r e q u i r e d = >1 , w e a k _ r e f = >1 , a u t o _ d e r e f = >1 );

est quivalente :
use M o o s e X :: Has :: S u g a r ; has a t t r = > ( rw , r e q u i r e d , w e a k _ r e f , auto_deref );

154

CHAPITRE 7 Moose

Les classes ayant un nombre dattributs important en bncieront donc grandement.

tendre une classe parente


extends

Lhritage se fait grce au mot-cl extends :


package Personne ; use M o o s e ; has nom = > ( is = > ro , isa = > Str , r e q u i r e d = >1 ) ; has p r e n o m = > ( is = > ro , isa = > Str , r e q u i r e d = >1 ) ; 1; package User ; use M o o s e ; extends Personne ; has l o g i n = > ( is = > ro , isa = > Str , r e q u i r e d = >1 ) ; 1;

Info
Une des fonctionnalits importantes de la programmation objet est lhritage. Lhritage permet dcrire une classe en tendant une classe de base, en ajoutant ce qui manque la classe parente, voire en rcrivant certains comportements qui changent.

Un objet de la classe User aura donc un nom, un prnom et un login. Il est possible de faire appel Moose pour hriter dune classe nutilisant pas Moose. Dans ce cas, la classe va hriter

Surcharger une mthode

155

dun constructeur qui ne connait pas le systme Moose : un grand nombre de raccourcis lis Moose ne fonctionneront plus, il faudra alors (entre autre) initialiser les attributs la main. En cas dhritage multiple, il faut lister les classes parentes dans le mme appel extends, sinon chaque appel extends rinitialiserait larbre dhritage de la classe.
package Carre ; use M o o s e ; extends Rectangle , Losange ; # hritage multiple 1;

Surcharger une mthode


Surcharger une mthode dans une classe lle se fait de la plus simple des manires, en crivant une nouvelle fonction du mme nom dans la sous-classe :
package Animal ; use M o o s e ; sub c r i e { say ... } package Chien ; use M o o s e ; extends Animal ; sub c r i e { say o u a h ! ; } my $c = Chien - > new ; $c - > c r i e ; # a f f i c h e " o u a h !"

Il est possible de modier plus nement le comportement des mthodes hrites avec les modicateurs de mthode (voir page 171).

156

CHAPITRE 7 Moose

Modier des attributs hrits


has +attribut => . . .

Par dfaut, une classe hrite de tous les attributs de ses parents. Il est cependant possible de modier certains aspects de ces attributs, tels que les valeurs par dfaut, la paresse de lattribut, son type, etc. Pour cela, il faut rednir lattribut avec la fonction has en le prxant avec un +, avant de modier les paramtres changer :
package Francais ; use M o o s e ; extends Personne ; has + nom = > ( d e f a u l t = > D u p o n t ) ;

Info
Il est cependant plus facile dutiliser un constructeur pour lattribut. De plus, changer les proprits dun attribut peut facilement mener des bugs subtils. Cette pratique nest donc pas utiliser la lgre.

Crer un rle
use Moose::Role

Rles
Les rles sont une alternative la hirarchie habituelle de la programmation oriente objet.

Consommer un rle

157

Un rle encapsule un ensemble de comportements qui peuvent tre partags entre diffrentes classes, potentiellement sans relation entre elles. Un rle nest pas une classe, mais un rle est consomm par une classe. En pratique, les attributs et mthodes du rle apparassent dans la classe comme si elle les avait dnis elle-mme. Une classe hritant de la classe ayant appliqu les rles hritera aussi de ces mthodes et attributs. Les rles sont peu prs lquivalent des traits de SmallTalk, des interfaces Java, ou des mixins de Ruby. En plus de dnir des mthodes et attributs, un rle peut requrir que la classe dnisse certaines mthodes.

Crer un rle se fait de la mme manire que crer une classe, mais en utilisant cette fois Moose::Role :
package AnimalDeCompagnie ; use M o o s e :: R o l e ; has nom = > ( is = > rw , isa = > Str ) ; sub c a r e s s e r { say p u r r r r ; }

Le rle AnimalDeCompagnie est un rle dnissant un attribut nom, et une mthode caresser(). Attention
Il nest pas possible dinstancier un rle.

Consommer un rle
with

Appliquer un rle une classe se fait avec la fonction with :

158

CHAPITRE 7 Moose

package Chien ; use M o o s e ; extends Mammifere ; with AnimalDeCompagnie ;

La classe Chien a donc maintenant un attribut nom, et une mthode caresser(). Il est possible de composer plusieurs rles au sein dune mme classe :
package Chien ; use M o o s e ; with AnimalDeCompagnie , GuideAveugle ;

Nous voyons bien que les rles fournissent une alternative intressante lhritage classique, car les rles AnimalDeCompagnie et GuideAveugle sont plus des fonctions que des proprits intrinsques de la classe.

Requrir une mthode


requires

Un rle peut forcer la classe qui le compose fournir une mthode. Lexemple ci-dessus fournissait la mthode caresser(), alors quil est plus judicieux de laisser la classe fournir cette mthode.
package AnimalDeCompagnie ; use M o o s e :: R o l e ; requires caresser ; has nom = > ( is = > rw , isa = > Str ) ;

Modier les paramtres du constructeur

159

Les classes Chat et Chien pourront maintenant fournir un comportement adapt lors de lappel caresser(). Une exception sera leve si la classe composant le rle ne fournit pas cette mthode. Un accesseur de la classe composant le rle peut trs bien acquitter le prrequis spci par le rle. Dans ce cas, lattribut gnrant laccesseur doit tre dni avant la composition du rle. Le rle peut aussi ajouter des modicateurs de mthodes (voir page 171) pour sassurer dun comportement ceci est une combinaison trs puissante. Le rle AnimalDeCompagnie pourrait alors augmenter un attribut contentement aprs un appel caresser().

Construire et dtruire des objets


Une classe utilisant Moose devient une classe drive de Moose::Object, qui fournit un constructeur appel new. Elle ne doit donc pas rednir ce constructeur, qui cache une bonne partie de la magie de Moose. Pour modier son comportement, Moose propose un ensemble de points dancrage. Si ces mthodes sont dnies dans la classe, elles seront appeles lors de la construction de lobjet.

Modier les paramtres du constructeur


around BUILDARGS => sub { . . .}

Moose utilise un mcanisme cl/valeur pour le passage dar-

guments. Il peut tre pratique de changer ce mcanisme, par exemple lorsque la classe na quun seul attribut.

160

CHAPITRE 7 Moose

Cela se fait en modiant la mthode BUILDARGS (voir page 171) :


package Personne ; use M o o s e ; has nom = > ( is = > ro , isa = > Str ) ; a r o u n d B U I L D A R G S = > sub { my $ o r i g = s h i f t ; my $ c l a s s = s h i f t ; r e t u r n $class - > $ o r i g ( nom = > @_ ) if @_ == 1; r e t u r n $class - > $ o r i g ( @_ ) ; }; my $p = P e r s o n n e - > new ( J e r o m e Q u e l i n ) ;

Interagir avec un objet nouvellement cr


sub BUILD { . . .}

Si une classe dnit une mthode BUILD, celle-ci sera appele aprs quun objet a t cr. Cela peut permettre de faire un certain nombre de validations sur lobjet luimme, initier un traitement sur lobjet. . . ou tout simplement enregistrer la cration dun nouvel objet :
sub B U I L D { my $ s e l f = s h i f t ; debug ( " nouvel objet cr : $self " ) ; }

Les mthodes BUILD des classes parentes sont automatiquement appeles (si elles existent), depuis la classe parente jusqu la classe lle. Cet ordre permet aux classes parentes de faire les initialisations ncessaires avant que les

Interagir lors de la destruction dun objet

161

spcicits de la classe lle ne soient prises en compte celles-ci dpendant en effet souvent de ltat de base des classes parentes.

Interagir lors de la destruction dun objet


sub DEMOLISH { . . .}

De la mme manire que BUILD est appel aprs la cration dun objet, la mthode DEMOLISH est appele avant la destruction dun objet.
sub D E M O L I S H { my $ s e l f = s h i f t ; debug ( " objet dtruit : $self " ); }

Moose appellera cette fois les mthodes DEMOLISH en par-

tant de la classe la plus profonde pour remonter dans la hirarchie, de la classe la plus spcique la plus gnrale. Cette mthode nest pas un destructeur : lobjet lui-mme sera dtruit par Perl lorsquil ne sera plus rfrenc. Cette mthode nest donc pas ncessaire la plupart du temps : elle ne doit servir que pour des nalisations externes, par exemple en cassant la connexion une base de donnes.

8
Le typage dans Moose
Les types dans Moose sont des objets. Ils dnissent un certain nombre de contraintes et peuvent tre hirarchiss. Moose dispose de types de base et il est possible de crer de nouveaux types et sous-types.

Utiliser les types de base


Bool Undef Maybe Dened

Voici les principaux types reconnus par Moose :


q

Bool : un boolen, acceptant soit la valeur 1 (vrai) ou

toute valeur que Perl traite comme faux (faux).


q q

Undef : la valeur undef. Maybe[type] : Moose acceptera soit une valeur du type donn, soit undef. Par exemple :
package Personne ; has p a r e n t = > ( is = > rw , isa = > Maybe [ Personne ] ) ;

164

CHAPITRE 8 Le typage dans Moose

Dened : toute valeur dnie (non undef, donc). Ce type possde de nombreuses sous-catgories, hirarchises : Value : une valeur. Les sous-catgories sont Str pour les chanes, Num pour tout ce que Perl accepte comme un nombre et Int pour les entiers. noter quun Int est aussi un Num, qui est aussi un Str.

ClassName ou RoleName : le nom dune classe ou dun rle (voir page 156). Dans ce cas, le type nacceptera que les objets de la classe (ou du rle) donn :
package Personne ; use M o o s e ; has p a r e n t = > ( is = > rw , isa = > Personne );

Lattribut parent nacceptera quune autre Personne comme valeur. Ref : une rfrence. Les sous-catgories sont ScalarRef, ArrayRef, HashRef, CodeRef, FileHandle, RegexpRef et Object. Les trois premiers types peuvent tre encore restreints, pour forcer un type sur les scalaires points par la rfrence. Par exemple, pour naccepter quune liste contenant des entiers :
package Loto ; use M o o s e ; has n u m r o s _ g a g n a n t s = > ( is = > rw , isa = > A r r a y R e f [ Int ] );

Le type Object comporte toutes les rfrences un objet Perl, mme si celui-ci nest pas un objet dune classe Moose.

Dnir un sous-type

165

Crer une bibliothque de types personnaliss


use Moose::Util::TypeConstraints

Il est recommand de regrouper les dnitions des types personnaliss dune application dans un module qui ne fera que cela. Cette bibliothque de types utilisera le module Moose::Util::TypeConstraints qui a deux rles : q Importation. Il importe toutes les fonctions permettant de travailler avec les types (voir ci-dessous). Exportation. Il exporte automatiquement tous les nouveaux types crs dans le module. Moose na quun seul espace de noms pour tous les types (quel que soit le module dnissant les types). Si une application dnit sa propre bibliothque de types, il lui faut donc sassurer que le nom de ses types personnaliss nentrent pas en conit avec les types dnis par dautres modules utiliss dans lapplication. Pour cela, il est conseill de prxer les types personnaliss par exemple My::App::Types::NombrePair. Dnir le type NombrePair dans le module My::App::Types nest pas la mme chose : si un autre module dnit aussi un type NombrePair, il y aura un conit de nom sur ce type.
q

Dnir un sous-type
subtype

Un sous-type est dni partir dun type parent et dune contrainte. Bien sr, pour quune valeur soit valide, elle

166

CHAPITRE 8 Le typage dans Moose

doit passer les contraintes du parent (vries en premier), ainsi que les contraintes additionnelles du sous-type. Il est aussi possible de dnir un message derreur spcique personnalis en cas de non-vrication des contraintes du sous-type.
use M o o s e :: U t i l :: T y p e C o n s t r a i n t s ; subtype NombrePair = > as Int = > w h e r e { $_ % 2 == 0 } = > m e s s a g e { " Le n o m b r e $_ n est pas p a i r . " };

Les fonctions as, where et message (et dautres encore), permettant de travailler avec les types de Moose, sont exportes par le module Moose::Util::TypeConstraints.

Dnir un nouveau type


type

Il est possible de dnir un type qui ne soit pas un soustype dun type dj existant. Cela se fait avec le mot-cl type de la mme manire quun sous-type :
use M o o s e :: U t i l :: T y p e C o n s t r a i n t s ; type UnCaractere = > w h e r e { d e f i n e d $_ && l e n g t h $_ == 1 };

Info
Il est cependant difcile de trouver un cas o la cration dun type ne puisse tre remplac par la cration dun sous-type, fut-il trs gnrique (Dened, Ref ou Object).

Dnir une union de types

167

Lexemple prcdent aurait par exemple t plus correct en tant que sous-type de Str.

Dnir une numration


enum

Moose permet la cration dun sous-type contenant un en-

semble de chanes. Ce sous-type dpend du type de base Str, ajoutant une contrainte forant la valeur avoir lune des valeurs listes (en tenant compte de la casse) :
enum LinuxDistributions = > qw { F e d o r a U b u n t u M a n d r i v a };

la diffrence dautres langages (comme le C par exemple), il ne sagit pas vraiment dun rel type numr affectant une valeur entire chaque membre de lnumration : cest juste un moyen rapide de gnrer une contrainte sur une liste de valeurs.

Dnir une union de types


|

Moose permet un attribut dtre de plusieurs types diff-

rents. Cela se fait avec une union de types :


has os = > ( is = > ro , isa = > L i n u x | BSD ) ;

Dans lexemple ci-dessus, lattribut os peut tre soit un objet dune classe Linux, soit un objet dune class BSD.

168

CHAPITRE 8 Le typage dans Moose

Cependant, dans ces cas, il peut tre prfrable dutiliser : q Soit un rle que les classes implmentent. Dans ce cas le type spcier est le nom du rle. Soit du transtypage (voir ci-dessous). Dans ce cas le type spcier est le type vers lequel les valeurs seront transtypes. Lunion de type est donc bien souvent inutile et avantageusement remplace par un autre mcanisme plus lisible.
q

Transtyper une valeur


coerce . . .from . . .via

Le transtypage consiste convertir une valeur dun type vers un autre. Cest une fonctionalit assez puissante, utiliser toutefois avec prcaution.
Moose permet de transtyper, mais il faut pour cela deux

conditions :
q

Condition 1 : le type cible doit savoir comment extraire la valeur depuis le type source. Cela se fait aprs la dnition du type, avec la fonction coerce :
subtype HexNum = > as Str = > w h e r e { /^0 x [ a - f0 - 9 ] + $ / i }; c o e r c e Int => from HexNum = > via { hex s u b s t r $_ , 2 };

Transtyper une valeur

169

Le type Int sait maintenant convertir une valeur de type HexNum en extrayant la valeur hexadcimale de la chane1 .
q

Condition 2 : lattribut doit spcier quil accepte dtre transtyp par Moose. Le transtypage est en effet une opration efcace mais qui peut tre source de bugs difciles diagnostiquer et trouver. Par dfaut, Moose refuse donc de transtyper et force lutilisateur se montrer explicite :
has i = > ( is = > ro , isa = > Int , c o e r c e = >1 ) ;

Le paramtre coerce dans la dnition de lattribut indique Moose de faire les oprations de transtypage sur cet attribut.

1. Certes, Perl reconnat dj la notation 0x... pour dnir un entier, mais ce morceau de code a valeur dexemple.

9
Moose et les mthodes
Une mthode reste une fonction du package, appele suivant les mmes conventions de Perl. Moose fournit cependant un certain nombre de fonctionnalits assez pratiques autour des mthodes.

Modier des mthodes


Les modicateurs de mthodes sont des mthodes qui seront automatiquement appeles par Moose lors de lappel de la mthode originale. Cela est bien utile, en particulier pour les mthodes hrites de classes parentes. Les sections suivantes modieront la classe suivante, qui servira de base :
package Exemple ; use M o o s e ; sub m e t h o d { say d a n s m e t h o d () ; } my $ o b j = E x e m p l e - > new ;

172

CHAPITRE 9 Moose et les mthodes

Le morceau de code cre donc une classe Exemple qui dnit une mthode method() afchant un message. Une instance $obj de la classe Exemple est alors cre.

Intercaler un prtraitement
before

Il est possible dintercaler une mthode qui sera appele avant lappel la mthode :
b e f o r e m e t h o d = > sub { say a v a n t m e t h o d () 1 ; };

Lappel la mthode via $obj->method() afchera maintenant :


avant method() 1 dans method()

La mthode appele avant est une mthode comme une autre, recevant donc lobjet en premier paramtre, suivi des paramtres dappel orginaux. Sa valeur de retour est ignore. Ce modicateur est intressant pour tendre le fonctionnement des mthodes auto-gnres par Moose comme les accesseurs ou pour faire de la validation avant dappeler la mthode :
b e f o r e m o v e = > sub { my $ s e l f = s h i f t ; die " un v h i c u l e est n c e s s a i r e p o u r se d p l a c e r " if not $self - > h a s _ v e h i c l e ; };

Intercaler un post-traitement

173

Dnir un deuxime puis dautres prtraitement est possible, ils seront dans ce cas appels dans lordre inverse de dnition :
b e f o r e m e t h o d = > sub { say a v a n t m e t h o d () 2 ; }; # $obj - > m e t h o d () a f f i c h e r a : # a v a n t m e t h o d () 2 # a v a n t m e t h o d () 1 # d a n s m e t h o d ()

Comme le modicateur de mthode est implment par une fonction, il faut bien terminer la dclaration par un point-virgule.

Intercaler un post-traitement
after

De la mme manire, Moose permet dintercaler une mthode qui sera appele juste aprs lappel cette mthode :
a f t e r m e t h o d = > sub { say a p r s m e t h o d () 1 ; };

Lafchage lors de lappel de $obj->method() devient donc :


avant method() 2 avant method() 1 dans method() aprs method() 1

Tout comme avec before, la valeur de retour de cette mthode est aussi ignore. Il est bien sr ici aussi possible dintercaler plusieurs mthodes, par symtrie, celles-ci seront alors appeles dans lordre de leur dnition :

174

CHAPITRE 9 Moose et les mthodes

a f t e r m e t h o d = > sub { say a p r s m e t h o d () 2 ; }; # $obj - > m e t h o d () a f f i c h e r a : # a v a n t m e t h o d () 2 # a v a n t m e t h o d () 1 # d a n s m e t h o d () # a p r s m e t h o d () 1 # a p r s m e t h o d () 2

Sintercaler autour dune mthode


around

Enn, Moose permet de sintercaler autour de lappel dune mthode, via la fonction around. Elle est plus puissante que les modicateurs before et after, car elle permet de modier les arguments passs la mthode, et mme de ne pas appeler la mthode originale ! Elle permet aussi de modier la valeur de retour. La mthode entourant la mthode originale reoit cette dernire en premier paramtre, puis lobjet1 et enn les paramtres passs la mthode :
a r o u n d m e t h o d = > sub { my $ o r i g = s h i f t ; # m t h o d e o r i g i n a l e my $ s e l f = s h i f t ; # o b j e t say a u t o u r de m e t h o d () 1 ; $self - > $ o r i g ( @_ ) ; # a p p e l de la m t h o d e # originale say a u t o u r de m e t h o d () 1 ; };

1. Cela est donc diffrent de lordre de passage habituel voulant que lobjet soit le premier paramtre.

Modier plusieurs mthodes

175

Les mthodes insres avec around seront appeles aprs les prtraitements et avant les post-traitements. De plus, la dnition de plusieurs modicateurs suit la mme logique que prcdemment : ordre inverse de dnition avant la mthode, ordre de dnition aprs la mthode. Lafchage devient donc :
avant method() 2 avant method() 1 autour de method() autour de method() dans method() autour de method() autour de method() aprs method() 1 aprs method() 2

2 1 1 2

Modier plusieurs mthodes


Des modicateurs identiques peuvent tre installs en une seule fois :
b e f o r e qw { m e t h o d 1 m e t h o d 2 m e t h o d 3 } = > sub { say s c a l a r l o c a l t i m e ; };

Il est aussi possible de spcier une expression rgulire qui sera vrie avec le nom de la mthode appele :
a f t e r qr /^ m e t h o d \ d$ / = > sub { say s c a l a r l o c a l t i m e ; };

Cependant, ces mthodes ne permettent pas de savoir quelle mthode est appele originellement : attention donc nutiliser cette notation que lorsque le pr/post-traitement est strictement identique quelle que soit la mthode !

176

CHAPITRE 9 Moose et les mthodes

Appeler la mthode parente


super()

Dans le monde objet, il est assez facile dcrire une classe hritant dune autre classe. Les mthodes appeles seront alors celles de la classe lle si elles sont surcharges. Moose propose deux mcanismes pour que les classes de larbre dhritage travaillent ensemble : appeler la mthode surcharge de la classe parente et augmenter la mthode (voir page 177). Le premier mcanisme est courant dans les langages supportant la programmation oriente objet. Avec Moose, cela se fait avec un appel la fonction super(). Cependant, il faut pour cela spcier que la mthode est surcharge avec intention de faire appel la classe de base, avec la fonction override :
package Base ; use M o o s e ; sub m e t h o d { say b a s e ; } p a c k a g e Sub ; use M o o s e ; extends Base ; o v e r r i d e m e t h o d = > sub { say sub ; s u p e r () ;}; Sub - > new - > m e t h o d ; # affiche : # sub # base

Comme dhabitude, la fonction override est importe par Moose. Et comme son invocation est une instruction, il faut la terminer avec un point-virgule.

Augmenter une mthode

177

La fonction super() ne prend pas darguments2 , et appelle la mthode de la classe parente avec les mmes arguments que ceux de la mthode de la classe lle mme si @_ a t modi. La fonction super() renvoie la valeur de retour de la mthode parente.

Augmenter une mthode


inner() augment()

En plus de ce fonctionnement somme toute classique dans les langages de programmation orients objet, Moose propose un mcanisme astucieux de coopration entre les classes lle et parente. Ce mcanisme nest pas courant, car cest alors la mthode parente qui va appeller la mthode lle linverse du paradigme objet habituel (voir page 175). Ainsi, la mthode de la classe parente va faire un appel la fonction inner() importe par Moose :
package Document ; use M o o s e ; sub a s _ x m l { my $ s e l f = s h i f t ; my $ x m l = " < d o c u m e n t >\ n " ; $ x m l .= i n n e r () ; # a p p e l de la m t h . f i l l e $ x m l .= " </ d o c u m e n t >\ n " ; return $xml ; }

2. Et ignorera donc ceux qui lui seront passs quand mme. . .

178

CHAPITRE 9 Moose et les mthodes

La classe lle va surcharger la mthode de la classe parente grce la fonction augment() :


package Livre ; use M o o s e ; extends Document ; a u g m e n t a s _ x m l = > sub { my $ s e l f = s h i f t ; my $ x m l = " < livre >\ n " ; # a j o u t du c o n t e n u xml , v o i r e # a p p e l i n n e r () $xml .= " </ livre >\ n " ; return $xml ; };

Appeler la mthode as_xml() sur un objet de classe Livre va donc appeler la mthode de la classe Document, qui appellera en cours de route la mthode de Livre :
say Livre - > new - > a s _ x m l ; # affiche : # < document > # < livre > # ... c o n t e n u ... # </ livre > # </ d o c u m e n t >

Il est bien sr possible de continuer appeler inner() dans la classe lle. Cest mme recommand, au cas o la classe lle soit elle-mme sous-classe dans le futur. . . Sil ny a pas de classe lle, lappel inner() ne fera rien.

Dlguer une mthode un attribut

179

Dlguer une mthode un attribut


handles => . . .

Moose permet de gnrer des mthodes dans certains cas,

an de gagner en clart ou en nombre de lignes de code maintenir. Le mcanisme employ de dlgation sapplique un attribut ou une structure (voir page 180). Le premier cas consiste crer une mthode qui va en appeler une autre sur un attribut. Cela permet de simplier lAPI de la classe, sans que les utilisateurs de la classe aient besoin de savoir comment elle est construite.
package Image ; use M o o s e ; has f i c h i e r is isa handles ); => => => => ( rw , P a t h :: C l a s s :: F i l e , [ qw { s l u r p s t r i n g i f y } ] ,

Ainsi, un objet Image correctement initialis permettra dcrire :


# c o n t e n u du f i c h i e r my $ d a t a = $image - > s l u r p ; # c h e m i n du f i c h i e r i m a g e my $ p a t h = $image - > s t r i n g i f y ;

Il est mme possible de personnaliser lAPI de la classe :


has f i c h i e r = > ( is = > rw ,

180

CHAPITRE 9 Moose et les mthodes

isa = > P a t h :: C l a s s :: F i l e , handles => { blob => slurp , path => stringify }, );

Ce qui permet davoir une classe agrable utiliser :


$image - > b l o b ; # a p p e l de $image - > f i c h i e r - > s l u r p ; $image - > p a t h ; # a p p e l de $image - > f i c h i e r - > s t r i n g i f y ;

Dlguer des mthodes une structure Perl


handles => . . .

Moose propose de gnrer un certain nombre de mthodes

pour les attributs dune classe en fonction de son type. Par exemple, il est possible de crer une mthode qui fera un push sur un attribut de type rfrence de liste :
has l i s t e _ c o u r s e s = > ( isa = > A r r a y R e f [] , traits => [ Array ], d e f a u l t = > sub { [] } , handles => { ajout => push , suivant => shift , }, );

Appeler la mthode ajout() sur un objet de la classe ajoutera un item dans la liste liste_courses. Cela se fait en dnissant un trait sur lattribut.

Dlguer des mthodes une structure Perl

181

Le tableau suivant donne la liste des traits disponibles, avec le type de lattribut sur lequel lappliquer et les mthodes que propose le trait.
Trait Type Mthodes proposes

Array

ArrayRef[]

count, is_empty, elements, get, pop, push, shift, unshift, splice, rst, grep, map, reduce, sort, sort_in_place, shufe, uniq, join, set, delete, insert, clear, accessor, natatime set, unset, toggle, not

Bool Code Counter Hash

Bool CodeRef Num HashRef[]

execute, execute_method
set, inc, dec, reset get, set, delete, keys, exists, dened, values, kv, elements, clear, count, is_empty, accessor set, add, sub, mul, div, mod, abs inc, append, prepend, replace, match, chop, chomp, clear, length, substr

Number String

Num Str

La plupart de ces mthodes sont assez faciles comprendre et reprennent les fonctions de base appliquer sur les hashs, tableaux, nombres et chanes. Pour plus dinformations sur les mthodes, se reporter la documentation des traits, Moose::Meta::Attribute::Native::Trait::Hash par exemple.

10
Fichiers et rpertoires
La gestion de chiers est centrale pour tout programme un tant soit peu consquent. Les ouvrir, les lire, les crire. . . Mais cela va plus loin, avec le parcours de rpertoires, la portabilit entre les systmes dexploitation, etc. Toutes choses dont Perl se joue avec la plus grande facilit.

Partie III Manipulation de donnes

Ouvrir des chiers


IO::File->new(..)

Toute manipulation de chier en Perl passe par un descripteur de chier. Obtenir un descripteur chier se fait avec le constructeur de IO::File :
use IO :: F i l e ; my $ p a t h = / c h e m i n / v e r s / le / f i c h i e r ; my $fh = IO :: File - > new ( $path , < ) or die " ne p e u t o u v r i r $ p a t h : $ !\ n " ;

184

CHAPITRE 10 Fichiers et rpertoires

Cette mthode accepte trois arguments : q Argument 1 : un chemin vers le chier ouvrir. Ce chemin doit suivre les canons du systme dapplication et peut tre absolu ou relatif.
q

Argument 2 : un mode douverture. IO::File propose les modes douverture lists dans le tableau suivant1 .
Tableau 10.1: Modes douverture des chiers
Mode Description Mode lecture Mode criture, crase lancien chier Mode chier criture, concatnation aprs lancien

<
> >>

Argument 3 : une indication des permissions appliquer dans le cas dune ouverture en criture dun chier qui nexiste pas.

Le constructeur renvoie un objet IO::File qui sert de descripteur de chier, ou undef en cas derreur. Dans ce cas, la variable spciale $! contient le code derreur correspondant. Comme le constructeur de IO::File renvoie undef en cas derreur, il est courant de voir un appel cette fonction suivi dun ou boolen appelant la fonction die. Outre la concision, cela permet de produire du code qui se lit de manire naturelle : ouvre ce chier ou meurt. Si les oprations faire en cas derreur sont plus consquentes, un if sera plus pertinent.
1. Les familiers du langage C peuvent aussi utiliser les modes douverture de la fonction ANSI fopen() : w, r, r+, etc.

Utiliser un descripteur de chier en lecture

185

use IO :: F i l e ; my $ p a t h = / c h e m i n / v e r s / le / f i c h i e r ; my $fh = IO :: File - > new ( $path , < ) ; if ( not d e f i n e d $fh ) { # traiter l erreur }

Utiliser un descripteur de chier en lecture


getline()

Une fois le chier ouvert, il faut bien sr pouvoir utiliser le descripteur de chier renvoy par le constructeur de IO::File. La lecture se fait avec la mthode getline(). La manire la plus simple pour lire une ligne sera donc :
my $ l i n e = $fh - > g e t l i n e ;

La mthode getline() renvoyant undef en n de chier, il est donc courant de voir pour lire un chier :
w h i l e ( d e f i n e d ( my $ l i n e = $fh - > g e t l i n e ) ) { # utiliser $line }

La mthode getlines() (noter le s) permet de lire le chier dun coup dans un tableau :
my @ l i n e s = $fh - > g e t l i n e s ;

En fait, les mthodes getline() et getlines() vont utiliser la variable $/ pour savoir ce quest une ligne. Une

186

CHAPITRE 10 Fichiers et rpertoires

ligne sarrtera aprs la premire occurrence de cette variable. Comme $/ est positionne par dfaut \n (LF, qui vaut \xA) sur les systmes de type Unix2 et \r\n (CR LF, \xD\xA) pour les systmes Windows, les mthodes getline() et getlines() renverront bien une ligne telle que le dveloppeur sy attend. Il est tout fait possible de modier cette variable. Par exemple, pour lire un chier paragraphe par paragraphe, il faut indiquer de lire jusqu rencontrer une ligne vide, cest--dire deux retours chariots la suite :
$/ = "\n\n"; w h i l e ( d e f i n e d ( my $ p a r a g r a p h = $fh - > getline ) ) { # utiliser $paragraph }

Astuce
En fait, le mode paragraphe se dnit avec :
$/ = "";

Cela permettra de lire le chier jusqu la prochaine ligne vide. La diffrence se fera en cas de plusieurs retours chariots la suite : dans ce cas, ils seront tous considrs comme une seule ligne vide alors quavec \n\n ils seront compts comme autant de nouveaux paragraphes, mme si le paragraphe ne contient quune ligne vide.

Pour lire le chier dun seul coup dans un scalaire, il suft de positionner cette variable undef :
u n d e f $ /; my $ d a t a = $fh - > g e t l i n e ;

2. Mac OS X est considr comme un Unix.

Utiliser un descripteur de chier en criture

187

Attention
La variable $/ est utilise dans de nombreux endroits. La modier nest donc pas sans impact sur le reste du code. . . ou des modules utiliss !

Il est donc prfrable de faire ce changement dans un bloc rduit, avec le mot-cl local qui remet automatiquement lancienne valeur en place la sortie de la porte.
my $ d a t a ; { # p o s i t i o n n e $ / u n d e f de m a n i r e # temporaire l o c a l $ /; $ d a t a = < $fh >; } # fin du b l o c : $ / r e p r e n d # son a n c i e n n e v a l e u r

Pour lire un chier dun bloc, le module File::Slurp facilite dailleurs la vie, en permettant dcrire :
use F i l e :: S l u r p ; my $ d a t a = r e a d _ f i l e ( / c h e m i n / v e r s / mon / fichier );

Ce qui est tout de mme plus simple !

Utiliser un descripteur de chier en criture


print(..)

Pour crire dans un chier, cest tout aussi ais : il suft dutiliser la mthode print().

188

CHAPITRE 10 Fichiers et rpertoires

$fh - > p r i n t ( " n o u v e l l e l i g n e a j o u t e \ n " ) ;

Pour tre tout fait correcte, une application se devrait de vrier le code de retour de la fonction et dagir en consquence si une erreur survient :
$fh - > p r i n t ( " n o u v e l l e l i g n e \ n " ) or die " e r r e u r l o r s de l c r i t u r e : $ ! " ;

La mthode printf() est aussi disponible pour les sorties formates. Elle prend les mmes arguments que la fonction printf dcrite dans perlfunc.

Fermer un descripteur de chier


close()

Une fois les oprations de lecture ou dcriture dans un chier termines, il faut fermer le chier. Il faut utiliser la mthode close() et vrier l aussi pour une application consciencieuse son code de retour :
$fh - > c l o s e or die " e r r e u r l o r s de la f e r m e t u r e : $ ! " ;

noter que IO::File va fermer le chier automatiquement lorsque lobjet arrivera en n de porte :
use IO :: F i l e ; my $ p a t h = / c h e m i n / v e r s / le / f i c h i e r ; { my $fh = IO :: File - > new ( $path , > ) or die $ !; p r i n t $fh " n o u v e l l e l i g n e \ n " ; } # f e r m e t u r e de $fh a u t o m a t i q u e

Manipuler des chemins avec Path::Class

189

Cependant, il est prfrable de fermer explicitement les chiers : cela a le mrite de produire un code facile comprendre, plutt que davoir deviner quand le descripteur de chier nest plus utilis.

Manipuler des chemins avec Path::Class


Pour travailler avec des chiers, il faut dabord connatre leur emplacement dans larborescence du systme de chiers. De plus, travailler de manire portable nest pas forcment facile sans utiliser le bon module. Heureusement, Path::Class permet de raliser toutes ces oprations, et mme plus !
use P a t h :: C l a s s ; my $ f i l e = f i l e ( c h e m i n , sous - c h e m i n , f i c h i e r . txt ) ; p r i n t $file - > s t r i n g i f y ;

Ce court exemple donnera des rsultats diffrents suivant la plateforme utilise pour le test :
q

chemin/sous-chemin/chier.txt sous Unix ; chemin\sous-chemin\chier.txt sous Windows.

Un objet Path::Class sera automatiquement converti lors dun contexte de chane. Les deux notations suivantes sont donc quivalentes :
my $ s t r i n g = $dir - > s t r i n g i f y ; my $ s t r i n g = " $ d i r " ;

Cela permet dutiliser un objet Path::Class facilement dans quasiment tous les endroits qui attendent normalement une chane contenant un chemin vers un chier ou un rpertoire.

190

CHAPITRE 10 Fichiers et rpertoires

Pointer un chier ou un rpertoire


dir() le()

Les constructeurs dir() et le() acceptent soit un chemin dcoup logiquement (comme dans lexemple ci-dessus), soit un chemin natif de la plateforme, soit un mlange des deux. Les exemples suivants sont tous valides et pointent sur le mme chier :
use P a t h :: C l a s s ; my $ f i l e 1 = f i l e ( c h e m i n , sous - c h e m i n , f i l e . txt ) ; my $ f i l e 2 = f i l e ( c h e m i n / sous - c h e m i n / f i l e . txt ) ; my $ f i l e 3 = f i l e ( c h e m i n / sous - c h e m i n , f i l e . txt ) ;

Le chier ou rpertoire ainsi point peut ne pas exister, Path::Class est un module qui aide travailler avec les chemins daccs dans labsolu. Les objets vus ci-dessus pointent des chemins relatifs au rpertoire courant. Pour crer des chemin absolus, il faut utiliser soit une chane vide en premier argument, soit la syntaxe de la plateforme :
use P a t h :: C l a s s ; my $ f i l e 1 = f i l e ( , usr , bin , p e r l ) ; my $ f i l e 2 = f i l e ( / usr / bin / p e r l ) ;

Pointer un objet parent

191

Pointer un objet relatif


subdir(..)

Une fois un objet Path::Class::Dir3 cr, il peut tre utilis pour pointer des chiers et rpertoires contenus dans ce rpertoire grce aux mthodes le et subdir.
use P a t h :: C l a s s ; my $ d i r = dir ( / etc ) ; my $ h o s t s = $dir - > f i l e ( h o s t s ) ; my $ x d i r = $dir - > s u b d i r ( X11 ) ;

Les variables $hosts et $xdir seront respectivement un objet Path::Class:File et Path::Class::Dir.

Pointer un objet parent


parent()

Inversement, Path::Class permet de pointer le rpertoire parent dun objet avec la mthode parent() :
use P a t h :: C l a s s ; my $ h o s t s = f i l e ( / etc / h o s t s ) ; my $ x d i r = dir ( / etc / X11 ) ; my $ d i r 1 = $hosts - > p a r e n t ; my $ d i r 2 = $xdir - > p a r e n t ;

Les objets $dir1 et $dir2 pointent tous les deux sur /etc.

3. Un rpertoire sera un objet de classe Path::Class::Dir tandis quun chier sera un objet de classe Path::Class::File.

192

CHAPITRE 10 Fichiers et rpertoires

Info
noter que cette mthode renvoie le parent logique, qui peut tre diffrent du parent physique (en cas de lien symbolique par exemple). Si le rpertoire est relatif, la notation relative du rpertoire parent est utilise. Voici un exemple pour aider la comprhension :
$ d i r = dir ( / foo / bar ) ; for ( 1 . . 6 ) { print " absolu : $dir \ n"; $ d i r = $dir - > p a r e n t ; } $ d i r = dir ( foo / bar ) ; for ( 1 . . 6 ) { print " relatif : $dir \n" ; $ d i r = $dir - > p a r e n t ; }

Ceci afchera sur une plateforme Unix : absolu : /foo/bar absolu : /foo absolu : / absolu : / absolu : / absolu : / relatif : foo/bar relatif : foo relatif : . relatif : .. relatif : ../.. relatif : ../../..

Obtenir des informations


stat()

Lister un rpertoire

193

Une fois un objet rpertoire ou chier cr, la mthode stat() permet de faire un appel la fonction du mme nom. Elle renvoie un objet File::stat, qui possde des mthodes nommes pour accder aux valeurs :
my $ s t a t f = my $ s t a t d = p r i n t $statf p r i n t $statd $file - > s t a t ; $dir - > s t a t ; -> size ; -> nlinks ;

Crer ou supprimer un rpertoire


mkpath(..), rmtree(..)

use P a t h :: C l a s s ; my $ d i r = dir ( c h e m i n / sous - c h e m i n ) ; $dir - > m k p a t h ; $dir - > r m t r e e ;

Les mthodes mkpath() et rmtree() vont respectivement crer toute larborescence (cela inclut tous les rpertoires intermdiaires ncessaires) et la supprimer rcursivement avec tout son contenu.

Lister un rpertoire
children()

La mthode children() renvoie un ensemble dobjets Path::Class (chiers et rpertoires) contenus dans le rpertoire. Bien sr, il est ncessaire dans ce cas que $dir existe et soit accessible en lecture pour pouvoir le lister :

194

CHAPITRE 10 Fichiers et rpertoires

use P a t h :: C l a s s ; my $ d i r = dir ( c h e m i n / sous - c h e m i n ) ; my @ c h i l d r e n = $dir - > c h i l d r e n ;

Chaque membre de @children sera un lment de $dir, cest--dire que les membres seront de la forme chemin/sous-chemin/chier.txt et non chier.txt. La mthode children() ne renvoie pas les entres correspondant aux rpertoires courant et parent4 . Pour obtenir ces entres spciales dans la liste des lments dun rpertoire, il faut passer une valeur vraie au paramtre all.
# s e u l e s les e n t r e s s t a n d a r d my @ c h i l d r e n = $dir - > c h i l d r e n ; # t o u t e s les e n t r e s my @ c h i l d r e n = $dir - > c h i l d r e n ( all = > 1) ;

Ouvrir un chier
open(..)

Lopration la plus courante pour un chier est bien sr son ouverture. Le dbut de chapitre prsente comment ouvrir un chier dont lemplacement tait dj connu. Mais la mthode open() dun objet Path::Class::File permet de court-circuiter lappel au constructeur de IO::File et de rcuprer directement un descripteur sur ce chier :

4. . et .. sous Unix et Windows.

Ouvrir un chier

195

my $fh = $file - > o p e n ( $mode , $ p e r m i s s i o n s ) ;

Bien sr, il faut vrier la validit de ce descripteur qui sera undef en cas derreur, avec la variable spciale $! positionne de manire adquate. Comme le traitement standard dune erreur lors de louverture dun chier est souvent dappeler die avec le message derreur, Path::Class propose deux mthodes qui font cela :
my $ f h r = $file - > o p e n r ; my $ f h w = $file - > o p e n w ;

Elles sont quivalentes respectivement :


my $ f h r = $file - > o p e n ( r ) or die " Can t r e a d $ f i l e : $ ! " ; my $ f h w = $file - > o p e n ( w ) or die " Can t w r i t e $ f i l e : $ ! " ;

Attention cependant, le message derreur est en anglais, et non localis. Finalement, la mthode slurp() permet de lire le chier dun seul coup. La mthode est sensible au contexte et renverra donc soit le contenu du chier en mode scalaire, soit un tableau de lignes5 en mode liste :
my $ d a t a = $file - > s l u r p ; my @ l i n e s = $file - > s l u r p ;

5. Lignes dnies ici aussi par $/.

196

CHAPITRE 10 Fichiers et rpertoires

Supprimer un chier
remove()

Supprimer un chier peut se rvler compliqu sur certaines plateformes6 . Pour simplier cette opration, Path::Class propose la mthode remove() qui soccupe des spcicits de la plateforme :
$file - > r e m o v e ;

Elle renvoie simplement un boolen pour indiquer le succs ou lchec de lopration.

Parcourir un rpertoire rcursivement


recurse(..)

La mthode recurse() permet de parcourir un rpertoire de manire rcursive. Pour chaque lment rencontr, la fonction anonyme passe en paramtre est appele :
$dir - > r e c u r s e ( c a l l b a c k = > sub { ... } ) ;

Couple la mthode is_dir() qui permet de savoir si un objet rencontr est de type rpertoire ou chier, recurse() est un outil extrmement puissant pour construire des fonctions de recherche performantes et concises. Lexemple suivant permet de trouver tous les chiers nomms *.pm et dafcher leur taille :
6. Telles que VMS qui maintient des versions de chiers.

Crer un chier temporaire

197

$dir - > r e c u r s e ( c a l l b a c k = > sub { my $ o b j = s h i f t ; r e t u r n if $obj - > i s _ d i r ; r e t u r n if $ o b j =~ /\. pm /; p r i n t " $ o b j \ t " . $obj - > stat - > s i z e . " \ n " ; } );

Enn, la mthode recurse() accepte le paramtre depthrst qui permet de faire dabord une recherche en profondeur :
$dir - > r e c u r s e ( d e p t h f i r s t = > 1 , c a l l b a c k = > sub { ... } );

Crer un chier temporaire


File::Temp

Crer des chiers (ou rpertoires) temporaires est une tche courante qui est en pratique assez difcile raliser pour viter les race conditions. Le module File::Temp va crer un chier temporaire et renverra un objet qui pourra tre utilis comme un descripteur de chier classique. Il possde aussi une mthode lename() qui renvoie le nom du chier temporaire ainsi conu.
use F i l e :: T e m p ; my $fh = F i l e :: Temp - > new ; $fh - > p r i n t ( " une n o u v e l l e l i g n e \ n " ) ; p r i n t $fh - > f i l e n a m e ;

198

CHAPITRE 10 Fichiers et rpertoires

Le chier temporaire va tre cr dans le rpertoire temporaire par dfaut (souvent /tmp sous Unix), sauf si le paramtre DIR est fourni. Il est aussi possible de contrler la forme du nom du chier temporaire avec les paramtres TEMPLATE et SUFFIX. Le paramtre TEMPLATE doit comporter assez de caractres X qui seront remplacs alatoirement lors de la cration du chier :
my $fh = F i l e :: Temp - > new ( DIR = > / tmp / s p e c i a l , TEMPLATE => temp_XXXXXXXX , SUFFIX = > . dat , ) ; # fichier cr : # / tmp / s p e c i a l / t e m p _ Y b U z I J j 7 . dat

Le chier temporaire sera supprim lorsque lobjet sera dtruit (en n de porte le plus souvent), mais le paramtre UNLINK permet de contrler ce comportement :
use F i l e :: T e m p ; { my $fh = F i l e :: Temp - > new ( U N L I N K = > 0 ) ; # u t i l i s a t i o n de $fh } # le f i c h i e r ne s e r a pas s u p p r i m # automatiquement

Crer un rpertoire temporaire


De la mme manire, il est parfois bien pratique de crer un rpertoire temporaire. L encore, le module File::Temp vient la rescousse :
use F i l e :: T e m p ; my $ t m p = F i l e :: Temp - > n e w d i r ; p r i n t $tmp - > d i r n a m e ;

Identier les rpertoires personnels

199

Le constructeur newdir() comprend la mme option DIR que pour les chiers temporaires. Le rpertoire ainsi cr sera aussi supprim lorsque lobjet sera dtruit, sauf si le paramtre CLEANUP est fourni avec une valeur fausse :
use F i l e :: T e m p ; { my $ t m p = F i l e :: Temp - > n e w d i r ( CLEANUP => 0 ); my $ d i r = $tmp - > d i r n a m e ; # u t i l i s a t i o n de $ t m p } # le r p e r t o i r e ne s e r a pas s u p p r i m # automatiquement

Identier les rpertoires personnels


File::HomeDir

Certaines oprations se font sur des chiers situs dans le rpertoire personnel de lutilisateur. Savoir o se situe ce rpertoire utilisateur peut tre assez compliqu suivant la plateforme, et mme suivant la version de celle-ci7 . Le module File::HomeDir transcende ces diffrences et renvoie la bonne valeur sur lensemble des plateformes :
use F i l e :: H o m e D i r ; my $ h o m e = F i l e :: H o m e D i r - > m y _ h o m e ;

De plus, un certain nombre de mthodes peuvent tre utilises pour retrouver les autres rpertoires personnels :
7. Par exemple, Windows XP et Windows Vista ne placent pas le rpertoire personnel des utilisateurs au mme endroit.

200

CHAPITRE 10 Fichiers et rpertoires

use F i l e :: H o m e D i r ; my $ d e s k t o p = F i l e :: H o m e D i r # b u r e a u my $ d o c s = F i l e :: H o m e D i r # d o c u m e n t s my $ m u s i c = F i l e :: H o m e D i r # m u s i q u e my $ p i c s = F i l e :: H o m e D i r # p h o t o s my $ v i d e o s = F i l e :: H o m e D i r # f i l m s my $ d a t a = F i l e :: H o m e D i r # d o n n e s

-> my_desktop ; -> my_documents ; -> my_music ; -> my_pictures ; -> my_videos ; -> my_data ;

Dans le monde Unix, toutes sortes de donnes sont mlanges dans le rpertoire personnel bien que cela soit en train de changer sur les plateformes supportant la norme FreeDesktop. Mais les autres plateformes utilisent depuis quelques temps des rpertoires diffrents pour stocker des donnes diffrentes. Une application utilisant File::HomeDir devra donc essayer de recourir la mthode la plus spcique possible: les documents utilisateurs devront tre sauvegards dans my_documents(), les donnes internes dune application non destines lutilisateur dans my_data(). Ceci ne prtera pas consquence sur les plateformes ne faisant pas de distinction, car File::HomeDir renverra le rpertoire personnel par dfaut pour ces rpertoires particuliers. Mais cela permettra lapplication de fonctionner de manire native sur les plateformes pour qui cette distinction est importante. La valeur renvoye par ces diverses mthodes est une chane, complter dun appel Path::Class pour proter des facilits de ce module.
use F i l e :: H o m e D i r ; use P a t h :: C l a s s ; my $ h o m e = dir ( F i l e :: H o m e D i r - > m y _ h o m e ) ;

Changer de rpertoire

201

Enn, il est bon de noter que ces fonctions sassureront que les rpertoires renvoys existent. Ainsi, lapplication peut tre sre quaucune race condition narrivera lors de leur utilisation.

Connatre le rpertoire courant


getcwd()

Beaucoup de programmes travaillent avec des chiers qui par dfaut seront relatifs au rpertoire courant. Il est donc important de savoir comment utiliser ce rpertoire en Perl.
use Cwd ; my $ d i r = g e t c w d ;

Rcuprer le rpertoire courant se fait avec la fonction getcwd() du module Cwd. La valeur renvoye est une chane, quil faut donc traiter pour pouvoir utiliser la puissance de Path::Class (voir page 189) :
use Cwd ; use P a t h :: C l a s s ; my $ d i r = dir ( g e t c w d ) ;

Changer de rpertoire
chdir()

La fonction chdir() connue des programmeurs C existe aussi en Perl, et est sans doute le moyen le plus facile pour changer de rpertoire :
chdir $newdir ;

202

CHAPITRE 10 Fichiers et rpertoires

Cependant, linconvnient de cette fonction est que lancien rpertoire est, par dnition, compltement oubli. Le module File::pushd permet de pallier ce problme, en empilant les rpertoires de la mme manire que son quivalent pushd du shell bash8 .
use F i l e :: H o m e D i r ; use F i l e :: p u s h d ; c h d i r F i l e :: H o m e D i r - > m y _ h o m e ; { # c h a n g e m e n t de r p e r t o i r e d u r a n t # une p o r t e l i m i t e my $ d i r = p u s h d ( / tmp ) ; # t r a v a i l d a n s / tmp } # fin de la p o r t e : # r e t o u r au r p e r t o i r e h o m e

Comme il est courant de changer de rpertoire pour un rpertoire temporaire, File::pushd propose aussi la fonction tempd() qui combine pushd() avec un appel File::Temp.
use F i l e :: p u s h d ; { my $ d i r = t e m p d () ; # t r a v a i l d a n s un r p e r t o i r e temporaire } # r e t o u r au r p e r t o i r e i n i t i a l

Le rpertoire temporaire sera automatiquement supprim en n de porte.

8. La fonction popd nexiste pas, la n de porte joue ce rle.

11
Bases de donnes SQL
Est-il besoin de prciser quel point les bases de donnes sont omniprsentes dans le monde informatique, sous des formes trs diverses et avec des paradigmes trs diffrents en fonction des besoins ? Ce chapitre est consacr aux bases de donnes relationnelles car elles reprsentent un passage quasi oblig dans la programmation actuelle ; mais il faut garder lesprit quelles ne reprsentent quun aspect de lunivers, plus large quil ny parat, des nombreux autres paradigmes de bases de donnes existant depuis longtemps (les chiers /etc/passwd dUnix, la base de registres de Windows, le DNS) ou apparus plus rcemment (CouchDB, TokyoCabinet, Redis, etc.). Bien videmment, Perl dispose de tout ce qui est ncessaire pour se connecter aux bases de donnes. De manire amusante, alors que le slogan de Perl est il y a plus dune manire de le faire , dans le cas des bases de donnes, le module DBI rgne sur les mcanismes de connexion, tablissant le standard de fait. DBI (Database Interface) constitue vritablement une API gnrique et normalise daccs aux bases de donnes (dans un sens trs large du terme), comparable dans lesprit

204

CHAPITRE 11 Bases de donnes SQL

JDBC dans le monde Java. Les mcanismes de connexion et le protocole proprement dits sont implments dans les pilotes correspondants chaque base, les DBD Database Driver. DBI est un de ces modules majeurs dans le monde Perl qui ont conduit les dveloppeurs contribuer de nombreux pilotes sur CPAN, et comme souvent quand il y a plthore, certains sont corrects, certains bien moins, dautres encore sont trs bons. Il ne sera pas surprenant de trouver dans cette dernire catgorie les pilotes des bases majeures que sont Oracle, PostgreSQL, MySQL, DB2, Ingres, SQLite. Un pilote ODBC gnrique est disponible pour les bases Windows. Info
Une dernire catgorie de pilotes mrite lattention : ceux crits par des gens pouvant tre quali de drangs , mais qui peuvent savrer tre la seule solution dans bien des situations compliques. Il sagit des pilotes crits en pur Perl, permettant de se connecter certaines bases sans utiliser leurs bibliothques usuelles. Sans surprise, ces pilotes nexistent que pour les bases libres PostgreSQL et MySQL : DBD::PgPP pour PostgreSQL, et DBD::mysqlPP et DBD::Wire10 pour MySQL (DBD::Wire10 pouvant aussi se connecter aux bases Drizzle et Sphinx). Certes, ces pilotes seront moins efcaces que les versions natives, mais ils peuvent tre salvateurs sil faut dployer dans un environnement ancien ou trs contraignant (conits de versions des clients). noter quil existe de mme une version pur Perl de DBI, par dfaut nomme DBI::PurePerl.

Enn, il faut savoir que DBI fonctionne de manire transparente par rapport au langage de requtage. Sil sagira de SQL dans la trs grande majorit des cas, il est parfaitement possible dcrire des pilotes avec un autre langage. Ainsi, DBD::WMI utilise logiquement WQL (WMI Query

Se connecter

205

Language) pour interroger la base WMI sous Windows, et lun des auteurs travaille sur DBD::Nagios, qui utilise LQL (Livestatus Query Language). Sil est encore besoin de montrer la capacit dadaptation de DBI aux environnements les plus exigeants, il suft de mentionner quil est fourni avec deux modules de proxy diffrents, lun, DBD::Proxy, avec conservation dtat (stateful), le second, DBD::Gofer, sans conservation dtat (stateless). Cas dutilisation typiques : traverse de pare-feu ou connexion une ou plusieurs bases exotiques dont les pilotes ne sont disponibles quen JDBC. DBD::Gofer peut aussi servir de systme de rpartition de charge.

Se connecter
DBI->connect()

use DBI ; my $ d b h = DBI - > c o n n e c t ( " dbi : Pg : h o s t = d b h o s t ; d b n a m e = m y b a s e " , $login , $ p a s s w o r d , { R a i s e E r r o r = > 1 } ); $dbh - > do ( " T R U N C A T E l o g s " ) ; $dbh - > p r e p a r e ( " S E L E C T id , n a m e FROM customers "); # ...

La mthode connect() de DBI est un constructeur, similaire la mthode new() dautres modules, qui tente dtablir la connexion la base et renvoie un objet descripteur de base (DBH, database handler).

206

CHAPITRE 11 Bases de donnes SQL

Les trois premiers arguments sont la chane de connexion (ou DSN, data source name), le nom dutilisateur et le mot de passe. Ces deux derniers peuvent tre des vides, mais il faut quand mme les fournir. La chane de connexion ressemble une URL et se dcompose ainsi :
q

"dbi:" : La chane "dbi:" est lquivalent du protocole

et dnit ici lAPI ; elle est donc toujours celle de DBI.


q

Le nom du module. Le nom du module Perl qui fournit le pilote utiliser, sans son prxe DBD::, ainsi pour accder une base Oracle, le module utiliser est DBD::Oracle, do le nom Oracle. Idem pour SQLite. Par contre, pour PostgreSQL le module Perl sappelle DBD::Pg, et pour MySQL, DBD::mysql (tout en minuscules). Si aucun nom nest fourni, DBI ira le chercher dans la variable denvironnement $DBI_DRIVER. noter que DBI chargera automatiquement le module correspond. Les arguments. Le reste de la chane, aprs les deuxpoints qui suit le pilote, constitue les arguments du pilote, et nest pas interprt par DBI. Si la chane fournie est vide, connect() ira la chercher dans la variable denvironnement $DBI_DSN.

Le dernier argument de connect(), optionnel, est une rfrence vers un hash contenant des attributs DBI. Parmi les nombreux attributs existants, il nest vraiment besoin que de connatre les suivants : q AutoCommit : active ou dsactive le support des transactions. La valeur vraie, positionne par dfaut en respect des conventions JDBC et ODBC, dsactive le support des transactions. Il faut donc explicitement positionner cet attribut faux pour activer les transactions, si le pilote les supporte. Les bonnes pratiques conseillent de toujours le positionner la valeur attendue par le programme, mme sil sagit de la valeur par dfaut.

Se connecter

207

RaiseError : force les erreurs provoquer des exceptions, plutt que de simplement renvoyer des codes derreur. Par dfaut dsactive, cette option est trs utile pour lcriture de courts programmes o la gestion des erreurs rajoute une quantit non ngligeable de code assez ennuyeux. ShowErrorStatement : permet dafcher la requte qui a

provoqu lerreur dans les messages.


q

TraceLevel : cest un des moyens pour activer les traces

dexcution.
q

Prole : cest un des moyens pour activer le prolage

dexcution. Du point de vue de lutilisateur, un des aspects gnants de la chane de connexion, et en particulier des arguments du pilote, est que ceux-ci sont spciques chaque pilote, et quil ny a donc aucune normalisation alors que, dans bien des cas, les paramtres sont les mmes : nom de la base, ventuels nom dhte et numro de port. Pour ces raisons, lun des auteurs a crit un petit module, DBIx::Connect::FromCong, qui offre une interface un peu plus homogne :
use DBI ; use D B I x :: C o n n e c t :: F r o m C o n f i g - i n _ d b i ; my % s e t t i n g s = ( driver = > " Pg " , host = > " bigapp - db . s o c i e t y . com " , database => " bigapp " , username => " appuser " , password => " sekr3t " , attributes => { AutoCommit => 1, RaiseError => 1 }, );

208

CHAPITRE 11 Bases de donnes SQL

my $ d b h = DBI - > c o n n e c t _ f r o m _ c o n f i g ( c o n f i g = > \% s e t t i n g s ) ;

Cest dj intressant, mais mieux encore, le module peut prendre lobjet dun module de conguration en argument :
my $ c o n f i g = C o n f i g :: I n i F i l e s - > new ( - f i l e = > " / etc / m y p r o g r a m / d a t a b a s e . c o n f " ); my $ d b h = DBI - > c o n n e c t _ f r o m _ c o n f i g ( config => $config );

o le chier de conguration ressemble :


[database] driver host database username password attributes = = = = = = Pg bigapp-db.society.com bigapp appuser sekr3t AutoCommit=1|RaiseError=1

Ce qui simplie la gestion de la conguration lorsquune application doit tre dploye dans des environnements diffrents (dveloppement, recette, prproduction, production).

Tester la connexion
$dbh->ping()

Bien que ce soit rarement ncessaire, il est possible de tester si la connexion est toujours active (si ce mcanisme est vritablement cod dans le pilote correspondant).

Prparer une requte SQL

209

Se dconnecter
$dbh->disconnect()

Se dconnecter dune base ne demande quun appel la mthode disconnect(). noter quelle est automatiquement invoque quand le descripteur de base est sur le point dtre dtruit, par exemple la n du bloc o il a t dni ; mais il est plus propre de lappeler explicitement. Un avertissement sera afch si des requtes taient encore actives, par exemple une requte SELECT avec des donnes non encore rcupres.

Prparer une requte SQL


$dbh->prepare(. . .)

La manire la plus efcace dutilisation des requtes est de suivre le protocole en quatre tapes : prepare (prparation), bind (liaison), execute (excution), fetch (rcupration). La prparation consiste en lanalyse de la requte, soit au niveau du client (ici, du pilote), soit au niveau du serveur, an de vrier que la syntaxe est correcte, et quand il y en a de reprer les paramtres. En effet, DBI supporte le passage de valeurs dans les requtes au travers de marqueurs (placeholders), nots par des points dinterrogation :
I N S E R T I N T O u s e r s ( login , name , g r o u p ) V A L U E S (? , ? , ?)

Comme le montre lexemple, les marqueurs indiquent lemplacement de chaque valeur, et ne doivent donc pas

210

CHAPITRE 11 Bases de donnes SQL

tre mis entre guillemets, mme si la valeur qui sera passe est une chane. Un marqueur ne correspond qu une seule et unique valeur, ce qui interdit de construire une requte avec un IN (?) en esprant passer plus dune valeur ; il est ncessaire dexpliciter chaque valeur par un marqueur. La solution pour ce genre de cas est de construire la requte dynamiquement :
my $ l i s t = j o i n " , " , ( " ? " ) x @ v a l u e s ; my $ q u e r y = " S E L E C T * F R O M l i b r a r y W H E R E a u t h o r IN ( $ l i s t ) " ;

Attention
Les marqueurs ne peuvent correspondre qu des valeurs, et non des noms de table ou de colonne (voir page 223 pour une solution).

La mthode DBI pour prparer une requte sappelle tout simplement prepare() ; elle sinvoque sur un objet DBH et renvoie un descripteur de requte (STH, statement handler) :
my $ s t h = $dbh - > p r e p a r e ( " SELECT * FROM library WHERE author LIKE ?" );

Valeurs nulles en SQL


Un point important noter est que les valeurs NULL (au sens SQL) sont traduites en Perl par undef. Cela marche aussi bien en entre quen sortie, mais il faut se souvenir quen SQL, value = NULL doit scrire value IS NULL, ce qui ncessite de traiter diffremment les arguments non dnis.

Excuter une requte SQL

211

Lier une requte SQL


$str->bind_param(. . .)

Aprs avoir prpar une requte, les valeurs sont associes aux marques lors de ltape de liaison, laide de la mthode bind_param() :
$sth - > b i n d _ p a r a m (1 , " N a o k i U r a s a w a " ) ;

Le premier argument est le numro du marqueur (ceux-ci sont numrots partir de 1), le deuxime la valeur. Il est aussi possible de passer en troisime argument le type SQL de la valeur :
$sth - > b i n d _ p a r a m (1 , " N a o k i U r a s a w a " , SQL_VARCHAR );

En pratique, cette manire de faire est rarement utilise car la liaison peut tre ralise en mme temps que lexcution.

Excuter une requte SQL


$str->execute(. . .)

Il est possible dexcuter une requte en liant les paramtres en un seul appel la fonction execute :
$str - > e x e c u t e ( " N a o k i U r a s a w a " ) ;

212

CHAPITRE 11 Bases de donnes SQL

Rcuprer les donnes de retour dune requte SQL


La rcupration des enregistrements se fait laide des mthodes fetchxxx_yyy(). La partie xxx du nom correspond la manire de rcuprer les enregistrements, soit un par un (row), soit tout dun coup (all). La partie yyy du nom est la forme sous laquelle DBI va fournir les donnes : tableau direct (array), rfrence de tableau (arrayref), rfrence de hash (hashref). Ainsi, avec la requte prcdente, un appel fetchrow_array() donnera :
my @ r o w = $sth - > f e t c h r o w _ a r r a y () ; # @ r o w = ( " P L U T O v o l u m e 1" , " 2 0 1 0 - 0 2 - 1 9 " , # " Naoki Urasawa ", " Kana " )

@row reoit les valeurs du premier enregistrement, dans lordre des colonnes indiqu dans le SELECT ou dans lordre

naturel (typiquement, celui de cration). Mme chose avec fetchrow_arrayref() :


my $ r o w = $sth - > f e t c h r o w _ a r r a y r e f () ; # $ r o w = [ " P L U T O v o l u m e 1" , " 2 0 1 0 - 0 2 - 1 9 " , # " N a o k i U r a s a w a " , ... ]

fetchrow_hashref() renvoie lenregistrement sous forme

dune rfrence de hash, avec les colonnes en cls :


my $ r o w = $sth - > f e t c h r o w _ h a s h r e f () ; # $ r o w = { t i t l e = > " P L U T O v o l u m e 1" , # date => "2010 -02 -19" , # a u t h o r = > " N a o k i U r a s a w a " , ... }

Rcuprer les donnes de retour dune requte SQL

213

Bien sr, les appels successifs ces mthodes permettent de rcuprer les enregistrements au fur et mesure ; elles renvoient faux quand il ny a plus denregistrement :
w h i l e ( my print "* } # affiche # * PLUTO # * PLUTO # * PLUTO # ... $ b o o k = $sth - > f e t c h r o w _ h a s h r e f () ) { $book - >{ t i t l e } ( $book - >{ d a t e }) \ n " ; : volume 1 (2010 -02 -19) volume 2 (2010 -02 -19) volume 3 (2010 -04 -02)

fetchall_arrayref() renvoie assez simplement tous les en-

registrements dans une rfrence de tableau englobant :


my $ b o o k s = $sth - > f e t c h a l l _ a r r a y r e f () ; # $books = [ # [ " P L U T O v o l u m e 1" , " 2 0 1 0 - 0 2 - 1 9 " , " N a o k i U r a s a w a " ] , # [ " P L U T O v o l u m e 2" , " 2 0 1 0 - 0 2 - 1 9 " , " N a o k i U r a s a w a " ] , # [ " P L U T O v o l u m e 3" , " 2 0 1 0 - 0 4 - 0 2 " , " N a o k i U r a s a w a " ] , # ... # ]

fetchall_hashref() est un peu plus subtile. Elle attend

comme argument le nom dune colonne qui fait ofce de cl, cest--dire didentiant unique de chaque enregistrement. Cela permet ainsi daccder chaque champ de manire directe :
my $ l i b r a r y = $sth - > f e t c h a l l _ h a s h r e f ( " t i t l e " ) ; # $library = { # " P L U T O v o l u m e 1" = > { # date => "2010 -02 -19" , author => " Naoki Urasawa " , # }, # " P L U T O v o l u m e 2" = > { # date => "2010 -02 -19" , author =>

214

CHAPITRE 11 Bases de donnes SQL

" Naoki Urasawa " , }, " P L U T O v o l u m e 3" = > { date => "2010 -04 -02" , author => " Naoki Urasawa " , # }, # ... # } # # #

Sil ny a pas une colonne pour identier de manire unique chaque enregistrement, une rfrence vers un tableau de cls peut tre fournie la mthode, qui construira le hash rsultat avec autant de niveaux de profondeur que de cls.
my $ s t h = $dbh - > p r e p a r e ( q { S E L E C T host , s e r v i c e , id , type , level , time , m e s s a g e FROM events WHERE t y p e = ? AND l e v e l = ? }) ; $sth - > e x e c u t e ( " p e r s i s t e n t " , " e r r o r " ) ; my $ e v e n t s = $sth - > f e t c h a l l _ h a s h r e f ( [ " h o s t " , " s e r v i c e " , " id " ] ); say " f r o m $ h o s t : . $ e v e n t s - >{ $ h o s t } { $ s e r v i c e }{ $id }{ m e s s a g e } " ;

Combiner les tapes dexcution dune requte SQL


Dans le cas particulier des requtes SELECT, il existe des mthodes permettant de combiner les quatre tapes en une seule : selectrow_array(), selectrow_arrayref(), selectrow_hashref(), selectall_arrayref() et selectall_hashref().

Combiner les tapes dexcution dune requte SQL

215

my @ r o w = $dbh - > s e l e c t r o w _ a r r a y ( " S E L E C T n a m e F R O M u s e r s W H E R E id = ? " , {} , $id );

Le second argument de la mthode est une rfrence vers un hash, vide dans lexemple, qui accepte des attributs (comme RaiseError) qui seront positionns juste pour cette requte. Pour les requtes, hors SELECT, qui nont pas besoin dtre excutes de nombreuses fois, la mthode do() permet de tout faire dun coup :
$dbh - > do ( " T R U N C A T E l o g s " ) ;

Bien sr, les requtes peuvent contenir des marqueurs, les valeurs tant passes de manire similaire aux mthodes selectxxx() :
my $n = $dbh - > do ( " D E L E T E F R O M e v e n t s W H E R E h o s t =? AND s e r v i c e =? " , {} , $host , $ s e r v i c e , );

La mthode renvoie le nombre de lignes affectes par la requte, undef en cas derreur et -1 si le nombre nest pas connu ou na pas de sens. Cette mcanique peut sembler un peu lourde, mais elle permet en ralit dobtenir de trs bonnes performances, en particulier quand les mthodes les plus rapides (comme fetchrow_arrayref()) sont utilises. DBI dispose dautres mcanismes pour gagner encore en performance, comme par exemple bind_col(), mais ils ne seront pas abords car les quelques mthodes prsentes ici permettent dj dobtenir de trs bons rsultats.

216

CHAPITRE 11 Bases de donnes SQL

Mise en cache et scurit


Un point fondamental comprendre est que la prparation dune requte autorise bien plus facilement la mise en cache de celle-ci, ce qui permet de la rutiliser par la suite plus rapidement. Lutilisation des marqueurs est de ce point de vue essentielle pour deux raisons. Dune part, cela garantit que le texte de la requte ne change pas, et donc quil sagit bien de la mme requte. Dautre part, linterpolation des valeurs conduit immanquablement des problmes de scurit, les fameuses injections SQL. Il nexiste aucun moyen able dexcuter des requtes avec des donnes interpoles. La seule manire est justement de remplacer les valeurs par des marqueurs, et dtablir a posteriori une correspondance entre chaque marqueur et sa valeur. tant donn que ce principe offre la fois scurit et rapidit, les quelques rares inconvnients qui peuvent exister sont plus que largement compenss par la tranquillit desprit quil procure.

Grer les erreurs


$h->err $h->errstr $DBI::err $DBI::errstr

Pour vrier si une erreur sest produite (si RaiseError nest pas utilis), DBI fournit la mthode err() qui du point de vue utilisateur nal renvoie un boolen (en pratique, il sagit dun code derreur, mais dont la signication na gnralement de sens que pour le pilote). Pour avoir le dtail de lerreur, cest la mthode errstr() qui doit tre utilise. Les valeurs de ces mthodes ont une du-

Tracer lexcution

217

re de vie assez courte, car elles sont remises zro par un appel la plupart des autres mthodes.
if ( $sth - > err ) { die " e r r e u r : " , $sth - > e r r s t r ; }

Il existe aussi des variables globales $DBI::err et $DBI:errstr qui ont pour valeur celles des mthodes err() et errstr() du dernier descripteur qui a t utilis. Il est sans surprise dconseill de les utiliser et il faut leur prfrer les mthodes correspondantes.

Tracer lexcution
DBI_TRACE $h->{TraceLevel} trace()

DBI intgre un mcanisme pour tracer lexcution des re-

qutes an de faciliter la recherche des problmes. Celuici peut sactiver de plusieurs manires : q par la variable denvironnement $DBI_TRACE ;
q q

par lattribut TraceLevel ; par la mthode trace().

La valeur passer est le niveau de verbosit souhait de la trace. En pratique, la valeur 2 est la plus approprie pour commencer car elle afche lensemble des valeurs passes en argument. Ainsi, le code suivant :
$dbh - > t r a c e (2) ; my $ s t h = $dbh - > p r e p a r e ( q { I N S E R T I N T O e v e n t s ( host , s e r v i c e , id , message ) V A L U E S (? , ? , ? , ?) }) ;

218

CHAPITRE 11 Bases de donnes SQL

$sth - > e x e c u t e ( ns . d o m a i n . com , dns , 123 , dns s y s t e m ok );

afche cette trace :


DBI::db=HASH(0x186ba18) trace level set to 0x0/2 (DBI @ 0x0/0) in DBI 1.609-ithread (pid 7346) -> prepare for DBD::SQLite::db (DBI::db=HASH(0x186ba60)~0x186ba18 INSERT INTO events (host, service, id, message) VALUES (?, ?, ?, ?) ) thr#1800400 <- prepare= DBI::st=HASH(0x1847a70) at dbi-trace line 15 -> execute for DBD::SQLite::st (DBI::st=HASH(0x1847a70)~0x186bc88 ns.domain.com dns 123 dns system ok) thr#1800400 <- execute= 1 at dbi-trace line 18 -> DESTROY for DBD::SQLite::st (DBI::st=HASH(0x186bc88)~INNER) thr#1800400 <- DESTROY= undef -> DESTROY for DBD::SQLite::db (DBI::db=HASH(0x186ba18)~INNER) thr#1800400 <- DESTROY= undef

La variable denvironnement prsente lavantage dactiver les traces sans modier le programme :
D B I _ T R A C E =2 p e r l dbi - p r o g r a m ...

Par contre, lattribut permet dactiver la trace de manire locale un bloc :


{ # s e u l e s les r e q u t e s c r e s d a n s ce bloc # seront traces l o c a l $dbh - >{ T r a c e L e v e l } = 2; ... }

Cet attribut est comme beaucoup dautres hrit par les requtes du descripteur de base partir duquel elles sont cres :

Proler lexcution

219

my $ s t h ; { l o c a l $dbh - >{ T r a c e L e v e l } = 2; $ s t h = $dbh - > p r e p a r e ( " ... " ) ; } # cet a p p e l s e r a t r a c en s o r t i e $sth - > e x e c u t e ;

La mthode permet de rediriger la trace, par dfaut afche sur la sortie derreur, vers un chier ou un descripteur de chier :
$fh = IO :: File - > new ( " > t r a c e . log " ) ; $h - > t r a c e (2 , $fh ) ;

Proler lexcution
DBI_PROFILE $h->{Prole}

DBI intgre un proleur trs abouti, disposant de plusieurs

interfaces pour y accder. Seule la premire sera vritablement prsente ici mais elle convient la plupart des besoins. Un peu comme avec les traces, le proleur peut se contrler dune part avec la variable denvironnement $DBI_PROFILE, dautre part avec lattribut Prole. Dans les deux cas, il sufra daffecter une valeur dcrivant le prolage effectuer. Dans sa forme la plus simple, il sagira dune valeur numrique, en particulier 2 qui afche un rapport indiquant le cot et lutilisation de chaque requte excute :
$ DBI_PROFILE=2 perl init-database.pl DBI::Prole: 0.230871s 23.09% (47 calls) dbi-prole @ 2010-08-30

220

CHAPITRE 11 Bases de donnes SQL

02:42:09 => 0.007850s / 13 = 0.000604s avg (rst 0.007669s, min 0.000004s, max 0.007669s) CREATE TABLE events ( host varchar, service varchar, id integer, message varchar ) => 0.052813s INSERT INTO events (host, service, id, message) VALUES (?, ?, ?, ?) => 0.170209s / 33 = 0.005158s avg (rst 0.000345s, min 0.000043s, max 0.017538s)

Les valeurs indiques pour la requte vide correspondent en ralit au temps pass dans les mthodes propres au descripteur de base de donnes (connexion, affectation des attributs, etc). Pour les autres requtes, cela correspond au temps total pass que chacune a consomm. Dans le cas du INSERT, la requte a t prpare une fois puis excute 30 fois, ce qui signie que le proleur a aussi compt deux appels internes. Pour avoir plus de dtails, il suft daugmenter la valeur passe $DBI_PROFILE 6, qui afche pour chaque requte le temps pass dans chaque mthode :
INSERT INTO events (host, service, id, message) VALUES (?, ?, ?, ?) => DESTROY => 0.002115s / 2 = 0.001058s avg (rst 0.000043s, min 0.000043s, max 0.002072s) execute => 0.331492s / 30 = 0.011050s avg (rst 0.011977s, min 0.004121s, max 0.057116s) prepare => 0.000341s

Le proleur de base accepte une conguration plus pousse au travers darguments comme !File, qui groupe les rsultats par nom de chiers, ou !TimeN, qui permet de grouper les rsultats par tranche de N secondes :
$ DBI_PROFILE=!File:!Time~2:!Statement perl init-database.pl DBI::Prole: 1.437674s 143.77% (317 calls) dbi-prole @ 2010-08-30 03:26:14 1283131572 => =>

Proler lexcution

221

0.007492s / 12 = 0.000624s avg (rst 0.007335s, min 0.000003s, max 0.007335s) INSERT INTO events (host, service, id, message) VALUES (?, ?, ?, ?) => 0.490690s / 85 = 0.005773s avg (rst 0.000342s, min 0.000342s, max 0.033933s) CREATE TABLE events ( host varchar, service varchar, id integer, message varchar ) => 0.053218s 1283131574 => => 0.000024s INSERT INTO events (host, service, id, message) VALUES (?, ?, ?, ?) => 0.886250s / 218 = 0.004065s avg (rst 0.003718s, min 0.000041s, max 0.029571s)

Seule la surface du proleur PERMOD:DBI a t aborde. Mme linterface simple offre dj une conguration assez pousse au travers de lobjet DBI::Profile qui peut tre affect lattribut Prole. Lutilisateur avec des besoins plus avancs se tournera vers DBI::ProfileDumper, qui permet denregistrer les rsultats du prolage sur disque, et la commande dbiprof pour les exploiter. En reprenant lexemple prcdent, les commandes suivantes permettent de voir quelle est la requte la plus consommatrice :
$ DBI_PROFILE=6/DBI::ProleDumper perl init-database.pl $ dbiprof --sort count --number 1 DBI Prole Data (DBI::ProleDumper 2.009894) Program Path Total Records Total Count Total Runtime : : : : : pad/dbi-prole [ !Statement, !MethodName ] 9 (showing 1, sorted by count) 317 2.252447 seconds

#####[ 1 ]########################################### Count : 300 Total Time : 1.976474 seconds

222

CHAPITRE 11 Bases de donnes SQL

Longest Time Shortest Time Average Time Key 1 Key 2

: : : :

0.204763 seconds 0.002353 seconds 0.006588 seconds INSERT INTO events (host, service, id, message) VALUES (?, ?, ?, ?) : execute

12
Abstraction du SQL, ORM et bases non-SQL
DBI (voir page 203) permet lexcution de requtes para-

mtres, autorisant un passage de valeurs de manire sre, sans risque dinjection SQL. Mais comme il faut passer les valeurs dans lordre de dnition des paramtres, les requtes deviennent rapidement peu confortables grer au-del de trois ou quatre paramtres. Certains pilotes autorisent lutilisation de marqueurs nomms, mais ils sont rares, et forcment ce nest pas portable. Pour contourner cette limitation, il faut passer par un module dabstraction du SQL. Parmi les diffrents disponibles sur CPAN, il en existe un qui constitue un bon compromis entre abstraction et simplicit dutilisation, Data::Phrasebook::SQL.

Utiliser Data::Phrasebook::SQL
Il sagit dun module faisant partie de Data::Phrasebook, qui est un systme de gestion de dictionnaires. Le principe est de rassembler en un seul endroit des chanes de texte

224

CHAPITRE 12 Abstraction du SQL, ORM et bases non-SQL

qui peuvent apparatre plusieurs emplacements du code, par exemple les messages derreurs. Si cela parat confus, pensez aux bibliothques comme gettext : au nal, il sagit de rcuprer la chane correspondant un identiant (gnralement la chane en anglais) et la langue destination, toutes les chanes tant stockes dans les fameux chiers .po, qui constituent autant de dictionnaires. Par rapport gettext, lintrt de Data::Phrasebook est quil supporte de nombreux formats de chiers, et surtout autorise des variables de remplacement, un peu comme dans un systme de templates. Data::Phrasebook::SQL est donc une sous-classe de Data::Phrasebook spcialise dans la gestion de dictionnaires SQL et dans lexcution de ces requtes au travers de DBI. Le gros intrt est que cela permet une sparation entre le code Perl et le code SQL, ce qui est gnralement considr comme une bonne pratique. Une combinaison intressante est dutiliser YAML comme format de stockage, car il permet dcrire les requtes de manire are et naturelle. Le cas dutilisation qui va servir dexemple est adapt dune situation rencontre par lun des auteurs, qui sans laide de Data::Phrasebook::SQL aurait d crire des dizaines et des dizaines de requtes SQL toutes semblables. Le seul vrai dfaut de ce module est de pcher au niveau de la documentation, ce qui rend son apprentissage moins ais quil ne devrait tre. lutilisation, le module savre par contre vritablement agrable, car il sagit au nal dune surcouche assez ne au-dessus de DBI, qui ne perturbe pas lutilisateur habitu ce dernier. Data::Phrasebook::SQL attend dailleurs de lutilisateur quil lui fournisse le descripteur de base, linstanciation partir des requtes stockes dans un chier YAML ressemblant ceci :

Utiliser Data::Phrasebook::SQL

225

my $ b o o k = D a t a :: P h r a s e b o o k - > new ( c l a s s = > " SQL " , dbh = > $dbh , loader => " YAML " , file => " queries . yaml " , );

Pour planter le contexte, considrons une base de donnes assez classique avec une table qui contient des informations sur des serveurs :
CREATE TABLE hosts ( host_id integer, host_name varchar, hard_id integer, site_id integer, cust_id integer, PRIMARY KEY (host_id) ); -----ID du serveur nom du serveur ID du matriel ID du site ID du client

En DBI classique, la requte pour trouver lID dun serveur partir de son nom scrit :
my $ s t h = $dbh - > p r e p a r e ( " SELECT host_id FROM hosts WHERE host_name = ?" ); $sth - > e x e c u t e ( $ n a m e ) ; my ( $id ) = $sth - > f e t c h r o w _ a r r a y ;

Avec Data::Phrasebook::SQL, la requte est stocke dans un chier, ici YAML, et est donc nomme :
get_host_id: SELECT FROM WHERE | host_id hosts host_name = :host_name

La barre verticale qui suit le deux-points est la syntaxe YAML qui signale que le bloc de lignes qui suit, jusqu la prochaine ligne vide, est affecter comme valeur

226

CHAPITRE 12 Abstraction du SQL, ORM et bases non-SQL

get_host_id. :host_name est la syntaxe Data::Phrasebook pour crire un marqueur nomm host_name.

Ct Perl, le code devient :


my $ q r y = $book - > q u e r y ( " g e t _ h o s t _ i d " ) ; $qry - > e x e c u t e ( h o s t _ n a m e = > $ h o s t _ n a m e ) ; my ( $ h o s t _ i d ) = $qry - > f e t c h r o w _ a r r a y ;

Par rapport DBI, la requte est rcupre et prpare en indiquant simplement son nom, qui fournit un objet comparable au descripteur de requte de DBI. Par contre, sa mthode execute() reoit maintenant les valeurs sous forme paramtre, attendant comme noms ceux des marqueurs de la requte. La rcupration des rsultats est quant elle identique au cas DBI. Pour les besoins de lapplication et de lexemple, considrons quil est ncessaire de pouvoir accder chaque champ de manire indpendante. Avec DBI, il faut alors crire une requte pour chaque champ. Avec Data::Phrasebook::SQL, ce nest quune question de remplacement de marqueurs. Ainsi, ct SQL :
get_host_eld: | SELECT :host_eld FROM sys_hosts WHERE host_id = :host_id

et ct Perl :
my $ q r y = $hosts - > q u e r y ( " get_host_field " , replace => { host_field => " hard_id " }, ); $qry - > e x e c u t e ( h o s t _ i d = > $ h o s t _ i d ) ; my ( $ h a r d _ i d ) = $qry - > f e t c h r o w _ a r r a y ;

Utiliser Data::Phrasebook::SQL

227

Le seul ajout est celui du replace dans lappel de query() qui indique les marqueurs substituer pour gnrer la requte SQL (ce qui constitue la partie templating), les marqueurs restants tant remplacs lors de lappel execute() (ce qui constitue la partie paramtres nomms). Si ce mcanisme semble trop lourd pour rpondre un besoin ici assez simple, il suft de corser un peu la donne en ajoutant quelque chose de typique dans les grosses bases de donnes : un systme de proprits gnriques.
CREATE TABLE prop_id prop_name prop_type prop_list integer, varchar, varchar, ( -- ID de la proprit -- nom de la proprit -- type de la proprit

PRIMARY KEY (prop_id) ); CREATE TABLE prop_values ( val_id integer, -- ID de la valeur de proprit val_prop_id integer, -- ID de la proprit val_host_id integer, -- ID de lhte associ val_t_bool val_t_int val_t_oat val_t_char val_t_text val_t_date boolean, -- champ pour valeur boolenne integer, -- champ pour valeur entire oat, -- champ pour valeur ottante varchar, -- champ pour une courte chane text, -- champ pour du texte long date, -- champ pour une date

PRIMARY KEY (val_id) );

Avec Data::Phrasebook::SQL, crire les requtes pour grer cela est confondant de simplicit. Il suft en effet de quatre requtes :

228

CHAPITRE 12 Abstraction du SQL, ORM et bases non-SQL

fetch_properties: | SELECT prop_name, prop_id, prop_type FROM prop_list get_property_value: | SELECT :prop_eld FROM prop_values WHERE val_host_id = :host_id AND val_prop_id = :prop_id insert_property_value: | INSERT INTO prop_values ( :prop_eld, val_host_id, val_prop_id ) VALUES ( :prop_value, :host_id, :prop_id ) update_property_value: | UPDATE prop_values SET :prop_eld = :prop_value WHERE val_host_id = :host_id AND val_prop_id = :prop_id

et de deux fonctions :
use c o n s t a n t { ID = > 0 , T Y P E = > 1 }; my $ f e t c h _ p r o p e r t i e s _ q r y = $self - > q u e r y ( " f e t c h _ p r o p e r t i e s " ) ; $fetch_properties_qry -> execute ; # l i n s t r u c t i o n s u i v a n t e c o n s t r u i t un h a s h # a v e c en cl les n o m s des p r o p r i t s et en # v a l e u r un a r r a y r e f qui c o n t i e n t l ID et # le t y p e de la p r o p r i t my % p r o p e r t y = map { s h i f t @$_ , $_ } @ { $ f e t c h _ p r o p e r t i e s _ q r y - > f e t c h a l l _ a r r a y r e f }; sub g e t _ p r o p e r t y { my ( $ h o s t _ i d , $ p r o p _ n a m e ) = @_ ; # d t e r m i n a t i o n de l ID et du t y p e de la

Utiliser Data::Phrasebook::SQL

229

# p r o p r i t , et d o n c du c h a m p a t t a q u e r my $ p r o p _ i d = $ p r o p e r t y { $ p r o p _ n a m e }[ ID ]; my $ p r o p _ t y p e = $ p r o p e r t y { $ p r o p _ n a m e } [ T Y P E ]; my $ p r o p _ f i e l d = " v a l _ t _ $ p r o p _ t y p e " ; my $ g e t _ p r o p _ q r y = $book - > q u e r y ( " get_property_value " , replace => { prop_field => $prop_field } ); $get_prop_qry -> execute ( host_id => $host_id , prop_id => $prop_id , ); my ( $ p r o p _ v a l u e ) = $ g e t _ p r o p _ s t h - > fetchrow_array ; return $prop_value }

sub s e t _ p r o p e r t y { my ( $ h o s t _ i d , $ p r o p _ n a m e , $ p r o p _ v a l u e ) = @_ ; # d t e r m i n a t i o n de l ID et du t y p e de la # p r o p r i t , et d o n c du c h a m p a t t a q u e r my $ p r o p _ i d = $ p r o p e r t y { $ p r o p _ n a m e }[ ID ]; my $ p r o p _ t y p e = $ p r o p e r t y { $ p r o p _ n a m e } [ T Y P E ]; my $ p r o p _ f i e l d = " v a l _ t _ $ p r o p _ t y p e " ; # r c u p r a t i o n de l v e n t u e l l e a n c i e n n e # valeur pour savoir quelle requte # ( I N S E R T ou U P D A T E ) u t i l i s e r my $ o l d _ v a l u e = g e t _ p r o p e r t y ( $ h o s t _ i d , $prop_name ); my $ s e t _ p r o p _ q r y ;

230

CHAPITRE 12 Abstraction du SQL, ORM et bases non-SQL

if ( d e f i n e d $ o l d _ v a l u e ) { if ( $ p r o p _ v a l u e eq $ o l d _ v a l u e ) { # p e t i t e o p t i m i s a t i o n , la v a l e u r # e x i s t a i t m a i s n a pas c h a n g e # => rien faire return } else { # la v a l e u r e x i s t a i t et d o i t c h a n g e r # => UPDATE $ s e t _ p r o p _ s t h = $self - > q u e r y ( " update_property_value " , replace => { prop_field => $prop_field } ); } } else { # il n y a v a i t pas de v a l e u r = > I N S E R T $ s e t _ p r o p _ s t h = $self - > q u e r y ( " insert_property_value " , replace => { prop_field => $prop_field } ); } $set_prop_sth host_id prop_id prop_value ); } -> execute ( => $host_id , => $prop_id , => $prop_value ,

Certes, le code peut sembler un peu complexe au premier abord, mais il est globalement court et facile suivre. Lexemple prsent est une version rduite du systme crit par lauteur qui utilise Data::Phrasebook::SQL en production, et qui permet de grer lajout dynamique de proprits et supporte des valeurs numres. Lajout dune nouvelle proprit se rsume une simple insertion dans

Utiliser Data::Phrasebook::SQL

231

la table prop_list. Lensemble tant de plus encapsul de manire objet, les proprits deviennent des attributs de lobjet, et le code nal qui met jour les proprits ressemble ceci :
my $ h o s t = H e b e x :: S y s I n f o :: DWH :: Host - > new ( host_name => $hostname ); # general operating system information my $ o s _ p r o p = o s _ p r o p e r t i e s ( $ x m l d o c ) ; $host - > o s _ t y p e ( $ o s _ p r o p - >{ t y p e } ) ; $host - > o s _ n a m e ( $ o s _ p r o p - >{ n a m e } ) ; $host - > o s _ k e r n e l ( $ o s _ p r o p - >{ k e r n e l } ) ; $host - > o s _ v e r s i o n ( $ o s _ p r o p - >{ v e r s i o n } ) ; # CPU i n f o r m a t i o n $host - > c p u _ n a m e ( $xmldoc -> findvalue ( "/ conf / processor / name ") ); $host - > c p u _ v e n d o r ( $xmldoc -> findvalue ( "/ conf / processor / vendor ") ); $host - > c p u _ q u a n t i t y ( $xmldoc -> findvalue ( "/ conf / processor / quantity ") ); $host - > c p u _ f r e q u e n c y ( $xmldoc -> findvalue ( "/ conf / processor / frequency ") ); $host - > c p u _ c a c h e ( $xmldoc -> findvalue ( "/ conf / processor / cache " ) ); # network information $host - > d e f a u l t _ g a t e w a y ( $ d e f a u l t _ g a t e w a y ) ;

232

CHAPITRE 12 Abstraction du SQL, ORM et bases non-SQL

Lauteur ne prtend pas que ceci est la meilleure manire de procder, mais afrme en tout cas que cela marche bien. . . Et sil est vrai que la complexit globale de lapplicatif nest pas triviale, la complexit locale de chaque composant est trs raisonnable, ce qui assure une maintenance plus facile du code. La sparation entre le code SQL et le code Perl est un point important, qui permet le partage du SQL entre plusieurs programmes indpendants, vitant les problmes lis par exemple aux modications de schma.

ORM avec DBIx::Class


Perl dispose, depuis de nombreuses annes, de frameworks ORM qui apparaissent au fur et mesure de lapparition de nouveaux besoins en capitalisant lexprience acquise sur le framework prcdent. Ainsi, le monde Perl a vu se succder des modules comme MLDBM qui ds 1996 sappuyait sur des bases BerkeleyDB pour stocker des structures Perl, puis Alzabo, Pixie et SPOPS au dbut des annes 2000, suivis des excellents Tangram et Class::DBI jusquau milieu de cette mme dcennie. ORM
Un ORM (Object-Relational Mapping) est un mcanisme permettant de transformer une base de donnes en une collection dobjets au niveau du langage de programmation. Il existe des ORM dans tous les langages. Le point important dun ORM est de pouvoir interroger une base sans jamais devoir crire de requtes SQL, le moteur de lORM se chargeant de les gnrer, les excuter et de traduire les rsultats sous forme objet, la seule que voit lutilisateur.

ORM avec DBIx::Class

233

Lintrt, outre la manipulation dobjets rendue a priori plus facile compare la manipulation denregistrements relationnels , rside dans lajout dun tage qui constitue une copie locale des donnes de la base, offrant une plus grande libert pour travailler les relations entre ces donnes. Cela implique par contre de devoir synchroniser les objets en retour sur la base, pour sauver les modications, et an que cela fonctionne correctement, que le schma des classes dobjets soit en concordance avec le schma de la base.

Depuis 2005, lORM en Perl se ralise principalement au travers de DBIx::Class, qui est originellement n du besoin dune refonte de Class::DBI pour le framework web Catalyst. DBIx::Class est en ralit un framework pour crer des ORM ; mais proposant par dfaut une API (originellement celle de Class::DBI), il est donc sufsamment extensible pour que le besoin dun nouveau modle ne se fasse pas sentir pour le moment, du moins dans le cadre des bases SQL relationnelles.
DBIx::Class rend les oprations de manipulation et de

synchronisation trs simples. Il sait crer une base partir dun schma objet, et inversement il sait construire un modle objet partir dune base existante. Il propose galement des outils facilitant la migration et la mise jour de schmas. Dans le schma objet de DBIx::Class, les tables sont dnies par des classes Result qui indiquent leurs colonnes et relations avec les autres tables. Les requtes pour interroger ces tables sont modlises par des classes ResultSet. Un point intressant noter est que lutilisation dun ResultSet ne gnre pas daccs la base. Seule la rcupration des donnes va effectivement excuter les requtes SQL sous-jacentes et rapatrier les enre-

234

CHAPITRE 12 Abstraction du SQL, ORM et bases non-SQL

gistrements. Le but est de permettre de travailler le plus possible sur des ensembles virtuels, an dconomiser les accs la base. Les donnes sont fournies sous la forme dobjets DBIx::Class::Row.

Crer un schma DBIx::Class


Sans grande originalit, lexemple typique du monde des ORM sera utilis ici, soit une base de donnes rfrenant une collection musicale. Son schma SQL est le suivant :
CREATE TABLE artist ( artist_id INTEGER PRIMARY KEY, name TEXT NOT NULL ); CREATE TABLE album ( album_id INTEGER PRIMARY KEY, artist INTEGER NOT NULL REFERENCES artist(artist_id), title TEXT NOT NULL ); CREATE TABLE track ( track_id INTEGER PRIMARY KEY, album INTEGER NOT NULL REFERENCES album(album_id), title TEXT NOT NULL );

Il convient de dnir les quelques classes mentionnes, en commenant par la classe mre Schema :

Crer un schma DBIx::Class

235

p a c k a g e M u s i c D B :: S c h e m a ; use p a r e n t qw < D B I x :: C l a s s :: S c h e m a >; my $ c l a s s = _ _ P A C K A G E _ _ ; $class - > l o a d _ n a m e s p a c e s () ; 1;

Puis une classe Result correspondant une table artist :


p a c k a g e M u s i c D B :: S c h e m a :: R e s u l t :: A r t i s t ; use p a r e n t qw < D B I x :: C l a s s :: C o r e >; my $ c l a s s = _ _ P A C K A G E _ _ ; $class - > t a b l e ( " a r t i s t " ) ; $class - > a d d _ c o l u m n s ( qw < a r t i s t _ i d n a m e >) ; $class - > s e t _ p r i m a r y _ k e y ( " a r t i s t _ i d " ) ; $class - > h a s _ m a n y ( a l b u m s = > " M u s i c D B :: S c h e m a :: R e s u l t :: A l b u m " ); 1;

Une classe pour la table album :


p a c k a g e M u s i c D B :: S c h e m a :: R e s u l t :: A l b u m ; use p a r e n t qw < D B I x :: C l a s s :: C o r e >; my $ c l a s s = _ _ P A C K A G E _ _ ; $class - > l o a d _ c o m p o n e n t s ( " I n f l a t e C o l u m n :: DateTime "); $class - > t a b l e ( " a l b u m " ) ; $class - > a d d _ c o l u m n s ( qw < a l b u m _ i d a r t i s t _ i d t i t l e y e a r >) ; $class - > s e t _ p r i m a r y _ k e y ( " a l b u m _ i d " ) ; $class - > b e l o n g s _ t o ( a r t i s t = > " M u s i c D B :: S c h e m a :: A r t i s t " , " artist_id "); 1;

236

CHAPITRE 12 Abstraction du SQL, ORM et bases non-SQL

Et enn une classe pour la table track :


p a c k a g e M u s i c D B :: S c h e m a :: R e s u l t :: T r a c k ; use p a r e n t qw < D B I x :: C l a s s :: C o r e >; my $ c l a s s = _ _ P A C K A G E _ _ ; $class - > t a b l e ( " t r a c k " ) ; $class - > a d d _ c o l u m n s ( qw < t r a c k _ i d a l b u m t i t l e >) ; $class - > s e t _ p r i m a r y _ k e y ( " t r a c k _ i d " ) ; $class - > b e l o n g s _ t o ( a l b u m = > " M u s i c D B :: S c h e m a :: R e s u l t :: A l b u m " , " album_id ");

En fait, ces classes sont terriblement sommaires, an de rester lisibles, mais il manque ici toute la notion de typage, alors que DBIx::Class permet des dnitions de types et de relations au moins aussi avances quen SQL. Toutefois, cela ne prsente que peu dintrt de devoir tout crire la main, surtout quand il existe dj un module pouvant sen charger, DBIx::Class::Schema::Loader. Comme son nom lindique, ce module charge le schma depuis la base de donnes pour en extraire le plus dinformations possible et gnrer les classes Perl correspondantes. Il est fourni avec une commande dbicdump qui simplie encore le travail. Son utilisation est la suivante :
dbicdump [-o <opt>=<value> ] <schema> <connect_info>

o opt est une option de DBIx::Class::Schema::Loader::Base, schema le nom de la classe de base qui dnit le schma et connect_info les informations de connexion, cest--dire le DSN et les ventuels nom dutilisateur et mot de passe. Avec lexemple prcdent, la commande excuter est :
dbicdump -o dump_directory=lib MusicDB::Schema \ dbi:SQLite:dbname=music.db

Utiliser un schma DBIx::Class

237

et elle gnre dans le rpertoire lib/ les classes prcdemment montres, mais en bien mieux car celles-ci comprennent maintenant toutes les informations de types et de relations extraites du schma SQL. Et de plus, les modules disposent dune documentation minimale rappelant ces informations.

Utiliser un schma DBIx::Class


Il faut avant tout charger le schma et se connecter la base :
use M u s i c D B ; my $ m u s i c = M u s i c D B - > c o n n e c t ( " dbi : S Q L i t e : d b n a m e = m u s i c . db " ) ;

Un album peut alors se rcuprer directement par son identiant, au travers du ResultSet correspondant de la table album :
my $ a l b u m = $music - > r e s u l t s e t ( " A l b u m " ) - > find (32) ;

Lobjet reprsente une copie en mmoire de lenregistrement en base de lalbum, directement modiable :
$album - > t i t l e ( " F a i r y D a n c e " ) ;

Pour reporter les modications en base, il faut invoquer la mthode update() :


$album - > u p d a t e ;

Et inversement, pour jeter dventuelles modications locales :


$album - > d i s c a r d _ c h a n g e s if $album - > is_changed ;

238

CHAPITRE 12 Abstraction du SQL, ORM et bases non-SQL

Une recherche partir du nom de lartiste peut se raliser au travers dun ResultSet :
my $rs = $music - > r e s u l t s e t ( " A l b u m " ) - > s e a r c h ( { artist => " Kokia " } );

qui peut se parcourir comme un itrateur :


w h i l e ( my $ a l b u m = $rs - > n e x t ) { say $album - > t i t l e ; }

En contexte de liste, search() renvoie directement les objets rsultat :


my @ a l b u m s = $music - > r e s u l t s e t ( " A l b u m " ) - > search ( { artist => " Aural Vampire " } );

Stocker des objets avec KiokuDB


Si les ORM offrent une reprsentation objet dune base de donnes, ils ne permettent toutefois pas directement de stocker des objets Perl natifs. Les premires tentatives en la matire ne sont pas rcentes puisque ctait lun des buts de Pixie ds 2000. Toutefois, la tche ntait pas aise cause du modle objet de base, trs (trop) laxiste. Depuis, un changement est survenu avec Moose, et surtout le mcanisme sous-jacent, le protocole mta-objet (MOP), qui permet de construire des classes et des objets avec des fonctionnalits avances, tout en conservant une capacit complte dintrospection. Cest sur ces bases que Yuval Kogman sest appuy pour concevoir KiokuDB, un moteur de stockage de graphes

Se connecter une base KiokuDB

239

dobjets. En effet, dans un application un tant soit peu complexe, un objet est gnralement reli dautres objets, ce qui forme un vritable graphe en mmoire. Srialiser un objet ncessite donc souvent de pouvoir srialiser le graphe complet. KiokuDB propose un framework transparent pour stocker les objets Perl. Il est spcialement conu pour les objets sappuyant sur Moose, mais supporte aussi les objets natifs simples .

Se connecter une base KiokuDB


connect()

Son utilisation est remarquablement simple. Premire tape, se connecter. Sans surprise, KiokuDB permet dutiliser nimporte quelle base relationnelle classique au travers de DBI, par exemple, une petite base SQLite :
my $db = K i o k u D B - > c o n n e c t ( " dbi : S Q L i t e : d b n a m e = a p p _ o b j e c t s . db " , create => 1, );

et bien sr, MySQL ou PostgreSQL :


my $db = K i o k u D B - > c o n n e c t ( " dbi : Pg : d a t a b a s e = a p p _ o b j e c t s " , user => " app_user " , password => " sekr3t " , create => 1, );

Mais KiokuDB propose aussi des systmes de stockage diffrents comme BerkeleyDB ou de simples chiers :
# u t i l i s a t i o n de B e r k e l e y D B my $db = K i o k u D B - > c o n n e c t ( " bdb : dir =/ p a t h / to / s t o r a g e " ,

240

CHAPITRE 12 Abstraction du SQL, ORM et bases non-SQL

create => 1, ); # u t i l i s a t i o n de f i c h i e r s my $db = K i o k u D B - > c o n n e c t ( " f i l e s : dir =/ p a t h / to / s t o r a g e " , s e r i a l i z e r = > " y a m l " , # S t o r a b l e par df . create => 1, );

Ou encore les bases non-SQL comme CouchDB, MongoDB et Redis (voir page 243) :
my $db = K i o k u D B - > c o n n e c t ( " c o u c h d b : uri = h t t p : / / 1 2 7 . 0 . 0 . 1 : 5 9 8 4 / database " , create => 1, );

Il est aussi possible de fournir connect() le chemin vers un chier de conguration en YAML.

Stocker et rcuprer des objets


new_scope() store()

Une fois connect, le stockage dun objet est aussi simple que ces quelques lignes :
$db - > n e w _ s c o p e () ; my $ u u i d = $db - > s t o r e ( $ o b j ) ;

La mthode new_scope() cre une nouvelle porte dans laquelle raliser les oprations sur un ensemble dobjets. Cest un moyen simple propos par KiokuDB pour masquer

Stocker et rcuprer des objets

241

autant que possible le problme des rfrences circulaires, qui pourraient sinon conduire une non-libration de la mmoire. La mthode store() stocke lobjet et renvoie un identiant unique, par dfaut un UUID. Il est aussi possible de fournir directement lidentiant :
$db - > s t o r e ( $id = > $ o b j ) ;

Pour rcuprer lobjet, il suft donc de connatre son identiant :


my $ o b j = $db - > l o o k u p ( $ u u i d ) ;

Considrons un petit exemple. Voici une classe :


use M o o s e X :: D e c l a r e ; class Node { has x = > ( isa = > " Num " , is = > " rw " , default => 0.0) ; has y = > ( isa = > " Num " , is = > " rw " , default => 0.0) ; has n a m e = > ( isa = > " Str " , is = > " rw " , default => ""); has p e e r = > ( isa = > " N o d e " , is = > " rw " , w e a k _ r e f = > 1) ; };

dont deux objets sont instancis et lis entre eux, ce qui cre un graphe (certes simple, mais un graphe tout de mme) :
my $ n o d e _ A = Node - > new ( n a m e = > " A " , x = > 1 , y = > 1) ; my $ n o d e _ B = Node - > new ( n a m e = > " B " , x = > 5 , y = > 1) ; $node_A -> peer ( $node_B );

242

CHAPITRE 12 Abstraction du SQL, ORM et bases non-SQL

Leur stockage en base se fait naturellement :


$db - > n e w _ s c o p e () ; $db - > s t o r e ( n o d e _ A = > $ n o d e _ A , n o d e _ B = > $node_B );

Le graphe comprenant ces deux objets est maintenant stock, avec leur relation. Si plus tard, lun deux est rcupr :
$db - > n e w _ s c o p e () ; my $ n o d e _ A = $db - > l o o k u p ( " n o d e _ A " ) ;

le graphe complet est charg, an que lattribut peer de cet objet ait bien quelque chose de correct au bout :
print Dumper ( $node_A -> peer ); # affiche : # $VAR1 = bless ( { # y => 1, # name => B , # x => 5 # }, Node );

Administrer une base KiokuDB


Chose bien utile, une interface en ligne de commande est disponible via le module KiokuDB::Cmd qui installe la commande kioku. Celle-ci permet deffectuer des oprations de maintenance sur une base KiokuDB, pour modier des entres (en les prsentant dans un format lisible comme YAML ou JSON), vrier et nettoyer les rfrences internes.

Utiliser une base oriente paires de cl-valeur

243

Utiliser une base oriente paires de cl-valeur


Dans la catgorie des bases non-SQL, on trouve notamment Memcached, Tokyo Cabinet et Redis. Memcached est en ralit un cache mmoire partag, donc volatile, mais trs utile pour justement mettre en mmoire des donnes temporaires. Tokyo Cabinet et Redis sont par contre des bases persistantes, qui enregistrent les donnes. Bases de donnes non-SQL
De nouveau types de bases de donnes sont apparus (ou rapparus) durant ces dernires annes, dont la principale caractristique est de ntre pas relationnelles et orientes enregistrements comme les bases SQL, mais plutt orientes paires (de cl-valeur) ou orientes documents, et doffrir des API natives plutt quun langage de requte comme SQL. Do leur regroupement derrire le nom gnrique de bases non-SQL, bien que cela soit un peu abusif puisque cela met sur le mme plan des bases aux caractristiques trs diffrentes.

Elles ont des API logiquement assez proches. Ainsi pour Memcached :
use C a c h e :: M e m c a c h e d ; my $ m e m d = C a c h e :: M e m c a c h e d - > new ( servers => [ " 10.0.0.25:11211 " , " 10.0.0.26:11211 " ], ); # a j o u t de v a l e u r s $memd - > set ( t h e _ a n s w e r = > 42) ;

244

CHAPITRE 12 Abstraction du SQL, ORM et bases non-SQL

# m a r c h e a u s s i a v e c des s t r u c t u r e s $memd - > set ( r e s u l t _ s e t = > { l i s t = > [ ... ] }) ; # r c u p r a t i o n des v a l e u r s my $ v a l = $memd - > get ( " t h e _ a n s w e r " ) ; my $ s e t = $memd - > get ( " r e s u l t _ s e t " ) ;

Et avec Redis :
use R e d i s ; my $ r e d i s = Redis - > new ( s e r v e r = > " 1 0 . 0 . 0 . 2 5 : 6 3 7 9 " ) ; # a j o u t de v a l e u r s $redis - > set ( t h e _ a n s w e r = > 42) ; # r c u p r a t i o n de v a l e u r s my $ v a l = $memd - > get ( " t h e _ a n s w e r " ) ;

Le module Perl qui fournit le support Redis ne permet pas encore de stocker directement des rfrences, mais il est toujours possible de les srialiser manuellement.

Utiliser une base oriente documents


La notion de document doit ici se comprendre dans un sens assez gnrique. Il sagit au nal de structures stockes en ltat. La base la plus connue dans cette catgorie est CouchDB, qui utilise JSON (voir page 308) comme format. Plusieurs modules CPAN, aux API assez diffrentes, sont disponibles pour accder CouchDB, tels que POE::Component::Client::CouchDB, CouchDB::Client ou encore AnyEvent::CouchDB. Petit exemple avec ce dernier :

Utiliser une base oriente documents

245

use A n y E v e n t :: C o u c h D B ; my $ c o u c h = c o u c h ( h t t p :// l o c a l h o s t : 5 9 8 4 / ) ; my $db = $couch - > db ( " a p p _ o b j e c t s " ) ; my $ h o s t = $db - > o p e n _ d o c ( " f r o n t 0 1 . d o m a i n . net " ) - r e c v ; $host - >{ os }{ a r c h } = " x 8 6 _ 6 4 " ; $db - > s a v e _ d o c ( $ h o s t ) - > r e c v ;

Lun des intrts de ces bases est de fournir un mcanisme pour stocker des objets sous une forme srialise et portable (comme JSON), ce qui permet de les interroger depuis nimporte quel langage de programmation, alors que les bases dobjets natifs comme KiokuDB ne peuvent bien videmment fonctionner quavec des programmes Perl. CouchDB en particulier, par son stockage JSON, est mme directement utilisable depuis du code JavaScript excut au sein dune page web.

13
Dates et heures
La gestion du temps en informatique est un problme dlicat car les concepts humains reprsentant le temps, les dates et les dures sont multiples, complexes et changeants. Pire encore, il sagit dun problme qui est mme souvent dordre politique, dampleur internationale. Cette description peut paratre exagre, mais hlas la gestion correcte des dates et heures tient du casse-tte eschrien. Voici quelques petits exemples pour sen convaincre. Premirement, le calcul dune anne bissextile : cela parat une opration assez simple effectuer. Il sagit dune anne dont le millsime est divisible par 4, sauf celles divisibles par 100, mais en comprenant celles divisibles par 400. Ainsi, si 2000 et 2008 sont bissextiles, 1800, 1900 et 2010 ne le sont pas. (Et pour tre complet, il faut signaler quil existe encore un dcalage de trois jours sur un cycle de 10 000 ans compenser.) Le calcul est a priori simple, et pourtant les consoles PlayStation 3 ont t affectes par un problme le 1er mars 2010 : celles-ci se croyaient dans une anne bissextile, et donc un 29 fvrier. Do le dysfonctionnement de nombreux services. Le problme navait pas t dtect auparavant car la PS3 ayant t commercialise en novembre 2006, 2010 tait la premire anne paire non bissextile pour laquelle elle devait faire ce calcul (erron. . .).

248

CHAPITRE 13 Dates et heures

Microsoft avait affront un problme similaire le 31 dcembre 2008, lorsque ses lecteurs multimdia portables Zune (vendus seulement aux tats-Unis) se sont tous bloqus, fuseau horaire aprs fuseau horaire, au fur et mesure que ceux-ci passaient lanne. En effet, 2008 tait une anne avec une seconde intercalaire, ajoute la n du 31 dcembre, note 23:59:60. Pas de chance pour Microsoft, le code de gestion des dates de Freescale ne grait pas cette seconde, do le blocage gnral. Et propos de fuseaux horaires, sur combien de fuseaux stend la Russie ? Neuf depuis le 28 mars 2010, date laquelle, sur dcision du Prsident Dmitri Medvedev, deux des onze fuseaux jusqualors utiliss ont t abandonns (UTC+4 et UTC+12). Enn, le passage de lheure dhiver lheure dt ne seffectue pas au mme moment dans tous les pays. Pire, pour certains tats fdraux comme les tats-Unis dAmrique, la dcision se prend au niveau local et non fdral, do certains tats qui, bien que situs dans un mme fuseau horaire, ne sont pas la mme heure. La base tz
Les tats publient bien videmment les informations lies par exemple aux changements dhoraire, mais il nexiste pas dorganisme international qui regrouperait toutes ces donnes pour les compiler. Fort heureusement, lapparition des rseaux informatiques a permis la mise en place dune collaboration internationale pour la constitution dune base de donnes prvue pour lusage informatique, compilant justement les informations publies par les tats et organismes en charge de la gestion du temps. Cette base est nomme base tz, pour timezone, ou encore base Olson, daprs Arthur David Olson qui a

Utiliser le module Date::Parse

249

initi le projet en 1986 et a publi dans le domaine public le code source pour utiliser les donnes de cette base, aujourdhui principalement maintenue par Paul Eggert. Un aspect important de la base Olson est quelle dnit les zones de temps (time zones) qui donnent leur nom cette base, mais qui sont trs diffrentes des fuseaux horaires. En effet, ces zones sont dnies comme des rgions gographiques o les horloges locales sont en accord depuis 1970, une zone pouvant couvrir un tat ou une partie dun tat. Il faut noter que cette dnition, contrairement celle des fuseaux horaires, nest pas normative. Ces zones sont nommes suivant le schma aire/zone, par exemple Europe/Paris , ou America/New_York , tous les noms de lieux tant en anglais. Laire est le nom dun continent (Asia, Africa) ou dun ocan (Atlantic, Pacic). La plupart des zones correspondent par construction un pays, sauf pour certains pays, en particulier les plus tendus (Russie, tatsUnis, Canada, Chine, Brsil) qui sont diviss en plusieurs zones. Dans tous les cas, les zones ont pour nom celui de la ville la plus peuple ou la plus reprsentative de la rgion.

Utiliser le module Date::Parse


Une date peut tre crite sous des formats trs divers : ISO-8601 dans le meilleur des cas, ctime(3) qui se rencontre dans bien des chiers de journalisation, timestamp Unix dans certains cas, ou encore des formats personnaliss (comme celui dApache). Dans certains cas, il peut manquer des informations. Un cas typique est celui des journaux de serveurs de mails (Sendmail, Postx) dans lesquels lanne et le fuseau horaire sont souvent absents.

250

CHAPITRE 13 Dates et heures

Il est bien sr possible de vouloir grer cela la main , mais pourquoi se compliquer la vie et perdre du temps dcouvrir tous les problmes quil faut rsoudre quand un module de la qualit de Date::Parse est disponible sur CPAN ? Il est en effet capable danalyser un grand nombre de formats diffrents, et supporte mme plusieurs langues. Son interface est constitue des deux fonctions str2time() et strptime() qui toutes deux acceptent une chane en argument, ainsi quun nom de zone en second argument optionnel.

Lire une date avec Date::Parse


str2time(..)

str2time() donne comme rsultat le timestamp Unix cor-

respondant :
use D a t e :: P a r s e ; # format Apache my $ d a t e = " 21/ Jun / 2 0 1 0 : 2 3 : 0 9 : 1 7 + 0 2 0 0 " ; my $ t i m e = s t r 2 t i m e ( $ d a t e ) ; print $time ; # "1277154557"

Interprter une date avec Date::Parse


strptime(..)

strptime() renvoie une liste de valeurs correspondant aux

diffrentes composantes de la date :

Changer la langue avec Date::Language

251

use D a t e :: P a r s e ; # format Apache my $ d a t e = " 21/ Jun / 2 0 1 0 : 2 3 : 0 9 : 1 7 + 0 2 0 0 " ; my ( $ss , $mm , $hh , $day , $month , $year , $zone ) = strptime ( $date ); $ m o n t h += 1; $ y e a r += 1 9 0 0 ; p r i n t " $ d a y / $ m o n t h / $ y e a r - $hh : $mm : $ss ( $ z o n e ) " ; # affiche "21/6/2010 - 23:09:17 (7200) "

Info
Ici, $zone est le dcalage en secondes de la zone par rapport GMT. Il faut noter que strptime() suit les mmes conventions que localtime() et les autres fonctions internes de gestion du temps, et que le mois est donc de 0 11, et lanne est retranche de 1900. Il est donc ncessaire de corriger ces deux valeurs pour toute utilisation o les intervalles normaux sont attendus, que ce soit pour un afchage destin un humain ou comme argument un module comme DateTime.

Changer la langue avec Date::Language


Date::Language->new("French);

Par dfaut, Date::Parse nanalyse que les dates en anglais, mais supporte prs dune trentaine de langues diffrentes par le biais du module Date::Language, qui propose une API proche si ce nest quil faut crer un objet correspondant la langue danalyse :

252

CHAPITRE 13 Dates et heures

use D a t e :: L a n g u a g e ; my $ d a t e = " 21 j u i n 2 0 1 0 0 4 : 1 5 : 3 1 " ; # c r a t i o n de l o b j e t D a t e :: L a n g u a g e # p o u r a n a l y s e r en f r a n a i s my $ l a n g = D a t e :: L a n g u a g e - > new ( " F r e n c h " ) ; my $ t i m e = $lang - > s t r 2 t i m e ( $ d a t e ) ; print " $time "; # affiche "1277086531" my ( $ss , $mm , $hh , $day , $month , $year , $ z o n e ) = $lang - > s t r p t i m e ( $ d a t e ) ; $ m o n t h += 1; $ y e a r += 1 9 0 0 ; p r i n t " $ d a y / $ m o n t h / $ y e a r - $hh : $mm : $ss ( $ z o n e ) " ; # a f f i c h e " 2 1 / 6 / 2 0 1 0 - 0 4 : 1 5 : 3 1 () "

La zone est ici logiquement vide puisque aucune ntait prcise dans la date fournie. Il faut dailleurs noter que strptime() ne fournit des valeurs que sil a pu les dterminer, et renvoie un tableau vide en cas derreur.

Grer les intervalles de temps avec Time::Duration


Un autre besoin trs courant est dafcher sous forme comprhensible le dlai avant la n dune tche, ou la dure entre deux dates. Les conversions sont bien videmment simples crire, mais le module Time::Duration, outre cette petite factorisation, apporte un plus non ngligeable sous la forme dune approximation, dune rduction de linformation an de la rendre plus lisible.

Interprter une dure avec Time::Duration

253

Interprter une dure avec Time::Duration


duration(..)

La fonction de base du module Time::Duration est duration(), qui traduit une dure comprhensible par un tre humain :
print duration (3820) ; # a f f i c h e "1 h o u r and 4 m i n u t e s "

Astuce
Ce nest pas immdiatement visible, mais la fonction a ici arrondi le rsultat. La valeur exacte peut tre obtenue ainsi :
print duration_exact (3820) ; # a f f i c h e "1 hour , 3 m i n u t e s , and 40 s e c o n d s "

Par dfaut, duration() rduit le nombre dunits afcher deux, car un tre humain sinquite par exemple rarement davoir dune prcision de lordre de la seconde si lattente afche est de lordre de lheure. Le nombre dunits afches peut bien sr se contrler par un second argument :
p r i n t d u r a t i o n ( 3 8 4 8 7 5 9 2 , 3) ; # a f f i c h e "1 year , 80 days , and 11 h o u r s " # ce qui r e s t e p l u s l i s i b l e que la v a l e u r # e x a c t e "1 year , 80 days , 10 hours , # 59 m i n u t e s , and 52 s e c o n d s "

254

CHAPITRE 13 Dates et heures

Obtenir la dure partir de maintenant


ago(..) later(..)

Time::Duration propose une srie de fonctions qui per-

mettent den exprimer un peu plus :


p r i n t ago ( 1 2 0 ) ; p r i n t l a t e r (3750 , 1) ; # "2 m i n u t e s ago " # "1 h o u r l a t e r "

Astuce
Lexemple prcdent nest certes pas transcendant, mais ces fonctions ont lavantage dtre un peu plus malignes :
p r i n t ago (0) ; # " r i g h t now " p r i n t ago ( -130) ; # "2 m i n u t e s and 10 s e c o n d s f r o m now " p r i n t l a t e r (0) ; # " right then " p r i n t l a t e r ( - 2 4 6 0 ) ; # "41 m i n u t e s e a r l i e r "

Ainsi, quand ago() reoit une valeur ngative, elle est passe from_now(), et inversement ; de mme pour les fonctions later() et earlier().

Rduire lafchage
concise(..)

Une fonction de Time::Duration, concise(), permet de rduire les chanes produites par les autres fonctions :

Changer la langue avec Time::Duration::fr

255

p r i n t ago ( 5 9 3 8 ) ; # a f f i c h e "1 h o u r and 39 m i n u t e s ago " print concise ( duration (5938) ); # "1 h 3 9 m ago "

Changer la langue avec Time::Duration::fr


use Time::Duration::fr

Lauteur de Time::Duration a pens linternationalisation, et a rendu possible lcriture de sous-classes permettant la localisation. Comme il nexistait pas de version franaise, lun des auteurs de ce livre sen est charg, ce qui ne lui a demand que quelques heures durant un long voyage en train. . . Le module Time::Duration::fr est disponible sur CPAN :
use T i m e :: D u r a t i o n :: fr ; p r i n t d u r a t i o n ( 2 4 3 5 5 0 , 3) ; # "2 jours , 19 heures , et 39 m i n u t e s " p r i n t ago ( 2 4 3 5 5 0 ) ; # " il y a 2 j o u r s et 20 h e u r e s " p r i n t e a r l i e r ( 2 4 3 5 5 0 , 1) ; # "3 j o u r s p l u s tt "

Info
Voici quelques exemples plus concrets :
my $ f i l e = " data - dumper - ex2 " ; my $ a g e = $ ^ T - ( s t a t ( $ f i l e ) ) [ 9 ] ; p r i n t " Le f i c h i e r a t c h a n g " , ago ( $ a g e ) , $ /;

256

CHAPITRE 13 Dates et heures

# " Le f i c h i e r a t c h a n g il y a 3 j o u r s # et 47 m i n u t e s " p r i n t " E x p i r a t i o n : " , f r o m _ n o w ( 7 3 9 0 ) , $ /; # " E x p i r a t i o n : d a n s 2 h e u r e s et 3 m i n u t e s " print " Cette action sera excute " , l a t e r ( 2 4 5 ) , " que la p r c d e n t e . " ; # " C e t t e a c t i o n s e r a e x c u t e 4 m i n u t e s et # 5 s e c o n d e s p l u s t a r d que la p r c d e n t e ."

Utiliser les modules DateTime


DateTime::Duration DateTime::Format

La gestion des dates, heures, dures et autres questions lies est complexe. DateTime est un ensemble de modules CPAN stables et matures qui allient facilit dutilisation pour les cas simples et fonctionnalits puissantes pour rsoudre les problmes complexes : q DateTime : le module principal est DateTime, qui permet de crer des objets correspondant des moments prcis dans le temps.
q

DateTime::Duration : il permet de reprsenter une du-

re, un intervalle entre deux dates.


q

DateTime::Format : accompagn de ses modules dri-

vs, ils permettent de transformer des donnes (gnralement du texte) en dates, et surtout de produire des reprsentations (souvent textuelles) de dates et dures.
q

DateTime::Event : les modules DateTime::Event::* con-

tiennent des listes dvnements connus, pouvant tre utiliss facilement.

Construire une instance DateTime arbitraire

257

Construire une instance DateTime arbitraire


DateTime->new(. . .)

DateTime propose principalement deux constructeurs pour reprsenter un moment dans le temps : new() et now().
use D a t e T i m e ; my $ c h r i s t m a s = D a t e T i m e - > new ( y e a r = > 2010 , m o n t h = > 12 , day = > 25 , );

La variable $christmas est maintenant un objet DateTime, qui reprsente le 25 dcembre 2010. On peut sen persuader en lafchant :
print $christmas ; # a f f i c h e 2 0 1 0 - 1 2 - 2 5 T00 : 0 0 : 0 0

Le seul argument obligatoire new() est year, les autres prendront des valeurs par dfaut. Il est possible dtre plus prcis et de rajouter des paramtres new() :
use D a t e T i m e ; my $ c h r i s t m a s = D a t e T i m e - > new ( y e a r = > 2010 , m o n t h = > 12 , day = > 25 , hour = > 14 , m i n u t e = > 12 , s e c o n d = > 42 , nanosecond => 600000000 , );

258

CHAPITRE 13 Dates et heures

Ici, plus darguments ont t passs, permettant dindiquer presque parfaitement un moment prcis dans le temps. Il manque cependant une notion, celle du fuseau horaire.

Choisir un fuseau horaire


time_zone => Europe/Paris

Par dfaut, lorsque le fuseau horaire nest pas prcis, DateTime utilise un fuseau horaire spcial, dit ottant : lors des calculs et manipulations de temps, les changement de fuseau horaire ne seront pas pris en compte. Pour des situations simples, cest sufsant, mais il est galement possible de spcier le fuseau horaire lors de la cration dun objet DateTime :
use D a t e T i m e ; my $ c h r i s t m a s _ i n _ l o n d o n = D a t e T i m e - > new ( year = > 2010 , m o n t h = > 12 , day = > 25 , time_zone => Europe / London ); my $ c h r i s t m a s _ i n _ p a r i s = D a t e T i m e - > new ( year = > 2010 , m o n t h = > 12 , day = > 25 , time_zone => Europe / Paris );

Obtenir linstant prsent


DateTime->now()

Obtenir lanne

259

Il est facile de crer un objet DateTime correspondant linstant prsent, en utilisant le constructeur now(), qui comme new() peut prendre comme paramtre time_zone.
use D a t e T i m e ; my $ n o w = D a t e T i m e - > now () ; s l e e p 5; my $ f i v e _ s e c o n d s _ l a t e r = D a t e T i m e - > now () ;

Obtenir la date du jour


DateTime->today()

Il existe galement un raccourci pour obtenir un objet date du jour, en utilisant today().
use D a t e T i m e ; my $ t o d a y = D a t e T i m e - > t o d a y () ;

Obtenir lanne
$dt->year() $dt->set_year(..)

Retourne lanne de lobjet. $dt->set_year() permet de mettre jour la valeur. Info


Les objets DateTime ont beaucoup de mthodes pour rcuprer et modier leur attributs. Il est inutile de les dtailler toutes ici, seules les plus importantes sont prsentes.

260

CHAPITRE 13 Dates et heures

Obtenir le mois
$dt->month() $dt->set_month(..)

Retourne le mois comme entier de 1 12. $dt->set_month() permet de mettre jour la valeur.

Obtenir le nom du mois


$dt->month_name()

Retourne le nom du mois, conformment la locale.

Obtenir le jour du mois


$dt->day_of_month()

Retourne le jour du mois, de 1 31. $dt->set_day() permet de mettre jour la valeur.

Obtenir le jour de la semaine


$dt->day_of_week()

Retourne le jour de la semaine, de 1 7.

Obtenir les secondes

261

Obtenir le nom du jour


$dt->day_name()

Retourne le nom du jour de la semaine, conformment la locale.

Obtenir lheure
$dt->hour() $dt->set_hour()

Retourne lheure, de 0 23. $dt->set_hour() permet de mettre jour la valeur.

Obtenir les minutes


$dt->minute() $dt->set_minute()

Retourne les minutes, de 0 59. $dt->set_minute() permet de mettre jour la valeur.

Obtenir les secondes


$dt->second() $dt->set_second()

262

CHAPITRE 13 Dates et heures

Retourne les secondes, de 0 61. Les valeurs 60 et 61 sont utilises pour les secondes intercalaires. $dt->set_second() permet de mettre jour la valeur. Les secondes intercalaires
Les secondes intercalaires sont des secondes. Selon Wikipedia, une seconde intercalaire, galement appele saut de seconde ou seconde additionnelle, est un ajustement dune seconde du Temps universel coordonn (UTC). Et ce, an quil reste assez proche du Temps universel (UT) dni quant lui par lorientation de la Terre par rapport aux toiles. Il sagit dun systme, qui date de 1972, et qui met en place des moments o une seconde est retranche ou ajoute. Cela permet de garder le Temps universel coordonn moins de 0,9 seconde du Temps universel (UT). Les besoin dajout ou de retranchement de ces secondes intercalaires ne peuvent pas tre prvus, le mouvement de la Terre ntant pas prvisible avec une trs grande prcision. Cest pourquoi il est possible davoir une date avec un nombre de secondes suprieur 59, jusqu 61.

Obtenir les nanosecondes


$dt->nanosecond() $dt->set_nanosecond()

Retourne les nanosecondes. Par exemple, 500 000 000 est une demi-seconde. $dt->set_nanosecond() permet de mettre jour la valeur.

Obtenir des dures de temps

263

Obtenir des dures de temps


DateTime::Duration->new()

Un objet de type DateTime reprsente un instant donn. Pour pouvoir manipuler des temps et dates plus facilement, il est trs utile de disposer du concept de dure. La classe DateTime::Duration implmente ce concept. Les objets qui en sont instancies reprsentent des dures arbitraires, qui peuvent tre modies et utilises pour modier ou crer de nouvelles dates. Un objet DateTime::Duration peut sobtenir partir de rien, en utilisant son constructeur new() :
my $ d u r a t i o n = D a t e T i m e :: D u r a t i o n - > new ( years => 1, months => 6, ); # $ d u r a t i o n r e p r s e n t e un an et 6 m o i s

Les paramtres du constructeur sont donns dans le tableau suivant.


Tableau 13.1: Constructeur de DateTime::Duration Paramtre
years months weeks days hours minutes seconds nanoseconds

Description
le nombre dannes le nombre de mois le nombre de semaines le nombre de semaines le nombre dheures le nombre de minutes le nombre de secondes le nombre de nanosecondes

264

CHAPITRE 13 Dates et heures

Astuce
Il est galement possible de crer une dure directement partir de deux dates :
my $ d t 1 = D a t e T i m e - > new ( y e a r = > 2011 , m o n t h = > 12 , day = > 25 , ); my $ d t 2 = D a t e T i m e - > new ( y e a r = > 2010 , m o n t h = > 12 , day = > 25 , ); my $ d u r a t i o n = $dt1 - > s u b t r a c t _ d a t e t i m e ( $ d t 2 ) ; # $ d u r a t i o n r e p r s e n t e la d u r e e n t r e # les d e u x dates , d o n c un an .

Dcaler une date dans le futur


$dt->add(..)

La mthode add() permet de dcaler un objet DateTime dans le futur.


# date reprsentant Nol 2010 my $dt = D a t e T i m e - > new ( y e a r = > 2010 , m o n t h = > 12 , day = > 25 , ); $dt - > add ( y e a r = > 1 ) ; # $dt r e p r s e n t e m a i n t e n a n t N o l 2 0 1 1 $dt - > add ( d a y s = > 1 ) ; # $dt r e p r s e n t e m a i n t e n a n t # le 26 d c e m b r e 2 0 1 1

Dcaler une date dans le pass

265

Les paramtres sont les mmes que ceux du constructeur de la classe DateTime::Duration : donc years, months, weeks, days, hours, minutes, seconds, nanoseconds.

Ajouter une dure


$dt->add_duration(..)

Cette mthode est similaire add(), cependant elle prend en argument une dure, cest--dire un objet DateTime::Duration.
use D a t e T i m e ; use D a t e T i m e :: D u r a t i o n ; my $ d u r a t i o n = D a t e T i m e :: D u r a t i o n - > new ( d a y s = > 15 ) ; my $dt = D a t e T i m e - > now () ; $dt - > a d d _ d u r a t i o n ( $ d u r a t i o n ) ; # $dt est m a i n t e n a n t 15 j o u r s # d a n s le f u t u r

Attention
Il ne faut pas utiliser add() ou add_duration()avec des valeurs ngatives pour dcaler un objet DateTime dans le pass. Pour cela, utiliser la mthode subtract().

Dcaler une date dans le pass


$dt->subtract(..)

La mthode subtract() permet de dcaler un objet DateTime dans le pass.

266

CHAPITRE 13 Dates et heures

# date reprsentant nol 2010 my $dt = D a t e T i m e - > new ( y e a r month day );

= > 2010 , = > 12 , = > 25 ,

$dt - > s u b t r a c t ( y e a r = > 1 ) ; # $dt r e p r s e n t e m a i n t e n a n t n o l 2 0 0 9 $dt - > s u b t r a c t ( d a y s = > 1 ) ; # $dt r e p r s e n t e m a i n t e n a n t # le 24 d c e m b r e 2 0 0 9

Cette mthode est trs similaire add(), et accepte les mme paramtres. La diffrence est que les valeurs sont retranches, et non ajoutes.

Soustraire une dure


$dt->subtract_duration(..)

Cette mthode est similaire subtract(), cependant elle prend en paramtre une dure, cest--dire un objet DateTime::Duration.
use D a t e T i m e ; use D a t e T i m e :: D u r a t i o n ; my $ d u r a t i o n = D a t e T i m e :: D u r a t i o n - > new ( d a y s = > 15 ) ; my $dt = D a t e T i m e - > now () ; $dt - > s u b t r a c t _ d u r a t i o n ( $ d u r a t i o n ) ; # $dt est m a i n t e n a n t 15 j o u r s d a n s le p a s s

Calculer un intervalle de temps

267

Calculer un intervalle de temps


$dt->subtract_datetime(..) delta_md() delta_days() delta_ms()

Cette mthode permet de calculer lintervalle de temps entre deux objets DateTime. Elle retourne un objet DateTime::Duration, relativement lobjet sur lequel elle est applique :
# date reprsentant nol 2010 my $ d t _ 1 0 = D a t e T i m e - > new ( y e a r = > 2010 , m o n t h = > 12 , day = > 25 , ); # date reprsentant nol 2009 my $ d t _ 0 9 = D a t e T i m e - > new ( y e a r = > 2009 , m o n t h = > 12 , day = > 25 , ); my $ d u r a t i o n = $dt_10 - > s u b t r a c t _ d a t e t i m e ( $dt_09 ); # $ d u r a t i o n r e p r s e n t e 1 an

Autre possibilit, les mthodes delta_*. delta_md renvoie une DateTime::Duration qui reprsente une portion de la diffrence entre deux objets DateTime.
$dt - > d e l t a _ m d ( $ d a t e t i m e ) ; $dt - > d e l t a _ d a y s ( $ d a t e t i m e ) ; $dt - > d e l t a _ m s ( $ d a t e t i m e ) ;
q

delta_md() : cette mthode renvoie une dure exprime en mois et en jours.

268

CHAPITRE 13 Dates et heures

delta_days() : cette mthode renvoie une dure exprime uniquement en jours. delta_ms() : cette mthode renvoie une dure exprime uniquement en minutes et secondes.

Attention
Ces mthodes renvoient toujours une dure positive ou nulle.

Les modules formateurs


Il existe beaucoup de modules drivs de DateTime::Format. Ils sont dsigns sous le terme DateTime::Format::*. Par abus de langage, un formateur (ou formatter en anglais) dsigne un de ces modules (par exemple DateTime::Format::Pg), ou bien une instance de ce module. Un formateur est un module qui va permettre tout dabord dinterprter une chane de texte, et de construire un objet DateTime, et galement dutiliser un objet DateTime pour en gnrer une reprsentation textuelle.

Gnrer une reprsentation textuelle dune date


format_datetime(..)

Tous les formateurs ont un point commun : ils implmentent une mthode format_datetime, qui permet de gnrer la reprsentation texte dun objet DateTime. Il suft de lutiliser et dafcher la chane de caractres :

Gnrer une reprsentation textuelle dune date

269

use D a t e T i m e ; use D a t e T i m e :: F o r m a t :: H u m a n ; my $ f o r m a t t e r = D a t e T i m e :: F o r m a t :: Human - > new () ; my $ s p o k e n _ t i m e = $ f o r m a t t e r - > format_datetime ( D a t e T i m e - > now () ); say " The t i m e is now $ s p o k e n _ t i m e " ;

Cet exemple afchera sur la console :


The time is now a little after quarter past eight in the evening

Astuce
Il est possible dattacher directement un formateur un objet DateTime. Ainsi, ds que lobjet en question est utilis dans un contexte de chane1 , le formateur sera utilis de manire transparente. Un exemple est plus parlant :
use D a t e T i m e ; use D a t e T i m e :: F o r m a t :: S t r p t i m e my $ f o r m a t t e r = D a t e T i m e :: F o r m a t :: S t r p t i m e - > new ( pattern => % T ); my $ n o w = D a t e T i m e - > now ( f o r m a t t e r = > $formatter ); say " Il est m a i n t e n a n t : $ n o w " ;

Ce code afchera quelque chose comme : Il est maintenant : 18:15:35

1. Voir le contexte de chane au Chapitre lments du langage page 22.

270

CHAPITRE 13 Dates et heures

La majorit des formateurs prennent des arguments, qui permettent de paramtrer le comportement de la reprsentation texte gnre : nafcher quune partie de linformation (par exemple juste les heures), spcier le langage, lordre des informations, etc.

Interprter une date


parse_datetime(..)

La plupart des formateurs permettent dinterprter une chane de caractres, et de renvoyer un objet DateTime correspondant. Pour cela, il faut gnralement leur donner quelques arguments pour les aider reconnatre la date correctement. Voici un exemple simple avec DateTime::Format::Strptime :
use D a t e T i m e :: F o r m a t :: S t r p t i m e ; my $ f o r m a t t e r = new D a t e T i m e :: F o r m a t :: Strptime ( pattern => % T , locale => en_FR , time_zone => Europe / Paris , ); my $dt = $ f o r m a t t e r - > p a r s e _ d a t e t i m e ( 2 2 : 1 4 : 3 7 ) ;

Attention
Certains formateurs nimplmentent pas linterprtation de dates, mais juste la gnration de texte. Il faut se rfrer leur documentation (par exemple sur CPAN) pour vrier leur fonctionnalits.

Interprter une date

271

Il serait bien trop long dexaminer ici les quatre-vingts modules de formatage DateTime::Format::*. Voici une liste des modules les plus intressants et utiles, consulter sur CPAN : q DateTime::Format::Natural : probablement le plus polyvalent et utile, il permet dinterprter et gnrer des dates qui ont un format courant, que lon retrouve dans les crits. Ce module est paramtrable trs nement, pour sadapter presque toutes les manires quont les hommes dcrire une date.
q

DateTime::Format::Strptime : comme vu prcdemment,

permet de manipuler les reprsentations de dates aux formats strp et strf, trs utiliss en informatique.
q

DateTime::Format::DBI : permet de travailler avec les

reprsentations de date dans les bases de donnes. Voir aussi Oracle, DateTime::Format::SQLite, DateTime::Format::Oracle, etc.
q

DateTime::Format::Human : comme vu prcdemment,

permet de gnrer la date dans un format intelligible pour un humain. DateTime::Format::Human::Duration permet de gnrer et interprter des dures.
q

DateTime::Format::Builder : permet de crer ses propres

formateurs spciques, partir de diffrentes rgles.

14
XML
Cette partie est consacre la manipulation des formats structurs, cest--dire des donnes qui suivent une structure plus ou moins bien dnie. tant pour la plupart en format texte, donc faciles lire, ils semblent simples grer par programmation. Mais cette apparente simplicit est souvent trompeuse en raison dune part de la complexit intrinsque des formats, et dautre part de la grande variabilit de conformit des donnes que le dveloppeur, ou son programme, est amen rencontrer dans la vie relle. Mme sil semble souvent plus rapide, ou facile, de sortir larme des expressions rgulires pour analyser le format, cela savre dans quasiment tous les cas une mauvaise ide. La bonne manire de faire est dutiliser le ou les modules appropris1 , auxquels il faut dlguer le rle de correctement lire et crire les donnes en respectant leur grammaire. Labstraction que ces modules apportent permet aussi, bien souvent, de simplier le code en aval, sans parler du fait que ceux qui seront prsents ici sont intensivement tests et utiliss depuis des annes par un grand nombre de personnes.
1. Ce rappel peut sembler superu, mais les auteurs ont dj crois plus dun analyseur HTML en expressions rgulires, et voudraient ne pas en voir davantage dans la nature. . .

Partie IV Formats structurs

274

CHAPITRE 14 XML

Est-il encore besoin de prsenter XML ? Rappelons que plus quun simple format, il sagit dune grammaire qui permet de dnir des formats, appels applications en terminologie XML. XHTML (la reformulation de HTML conforme XML), SOAP, RSS ou encore OpenDocument (le format des chiers OpenOfce.org) sont des applications XML, mais qui correspondent clairement des formats bien diffrents. Perl dispose de trs nombreux modules lis au traitement du XML. Trop nombreux mme, mais cest le cas dans tous les langages qui ont des bibliothques pour manipuler le XML depuis la premire publication de cette norme en 1998. Modules danalyse, de gnration, dAPI standard ou non daccs au document (DOM, SAX, XPath), ou de transformation (XSLT), Perl dispose de modules pour peu prs tous les cas et avec la plupart, si ce nest toutes, les bibliothques externes qui forment les standards actuels. Nous nallons vraiment examiner que deux modules, car ils sufsent combler la majeure partie des besoins courants. DOM et SAX
Petit rappel pour ceux qui ne connatraient pas certains des nombreux noms bizarres dont regorgent les spcications XML. DOM (Document Object Model) est un modle o le document XML est prsent sous la forme dun arbre ni, manipulable au travers de fonctions normalises. DOM est un standard dict par le W3C, an de dnir une API universelle, o les fonctions ont les mmes noms et mmes arguments quel que soit le langage de programmation. Lensemble de la structure du document tant en mmoire, il est trs facile de parcourir les branches de larbre, rcuprer

Charger un document XML avec XML::LibXML

275

et modier des informations, et mme changer la structure du document en ajoutant ou supprimant des branches. Ce modle est pratique pour les documents de faible taille, mais sa consommation mmoire le rend rapidement inutilisable pour les documents de taille plus importante. La limite exacte dpend de la consommation mmoire admise, mais au del de la centaine de mga-octets, cest gnralement un gaspillage de ressources. SAX (Simple API for XML) fonctionne au contraire sur un modle vnementiel, invoquant des mthodes dnies par le programmeur au fur et mesure de lanalyse. Seul le nud courant est visible, et il nest pas possible daccder une autre partie du document. Trs rapide dexcution, cette mthode consomme aussi peu de ressources puisque seule une petite partie du contenu XML est charge en mmoire chaque instant. Cest donc une technique conue pour le traitement des documents de grande taille, qui ne peuvent tenir en mmoire. Elle est aussi adapte au traitement des ux XML continus. Son inconvnient est logiquement labsence de contexte, qui impose au programme de reconstituer celui dont il a besoin en stockant en parallle les informations ncessaires.

Charger un document XML avec XML::LibXML


Derrire un nom assez peu parlant se cache le couteau suisse du XML en Perl, car XML::LibXML est le binding de la libxml2, la bibliothque maintenue par la Fondation Gnome et le W3C. Il propose, outre lanalyse de XML

276

CHAPITRE 14 XML

avec support des espaces de noms, la validation contre des DTD, lanalyse de document HTML/4, le support de XPath, XPointer, XInclude et offre les API DOM et SAX. Malgr toutes ces fonctionnalits supportes, le module reste tout de mme simple utiliser :
use XML :: L i b X M L ; my $ p a r s e r = XML :: LibXML - > new ; my $ x m l d o c = $ p a r s e r - > p a r s e _ f i l e ( $ p a t h ) ;

Ces quelques lignes sufsent pour analyser un document XML dont on a donn le chemin ($path) et obtenir larbre DOM correspondant dans $xmldoc. Lanalyseur accepte bien sr de nombreuses options, par exemple pour la gestion des erreurs voire lactivation de la validation du document, lexpansion des entits et DTD, le support XInclude, etc.
XML::LibXML accepte comme source le chemin dun chier (parse_le()), un descripteur de chier (parse_fh()) ou encore une chane de caractres (parse_string()).

Lanalyse dune page web nest gure plus complexe :


use LWP :: S i m p l e ; use XML :: L i b X M L ; my $ p a r s e r = XML :: LibXML - > new ; my $ h t m l = get ( " h t t p :// m o n g u e u r s . net / " ) ; my $ h t m l d o c = $ p a r s e r - > p a r s e _ h t m l _ s t r i n g ( $html ) ;

noter que XML::LibXML est bien moins permissif que HTML::Parser sur les documents HTML quil accepte de considrer comme valides, mais il peut savrer utile sur des documents de type XHTML.

Parcourir un arbre DOM

277

Il faut remarquer que XML::LibXML propose depuis peu une nouvelle API, un peu plus souple, sous la forme des mthodes load_xml() et load_html() qui permettent de spcier la source de donnes sous la forme dune option, location pour lire depuis un chier local ou depuis une URL :
my $ x m l d o c = $ p a r s e r - > l o a d _ x m l ( l o c a t i o n = > $location );

string pour lire depuis une chane :


my $ x m l d o c = $ p a r s e r - > l o a d _ x m l ( s t r i n g = > $xml_content );

ou encore IO pour lire depuis un descripteur de chier :


my $ x m l d o c = $ p a r s e r - > l o a d _ x m l ( IO = > $fh ) ;

Ces mthodes peuvent mme tre invoques sous forme de mthodes de classe, le module se chargeant de crer lanalyseur, avec les options par dfaut. Dans tous les cas, lobjet renvoy reprsente larbre DOM du document XML fourni en entre.

Parcourir un arbre DOM


Pour regarder cela plus en dtail, prenons le chier XML suivant comme exemple, qui dcrit des informations sur une machine :
<?xml version="1.0"?> <host> <name>www01.dcf.domain.net</name> <network> <interface name="eth0"> <address type="ipv4" prex="24">10.0.0.132</address> <address type="ipv6" prex="64" >2a01:e35:2f26:dde0:213:51af:fe7c:b4d8</address>

278

CHAPITRE 14 XML

</interface> </network> <os> <type>Mac OS X</type> <version>10.6.4</version> <kernel>Darwin Kernel Version 10.4.0: Fri Apr 23 18:28:53 PDT 2010; root:xnu-1504.7.4~1/RELEASE_I386</kernel> <arch>i486</arch> </os> <uptime>5895074.06</uptime> <!-- ... --> </host>

Le constructeur fournit un objet reprsentant larbre DOM :


my $ d o m = $ p a r s e r - > p a r s e _ f i l e ( " w w w 0 1 . dcf . d o m a i n . net . xml " ) ;

Une premire manire de parcourir cet arbre est dutiliser lAPI DOM :
my $ r o o t = $dom - > d o c u m e n t E l e m e n t () ; my ( $ n o d e ) = $root - > g e t C h i l d r e n B y T a g N a m e ( " n a m e " ) ; p r i n t $node - > n o d e N a m e () , " = " , $node - > t e x t C o n t e n t () , " \ n " ;

Mme si les noms des mthodes sont trs explicites, ce premier bout de code montre tout ce quil faut crire pour simplement accder au premier ls du nud racine. Pour afcher ladresse IPv4 de linterface rseau, en parcourant scrupuleusement larbre, il faut drouler encore plus de code :
my ( $ n e t w o r k ) = $root - > g e t C h i l d r e n B y T a g N a m e ( " n e t w o r k " ) ; my ( $ i n t e r f a c e ) = $network -> getChildrenByTagName (" interface "); my ( @ n o d e s ) =

Parcourir un arbre DOM

279

$interface

-> getChildrenByTagName ("

address "); for my $ n o d e ( @ n o d e s ) { n e x t u n l e s s $node - > n o d e T y p e == XML_ELEMENT_NODE ; if ( $node - > g e t A t t r i b u t e ( " t y p e " ) eq " i p v 4 " ) { p r i n t $node - > t e x t C o n t e n t , " \ n " ;} }

En ralit, il y a moyen de raccourcir un peu et daller directement aux nuds address :


my @ n o d e s = $root - > g e t E l e m e n t s B y T a g N a m e ( " a d d r e s s " ) ; for my $ n o d e ( @ n o d e s ) { if ( $node - > g e t A t t r i b u t e ( " t y p e " ) eq " i p v 4 " ) { p r i n t $node - > t e x t C o n t e n t , " \ n " ;} }

mais cela ne fonctionne bien que si le document a des lments dont les noms sont tous non ambigus, mais en gnral alors peu digestes comme netInterfaceAddress. Si le nom est trop gnrique, comme type ou name, getElementsByTagName() est inutilisable. Cet exemple est un peu injuste car, en ne montrant que la lecture de valeurs, il met en exergue la lourdeur de cette API, qui nest pas imputer XML::LibXML, mais bien au standard DOM Level 3 quil respecte scrupuleusement. Mais pour tre honnte, il faut signaler quelle est extrmement complte, et permet de construire un arbre partir de zro, dajouter de nouveaux nuds, qui peuvent recevoir attributs, textes et nuds ls. Toutefois, il faut bien reconnatre que pour le cas somme toute trs courant de la simple exploitation dun document, DOM nest pas ce quil y a de plus simple. Cest l

280

CHAPITRE 14 XML

o XML::LibXML apporte ce qui fait son plus par rapport bien dautres modules XML, le support de XPath.

Utiliser XPath
XPath est aussi une norme dicte par le W3C, qui permet de naviguer et deffectuer des recherches au sein dun arbre DOM au moyen dun chemin inspir, dans sa forme la plus simple, de la syntaxe des chemins Unix : les noms sont spars par des slashs, . reprsente le nud courant et .. le nud parent. Ainsi, dans le document prcdent, llment qui contient le nom de la machine est accessible par le chemin /host/name, et le type de son systme dexploitation par /host/os/type. Pour les attributs, il suft de prcder leur nom dune arobase : /host/network/interface/@name. Avec un sous-chemin sufsamment non ambigu, un chemin ottant peut tre utilis la place dun chemin absolu, avec un double slash : //os/version. Quand plusieurs lments peuvent correspondre, il est possible de ltrer avec un slecteur, not entre crochets. Le slecteur le plus simple est le numro du nud, comme dans un tableau, sauf que la numrotation commence 1 : //interface/address[1]/@type. La slection peut dailleurs aussi seffectuer sur la valeur dun attribut //interface/address[@type="ipv6], ou encore sur la partie texte de llment //os/kernel[contains(., Kernel)]. Ce dernier exemple montre que XPath propose une petite bibliothque de fonctions permettant de raliser des oprations simples sur les nuds (last(), position(), count(), id(). . .), ainsi que des oprations numriques (sum(), round(). . .), boolennes (not(). . .) et sur les chanes (concat(), contains(), substring(), translate(). . .).

Utiliser SAX

281

Avec XML::LibXML et XPath, rechercher un nud devient bien plus simple :


my ( $ a d d r _ n o d e ) = $dom - > f i n d n o d e s ( // i n t e r f a c e / a d d r e s s [ @ t y p e =" i p v 4 "] ) ;

ndnodes() renvoyant des nuds, lAPI DOM reste par-

faitement utilisable ce niveau :


p r i n t $ a d d r _ n o d e - > t e x t C o n t e n t () , " \ n " ;

La diffrence tant quil faut bien moins de code et quil est plus facile de relire une recherche ralise en XPath quen pur DOM. Il est mme possible daccder directement la valeur :
my $ i p v 4 = $dom - > f i n d v a l u e ( // i n t e r f a c e / a d d r e s s [ @ t y p e =" i p v 4 "] ) ;

Quand la structure du document est bien connue et que le chemin est bien construit, cela permet de rduire le code au strict minimum.

Utiliser SAX
XML::LibXML supporte SAX1 et SAX2, dans les portages dnis par XML::SAX, qui dnit le standard en la matire

dans le monde Perl. La documentation reconnat que ce support nest pas aussi avanc et complet que pour DOM. Cest peut-tre en partie d au fait que, SAX tant n dans le monde Java, il est trs orient objet et ncessite de crer une classe respectant une interface particulire pour raliser le traitement du document. Si cette manire de faire est assez coutumire avec un langage comme Java, elle lest beaucoup moins en Perl, o lhabitude est plutt de spcier les callbacks par rfrence de fonction.

282

CHAPITRE 14 XML

La cration dune classe de traitement SAX reste tout de mme une tche assez simple, puisquil suft dhriter de XML::SAX::Base et de ne dnir que les mthodes qui seront utiles au traitement :
package MySAXHandler ; use s t r i c t ; use p a r e n t " XML :: SAX :: B a s e " ; sub s t a r t _ e l e m e n t { my ( $self , $ e l t ) = @_ ; p r i n t " s t a r t _ e l e m e n t : $elt - >{ N a m e }\ n " ; } sub e n d _ e l e m e n t { my ( $self , $ e l t ) = @_ ; p r i n t " e n d _ e l e m e n t : $elt - >{ N a m e }\ n " ; }

Les noms de ces mthodes sont xs par le standard SAX. Ici, start_element et end_element correspondent de manire assez vidente aux lments ouvrants et fermants. Lexcution de ce bout de code sur le chier XML dexemple afche :
start_element: host start_element: name end_element: name start_element: network start_element: interface start_element: address end_element: address ...

Pour rechercher un lment particulier, il suft donc de ltrer sur son nom, ventuellement en faisant attention au contexte pour viter les problmes de collision. Le code est trivial et sans grand intrt, car l o SAX prend son ampleur, cest dans la notion assez gnrique de ltre quil autorise.

Utiliser SAX

283

En effet, si un ltre SAX traite son ux XML et renvoie le rsultat toujours sous forme XML, cette sortie peut tre fournie en entre un nouveau ltre SAX. Il devient alors possible de chaner ainsi plusieurs ltres les uns la suite des autres, exactement comme cest le cas avec les ltres de texte (grep, sed, cut, awk, etc) dans un shell Unix. Le module XML::SAX::Machines permet ainsi de raliser des chanes (des tubes, dans la terminologie usuelle) de manire trs naturelle. Bien que les ltres montrs dans cet exemple nexistent pas tous, cela permet dillustrer la manire de construire un tel chanage :
use use use use XML :: F i l t e r :: G r e p ; XML :: F i l t e r :: S o r t ; XML :: F i l t e r :: A s T e x t ; XML :: SAX :: M a c h i n e s qw < P i p e l i n e >;

# ne g a r d e r que les t i t r e s de K o k i a # et A u r a l V a m p i r e my $ g r e p = XML :: F i l t e r :: Grep - > new ( // a u t h o r = > qr / K o k i a | A u r a l V a m p i r e / , ); # t r i e r les t i t r e s par nom d a l b u m # et n u m r o de c h a n s o n my $ s o r t = XML :: F i l t e r :: Sort - > new ( Record => " track " , Keys => [ [ " a l b u m " , " a l p h a " , " asc " ] , [ " track - n u m b e r " , " num " , " asc " ] , ], ); my $ p i p e = P i p e l i n e ( $ g r e p = > $ s o r t = > XML :: F i l t e r :: A s T e x t = > \* S T D O U T ); $pipe - > p a r s e _ f i l e ( " music - l i b r a r y . xml " ) ;

284

CHAPITRE 14 XML

Cet exemple permettrait, partant dune hypothtique base de musiques, de ltrer pour ne garder que les titres des artistes Kokia et Aural Vampire, puis trierait les titres restants par nom dalbum (un tri alphabtique) et numro de chanson dans lalbum (un tri numrique). Le rsultat serait alors transform en texte simple, plus lisible, qui serait alors afch sur la sortie standard. Cela permet dillustrer comment fonctionne XML::SAX::Pipeline, qui accepte aussi bien un objet correspondant un ltre SAX que le simple nom du module correspond (auquel cas il le charge automatiquement). Le premier et le dernier argument peuvent tre des chemins passer la fonction open() de Perl, ou des descripteurs de chiers. Le tout est assez magique et donc un peu perturbant matriser, mais permet de raliser des choses tonnantes.
XML::SAX::Machines propose aussi dautres mcanismes de multitraitement, comme XML::SAX::Manifold, qui permet

de rpartir le traitement de diffrentes parties du document entre diffrents ltres, et de fusionner les rsultats la n.

XML::Twig
Les deux approches utilises pour manipuler du contenu XML, le DOM et le SAX, ont chacunes leurs avantages et inconvnients. XML::Twig est une solution qui mlange ces deux approches. Pour simplier, lutilisation typique de XML::Twig se fait comme suit : le contenu XML est parcouru squentiellement grce au SAX parsing, avec une liste de rgles qui permettent didentier des lments XML sur lesquels sarrter. Ds quun tel lment est trouv, le parcours squentiel est arrt, et la branche qui part du nud est charge entire-

Crer un objet XML::Twig

285

ment en mmoire. Il est alors possible de faire du DOM parsing sur cette partie du contenu. Le module XML::Twig sutilise en crant un objet instance, qui permet de charger du contenu, et deffectuer toutes les oprations sur le document dans son ensemble. Pour changer, supprimer ou ajouter du contenu, XML::Twig permet daccder des objets lments (de type XML::Twig::Elt). Ces objets lments reprsentent un tag XML, et proposent une grande quantit de mthodes pour altrer le contenu du document.

Crer un objet XML::Twig


XML::Twig->new(. . .)

La mthode new() permet de crer un objet XML::Twig, indispensable lutilisation de XML::Twig.


my $t = XML :: Twig - > new ()

Une option importante de cette mthode est load_DTD, qui permet de charger les informations de DTD.
my $t = XML :: Twig - > new ( l o a d _ D T D = > 1) ;

Lorsque cette option est dnie une valeur vraie, XML::Twig interprte les instructions relatives aux DTD et charge les chiers DTD correspondants.

286

CHAPITRE 14 XML

Charger du contenu XML avec XML::Twig


$t->parse() $t->parsele() $t->parsele_inplace()

La mthode parse() prend en argument soit une chane de caractres contenant le contenu XML traiter, soit un objet de type IO::Handle, dj ouvert (sur un chier par exemple). Dans la plupart des cas cependant, il est plus utile dutiliser parsele(), qui permet de lire le contenu depuis un chier.
use XML :: T w i g ; my $t = XML :: Twig - > new () ; $t - > p a r s e ( < doc > < elt c o u l e u r =" r o u g e "/ > </ doc > ) ; $t - > p a r s e f i l e ( / un / f i c h i e r . xml ) ; $t - > p a r s e f i l e _ i n p l a c e ( / un / f i c h i e r . xml ) ;

parsele_inplace charge un document XML depuis un

chier ; cependant ce mme chier servira galement stocker le contenu nal modi. Cette mthode est utile pour faire des modications sur un chier XML. Info
Cet exemple de code ne produit rien, et cest normal : contrairement XML::LibXML, les mthodes parse et parsele ne renvoient rien. Ici, il est juste demand au module XML::Twig de charger et parcourir le contenu XML, mais aucune action ou rgle na t donne, donc aucun rsultat nest produit.

Crer des handlers avec XML::Twig

287

Il faut en effet crer au moins un gestionnaire (handler, voir la section suivante) pour interagir avec le document XML.

Crer des handlers avec XML::Twig


$t->setTwigHandlers(..)

Pour pouvoir interagir avec le document XML, il faut crer des gestionnaires (handlers). Ce sont des rgles qui associent une expression de type XPath une rfrence sur une fonction. Voici un exemple de gestionnaire :
{ liste / pays = > \& f o n c t i o n 1 , v i l l e [ @ c a p i t a l e ] = > \& f o n c t i o n 2 , }

Le principe est simple : lors du parcours du document XML, chaque lment (tag XML) parcouru est compar aux expressions XPath des gestionnaires. Lorsquune rgle correspond, la fonction associe est value. Celle-ci reoit en arguments :
q q

Argument 1 : lobjet XML::Twig, (ici $t). Argument 2 : un nouvel objet XML::Twig::Elt (ici $elt), qui correspond llment qui a vri la rgle du gestionnaire.

Dans lexemple prcdent, si un tag XML a pour nom pays et comme parent liste, alors il vrie la premire rgle XPath et la mthode f1() est appele. De manire semblable, si un lment XML a pour nom ville et comporte un attribut capitale, alors f2() est appele. Voici un exemple fonctionnel avec ces gestionnaires :

288

CHAPITRE 14 XML

use XML :: T w i g ; my $t = XML :: Twig - > new () ; my $ x m l = < doc > < liste > . < p a y s nom =" F r a n c e " > . < v i l l e nom =" P a r i s " c a p i t a l e ="1"/ > . < v i l l e nom =" L y o n "/ > . </ pays > . </ liste > </ doc > ; my $ h a n d l e r s = { liste / pays = > \& f1 , v i l l e [ @ c a p i t a l e ] = > \& f2 , }; $t - > s e t T w i g H a n d l e r s ( $ h a n d l e r s ) ; $t - > p a r s e ( $ x m l ) ; sub f1 { my ( $t , $ e l t ) = @_ ; say " p a y s " } sub f2 { my ( $t , $ e l t ) = @_ ; say " c a p i t a l e " }

Attention
Il est important dappeler setTwigHandlers avant parse ou parsele. En effet, les gestionnaires doivent tre en place avant de parcourir le document XML.

Cet extrait de code afche :


capitale pays

En effet, il y a un lment pays qui correspond la premire rgle, et un lment ville (Paris) qui correspond la seconde. Info
La rgle XPath ville[@capitale] est dclenche avant la rgle liste/pays car XML::Twig attend le tag de n dun lment avant dexaminer les rgles. Or, llment ville est lintrieur de llment pays, donc il se ferme avant. XML::Twig dclenche les valuations des rgles les plus profondes dabord : il effectue un parcours en profondeur dans larbre XML.

Ignorer le contenu XML de sortie

289

Produire le contenu XML de sortie


$t->ush() $elt->ush()

Pour pouvoir produire le contenu XML du document, la mthode ush peut tre utilise sur lobjet XML::Twig mais galement sur lobjet lment XML::Twig::Elt (reu en argument des fonctions du gestionnaire).
ush va produire le contenu XML des lments qui ont

t parcourus, et dcharger la mmoire. Il est donc possible de parcourir de trs gros chiers en ush-ant rgulirement le contenu. Lexemple suivant utilise ush sur lobjet XML::Twig (voir page 291 pour des exemples qui utilisent ush sur des objets lments de type XML::Twig::Elt).
use XML :: T w i g ; my $t = XML :: Twig - > new () ; $t - > p a r s e ( < doc > < elt c o u l e u r =" r o u g e "/ > </ doc > ) ; $t - > f l u s h () ; # a f f i c h e le XML en s o r t i e

Ignorer le contenu XML de sortie


$t->purge() $elt->purge()

Il arrive souvent que la tche accomplir sur un document XML soit en lecture seule, cest--dire que les opra-

290

CHAPITRE 14 XML

tions vont consister uniquement rcuprer des informations du document, pas modier le document lui-mme. Ainsi, le contenu XML du document nest pas intressant. Pour signier XML::Twig que le contenu examin jusquici peut tre dcharg de la mmoire, il suft dutiliser purge.

Accder au nom dun lment


$elt->name()

Une fois quun gestionnaire a t mis en place, il est important de pouvoir accder aux donnes de llment qui a vri la rgle.
name() permet dobtenir le nom du tag XML qui a vri-

la rgle. Voici un exemple qui illustre lutilisation de name().


use XML :: T w i g ; my $t = XML :: Twig - > new () ; $t - > s e t T w i g H a n d l e r s ({ v i l l e s /* = > \& f1 }) ; $t - > p a r s e ( < villes > < P a r i s / > < L y o n / > </ villes > ); sub f1 { my ( $t , $ e l t ) = @_ ; # r c u p r e # les p a r a m t r e s say $elt - > n a m e () ; # a f f i c h e le nom }

Ce programme afche :
Paris Lyon

Changer le nom dun lment

291

Attention
La mthode name et les suivantes sappliquent sur un objet XML::Twig::Elt, obtenu en gnral en argument de la fonction associe au gestionnaire. Une erreur trs frquente est dessayer dutiliser ces mthodes sur lobjet XML::Twig, ce qui ne fonctionnera pas

Changer le nom dun lment


$elt->set_name(..)

Pour changer le nom dun tag XML, il suft dutiliser set_name() :


use XML :: T w i g ; my $t = XML :: Twig - > new () ; $t - > s e t T w i g H a n d l e r s ({ v i l l e s /* = > \& f1 }) ; $t - > p a r s e ( < villes > < P a r i s / > < L y o n / > </ villes > ); sub f1 { my ( $t , $ e l t ) = @_ ; # r c u p r e les # paramtres $elt - > s e t _ n a m e ( " N a n c y " ) ; # c h a n g e le nom $elt - > f l u s h () ; } $t - > f l u s h () ;

Ce qui donne en sortie :


<villes><Nancy/><Nancy/></villes>

292

CHAPITRE 14 XML

Obtenir le contenu texte dun lment


$elt->text() $elt->text_only() $elt->trimmed_text();

La mthode text() renvoie le contenu PCDATA et CDATA de llment. Les tags XML ne sont pas retourns, seul leur contenu texte lest. Cette mthode renvoie galement le contenu des lments enfants.
text_only se comporte de la mme manire, mais seul le

contenu de llment est renvoy, pas ceux de ses enfants. trimmed_text est identique text(), la diffrence que les espaces superues sont retires de la chane de caractres retourne. Astuce
La chane no_recurse peut tre passe en argument pour se restreindre au contenu de llment, et ne pas inclure ceux des enfants de llment.

Changer le contenu XML dun lment


$elt->set_inner_xml(..)

Dans certains cas, il est utile de remplacer compltement le contenu dun lment. Il est possible de donner une chane XML lobjet XML::Twig::Elt pour remplacer son

Interagir avec les attributs dun lment

293

contenu (sans changer le tag de llment lui-mme), grce la mthode set_inner_xml :


use XML :: T w i g ; my $t = XML :: Twig - > new () ; $t - > s e t T w i g H a n d l e r s ({ v i l l e s = > \& f1 }) ; $t - > p a r s e ( < villes > </ villes > ) ; sub f1 { my ( $t , $ e l t ) = @_ ; # r c u p r e les p a r a m . $elt - > s e t _ i n n e r _ x m l ( < v i l l e n a m e =" P a r i s "/ > ); } $t - > f l u s h () ;

Le rsultat est le suivant :


<villes><ville name="Paris"/></villes>

Interagir avec les attributs dun lment


$elt->att() $elt->set_att(..) $elt->del_att() . . .

Il est trs facile de manipuler les attributs dun lment XML::Twig::Elt, en utilisant les mthodes suivantes :
q

att($nom_attribut) : cette mthode renvoie la valeur de lattribut dont le nom est $nom_attribut. set_att($nom_attribut, $valeur_attribut) : cette m-

thode change la valeur de lattribut dont le nom est $nom_attribut, la valeur $valeur_attribut.

294

CHAPITRE 14 XML

del_att($nom_attribut) : cette mthode supprime lattribut dont le nom est $nom_attribut. att_exists($nom_attribut) : cette mthode renvoie vrai si llment contient un attribut dont le nom est $nom_attribut.

Voir page 295 pour un exemple de code illustrant lutilisation de ces mthodes.

Interagir avec les lments environnants


$elt->root() $elt->parent() $elt->children(), . . .

Lorsquune rgle du gestionnaire est vrie, XML::Twig charge llment qui vrie la rgle et donne accs larbre de tous les lments qui nont pas encore t ush-s ou purge-s. Ainsi, XML::Twig offre un vritable support DOM, et fournit une liste consquente de mthodes pour naviguer dans larbre du document, en partant de llment, ou de la racine. En voici une slection : q $elt-root() : cette mthode renvoie un objet XML::Twig::Elt qui correspond la racine du document XML.
q

$elt-parent() : cette mthode renvoie un objet XML::Twig::Elt qui correspond au parent de llment en

cours.
q

$elt-children() : cette mthode renvoie la liste des en-

fants de llment. Les lments enfants sont galement de type XML::Twig::Elt.

Interagir avec les lments environnants

295

$elt-ancestors() : cette mthode renvoie la liste des

anctres de llment. Cette liste contient le parent de llment, le parent du parent, etc., jusqu la racine du document.
q

prev_sibling et prev_siblings : prev_sibling renvoie

llment prcdant llment en cours, au mme niveau de larbre XML2 . prev_siblings renvoie la liste des lments de mme niveau prcdant llment en cours.
q

next_sibling et next_siblings : ces mthodes sont les pendants de prev_sibling et prev_siblings, et renvoient

le frre suivant, ou la liste des frres suivants. Info


Toutes ces mthodes peuvent prendre en paramtre une expression XPath optionnelle, qui permet de ltrer les rsultats. Ainsi, pour obtenir tous les enfants dun lment donn qui ont lattribut couleur la valeur bleu, il suft dcrire :
$elt - > c h i l d r e n ( *[ @ c o u l e u r = " b l e u "] ) ;

Cet extrait de code utilise les mthodes prcdentes pour manipuler un document XML complexe :
use XML :: T w i g ; my $t = XML :: Twig - > new () ; my $ x m l = < doc > < liste > . < p a y s nom =" F r a n c e " > . < v i l l e nom =" P a r i s " c a p i t a l e ="1"/ > . < v i l l e nom =" L y o n "/ > . </ pays > . < p a y s nom =" A l l e m a g n e " > . < v i l l e nom =" B e r l i n " c a p i t a l e ="1" > . C e c i est B e r l i n .

2. Sibling veut dire frres et surs .

296

CHAPITRE 14 XML

</ ville > . < v i l l e nom =" F r a n c f o r t " c a p i t a l e ="0"/ > . </ pays > . </ liste > </ doc > ; my $ h a n d l e r s = { pays = > \& f1 , }; $t - > s e t T w i g H a n d l e r s ( $ h a n d l e r s ) ; $t - > p a r s e ( $ x m l ) ; sub f1 { my ( $t , $ e l t ) = @_ ; say " r a c i n e : " . $elt - > root - > n a m e ; say $elt - > n a m e . : . $elt - > att ( nom ) ; my @ e n f a n t s = $elt - > c h i l d r e n ; say " nb e n f a n t s : " . s c a l a r ( @ e n f a n t s ) ; foreach ( @enfants ) { say " - " . $_ - > n a m e . : . $_ - > att ( nom ) ; say " est c a p i t a l e " if $_ - > att ( capitale ); my $ t e x t e = $_ - > t r i m m e d _ t e x t () ; say " $ t e x t e " if l e n g t h $ t e x t e ; } $elt - > p u r g e ; }

Ce programme afche :
racine : doc pays: France nb enfants : 2 - ville: Paris est capitale - ville: Lyon racine : doc pays: Allemagne nb enfants : 2 - ville: Berlin est capitale Ceci est Berlin - ville: Francfort

Effectuer un copier-coller

297

Effectuer un copier-coller
$elt->copy() $elt->paste(..)

La puissance de XML::Twig sillustre dans une fonctionnalit majeure de ce module : le copier-coller. La mthode copy renvoie une copie profonde de llment, cest--dire que tous les lments enfants sont galement copis. La mthode cut coupe llment. Dornavant, il nest plus attach larbre DOM, il nappartient plus au document XML. Cependant il peut tre utilis, notamment en argument la mthode paste. La mthode paste permet de coller un lment qui a t prcdemment copi ou coup. paste prend en argument la position laquelle coller, et llment coller. La position peut tre :
q

before : llment coller est alors plac avant llment

courant. after : llment coller est alors plac aprs llment courant. rst_child : llment coller est insr comme premier enfant de llment courant. last_child : llment coller est insr comme dernier enfant de llment courant.

Attention
La mthode paste() doit tre applique sur llment source, qui a t copi ou coup, et non sur llment cible. Llment cible doit tre pass en argument.

298

CHAPITRE 14 XML

Voici un exemple :
use XML :: T w i g ; my $t = XML :: Twig - > new () ; my $ x m l = < doc > < liste > . < p a y s nom =" F r a n c e " > . < v i l l e nom =" P a r i s " c a p i t a l e ="1"/ > . < v i l l e nom =" L y o n " c a p i t a l e ="0"/ > . </ pays > . < p a y s nom =" A l l e m a g n e " > . < v i l l e nom =" F r a n c f o r t " c a p i t a l e ="0"/ > . </ pays > . </ liste > </ doc > ; my $ h a n d l e r s = { p a y s [ @ n o m =" A l l e m a g n e "] = > \& f1 , }; $t - > s e t T w i g H a n d l e r s ( $ h a n d l e r s ) ; $t - > p a r s e ( $ x m l ) ; $t - > f l u s h () ; sub f1 { my ( $t , $ a l l e m a g n e ) = @_ ; my $ f r a n c f o r t =( $ a l l e m a g n e - > c h i l d r e n () ) [ 0 ] ; say $ f r a n c f o r t - > att ( nom ) ; $ f r a n c f o r t - > cut () ; my $ f r a n c e = $ a l l e m a g n e - > p r e v _ s i b l i n g () ; $francfort -> paste ( last_child => $france ); }

Ce programme produit le document XML suivant :


<doc> <liste> <pays nom="France"> <ville nom="Paris" capitale="1" /> <ville nom="Lyon" capitale="0" /> <ville nom="Francfort" capitale="0"/> </pays> <pays nom="Allemagne"> </pays> </liste> </doc>

Effectuer un copier-coller

299

Autres rfrences
XML::Twig implmente normment de mthodes, certaines tant des synonymes dautres (ainsi $elt-name() est quivalent $elt-tag() et $elt-gi()). Cet aspect apporte beaucoup de puissance, mais est quelque peu droutant pour le dbutant. Cest pourquoi ce prsent chapitre sest concentr sur une slection des concepts et fonctions majeurs de XML::Twig. Pour acqurir une meilleure matrise du module XML::Twig, consultez sa documentation exhaustive sur CPAN : http:// search.cpan.org/perldoc?XML$::$Twig.

15
Srialisation de donnes
La srialisation est lopration consistant transformer une structure de donnes en mmoire en un format pouvant tre transfr ou enregistr sur disque, an de pouvoir relire ce format (on dit dsrialiser) pour reconstituer la structure plus tard, dans le mme programme, ou dans un autre programme. Cela permet par exemple dchanger des donnes complexes entre des programmes, ou de sauvegarder ltat mmoire pour une reprise rapide sur panne. De nombreux formats existent et correspondent des types de besoins diffrents. Nous examinerons en premier lieu les formats natifs, puis JSON et YAML.

Srialiser avec Data::Dumper


use Data::Dumper;

Perl intgre dj en standard des modules permettant de srialiser des structures. Le plus simple est Data::Dumper qui gnre le code Perl permettant de reconstruire la structure par une simple valuation.

302

CHAPITRE 15 Srialisation de donnes

use D a t a :: D u m p e r ; my % c o n t a c t = ( dams => { name => " Damien Krotkine " , e m a i l = > " ... " , }, jq = > { name => " Jrme Quelin " , e m a i l = > " ... " , }, book => { name => " Philippe Bruhat " , e m a i l = > " ... " , }, maddingue => { name => " Sbastien Aperghis - Tramoni " , e m a i l = > " ... " , }, ); my $ s e r i a l i s e d = D u m p e r (\% c o n t a c t ) ; print $serialised ;

Cet exemple afche :


$VAR1 = { jq => { email => ..., name => Jrme Quelin }, dams => { email => ..., name => Damien Krotkine }, maddingue => { email => ..., name => Sbastien Aperghis-Tramoni }, book => {

Srialiser avec Data::Dumper

303

email => ..., name => Philippe Bruhat } };

Pour relire la structure en mmoire, il suft dvaluer la chane ou le chier dans lequel le rsultat de Data::Dumper a t stock, avec respectivement eval ou do.
eval $serialised ;

Data::Dumper est sufsamment intelligent pour dtecter si

une sous-structure est utilise plus dune fois, et il emploiera des rfrences pour viter de dupliquer les donnes.
my % c o n t a c t = ( dams => { name => " Damien Krotkine " , e m a i l = > " ... " , }, jq => { name => " Jrme Quelin " , e m a i l = > " ... " , }, book => { name => " Philippe Bruhat " , e m a i l = > " ... " , }, maddingue => { name => " Sbastien Aperghis - Tramoni " , e m a i l = > " ... " , }, ); my % p r o j e c t _ i n f o = ( perlbook1 => { name => " Perl Moderne " , manager => $contact { dams },

304

CHAPITRE 15 Srialisation de donnes

team => [ @ c o n t a c t { qw < d a m s jq b o o k m a d d i n g u e >} ], }, Curses_Toolkit => { name = > " C u r s e s :: T o o l k i t " , manager => $contact { dams }, team => [ @ c o n t a c t { qw < d a m s jq m a d d i n g u e >} ], }, );

La srialisation de %project_info donne le rsultat suivant :


$VAR1 = { perlbook1 => { name => xxx, team => [ { email => ..., name => Damien Krotkine }, { email => ..., name => Jrme Quelin }, { email => ..., name => Philippe Bruhat }, { email => ..., name => Sbastien Aperghis-Tramoni } ], manager => $VAR1->{perlbook1}{team}[0] },

Srialiser avec Data::Dumper

305

Curses_Toolkit => { name => Curses::Toolkit, team => [ $VAR1->{perlbook1}{team}[0], $VAR1->{perlbook1}{team}[1], $VAR1->{perlbook1}{team}[3] ], manager => $VAR1->{perlbook1}{team}[0] } };

La structure est correctement sauvegarde de faon tre autonome, tout en prservant ses liens internes. Si les deux structures sont srialises en mme temps, Data::Dumper voit mieux les liens, conservant une meilleure cohrence globale :
p r i n t D u m p e r (\% c o n t a c t , \% p r o j e c t _ i n f o ) ;

afche :
$VAR1 = { # contenu de %contact }; $VAR2 = { perlbook1 => { name => xxx, team => [ $VAR1->{dams}, $VAR1->{jq}, $VAR1->{book}, $VAR1->{maddingue} ], manager => $VAR1->{dams} }, # etc. };

306

CHAPITRE 15 Srialisation de donnes

Mais ces exemples mettent en vidence certains aspects ennuyeux de Data::Dumper. En premier lieu, le fait quil affecte les donnes des variables aux noms courts mais disgracieux $VAR1, $VAR2, etc. En mode strict, il faut que ces noms existent, ce qui est plus que gnant. Cela peut se contourner en utilisant une forme plus labore dappel de Data::Dumper, qui permet de donner des noms plus explicites :
p r i n t D a t a :: Dumper - > D u m p ( [ \% c o n t a c t , \% p r o j e c t _ i n f o ] , [ qw < c o n t a c t p r o j e c t _ i n f o >] );

afche :
$contact = { # contenu de %contact }; $project_info = { # contenu de %project_info };

Cest mieux, mais cela impose tout de mme que ces variables devront exister dans le contexte du programme qui chargera ces donnes. Un autre problme assez vident est que, du ct du programme qui va charger les donnes, il va vritablement falloir valuer du code Perl tranger, ce qui est videmment dangereux. Hormis dans les cas o les modules prsents ci-aprs ne seraient pas utilisables, Data::Dumper est donc dconseiller comme solution de srialisation, mais savre un prcieux partenaire de dboggage.

Srialiser avec Storable

307

Srialiser avec Storable


use Storable;

Perl propose un autre module pour la srialisation, Storable. La diffrence majeure avec Data::Dumper est que Storable, crit en XS, srialise sous forme binaire, ce qui offre un stockage beaucoup plus compact et vite les problmes lis lvaluation du code gnr par Data::Dumper. Son API, extrmement simple, rend son utilisation plus agrable :
# c o p i e p r o f o n d e de % c o n t a c t my $ s e r i a l i s e d = f r e e z e (\% c o n t a c t ) ; my $ c o n t a c t s = thaw ( $serialised ); # s a u v e d a n s le f i c h i e r s t o r e (\% p r o j e c t _ i n f o , " p r o j e c t s . dmp " ) ; # r e s t a u r e d e p u i s le f i c h i e r my $ p r o j e c t s = r e t r i e v e ( " p r o j e c t s . dmp " ) ;

Ces fonctions existent aussi en versions portables, qui stockent les octets dans lordre dit rseau : nfreeze(), nstore(). Toutefois, qui dit format binaire, dit problme de compatibilit. Storable ne fait pas exception mais chaque version sait bien sr lire les formats gnrs par les versions prcdentes, et les versions rcentes savent se montrer plus conciliantes. Globalement, Storable fonctionne comme on sy attend. Rapide, bien plus sr, Storable constitue donc une solution de srialisation recommander pour rpondre aux problmes de stockage des donnes en local ou de transfert de structures et objets entre programmes Perl. Bien

308

CHAPITRE 15 Srialisation de donnes

videmment, ds quil faut communiquer avec des programmes crits dans dautres langages, Storable, spcique Perl, devient inappropri, et il faut recourir JSON ou YAML.

JSON
JSON (JavaScript Object Notation) est un format qui drive en fait de la syntaxe normale de JavaScript pour construire les structures, limite la seule sous-syntaxe pour raliser cette tche spcique, pour dvidentes raisons de scurit. La syntaxe est trs naturelle pour un programmeur Perl :
q

Les tableaux sont dclars par des crochets, avec les valeurs spares par des virgules : [ value, . . .]. Les tables de hachages (la norme JSON parle dobjets) sont dclares par des accolades, avec les cls spares de leur valeur par un deux-points et les couples cl-valeur spars entre eux par des virgules : { key: value, . . .}. Les boolens sont dclars avec les littraux true et false. La valeur undef se traduit par le littral null. Les nombres sont reconnus sous forme dcimale, avec partie fractionnelle et notation exponentielle : 42 3.1415269 -273.15 3.086e16 # # # # valide valide valide valide

q q q

0755 # NON valide (octal) 0xdeadbeef # NON valide (hexadcimal)


q

Toute autre valeur doit tre sous forme de chane, entre guillemets, avec les rgles usuelles des chanes en C :

Srialiser avec Storable

309

"The quick brown fox jumps over the lazy dog.\n" "c:\\windows\\system" JSON est par dfaut en UTF-8. Les caractres Unicode du plan de base multilingue (entre U+0000 et U+FFFF) peuvent scrire sous la forme \uXXXX o XXXX est la valeur hexadcimale de quatre chiffres correspondant au code-point du caractre. "J\u00e9r\u00f4me va \u00e0 la p\u00eache en sifant \u266A" Si le caractre nest pas dans le plan de base, il suft dutiliser les paires de seizets dindirection (surrogate pairs). JSON est donc un format assez simple, lger, qui permet de reprsenter de manire directe, et sans trop de rexion, les structures typiques rencontres aussi bien en Perl que dans la plupart des autres langages dynamiques. Il est donc trs utile pour changer des donnes complexes entre des programmes crits dans des langages diffrents. Plus encore, il est parfaitement adapt au contexte du dveloppement web, pour servir dintermdiaire entre les programmes serveurs en Perl et linterface en HTML et JavaScript, o lvaluation du JSON est bien plus rapide que lanalyse du XML, et son exploitation bien plus aise. Raison pour laquelle il a remplac depuis longtemps ce dernier comme format de prdilection dans les techniques dites Ajax.

310

CHAPITRE 15 Srialisation de donnes

Srialiser avec JSON


use JSON;

Bien videmment, il existe en Perl des modules appropris pour gnrer et lire le JSON sans problme. Les deux plus importants retenir sont JSON (dnomm JSON.pm dans la suite pour mieux le distinguer du format lui-mme) et JSON::XS. La diffrence entre les deux est que le premier est crit en pur Perl alors que le second est en C, et donc bien plus rapide. Mais JSON.pm sait utiliser JSON::XS sil est prsent, ce qui simplie la tche. Dans sa forme la plus simple, ce module sutilise ainsi :
use J S O N ; my $ j s o n _ t e x t = t o _ j s o n (\% s t r u c t ) ; my $ p e r l _ s t r u c t = f r o m _ j s o n ( $ j s o n _ t e x t ) ;

Ainsi, avec notre hash de contacts :


my $ j s o n _ t e x t print $json ; = t o _ j s o n (\% c o n t a c t ) ;

cela afche :
{"jq":{"email":"...", "name":"Jrme Quelin"}, "dams":{"email":"...", "name":"Damien Krotkine"}, "maddingue":{"email":"...", "name":"Sbastien Aperghis-Tramoni"}, "book":{"email":"...", "name":"Philippe Bruhat"}}

Les sauts de ligne ont ici t ajouts pour rendre la sortie plus lisible, mais JSON se voulant un format trs compact,

Srialiser avec JSON

311

il est par dfaut gnr sans espace ni saut de ligne. Cela peut se changer en passant des options ces fonctions, sous forme de rfrence un hash :
my $ j s o n _ t e x t = t o _ j s o n (\% struct , { ... } ) ; my $ p e r l _ s t r u c t = f r o m _ j s o n ( $ j s o n _ t e x t , { ... }) ;

Les options les plus intressantes sont : q ascii : tous les caractres Unicode hors ASCII seront transcrits sous la forme \uXXXX, an que le rsultat soit parfaitement compatible avec ASCII (7 bits).
q

latin1 : lencodage se fera en ISO-Latin-1 (ISO-8859-

1), les caractres en dehors de ISO-Latin-1 seront transcris sous la forme \uXXXX.
q q

utf8 : lencodage se fera en UTF-8. pretty : quivalente aux options indent, space_before et space_after, permet un afchage plus lisible pour les humains. relaxed : autorise le dcodage de textes qui ne sont pas

totalement conformes la norme JSON, dans certaines limites, ce qui peut tre utile pour relire des donnes crites par des humains ou par un mauvais gnrateur. En reprenant lexemple prcdent et en ajoutant quelques options :
my $ j s o n _ t e x t = t o _ j s o n ( \% c o n t a c t , { p r e t t y = > 1 , a s c i i = > 1 } ); print $json_text ;

afche :

312

CHAPITRE 15 Srialisation de donnes

{ "jq" : { "email" : "...", "name" : "J\u00e9r\u00f4me Quelin" }, ... }

Comme il serait ennuyeux de devoir passer ces options chaque appel de fonction, JSON.pm permet de crer des objets an davoir son convertisseur JSON personnalis.
my $ j s o n = JSON - > new ; # les m o d i f i c a t e u r s d o p t i o n s r e n v o i e n t # l objet , ce qui p e r m e t de les c h a n e r $json - > p r e t t y (1) - > s p a c e _ b e f o r e (0) ; $json - > a s c i i (1) ; my $ j s o n _ t e x t = $json - > e n c o d e (\% c o n t a c t ) ; print $json_text ;

afche :
{ "jq": { "email": "...", "name": "Jrme Quelin" }, ... }

Si crer un objet est moyennement intressant pour les options assez simples prsentes ici, cela devient par contre indispensable pour exploiter les options avances telles que les ltres ou les mcanismes de gestion des donnes spciales et inconnues. noter que les exemples donns ici utilisent lAPI de la version 2.xx du module JSON.pm, incompatible avec celle

Srialiser avec JSON

313

de la prcdente version. Mme si cest fondamentalement pour le mieux (fonctions mieux nommes, meilleure API objet, plus aucune variable globale), il est fcheux que lauteur nait pas intgr un moyen pour permettre une meilleure transition de la v1 la v2. Pour des usages basiques, les quelques lignes suivantes permettent dutiliser lAPI de JSON.pm v2 y compris avec la v1 :
# use J S O N v2 API e v e n w i t h J S O N v1 if ( JSON - > V E R S I O N < 2 . 0 0 ) { no w a r n i n g s ; * t o _ j s o n = * J S O N :: e n c o d e = * J S O N :: t o _ j s o n = \& J S O N :: o b j T o J s o n ; * f r o m _ j s o n = * J S O N :: d e c o d e = * J S O N :: from_json = \& J S O N :: j s o n T o O b j ; }

Le cas typique dun tel besoin, rencontr par lun des auteurs, est que lenvironnement de production ne peut souvent pas utiliser les versions trs rcentes des modules, de par leur dlai dintgration dans les distributions.

YAML
Par rapport JSON, YAML nest pas tant un format de srialisation quun vritable langage de srialisation. Conu par des dveloppeurs chevronns des langages dynamiques Perl, Python et Ruby, il est disponible pour tous les langages majeurs, ce qui le rend trs intressant pour srialiser des donnes complexes de manire portable. Ses capacits viennent toutefois avec un cot, celui de la complexit de sa spcication. Elle ne sera donc examine ici que dans ses aspects les plus simples. Ce nest toutefois pas trs gnant pour commencer car du point de vue de lutilisateur,

314

CHAPITRE 15 Srialisation de donnes

cela reste transparent, et surtout YAML est bien plus lisible que JSON. YAML possde deux modes de syntaxe : par bloc et en ligne, qui peuvent se mlanger sans grandes restrictions. La syntaxe en ligne est fondamentalement du JSON, ce qui permet tout analyseur YAML de pouvoir relire sans problme un chier JSON. La syntaxe en mode bloc spare chaque donne sur sa ligne propre, et repose fortement sur lindentation pour dterminer en partie la profondeur des structures.
q q

Les commentaires commencent par un dise #. Les valeurs sont reconnues de faon assez naturelle par lanalyseur YAML, et hormis quand elles contiennent des caractres faisant partie de la syntaxe, il nest mme pas ncessaire dencadrer les chanes entre guillemets. # pas besoin de guillemets The quick brown fox jumps over the lazy dog # par contre, dans ce cas, oui "Le monsieur te demande : o es-tu ?"

Les tableaux sappellent des squences et sont nots en mode ligne par des crochets, les valeurs tant spares par des virgules : [ Sbastien, 6, Antibes ] En mode bloc, les donnes sont simplement introduites par un tiret sur la colonne courante : Damien Jrme Philippe Sbastien

Srialiser avec JSON

315

Les tables de hachages sappellent des correspondances (mappings) et sont notes en mode ligne par des accolades, les cls spares de leur valeur par un deux-points et les couples cl-valeur spars entre eux par des virgules : {name: Damien, dept: 94, city: Vincennes} En mode bloc, les couples cl-valeur sont simplement chacun sur leur ligne propre : name: Philippe dept: 69 city: Villeurbanne YAML impose que chaque cl soit unique au sein dune correspondance.

Bien videmment, ces lments peuvent simbriquer pour construire des structures complexes : perlbook1: - title: Perl Moderne - publisher: Pearson - {name: Damien, dept: 94, city: Vincennes} - {name: Jrme, dept: 69, city: Lyon} - {name: Philippe, dept: 69, city: Villeurbanne} - {name: Sbastien, dept: 6, city: Antibes} Un point important est quun chier YAML peut contenir plusieurs documents internes. Un document commence par trois tirets - - -, et continue jusquau document suivant, jusqu une n de document, marque par trois points . . . ou jusqu la n du chier.

316

CHAPITRE 15 Srialisation de donnes

Srialiser avec YAML


use YAML;

Sans surprise, de nombreux modules sont disponibles sur le CPAN, mais il nest vritablement ncessaire den connatre quun seul, tout simplement nomm YAML, qui correspond limplmentation standard, en pur Perl de lanalyseur YAML. Parmi les autres modules qui mritent dtre mentionns se trouvent YAML::Syck qui utilise la bibliothque C libsyck (supporte YAML 1.0), et YAML::XS qui utilise la libyaml (supporte YAML 1.1). Le bon point est que YAML dtecte seul la prsence dun de ces modules, et le charge automatiquement an de bncier de meilleures performances. Dans tous les cas, linterface dutilisation reste la mme, et ressemble un peu celle de Data::Dumper. Ainsi, pour transformer des structures en documents YAML, il suft dutiliser la fonction Dump() :
use Y A M L ; my % b o o k = ( title => " Perl Moderne " , publisher => " Pearson " , ); my % c o n t a c t = ( # t o u j o u r s les m m e s i n f o r m a t i o n s .. ); p r i n t D u m p (\% book , \% c o n t a c t ) ;

afche :

Srialiser avec YAML

317

--publisher: Pearson title: Perl Moderne --book: email: ... name: Philippe Bruhat dams: email: ... name: Damien Krotkine jq: email: ... name: Jrme Quelin maddingue: email: ... name: Sbastien Aperghis-Tramoni

qui contient deux documents YAML, puisque deux structures ont t fournies. La fonction DumpFile() permet de sauver le ux YAML directement dans un chier :
use Y A M L qw < D u m p F i l e >; D u m p F i l e ( " p e r l b o o k 1 . y a m l " , \% book , \% c o n t a c t ) ;

Charger un ux YAML utilise les fonctions inverses Load() et LoadFile(), qui renvoient logiquement autant de rfrences quil y a de documents dans le ux :
use Y A M L qw < L o a d F i l e >; my ( $book , $ c o n t a c t ) = LoadFile (" perlbook1 . yaml ") ;

Le module dispose bien sr de son lot doptions diverses, mais il est rare de devoir y faire appel. Il faut noter que YAML permet aussi de srialiser des objets, mais il faut quils disposent des mthodes adquates.

16
Fichiers de conguration
Quoi de plus banal que de pouvoir congurer un programme ? Pourtant, cest trop souvent un point laiss de ct durant le dveloppement, ajout au dernier moment, lorsque le temps manque, ce qui aboutit bien frquemment cette horreur :
do " $ E N V { H O M E }/ etc / m o n p r o g . pl " ;

Un chier de conguration est un chier de donnes mortes, qui doivent donc imprativement rester spares du code. Deux grands cas dutilisation se distinguent : dun ct, la simple affectation de paramtres, pour lequel le format .INI est trs bien adapt, de lautre, la dnition de structures complexes, pour lesquelles JSON (voir page 308) ou YAML (voir page 313) fournissent enn une rponse universelle.

320

CHAPITRE 16 Fichiers de conguration

Fichiers .INI
use Cong::IniFiles;

Le chier .INI, popularis par Microsoft, permet daffecter des valeurs des paramtres (param = value), euxmmes groups au sein de sections dont le nom est donn entre crochet ([section_name]), et supporte des commentaires, commenant par un point-virgule ( ;) et jusqu la n de la ligne. Toutefois, nayant jamais t vraiment formalis de manire normative, de nombreuses extensions ou variations de ce format existent au gr des diffrentes implmentations : support du dise (#) comme dbut de commentaire, support de squences dchappement (\t, \n, \xNNNN. . .), support de continuation de ligne (avec une barre oblique inverse \ en n de ligne), etc. Sans surprise, de nombreux modules sont disponibles sur CPAN pour lire des chiers .INI, mais Cong::IniFiles est probablement le plus complet de tous, et supporte en particulier plusieurs extensions. Son API nest pas tout fait orthodoxe, avec un mlange de mthodes nommes en minuscules et dautres en camel case, mais elle est globalement agrable et pratique utiliser, ce qui est tout de mme le principal. Avec ce chier de conguration comme exemple :
[general] stats_path = /var/cache/stats.yml [network] port = 1984 address = localhost timeout = 5

Fichiers .INI

321

lire des paramtres se fait ainsi :


use C o n f i g :: I n i F i l e s ; my $ c o n f i g = C o n f i g :: I n i F i l e s - > new ( - f i l e = > " / etc / m y s o f t . c o n f " ); my $ p o r t = $ c o n f i g - > val ( n e t w o r k = > " p o r t " ) ;

Point intressant, la mthode val() permet de grer directement des valeurs par dfaut, ce qui vite de devoir le faire avec // ou son quivalent sur les vieux Perl :
my $ p o r t = $ c o n f i g - > val ( n e t w o r k = > " p o r t " , 1 9 8 4 ) ;

Cong::IniFile a dailleurs une gestion intressante des

valeurs par dfaut, permettant par exemple daller chercher celles-ci dans une section particulire, qui peut tre indique avec loption -default de new(). Curieusement, il considre par dfaut les chiers vides comme une erreur, moins de passer loption -allowempty vrai. Le module propose aussi dhriter depuis un objet existant, spci avec loption -import : les sections sont fusionnes, mais les paramtres provenant du chier analys ont priorit.
my $ d e f a u l t = C o n f i g :: I n i F i l e s - > new ( - f i l e = > " / etc / m y s o f t . g l o b a l . c o n f " ); my $ c o n f i g = C o n f i g :: I n i F i l e s - > new ( - import => $default , - f i l e = > " / etc / m y s o f t . l o c a l . c o n f " , );

322

CHAPITRE 16 Fichiers de conguration

Dautres options de new() permettent de contrler les extensions de syntaxe prcdemment mentionnes :
q

-allowcontinue : active le support des continuations de ligne par le caractre antislash \. -commentchar et -allowedcommentchars : permettent de

spcier les caractres de dbut de commentaire, par dfaut # et ;.


q

-nocase : permet de grer les sections et paramtres de

manire insensible la casse (cest--dire sans distinction entre majuscules et minuscules). Cette prsentation rapide couvre dj une bonne partie des usages courants, par exemple rcuprer des valeurs depuis le chier de conguration, mais Cong::IniFiles offre une gestion complte des paramtres, sections et groupes (encore une extension au format), permettant de les ajouter ou les dtruire la vole. Et pour tre pleinement utile, il permet aussi de sauver la nouvelle conguration sur disque. Il propose enn une interface tie() pour ceux qui prfrent que tout ressemble un hash :
tie my % config , " C o n f i g :: I n i F i l e s " , - f i l e = > " / etc / m y s o f t . c o n f " ; p r i n t $ c o n f i g { n e t w o r k }{ p o r t }; # affiche "1984"

Pour ne rien gcher, ce module existe depuis dj plusieurs annes, do une bonne disponibilit sous forme de paquet pour votre distribution favorite, et il est toujours bien maintenu.

Fichiers .INI

323

Utilisation de JSON/YAML
Une question qui peut naturellement se poser est, aprs tout, pourquoi ne pas utiliser JSON ou YAML comme format de conguration ? Comme ces formats peuvent tout faire , autant sen servir aussi dans ce cas-l. Certes, mais il faut pousser la rexion plus loin. Le problme de JSON et YAML est que leur syntaxe, quoique relativement simple (compare du XML par exemple), reste nanmoins plus exigeante que celle dun chier .INI, et les modules danalyse de ces derniers pardonnent ainsi beaucoup plus de petites erreurs. Par ailleurs, si JSON et YAML sont assez faciles lire, ils restent moins naturels crire et demandent donc plus de rexion. JSON en particulier demande de respecter de nombreux dtails de syntaxe qui le rend vraiment peu intressant comme format de chier de conguration, ce qui fera donc prfrer YAML pour sa plus grande clart. Pour des chiers de conguration courants, la structure section / cl / valeur est largement sufsante, et YAML napporte aucun gain. Par contre, il devient bien plus intressant ds quil sagit de grer des donnes plus complexes, qui ne rentrent pas dans le cadre simpliste du couple cl-valeur, lexemple typique tant le stockage de donnes arborescentes.

17
Principes gnraux de POE
La programmation vnementielle, ou programmation asynchrone, peut facilement drouter le dveloppeur qui na pas encore t confront cette manire de concevoir et dcrire des programmes, mais elle est au cur de nombreux problmes actuels. Parmi les premiers exemples qui viennent en tte, il y a les interfaces utilisateurs, quelles soient graphiques ou en mode console ou encore les serveurs rseau qui doivent gnralement tre en mesure de rpondre plus dun client la fois. Dans tous les cas, il sagit dtre en mesure de traiter des vnements qui peuvent survenir tout moment (connexion dun client, clic sur un widget, etc.), ce qui est impossible raliser avec un programme classique ot dexcution continu. Il faut donc savoir reconnatre la nature asynchrone dun futur logiciel avant de commencer effectivement coder pour viter de douloureuses dconvenues par la suite. Lun des auteurs peut ainsi tmoigner des consquences que peut engendrer un choix trop rapide. Brossons rapidement le contexte : il fallait dvelopper un couple de dmons, un serveur, un client, devant dialoguer par rseau, chaque client se connectant sur lensemble des serveurs dploys. Chaque dmon doit donc grer un certain

Partie V Programmation vnementielle

326

CHAPITRE 17 Principes gnraux de POE

nombre de connexions actives, une demi-douzaine dans le cas des clients, une trentaine dans le cas des serveurs. Par peur de ne pas avoir le temps de matriser POE (trs peu de temps de dveloppement avait t accord), cet auteur sest tourn vers des modules plus simples comprendre, IO::Socket et IO::Select. Et en effet, il est facile dobtenir ainsi un prototype fonctionnel. Mais une fois en production, les problmes et bogues arrivent bien rapidement, et mme des mois plus tard, certains problmes incomprhensibles dus une mauvaise gestion des sockets. Tout cela parce que le choix de rinventer sa propre roue1 , surtout quand le temps manque, est trs rarement une bonne ide. La leon retenir est que le temps pass apprendre un environnement comme POE doit se voir comme un investissement qui ne pourra que savrer protable long terme.

POE
POE2 est un environnement de programmation asynchrone

pour Perl. Le principe est davoir plusieurs tches qui travaillent ensemble, do le nom de multitche coopratif. Cela rappellera peut-tre de mauvais souvenirs ceux qui ont connu Windows 98 (et ses prdcesseurs) ou MacOS Classic, qui taient des systmes dexploitation de ce type. Ainsi, si un programme tait bloqu, cela geait lensemble du systme dexploitation. Dautre part, le partage de ressources (processeur, mmoire et I/O) tait inefcace et injuste.
1. Ou plutt, comme dirait Jean Forget, rinventer sa propre brosse dents ; cf. http://journeesperl.fr/2007/talk/916 pour la vido de sa prsentation. 2. Perl Object Environment, bien que lacronyme ait donn lieu de nombreuses autres interprtations srieuses, telles que Parallel Object Executor ou plus factieuses comme Part Of an Elephant ou Potatoes Of Eternity.

vnements

327

Pourquoi alors utiliser le mme paradigme ? Parce que mis en uvre travers une application, le problme est compltement diffrent. En effet, une application est crite par un seul dveloppeur, ou une quipe plus ou moins rduite. Dans tous les cas, il est (relativement) facile de respecter lenvironnement et quelques rgles de bon fonctionnement. Cela rduit les inconvnients du multitche coopratif, en rvlant toute la puissance dun environnement multitche.
POE est donc un environnement multitche, mais utilise

pour cela un seul processus et un seul thread ; ce qui a comme consquence immdiate que les applications sont facilement portables, mme sur les systmes qui ne disposent pas de lappel systme fork ou sur les interprteurs Perl qui ne sont pas multithreads. Deuxime consquence : les applications sont beaucoup plus faciles crire. Finis les IPC et autres verrous pour sassurer laccs une ressource : comme une seule tche est en cours, toute action est considre comme tant atomique. POE est donc analogue des bibliothques de gestion de threads comme Pth en C, mais la comparaison sarrte l car POE est de plus haut niveau et surtout entirement orient objet.

vnements
Une application POE est une suite dvnements (events) que les tches senvoient. La documentation parle encore souvent dtats (states), du fait de la construction originelle sous forme de machine tats, qui a par la suite volu. Un vnement signale simplement quil sest pass quelque chose, par exemple :

328

CHAPITRE 17 Principes gnraux de POE

q q q q

un chier est prt ; une alarme a expir ; une nouvelle connexion est arrive ; ou nimporte quel autre vnement indiquant que lapplication a chang dtat.

Les gestionnaires dvnements sont des callbacks, cest-dire des fonctions enregistres pour tre appeles lorsque ces vnements se produisent.

Sessions
Les tches sont appeles des sessions en POE. Une session a des ressources prives, tels que descripteurs de chiers, variables, etc. Elle dispose en particulier dune pile (heap), dans laquelle elle peut stocker ce quelle veut. Elle peut aussi senvoyer des vnements privs, et mme avoir des sessions lles. Pour faire lanalogie avec un systme dexploitation, une session est donc comme un processus. Une session reste en vie tant quil le faut, cest--dire tant quelle a des vnements traiter. Quand elle na plus rien faire, la session sarrte. Ceci peut paratre un peu nbuleux, mais est en fait trs simple : une session peut par exemple rfrencer une connexion, avec des vnements pour ragir ce qui se passe sur cette connexion. Tant que la connexion est active, la session reste en vie, car celle-ci peut gnrer des vnements traiter. Quand la connexion est ferme et libre, alors la session na plus de but (la connexion ne peut plus gnrer dvnements) et est donc termine par POE. Le corollaire est quune session, lors de sa cration, doit obligatoirement crer et rfrencer quelque chose qui va la maintenir en vie. Sinon, elle est sera aussitt passe au ramasse-miettes de POE.

Le noyau POE

329

Le noyau POE
Le noyau est le cur de POE. Il est charg dacheminer les vnements, dappeler le code devant les grer, dorchestrer les sessions, mais aussi de vrier des conditions en gnrant des vnements si besoin. Le noyau est en effet le gestionnaire du temps (cest lui qui va savoir quand lancer une alarme) et le responsable des objets bas-niveau tels que les descripteurs de chiers et de connexions. Le noyau est implment dans le module POE::Kernel, et fournit la mthode run() qui est en fait la boucle principale du programme, ne retournant pas tant quil reste des tches :
# l a n c e le p r o g r a m m e une f o i s # les s e s s i o n s c r e s POE :: Kernel - > run ;

Un mot sur les performances


POE est un framework avanc, trs orient objet, ce qui implique une perte defcacit compar au procdural pur. Il reste cependant performant et devrait sufre pour la majorit des besoins, dautant plus quil apporte de nombreux avantages. Nous verrons par la suite quil existe des extensions permettant dobtenir de meilleures performances. En parlant chiffres : pour une application grant jusqu 1 000 messages par seconde (ce qui correspond la plupart des applications), POE convient sans hsiter. Au del de 10 000 messages par seconde, POE ne suft plus, ou en tout cas plus tout seul (voir au Chapitre 19 consacr POE distribu). Entre ces deux valeurs, une valuation reste ncessaire.

18
POE en pratique
Crer une session POE
POE::Session->create

Une session POE est cre avec le constructeur de session. Celui-ci accepte divers arguments, dont le plus important est inline_states qui dnit les vnements1 que la session sattend recevoir :
use POE ; POE :: S e s s i o n - > c r e a t e ( inline_states => { _ s t a r t = > sub { say " s e s s i o n d m a r r e " ; }, }, ); POE :: Kernel - > run ;

1. Au dbut, Rocco Caputo voulait crer avec POE un gnrateur de machines tats, do le nom de largument.

332

CHAPITRE 18 POE en pratique

Une session va tre cre et donc planie par le noyau POE lorsque celui-ci sera lanc. Lvnement _start va automatiquement tre appel lors du dmarrage de la session, aprs la cration de la session elle-mme. Puis, comme la session na pas cr dobjet qui la maintienne en vie, POE va la passer au ramasse-miettes. Le noyau va alors se rendre compte quil ne reste plus de tches en cours, et va donc sortir de la mthode run(). Le programme sarrtera alors.

Envoyer un vnement
POE::Kernel->yield

use POE ; POE :: S e s s i o n - > c r e a t e ( inline_states => { _ s t a r t = > sub { POE :: Kernel - > y i e l d ( " t i c k " ) ; }, # d f i n i t i o n de l v n e m e n t " t i c k " t i c k = > sub { say s c a l a r l o c a l t i m e ; POE :: Kernel - > d e l a y ( t i c k = > 1) ; }, }, ); POE :: Kernel - > run ;

Ici, la session lors de sa cration (dans la fonction traitant lvnement _start) va senvoyer un vnement elle-

Passer des paramtres

333

mme avec la mthode yield() du noyau. Le noyau sait donc que la session a du travail en cours, tant donn quil y a un vnement qui lui est destin. La session ne sera pas dtruite tout de suite. Le noyau va soccuper denvoyer lvnement et dappeler le callback associ. Celui-ci est dni en ajoutant un vnement auquel devra ragir la session dans les inline_states, avec le callback associ. Le callback est donc appel par le noyau, permettant dimprimer la date et lheure courante. Le gestionnaire dvnements va alors nouveau senvoyer un vnement tick, mais au bout dune seconde. Ceci se fait avec la mthode delay() du noyau. En effet, si la fonction Perl sleep avait t utilise, le programme se serait mis en pause. . . et le noyau aussi ! Et les autres sessions, sil y en avait, nauraient pas t planies tout de suite, ce qui aurait ralenti tout le programme l o le but tait de mettre en pause la session courante seulement ! Bien sr, le fait de planier lenvoi dun vnement indique au noyau de garder la session courante active, pour pouvoir traiter les vnements futurs. Le programme ci-dessus va donc imprimer la date et lheure courantes toutes les secondes, sans jamais sarrter.

Passer des paramtres


Passer un argument se fait trs simplement la suite du nom dvnement planier :
use POE ; POE :: S e s s i o n - > c r e a t e ( inline_states => {

334

CHAPITRE 18 POE en pratique

_ s t a r t = > sub { # passage d argument POE :: Kernel - > y i e l d ( n e x t = > 10 ) ; }, n e x t = > sub { my $i = $_ [ A R G 0 ]; say $i ; POE :: Kernel - > y i e l d ( n e x t = > $i -1 ) u n l e s s $i == 0; }, }, ); POE :: Kernel - > run ;

Pour le rcuprer, toutefois, il faut le chercher un offset particulier de $@_. Info


POE passe un grand nombre de paramtres additionnels aux callbacks (dont la session, la pile, lobjet courant, une rfrence sur le noyau lui-mme, etc.). POE exporte donc des constantes permettant de rcuprer facilement les paramtres souhaits.

Le premier argument pass lors de lappel yield() sera accessible via $_[ARG0], le suivant via $_[ARG1], jusquau dernier disponible via $_[$#_]. Les paramtres sont en effet ajouts en dernier, donc tous les arguments peuvent tre rcuprs ainsi :
my @ p a r a m s = @_ [ A R G 0 .. $# _ ];

Dans lexemple ci-dessus, le callback va rcuprer la valeur du paramtre dans $i, lafcher, puis lancer un autre vnement en dcrmentant le paramtre. Si le paramtre $i vaut 0, lvnement nest pas plani et donc la ses-

Utiliser des variables prives

335

sion ne sert plus rien, passe donc au ramasse-miettes, et le programme sarrte. Le programme va donc afcher les valeurs 10 0, puis sarrter.

Utiliser des variables prives


$_[HEAP]

Une session dispose dun espace rserv la pile (heap) lui permettant de stocker des donnes. Ces donnes sont prives et accessibles uniquement par la session. En pratique, la pile est simplement un scalaire, qui est la plupart du temps une rfrence de hash pour stocker plusieurs variables :
use POE ; f o r e a c h my $ c o u n t e r ( 1 , 3 ) { POE :: S e s s i o n - > c r e a t e ( args => [ $counter ], inline_states => { _ s t a r t = > sub { my ( $h , $i ) = @_ [ HEAP , A R G 0 ]; $h - >{ c o u n t e r } = $i ; POE :: Kernel - > y i e l d ( " n e x t " ) ; }, n e x t = > sub { my $h = $_ [ H E A P ]; say $h - >{ c o u n t e r }; POE :: Kernel - > y i e l d ( " n e x t " ) u n l e s s $h - >{ c o u n t e r } - - == 0; }, },

336

CHAPITRE 18 POE en pratique

); } POE :: Kernel - > run ;

Astuce
Notez le paramtre args de la mthode create(), spciant une liste passer en argument lors de lvnement _start. Ici, un seul argument est pass, mais lusage dune rfrence de liste permet den passer autant que ncessaire.

Dans cet exemple, deux sessions seront lances et dcrmenteront leurs paramtres jusqu zro, tour de rle. La sortie du programme sera donc :
1 3 0 2 1 0 # # # # # # # premire session deuxime session premire, qui sarrte deuxime nouveau deuxime deuxime, qui sarrte le programme sarrte aussi

Nous commenons voir lintrt de POE, qui va donc empiler les vnements gnrs et les dlivrer au fur et mesure aux sessions concernes. Le noyau traite les vnements avec une politique premier arriv, premier servi ou FIFO (First In, First Out).

Communiquer avec une autre session


POE::Kernel->post

Communiquer avec une autre session

337

Jusqu prsent, les exemples montraient des sessions qui senvoyaient des vnements elles-mmes. Mais bien sr POE permet aux sessions de communiquer entre elles et de se transmettre des informations. Dans lexemple qui suit, une premire session attend des vnements debug pour les afcher. Une deuxime session va dcrmenter un compteur, en envoyant des messages la premire pour indiquer ce quil se passe2 :
use POE ; POE :: S e s s i o n - > c r e a t e ( inline_states => { _ s t a r t = > sub { POE :: Kernel - > a l i a s _ s e t ( " l o g g e r " ) ; }, d e b u g = > sub { my @ a r g s = @_ [ A R G 0 .. $# _ ]; say @ a r g s ; }, }, ); POE :: S e s s i o n - > c r e a t e ( inline_states => { _ s t a r t = > sub { POE :: Kernel - > y i e l d ( t i c k = > 3 ) ; }, t i c k = > sub { my $i = $_ [ A R G 0 ];

2. Mme si laction de log est ici trs simple, cela permettrait par exemple davoir une session connecte une base de donnes, ou faisant des traitements complexes (en multiplexant les logs sur diffrents supports par exemple). Lexemple nest donc pas si tir par les cheveux que cela. . .

338

CHAPITRE 18 POE en pratique

# p a s s a g e de m e s s a g e la 1 re s e s s i o n POE :: Kernel - > p o s t ( l o g g e r = > d e b u g = > " t e s t - i v a u t $i " ) ; POE :: Kernel - > y i e l d ( t i c k = > $i -1) u n l e s s $i == 0; }, }, ); POE :: Kernel - > run ;

Si rien ntait fait lors de son dmarrage, la premire session cre par POE::Session->create dmarrerait, mais sarrterait immdiatement. En effet, comme elle naurait rien faire, POE la passerait au ramasse-miettes. Lastuce consiste donc lui affecter un alias, qui est un nom symbolique pour la session. Comme tout le monde peut envoyer un message un alias, POE ne peut savoir lavance si la session est utile ou pas. Dans le doute, il va donc garder la session en vie. Et heureusement, car la deuxime session va lui envoyer des messages avec la ligne :
POE :: Kernel - > p o s t ( l o g g e r = > d e b u g = > " t e s t - i v a u t $i " ) ;

La syntaxe est assez simple : la mthode post() du noyau permet de spcier dans lordre le destinataire, lvnement envoyer et des paramtres ventuels. Une fois que la deuxime session a envoy ses quatre messages, elle va sarrter. Mais POE narrte toujours pas la premire session, car elle a un alias ! Ce qui pose problme, car le programme dans ce cas ne sarrterait jamais, sans toutefois ne rien faire. En effet, la premire session serait toujours en attente, mais aucune autre session nexistant, elle attendrait en vain. . .

Envoyer un message diffr

339

Pour empcher cela, POE va dtecter quand il ne reste que des sessions maintenues en vie grce des alias. Si cest le cas, le noyau va envoyer un signal IDLE toutes les sessions, pour leur donner une chance de se rveiller et de recommencer travailler. Si aucune ne recommence travailler, alors le noyau enverra un signal ZOMBIE qui nest pas grable par les sessions : celles-ci vont donc mourir et le programme se terminer. . . Ouf ! Attention
Un programme POE implique une bonne coopration des sessions entre elles. En effet, si lun des gestionnaires dvnements bloque pour une raison ou pour une autre, cest lensemble du programme qui sarrte. Les sections suivantes indiquent donc quelques problmes potentiels et la manire dy remdier pour que le programme POE continue bien fonctionner.

Envoyer un message diffr


POE::Kernel->delay

Ce point a dj t abord dans un exemple (voir page 333), mais il est bon de le rappeler : la fonction sleep() est bannir. Avec delay, la session indique au noyau POE quelle souhaite recevoir un vnement aprs un certain nombre de secondes. Elle peut aussi senvoyer des paramtres supplmentaires :
# s e n v o y e r un v n e m e n t d i f f r # dans $sec secondes POE :: Kernel - > d e l a y ( e v e n t = > $sec , @ p a r a m s ) ;

340

CHAPITRE 18 POE en pratique

Les machines disposant de Time::HiRes (cest--dire tous les Perl depuis la version 5.8) peuvent utiliser des fractions de secondes. Cependant, Perl nest pas un langage dit temps rel et la prcision de lalarme dpend de beaucoup de choses, dont le systme dexploitation. Il nest pas possible denvoyer un vnement diffr une autre session. Pour cela, il faut senvoyer un vnement diffr, qui se chargera denvoyer le message la deuxime session :
POE :: S e s s i o n - > c r e a t e ( inline_states => { _ s t a r t = > sub { POE :: Kernel - > d e l a y ( l a t e r = > 5) ; }, l a t e r = > sub { POE :: Kernel - > p o s t ( s e s s i o n = > " e v e n t " ) ; }, );

Envoyer un message lheure dite


POE::Kernel->alarm

Quand le programme doit se rveiller une date et une heure prcises, il ne faut plus utiliser delay mais la fonction alarm.
# s e n v o y e r un v n e m e n t la d a t e / h e u r e # i n d i q u e par $ e p o c h POE :: Kernel - > a l a r m ( e v e n t = > $epoch , @ p a r a m s ) ;

Couper les longs traitements

341

La date est fournie au format Epoch3 .

Terminer le programme
Pour donner une chance toutes les sessions de se terminer correctement, il faut bien sr bannir les appels exit ou die. En effet, ces fonctions terminent le programme dun seul coup sans laisser aux sessions un dlai de grce. Le bon citoyen POE enverra des messages de n aux diverses sessions :
# il est t e m p s de s a r r t e r ! POE :: Kernel - > p o s t ( $_ = > " s h u t d o w n " ) for @ s e s s i o n s ;

Charge elles de ragir ce signal en sarrtant proprement : fermeture des chiers et connexions, suppression des alias de session, arrt des composants. La session sera alors passe au ramasse-miettes, et quand il ne restera plus aucune session, le programme sarrtera.

Couper les longs traitements


Certains vnements ncessitent un long traitement, par exemple, en lanant une boucle itrant un grand nombre de fois. Mais du fait de sa nature monoprocessus et monothread, lensemble du programme POE va se retrouver bloqu le temps que le traitement de lvnement se termine. Ceci peut tre inacceptable, car pendant ce temps, aucun vnement nest achemin et donc aucun callback appel.
3. Nombre de secondes depuis la date initiale (qui dpend du systme dexploitation, souvent le 1er janvier 1970).

342

CHAPITRE 18 POE en pratique

Imaginez une interface graphique avec une telle boucle : lutilisateur aura beau cliquer, rien ne se passera. . . jusqu ce que le traitement abusif soit termin, et alors toutes les interactions seront traites dun coup ! Pire encore, une application rseau mal code utilisant un trs long callback pourra bien voir toutes les connexions en cours perdues, pour cause de rponse trop lente de lapplication ! Lexemple suivant calcule la somme de 0 1 000 000, tout en afchant la date et lheure toutes les secondes. Pour ne pas perturber lafchage4 , la boucle va tre dcoupe pour ne faire quune itration par vnement :
use POE ; POE :: S e s s i o n - > c r e a t e ( inline_states => { _ s t a r t = > sub { POE :: Kernel - > y i e l d ( " t i c k " ) ; POE :: Kernel - > y i e l d ( " sum " ) ; }, t i c k = > sub { say s c a l a r l o c a l t i m e ; POE :: Kernel - > d e l a y ( t i c k = > 1) ; }, sum = > sub { s t a t e $i = 0; s t a t e $ s u m = 0; $ s u m += $i ++; if ( $i > 1 _ 0 0 0 _ 0 0 0 ) { # a f f i c h a g e du r s u l t a t f i n a l say " sum = $ s u m " ;

4. Cet exemple est naf, mais imaginez un traitement complexe chaque itration. . .

Couper les longs traitements

343

# a r r t de l h o r l o g e POE :: Kernel - > d e l a y ( " t i c k " ) ; } else { POE :: Kernel - > y i e l d ( " sum " ) ; } }, }, ); POE :: Kernel - > run ;

Il y a plusieurs lignes intressantes dans cet exemple. Les variables dnies avec le mot-cl state gardent leur valeur dune invocation du callback la suivante. Cependant, si plus dune session tait cre, ces variables seraient partages et modies par les deux sessions. Ce nest gnralement pas le but recherch. Pour pallier cet inconvnient, il faut alors utiliser la pile qui est, elle, prive la session. Il est aussi intressant de noter la ligne :
# a r r t de l h o r l o g e POE :: Kernel - > d e l a y ( " t i c k " ) ;

Lappel de delay avec seulement le nom de lvnement va annuler la planication de cet vnement dj ralise via un appel delay. Malheureusement, mme sil permet de laisser aux autres vnements le temps de sexcuter, le fait de senvoyer un million de messages (un par itration) est trs inefcace. L o une simple boucle :
p e r l - E $s += $_ for 0 . . 1 _ 0 0 0 _ 0 0 0 ; say $s

344

CHAPITRE 18 POE en pratique

se termine en moins dune seconde, le programme bas sur POE ci-dessus met. . . un peu plus dune minute ! Et mme si lexemple est vraiment trivial et non reprsentatif de ce qui peut se faire dans le monde rel, cela nen reste pas moins inacceptable. Lastuce consiste donc dcouper le long traitement non pas en trs nes tranches, mais en tranches assez consquentes pour que le surcot ne soit pas trop gros, mais assez nes pour ne pas perturber le ux du programme. Dans lexemple pris, les itrations ne se feront donc pas une par une (trop lent) ou par un million (pas assez ractif), mais mille par mille5 . La boucle du programme devient donc :
sum = > sub { s t a t e $i = 0; s t a t e $ s u m = 0; $ s u m += ( $i > 1 _ 0 0 0 _ 0 0 0 ? 0 : $i ++ ) for 0 . . 9 9 9 ; if ( $i > 1 _ 0 0 0 _ 0 0 0 ) { # a f f i c h a g e du r s u l t a t f i n a l say " sum = $ s u m " ; # a r r t de l h o r l o g e POE :: Kernel - > d e l a y ( " t i c k " ) ; } else { POE :: Kernel - > y i e l d ( " sum " ) ; } },

Et ainsi, le temps dexcution revient en dessous de la seconde. . .

5. Ce chiffre dpend bien sr du programme et de la complexit de chaque itration du traitement.

Bannir les entres-sorties bloquantes

345

Attention
Notez le test lors de laddition : comme la boucle est trononne, les tranches ne sont peut-tre pas un multiple entier de la boucle totale. Il faut donc sassurer que cette astuce ne fausse pas le rsultat du programme : mieux vaut un programme lent quun programme faux !

Bannir les entres-sorties bloquantes


Un autre point qui peut poser problme dans lenvironnement POE concerne les entres-sorties bloquantes. Pour viter que le programme ne sarrte en attente dune entresortie quelconque, il faut sassurer que les connexions sont ouvertes en mode non bloquant, ne pas sarrter lors de la lecture des chiers ou pipes, ne pas bloquer en attente de donnes non prsentes. . . Heureusement, POE propose des solutions pour simplier la tche du programmeur. Rutilisation de code
POE nest pas seulement un framework vnementiel labor, cest aussi tout un cosystme de modules fournissant des composants rutilisables facilement dans tout programme POE. Ces modules sont rpartis en trois grandes catgories, suivant des degrs dabstraction variables, et fournissant une balance diffrente entre les efforts de dveloppement et le contrle quaura le dveloppeur sur le comportement de ces modules. Il est bien sr possible de mlanger des composants de diffrents niveaux, permettant ainsi une exibilit accrue.

346

CHAPITRE 18 POE en pratique

Composants de haut niveau


POE::Component

Les composants (component en anglais) sont puissants et fournissent des services avancs peu de frais. Souvent une ou plusieurs sessions POE distinctes seront cres, qui dialogueront avec les autres sessions comme un composant distinct du programme. Ces composants se retrouvent dans lespace de noms POE::Component. Une grande partie dentre eux propose par exemple des clients ou des serveurs rseau : accs ou fourniture de services web, syslog, rsolution de nom sont disponibles directement, mais aussi les briques ncessaires pour crer son propre client ou serveur gnrique. Le programme suivant implmente un serveur decho complet :
use POE ; use POE :: C o m p o n e n t :: S e r v e r :: TCP ; POE :: C o m p o n e n t :: S e r v e r :: TCP - > new ( Port = > 17000 , C l i e n t I n p u t = > sub { my ( $heap , $ i n p u t ) = @_ [ HEAP , A R G 0 ]; $heap - >{ c l i e n t } - > put ( $ i n p u t ) ; }, ); POE :: Kernel - > run ;

Parmi les autres principaux composants de haut niveau se trouvent des interfaces des bibliothques ou des applications : libpcap, oggenc, mpd, etc. CPAN regroupe environ 600 composants de haut niveau pour POE.

Bote outils de niveau intermdiaire

347

Bote outils de niveau intermdiaire


POE::Wheel

Lorsque les POCO6 ne sont pas appropris, ou trop spcialiss, il faut rutiliser des roues standard de plus bas niveau. Ces roues fournies par les modules de lespace de noms POE::Wheel (do leur nom) sont plus gnralistes. Ce sont les briques de base de nombreux composants. Elles ne crent pas de nouvelles sessions, mais ajoutent des traitements dvnements la session utilisant la roue autrement dit, la session est modie. Comme les roues ne crent pas de session autonome, le noyau POE ne les stockera pas : cest la session qui utilise la roue de la stocker. . . et de la dtruire quand elle ne sert plus (pas de ramasse-miettes automatique fourni par le noyau). Les roues sont donc troitement couples avec leur session mre, et ne peuvent tre passes ou utilises par dautres sessions : celles-ci doivent crer leur propre instance de la dite roue. Parmi les roues disponibles, peuvent tre cites la surveillance des lignes dun chier ( la tail -f), la gestion dentres-sorties non bloquantes (connexions rseau comprises), le lancement et le contrle de processus, etc. Leur utilisation implique bien sr plus defforts de programmation quun composant fournissant un service cl en main, mais cest le prix payer pour plus de contrle.
6. POCO comme POE::Component, les composants de haut niveau vus ci-dessus.

348

CHAPITRE 18 POE en pratique

Fonctions de bas niveau POE


Finalement, quand mme les roues fournies par POE sont trop volues, POE propose ses propres fonctions de bas niveau. Le code rsultant sera certes moins rafn et plus verbeux, mais fournira le contrle le plus complet aux dveloppeurs. Les fonctions proposes permettent de grer les alarmes et les signaux (au sens Unix du terme) et des interfaces aux appels systme tels que select. Bien sr, lensemble des fonctionnalits dorchestration et de passage de messages fournies par POE restent disponibles. . . Astuce
Les diffrents niveaux des composants POE permettent aux dveloppeurs de choisir le degr dabstraction quils souhaitent mettre en uvre dans leur programme. Le nombre de briques de base disponibles donne la possibilit dcrire un programme complexe en assemblant des composants prouvs, et cela participe aussi de lintrt dutiliser POE.

Exemple dutilisation : le composant DBI


POE::Component::EasyDBI

Voici une illustration du fait que la programmation vnementielle impose de shabituer tout penser de manire asynchrone. Laccs aux bases de donnes est un exemple typique de service dont lAPI est fondamentalement synchrone, et qui induit donc des temps de latence. Le prin-

Exemple dutilisation : le composant DBI

349

cipe gnral utilis pour fournir un composant asynchrone de tels services est de dporter la synchronicit dans un processus spar avec lequel le composant POE discute au travers dun tube et de POE::Wheel::Run. Il existe quelques composants de pilotage DBI pour POE sur CPAN, tous fonctionnant sur le mme principe gnral : pour chaque connexion une base de donne, le composant cre un nouveau processus qui se charge dexcuter les commandes DBI. Le composant qui va tre prsent plus en dtails est POE::Component::EasyDBI, parce quil est un peu plus agrable manipuler que les autres, et parce que cest celui utilis par lun des auteurs. Fondamentalement, tous les composants daccs aux bases de donnes reposent sur le mme principe de dcoupage des requtes en deux vnements POE : le premier pour prparer la requte et passer les ventuels arguments, et le second pour la rception des rsultats. Il faut dabord crer une session POE::Component::EasyDBI qui se charge de la communication avec la base de donnes. Les paramtres passs la mthode spawn() seront familiers lutilisateur averti de DBI :
POE :: C o m p o n e n t :: E a s y D B I - > s p a w n ( alias => " events_dbh " , dsn = > " dbi : m y s q l : d a t a b a s e = e v e n t s " , username => " user " , password => " pass " , );

spawn() accepte bien sr dautres options, mais les rglages

par dfaut conviennent en gnral trs bien. Le nom de la session, si aucun alias nest indiqu, est EasyDBI. EasyDBI propose un grand nombre de commandes, chacune spcialise dans un usage donn, un peu comme les nombreuses mthodes de DBI.

350

CHAPITRE 18 POE en pratique

La commande insert sert pour excuter des requtes INSERT :


sub s t o r e _ e v e n t { my ( $ k e r n e l , $host , $ s e r v i c e , $id , $ m s g ) = @_ [ KERNEL , A R G 0 .. $# _ ]; my $ s q l = q { INSERT INTO events ( host , s e r v i c e , id , m e s s a g e ) VALUES ( ?, ?, ?, ? ) }; $kernel -> post ( $easydbi , insert => { sql = > $sql , event => " store_event_result " , placeholders = > [ $host , $ s e r v i c e , $id , $ m s g ] , }, ); }

Les paramtres sont assez simples comprendre : sql pour donner la requte SQL, placeholders pour les ventuelles valeurs passer en arguments de la requte et event pour indiquer le nom de lvnement de la session courante qui sera appel pour recevoir les rsultats. Celui-ci reoit une rfrence vers un hash qui contient des champs diffrents en fonction de la commande excute, ainsi que quelques champs communs toutes : sql contient la requte SQL envoye, placeholders la rfrence vers les ventuels valeurs, result le rsultat si la commande a russi et error la chane derreur si elle a au contraire chou. Cest dailleurs le premier champ vrier dans les fonctions de traitement :

Exemple dutilisation : le composant DBI

351

sub s t o r e _ e v e n t _ r e s u l t { my ( $ k e r n e l , $ d b _ r e s u l t ) = @_ [ KERNEL , A R G 0 ]; w a r n " s t o r e _ e v e n t : $ d b _ r e s u l t - >{ e r r o r }\ n " if $ d b _ r e s u l t - >{ e r r o r }; }

La commande do sert pour la plupart des requtes SQL hors SELECT, comme UPDATE, DELETE, etc.
sub d e l e t e _ e v e n t { my ( $ k e r n e l , $host , $ s e r v i c e , $id , $ m s g ) = @_ [ KERNEL , A R G 0 .. $# _ ]; my $ s q l = DELETE FROM WHERE AND AND }; q{ events host = ? service = ? id = ?

$kernel -> post ( $easy_dbi , array => { sql = > $sql , event => " delete_event_result " , placeholders = > [ $host , $ s e r v i c e , $id ] , }, ); } sub d e l e t e _ e v e n t _ r e s u l t { my ( $ k e r n e l , $ d b _ r e s u l t ) = @_ [ KERNEL , A R G 0 ]; w a r n " d e l e t e _ e v e n t : $ d b _ r e s u l t - >{ e r r o r }\ n " if $ d b _ r e s u l t - >{ e r r o r }; }

352

CHAPITRE 18 POE en pratique

Plusieurs commandes existent pour les requtes SELECT, de manire similaire aux diffrentes mthodes de DBI, pour rcuprer les donnes sous des formes et des structures varies : single pour une valeur unique (comme fetchrow_array()), hash pour un ligne de plusieurs colonnes nommes (fetchrow_hashref()), array pour plusieurs lignes dune colonne (avec jointure de colonnes si ncessaire), arrayarray pour plusieurs lignes de plusieurs colonnes (fetchall_arrayref()), hashhash pour plusieurs lignes de plusieurs colonnes nommes (comme fetchall_ hashref()), arrayhash pour plusieurs lignes colonnes nommes (mais sous la forme dun tableau de fetchrow_hashref()). Voici un exemple qui rcupre une simple valeur :
sub f e t c h _ e v e n t { my ( $ k e r n e l , $ h o s t ) = @_ [ KERNEL , A R G 0 ]; my $ s q l = q { S E L E C T c o u n t (*) FROM events WHERE host = ? }; $kernel -> post ( $easydbi , single => { sql = > $sql , event => " fetch_event_result " , placeholders => [ $host ], }, ); } sub f e t c h _ e v e n t _ r e s u l t { my ( $ k e r n e l , $ d b _ r e s u l t ) = @_ [ KERNEL , A R G 0 ];

Exemple dutilisation : le composant DBI

353

if ( $ d b _ r e s u l t - >{ e r r o r }) { w a r n " f e t c h _ e v e n t : $ d b _ r e s u l t - >{ e r r o r } \n"; return ; } p r i n t $ d b _ r e s u l t - >{ r e s u l t } , " vnements associs l hte " , " $ p l a c e h o l d e r s - >[0]\ n " ; }

Et un exemple qui rcupre les rsultats sous forme dun tableau de structures :
sub f i n d _ e v e n t s { my ( $ k e r n e l , $ h o s t ) = @_ [ KERNEL , A R G 0 ]; my $ s q l = SELECT FROM WHERE }; q{ * events host like ?

$kernel -> post ( $easydbi , arrayhash => { sql = > $sql , event => " find_events_result " , placeholders => [ $host ], }, ); } sub f i n d _ e v e n t s _ r e s u l t { my ( $ k e r n e l , $ d b _ r e s u l t ) = @_ [ KERNEL , A R G 0 ]; if ( $ d b _ r e s u l t - >{ e r r o r }) { w a r n " f i n d _ e v e n t s : $ d b _ r e s u l t - >{ e r r o r } \n"; return ; }

354

CHAPITRE 18 POE en pratique

for my $ e v e n t ( @ { $ d b _ r e s u l t - >{ r e s u l t }}) { p r i n t " t r a i t e m e n t de l v n e m e n t p o u r / " , $event - >{ h o s t } , "/", $event - >{ s e r v i c e } , " / " , $event - >{ id } , "\n"; $kernel -> yield ( " process_event " , $event );

} }

POE::Component::EasyDBI permet de passer des donnes

supplmentaires aux vnements traitant les rsultats : il suft dajouter les champs correspondants dans le hash pass la commande EasyDBI, qui se retrouveront dans le hash fournit en rsultat. De manire assez vidente, il vaut donc mieux utiliser des noms de champs qui soient propres, par exemple en les prxant par des doubles espaces soulignes. Petit exemple pour mesurer la latence au niveau base de donnes :
sub s t o r e _ e v e n t { my ( $ k e r n e l , $host , $ s e r v i c e , $id , $ m s g ) = @_ [ KERNEL , A R G 0 .. $# _ ]; my $ n o w _ m s = g e t t i m e o f d a y () ; my $ s q l = q { INSERT INTO events ( host , s e r v i c e , id , m e s s a g e ) VALUES ( ?, ?, ?, ? ) }; $kernel -> post ( $easydbi , insert => { sql = > $sql , event => " store_event_result " , __start_time => $now_ms ,

Exemple dutilisation : le composant DBI

355

placeholders = > [ $host , $ s e r v i c e , $id , $ m s g ] , }, ); } sub s t o r e _ e v e n t _ r e s u l t { my ( $ k e r n e l , $ d b _ r e s u l t ) = @_ [ KERNEL , A R G 0 ]; w a r n " s t o r e _ e v e n t : $ d b _ r e s u l t - >{ e r r o r }\ n " if $ d b _ r e s u l t - >{ e r r o r }; my $ n o w _ m s = g e t t i m e o f d a y () ; p r i n t f " La r e q u t e SQL a mis %.2 f sec \ n " , $ n o w _ m s - $ d b _ r e s u l t - >{ _ _ s t a r t _ t i m e }; }

Bien sr, POE::Component::EasyDBI na pas t prsent en profondeur ici, mais limportant tait de signaler son existence, et lintrt quil y a utiliser ce genre de composants. Il est clair quil induit un surcot non ngligeable en termes de nombre de fonctions ou vnements POE crire, puisque pratiquement chaque requte SQL implique deux vnements. Nanmoins, le gain que cela apporte en termes de bande passante dexcution au niveau du noyau POE est trs sensible et en justie le cot. Intgration Moose
Il est possible de reprocher POE sa verbosit, en particulier lors de lemploi dune pile. Heureusement, le module MooseX::POE permet de marier lgamment POE avec tous les avantages de Moose. Lexemple rencontr auparavant montrant comment avoir des variables prives peut donc se rcrire de la sorte :

356

CHAPITRE 18 POE en pratique

package Counter ; use M o o s e X :: POE ; # attribut propre chaque session has c o u n t = > ( is = > " rw " , isa = > " Int " , required => 1 ); sub S T A R T { my $ s e l f = s h i f t ; $self - > y i e l d ( " n e x t " ) ; } e v e n t n e x t = > sub { my $ s e l f = s h i f t ; my $i = $self - > c o u n t ; say $i ; $self - > c o u n t ( $i -1) ; $self - > y i e l d ( " n e x t " ) u n l e s s $i == 0; }; C o u n t e r - > new ( c o u n t = >3) ; C o u n t e r - > new ( c o u n t = >1) ; POE :: Kernel - > run ;

Chaque session sera dnie dans une classe, et cre en instanciant cette classe. La mthode START sera automatiquement appele lors du dmarrage de la session, aprs la cration de lobjet lui-mme. Les vnements seront dnis avec le mot-cl event prenant en compte deux paramtres : le nom de lvnement et le callback associ. Bien sr, tous les avantages de POE (dnition dattributs, vrications diverses, etc.) se retrouvent dans MooseX::POE, avec en plus quelques mthodes daide telles que yield(), faisant exactement la mme chose que la mthode du noyau du mme nom. Lutilisation de la notation objet avec MooseX::POE permet donc davoir un code plus pur, plus lisible. . . et donc plus maintenable.

19
POE distribu
Bien que les avantages de POE soient nombreux, son problme principal dcoule de lun dentre eux, savoir sa nature monoprocessus et monothreade. En effet, si ce mode de fonctionnement aide normment lcriture de tout programme, il ne facilite pas le passage lchelle. Les performances seront donc limites la puissance brute dun seul processeur ; ajouter des curs ou dautres processeurs ne rendra pas le programme plus rapide. . . Pour pallier ce problme, le composant1 POE IKC (Inter Kernel Communication) permet plusieurs programmes POE de se passer des messages et des vnements comme sils taient orchestrs par le mme noyau POE. Les avantages sont vidents : faire tourner plusieurs programmes permet au systme dexploitation de les orchestrer sur autant de processeurs et de curs que disponibles. Mieux mme, IKC permet de faire communiquer de manire transparente des programmes POE sur diffrentes machines, travers le rseau !

1. Au sens POCO .

358

CHAPITRE 19 POE distribu

Crer un serveur IKC


use POE::Component::IKC::Server

Lillustration dun serveur IKC se fait en reprenant lexemple du logger, mais qui sera cette fois disponible en tant que service. Il tournera dans un serveur distinct, accessible sur le port 17000 :
use POE ; use POE :: C o m p o n e n t :: IKC :: S e r v e r ; # c r a t i o n du s e r v e u r IKC POE :: C o m p o n e n t :: IKC :: Server - > s p a w n ( name => " my_server " , ip => " localhost " , p o r t = > 17000 , ); POE :: S e s s i o n - > c r e a t e ( inline_states => { _ s t a r t = > sub { POE :: Kernel - > a l i a s _ s e t ( " l o g g e r " ) ; POE :: Kernel - > p o s t ( IKC => publish => logger => [ " debug " ] ); }, d e b u g = > sub { my @ a r g s = @_ [ A R G 0 .. $# _ ]; say @ a r g s ; }, }, ); POE :: Kernel - > run ;

Crer un client IKC

359

Le programme va donc lancer un composant serveur IKC (avec la mthode POE::Component::IKC::Server->spawn), en lui donnant un nom, ainsi que les paramtres rseau sur lesquels couter. Une session va alors tre cre. Lors de son dmarrage, cette session va prendre un alias ; mais surtout elle va senregistrer auprs de IKC en listant la session et les vnements qui seront accessibles par les clients du service. Cela se fait en envoyant le message publish la session IKC. La nouvelle session dnit aussi lvnement debug, celuil mme qui est publi auprs de IKC. Comme dans lexemple prcdent, il ne fait quafcher les paramtres qui lui sont passs mais il pourrait faire beaucoup plus. . .

Crer un client IKC


use POE::Component::IKC::Client

Le client nest pas tellement plus compliqu :


use POE ; use POE :: C o m p o n e n t :: IKC :: C l i e n t ; use POE :: C o m p o n e n t :: IKC :: R e s p o n d e r ; POE :: C o m p o n e n t :: IKC :: R e s p o n d e r - > s p a w n ; POE :: C o m p o n e n t :: IKC :: Client - > s p a w n ( ip => " localhost " , port = > 17000 , name => " my_client " , o n _ c o n n e c t = > sub { POE :: S e s s i o n - > c r e a t e ( inline_states => {

360

CHAPITRE 19 POE distribu

_ s t a r t = > sub { POE :: Kernel - > y i e l d ( " t i c k " ) ; }, t i c k = > sub { POE :: Kernel - > p o s t ( IKC => post = > " poe :// m y _ s e r v e r / l o g g e r / d e b u g " => scalar localtime ); POE :: Kernel - > d e l a y ( t i c k = > 1) ; }, }, ); }, ); POE :: Kernel - > run ;

Un composant Responder est cr (sans paramtre), il sera utilis en interne par IKC qui en a besoin pour discuter avec le serveur. La cration du client IKC lui-mme se fait ainsi :
POE :: C o m p o n e n t :: IKC :: Client - > s p a w n (% p a r a m s ) ;

Outre les paramtres rseau permettant de joindre le serveur, le client va prendre un nom et un callback. Ce callback sera appel lorsque la connexion au serveur est effective. En effet, il nest pas judicieux de vouloir utiliser le serveur tant quil nest pas disponible. . . Le callback va ici simplement crer une nouvelle session. Celle-ci va nouveau senvoyer des vnements toutes les secondes, qui seront traits en utilisant le service debug propos par le serveur. Cela se ralise en envoyant lvnement post la session IKC, qui utilisera le premier pa-

Crer un client IKC

361

ramtre pour savoir quel serveur, session et vnement envoyer le message. La syntaxe pour dcrire lvnement cibl est :
poe :// $ s e r v e r / $ s e s s i o n / $ e v e n t

En effet, il est tout fait possible de se connecter plusieurs serveurs IKC (un par service), ou quun serveur accepte plusieurs vnements destins plusieurs sessions diffrentes. IKC se chargera en interne de toute la communication rseau, srialisera et dsrialisera (avec Data::Dumper) automatiquement les donnes. Attention
Un point important sur la scurit : un utilisateur du service peut passer nimporte quoi la session serveur, qui le dsrialisera dans un eval : cela peut impliquer de lexcution de code pour un client malveillant. . .

Aller plus loin


Cette introduction ne fait quefeurer le sujet de POE distribu. Beaucoup de choses peuvent tre faites en utilisant IKC. Pour en citer quelques-unes :
q

Passage dvnements retour : avec ou sans RSVP (retour diffr dans le temps). Cela permet de crer ou utiliser des protocoles de type RPC, synchrones ou asynchrones. Communication peer to peer : avec un noyau IKC serveur sur lequel se connectent plusieurs client IKC de type esclave (worker). Les tches sont ensuite planies par le serveur qui les distribue et collecte ensuite les rsultats.

362

CHAPITRE 19 POE distribu

Surveillance intgre des noyaux distants : IKC fournit les moyens techniques pour suivre lorsquun noyau se dconnecte ou se (re-)connecte suite une pertubation rseau (ou autres vnements).

Bref, IKC permet le passage lchelle de POE, tant en termes de meilleure utilisation des performances que de modularisation des programmes. Vitesse et robustesse accrues, pour une maintenance facilite : tels sont les avantages quIKC apporte POE.

20
Analyse de documents HTML
Cette partie prsente les moyens dinteragir en Perl avec le World Wide Web. Exactement comme un utilisateur aux commandes de son navigateur web, sauf que dans le cas prsent, le client web (le navigateur ) sera crit en Perl. Info
Le Web est la combinaison de trois lments importants : le format HTML pour encoder les documents, le protocole HTTP pour les transfrer et les URI pour les localiser. partir dune URI, un client saura quel serveur demander le document correspondant, et utilisera HTML (si le document est encod ainsi) pour lanalyser et pouvoir lafcher.

Partie VI Web

Il existe de nombreuses manires danalyser un document HTML. Ce chapitre prsente trs rapidement une approche simple mais dconseille, avant de proposer trois modules qui permettent une analyse correcte du format HTML. Ces modules seront prsents en partant de celui de plus bas niveau, qui sert de base aux suivants. Les exemples utiliss seront les mmes, an de pouvoir comparer les diffrences entre les modules.

364

CHAPITRE 20 Analyse de documents HTML

Analyser avec les expressions rgulires


m{<b>(.*?)</b>}i

premire vue, un document HTML, cest avant tout du texte. Et Perl dispose dun outil surpuissant pour lanalyse de texte : les expressions rgulires. Pourtant, nanalysez jamais des documents HTML avec des expressions rgulires ! Utiliser des expressions rgulires semble simple et rapide, mais (en dehors de scripts jetables qui ne seront jamais rutiliss), cette approche naura aucune prennit long terme. Voici quelques-uns des inconvnients de lutilisation des regexp dans le cadre de lanalyse de documents HTML :
q

Fragilit. Il suft quun caractre change dans le document analyser pour que lexpression ne corresponde plus et renvoie nimporte quoi (ou rien du tout). Inadaptation. Elles ne sont pas adaptes lanalyse dlments imbriqus (par exemple des listes de listes, avec un niveau arbitraire dimbrication).

Mauvaise reprsentation. HTML est un format qui reprsente des donnes structures en arbre. Il y a une innit de manires de reprsenter un contenu identique smantiquement. Les expressions rgulires ne sont pas adaptes la reconnaissance de donnes arborescentes, car elles analysent le texte un trs bas niveau : il faut tenir compte des blancs, des sauts de ligne, des guillemets simple ou double, des commentaires HTML, autant de choses qui peuvent vaq

Instancier un analyseur HTML::Parser

365

rier dans un document source, sans pour autant en changer la signication.

Utiliser lanalyseur vnementiel HTML::Parser


La plupart des modules danalyse HTML sont bass sur HTML::Parser. Ce module nest pas un analyseur SGML gnrique (mais qui se souvient quHTML est dni en SGML1 ?) ; il est capable danalyser le HTML tel quil existe sur le Web, tout en disposant doptions permettant de respecter les spcications du W3C (World-Wide Web Consortium) si cela est souhait.
HTML::Parser est un analyseur HTML vnementiel : lana-

lyse se fait partir dun ux de donnes, cest--dire que le document pourra tre reu sous forme de morceaux de taille arbitraire par lanalyseur, et au fur et mesure de lanalyse du document, des vnements (voir page 369) sont dclenchs, et pris en charge par des fonctions de rappel (callback).

Instancier un analyseur HTML::Parser


HTML::Parser->new(. . .)

De manire classique, un analyseur HTML::Parser est un objet qui est cr grce au constructeur new du module HTML::Parser.
1. SGML est un systme permettant de dnir des types de documents structurs et des langages de balisage pour reprsenter les instances de ces types de documents.

366

CHAPITRE 20 Analyse de documents HTML

use H T M L :: P a r s e r ; my $p = H T M L :: Parser - > new ( a p i _ v e r s i o n = > 3) ;

Lors de lappel new() ci-dessus, largument api_version => 3 a t pass. Par dfaut, un nouvel objet HTML::Parser est cr en utilisant la version 2 de lAPI du module, qui suppose lutilisation de noms de gestionnaires et de spcications darguments prdnis. La version 3 de lAPI est donc beaucoup plus souple. La classe HTML::Parser est sous-classable. Les objets sont de simples tables de hachage, et HTML::Parser se rserve uniquement les cls commenant par _hparser. Cela permet dutiliser lobjet HTML::Parser pour y stocker les tats ncessaires au cours de lanalyse.

Crer un gestionnaire dvnements


$p->handler(. . .)

Les gestionnaires dvnement sont dclars avec la mthode handler() :


$p - > h a n d l e r ( e v e n t = > \& h a n d l e r , $ a r g s p e c ) ;

Dans lexemple de code ci-dessus, event est le nom de lvnement traiter, \&handler est une rfrence la fonction codant le gestionnaire (il est galement possible de donner simplement un nom de mthode) et $argspec est une chane de caractres reprsentant la spcication des arguments attendus par le gestionnaire (handler() accepte galement dautres arguments : la documentation du module en prsente la liste complte).

Crer un gestionnaire dvnements

367

Il est aussi possible de dnir les gestionnaires lors de linitialisation de lanalyseur, laide de cls composes du nom de lvnement traiter et du sufxe _h (handler).
$p = H T M L :: Parser - > new ( api_version => 3, event_h = > [ \& h a n d l e r , $ a r g s p e c ] );

La valeur associe est une rfrence un tableau contenant les mmes paramtres que lors de lappel handler() (hormis le nom de lvnement). HTML::Parser offre une grande souplesse pour la dnition des gestionnaires dvnements : lors de leur dclaration (ce sont en gnral des fonctions spciques), il est possible de dnir prcisment quels paramtres ils vont recevoir. Voici la liste des principaux paramtres (voir la documentation de HTML::Parser pour une liste complte) :
q q

self : rfrence lobjet HTML::Parser ; attr : rfrence une table de hachage des cls/valeurs

des attributs ;
q q q

tagname : le nom de la balise ; text : texte source (y compris les lments de balisage) ; dtext : texte dcod (&eacute; sera transform en ).

Voici un exemple qui montre la cration dun gestionnaire avec un vnement. Pour la liste complte des vnements disponibles, voir pages 370 et suivantes. Pour le dtail sur la mthode parse, voir page 368.
use H T M L :: P a r s e r ; my $p = H T M L :: Parser - > new ( a p i _ v e r s i o n = > 3) ; $p - > h a n d l e r ( s t a r t = > \& h a n d l e r , " t a g n a m e , t e x t " );

368

CHAPITRE 20 Analyse de documents HTML

sub h a n d l e r { my ( $ t a g n a m e , $ t e x t ) = @_ ; say " d t e c t i o n de la b a l i s e $ t a g n a m e " ; say " le s o u r c e H T M L : $ t e x t " ; } $p - > p a r s e ( " < html > < body > Hello </ body > </ html > " ) ;

Cet exemple produira sur la console :


dtection le source dtection le source de la balise html HTML : <html> de la balise body HTML : <body>

Lancer lanalyse HTML


$p->parse(. . .) $p->parse_le(. . .)

Une fois les gestionnaires dnis, il reste lancer lanalyse, au moyen dune des mthodes suivantes : q $p->parse( $string ) : analyse le morceau suivant du document HTML. Les gestionnaires sont appels au fur et mesure. Cette fonction doit tre appele rptition tant quil reste des donnes analyser.
q

$p->parse( $coderef ) : il est galement possible dappeler parse() avec une rfrence de code, auquel cas

cette routine sera appele rptition pour obtenir le prochain morceau de document analyser. Lanalyse se termine quand la rfrence de code renvoie une chane vide ou la valeur undef.

Dtecter un nouveau document

369

$p->parse_le( $le ) : ralise lanalyse directement

depuis un chier. Le paramtre peut tre un nom de chier, un handle de chier ouvert, ou une rfrence un handle de chier ouvert. Si lun des gestionnaires dvnement interrompt lanalyse par un appel eof() avant la n du chier, alors parse_le() pourra ne pas avoir lu lintgralit du chier.

Terminer lanalyse du contenu


$p->eof()

Une fois toutes les donnes envoyes lanalyseur, il est trs important de signaler la n de chier, laide de $p>eof(). Si lun des gestionnaires termine prmaturment lanalyse en appelant directement $p->eof(), la mthode parse() renvoie une valeur fausse. Dans le cas contraire, parse() renvoie une rfrence lanalyseur lui-mme, qui est une valeur vraie.

Dtecter un nouveau document


start_document

Cet vnement est dclench avant tous les autres pour un nouveau document. Son gestionnaire pourra tre utilis des ns dinitialisation. Il ny a aucun texte du document associ cet vnement.

370

CHAPITRE 20 Analyse de documents HTML

my $p = H T M L :: Parser - > new ( a p i _ v e r s i o n = > 3) ; $p - > h a n d l e r ( s t a r t = > sub { say " d b u t du d o c u m e n t " } , );

Info
HTML::Parser gre neuf types dvnements, qui sont dclenchs dans diffrents cas.

Dtecter une balise


declaration

Cet vnement est dclench quand une dclaration de balisage (markup declaration) est rencontre. Dans la plupart des documents HTML, la seule dclaration sera <!DOCTYPE . . .>.
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">

Dtecter un commentaire
comment

Cet vnement est dclench quand un commentaire HTML est dtect.


use H T M L :: P a r s e r ;

Dtecter du texte brut

371

my $p = H T M L :: Parser - > new ( a p i _ v e r s i o n = > 3) ; $p - > h a n d l e r ( c o m m e n t = > sub { say s h i f t } , " t a g n a m e " ); $p - > p a r s e ( " <! - - c e c i est un c o m m e n t a i r e H T M L - - > " );

Dtecter un dbut de balise


start

Cet vnement est dclench lorsquune balise de dbut est reconnue.


use H T M L :: P a r s e r ; my $p = H T M L :: Parser - > new ( a p i _ v e r s i o n = > 3) ; $p - > h a n d l e r ( s t a r t = > sub { say s h i f t } , " t a g n a m e " ); $p - > p a r s e ( < a h r e f =" h t t p :// www . m o n g u e u r s . net /" > );

Dtecter du texte brut


text

Cet vnement est dclench lorsque du texte brut (des caractres) est dtect. Le texte peut contenir plusieurs lignes.

372

CHAPITRE 20 Analyse de documents HTML

Une squence de texte peut tre dcoupe entre plusieurs vnements text (sauf si loption unbroken_text de lanalyseur est active). Cependant, lanalyseur fait tout de mme attention ne pas couper un mot ou une srie de blancs entre deux vnements text.
use H T M L :: P a r s e r ; my $p = H T M L :: Parser - > new ( a p i _ v e r s i o n = > 3) ; $p - > h a n d l e r ( t e x t = > sub { say s h i f t } , " t e x t " ); $p - > p a r s e ( < body > Du texte </ body > ) ;

Dtecter la n dune balise


end

Cet vnement est dclench lorsquune balise de n est reconnue, comme par exemple </a>.

Dtecter la n du document
end_document

Cet vnement est dclench lors de la n du document. Il ny a aucun texte du document associ cet vnement.

Dtecter des instructions de traitement


process

Extraire du texte dun document

373

Cet vnement est dclench lorsque des instructions de traitement (processing instructions) sont dtectes. Lun des rares cas o cet vnement sert est quand le document est du XHTML, qui contiendra au moins la balise <?xml version="1.0?>

Capturer les autres vnements


default

Cet vnement est dclench pour les vnements qui nont pas de gestionnaire spcique.

Extraire du texte dun document


Lexemple suivant extrait le texte dun document HTML. Le gestionnaire associ lvnement text reoit le paramtre dtext et en afche directement le contenu.
use H T M L :: P a r s e r ; # c r a t i o n de l a n a l y s e u r my $p = H T M L :: Parser - > new ( a p i _ v e r s i o n = > 3) ; $p - > h a n d l e r ( t e x t = > sub { p r i n t $_ [0] } , " d t e x t " ); # a n a l y s e du d o c u m e n t $p - > p a r s e ( " " ) ; $p - > eof ;

Cet analyseur est un peu naf : dune part, tous les blancs (espaces et sauts de lignes) seront afchs tels quels ; dautre part, le contenu des blocs <script> et <style> sera galement afch.

374

CHAPITRE 20 Analyse de documents HTML

Pour ignorer ces blocs, il faut congurer lanalyseur avec la mthode ignore_elements() :
$p - > i g n o r e _ e l e m e n t s ( qw ( s c r i p t s t y l e ) ) ;

Pour supprimer les blancs inutiles, il faut modier le gestionnaire :


sub { $_ [0] =~ s /\ s +/ / g ; p r i n t " $_ [0] " ; }

On pourra galement minimiser le nombre dvnements text reus en congurant lanalyseur avec :
$p - > u n b r o k e n _ t e x t ;

Produire une table des matires


HTML est un format structur, qui permet de dnir des niveaux de titres dans un document (les balises h1, h2, jusqu h6). Cet exemple utilise les titres du document pour produire une table des matires.
# d f i n i t i o n de l a n a l y s e u r use H T M L :: P a r s e r ; my $p = H T M L :: Parser - > new ( api_version => 3, s t a r t _ h = > [ \& start , " self , t a g n a m e " ] , end_h = > [ \& end , " self , t a g n a m e " ] , t e x t _ h = > [ \& text , " self , d t e x t " ], ); # gestionnaires d vnements sub s t a r t { my ( $self , $ t a g n a m e ) = @_ ; $ t a g n a m e =~ /^ h (\ d +) $ / and $self - >{ l e v e l } = $1 ; }

Produire une table des matires

375

sub end { my ( $self , $ t a g n a m e ) = @_ ; if ( $ t a g n a m e =~ /^ h (\ d +) $ / ) { my ( $level , $ t e x t ) = d e l e t e @ { $ s e l f }{ qw ( l e v e l t e x t ) }; $ t e x t =~ s /\ s +/ / g ; $ t e x t =~ s /^\ s +|\ s + $ // g ; say " " x ( $ l e v e l - 1 ) , $ t e x t ; } } sub t e x t { my ( $self , $ t e x t ) = @_ ; $self - >{ l e v e l } and $self - >{ t e x t } .= " " . $ t e x t ; } # a n a l y s e du d o c u m e n t $p - > p a r s e ( $_ ) w h i l e < >; $p - > eof ;

Info
Cet exemple est stocker dans un chier, ici outline.pl. Pour lexcuter, il faut lui passer un nom de chier HTML en paramtre. Il peut galement sutiliser en sortie de pipe sous unix. perl ./outline.pl chier.html

Dans cet exemple, le travail est rparti entre trois gestionnaires :


q

start : se contente de tenir jour le niveau de titre en cours (level). text : dans le cas o un niveau de titre est dni (level est vrai), concatne le texte dcod (argument dtext) dans la cl text. end : la fermeture dune balise de titre, afche le texte

accumul jusque l, en liminant les blancs surnumraires.

376

CHAPITRE 20 Analyse de documents HTML

Ce code sappuie en particulier sur le fait quil ne peut y avoir de balises de titre imbriques en HTML. Un document HTML invalide (par exemple <h1>Titre 1<h2>Titre 2</h2></h1>) provoquera des avertissements. Voici ce que produit le programme sur le manuel de Gnuplot :
$ lwp-request http://www.gnuplot.info/docs/gnuplot.html | ./outline.pl Contents Part I Gnuplot 1 Copyright 2 Introduction 3 Seeking-assistance 4 New features introduced in version 4.4 4.1 Internationalization 4.2 Transparency 4.3 Volatile Data 4.4 Canvas size ...

On constate en passant que cette documentation na pas de titres de niveau 2 et saute directement du niveau 1 au niveau 3. Analyseur par token
Lanalyse vnementielle nest pas toujours la plus intuitive. HTML::TokeParser est un analyseur par token, utilisable dans une programmation imprative. Les lments analyss sont fournis par une mthode qui sera appele rptition, jusqu puisement des donnes. Le traitement ne se fait plus sur un ux, mais sur le document en entier. Il faut donc fournir un chier, un descripteur de chier ou une rfrence au texte complet du document au constructeur.

Crer une instance HTML::TokeParser

377

Crer une instance HTML::TokeParser


HTML::TokeParser->new()

Il y a plusieurs moyens dinitialiser HTML::TokeParser, et de crer un objet. Lextrait de code suivant prsente les trois mthodes principales, partir dun chier HTML, dun descripteur de chier, ou directement partir dune chane de caractres ($html).
use H T M L :: T o k e P a r s e r ; # nom de f i c h i e r my $p = H T M L :: T o k e P a r s e r - > new ( $file , % o p t i o n s ) ; # d e s c r i p t e u r de f i c h i e r o p e n my $fh , <: u t f 8 , i n d e x . h t m l or die " E r r e u r sur $ f i l e : $ ! " ; my $p = H T M L :: T o k e P a r s e r - > new ( $fh , % o p t i o n s ) ; # r f r e n c e v e r s le t e x t e du d o c u m e n t my $p = H T M L :: T o k e P a r s e r - >( \ $html , % o p t i o n s ) ;

Info
HTML::TokeParser est en ralit bas sur HTML::Parser en interne. Cest pourquoi les options ventuelles, sous forme dune liste de cls-valeurs, sont les mme que celles de HTML::Parser. Elles sont utilises pour congurer lanalyseur HTML interne HTML::TokeParser.

378

CHAPITRE 20 Analyse de documents HTML

Rcuprer des tokens


get_token

La principale mthode de HTML::TokeParser est get_token, qui renvoie le token suivant sous forme dune rfrence de tableau. Le premier lment du tableau est une chane reprsentant le type de llment, et les paramtres suivants dpendent du type. Lutilisation de get_token est trs simple, il suft de boucler :
use H T M L :: T o k e P a r s e r ; use D a t a :: D u m p e r ; my $ h t m l = < html > < body > </ body > </ html > ; my $p = H T M L :: T o k e P a r s e r - >(\ $ h t m l ) ; w h i l e ( my $ t o k e n = $p - > g e t _ t o k e n () ) { say D u m p e r ( $ t o k e n ) ; }

Il y a six types de token, chacun ayant diffrents paramtres associs :


q

Dbut de tag (S) :


["S", $tag , $attr , $ a t t r s e q , $ t e x t ]

Fin de tag (E) :


["E", $tag , $ t e x t ]

Texte (T) :
["T", $text , $ i s _ d a t a ]

Obtenir des balises

379

Commentaire (C) :
["C", $text ]

Dclaration (D) :
["D", $text ]

Instruction de traitement (PI) :


[ " PI " , $ t o k e n 0 , $ t e x t ]

La signication des tokens est la mme que pour HTML::Parser (voir page 369).
HTML::TokeParser na pas de mthodes pour ltrer les ba-

lises, mais fournit plusieurs mthodes de plus haut niveau.

Obtenir des balises


get_tag( @tags )

Cette fonction renvoie la prochaine balise de dbut ou de n, en ignorant les autres tokens, ou undef sil ny a plus de balises dans le document. Si un ou plusieurs argument sont fournis, les tokens sont ignors jusqu ce que lune des balises spcies soit trouve. Ce code trouvera la prochaine balise douverture ou de fermeture de paragraphe :
$p - > g e t _ t a g ( " p " , " / p " ) ;

Le rsultat est renvoy sous la mme forme que pour get_token(), avec le code de type en moins. Le nom des balises de n est prx par un /.

380

CHAPITRE 20 Analyse de documents HTML

Une balise de dbut sera renvoye comme ceci :


[ $tag , $attr , $ a t t r s e q , $ t e x t ]

Et une balise de n comme cela :


[ "/ $tag " , $text ]

Obtenir du texte
get_text( @endtags )

Cette fonction renvoie tout le texte trouv la position courante. Si le token suivant nest pas du texte, renvoie une chane vide. Toutes les entits HTML sont converties vers les caractres correspondants. Si un ou plusieurs paramtres sont fournis, alors tout le texte apparaissant avant la premire des balises spcies est renvoy. Certaines balises peuvent tre converties en texte (par exemple la balise <img>, grce son paramtre alt). Le comportement de lanalyseur est contrl par lattribut textify, qui est une rfrence un hash dnissant comment certaines balises peuvent tre converties. Chaque cl du hash dnit une balise convertir. La valeur associe est le nom du paramtre dont la valeur sera utilise pour la conversion (par exemple, le paramtre alt pour la balise img). Si le paramtre est manquant, le nom de la balise en capitales entre crochets sera utilis (par exemple [IMG]). La valeur peut galement tre une rfrence de code, auquel cas la fonction recevra en paramtre le contenu du token de dbut de balise, et la valeur de retour sera utilise comme du texte.

Extraire le texte dun document avec HTML::Parser

381

Par dfaut textify est dni comme :


{ img = > alt , a p p l e t = > alt }

Obtenir du texte nettoy


get_trimmed_text( @endtags )

Cette fonction opre comme get_text() mais remplace les blancs multiples par une seule espace. Les blancs en dbut et n de chane sont galement supprims.

Extraire le texte dun document avec HTML::Parser


Info
Dans les exemples qui suivent, lanalyseur lit le chier donn sur la ligne de commande. Les exemples sont les mmes que dans les sections consacres HTML::Parser, an de faciliter la comparaison entre les diffrents modules.

Voici une version nave :


use H T M L :: T o k e P a r s e r ; my $p = H T M L :: T o k e P a r s e r - > new ( s h i f t ) ; p r i n t $p - > g e t _ t r i m m e d _ t e x t ( " / h t m l " ) ;

Cette version a un petit dfaut : le contenu des balises <script> et <style> est considr comme du texte, et le code JavaScript ou CSS sera donc inclus dans le texte du document.

382

CHAPITRE 20 Analyse de documents HTML

La version qui suit lit le texte du document jusqu rencontrer lune des balises en question, saute la balise de n, et recommence jusqu puisement du document :
use H T M L :: T o k e P a r s e r ; my $p = H T M L :: T o k e P a r s e r - > new ( s h i f t ) ; # b o u c l e sur la r e c h e r c h e de t e x t e my @ s k i p = qw ( s c r i p t s t y l e ) ; while ( defined ( my $t = $p - > g e t _ t r i m m e d _ t e x t ( @ s k i p ) ) ) { # a f f i c h e le t e x t e o b t e n u p r i n t $t ; # s a u t e la b a l i s e de fin ou t e r m i n e # la l e c t u r e s il n y en a pas $p - > g e t _ t a g ( qw (/ s t y l e / s c r i p t ) ) or l a s t ; }

Produire une table des matires avec HTML::Parser


Ici, seul le texte lintrieur des balises <h1> <h6> doit tre afch. Les paramtres optionnels des mthodes get_tag() et get_text() seront bien utiles :
use H T M L :: T o k e P a r s e r ; my $p = H T M L :: T o k e P a r s e r - > new ( s h i f t ) ; # d f i n i t i o n de la l i s t e des b a l i s e s my @ s t a r t = qw ( h1 h2 h3 h4 h5 h6 ) ; my @ e n d = map { " / $_ " } @ s t a r t ;

Analyse par arbre avec HTML::TreeBuilder

383

# b o u c l e de r e c h e r c h e des d b u t s de t i t r e w h i l e ( my $ t o k e n = $p - > g e t _ t a g ( @ s t a r t ) ) { # c a l c u l du n i v e a u de t i t r e my ( $ l e v e l ) = $token - >[0] =~ / h (\ d ) /; # t e x t e j u s q u la fin de t i t r e my $ t e x t = $p - > g e t _ t r i m m e d _ t e x t ( @ e n d ) ; # a f f i c h e le texte , a v e c i n d e n t a t i o n p r i n t " " x ( $ l e v e l - 1 ) , $text , " \ n " if $ t e x t ; }

Analyse par arbre avec HTML::TreeBuilder


Comme on la vu, HTML dnit une structure arborescente. Les analyseurs prsents jusquici nutilisaient pas cet aspect fondamental, se contentant dvnements dclenchs ou de tokens dtects au l de la lecture du document.
HTML::TreeBuilder est un module qui hrite de HTML::Parser pour construire une structure de donnes en arbre. HTML::TreeBuilder hrite galement de HTML::Element. Lobjet HTML::TreeBuilder est la fois analyseur HTML

et nud racine dun arbre compos dautres objets de la classe HTML::Element. Les mthodes issues de HTML::Parser sont utilises pour construire larbre HTML, et celles issues de HTML::Element sont utilises pour inspecter larbre. ( La page de manuel HTML::Tree pointe vers plusieurs pages de manuels et articles permettant de mieux comprendre la documentation de HTML::TreeBuilder et HTML::Element. )

384

CHAPITRE 20 Analyse de documents HTML

Astuce
Il faut noter que le HTML est en gnral beaucoup plus difcile analyser que ceux qui lcrivent ne le croient. Les auteurs de HTML::TreeBuilder ont pris soin, pour produire une reprsentation arborescente correcte (cela nest pas ncessaire quand on se contente de dclencher des vnements ou produire un ux de tokens), de grer un certain nombre de cas particuliers comme les lments et balises de n implicites. Un exemple permet de mieux comprendre le problme, et la solution apporte par HTML::TreeBuilder. Soit le document HTML (complet !) : <li>item Le code suivant analyse le document et produit une sortie HTML correspondant larbre construit (en ajoutant les balises implicites, telles que <html>, <head>, etc.) :
p r i n t H T M L :: T r e e B u i l d e r - > n e w _ f r o m _ c o n t e n t ( < >) - > a s _ H T M L ;

Le rsultat est : <html><head></head><body><ul><li>item </ul></body></html> La sortie XML (en utilisant la mthode as_XML()) ajoute les balises de n implicites qui manquaient : <html><head></head><body><ul><li>item </li></ul> </body></html>

Crer un arbre
HTML::TreeBuilder->new_from_le(..) HTML::TreeBuilder->new_from_content(..)

La cration de larbre se fait partir du document HTML en utilisant lune des mthodes suivantes :

Rechercher un lment

385

q q

new_from_le( $le ) new_from_content( @args )

Ces mthodes sont des raccourcis qui combinent la construction dun nouvel objet, et lappel la mthode parse_ le() (ou parse(), rptition sur chacun des lments de @args) de HTML::Parser. Ces deux mthodes ne permettent pas de dnir des options sur lanalyseur avant lanalyse proprement dite. Une fois lanalyseur cr avec new() (et ventuellement congur en utilisant les mthodes hrites de HTML::Parser), il est galement possible dutiliser directement les mthodes parse_le() et parse() de HTML::Parser. La structure arborescente issue de lanalyse du HTML est relativement lourde en mmoire, et regorge de rfrences circulaires (chaque nud enfant pointe vers son parent, et rciproquement). Pour effacer proprement la structure de donnes, il est donc impratif dutiliser la mthode delete() sur lobjet racine.

Rechercher un lment
$tree->look_down( . . .)

Pour des programmes consacrs lanalyse dune page web pour en extraire les donnes utiles, la seule mthode vraiment indispensable connatre est look_down(), qui cherche partir de linvoquant les lments enfants qui correspondent aux critres de recherche. En contexte de liste, look_down() renvoie tous les lments qui correspondent aux critres, et en contexte scalaire, seulement le premier lment (ou undef).

386

CHAPITRE 20 Analyse de documents HTML

Info
Le module HTML::Element dispose dune API trs riche, avec de nombreuses mthodes consacres au fonctionnement de base (cration dlments, manipulation des balises et de leurs attributs), la modication de la structure (ajout, insertion, dtachement ou suppression dlments, clonage dobjets), au formatage et lafchage (dboggage, conversion en HTML (propre), XML, texte), et enn la structure elle-mme (parcours de larbre ; recherche partir dun nud ; obtention dlments structurels tels que le parent, les anctres, les descendants dun nud ; comparaisons dlments).

La forme gnrale dappel est :


$elem - > l o o k _ d o w n ( @ c r i t e r e s ) ;

o $elem est un objet HTML::Element, et les critres contenus dans @criteres sont de trois types :
q

(attr_name, attr_value) : cherche les lments ayant

la valeur souhaite pour lattribut indiqu. Il faut noter quil est possible dutiliser des attributs internes, tel _tag.
q

(attr_name, qr/. . ./) : cherche les lments dont la valeur correspond lexpression rgulire fournie pour lattribut indiqu.

un coderef : cherche les lments pour lesquels $coderef->( $element ) renvoie vrai. La liste de critres est value dans lordre. Il faut savoir que les deux premiers types de critres sont presque toujours plus rapides que lappel un coderef, aussi il est intressant de placer les coderefs en n de liste.
q

Le code suivant va rechercher les images dont le texte alternatif indique quil sagit de photos et dont la largeur (lattribut HTML de la balise <img>, pas la largeur relle du chier image) est suprieure 400 pixels.

Produire une table des matires avec HTML::TreeBuilder

387

@ p h o t o s = $elem - > l o o k _ d o w n ( _ t a g = > " img " , alt = > qr / p h o t o / i , sub { $_ [0] - > a t t r ( w i d t h ) >= 400 } , );

HTML::TreeBuilder est donc un module de trs haut ni-

veau pour analyser et manipuler un document HTML. Les exemples cods avec HTML::Parser et HTML::TokeParser vont sen trouver considrablement rduits.

Extraire du texte dun document avec HTML::TreeBuilder


use H T M L :: T r e e B u i l d e r ; my $ r o o t = H T M L :: T r e e B u i l d e r - > new () ; $root - > p a r s e ( $_ ) w h i l e < >; $root - > eof () ; p r i n t $root - > a s _ t r i m m e d _ t e x t ; $root - > d e l e t e () ;

Difcile de faire plus court. . .

Produire une table des matires avec HTML::TreeBuilder


use H T M L :: T r e e B u i l d e r ; my $ r o o t = H T M L :: T r e e B u i l d e r - > new () ; $root - > p a r s e ( $_ ) w h i l e < >; $root - > eof () ; my @ e l e m s = $root - > l o o k _ d o w n ( _ t a g = > qr /^ h [1 -6] $ / ) ;

388

CHAPITRE 20 Analyse de documents HTML

for my $ e l e m ( @ e l e m s ) { my $ l e v e l = s u b s t r ( $elem - > tag , -1 , 1) ; print " " x ( $level - 1 ) , $elem - > a s _ t r i m m e d _ t e x t , " \ n " ; } $root - > d e l e t e () ;

Extraire le titre dun document avec HTML::TreeBuilder


use H T M L :: T r e e B u i l d e r ; my $ r o o t = H T M L :: T r e e B u i l d e r - > new () ; $root - > p a r s e ( $_ ) w h i l e < >; $root - > eof () ; p r i n t $_ - > a s _ t r i m m e d _ t e x t , " \ n " for $root - > l o o k _ d o w n ( _ t a g = > " t i t l e " ) ; $root - > d e l e t e () ;

Astuce
Dans cet exemple, la boucle for permet de ne pas traiter le cas o il ny a pas de balise <title> comme un cas particulier. Puisquil y a au plus une balise <title> dans un document HTML, il est possible dutiliser look_down() en contexte scalaire et dcrire :
p r i n t $root - > l o o k _ d o w n ( _ t a g = > t i t l e ) - > as_trimmed_text , "\n";

En contexte scalaire, look_down() va renvoyer undef en labsence de balise <title>, ce qui impose de grer ce cas particulier, sous peine de voir le programme sinterrompre avec le message derreur :

Cant call method as_trimmed_text on an undened value.

21
HTTP et le Web
HTTP, le protocole de transfert la base du Web est devenu omniprsent. Parce quil est le seul protocole dont il est quasi certain quil passera travers les proxies et les rewalls, celui-ci est utilis comme protocole de transport gnrique pour tous les usages. Non seulement pour le transport de documents hypertexte (HTTP signie Hyper-Text Transport Protocol), mais aussi pour encapsuler des changes entre applications ou des appels de procdures distantes (web services, SOAP, XML-RPC, etc.) La matrise du protocole HTTP est donc indispensable pour un programme qui sera amen faire des changes avec le monde extrieur. La bibliothque libwww-perl a t crite en 1994 pour Perl 4 par Roy Fielding, lun des principaux auteurs de la spcication HTTP et membre fondateur de la fondation Apache. La version pour Perl 5 (oriente objet) a t crite par Gisle Aas, et est connue sous le nom de LWP. Cest elle (et les modules qui la composent et sappuient dessus) qui sera dcrite dans ce chapitre et le suivant. Ce chapitre prsente ce qui se passe lorsque quun navigateur se connecte un serveur web, et met les diffrents lments dcrit en relation avec la bibliothque Perl correspondante.

390

CHAPITRE 21 HTTP et le Web

Adresses
http://www.perdu.com:80/index.html

Tout commence par une adresse Internet, autrement dit une URL1 (ou URI2 ). Cette URI peut tre dcoupe en plusieurs lments :
q

http:// (scheme) : le protocole utiliser (ici HTTP).

Les URI servant dcrire des adresses plus gnrales, il existe des URI associes dautres protocoles (FTP, IRC, etc.).
q

www.perdu.com (host) : ladresse du serveur au sens du

DNS.
q

:80 (port : le port (TCP) sur lequel coute le serveur

web. Celui est optionnel, et sil est omis, la valeur par dfaut 80 est utilise.
q

/index.html (path) : le chemin du document sur le ser-

veur. En Perl, la bibliothque de gestion des URI sappelle tout simplement URI. Voici un exemple dutilisation :
use URI ; w h i l e ( < >) { chomp ; my $ u r i = URI - > new ( $_ ) ; say j o i n " \ n " , map { " $_ :\ t " . $uri - > $_ } qw ( s c h e m e h o s t p o r t p a t h ) ; }

1. Uniform Resource Locator. 2. Uniform Resource Identier.

Messages

391

Ce code donnera le rsultat suivant quand il reoit lURL prcdente sur lentre standard :
scheme: http host: www.perdu.com port: 80 path: /index.html

La classe URI fournit de nombreuses mthodes pour manipuler les URI, et dispose de sous-classes adaptes aux divers schemes. Ainsi, dans lexemple prcdent, lobjet $uri renvoy par URI->new() tait de classe URI::http.

Messages
quip de ladresse du document tlcharger, le client va se charger de lobtenir auprs du serveur. Il tablit donc une connexion au port indiqu et va dialoguer avec le serveur en utilisant le protocole HTTP. HTTP est un protocole base de messages. Les messages envoys par le client sont appels requtes , et les messages envoys par le serveur sont appels rponses . Dans les premires versions dHTTP, la connexion TCP servait changer une seule requte et une seule rponse. Ce genre dinefcacit a t corrig par la suite (en HTTP /1.1, les connexions TCP entre client et serveur peuvent tre persistantes et transporter plusieurs couples requterponse). Mais le principe reste le mme et il ny a pas vraiment dchange : le client envoie lintgralit de la requte, et ensuite seulement le serveur envoie lintgralit de la rponse3 .
3. Du point de vue du serveur, deux requtes successives sur la mme connexion TCP peuvent venir de clients diffrents. HTTP permet en effet lutilisation de serveurs mandataires (proxies), et une requte peut donc transiter de faon automatique travers un ou plusieurs intermdiaires (parfois linsu du client, par exemple dans le cas o un proxy transparent intercepte la connexion TCP du client).

392

CHAPITRE 21 HTTP et le Web

Les messages HTTP sont diviss en trois parties : q La ligne de statut. Cest la premire ligne du message. Pour une requte, elle contient la mthode, ses paramtres, et la version du protocole reconnue par le client. Pour une rponse, la ligne de statut contient un code derreur, un message en texte clair, et la version du protocole reconnue par le serveur.
q

Les en-ttes. Les en-ttes se prsentent sous la forme dune liste de cls/valeurs spares par le caractre :. Ce format est similaire celui des en-ttes dun courriel. Le corps. Le corps (optionnel) du message est spar des en-ttes par une ligne vide.

Certaines requtes nont pas de corps de message, par dnition. Cest le cas des requtes utilisant les mthodes GET et HEAD, par exemple. Dautres ont un corps de message, comme les requtes POST. Dans LWP, la classe HTTP::Message reprsente les messages. Les en-ttes peuvent tre manipuls via la classe HTTP::Headers.

Requtes
GET / HTTP/1.1

Lobservation de ce qui se passe sur le rseau, avec un outil comme Wireshark, montre que le client envoie ceci :
GET /index.html HTTP/1.1 Host: www.perdu.com User-Agent: Mozilla/5.0 (X11; U; Linux i686; fr; rv:1.9.2.3) Gecko/20100423 Ubuntu/10.04 (lucid) Firefox/3.6.3

Rponses

393

Accept: text/html,application/xhtml+xml,application/xml; q=0.9,*/*;q=0.8 Accept-Language: fr-fr,fr;q=0.8,en-gb;q=0.5,en;q=0.3 Accept-Encoding: gzip,deate Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7 Keep-Alive: 115 Connection: keep-alive

Il sagit dune requte GET qui, par dnition, na pas de corps de message. La classe HTTP::Request de LWP hrite de HTTP::Message.

Rponses
HTTP/1.1 200 OK

Voici quoi ressemble la rponse envoye par le serveur :


HTTP/1.1 200 OK Date: Tue, 01 Jun 2010 21:19:50 GMT Server: Apache Last-Modied: Tue, 02 Mar 2010 18:52:21 GMT Etag: "4bee144-cc-dd98a340" Accept-Ranges: bytes Vary: Accept-Encoding Content-Encoding: gzip Content-Length: 163 Keep-Alive: timeout=2, max=100 Connection: Keep-Alive Content-Type: text/html <163 octets de donnes compresse>

Le passage des donnes compresses contenues dans le corps du message travers gunzip fait apparatre le corps du message, ici le source HTML de la page :

394

CHAPITRE 21 HTTP et le Web

<html> <head> <title>Vous Etes Perdu ?</title> </head> <body> <h1>Perdu sur lInternet ?</h1> <h2>Pas de panique, on va vous aider</h2> <strong> <pre> * <----- vous &ecirc;tes ici</pre> </strong> </body> </html>

Cest le navigateur qui se charge du rendu de ce document. La classe HTTP::Response de LWP hrite de HTTP::Message.

22
LWP
LWP est une bibliothque trs riche (plus de 50 modules), et ce chapitre ne fait que montrer les principaux modules et mthodes disponibles pour raliser une navigation simple.

Utiliser LWP::Simple
Pour les utilisateurs ayant des besoins trs limits, ou dsirant crire des unilignes jetables, la bibliothque LWP::Simple fournit une vue simplie de libwww-perl, en exportant quelques fonctions ralisant des tches courantes. Il nest pas ncessaire de crer un objet URI, LWP::Simple sen chargera, en appelant URI-new() avec la chane de caractres fournie en paramtre.

Faire une requte GET sur une URL


get( $url )

Comme son nom lindique, get() fait une requte GET sur lURL fournie en paramtre, et renvoie le contenu de la rponse associe.

396

CHAPITRE 22 LWP

LURL est une simple chane de caractres :


use LWP :: S i m p l e ; $ c o n t e n t = get ( s h i f t ) ;

La fonction getprint() afche le contenu de la rponse associe lURL fournie et renvoie le code HTTP de la rponse,

Enregistrer le contenu de la rponse


getstore( $url => $chier )

Cette fonction enregistre le contenu de la rponse associe lURL fournie dans le chier spci et renvoie le code HTTP de la rponse.

Faire une requte HEAD sur une URL


head( $url )

Lorsquun programme na pas besoin du corps de la rponse, il est inutile (et coteux au niveau rseau) de le tlcharger pour le jeter ensuite. La mthode HEAD permet de demander un serveur web de faire le mme traitement quil aurait effectuer avec une requte GET, mais de ne renvoyer que les en-ttes de la rponse. En contexte de liste, la fonction head() renvoie quelques valeurs utiles issues de la rponse, ou la liste vide en cas dchec :

Crer un agent LWP::UserAgent

397

my ( $ c o n t e n t _ t y p e , $ d o c u m e n t _ l e n g t h , $modified_time , $expires , $server ) = head ( $url );

En contexte scalaire, head() renvoie une valeur vraie (en fait, lobjet HTTP::Response) en cas de succs. Cela peut servir par exemple tester une liste de liens pour ltrer les liens morts :
use LWP :: S i m p l e ; for my $ u r l ( @ u r l ) { say $ u r l if h e a d ( $ u r l ) ; }

Utiliser LWP::UserAgent
Ds que le programme ncessite plus de contrle sur les requtes et les rponses (par exemple pour manipuler les en-ttes), il est ncessaire de passer par la classe principale de la bibliothque LWP : LWP::UserAgent. LWP::UserAgent implmente un navigateur complet, qui sait manipuler les objets requtes et rponses prsents page 391 et permet de contrler dans les moindres dtails les requtes envoyes.

Crer un agent LWP::UserAgent


LWP::UserAgent->new( . . .)

La mthode new() permet de crer un nouvel objet LWP::UserAgent. Celle-ci prend une liste de paires cl/valeur en paramtres, pour congurer ltat initial du navigateur.

398

CHAPITRE 22 LWP

my $ua = LWP :: U s e r A g e n t - > new (% o p t i o n s ) ;

Parmi les cls importantes, il faut retenir : q agent : permet de dnir la valeur de len-tte UserAgent.
q

cookie_jar : dnit un objet HTTP::Cookies qui sera utilis pour conserver les cookies envoys par les serveurs web. env_proxy : demande la dnition des proxies partir

des variables denvironnement.


q

keep_alive : si elle est vraie, cette option cre un cache de connexions (LWP::ConnCache), la valeur associe d-

nit la capacit maximale du cache de connexions. Le cache de connexion permet au client dutiliser le mode keep-alive de HTTP 1.1, et dconomiser les connexions TCP.
q

show_progress : si elle vraie, une barre de progression

sera afche sur le terminal au fur et mesure du traitement des requtes. Ces options peuvent galement tre dnies en appelant les mthodes correspondantes sur un objet LWP::UserAgent directement.

Grer les rponses


HTTP::Response

Les mthodes qui envoient des requtes HTTP un serveur web vont renvoyer des objets HTTP::Response en retour. Le programme va pouvoir utiliser les attributs de cet

Faire une requte GET sur une URL avec LWP::UserAgent

399

objet HTTP::Reponse pour interagir avec lutilisateur ou le serveur web. En tant que sous-classe de HTTP::Message, HTTP::Reponse, comme HTTP::Request, dispose en particulier des mthodes suivantes :
q

headers() : renvoie lobjet HTTP::Headers qui repr-

sente les en-ttes du message.


q

content() : renvoie le contenu brut du corps du mes-

sage.
q

decoded_content() : renvoie le contenu dcod du corps

du message, en utilisant le jeu de caractres dni dans le message. Les principaux attributs dun objet HTTP::Response sont :
q q

code : le code de la rponse (200, 301, etc.) ; message : le message textuel associ au code de la r-

ponse ;
q

status_line : la ligne de statut de la rponse (cest-dire la chane <code> <message>).

Faire une requte GET sur une URL avec LWP::UserAgent


$ua->get( $url );

Pour les requtes HTTP qui nont pas de corps de message (GET et HEAD, par exemple), les mthodes de LWP::UserAgent peuvent se contenter dune URI, et ajouteront les en-ttes par dfaut. Ici aussi pour lURI, une chane de caractres suft ; la bibliothque crera lobjet URI ncessaire de faon transparente.

400

CHAPITRE 22 LWP

Cette mthode effectue donc une requte GET partir de lURI fournie. La valeur renvoye est un objet HTTP::Response.
use LWP :: U s e r A g e n t ; my $ r e s p o n s e = LWP :: U s e r A g e n t - > new () - > get ( $ u r l ) ; print $response -> is_success ? $response -> decoded_content : $response -> status_line ;

Un objet HTTP::Response est renvoy dans tous les cas, y compris quand LWP ne peut se connecter au serveur. (Une rponse derreur gnre par LWP aura la valeur Internal response pour len-tte Client-Warning.) Il est possible dajouter des en-ttes aux en-ttes par dfaut, en faisant suivre lURL dune liste de paires cl/valeur.
my $ r e s p o n s e = $ua - > get ( $url , User_Agent => Mozilla /3.14159 );

Enregistrer le contenu de la rponse


:content_le

Lors de lappel get(), les cls qui commencent par : sont spciales et ne seront pas interprtes comme des enttes ajouter la requte. Parmi les cls spciales reconnues, :content_le permet dindiquer un nom de chier dans lequel sauver le corps de la rponse. Dans le cas o la rponse nest pas un succs (code 2xx), le corps de la

Faire une requte HEAD sur une URL avec LWP::UserAgent

401

rponse sera toujours sauv dans lobjet HTTP::Response. Lquivalent de :


use LWP :: S i m p l e ; g e t s t o r e ( $url , $ f i l e n a m e ) ;

sera donc :
use LWP :: U s e r A g e n t ; my $ua = LWP :: U s e r A g e n t - > new () ; $ua - > get ( $url , : content_file => $filename );

Faire une requte HEAD sur une URL avec LWP::UserAgent


$ua->head( $url );

Exactement comme get(), head() va crer une requte HEAD partir de lURL fournie, et va renvoyer lobjet HTTP::Response correspondant. Un programme de test de lien pourra fournir un peu plus dinformations sur ltat des liens (ici, le code HTTP de la rponse) :
use LWP :: U s e r A g e n t ; my $ua = LWP :: U s e r A g e n t - > new () ; for my $ u r l ( @ u r l ) { my $ r e s p o n s e = $ua - > h e a d ( $ u r l ) ; say $ r e p o n s e - > code , , $ u r l ; }

402

CHAPITRE 22 LWP

Faire une requte POST sur une URL avec LWP::UserAgent


$ua->post( $url, . . .);

Les requtes POST sont des messages avec un corps. Elles servent typiquement lors de la validation de formulaires HTML, et lors de lenvoi de chiers. Cette mthode prend toujours lURL comme premier paramtre. Celle-ci et les paramtres qui suivent sont passs la fonction POST HTTP::Request::Common, laquelle est dlgue la cration de lobjet HTTP::Request reprsentant la requte POST. En plus des paires cl/valeur permettant de dnir des enttes spciques, elle accepte une rfrence de tableau ou de hash pour dnir le contenu dun formulaire web. Le code suivant :
$ua - > p o s t ( h t t p :// www . e x a m p l e . com / s u b m i t . cgi , [ couleur => rouge , fruit => banane , nombre => 3 ] );

produira la requte suivante :


POST http://www.example.com/submit.cgi Content-Length: 35 Content-Type: application/x-www-form-urlencoded couleur=rouge&fruit=banane&nombre=3

Le rsultat sera identique avec une rfrence de hash.

Diffrences entre LWP::UserAgent et un vrai navigateur

403

Envoyer des requtes


$ua->request( $request );

Les mthodes get(), head() et post() dcrites prcdemment sont en fait des raccourcis pour la mthode gnrale denvoi de requtes request(). Celle-ci prend en premier paramtre un objet HTTP::Request, que les raccourcis craient partir des paramtres fournis. Une autre mthode gnrale existe, simple_request(). la diffrence de request(), elle ne suit pas automatiquement les redirections, et sera donc susceptible de renvoyer un objet HTTP::Response avec un code 3xx.

Diffrences entre LWP::UserAgent et un vrai navigateur


LURL http://www.perdu.com/ a dj servi dexemple, page 389. Le code ci-dessous afche le texte de la requte envoye par LWP::UserAgent et celui de la rponse reue.
use LWP :: U s e r A g e n t ; my $ua = LWP :: U s e r A g e n t - > new () ; my $ u r i = URI - > new ( s h i f t // " h t t p :// www . p e r d u . com / " ); # la r e q u t e my $ r e q = H T T P :: R e q u e s t - > new ( GET = > $ u r i ) ; say $req - > a s _ s t r i n g ; # la r p o n s e my $ r e s = $ua - > r e q u e s t ( $ r e q ) ; say $res - > a s _ s t r i n g ;

404

CHAPITRE 22 LWP

Ce code afche le rsultat suivant :


GET http://www.perdu.com/index.html HTTP/1.1 200 OK Connection: close Date: Fri, 04 Jun 2010 22:56:04 GMT Accept-Ranges: bytes ETag: "4bee144-cc-dd98a340" Server: Apache Vary: Accept-Encoding Content-Length: 204 Content-Type: text/html Last-Modied: Tue, 02 Mar 2010 18:52:21 GMT Client-Date: Fri, 04 Jun 2010 22:56:04 GMT Client-Peer: 208.97.189.107:80 Client-Response-Num: 1 Title: Vous Etes Perdu ? X-Pad: avoid browser bug <html><head><title>Vous Etes Perdu ?</title></head> <body><h1>Perdu sur lInternet ?</h1><h2> Pas de panique, on va vous aider</h2><strong><pre> * <----- vous &ecirc;tes ici</pre></strong> </body></html>

La premire diffrence notable avec la rponse qui a t envoye un navigateur normal (voir page 393), cest que le corps de la rponse nest pas compress. Mais il y a dautres diffrences. . . La capture du trac rseau permet de voir la requte que LWP::UserAgent a effectivement envoy :
GET /index.html HTTP/1.1 TE: deate,gzip;q=0.3 Connection: TE, close Host: www.perdu.com User-Agent: libwww-perl/5.834

Diffrences entre LWP::UserAgent et un vrai navigateur

405

Celle-ci est remarquablement plus courte que celle de Firefox (voir page 392). Les diffrences entre les deux requtes expliquent les diffrences entre les rponses. Par exemple, en envoyant len-tte Accept-Encoding, Firefox signale quil accepte un certain nombre dencodages pour la rponse. La rponse du serveur contenait len-tte Content-Encoding: gzip qui indiquait que le corps (et le corps seulement) de celle-ci tait encod avec gzip. Lutilisation de la compression permet dconomiser de la bande passante, quand client et serveur peuvent sentendre sur lencodage. Tant qu scruter le rseau, voici galement les en-ttes de la rponse telle quelle a circul :
HTTP/1.1 200 OK Date: Fri, 04 Jun 2010 22:56:04 GMT Server: Apache Last-Modied: Tue, 02 Mar 2010 18:52:21 GMT ETag: "4bee144-cc-dd98a340" Accept-Ranges: bytes Content-Length: 204 Vary: Accept-Encoding Connection: close Content-Type: text/html X-Pad: avoid browser bug

Les en-ttes suivants nont pas t envoys par le serveur !


Client-Date: Fri, 04 Jun 2010 22:56:04 GMT Client-Peer: 208.97.189.107:80 Client-Response-Num: 1 Title: Vous Etes Perdu ?

Ces en-ttes ont t ajouts par LWP : les en-ttes Clientfournissent quelques informations supplmentaires sur lchange, et len-tte Title a t obtenu en lisant le bloc <head> du contenu de la rponse HTML.

406

CHAPITRE 22 LWP

Un point important retenir de cet exemple, et garder lesprit lors du dboggage dun programme, cest quune URI ne suft pas pour connatre la rponse exacte qui sera envoye. Celle-ci dpend galement des en-ttes de la requte. Et un navigateur courant peut envoyer beaucoup den-ttes, qui seront interprts par le serveur et inueront sur le contenu de la rponse.

23
Navigation complexe
crire un robot pour naviguer sur le Web na pas pour objectif de rcuprer une simple page. Il sagit en gnral de rejouer une navigation plus ou moins complexe, pour mettre jour des donnes ou tlcharger des chiers. La complexit peut prendre plusieurs formes, dont les plus courantes sont dcrites dans ce chapitre.

Traiter les erreurs


$response->is_success()

Les rponses HTTP sont spares en cinq diffrentes classes. Le premier des trois chiffres du code de la rponse indique sa classe. HTTP::Response dispose de mthodes boolennes qui permettent de savoir si une rponse appartient une classe donne. Les diffrentes classes sont : q 1xx : Information : requte reue, le traitement continue.
$response -> is_info

408

CHAPITRE 23 Navigation complexe

2xx : Succs : laction requise a t reue, comprise et

accepte avec succs.


$response -> is_success
q

3xx : Redirection : une action supplmentaire doit tre

effectue pour complter la requte.


$response -> is_redirect
q

4xx : Erreur du client : la requte a une syntaxe incor-

recte ou ne peut tre effectue.


$response -> is_error
q

5xx : Erreur du serveur : le serveur a t incapable de

rpondre une requte apparemment correcte.


$response -> is_error

(La mthode est la mme pour les erreurs client et les erreurs serveur.) Cela permet de tester de faon gnrique le rsultat dune requte, sans regarder le dtail de la rponse.

Authentier
$ua->credentials( . . .)

Laccs certains documents peut tre limit un petit nombre dutilisateurs, et protg par le mcanisme dauthentication inclus dans le protocole HTTP. Lorsquune ressource est protge par mot de passe, le serveur renvoie une rponse derreur (401 Authentication required). Il est possible de laisser LWP::UserAgent grer

Authentier

409

lauthentication directement, en linformant des codes dauthentication lis un site et aux domaines associs (realm en anglais). Les informations dauthentications (nom dutilisateur et mot de passe) sont associes lemplacement rseau (la chane host:port) et au domaine dauthentication (le realm dans le jargon HTTP). Le programme suivant teste la fonction sur le site http:// diveintomark.org/tests/client/http/ qui contient toute une batterie de tests pour les clients HTTP :
use LWP ; my $ u r l = h t t p :// d i v e i n t o m a r k . org / t e s t s / . c l i e n t / h t t p / 2 0 0 _ b a s i c _ a u t h . xml ; my $ua = LWP :: U s e r A g e n t - > new () ; $ua - > c r e d e n t i a l s ( d i v e i n t o m a r k . org :80 = > Use t e s t / b a s i c , test => basic ); my $ r e s p o n s e = $ua - > get ( $ u r l ) ; say $ r e s p o n s e - > s t a t u s _ l i n e ;

Si lappel credentials() est comment, le programme afche :


401 Authorization Required

sinon, il afche :
200 OK

LWP::UserAgent supporte les mthodes dauthentication

Basic, Digest et NTLM.

410

CHAPITRE 23 Navigation complexe

Grer les cookies


HTTP::Cookies

Les cookies sont un moyen dassocier un tat (en fait une chane de caractres, qui sera dcode par le serveur web) un site et un chemin dans le site. Certains sites utilisent des cookies pour maintenir une session utilisateur, aussi une navigation enchanant plusieurs requtes peut donc ncessiter de maintenir les cookies entre les requtes, an que le serveur soit en mesure dassocier celles-ci au mme utilisateur . Normalement, le serveur web cre le cookie en envoyant un en-tte Set-Cookie: ou Set-Cookie2: avec sa rponse. Le client envoie les cookies quil possde avec len-tte Cookie:. La classe HTTP::Cookies sert manipuler un cookie jar (une bote gteaux ), qui sera utilis par lobjet LWP::UserAgent pour analyser les en-ttes de rponse modiant les cookies, et mettre jour les en-ttes associs dans les requtes. Il est possible dinitialiser le navigateur avec un cookie jar vide, comme ceci :
my $ua = LWP :: U s e r A g e n t - > new ( c o o k i e _ j a r = > {} );

HTTP::Cookies permet de sauvegarder automatiquement

puis de relire les cookies depuis un chier :


use H T T P :: C o o k i e s ; my $ c o o k i e _ j a r = H T T P :: C o o k i e s - > new ( file = > " $ E N V { H O M E }/ c o o k i e s . dat " , autosave => 1, );

Crer un objet HTML::Form

411

Il est ensuite possible dutiliser ce cookie jar :


$ua - > c o o k i e _ j a r ( $ c o o k i e _ j a r ) ;

Grce au paramtre autosave, les cookies seront automatiquement enregistrs lors de la destruction de linstance de HTTP::Cookies.

Crer un objet HTML::Form


HTML::Form->parse( . . .)

La mthode de classe parse() analyse un document HTML et construit des objets HTML::Form pour chacun des lments <form> trouvs dans le document. En contexte scalaire, elle renvoie le premier formulaire. Formulaires
Lun des intrts de lutilisation de LWP est lautomatisation de tches fastidieuses ncessitant un navigateur web. Il devient possible dajouter une interface scriptable au-dessus dun banal site web. Nombre de tches consistent remplir un formulaire avec des donnes diverses. Si ces donnes peuvent tre obtenues par un programme, il est particulirement utile de permettre ce programme de remplir le formulaire lui-mme. Le module HTML::Form permet de produire des objets de type formulaire partir de source HTML, et de remplir ces formulaires. Leur validation renverra un objet requte qui pourra tre utilis an dobtenir une rponse.

412

CHAPITRE 23 Navigation complexe

La mthode dump() renvoie une version texte de ltat du formulaire, ce qui peut tre utile lors du dveloppement dun robot. Le script suivant afche le formulaire contenu dans une page web fournie en paramtre.
use LWP ; use H T M L :: F o r m ; say my $ u r l = s h i f t ; my $ r e s p o n s e = LWP :: U s e r A g e n t - > new () - > get ( $ u r l ) ; die $ r e s p o n s e - > s t a t u s _ l i n e if ! $ r e s p o n s e - > i s _ s u c c e s s ; say $_ - > d u m p for H T M L :: Form - > p a r s e ( $ r e s p o n s e , base => $url );

Excut sur la page daccueil dun moteur de recherche bien connu, celui-ci afche :
GET http://www.google.com/search [f] hl=fr (hidden readonly) source=hp (hidden readonly) ie=ISO-8859-1 (hidden readonly) q= (text) btnG=Recherche Google (submit) btnI=Jai de la chance (submit)

Slectionner et modier des champs de formulaire


$form->value( $selector, $value )

Un objet HTML::Form est une suite de champs (objets de la classe HTML::Form::Input).

Valider un formulaire

413

Les champs sont obtenus laide de slecteurs. Un slecteur prx par # doit correspondre lattribut id du champ. Sil est prx par . il doit correspondre son attribut class. Le prxe ^ (ou labsence de prxe) correspond une recherche sur le nom du champ. Cette mthode est en fait un raccourci, qui combine un appel la mthode nd_input() de HTML::Form (qui renvoie un objet HTML::Form::Input) et la mthode value() de HTML::Form::Input. Les deux lignes de code suivantes sont exactement quivalentes :
$form - > v a l u e ( $ s e l e c t o r , $ v a l u e ) ; $form - > f i n d _ i n p u t ( $ s e l e c t o r ) - > v a l u e ( $ v a l u e ) ;

Valider un formulaire
$form->click()

Renvoie lobjet HTTP::Request correspondant la validation du formulaire. Celui-ci peut ensuite tre utilis avec la mthode request() de LWP::UserAgent.

24
WWW::Mechanize
Lutilisation de LWP::UserAgent (voir page 397) peut se rvler fastidieuse pour lcriture de robots devant raliser des navigations longues et complexes, ncessitant contrle derreur, authentication, utilisation de cookies et validation de formulaires. Ces oprations exigent lajout de beaucoup de code structurel, qui est ncessaire mais rptitif, et obscurcit le droulement du programme. WWW::Mechanize est une sous-classe de LWP::UserAgent (ce qui permet le cas chant de revenir la classe de base si besoin est), qui fournit des mthodes adaptes lcriture de robots. Le code structurel est cach dans des mthodes de haut niveau, rendant le code source des robots plus court et plus lisible, puisquil ne contient que les oprations qui font sens au niveau de la navigation.

Crer un objet WWW::Mechanize


WWW::Mechanize->new( %options )

Le constructeur de WWW::Mechanize supporte les options du constructeur de LWP::UserAgent, et en ajoute quelquesunes.

416

CHAPITRE 24 WWW::Mechanize

Parmi elles, loption autocheck (active par dfaut) va forcer WWW::Mechanize vrier que chaque requte sest droule correctement (cest--dire que la rponse a un code en 2xx). En gnral, il ny a donc pas besoin de vrier que les requtes ont dbouch sur un succs.
$ p e r l - M W W W :: M e c h a n i z e \ - e WWW :: M e c h a n i z e - > new - > get ( s h i f t ) \ h t t p :// www . e x a m p l e . com / r i e n E r r o r G E T i n g h t t p :// www . e x a m p l e . com / r i e n : Not F o u n d at - e l i n e 1

Dans le cas o le robot doit traiter les erreurs de manire plus ne, il faudra dsactiver autocheck et faire la validation manuellement. Loption stack_depth permet de dnir la taille de lhistorique du navigateur. Par dfaut, celle-ci nest pas limite, mais un robot faisant beaucoup de requtes risque de consommer beaucoup de mmoire. Si stack_depth est initialise 0, aucun historique nest conserv.
WWW::Mechanize active galement la gestion des cookies

automatiquement. Lexemple ci-dessous change les valeurs par dfaut :


use WWW :: M e c h a n i z e ; my $m = WWW :: M e c h a n i z e - > new ( # pas de v r i f i c a t i o n a u t o m a t i q u e autocheck => 0, # m m o i r e de 5 p a g e s stack_depth => 5, # pas de c o o k i e s c o o k i e _ j a r = > undef , );

Lancer une requte POST

417

Lancer une requte GET


$m->get( $url )

Cette mthode fait un GET sur lURL en paramtre et renvoie un objet HTTP::Response. Lobjet HTTP::Response correspondant la dernire requte est galement accessible via la mthode response().

Lancer une requte POST


$m->post( $url, . . .)

WWW::Mechanize tant une sous-classe de LWP::UserAgent, il possde galement une mthode post() (voir page 402 pour les dtails).

Revenir en arrire
$m->back()

Cette mthode est lquivalent de lappui sur le bouton Back dun navigateur. Renvoie une valeur boolenne indiquant le succs ou lchec de lopration. Elle ne rejoue pas la requte : rien nest envoy sur le rseau. Elle se contente de remettre le robot dans ltat o il se trouvait avant la dernire requte.

418

CHAPITRE 24 WWW::Mechanize

Recharger une page


$m->reload()

Cette mthode rejoue la dernire requte (nouvel envoi dune requte identique la prcdente) au serveur. Naltre pas lhistorique.

Suivre des liens


$m->follow_link( . . .)

Avec les mthodes dcrites ci-aprs, la puissance dexpression de WWW::Mechanize commence apparatre.
nd_link() permet de trouver un lien dans la page en

cours. Les critres de recherche permettent de tester lgalit ou la correspondance avec une expression rgulire. Les critres sont :
q q

text, text_regex : le texte du lien. url, url_regex : lURL du lien (telle que trouve dans

le document).
q

url_abs, url_abs_regex : lURL absolue du lien (les

URL relatives tant automatiquement converties).


q q q q

name, name_regex : le nom du lien. id, id_regex : lattribut id du lien. class, class_regex : lattribut class du lien. tag, tag_regex : la balise du lien (la balise <a> nest pas

la seule contenir des liens).


q

n : le nime lien correspondant.

Suivre des liens

419

Si plusieurs critres sont fournis, le lien renvoy devra les respecter tous. Quelques exemples : q text_regex => qr/download/ : le texte lien doit correspondre lexpression rgulire qr/download/.
q

text => bananana, url_regex => qr/spelling/ : le texte du lien doit tre bananana, et lURL associe doit correspondre lexpression rgulire qr/spelling/. text => Cliquez ici, n => 4 : le lien doit tre le quatrime lien dont le texte est Cliquez ici.

Par dfaut n est 1, cest donc le premier lien correspondant qui sera renvoy. Le cas particulier n => all demande de renvoyer lensemble des liens correspondants. Lobjet renvoy est de la classe WWW::Mechanize::Link. La partie intressante est son url().
use WWW :: M e c h a n i z e ; my $m = WWW :: M e c h a n i z e - > new () ; $m - > get ( $ u r l ) ; # t r o u v e le p r e m i e r l i e n qui p a r l e de b a n a n e my $ l i n k = $m - > f i n d _ l i n k ( t e x t _ r e g e x = > qr / b a n a n e / ) ; # et a f f i c h e son URL say $link - > url ;

follow_link() est un raccourci pour le cas le plus courant : elle fait simplement un get() sur le rsultat de nd_link().

Info
WWW::Mechanize permet de simplier normment la gestion de
la complexit dcrite page 407. Comme le montrent les sections suivantes, traitement derreur, gestion des cookies et des formulaires sont facilits.

420

CHAPITRE 24 WWW::Mechanize

Traiter les erreurs


Comme on la vu, WWW::Mechanize fait par dfaut une gestion derreur minimale : le programme meurt quand la rponse reue par get() nest pas un succs. La mthode success() permet de tester directement le succs dune rponse, si le navigateur a t cr sans loption autocheck.
my $m = WWW :: M e c h a n i z e - > new ( a u t o c h e c k = > 0) ; $m - > get ( $ u r l ) ; if ( ! $m - > s u c c e s s ) { # t r a i t e m e n t de l e r r e u r }

Authentier
$m->credentials( $username, $password )

La mthode credentials dnit le nom dutilisateur et le mot de passe utiliser jusqu nouvel ordre.

Grer les cookies


Les cookies sont grs automatiquement par WWW::Mechanize. Il est toujours possible dutiliser le paramtre cookie_jar du constructeur pour forcer par exemple lutilisation de cookies sauvegards dans un chier (voir page 410).

Slectionner un formulaire par son nom

421

Grer les formulaires


$m->forms

La mthode forms() renvoie la liste des objets HTML::Form associs aux formulaires de la page en cours. Lorsquil faut afcher des formulaires intermdiaires au cours de la navigation (typiquement lors de lcriture dun programme complexe), il est utile demployer la formulation suivante :
p r i n t $_ - > d u m p () for $m - > f o r m s () ;

La plupart des mthodes de WWW::Mechanize associes aux formulaires travaillent sur le formulaire courant, qui a t slectionn au moyen dune des mthodes dcrites ciaprs. Si aucun formulaire na t slectionn, le formulaire par dfaut est le premier de la page.

Slectionner un formulaire par son rang


$m->form_number($number)

Slectionne le nime formulaire de la page. Attention, la numrotation commence 1.

Slectionner un formulaire par son nom


$m->form_name( $name )

Slectionne le premier formulaire portant le nom donn.

422

CHAPITRE 24 WWW::Mechanize

Slectionner un formulaire par son identiant


$m->form_id( $id )

Slectionne le premier formulaire ayant lattribut id donn.

Slectionner un formulaire par ses champs


$m->form_with_elds( @elds )

Slectionne le premier formulaire comportant les champs dont les noms sont donns.

Remplir le formulaire slectionn


$m->set_elds( . . .)

Cette mthode modie plusieurs champs du formulaire courant. Elle prend une liste de paires cl/valeur, reprsentant les noms des champs et les valeurs leur appliquer.
$m - > s e t _ f i e l d s ( username => anonyme , passowrd => s3kr3t , );

Slectionner, remplir et valider un formulaire

423

Valider le formulaire slectionn


$m->submit() $m->click( $button )

Ces deux mthodes valident et envoient le formulaire vers le serveur. submit() envoie le formulaire, sans cliquer sur aucun bouton. click() envoie le formulaire en simulant le clic sur un bouton. Si aucun nom de bouton nest donn en paramtre, elle simule un clic sur le premier bouton.

Slectionner, remplir et valider un formulaire


$m->submit_form( . . .)

Cette mthode combine la slection du formulaire, son remplissage et sa validation en un seul appel. Le code suivant :
$m - > s u b m i t _ f o r m ( form_name => search , fields => { query => Perl }, button = > S e a r c h Now , );

sera quivalent :
$m - > f o r m _ n a m e ( s e a r c h ) ; $m - > f i e l d s ( q u e r y = > P e r l ) ; $m - > c l i c k ( S e a r c h Now ) ;

424

CHAPITRE 24 WWW::Mechanize

Exemple dapplication
Pour illustrer lutilisation de WWW::Mechanize, lexemple dun site de paste va tre utilis. Ces sites web permettent de coller du texte dans un formulaire simple, et renvoient un URL quil suft ensuite de faire suivre un correspondant pour quil puisse visualiser le texte en question. Ce genre de site est trs utilis sur IRC (en particulier sur les canaux consacrs la programmation), an dviter de polluer les canaux avec des lignes et des lignes de code. Certains sites sont connects un robot qui annonce les nouveaux ajouts sur le canal slectionn. Cest le site http://nopaste.snit.ch qui sera utilis1 . Le programme suivant se connecte au site et y sauvegarde le contenu de loption paste ou dfaut le contenu de lentre standard, ce qui permet de lutiliser la n dune ligne de commande Unix.
use use use use use strict ; warnings ; 5.010; WWW :: M e c h a n i z e ; G e t o p t :: L o n g ;

my $ p a s t e = h t t p :// n o p a s t e . s n i t . ch / ; # r c u p r a t i o n des o p t i o n s my % o p t i o n = ( channel => , nick => , summary => , paste => , list => , );

1. Il en existe de nombreux autres, utilisant la mme interface ou une interface similaire. Le module CPAN uniant laccs aux sites de paste est App::Nopaste.

Exemple dapplication

425

GetOptions ( \% option , nick = s , summary =s , paste =s , channel =s , list ! , ) or die " M a u v a i s e s o p t i o n s " ; # c r a t i o n du r o b o t et c o n n e x i o n au s i t e my $m = WWW :: M e c h a n i z e - > new ; $m - > get ( $ p a s t e ) ; # a f f i c h e la l i s t e des c a n a u x if ( $ o p t i o n { l i s t } ) { p r i n t " C a n a u x d i s p o n i b l e s :\ n " , map " - $_ \ n " , g r e p $_ , $m - > c u r r e n t _ f o r m () -> find_input ( channel ) -> possible_values ; exit ; } # s a n s o p t i o n paste , # lit les d o n n e s sur l e n t r e s t a n d a r d if ( ! $ o p t i o n { p a s t e } ) { $ o p t i o n { s u m m a r y } ||= $ A R G V [0] || - ; $ o p t i o n { p a s t e } = j o i n " " , < >; } # r e m p l i t et v a l i d e le f o r m u l a i r e # n o t e : l i s t n est d a n s le f o r m u l a i r e d e l e t e $ o p t i o n { l i s t }; $m - > s e t _ f i e l d s (% o p t i o n ) ; $m - > c l i c k ; # a f f i c h e l URL d o n n e en r p o n s e p r i n t +( $m - > l i n k s ) [0] - > url , " \ n " ;

Loption list, quand elle est active, fournit la liste des canaux IRC disponibles en afchant les valeurs possibles du champ associ du formulaire (obtenues laide des mthodes de HTML::Form).

426

CHAPITRE 24 WWW::Mechanize

JavaScript
De nombreux sites utilisent JavaScript pour faire excuter du code par le client, le plus souvent li la prsentation, mais pas seulement. Ainsi, le code JavaScript peut crer des cookies (normalement, les cookies sont envoys par le serveur) qui ne seront pas dtectables par LWP::UserAgent. La technologie dite AJAX va ainsi crer des requtes supplmentaires pour obtenir des donnes (en gnral au format JSON) qui seront utilises par le code JavaScript pour modier les donnes afches. En gnral, il ny a pas besoin dexcuter le JavaScript pour simuler la navigation dun utilisateur humain. La lecture du code JavaScript et lanalyse du trac HTTP sufsent souvent dcouvrir quels requtes ou en-ttes supplmentaires sont ncessaires. Nanmoins, certains sites (trop modernes ou mal conus) ne sont vritablement fonctionnels quavec un navigateur disposant dun support JavaScript. Il existe toutefois plusieurs solutions, disponibles sur le CPAN, pour naviguer mcaniquement avec excution du code JavaScript. Les utilisateurs du systme Windows peuvent dj essayer avec Win32::IE::Mechanize, qui permet de piloter Internet Explorer au travers du protocole OLE. Mais ce module nest toutefois plus vraiment maintenu. Les utilisateurs de systmes Unix peuvent eux regarder les modules Mozilla::Mechanize et Gtk2::WebKit::Mechanize, qui tous les deux sappuient sur le toolkit Gtk2 et son support pour embarquer un moteur de navigation, dans le premier cas Gecko, le moteur de Mozilla Firefox, et dans le second cas

Exemple dapplication

427

WebKit, le moteur de Safari et Chrome. Le dfaut de ces modules est quils sont crits en XS et ncessitent donc de disposer des sources des moteurs en question, ce qui les rend difciles installer.

WWW::Mechanize::Firefox est de ce point de vue dj plus


facile mettre en place car il sappuie sur le plugin MozRepl de Firefox qui permet de contrler le navigateur distance par un simple protocole. Le module est donc pur Perl, et Firefox tant disponible sur de nombreux systmes, linstallation et lutilisation de cette solution est bien plus envisageable. Il existe encore une dernire solution, WWW::Scripter, qui fait partie de ces modules totalement fous (mais dans le bon sens du terme) qui peuplent le CPAN. Il sagit dune sous-classe de WWW::Mechanize qui utilise des modules implmentant un moteur JavaScript (JE) avec les supports DOM pour HTML et CSS associs. Tous ces modules tant en pur Perl, leur installation ne pose normalement pas de problme particulier. WWW::Scripter peut aussi utiliser SpiderMonkey, le moteur JavaScript de Mozilla Firefox, mais le module Perl correspondant a le problme usuelle de la dpendance aux sources du logiciel pour son installation. Il nest pas impossible quil supporte aussi V8, le moteur JavaScript de Chrome, tant donn que JavaScript::V8 fournit dj linterface.

A
Tableau rcapitulatif des oprateurs
Cette annexe fournit la liste des oprateurs de Perl, prsents dans lordre de prcdence.
Prcdence 1 2 2 3 4 4 4 5 5 6 6 6 Nom -> ++ ** ! \ = ! * / % Description Drfrencement, appel de mthode Incrmentation de variable Dcrmentation de variable Puissance NON logique NON binaire Cration de rfrence Application dune expression rgulire Application ngative dune expression rgulire Multiplication Division Modulo

430

ANNEXE A

Tableau rcapitulatif des oprateurs

Prcdence 6 7 8 8 9 9 9 9 10 10 10 10 10 10 11 12 12 13 14 15 16 17 17 18 18 19 20 21 21

Nom x +-. << >> < et <= > et >= lt et le gt et ge == != <=> eq ne cmp & | ^ && || .. et . . . ? : = += -= *= etc. , => not and or xor

Description Duplication de chanes Oprations arithmtiques et concatnation Dcalage binaire gauche Dcalage binaire droit Infriorit numrique Supriorit numrique Infriorit de chane Supriorit de chane galit numrique Non-galit numrique Comparaison numrique galit de chane Non-galit de chane Comparaison de chanes ET binaire OU binaire OU exclusif binaire ET logique OU logique Cration dintervalles Test alternatif Affectation de variable Modication et affectation Cration de liste Cration de liste NON logique (faible prcdence) ET logique (faible prcdence) OU logique (faible prcdence) OU exclusif logique

Index
Symboles
| (regexp) 121 * (oprateur) 24 ** (oprateur) 24 + (oprateur) 24 ++ (oprateur) 24 += (oprateur) 24 - (oprateur) 24 (oprateur) 24 -= (oprateur) 24 . (oprateur) 25 . (regexp) 100 .. (oprateur) 48, 61 / (oprateur) 24 $/ (variable prdnie) 185-187 = (regexp) 97, 98 == (oprateur) 30 ? (regexp) 112 ?+ (regexp) 115 ?? (regexp) 114 $ (regexp) 100, 107, 108 % (oprateur) 24, 89 $_ (variable prdnie) 36, 86, 87, 89, 97, 98, 124 ^ (oprateur) 73 ^ (regexp) 100, 107 \D (regexp) 105 \G (regexp) 109 \K (regexp) 109 \S (regexp) 105 \W (regexp) 105, 108 \Z (regexp) 108

\b (regexp) 108 \d (regexp) 105, 111 \s (regexp) 105, 111 \w (regexp) 105, 108, 111 \z (regexp) 108 > (oprateur) 30 >= (oprateur) 30 < (oprateur) 30 <= (oprateur) 30 -> (oprateur) 40 => (oprateur) 74 $! (variable prdnie) 184, 195 {n,}+ (regexp) 115 {n,}? (regexp) 114 {n,m} (regexp) 113 {n,m}+ (regexp) 115 {n,m}? (regexp) 114 {n} (regexp) 113 {n}+ (regexp) 115 {n}? (regexp) 114 \1 (variable prdnie) 117 $1 (variable prdnie) 97, 98, 117 \2 (variable prdnie) 117 $2 (variable prdnie) 97, 98, 117 \3 (variable prdnie) 117 $3 (variable prdnie) 97, 98, 117

432

A
abstraction SQL 223 accs un objet Moose 140 alternative dexpression rgulire 121 analyse de document HTML 363 HTML::Parser, 365 arrt, 369 vnement, 366 instanciation, 365 lancement, 368 HTML::TokeParser balise, 379 texte, 380, 381 token, 378 HTML::TreeBuilder cration, 384 recherche, 385 HTML::TreeBuilder, 383 HTML::TokeParser, 377 regexp, 364 ancre dexpression rgulire correspondance globale, 109 dbut de chane, 108 dbut de ligne, 106 n de chane, 108 n de ligne, 107 frontire de mot, 108 par prxe, 109 anne 259 AnyEvent::CouchDB (module) 244 aplatissement de liste, 61 de tableau, 61 App::cpanminus (module) 11 arbre DOM 276 arrter un programme 42 atome dexpression rguliere 102 attribut dlment XML, 293

Moose, 138 avec dclencheur, 150 obligatoire, 144 paresseux, 148 augmentation de mthode parente Moose 177 awk 124

B
base de donnes 203 abstraction SQL, 223 DBI, 203 connexion, 205 dconnexion, 209 erreur, 216 proling, 219 test connexion, 208 trace, 217 KiokuDB, 239 administration, 242 rcupration, 240 stockage, 240 non-SQL cl-valeur, 243 document, 244 ORM, 232 BerkeleyDB 239 bless (fonction) 132 boucle 35 foreach, 35 sur hash, 82, 83 sur liste, 65 sur tableau, 65 while, 36

INDEX

C
chanage dattribut Moose 153 chane de caractres 20 cration, 20

433

champ de formulaire HTML 412 changement de rpertoire 201 chargement de document XML 275, 286 chemin de chier 189, 190 chomp (fonction) 25 chop (fonction) 25, 37 Class::DBI (module) 232, 233 classe dun objet, 132 de caractres, 104 Moose, 137 cl de hash 81 commande systme 42 communication entre sessions POE 336 Cong::IniFiles (module) 320, 322 consommation de rle Moose 157 constructeur dobjet, 135 Moose, 159 construction dattribut Moose, 147 dobjet Moose, 159 contenu dlment XML 292 contexte 22 de liste, 23 scalaire de chaine, 23 scalaire numrique, 22 cookie HTTP 410, 420 copier-coller XML 297 cos (fonction) 24 CouchDB 240, 244 CouchDB::Client (module) 244 CPAN (Comprehensive Perl Archive Network) 7 cpanm 11 cration dobjet, 131 de rpertoire, 193 de session POE, 331

Cwd (module) 201

D
dboggage 14 Data::Dumper (module) 71-73, 82, 301, 303, 305-307, 316, 361 Data::Phrasebook::SQL (module) 226 Data::Phrasebook (module) 223, 224, 226 Data::Phrasebook::SQL (module) 223-225, 227, 230 date du jour, 259 interprtation, 249 langue, 251 str2time, 250 strptime, 250 date et heure 247 DateTime anne, 259 date du jour, 259 dure, 263 formateur, 268 heure, 261 interprtation, 270 intervalle de temps, 263 jour, 260 maintenant, 258 mois, 260 seconde, 261 dure, 252, 263 maintenant, 254 interprtation de dure, 253 intervalle de temps, 252, 263 maintenant, 254 Date::Language (module) 251 Date::Parse (module) 250, 251 DateTime anne, 259

INDEX

434

DateTime (suite) calcul ajout de dure, 265 futur, 264 intervalle, 267 pass, 265 soustraction de dure, 266 date du jour, 259 formateur, 268 heure, 261 interprtation, 270 jour, 260 maintenant, 258 mois, 260 seconde, 261 DateTime (module) 251, 256, 259, 263-265, 267-270 DateTime::Duration (module) 256, 263, 265-267 DateTime::Event (module) 256 DateTime::Format::Oracle (module) 271 DateTime::Format (module) 256, 268 DateTime::Format::Builder (module) 271 DateTime::Format::DBI (module) 271 DateTime::Format::Human (module) 271 DateTime::Format::Human::Duration (module) 271 DateTime::Format::Natural (module) 271 DateTime::Format::Pg (module) 268 DateTime::Format::SQLite (module) 271 DateTime::Format::Strptime (module) 270, 271 DB2 204 DBD::Gofer (module) 205

DBD::mysql (module) 206 DBD::mysqlPP (module) 204 DBD::Nagios (module) 205 DBD::Oracle (module) 206 DBD::Pg (module) 206 DBD::PgPP (module) 204 DBD::Proxy (module) 205 DBD::Wire10 (module) 204 DBD::WMI (module) 204 DBI 203, 349 connexion, 205 dconnexion, 209 erreur, 216 POE, 348 proling, 219 requte combinaison, 214 donne de retour, 212 excution, 211 liaison, 211 prparation, 209 test de connexion, 208 trace, 217 DBIx::Class architecture, 233 schma, 234, 237 DBI (module) 204-219, 224, 239, 349 DBI::Prof (module) 221 DBI::PurePerl (module) 204 $DBI_DRIVER (variable prdnie) 206 $DBI_DSN (variable prdnie) 206 $DBI_PROFILE (variable prdnie) 219, 220 $DBI_TRACE (variable prdnie) 217 DBIx::Class (module) 233, 236 DBIx::Class::Row (module) 234 DBIx::Class::Schema::Loader (module) 236

INDEX

435

DBIx::Class::Schema::Loader::Base (module) 236 DBIx::Connect::FromCong (module) 207 dclaration de fonction, 26 simple dattribut Moose, 153 dened (fonction) 56, 78 dlgation de mthode parente Moose 179, 180 delete (fonction) 78 delta_days() (fonction) 268 delta_md() (fonction) 267 delta_ms() (fonction) 268 drfrencement dattribut Moose, 151 de hash, 84 descripteur de chier criture, 187 fermeture, 188 lecture, 185 destruction dobjet Moose 159 die (fonction) 42, 184, 195, 341 do (fonction) 303 DOM (Document Object Model) 274, 284 DTD (Document Type Denition) 285 dure de temps 252, 263 maintenant, 254

E
each (fonction) 83 Eclipse 4, 6, 16 criture de chier 187 ed 96 Editplus 5 lment de tableau, 51

XML, 290, 291 Emacs 5 envoi dvnement POE 332 EPIC 4, 16 eq (oprateur) 31 espace de nommage 43 eval (fonction) 98, 303, 361 vnement POE 327 excuter perl 3 chier excutable, 15 ligne de commande, 13 mode dbug, 15 sur un chier, 15 excution de requte SQL 211 exists (fonction) 56, 78 exit (fonction) 341 expression rgulire alternative, 121 ancre correspondance globale, 109 dbut de chane, 108 dbut de ligne, 106 n de chane, 108 n de ligne, 107 frontire de mot, 108 par prxe, 109 Regexp::Assemble, 126 atome, 102 classe de caractre, 104 Regexp::Common, 124 dcoupage, 122 groupe capturant, 116 non capturant, 119 mtacaractre, 102 modicateur casse, 99 correspondance globale, 101 extension de syntaxe, 101 ligne simple, 100 multiligne, 100 quantieur *, 111

INDEX

436

quantieur (suite) +, 112 ?, 112 {n,m}, 113 {n}, 113 non avide, 114 possessifs, 115 recherche, 96 et remplacement, 97 stockage, 98 Text::Match::FastAlternatives, 128 YAPE::Regex::Explain, 128

foreach (mot-cl) 36 foreach (fonction) 82, 87 format de date 268 formulaire HTML, 411 HTTP, 421

G
ge (oprateur) 31 gnration de document XML 289 gestion derreur SQL 216 gestionnaire dvnement HTML::Parser 366 Getopt::Long (module) 42 grep (fonction) 86, 89 grep 96 groupe dexpression rgulire capturant, 116 non capturant, 119 gt (oprateur) 31 Gtk2::WebKit::Mechanize (module) 426

F
fermeture de chier 188 chier .ini, 320 de conguration, 320 criture, 187 fermeture, 188 lecture, 185 ouverture, 183 Path::Class, 190, 191 Path : :Class chemin, 189 temporaire, 197 File::HomeDir (module) 200 File::HomeDir (module) 199, 200 File::pushd (module) 202 File::Slurp (module) 187 File::stat (module) 193 File::Temp (module) 197, 198, 202 fonction dclaration, 26 passage de paramtres, 27 valeur de retour, 28 variable locale, 29

INDEX

H
hash 19, 74 afchage, 82 boucle, 82, 83 cration, 75 drfrence, 84 lment, 76 liste de cls, 81 liste de valeurs, 81 rfrence, 84 suppression dlment, 78 test dlment, 78 tranche, 79 hritage de classe Moose 154 heure 261

437

HTML 114, 273, 363 analyse, 363 HTML::Parser, 365 HTML::TokeParser, 377 HTML::TreeBuilder, 383 regexp, 364 formulaire, 411 HTML::Element (module) 383, 386 HTML::Form (module) 411-413, 421 HTML::Form::Input (module) 412, 413 HTML::Parser (module) 276, 365-367, 377, 379, 381, 383, 385, 387 HTML::TokeParser (module) 376-379, 387 HTML::Tree (module) 383 HTML::TreeBuilder (module) 383, 384, 387 HTTP 389 adresse, 390 cookie, 410 LWP, 395 contenu, 396 get, 395 head, 396 simple, 395 LWP::UserAgent, 397 contenu, 400 cration, 397 get, 399 head, 401 post, 402, 403 rponse, 398 rponse, 393 requte, 391, 392, 396, 399, 401-403 HTTP::Cookies (module) 398, 410-411 HTTP::Headers (module) 392, 399

HTTP::Message (module) 392-394, 399 HTTP::Reponse (module) 399 HTTP::Request (module) 393, 399, 402, 403, 413 HTTP::Request::Common (module) 402 HTTP::Response (module) 394, 397-401, 403, 407, 417

I
IDE 4, 13, 16 IDE 4 if (oprateur) 184 index (fonction) 26 Ingres 204 INI (format) 320 installer Perl sous Linux, 5 sous Mac, 6 sous Windows, 6 installer un module avec cpan, 10 avec cpanm, 11 interprtation de date, 249 DateTime, 270 langue, 251 str2time, 250 strptime, 250 de dure, 253 intervalle 48 de temps, 252, 263 maintenant, 254 inversion de liste, 60 de tableau, 60 IO::File (module) 183-185, 188, 194 IO::Handle (module) 286 IO::Select (module) 326

INDEX

438

IO::Socket (module) 326

J
JavaScript::V8 (module) 427 JE (module) 427 join (fonction) 25 jour 260 JSON (module) 310 JSON (JavaScript Object Notation) 308, 323 srialisation de donnes, 310 JSON::XS (module) 310

K
keys (fonction) 81 KiokuDB administration, 242 rcupration, 240 stockage, 240 KiokuDB (module) 238-240, 242, 245 KiokuDB::Cmd (module) 242

L
last (mot-cl) 36 le (oprateur) 31 lecture de chier 185 length (fonction) 54 lex 110 lier une requte SQL 211 List::MoreUtils (module) 47, 89 List::Util (module) 47, 89 liste 47 aplatissement, 61 de mots, 49 de rpertoires, 193

ddoublonnage, 94 inversion, 60 mlange, 92 plus grand lment, 90 plus petit lment, 90 premier lment, 89 rduction, 91 somme, 92 tricotage, 93 local (fonction) 187 lt (oprateur) 31 LWP 395 contenu, 396 get, 395 head, 396 simple, 395 LWP (module) 389, 392-395, 397, 411 LWP::ConnCache (module) 398 LWP::Simple (module) 395 LWP::UserAgent (module) 397-399, 403, 404, 408-410, 413, 415, 417, 426 LWP::UserAgent 397 contenu, 400 cration, 397 get, 399 head, 401 post, 402 rponse, 398 requte, 403

INDEX

M
m// (regexp) 96-98 MacPorts 6 maintenant 258 map (fonction) 86, 87, 89 Marpa (module) 110 Memcached 243 message diffr POE 339

439

mtacaractre dexpression rguliere 102 mthode dun objet, 133 obligatoire de rle Moose, 158 parente Moose, 176 modicateur dexpression rguliere casse, 99 correspondance globale, 101 extension de syntaxe, 101 ligne simple, 100 multiligne, 100 modication daccesseur Moose, 141 dattribut Moose hrit, 156 de mthode Moose, 171-174 module cration, 43 utilisation, 44 mois 260 MongoDB 240 Moose 137 attribut avec dclencheur, 150 chanage, 153 construction, 147 dclaration, 138 dclaration simple, 153 drfrencement, 151 hritage, 156 obligatoire, 144 paresseux, 148 prdicat, 143 rfrence faible, 152 typage, 145 valeur par dfaut, 145 classe dclaration, 137 hritage, 154 constructeur, 159 hritage attribut, 156

augmentation de mthode, 177 dlgation de mthode, 179-180 super, 176 surcharge, 155 mthode augmentation, 177 dlgation, 179, 180 intercalage, 174 modication, 171 parente, 176 post-traitement, 173 prtraitement, 172 surcharge, 155 objet accs, 140 accesseur, 141 construction, 159 dstruction, 159, 161 rle consommation, 157 cration, 156 mthode obligatoire, 158 typage cration, 166 de base, 163 numration, 167 personnalis, 165 sous-type, 165 transtypage, 168 union, 167 Moose (module) 131, 135, 137-180, 238, 355 Moose::FollowPBP (module) 142 Moose::Meta::Attribute::Native::Trait::Hash (module) 181 Moose::Object (module) 138, 159 Moose::Role (module) 157

INDEX

440

Moose::Util::TypeConstraints (module) 165, 166 MooseX::ChainedAccessor (module) 153 MooseX::Has::Sugar (module) 153 MooseX::POE (module) 355, 356 MooseX::SemiAffordanceAccessor (module) 142 MooseX::Singleton (module) 138 Mozilla::Mechanize (module) 426 my 19 MySQL 204

or, 34 prcdence, 25, 429 sur scalaires, 24 test ngatif, 33, 34 Oracle 204 ORM (Object-Relational Mapping) 232 ouverture de chier 183, 194

P
package (mot-cl) 43 Padre 4, 6, 16 paramtre dvnement POE, 333 de fonction, 27 ligne de commande, 41 par rfrence, 39 parcourir un rpertoire 196 parcours DOM, 277 SAX, 281 XPath, 280 Parse::RecDescent (module) 110 Parse::Yapp (module) 110 Path::Class (module) 189-191, 193, 195-196, 200, 201 chemin, 189, 190 chier ouverture, 194 suppression, 196 rpertoire cration, 193 information, 193 liste, 193 parcours, 196 parent, 191 sous-rpertoire, 191 suppression, 193

N
INDEX
navigation web 407, 415 authentication, 408 formulaire, 411 champ, 412 validation, 413 traitement des erreurs, 407 ne (oprateur) 31 next (mot-cl) 36 non-SQL cl-valeur, 243 document, 244 notepad++ 5 noyau POE 329

O
open() (fonction) 284 oprateurs and, 34 conditionnels, 32 de chanes, 25 de test, 29, 31

441

Path::Class::Dir (module) 191 Path::Class::File (module) 194 Path::Class:File (module) 191 POE (module) 326-362 POE (Perl Object Environment) 325 composant DBI, 348 de bas niveau, 348 de haut niveau, 346 distribu, 357 client, 359 serveur, 358 entre-sortie, 345 vnement, 327 paramtre, 333 message heure dite, 340 diffr, 339 noyau, 329 principe, 326 session, 328 communication, 336 cration, 331 variable, 335 terminer, 341 traitement, 341 POE::Component (module) 346 POE::Component::Client::CouchDB (module) 244 POE::Component::EasyDBI (module) 348, 349, 354, 355 POE::Component::IKC::Client (module) 359 POE::Component::IKC::Server (module) 358 POE::Kernel (module) 329 POE::Wheel (module) 347 POE::Wheel::Run (module) 349

pop (fonction) 57 POSIX 96, 105 PostgreSQL 204, 239 prcdence 25, 429 prparation de requte SQL 209 print (fonction) 20 print (mot-cl) 20 printf (fonction) 188 proling SQL 219 programmation vnementielle 325 client, 359 composant DBI, 348 de bas niveau, 348 de haut niveau, 346 de niveau intermdiaire, 347 distribue, 357 entre-sortie, 345 vnement, 327 envoi, 332 paramtre, 333 message heure dite, 340 diffr, 339 noyau, 329 principe, 326 serveur, 358 session, 328 communication, 336 cration, 331 variable, 335 terminer, 341 traitement, 341 programmation objet classe, 132 concept, 131 constructeur, 135 cration dobjet, 132 mthode appel, 133 dnition, 133

INDEX

442

Moose, 137 constructeur, 159 hritage, 154 push (fonction) 57

Q
q() (oprateur) 21 QED 96 qr// (regexp) 99 quantieur dexpression rgulire *, 111 +, 112 ?, 112 {n,m}, 113 {n}, 113 non avide, 114, 115 qw (oprateur) 49

courant, 201 parent, 191 personnel, 199 temporaire, 198 rponse HTTP 393 requte HTTP 391, 392, 396, 399, 401-403, 417 return (mot-cl) 28 reverse (fonction) 60 rindex (fonction) 26 rle Moose 156

S
s/// (regexp) 97, 98, 109 SAX (Simple API for XML) 275, 281 say (fonction) 20 say (mot-cl) 20 scalaire 18, 19 afchage, 19 dclaration, 19 drfrence, 38 initialisation, 19 oprateurs, 24 de chanes, 25 rfrence, 37 test de dnition, 26 scalar() (mot-cl) 24 SciTE 5 seconde 261 srialisation de donnes 301 Data::Dumper, 301 JSON, 310 Storable, 307 YAML, 316 session POE 328 set_inner_xml (fonction) 293 shift (fonction) 58 sigil 18, 21, 38, 50 sin (fonction) 24 sous-rpertoire 191

INDEX

R
Redis 240, 243, 244 rfrence 18 accs, 38 drfrence, 38, 68 fonction, 40 hash, 84 paramtre, 39 scalaire, 37 tableau, 67, 69 regexp voir expression rgulire Regexp::Assemble (module) 126, 127 Regexp::Common (module) 124, 125 Regexp::Grammars (module) 110 Regexp::Keep (module) 109 rpertoire changement, 201

443

splice (fonction) 58 split (fonction) 25, 122-124 SQL abstraction, 223 combinaison, 214 donne de retour, 212 erreur, 216 excution, 211 liaison, 211 ORM, 234 prparation, 209 POE, 348 proling, 219 trace, 217 SQLite 204, 239 Storable (module) 307, 308 strict (module) 138 structure hybride 85 substr (fonction) 26 supprimer chier, 196 rpertoire, 193 surcharge de mthode Moose 155 system (fonction) 42

T
table de hachage 18, 19, 74 afchage, 82 boucle, 82, 83 cration, 75 drfrence, 84 lment, 76 liste de cls, 81 liste de valeurs, 81 rfrence, 84 suppression dlment, 78 test dlment, 78 tranche, 79 tableau 18, 50

plusieurs dimensions, 67, 70, 71 affectation dlment, 52, 55 afchage, 71 aplatissement, 61 boucle, 65 dbut, 58 drfrence, 68 dernier lment, 53 lment, 51 n, 56 inversion, 60 milieu, 58 premier lment, 53 rfrence, 67, 69 suppression dlment, 60 taille, 54 test dlment, 55 tranche, 62 Tera Term 13 terminer un programme POE 341 Text::Match::FastAlternatives (module) 128 TextMate 5 Tie::Hash::NamedCapture (module) 119 Time::Duration (module) 252-255 Time::Duration::fr (module) 255 Time::HiRes (module) 340 Tokyo Cabinet 243 trace SQL 217 tranche de tableau 62 transtypage Moose 168 typage Moose 163, 165-168 types de donnes 18 liste, 47 structure hybride, 85 table de hachage, 19, 74 tableau, 18, 50

INDEX

444

U
uc (fonction) 87 undef (mot-cl) 19, 23, 30, 385 Unicode 110, 311 unshift (fonction) 58 URI (module) 390, 391, 395, 399 URI (Uniform Resource Identier) 363, 390 URI::http (module) 391 URL (Uniform Resource Locator) 390 use (mot-cl) 44 UTC (Temps universel coordonn) 262

INDEX

V
valeur de retour 28 valeurs de hash 81 validation de formulaire HTML 413, 423 values (fonction) 81 variable de session POE, 335 scalaire, 19 version de Perl 1 vim 5

W
warnings (module) 138 Web 389 adresse, 390 cookie, 410 LWP, 395 contenu, 396 get, 395 head, 396 simple, 395

LWP::UserAgent, 397 navigation, 407 authentication, 408 formulaire, 411 traitement des erreurs, 407 rponse, 393 requte, 391, 392, 396, 399, 401-403, 417 WWW::Mechanize, 415 authentication, 420 back, 417 cookie, 420 cration, 415 formulaire, 421 get, 417 lien, 418 post, 417 rechargement de page, 418 simple, 415 traitement des erreurs, 420 while (mot-cl) 36, 37 Win32::IE::Mechanize (module) 426 WWW::Mechanize (module) 415-427 back, 417 cookie, 420 cration, 415 formulaire, 421 raccourcis, 423 remplissage, 422 slection, 421, 422 validation, 423 get, 417 lien, 418 post, 417 rechargement de page, 418 traitement des erreurs, 420 WWW::Mechanize::Firefox (module) 427 WWW::Mechanize::Link (module) 419 WWW::Scripter (module) 427

445

X
XHTML 373 XML 273 XML::LibXML, 275 chargement, 275 DOM, 277 SAX, 281 XPath, 280 XML::Twig chargement, 286 copier-coller, 297 cration, 285 gnration, 289 handlers, 287 XML::LibXML (module) 275-277, 279-281, 286 XML::SAX (module) 281 XML::SAX::Base (module) 282 XML::SAX::Machines (module) 283, 284 XML::SAX::Manifold (module) 284 XML::SAX::Pipeline (module) 284 XML::Twig (module) 284-291, 294, 297, 299 XML::Twig::Elt (module) 285, 287, 289, 291-294 xor (oprateur) 73 XPath 280, 287, 295

INDEX

Y
yacc 110 YAML 224, 313, 316, 323 YAML (module) 316 YAML::Syck (module) 316 YAML::XS (module) 316 YAPE::Regex::Explain (module) 128

LE GUIDE DE SURVIE

Perl moderne
Ce Guide de survie est loutil indispensable pour programmer en Perl aujourdhui. Il prsente les dernires volutions de Perl 5 par ses versions 5.10 et 5.12, fortement empreintes de la version 6 en cours de nalisation.

CONCIS ET MANIABLE
Facile transporter, facile utiliser nis les livres encombrants !

PRATIQUE ET FONCTIONNEL
Plus de 350 squences de code pour rpondre aux situations les plus courantes et exploiter efcacement les fonctions et les bibliothques dun langage qui sest radicalement modernis. Sbastien Aperghis-Tramoni, Philippe Bruhat, Damien Krotkine et Jrme Quelin sont activement impliqus dans la communaut Perl franaise, via lassociation Les Mongueurs de Perl, qui organise notamment Les Journes Perl . Ils sont les auteurs de nombreux modules Perl.

Niveau : Intermdiaire / Avanc Catgorie : Programmation

Pearson Education France 47 bis rue des Vinaigriers 75010 Paris Tl. : 01 72 74 90 00 Fax : 01 42 05 22 17 www.pearson.fr

ISBN : 978-2-7440-4164-8

Você também pode gostar