NAME
XML::Reader - Lire du XML avec des informations du chemin, basé sur un parseur d'extraction.
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 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 un 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é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}, où on peut tester l'utilité de la méthodevalue
.
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 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 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;
}
}
}
}
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::Parser, XML::Parser::Expat, XML::TiePYX, XML::Writer.