NAME
XML::Reader_fr - 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 qw(XML::Parser);
my $text = q{<init>n <?test pi?> t<page node="400">m <!-- remark --> r</page></init>};
my $rdr = XML::Reader->new(\$text);
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:
On peut toujours rajouter un eval {...} autours d'un appel XML::Reader->new(...) afin de vérifier que l'appel a réussi:
my $rdr = eval{ XML::Reader->new('test.xml') }
or warn "Can't XML::Reader->new() because $@";
if ($rdr) {
# ... do something with $rdr ...
}
else {
# ... do some error handling ...
}
UTILISATION
Normalement, on n'utilise pas XML::Reader tout seul, on utilise plutôt XML::Reader::RS (qui utilise XML::Parser) ou on utilise XML::Reader::PP (qui utilise XML::Parsepp).
Par contre, si on veut utiliser XML::Reader directement, voici la procédure pour choisir entre XML::Parser et XML::Parsepp:
XML::Reader utilise XML::Parser comme sous-module. Cette configuration marche très bien, sauf dans le cas où il n'y a pas de compilateur C disponible pour installer XML::Parser. Dans ce cas, XML::Parsepp peut remplacer XML::Parser. Voici un exemple:
use XML::Reader qw(XML::Parser);
my $text = q{<init>n <?test pi?> t<page node="400">m <!-- remark --> r</page></init>};
my $rdr = XML::Reader->new(\$text);
while ($rdr->iterate) {
printf "Path: %-19s, Value: %s\n", $rdr->path, $rdr->value;
}
La seule chose est que XML::Reader dépend des deux modules XML::Parser et XML::Parsepp. En conséquence, si vous ne pouvez pas installer XML::Parser, vous pouvez quand même installer XML::Reader, mais vous ne lancez pas les tests.
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/XML::Parsepp (quelques fonctionalités basiques ont été copié de XML::TokeParser). XML::Parser, XML::Parsepp 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, XML::Parsepp 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, mode => 'pyx'}).
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, mode => 'pyx'}, 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 qw(XML::Parser);
my $rdr = XML::Reader->new(\$line1);
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 objet
Pour créer un objet, on utilise la syntaxe suivante:
my $rdr = XML::Reader->new($data,
{strip => 1, filter => 2, using => ['/path1', '/path2']});
L'élément $data est obligatoire, il est le nom d'un fichier XML, ou une URL qui commence par 'http://...', 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->new('input.xml');
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->new(\'<data>abc</data>');
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->new($fh);
Voici un exemple pour créer un objet XML::Reader avec \*STDIN:
my $rdr = XML::Reader->new(\*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 => } et {mode => }
-
Option {filter => 2} ou {mode => 'attr-bef-start'} affiche tous les éléments, y compris les attributs.
Option {filter => 3} ou {mode => 'attr-in-hash'} 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} ou {mode => 'pyx'} 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.
Option {filter => 5} ou {mode => 'branches'} selectionne uniquement les données pour les racines ("root"). Les éléments pour chaque "root" sont accumulé dans une référence à un array (comme specifié dans la partie "branch") et quand le "branch" est complet, les données sont retourné. Cette procédure se trouve au milieu entre l'option "using" (qui retourne les éléments un par un) et la fonction "slurp_xml" (qui accumule les données dans un "branch", et tous les "branches" sont accumulés dans une seule structure en mémoire).
La syntaxe est {filter => 2|3|4|5, mode => 'attr-bef-start'|'attr-in-hash'|'pyx'|'branches'}, le défaut est {filter => 2, mode => 'attr-bef-start'}
- 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}
- option {dupatt => }
-
L'option {dupatt => '*'} permet de lire plusieurs attributs avec le même nom. Les différentes valeurs sont donc concatenées par '*'.
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éthodecomment
. - 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, mode => 'pyx'}, où on peut tester l'utilité de la méthodevalue
. - rx
-
C'est l'indexe de la branche en cours (cette fonction est valide uniquement avec {filter => 5}).
- rvalue
-
C'est une référence à une valeur scalaire (ou à une liste) de la branche en cours (cette fonction est valide uniquement avec {filter => 5}). rvalue est plus rapide, mais moins simple à utiliser que la fonction value (avec rvalue vous devez faire votre déréférencement vous même).
- rstem
-
Cette fonction est identique à la fonction "path"
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 '/'.
Exemple avec 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 qw(XML::Parser);
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->new(\$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
Exemple sans using
Le programme suivant prend un fichier XML et le parse avec XML::Reader, mais sans option 'using':
use XML::Reader qw(XML::Parser);
my $rdr = XML::Reader->new(\$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 qw(XML::Parser);
my $text = q{<?xml version="1.0"?><dummy>xyz <!-- remark --> stu <?ab cde?> test</dummy>};
my $rdr = XML::Reader->new(\$text);
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 qw(XML::Parser);
my $text = q{<?xml version="1.0"?><dummy>xyz <!-- remark --> stu <?ab cde?> test</dummy>};
my $rdr = XML::Reader->new(\$text, {parse_ct => 1});
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 qw(XML::Parser);
my $text = q{<?xml version="1.0"?><dummy>xyz <!-- remark --> stu <?ab cde?> test</dummy>};
my $rdr = XML::Reader->new(\$text, {parse_ct => 1, parse_pi => 1});
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 / MODE
L'option {filter => } ou {mode => } permet de sélectionner des différents modes d'opératoires pour le traitement du XML.
Option filter 2 mode attr-bef-start
Avec l'option {filter => 2} ou {mode => 'attr-bef-start'}, 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, mode => 'attr-bef-start'} est le défaut.
Voici un exemple...
use XML::Reader qw(XML::Parser);
my $text = q{<root><test param='<>v"'><a><b>"e"<data id="<>z'">'g'&<></data>}.
q{f</b></a></test>x <!-- remark --> yz</root>};
my $rdr = XML::Reader->new(\$text);
# les quatres alternatives ci-dessous sont equivalent:
# ----------------------------------------------------
# XML::Reader->new(\$text);
# XML::Reader->new(\$text, {filter => 2 });
# XML::Reader->new(\$text, {filter => 2, mode => 'attr-bef-start'});
# XML::Reader->new(\$text, { mode => 'attr-bef-start'});
while ($rdr->iterate) {
printf "Path: %-24s, Value: %s\n", $rdr->path, $rdr->value;
}
Le programme (avec l'option {filter => 2, mode => 'attr-bef-start'} 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, mode => 'attr-bef-start'} 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, mode => 'attr-bef-start'} avec un algorithme pour reconstruire la structure originale du XML. En plus, on exige de rajouter des caractères "** **" pour chaque texte (mais pas les balises et pas non plus les attributs):
use XML::Reader qw(XML::Parser);
my $text = q{<root><test param='<>v"'><a><b>"e"<data id="<>z'">'g'&<></data>}.
q{f</b></a></test>x <!-- remark --> yz</root>};
my $rdr = XML::Reader->new(\$text);
# les quatres alternatives ci-dessous sont equivalent:
# ----------------------------------------------------
# XML::Reader->new(\$text);
# XML::Reader->new(\$text, {filter => 2 });
# XML::Reader->new(\$text, {filter => 2, mode => 'attr-bef-start'});
# XML::Reader->new(\$text, { mode => 'attr-bef-start'});
my %at;
while ($rdr->iterate) {
my $indentation = ' ' x ($rdr->level - 1);
if ($rdr->type eq '@') {
$at{$rdr->attr} = $rdr->value;
for ($at{$rdr->attr}) {
s{&}'&'xmsg;
s{'}'''xmsg;
s{<}'<'xmsg;
s{>}'>'xmsg;
}
}
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 '') {
my $v = $rdr->value;
for ($v) {
s{&}'&'xmsg;
s{<}'<'xmsg;
s{>}'>'xmsg;
}
print $indentation, " ** $v **\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 mode attr-in-hash
Pour la plupart, l'option {filter => 3, mode => 'attr-in-hash'} fonctionne comme l'option {filter => 2, mode => 'attr-bef-start'}.
Mais il y a une différence: avec l'option {filter => 3, mode => 'attr-in-hash'}, 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
}:
Voici un nouveau algorithme avec {filter => 3, mode => 'attr-in-hash'}, on ne s'occupe pas des attributs (c'est-à-dire, on ne verifie pas $rdr->type eq '@') et (comme d´jà mentionné ci-dessus) la variable %at est remplacée par %{$rdr->att_hash} :
use XML::Reader qw(XML::Parser);
my $text = q{<root><test param='<>v"'><a><b>"e"<data id="<>z'">'g'&<></data>}.
q{f</b></a></test>x <!-- remark --> yz</root>};
my $rdr = XML::Reader->new(\$text, {filter => 3});
# les trois alternatives ci-dessous sont equivalent:
# --------------------------------------------------
# XML::Reader->new(\$text, {filter => 3 });
# XML::Reader->new(\$text, {filter => 3, mode => 'attr-in-hash'});
# XML::Reader->new(\$text, { mode => 'attr-in-hash'});
while ($rdr->iterate) {
my $indentation = ' ' x ($rdr->level - 1);
if ($rdr->is_start) {
my %h = %{$rdr->att_hash};
for (values %h) {
s{&}'&'xmsg;
s{'}'''xmsg;
s{<}'<'xmsg;
s{>}'>'xmsg;
}
print $indentation, '<', $rdr->tag,
join('', map{" $_='$h{$_}'"} sort keys %h),
'>', "\n";
}
if ($rdr->type eq 'T' and $rdr->value ne '') {
my $v = $rdr->value;
for ($v) {
s{&}'&'xmsg;
s{<}'<'xmsg;
s{>}'>'xmsg;
}
print $indentation, " ** $v **\n";
}
if ($rdr->is_end) {
print $indentation, '</', $rdr->tag, '>', "\n";
}
}
...le résultat de {filter => 3, mode => 'attr-in-hash'} est identique au résultat de {filter => 2, mode => 'attr-bef-start'}:
<root>
<test param='<>v"'>
<a>
<b>
** "e" **
<data id='<>z''>
** 'g'&<> **
</data>
** f **
</b>
</a>
</test>
** x yz **
</root>
Finalement on pourrait (et on devrait) effectuer l'écriture XML par un autre module. Je propose d'utiliser le module XML::MinWriter. Voici un programme qui utilise XML::MinWriter pour écrire du XML:
use XML::Reader qw(XML::Parser);
use XML::MinWriter;
my $text = q{<root><test param='<>v"'><a><b>"e"<data id="<>z'">'g'&<></data>}.
q{f</b></a></test>x <!-- remark --> yz</root>};
my $rdr = XML::Reader->new(\$text, {filter => 3});
my $wrt = XML::MinWriter->new(OUTPUT => \*STDOUT, NEWLINES => 1);
while ($rdr->iterate) {
if ($rdr->is_start) { $wrt->startTag($rdr->tag, %{$rdr->att_hash}); }
if ($rdr->type eq 'T' and $rdr->value ne '') { $wrt->characters('** '.$rdr->value.' **'); }
if ($rdr->is_end) { $wrt->endTag($rdr->tag); }
}
$wrt->end();
Voici le résultat de XML::MinWriter:
<root
><test param="<>v""
><a
><b
>** "e" **<data id="<>z'"
>** 'g'&<> **</data
>** f **</b
></a
></test
>** x yz **</root
>
A la première lecture, le format est bizarre, mais c'est du XML tout à fait correct.
Option filter 4 mode pyx
Même si ce n'est pas la raison principale de XML::Reader, l'option {filter => 4, mode => 'pyx'} 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 qw(XML::Parser);
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->new(\$text, {filter => 4, parse_pi => 1});
# les trois alternatives ci-dessous sont equivalent:
# --------------------------------------------------
# XML::Reader->new(\$text, {filter => 4 , parse_pi => 1});
# XML::Reader->new(\$text, {filter => 4, mode => 'pyx', parse_pi => 1});
# XML::Reader->new(\$text, { mode => 'pyx', parse_pi => 1});
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 qw(XML::Parser);
my $text = q{
<delta>
<!-- remark -->
</delta>};
my $rdr = XML::Reader->new(\$text, {filter => 4, parse_ct => 1});
# les trois alternatives ci-dessous sont equivalent:
# --------------------------------------------------
# XML::Reader->new(\$text, {filter => 4, parse_ct => 1});
# XML::Reader->new(\$text, {filter => 4, mode => 'pyx', parse_ct => 1});
# XML::Reader->new(\$text, { mode => 'pyx', parse_ct => 1});
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, mode => 'pyx'}, 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 qw(XML::Parser);
my $text = q{<?xml version="1.0"?>
<parent abc="def"> <?pt hmf?>
dskjfh <!-- remark -->
<child>ghi</child>
</parent>};
my $rdr = XML::Reader->new(\$text, {filter => 4, parse_pi => 1, parse_ct => 1});
# les trois alternatives ci-dessous sont equivalent:
# --------------------------------------------------
# XML::Reader->new(\$text, {filter => 4, parse_ct => 1, parse_pi => 1});
# XML::Reader->new(\$text, {filter => 4, mode => 'pyx', parse_ct => 1, parse_pi => 1});
# XML::Reader->new(\$text, { mode => 'pyx', parse_ct => 1, parse_pi => 1});
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.
Option filter 5 mode branches
Avec option {filter => 5, mode => 'branches'}, on spécifie une (ou plusieurs) racines ("roots"), chaque racine a une collection de "branches". En résultat on obtient un enregistrement pour chaque "root" dans l'aborescence XML.
Une racine peut commencer par un slash simple (par ex. {root => '/tag1/tag2'}), dans ce cas la racine est absolue. Ou la racine peut commencer par un double-slash (par ex. {root => '//tag1/tag2'}), dans ce cas la racine est relative. Si la racine ne commence pas par un slash, dans ce cas la racine est également relative.
Chaque enregistrement contient les éléments qui ont été spécifié dans les "branches". (il y a un cas particulier: quand la branche est '*', dans ce cas l'aborescence du sous-arbre est construit)
Pour obtenir des valeurs qui sont spécifiées dans les branches, on peut utiliser la fonction $rdr->rvalue.
Voici un exemple:
use XML::Reader qw(XML::Parser);
my $line2 = q{
<data>
<supplier>ggg</supplier>
<customer name="o'rob" id="444">
<street>pod alley</street>
<city>no city</city>
</customer>
<customer1 name="troy" id="333">
<street>one way</street>
<city>any city</city>
</customer1>
<tcustomer name="nbc" id="777">
<street>away</street>
<city>acity</city>
</tcustomer>
<supplier>hhh</supplier>
<zzz>
<customer name='"sue"' id="111">
<street>baker street</street>
<city>sidney</city>
</customer>
</zzz>
<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> "'&<A>'" </city>
</customer>
</database>
</order>
<dummy value="ttt">test</dummy>
<supplier>iii</supplier>
<supplier>jjj</supplier>
<p>
<p>b1</p>
<p>b2</p>
</p>
<p>
b3
</p>
</data>
};
On veut lire les éléments "name", "street" et "city" de tous les clients dans le chemin relatif ('customer') et on veut également lire les fournisseurs dans le chemin absolu ('/data/supplier'). Puis, on veut lire les clients dans le chemin relatif ('//customer') en format sous-arborescence XML (voir {branch => '*'}). Finalement on veut lire le chemin relatif ('p') en format sous-arborescence XML.
Les données pour la première racine ('customer') sont identifiable par $rdr->rx == 0, les données pour la deuxième racine ('/data/supplier') sont identifiable par $rdr->rx == 1, les données pour la troisième racine ('//customer') sont identifiable par $rdr->rx == 2, les données pour la quatrième racine ('p') sont identifiable par $rdr->rx == 3 et les données pour la cinquième racine ('//customer') sont identifiable par $rdr->rx == 4.
Dans l'exemple ci-dessous, le paramètre {branch => '*'} indique que le résultat sera du XML et le paramètre {branch => '+'} indique que le résultat sera une liste des instructions PYX.
Dans le programme ci-dessus on va utiliser la fonction $rdr->rvalue pour lire les donnees:
my $rdr = XML::Reader->new(\$line2, {filter => 5},
{ root => 'customer', branch => ['@name', 'street', 'city'] },
{ root => '/data/supplier', branch => ['/'] },
{ root => '//customer', branch => '*' },
{ root => 'p', branch => '*' },
{ root => '//customer', branch => '+' },
);
# les trois alternatives ci-dessous sont equivalent:
# --------------------------------------------------
# XML::Reader->new(\$line2, {filter => 5, });
# XML::Reader->new(\$line2, {filter => 5, mode => 'branches'});
# XML::Reader->new(\$line2, { mode => 'branches'});
my $root0 = '';
my $root1 = '';
my $root2 = '';
my $root3 = '';
my $root4 = '';
my $path0 = '';
while ($rdr->iterate) {
if ($rdr->rx == 0) {
$path0 .= " ".$rdr->path."\n";
for ($rdr->rvalue) {
$root0 .= sprintf " Cust: Name = %-7s Street = %-12s City = %s\n", $_->[0], $_->[1], $_->[2];
}
}
elsif ($rdr->rx == 1) {
for ($rdr->rvalue) {
$root1 .= " Supp: Name = $_->[0]\n";
}
}
elsif ($rdr->rx == 2) {
for ($rdr->rvalue) {
$root2 .= " Xml: $_\n";
}
}
elsif ($rdr->rx == 3) {
for ($rdr->rvalue) {
$root3 .= " P: $_\n";
}
}
elsif ($rdr->rx == 4) {
for ($rdr->rvalue) {
local $" = "', '";
$root4 .= " Pyx: '@$_'\n";
}
}
}
print "root0:\n$root0\n";
print "path0:\n$path0\n";
print "root1:\n$root1\n";
print "root2:\n$root2\n";
print "root3:\n$root3\n";
print "root4:\n$root4\n";
Voici le résultat:
root0:
Cust: Name = o'rob Street = pod alley City = no city
Cust: Name = "sue" Street = baker street City = sidney
Cust: Name = <smith> Street = high street City = boston
Cust: Name = &jones Street = maple street City = new york
Cust: Name = stewart Street = ring road City = "'&<A>'"
path0:
/data/customer
/data/zzz/customer
/data/order/database/customer
/data/order/database/customer
/data/order/database/customer
root1:
Supp: Name = ggg
Supp: Name = hhh
Supp: Name = iii
Supp: Name = jjj
root2:
Xml: <customer id='444' name='o'rob'><street>pod alley</street><city>no city</city></customer>
Xml: <customer id='111' name='"sue"'><street>baker street</street><city>sidney</city></customer>
Xml: <customer id='652' name='<smith>'><street>high street</street><city>boston</city></customer>
Xml: <customer id='184' name='&jones'><street>maple street</street><city>new york</city></customer>
Xml: <customer id='520' name='stewart'><street>ring road</street><city>"'&<A>'"</city></customer>
root3:
P: <p><p>b1</p><p>b2</p></p>
P: <p>b3</p>
root4:
Pyx: '(customer', 'Aid 444', 'Aname o'rob', '(street', '-pod alley', ')street', '(city', '-no city', ')city', ')customer'
Pyx: '(customer', 'Aid 111', 'Aname "sue"', '(street', '-baker street', ')street', '(city', '-sidney', ')city', ')customer'
Pyx: '(customer', 'Aid 652', 'Aname <smith>', '(street', '-high street', ')street', '(city', '-boston', ')city', ')customer'
Pyx: '(customer', 'Aid 184', 'Aname &jones', '(street', '-maple street', ')street', '(city', '-new york', ')city', ')customer'
Pyx: '(customer', 'Aid 520', 'Aname stewart', '(street', '-ring road', ')street', '(city', '-"'&<A>'"', ')city', ')customer'
Nous pouvons également utiliser la fonction $rdr->value pour obtenir les mêmes données:
my $rdr = XML::Reader->new(\$line2, {filter => 5},
{ root => 'customer', branch => ['@name', 'street', 'city'] },
{ root => 'p', branch => '*' },
);
# les trois alternatives ci-dessous sont equivalent:
# --------------------------------------------------
# XML::Reader->new(\$line2, {filter => 5, });
# XML::Reader->new(\$line2, {filter => 5, mode => 'branches'});
# XML::Reader->new(\$line2, { mode => 'branches'});
my $out0 = '';
my $out1 = '';
while ($rdr->iterate) {
if ($rdr->rx == 0) {
my @rv = $rdr->value;
$out0 .= sprintf " Cust: Name = %-7s Street = %-12s City = %s\n", $rv[0], $rv[1], $rv[2];
}
elsif ($rdr->rx == 1) {
$out1 .= " P: ".$rdr->value."\n";
}
}
print "output0:\n$out0\n";
print "output1:\n$out1\n";
Voici le résultat:
output0:
Cust: Name = o'rob Street = pod alley City = no city
Cust: Name = "sue" Street = baker street City = sidney
Cust: Name = <smith> Street = high street City = boston
Cust: Name = &jones Street = maple street City = new york
Cust: Name = stewart Street = ring road City = "'&<A>'"
output1:
P: <p><p>b1</p><p>b2</p></p>
P: <p>b3</p>
Il faut dire ici que le résultat "root3" / "output1" { root => 'p', branch => '*' } contient toujours le sous-arbre XML le plus important. Ou dit autrement: la sortie "P: <p>b1</p>" et "P: <p>b2</p>" n'est pas possible, car une ligne plus importante qui contient les lignes "P: <p>b1</p>" et "P: <p>b2</p>" exsiste déjà: cette ligne est "P: <p><p>b1</p><p>b2</p></p>".
OPTION DUPATT
L'option dupatt permet de lire multiples attributs avec le même nom dans une seule valeur. Le principe fonctionne par la concatenation des différentes valeurs dans une seule variable, séparé par un caractère spécial. La liste des attributs est ensuite trié par order alphabétique.
L'option dupatt est valable uniquement si XML::Reader a été utilisé avec l'option XML::Parsepp.
Par exemple, le code suivant concatène les attribut doublons avec le caractère "|": (La chaîne de caractères ($str) {dupatt => $str} est limité à des caractères Ascii visibles, sauf les caractères alpha-numériques, ' ou ")
use XML::Reader qw(XML::Parser);
my $text = q{<data><item a2="abc" a1="def" a2="ghi"></item></data>}
my $rdr = XML::Reader->new(\$text, {dupatt => '|'});
while ($rdr->iterate) {
printf "Path: %-19s, Value: %s\n", $rdr->path, $rdr->value;
}
Voici le résultat du programme:
Path: /data , Value:
Path: /data/item/@a1 , Value: def
Path: /data/item/@a2 , Value: abc|ghi
Path: /data/item , Value:
Path: /data , Value:
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'
Exemple filter 2 mode attr-bef-start
Ci-dessous un programme pour parser le XML avec l'option {filter => 2, mode => 'attr-bef-start'}. (Notez que le préfixe '/start/param/data/item' est renseigné dans l'option {using =>} de la fonction new). 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->new(\$text,
{mode => 'attr-bef-start', using => '/start/param/data/item'});
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; }
}
Exemple filter 3 mode attr-in-hash
Avec l'option {filter => 3, mode => 'attr-in-hash'}, nous pouvons annuler les deux variables '$p1' et '$p3'. Le programme devient assez simple:
my $rdr = XML::Reader->new(\$text,
{mode => 'attr-in-hash', using => '/start/param/data/item'});
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};
}
}
Exemple filter 4 mode pyx
Avec l'option {filter => 4, mode => 'pyx'}, par contre, le programme devient plus compliqué: Comme déjà montré dans l'exemple {filter => 2, mode => 'attr-bef-start'}, 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->new(\$text,
{mode => 'pyx', using => '/start/param/data/item'});
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;
}
}
}
}
Exemple filter 5 mode branches
On peut combiner {filter => 5, mode => 'branches'} avec des expressions régulières pour parser les données XML:
my $rdr = XML::Reader->new(\$text, {mode => 'branches'},
{ root => '/start/param/data/item', branch => '*' });
while ($rdr->iterate) {
if ($rdr->value =~ m{\A <item
(?:\s+ p1='([^']*)')?
(?:\s+ p2='([^']*)')?
(?:\s+ p3='([^']*)')?
\s* > ([^<]*) <}xms) {
printf "item = '%s', p1 = '%s', p3 = '%s'\n", $4, $1, $3;
}
}
Exemple condition d'attribut
Ci-dessous un autre document XML ($text3):
my $text3 = q{
<data>
<database loc="alpha">
<item>
<customer name="smith" id="652">
<street>high street</street>
<city>rio</city>
</customer>
<customer name="jones" id="184">
<street>maple street</street>
<city>new york</city>
</customer>
<customer name="gates" id="520">
<street>ring road</street>
<city>dallas</city>
</customer>
<customer name="smith" id="800">
<street>which way</street>
<city>ny</city>
</customer>
</item>
</database>
<database loc="beta">
<item>
<customer name="smith" id="001">
<street>nowhere</street>
<city>st malo</city>
</customer>
<customer name="jones" id="002">
<street>all the way</street>
<city>leeds</city>
</customer>
<customer name="gates" id="003">
<street>bypass</street>
<city>rome</city>
</customer>
</item>
</database>
<database loc="alpha">
<item>
<customer name="peter" id="444">
<street>upton way</street>
<city>motown</city>
</customer>
<customer name="gates" id="959">
<street>don't leave me this way</street>
<city>cambridge</city>
</customer>
</item>
</database>
<database loc="alpha">
<item>
<customer name="smith" id="881">
<street>anyway</street>
<city>big apple</city>
</customer>
<customer name="thatcher" id="504">
<street>baker street</street>
<city>oxford</city>
</customer>
</item>
</database>
</data>
};
En utilisant des chemins (root => ... and branch => ...) on peut rajouter des conditions d'attribut ('/path1[@attr="val"]/...'), par ex.:
my $rdr = XML::Reader->new(\$text3, {mode => 'branches', sepchar => '|'}, {
root => '/data/database[@loc="alpha"]',
branch => [
'item/customer[@name="smith"]/city',
'item/customer[@name="gates"]/city',
]});
while ($rdr->iterate) {
my ($smith, $gates) = $rdr->value;
$smith = defined($smith) ? "'$smith'" : 'undef';
$gates = defined($gates) ? "'$gates'" : 'undef';
printf "smith = %-12s, gates = %s\n", $smith, $gates;
}
Voici le résultat:
smith = 'rio|ny' , gates = 'dallas'
smith = undef , gates = 'cambridge'
smith = 'big apple' , gates = undef
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(XML::Parser 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 "S: %s\n", $_;
}
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
S: <supplier>ggg</supplier>
S: <supplier>hhh</supplier>
S: <supplier>iii</supplier>
S: <supplier>jjj</supplier>
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.
On peut utiliser slurp_xml() avec des attributs doublons. Dans ce cas il faut faire deux choses: Premièrement il faut faire un "use XML::Reader" avec "qw(XML::Parsepp slurp_xml)". Deuxièmement il faut faire appel à slurp_xml avec l'option { dupatt => '|' }, comme dans l'exemple ci-dessous:
use XML::Reader qw(XML::Parser slurp_xml);
my $line3 = q{<data atr1='abc' atr2='def' atr1='ghi'></data>};
my $aref = slurp_xml(\$line3,
{ dupatt => '|' },
{ root => '/', branch => '*' });
print $aref->[0][0], "\n";
Voici le résultat:
<data atr1='abc|ghi' atr2='def'></data>
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 2.0, see http://www.opensource.org/licenses/artistic-license-2.0.php
MODULES ASSOCIES
Si vous souhaitez écrire du XML, je propose d'utiliser le module "XML::Writer" ou "XML::MinWriter". Chacun de ces deux modules 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::Parsepp, XML::Reader::RS, XML::Reader::PP, XML::Parser::Expat, XML::TiePYX, XML::Writer, XML::MinWriter.