NAME

XML::Reader::French - Lire du XML avec des informations du chemin, conduit par un parseur d'extraction.

TRADUCTION

This document is the French translation from English of the module XML::Reader. In order to get the Perl source code of the module, please see file XML/Reader.pm

Ce document est une traduction Française de l'Anglais du module XML::Reader. Pour obtenir la source Perl du module, consultez le fichier XML/Reader.pm

SYNOPSIS

use XML::Reader;

my $text = q{<init>n <?test pi?> t<page node="400">m <!-- remark --> r</page></init>};

my $rdr = XML::Reader->newhd(\$text) or die "Error: $!";
while ($rdr->iterate) {
    printf "Path: %-19s, Value: %s\n", $rdr->path, $rdr->value;
}

Ce programme crée le résultat suivant:

Path: /init              , Value: n t
Path: /init/page/@node   , Value: 400
Path: /init/page         , Value: m r
Path: /init              , Value:

DESCRIPTION

XML::Reader est un module simple et facile à utiliser pour parser des fichiers XML de manière séquentielle (aussi appellé parseur guidé par l'extraction) et, en même temps, il enregistre le chemin complet du XML.

Il a été développé comme une couche sur XML::Parser (quelques fonctionalités basiques ont été copié de XML::TokeParser). XML::Parser et XML::TokeParser utilisent chacun une méthode d'extraction séquentielle, mais ils n'enregistrent pas le chemin du XML.

De plus, avec les interfaces de XML::Parser et XML::TokeParser, on est obligé de séparer les balises de début, les balises de fin et du texte, ce qui, à mon avis, rend l'utilisation assez compliqué. (par contre, si on le souhaite, XML::Reader peut agir d'une manière à ce que les balises de début, les balises de fin et du texte sont séparés, par l'option {filter => 4}).

Il y a aussi XML::TiePYX, qui permet de parser des fichiers XML de manière séquentielle (voir http://www.xml.com/pub/a/2000/03/15/feature/index.html pour consulter une introduction à PYX). Mais même avec XML::TiePYX, il faut séparer les balises de début, les balises de fin et le texte, et il n'y a pas de chemin disponible.

Par contre, avec XML::Reader, les les balises de début, les balises de fin et le texte sont traduits en expressions similaires à XPath. En conséquence, il est inutile de compter des balises individuelles, on a un chemin et une valeur, et ça suffit. (par contre, au cas où on veut opérer XML::Reader en fonctionnement compatible à PYX, il y a toujours l'option {filter => 4}, comme déjà mentionné ci-dessus).

Mais revenons-nous au fonctionnement normal de XML::Reader, voici un exemple XML dans la variable '$line1':

my $line1 = 
q{<?xml version="1.0" encoding="iso-8859-1"?>
  <data>
    <item>abc</item>
    <item><!-- c1 -->
      <dummy/>
      fgh
      <inner name="ttt" id="fff">
        ooo <!-- c2 --> ppp
      </inner>
    </item>
  </data>
};

Cet exemple peut être parsé avec XML::Reader en utilisant les méthodes iterate pour lire séquentiellement les données XML, et en utilisant les méthodes path et value pour extraire le chemin et la valeur à un endroit précis dans le fichier XML.

Si nécessaire, on peut également identifier les balises individuelles de début et de fin: Il y a une méthode is_start, qui donne 1 ou 0 (c'est à dire: 1, s'il y a une balise de début à la position actuelle, sinon 0). Il y a également la méthode équivalente is_end.

En plus, il y a les méthodes tag, attr, type and level. tag retourne le nom de la balise en cours, attr retourne l'identifiant d'un attribut, type retourne 'T' s'il y a du texte, ou '@' s'il y a des attributs et level indique le niveau de cascadage (un nombre >= 0)

Voici un programme qui lit le XML dans la variable '$line1' (voir ci-dessus) pour montrer le principe...

use XML::Reader;

my $rdr = XML::Reader->newhd(\$line1) or die "Error: $!";
my $i = 0;
while ($rdr->iterate) { $i++;
    printf "%3d. pat=%-22s, val=%-9s, s=%-1s, e=%-1s, tag=%-6s, atr=%-6s, t=%-1s, lvl=%2d\n", $i,
      $rdr->path, $rdr->value, $rdr->is_start, $rdr->is_end, $rdr->tag, $rdr->attr, $rdr->type, $rdr->level;
}

...et voici le résultat:

 1. pat=/data                 , val=         , s=1, e=0, tag=data  , atr=      , t=T, lvl= 1
 2. pat=/data/item            , val=abc      , s=1, e=1, tag=item  , atr=      , t=T, lvl= 2
 3. pat=/data                 , val=         , s=0, e=0, tag=data  , atr=      , t=T, lvl= 1
 4. pat=/data/item            , val=         , s=1, e=0, tag=item  , atr=      , t=T, lvl= 2
 5. pat=/data/item/dummy      , val=         , s=1, e=1, tag=dummy , atr=      , t=T, lvl= 3
 6. pat=/data/item            , val=fgh      , s=0, e=0, tag=item  , atr=      , t=T, lvl= 2
 7. pat=/data/item/inner/@id  , val=fff      , s=0, e=0, tag=@id   , atr=id    , t=@, lvl= 4
 8. pat=/data/item/inner/@name, val=ttt      , s=0, e=0, tag=@name , atr=name  , t=@, lvl= 4
 9. pat=/data/item/inner      , val=ooo ppp  , s=1, e=1, tag=inner , atr=      , t=T, lvl= 3
10. pat=/data/item            , val=         , s=0, e=1, tag=item  , atr=      , t=T, lvl= 2
11. pat=/data                 , val=         , s=0, e=1, tag=data  , atr=      , t=T, lvl= 1

INTERFACE

Création d'un objet

Pour créer un objet, on utilise la syntaxe suivante:

my $rdr = XML::Reader->newhd($data,
  {strip => 1, filter => 2, using => ['/path1', '/path2']})
  or die "Error: $!";

L'élément $data est obligatoire, il est le nom d'un fichier XML, ou la référence à une chaîne de caractères, dans ce cas le contenu de cette chaîne de caractères est accepté comme XML.

Sinon, $data peut également être une référence à un fichier, comme par exemple \*STDIN. Dans ce cas, la référence de fichier est utiliser pour lire le XML.

Voici un exemple pour créer un objet XML::Reader avec un fichier:

my $rdr = XML::Reader->newhd('input.xml') or die "Error: $!";

Voici un autre exemple pour créer un objet XML::Reader avec une référence à une chaîne de caractères:

my $rdr = XML::Reader->newhd(\'<data>abc</data>') or die "Error: $!";

Voici un exemple pour créer un objet XML::Reader avec une référence à un fichier:

open my $fh, '<', 'input.xml' or die "Error: $!";
my $rdr = XML::Reader->newhd($fh);

Voici un exemple pour créer un objet XML::Reader avec \*STDIN:

my $rdr = XML::Reader->newhd(\*STDIN);

On peut ajouter une ou plusieurs options dans une référence à un hashage:

option {parse_ct => }

Option {parse_ct => 1} permet de lire les commentaires, le defaut est {parse_ct => 0}

option {parse_pi => }

Option {parse_pi => 1} permet de lire les processing-instructions et les XML-declarations, le défaut est {parse_pi => 0}

option {using => }

Option {using => } permet de sélectionner un arbre précis dans le XML.

La syntaxe est {using => ['/path1/path2/path3', '/path4/path5/path6']}

option {filter => }

Option {filter => 2} affiche tous les éléments, y compris les attributs.

Option {filter => 3} supprime les attributs (c'est à dire il supprime toutes les lignes qui sont $rdr->type eq '@'). En revanche, le contenu des attributs sont retourné dans le hashage $rdr->att_hash.

Option {filter => 4} crée une ligne individuel pour chaque balise de début, de fin, pour chaque attribut, pour chaque commentaire et pour chaque processing-instruction. Ce fonctionnement permet, en effet, de générer un format PYX.

La syntaxe est {filter => 2|3|4}, le défaut est {filter => 2}

option {strip => }

Option {strip => 1} supprime les caractères blancs au début et à la fin d'un texte ou d'un commentaire. (les caractères blancs d'un attribut ne sont jamais supprimé). L'option {strip => 0} laisse le texte ou commentaire intacte.

La syntaxe est {strip => 0|1}, le défaut est {strip => 1}

Méthodes

Un objet du type XML::Reader a des méthodes suivantes:

iterate

La méthode iterate lit un élément XML. Elle retourne 1, si la lecture a été un succès, ou undef à la fin du fichier XML.

path

La méthode path retourne le chemin complet de la ligne en cours, les attributs sont réprésentés avec des caractères '@'.

value

La méthode value retourne la valeur de la ligne en cours (c'est à dire le texte ou l'attribut).

Conseil: en cas de {filter => 2 ou 3} avec une déclaration-XML (c'est à dire $rdr->is_decl == 1), il vaut mieux ne pas prendre compte de la valeur (elle sera vide, de toute façon). Un bout de programme:

print $rdr->value, "\n" unless $rdr->is_decl;

Le programme ci-dessus ne s'applique *pas* à {filter => 4}, dans ce cas un simple "print $rdr->value;" est suffisant:

print $rdr->value, "\n";
comment

La méthode comment retourne le commentaire d'un fichier XML. Il est conseillé de tester $rdr->is_comment avant d'accéder à la méthode comment.

type

La méthode type retourne 'T' quand il y a du texte dans le XML, et '@' quand il y a un attribut.

Si l'option {filter => 4} est active, les possibilités sont: 'T' pour du texte, '@' poir un attribut, 'S' pour une balise de début, 'E' pour une balise de fin, '#' pour un commentaire, 'D' pour une déclaration-XML, '?' pour une processing-instruction.

tag

La méthode tag retourne le nom de la balise en cours.

attr

La méthode attr retourne le nom de l'attribut en cours (elle retourne une chaîne de caractères vide si l'élément en cours n'est pas un attribut)

level

La méthode level retourne le niveau de cascadage (un nombre > 0)

prefix

La méthode prefix retourne le préfixe du chemin (c'est la partie du chemin qui a été supprimé dans l'option {using => ...}). Elle retourne une chaîne de caractères vide au cas où l'option {using => ...} n'a pas été specifiée.

att_hash

La méthode att_hash retourne une référence à l'hachage des attributs de la balise de début en cours (au cas où l'élément en cours n'est pas une balise de début, un hachage vide est retourné)

dec_hash

La méthode dec_hash retourne une référence à l'hachage des attributs de la XML-declaration en cours (au cas où l'élément en cours n'est pas une XML-declaration, un hachage vide est retourné)

proc_tgt

La méthode proc_tgt retourne la partie cible (c'est à dire la première partie) de la processing-instruction (au cas où l'élément en cours n'est pas une processing-instruction, une chaîne de caractères vide est retourné)

proc_data

La méthode proc_data retourne la partie donnée (c'est à dire la deuxième partie) de la processing-instruction (au cas où l'élément en cours n'est pas une processing-instruction, une chaîne de caractères vide est retourné)

pyx

La méthode pyx retourne la chaîne de caractères format "PYX" de l'élément XML en cours.

La chaîne de caractères format "PYX" est une chaîne de caractères avec un premier caractère spécifique. Ce premier caractère de chaque ligne "PYX" détermine le type: si le premier caractère est un '(', alors ça signifie une balise de début. Si le premier caractère est un ')', alors ça signifie une balise de fin. Si le premier caractère est un 'A', alors ça signifie un attribut. Si le premier caractère est un '-', alors ça signifie un texte. Si le premier caractère est un '?', alors ça signifie une processing-instruction. (voir http://www.xml.com/pub/a/2000/03/15/feature/index.html pour une introduction à PYX)

La méthode pyx n'est utile que pour l'option {filter => 4}, sinon, pour un {filter => } différent de 4, on retourne undef.

is_start

La méthode is_start retourne 1, si l'élément en cours est une balise de début, sinon 0 est retourné.

is_end

La méthode is_end retourne 1, si l'élément en cours est une balise de fin, sinon 0 est retourné.

is_decl

La méthode is_decl retourne 1, si l'élément en cours est une XML-declaration, sinon 0 est retourné.

is_proc

La méthode is_proc retourne 1, si l'élément en cours est une processing-instruction, sinon 0 est retourné.

is_comment

La méthode is_comment retourne 1, si l'élément en cours est un commentaire, sinon 0 est retourné.

is_text

La méthode is_text retourne 1, si l'élément en cours est un texte, sinon 0 est retourné.

is_attr

La méthode is_attr retourne 1, si l'élément en cours est un attribut, sinon 0 est retourné.

is_value

La méthode is_attr retourne 1, si l'élément en cours est un texte ou un attribut, sinon 0 est retourné. Cette méthode est plutôt utile pour l'option {filter => 4}, où on peut tester l'utilité de la méthode value.

OPTION USING

L'option {using => ...} permet de sélectionner un sous-arbre du XML.

Voici comment ça fonctionne en détail...

L'option {using => ['/chemin1/chemin2/chemin3', '/chemin4/chemin5/chemin6']} d'abord elimine toutes les lignes où le chemin ne commence pas avec '/chemin1/chemin2/chemin3' ou '/chemin4/chemin5/chemin6'.

Les lignes restantes (ceux qui n'ont pas été eliminé) ont un chemin plus court. En fait le préfixe '/chemin1/chemin2/chemin3' (ou '/chemin4/chemin5/chemin6') a été supprimé. En revanche, ce préfixe supprimé apparaît dans la méthode prefix.

On dit que '/chemin1/chemin2/chemin3' (ou '/chemin4/chemin5/chemin6') sont "absolu" et "complèt". Le mot "absolu" signifie que chaque chemin commence forcement par un caractère '/', et le mot "complèt" signifie que la dernière partie 'chemin3' (ou 'chemin6') sera suivi implicitement par un caractère '/'.

Un exemple avec l'option 'using'

Le programme suivant prend un fichier XML et le parse avec XML::Reader, y compris l'option 'using' pour cibler des éléments spécifiques:

use XML::Reader;

my $line2 = q{
<data>
  <order>
    <database>
      <customer name="aaa" />
      <customer name="bbb" />
      <customer name="ccc" />
      <customer name="ddd" />
    </database>
  </order>
  <dummy value="ttt">test</dummy>
  <supplier>hhh</supplier>
  <supplier>iii</supplier>
  <supplier>jjj</supplier>
</data>
};

my $rdr = XML::Reader->newhd(\$line2,
  {using => ['/data/order/database/customer', '/data/supplier']});

my $i = 0;
while ($rdr->iterate) { $i++;
    printf "%3d. prf=%-29s, pat=%-7s, val=%-3s, tag=%-6s, t=%-1s, lvl=%2d\n",
      $i, $rdr->prefix, $rdr->path, $rdr->value, $rdr->tag, $rdr->type, $rdr->level;
}

Voici le résultat de ce programme:

 1. prf=/data/order/database/customer, pat=/@name , val=aaa, tag=@name , t=@, lvl= 1
 2. prf=/data/order/database/customer, pat=/      , val=   , tag=      , t=T, lvl= 0
 3. prf=/data/order/database/customer, pat=/@name , val=bbb, tag=@name , t=@, lvl= 1
 4. prf=/data/order/database/customer, pat=/      , val=   , tag=      , t=T, lvl= 0
 5. prf=/data/order/database/customer, pat=/@name , val=ccc, tag=@name , t=@, lvl= 1
 6. prf=/data/order/database/customer, pat=/      , val=   , tag=      , t=T, lvl= 0
 7. prf=/data/order/database/customer, pat=/@name , val=ddd, tag=@name , t=@, lvl= 1
 8. prf=/data/order/database/customer, pat=/      , val=   , tag=      , t=T, lvl= 0
 9. prf=/data/supplier               , pat=/      , val=hhh, tag=      , t=T, lvl= 0
10. prf=/data/supplier               , pat=/      , val=iii, tag=      , t=T, lvl= 0
11. prf=/data/supplier               , pat=/      , val=jjj, tag=      , t=T, lvl= 0

Un example sans option 'using'

Le programme suivant prend un fichier XML et le parse avec XML::Reader, mais sans option 'using':

use XML::Reader;

my $rdr = XML::Reader->newhd(\$line2);
my $i = 0;
while ($rdr->iterate) { $i++;
    printf "%3d. prf=%-1s, pat=%-37s, val=%-6s, tag=%-11s, t=%-1s, lvl=%2d\n",
     $i, $rdr->prefix, $rdr->path, $rdr->value, $rdr->tag, $rdr->type, $rdr->level;
}

Comme on peut constater dans le résultat suivant, il y a beaucoup plus de lignes, le préfixe est vide et le chemin est beaucoup plus longue par rapport au programme précédent:

 1. prf= , pat=/data                                , val=      , tag=data       , t=T, lvl= 1
 2. prf= , pat=/data/order                          , val=      , tag=order      , t=T, lvl= 2
 3. prf= , pat=/data/order/database                 , val=      , tag=database   , t=T, lvl= 3
 4. prf= , pat=/data/order/database/customer/@name  , val=aaa   , tag=@name      , t=@, lvl= 5
 5. prf= , pat=/data/order/database/customer        , val=      , tag=customer   , t=T, lvl= 4
 6. prf= , pat=/data/order/database                 , val=      , tag=database   , t=T, lvl= 3
 7. prf= , pat=/data/order/database/customer/@name  , val=bbb   , tag=@name      , t=@, lvl= 5
 8. prf= , pat=/data/order/database/customer        , val=      , tag=customer   , t=T, lvl= 4
 9. prf= , pat=/data/order/database                 , val=      , tag=database   , t=T, lvl= 3
10. prf= , pat=/data/order/database/customer/@name  , val=ccc   , tag=@name      , t=@, lvl= 5
11. prf= , pat=/data/order/database/customer        , val=      , tag=customer   , t=T, lvl= 4
12. prf= , pat=/data/order/database                 , val=      , tag=database   , t=T, lvl= 3
13. prf= , pat=/data/order/database/customer/@name  , val=ddd   , tag=@name      , t=@, lvl= 5
14. prf= , pat=/data/order/database/customer        , val=      , tag=customer   , t=T, lvl= 4
15. prf= , pat=/data/order/database                 , val=      , tag=database   , t=T, lvl= 3
16. prf= , pat=/data/order                          , val=      , tag=order      , t=T, lvl= 2
17. prf= , pat=/data                                , val=      , tag=data       , t=T, lvl= 1
18. prf= , pat=/data/dummy/@value                   , val=ttt   , tag=@value     , t=@, lvl= 3
19. prf= , pat=/data/dummy                          , val=test  , tag=dummy      , t=T, lvl= 2
20. prf= , pat=/data                                , val=      , tag=data       , t=T, lvl= 1
21. prf= , pat=/data/supplier                       , val=hhh   , tag=supplier   , t=T, lvl= 2
22. prf= , pat=/data                                , val=      , tag=data       , t=T, lvl= 1
23. prf= , pat=/data/supplier                       , val=iii   , tag=supplier   , t=T, lvl= 2
24. prf= , pat=/data                                , val=      , tag=data       , t=T, lvl= 1
25. prf= , pat=/data/supplier                       , val=jjj   , tag=supplier   , t=T, lvl= 2
26. prf= , pat=/data                                , val=      , tag=data       , t=T, lvl= 1

OPTION PARSE_CT

L'option {parse_ct => 1} permet de parser les commentaires (normalement, les commentaires ne sont pas pris en compte par XML::Reader, le défaut est {parse_ct => 0}.

Voici un exemple où les commentaires ne sont pas pris en compte par défaut:

use XML::Reader;

my $text = q{<?xml version="1.0"?><dummy>xyz <!-- remark --> stu <?ab cde?> test</dummy>};

my $rdr = XML::Reader->newhd(\$text) or die "Error: $!";

while ($rdr->iterate) {
    if ($rdr->is_decl)    { my %h = %{$rdr->dec_hash};
                            print "Found decl     ",  join('', map{" $_='$h{$_}'"} sort keys %h), "\n"; }
    if ($rdr->is_proc)    { print "Found proc      ", "t=", $rdr->proc_tgt, ", d=", $rdr->proc_data, "\n"; }
    if ($rdr->is_comment) { print "Found comment   ", $rdr->comment, "\n"; }
    print "Text '", $rdr->value, "'\n" unless $rdr->is_decl;
}

Voici le résultat:

Text 'xyz stu test'

Ensuite, les mêmes données XML et le même algorithme, sauf l'option {parse_ct => 1}, qui est maintenant active:

use XML::Reader;

my $text = q{<?xml version="1.0"?><dummy>xyz <!-- remark --> stu <?ab cde?> test</dummy>};

my $rdr = XML::Reader->newhd(\$text, {parse_ct => 1}) or die "Error: $!";

while ($rdr->iterate) {
    if ($rdr->is_decl)    { my %h = %{$rdr->dec_hash};
                            print "Found decl     ",  join('', map{" $_='$h{$_}'"} sort keys %h), "\n"; }
    if ($rdr->is_proc)    { print "Found proc      ", "t=", $rdr->proc_tgt, ", d=", $rdr->proc_data, "\n"; }
    if ($rdr->is_comment) { print "Found comment   ", $rdr->comment, "\n"; }
    print "Text '", $rdr->value, "'\n" unless $rdr->is_decl;
}

Voici le résultat:

Text 'xyz'
Found comment   remark
Text 'stu test'

OPTION PARSE_PI

L'option {parse_pi => 1} permet de parser les processing-instructions et les XML-Declarations (normalement, ni les processing-instructions, ni les XML-Declarations ne sont pris en compte par XML::Reader, le défaut est {parse_pi => 0}.

Comme exemple, on prend exactement les mêmes données XML et le même algorithme du paragraphe précédent, sauf l'option {parse_pi => 1}, qui est maintenant active (avec l'option {parse_ct => 1}):

use XML::Reader;

my $text = q{<?xml version="1.0"?><dummy>xyz <!-- remark --> stu <?ab cde?> test</dummy>};

my $rdr = XML::Reader->newhd(\$text, {parse_ct => 1, parse_pi => 1}) or die "Error: $!";

while ($rdr->iterate) {
    if ($rdr->is_decl)    { my %h = %{$rdr->dec_hash};
                            print "Found decl     ",  join('', map{" $_='$h{$_}'"} sort keys %h), "\n"; }
    if ($rdr->is_proc)    { print "Found proc      ", "t=", $rdr->proc_tgt, ", d=", $rdr->proc_data, "\n"; }
    if ($rdr->is_comment) { print "Found comment   ", $rdr->comment, "\n"; }
    print "Text '", $rdr->value, "'\n" unless $rdr->is_decl;
}

Notez le "unless $rdr->is_decl" dans le programme ci-dessus. C'est pour éviter le texte vide après la XML-déclaration.

Voici le résultat:

Found decl      version='1.0'
Text 'xyz'
Found comment   remark
Text 'stu'
Found proc      t=ab, d=cde
Text 'test'

OPTION FILTER

L'option {filter => } permet de sélectionner des différents modes d'opératoires pour le traitement du XML.

Option {filter => 2}

Avec l'option {filter => 2}, XML::Reader génère une ligne pour chaque morceau de texte. Si la balise précédente est une balise de début, alors la métode is_start retourne 1. Si la balise suivante est une balise de fin, alors la métode is_end retourne 1. Si la balise précédente est une balise de commentaire, alors la méthode is_comment retourne 1. Si la balise précédente est une balise de XML-declaration, alors la méthode is_decl retourne 1. Si la balise précédente est une balise de processing-instruction, alors la méthode is_decl retourne 1.

De plus, les attributs sont représentés par des lignes supplémentaires avec la syntaxe '/@...'.

Option {filter => 2} est le défaut.

Voici un exemple...

use XML::Reader;

my $text = q{<root><test param="v"><a><b>e<data id="z">g</data>f</b></a></test>x <!-- remark --> yz</root>};

my $rdr = XML::Reader->newhd(\$text) or die "Error: $!";
while ($rdr->iterate) {
    printf "Path: %-24s, Value: %s\n", $rdr->path, $rdr->value;
}

Le programme (avec l'option {filter => 2} implicitement par défaut) génère le résultat suivant:

Path: /root                   , Value:
Path: /root/test/@param       , Value: v
Path: /root/test              , Value:
Path: /root/test/a            , Value:
Path: /root/test/a/b          , Value: e
Path: /root/test/a/b/data/@id , Value: z
Path: /root/test/a/b/data     , Value: g
Path: /root/test/a/b          , Value: f
Path: /root/test/a            , Value:
Path: /root/test              , Value:
Path: /root                   , Value: x yz

L'option (implicite) {filter => 2} permet également de reconstruire la structure du XML avec l'assistance des méthodes is_start and is_end. Notez que dans le résultat ci-dessus, la première ligne ("Path: /root, Value:") est vide, mais elle est importante pour la structure du XML.

Prenons-nous le même exemple {filter => 2} avec un algorithme pour reconstruire la structure originale du XML:

use XML::Reader;

my $text = q{<root><test param="v"><a><b>e<data id="z">g</data>f</b></a></test>x <!-- remark --> yz</root>};

my $rdr = XML::Reader->newhd(\$text) or die "Error: $!";

my %at;

while ($rdr->iterate) {
    my $indentation = '  ' x ($rdr->level - 1);

    if ($rdr->type eq '@')  { $at{$rdr->attr} = $rdr->value; }

    if ($rdr->is_start) {
        print $indentation, '<', $rdr->tag, join('', map{" $_='$at{$_}'"} sort keys %at), '>', "\n";
    }

    unless ($rdr->type eq '@') { %at = (); }

    if ($rdr->type eq 'T' and $rdr->value ne '') {
        print $indentation, '  ', $rdr->value, "\n";
    }

    if ($rdr->is_end) {
        print $indentation, '</', $rdr->tag, '>', "\n";
    }
}

...voici le résultat:

<root>
  <test param='v'>
    <a>
      <b>
        e
        <data id='z'>
          g
        </data>
        f
      </b>
    </a>
  </test>
  x yz
</root>

...ce qui donne preuve que la structure originale du XML n'est pas perdu.

Option {filter => 3}

Pour la plupart, l'option {filter => 3} fonctionne comme l'option {filter => 2}.

Mais il y a une différence: avec l'option {filter => 3}, les attributs sont supprimées et à la place, les attributs sont présentés dans un hashage "$rdr->att_hash()" pour chaque balise de début.

Ainsi, dans l'algorithme précédent, on peut supprimer la variable globale "%at" et la remplacer par %{$rdr->att_hash}:

use XML::Reader;

my $text = q{<root><test param="v"><a><b>e<data id="z">g</data>f</b></a></test>x <!-- remark --> yz</root>};

my $rdr = XML::Reader->newhd(\$text, {filter => 3}) or die "Error: $!";

while ($rdr->iterate) {
    my $indentation = '  ' x ($rdr->level - 1);

    if ($rdr->is_start) {
        print $indentation, '<', $rdr->tag,
          join('', map{" $_='".$rdr->att_hash->{$_}."'"} sort keys %{$rdr->att_hash}),
          '>', "\n";
    }

    if ($rdr->type eq 'T' and $rdr->value ne '') {
        print $indentation, '  ', $rdr->value, "\n";
    }

    if ($rdr->is_end) {
        print $indentation, '</', $rdr->tag, '>', "\n";
    }
}

...le résultat de {filter => 3} est identique au résultat de {filter => 2}:

<root>
  <test param='v'>
    <a>
      <b>
        e
        <data id='z'>
          g
        </data>
        f
      </b>
    </a>
  </test>
  x yz
</root>

Option {filter => 4}

Même si ce n'est pas la raison principale de XML::Reader, l'option {filter => 4} permet de générer des lignes individuelles pour chaque balise de début, de fin, commentaires, processing-instruction et XML-Declaration. Le but est de générer une chaîne de caractères du modèle "PYX" pour l'analyse par la suite.

Voici un exemple:

use XML::Reader;

my $text = q{<?xml version="1.0" encoding="iso-8859-1"?>
  <delta>
    <dim alter="511">
      <gamma />
      <beta>
        car <?tt dat?>
      </beta>
    </dim>
    dskjfh <!-- remark --> uuu
  </delta>};

my $rdr = XML::Reader->newhd(\$text, {filter => 4, parse_pi => 1}) or die "Error: $!";

while ($rdr->iterate) {
    printf "Type = %1s, pyx = %s\n", $rdr->type, $rdr->pyx;
}

et voici le résultat:

Type = D, pyx = ?xml version='1.0' encoding='iso-8859-1'
Type = S, pyx = (delta
Type = S, pyx = (dim
Type = @, pyx = Aalter 511
Type = S, pyx = (gamma
Type = E, pyx = )gamma
Type = S, pyx = (beta
Type = T, pyx = -car
Type = ?, pyx = ?tt dat
Type = E, pyx = )beta
Type = E, pyx = )dim
Type = T, pyx = -dskjfh uuu
Type = E, pyx = )delta

Il faut dire que les commentaires, qui sont générés avec l'option {parse_ct => 1}, ne font pas partie du standard "PYX". En fait, les commentaires sont générés avec un caractère '#' qui n'existe pas dans le standard. Voici un exemple:

use XML::Reader;

my $text = q{
  <delta>
    <!-- remark -->
  </delta>};

my $rdr = XML::Reader->newhd(\$text, {filter => 4, parse_ct => 1}) or die "Error: $!";

while ($rdr->iterate) {
    printf "Type = %1s, pyx = %s\n", $rdr->type, $rdr->pyx;
}

Voici le résultat:

Type = S, pyx = (delta
Type = #, pyx = #remark
Type = E, pyx = )delta

Avec l'option {filter => 4}, les méthodes habituelles restent accessibles: value, attr, path, is_start, is_end, is_decl, is_proc, is_comment, is_attr, is_text, is_value, comment, proc_tgt, proc_data, dec_hash or att_hash. Voici un exemple:

use XML::Reader;

my $text = q{<?xml version="1.0"?>
  <parent abc="def"> <?pt hmf?>
    dskjfh <!-- remark -->
    <child>ghi</child>
  </parent>};

my $rdr = XML::Reader->newhd(\$text, {filter => 4, parse_pi => 1, parse_ct => 1}) or die "Error: $!";

while ($rdr->iterate) {
    printf "Path %-15s v=%s ", $rdr->path, $rdr->is_value;

    if    ($rdr->is_start)   { print "Found start tag ", $rdr->tag, "\n"; }
    elsif ($rdr->is_end)     { print "Found end tag   ", $rdr->tag, "\n"; }
    elsif ($rdr->is_decl)    { my %h = %{$rdr->dec_hash};
                               print "Found decl     ",  join('', map{" $_='$h{$_}'"} sort keys %h), "\n"; }
    elsif ($rdr->is_proc)    { print "Found proc      ", "t=",    $rdr->proc_tgt, ", d=", $rdr->proc_data, "\n"; }
    elsif ($rdr->is_comment) { print "Found comment   ", $rdr->comment, "\n"; }
    elsif ($rdr->is_attr)    { print "Found attribute ", $rdr->attr, "='", $rdr->value, "'\n"; }
    elsif ($rdr->is_text)    { print "Found text      ", $rdr->value, "\n"; }
}

Voici le résultat:

Path /               v=0 Found decl      version='1.0'
Path /parent         v=0 Found start tag parent
Path /parent/@abc    v=1 Found attribute abc='def'
Path /parent         v=0 Found proc      t=pt, d=hmf
Path /parent         v=1 Found text      dskjfh
Path /parent         v=0 Found comment   remark
Path /parent/child   v=0 Found start tag child
Path /parent/child   v=1 Found text      ghi
Path /parent/child   v=0 Found end tag   child
Path /parent         v=0 Found end tag   parent

Notez que "v=1" (c'est à dire $rdr->is_value == 1) pour tous les textes et pour tous les attributs.

EXEMPLES

Examinons-nous le XML suivant, où nous souhaitons extraire les valeurs dans la balise <item> (c'est la première partie 'start...', et non pas la partie 'end...' qui nous intéresse), ensuite les attributs "p1" et "p3". La balise <item> doit être dans le chemin '/start/param/data (et non pas dans le chemin /start/param/dataz).

my $text = q{
  <start>
    <param>
      <data>
        <item p1="a" p2="b" p3="c">start1 <inner p1="p">i1</inner> end1</item>
        <item p1="d" p2="e" p3="f">start2 <inner p1="q">i2</inner> end2</item>
        <item p1="g" p2="h" p3="i">start3 <inner p1="r">i3</inner> end3</item>
      </data>
      <dataz>
        <item p1="j" p2="k" p3="l">start9 <inner p1="s">i9</inner> end9</item>
      </dataz>
      <data>
        <item p1="m" p2="n" p3="o">start4 <inner p1="t">i4</inner> end4</item>
      </data>
    </param>
  </start>};

Nous expectons exactement 4 lignes de sortie dans le résultat (c'est à dire la ligne 'dataz' / 'start9' ne fait pas partie du résultat):

item = 'start1', p1 = 'a', p3 = 'c'
item = 'start2', p1 = 'd', p3 = 'f'
item = 'start3', p1 = 'g', p3 = 'i'
item = 'start4', p1 = 'm', p3 = 'o'

Parser l'exemple XML avec l'option {filter => 2}

Ci-dessous un programme pour parser le XML avec l'option {filter => 2}. (Notez que le préfixe '/start/param/data/item' est renseigné dans l'option {using =>} de la fonction newhd). En plus, nous avons besoins de 2 variables scalaires '$p1' et '$p3' pour enregistrer les paramètres '/@p1' et '/@p3' et les transférer dans la partie '$rdr->is_start' du programme, où on peut les afficher.

my $rdr = XML::Reader->newhd(\$text,
  {filter => 2, using => '/start/param/data/item'}) or die "Error: $!";

my ($p1, $p3);

while ($rdr->iterate) {
    if    ($rdr->path eq '/@p1') { $p1 = $rdr->value; }
    elsif ($rdr->path eq '/@p3') { $p3 = $rdr->value; }
    elsif ($rdr->path eq '/' and $rdr->is_start) {
        printf "item = '%s', p1 = '%s', p3 = '%s'\n",
          $rdr->value, $p1, $p3;
    }
    unless ($rdr->is_attr) { $p1 = undef; $p3 = undef; }
}

Parser l'exemple XML avec l'option {filter => 3}

Avec l'option {filter => 3}, nous pouvons annuler les deux variables '$p1' et '$p3'. Le programme devient assez simple:

my $rdr = XML::Reader->newhd(\$text,
  {filter => 3, using => '/start/param/data/item'}) or die "Error: $!";

while ($rdr->iterate) {
    if ($rdr->path eq '/' and $rdr->is_start) {
        printf "item = '%s', p1 = '%s', p3 = '%s'\n",
          $rdr->value, $rdr->att_hash->{p1}, $rdr->att_hash->{p3};
    }
}

Parser l'exemple XML avec l'option {filter => 4}

Avec l'option {filter => 4}, par contre, le programme devient plus compliqué: Comme déjà montré dans l'exemple {filter => 2}, nous avons besoin de deux variables scalaires ('$p1' et '$p3') pour enregistrer les paramètres '/@p1' et '/@p3' et les transférer à l'endoit où on peut les afficher. En plus, nous avons besoin de compter les valeurs de texte (voir variable '$count' ci-dessous), afin d'identifier la première partie du texte 'start...' (ce que nous voulons afficher) et supprimer la deuxième partie du texte 'end...' (ce que nous ne voulons pas afficher).

my $rdr = XML::Reader->newhd(\$text,
  {filter => 4, using => '/start/param/data/item'}) or die "Error: $!";

my ($count, $p1, $p3);

while ($rdr->iterate) {
    if    ($rdr->path eq '/@p1') { $p1 = $rdr->value; }
    elsif ($rdr->path eq '/@p3') { $p3 = $rdr->value; }
    elsif ($rdr->path eq '/') {
        if    ($rdr->is_start) { $count = 0; $p1 = undef; $p3 = undef; }
        elsif ($rdr->is_text) {
            $count++;
            if ($count == 1) {
                printf "item = '%s', p1 = '%s', p3 = '%s'\n",
                  $rdr->value, $p1, $p3;
            }
        }
    }
}

FONCTIONS

Fonction slurp_xml

La fonction slurp_xml lit un fichier XML et aspire son contenu dans une référence à une liste. Voici un exemple où nous souhaitons aspirer le nom, la rue et la ville de tous les clients dans le chemin '/data/order/database/customer'. Nous souhaitons aussi d'aspirer le 'supplier' dans le chemin '/data/supplier'.

use XML::Reader qw(slurp_xml);

my $line2 = q{
<data>
  <supplier>ggg</supplier>
  <supplier>hhh</supplier>
  <order>
    <database>
      <customer name="smith" id="652">
        <street>high street</street>
        <city>boston</city>
      </customer>
      <customer name="jones" id="184">
        <street>maple street</street>
        <city>new york</city>
      </customer>
      <customer name="stewart" id="520">
        <street>ring road</street>
        <city>dallas</city>
      </customer>
    </database>
  </order>
  <dummy value="ttt">test</dummy>
  <supplier>iii</supplier>
  <supplier>jjj</supplier>
</data>
};

my $aref = slurp_xml(\$line2,
  { root => '/data/order/database/customer', branch => ['/@name', '/street', '/city'] },
  { root => '/data/supplier',                branch => ['/']                          },
);

for (@{$aref->[0]}) {
    printf "Cust: Name = %-7s Street = %-12s City = %s\n", $_->[0], $_->[1], $_->[2];
}

print "\n";

for (@{$aref->[1]}) {
    printf "Supp: Name = %s\n", $_->[0];
}

Le premier paramètre de slurp_xml est ou le nom du fichier (ou une une référence à un scalaire, ou une référence à un fichier ouvert) du XML qu'on veut aspirer. Dans notre cas nous avons une référence à un scalaire \$line2. Le paramètre suivant est la racine de l'arbre qu'on veut aspirer (dans notre cas c'est '/data/order/database/customer') et nous donnons une liste des éléments que nous souhaitons sélectionner, relative à la racine. Dans notre cas c'est ['/@name', '/street', '/city']. Le paramètre suivant est la deuxième racine (définition root/branch), dans ce cas c'est root => '/data/supplier' avec branch => ['/'].

Voici le résultat:

Cust: Name = smith   Street = high street  City = boston
Cust: Name = jones   Street = maple street City = new york
Cust: Name = stewart Street = ring road    City = dallas

Supp: Name = ggg
Supp: Name = hhh
Supp: Name = iii
Supp: Name = jjj

Le fonctionnement de slurp_xml est similaire à XML::Simple, c'est à dire il lit toutes les données dans un seul coup dans une structure en mémoire. En revanche, la différence est que slurp_xml permet de spécifier les données qu'on veut avant de faire l'aspiration, ce qui résulte dans une structure en mémoire souvent plus petite et moins compliquée.

AUTEUR

Klaus Eichner, Mars 2009

COPYRIGHT ET LICENSE

Voici le texte original en Anglais:

Copyright (C) 2009 by Klaus Eichner.

All rights reserved. This program is free software; you can redistribute it and/or modify it under the terms of the artistic license, see http://www.opensource.org/licenses/artistic-license-1.0.php

MODULES ASSOCIES

Si vous souhaitez écrire du XML, je propose d'utiliser une autre module "XML::Writer". Ce module se présente avec une interface simple pour écrire un fichier XML. Si vous ne mélangez pas le texte et les balises (ce qu'on appelle en Anglais "non-mixed content XML"), je propose de mettre les options DATA_MODE=>1 et DATA_INDENT=>2, ainsi votre résultat sera proprement formaté selon les règles XML.

REFERENCES

XML::TokeParser, XML::Simple, XML::Parser, XML::Parser::Expat, XML::TiePYX, XML::Writer.