Mein letzter Artikel beschäftigte sich mit XML::Simple, einem (wie der Name schon sagt) simplen und einfach zu verstehendem Modul für Perl um XML-Strukturen zu parsen. Heute wollen wir uns mit einem weiteren Modul beschäftigen: XML::Mini::Document.
Kurzinformation:
3 von 5 Sternen Rating auf cpan
Letzte Änderung: 05 Feb. 2008
XML::Mini::Document ist ein Modul, welches auf Anhieb eher den Eindruck eines mächtigen XML-Parsers macht, da es nicht nur über die Option verfügt XML-Strukturen in ein Hash zu parsen, bzw. daraus zu generieren. Es verfügt auch über eine API, mit der man die XML-Strukturen selbst beeinflussen kann. Die Dokumentation und die Beispiele sind einfach und verständlich gehalten, sodaß man sich schnell mit dem Modul zurechtfindet.
Zunächst verwenden wir das selbe test-xml, welches wir auch schon für den Test von XML::Simple verwendet haben:
<?xml version="1.0" encoding="iso-8859-1"?>
<test debug="0" attr1="1" attr2="2" another="<>">
<info attr1="perl" attr2="xml module" />
<info attr1="perl" attr2="xml module" />
<info attr1="perl" attr2="xml module" />
<info attr1="perl" attr2="xml module"><deepinfo>last text here</deepinfo></info>
</test>
Und wie wollen die selben Operationen im XML ausführen, wie wir das mit XML::Simple davor ebenfalls getan haben. Wenn wir die “toHash()” Methode verwenden, so sieht der Quellcode nur leicht anders aus; das Testprogramm sieht dann wie folgt aus:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
| #!/usr/bin/perl
# Simple XML module test unit
###
# We are strict, cauz we are Elitecoderz!
use strict;
use XML::Mini::Document;
use XML::Mini;
################
# Get / Check Parameter (here we get the xml file we wanna deal with)
if ($#ARGV+1 != 1) {
print "Error: Wrong number of parameters.\n";
exit(1);
}
my $input = $ARGV[0];
chomp($input);
################
# Read Inputfile / check content / validate XML structure
# Some modules are able to read directly from a file; for easy going, we use this method here.
# Direct, dirty, but simple reading of a file
open(FILE, "<$input") || die "Error: File not readable.\n";
my @lines = <FILE>;
close(FILE);
# Put the lines into one string for this parser
my $XMLString = join(' ',@lines);
####################################################################
# XML::Mini
my $xmlDoc = XML::Mini::Document->new();
eval {
$xmlDoc->parse($XMLString);
};
if ($@) {
print "Error: XML parsing error: $@\n";
exit(1);
}
my $xmlHash = $xmlDoc->toHash();
# Adding an attribute and a text to the first node
$xmlHash->{'test'}->{'info'}->[0]->{'addon'} = 'valid text';
$xmlHash->{'test'}->{'info'}->[0]->{'content'} = "Here is an valid xml text\nusing linebreaks";
# Adding an attribute and an unescaped text to the second node
$xmlHash->{'test'}->{'info'}->[1]->{'addon'} = 'invalid unescaped text';
$xmlHash->{'test'}->{'info'}->[1]->{'content'} = "Here is an valid xml text\nusing linebreaks\nand unescaped characters like < and >\ndo you see the and?";
# Adding a third node unescaped text to the second node
$xmlHash->{'test'}->{'info'}->[2]->{'addon'} = 'valid unescaped text in cdata';
$xmlHash->{'test'}->{'info'}->[2]->{'content'} = "<![CDATA[Here is an valid xml text\nusing linebreaks\nand unescaped characters like < and >\ndo you see the and?]]>";
# my $newDoc = XML::Mini::Document->new();
$xmlDoc->fromHash($xmlHash);
open(DATEI, ">output_XMLMini") || die "Datei nicht gefunden";
print DATEI $xmlDoc->toString();
close(DATEI); |
Das resultierende XML sieht wie folgt aus:
<test>
<info>
<attr2>
xml module
</attr2>
<addon>
valid text
</addon>
<attr1>
perl
</attr1>
<content>
Here is an valid xml text using linebreaks
</content>
</info>
<info>
<attr2>
xml module
</attr2>
<addon>
invalid unescaped text
</addon>
<attr1>
perl
</attr1>
<content>
Here is an valid xml text using linebreaks and unescaped characters like < and > do you see the and?
</content>
</info>
<info>
<attr2>
xml module
</attr2>
<addon>
valid unescaped text in cdata
</addon>
<attr1>
perl
</attr1>
<content>
<![CDATA[Here is an valid xml text using linebreaks and unescaped characters like < and > do you see the and?]]>
</content>
</info>
<info>
<attr2>
xml module
</attr2>
<attr1>
perl
</attr1>
<deepinfo>
last text here
</deepinfo>
</info>
<attr2>
2
</attr2>
<attr1>
1
</attr1>
<another>
&lt;&gt;
</another>
<debug>
0
</debug>
</test>
<xml>
<version>
1.0
</version>
<encoding>
iso-8859-1
</encoding>
</xml>
*AUTSCH* – was zum Teufel ist mit unserem XML passiert?! Nach dem ersten Schock kann man erkennen, daß alle Attribute in XML-Knoten verwandelt wurden. Das ist zwar ein gültiger XML-Syntax, und alle Informationen sind nach wie vor enthalten, aber das Problem ist, daß jemand anderes ein erwartetes Attribut vermutlich nicht als XML-Knoten suchen wird. Das Gute dieses Mal ist jedoch, daß der “deepinfo”-Knoten in der Hirachie richtig steht.
Ein anderes Problem ist der Umgang mit CDATAs. XML::Mini::Document escaped den CDATA-Knoten und ist offensichtlich nicht in der Lage ein CDATA korrekt zu schreiben (jedenfalls habe ich keine Möglichkeit gefunden).
Das übelste Problem jedoch ist die Behandlung des “XML-Informations”-Knotens; Er wird als XML-Knoten behandelt, aufgebrochen und am Ende der Datei eingefügt!
Erstes Ergebnis:
Die Methoden “fromHash()” and “toHash()” in XML::Mini::Document sind technischer Müll, die bestenfalls in XMLs ohne Attribute und CDATAs zum Einsatz kommen können.
Aber es gibt einen weiteren Weg XML::Mini::Document zu nutzen
Im Gegensatz zu XML::Simple ist bei XML::Mini::Document das “Hash-Verfahren” nicht die einzige Option wie mit XML-Strukturen umgegangen werden kann. Es kann mit der Struktur über eine Toolbox auch direkt gearbeitet werden. Hierfür schreiben wir unser Testprogramm etwas um:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
| #!/usr/bin/perl
# Simple XML module test unit
###
# We are strict, cauz we are Elitecoderz!
use strict;
use XML::Mini::Document;
use XML::Mini;
################
# Get / Check Parameter (here we get the xml file we wanna deal with)
if ($#ARGV+1 != 1) {
print "Error: Wrong number of parameters.\n";
exit(1);
}
my $input = $ARGV[0];
chomp($input);
################
# Read Inputfile / check content / validate XML structure
# Some modules are able to read directly from a file; for easy going, we use this method here.
# Direct, dirty, but simple reading of a file
open(FILE, "<$input") || die "Error: File not readable.\n";
my @lines = <FILE>;
close(FILE);
# Put the lines into one string for this parser
my $XMLString = join(' ',@lines);
####################################################################
# XML::Mini
my $xmlDoc = XML::Mini::Document->new();
eval {
$xmlDoc->parse($XMLString);
};
if ($@) {
print "Error: XML parsing error: $@\n";
exit(1);
}
my $xmlRoot = $xmlDoc->getRoot();
my $firstnode = $xmlDoc->getElementByPath('test/info');
$firstnode->attribute('addon', "Here is an valid xml text\nusing linebreaks");
my $secondnode = $xmlDoc->getElementByPath('test/info',1,2);
$secondnode->attribute('addon','invalid unescaped text');
$secondnode->text("Here is an valid xml text\nusing linebreaks\nand unescaped characters like < and >\ndo you see the and?");
my $testnode = $xmlDoc->getElementByPath('test');
my $newchild = $testnode->createChild("info");
$newchild->attribute('addon', "unescaped text for a CDATA");
$newchild->cdata("Here is an valid xml text\nusing linebreaks\nand unescaped characters like < and >\ndo you see the and?");
open(DATEI, ">output_XMLMini3") || die "Datei nicht gefunden";
print DATEI $xmlDoc->toString();
close(DATEI); |
Das Ergebnis sieht hier wie folgt aus:
<?xml version="1.0" encoding="iso-8859-1"?>
<test another="<>" attr1="1" attr2="2" debug="0">
<info addon="Here is an valid xml text
using linebreaks" attr1="perl" attr2="xml module" />
<info addon="invalid unescaped text" attr1="perl" attr2="xml module">
Here is an valid xml text using linebreaks and unescaped characters like < and > do you see the and?
</info>
<info attr1="perl" attr2="xml module" />
<info attr1="perl" attr2="xml
module">
<deepinfo>
last text here
</deepinfo>
</info>
<info addon="unescaped text for a CDATA">
<![CDATA[ Here is an valid xml text
using linebreaks
and unescaped characters like < and >
do you see the and? ]]>
</info>
</test>
*Wow* – nach dem schlechten Bild zum Start macht dieses Ergbnis einen sehr guten Eindruck! Die Struktur ist komplett, ist inhaltlich korrekt und wurde korrekt escaped. In kurzen Worten: Das XML ist perfekt wie erwartet! Es scheint sich also zu lohnen einen weiteren Blick auf XML::Mini::Document zu werfen. Und genau das werden wir im nächsten Kapitel tun.
Was passiert beim parsing von aufwendigeren XML-Strukturen, die CDATAs und escaped Texte enthalten?
Um das testen zu können, passen wir unser Testprogramm nocheinmal etwas an
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
| #!/usr/bin/perl
# Simple XML module test unit
###
# We are strict, cauz we are Elitecoderz!
use strict;
use XML::Mini::Document;
use XML::Mini;
################
# Get / Check Parameter (here we get the xml file we wanna deal with)
if ($#ARGV+1 != 1) {
print "Error: Wrong number of parameters.\n";
exit(1);
}
my $input = $ARGV[0];
chomp($input);
################
# Read Inputfile / check content / validate XML structure
# Some modules are able to read directly from a file; for easy going, we use this method here.
# Direct, dirty, but simple reading of a file
open(FILE, "<$input") || die "Error: File not readable.\n";
my @lines = <FILE>;
close(FILE);
# Put the lines into one string for this parser
my $XMLString = join(' ',@lines);
####################################################################
# XML::Mini
my $xmlDoc = XML::Mini::Document->new();
eval {
$xmlDoc->parse($XMLString);
};
if ($@) {
print "Error: XML parsing error: $@\n";
exit(1);
}
my $xmlRoot = $xmlDoc->getRoot();
my $firstnode = $xmlDoc->getElementByPath('test/info');
$firstnode->attribute('addon', "Here is an valid xml text\nusing linebreaks");
my $cdatanode = $xmlDoc->getElementByPath('test/info',1,5);
print "\n\n--\n".$cdatanode->getValue."\n--\n\n";
my $textnode = $xmlDoc->getElementByPath('test/info',1,2);
print "\n\n--\n".$textnode->getValue."\n--\n\n";
open(DATEI, ">output_XMLMini4") || die "Datei nicht gefunden";
print DATEI $xmlDoc->toString();
close(DATEI); |
und nehmen als Test-XML das eben selbst generierte XML-File her. Die Ausgabe die wir erhalten sieht wie folgt aus:
--
Here is an valid xml text
using linebreaks
and unescaped characters like < and > do you see the and?
--
--
Here is an valid xml text
using linebreaks
and unescaped characters like < and >
do you see the and?
--
Das Ergebnis des CDATAs sieht perfekt aus, der aus dem zweiten Knoten ausgelesene Text wurde jedoch leider nicht unescaped. Es ist zwar kein große Sache dies selbst zu tun, aber meiner Meinung nach sollte das XML::Mini::Document für mich tun.
Ergebnis:
Die Methode “fromHash()” sollte man tunlichst nicht verwenden, es sei denn man ist darauf aus seine XML-Struktur gründlich zu zerstören. Die “toHash()” Methode kann eventuell ganz nützlich sein um kurz und schnell Informationen aus einem XML auszulesen.
Die Toolbox für das direkte bearbeiten von XML-Strukturen hingegen ist sehr praktisch. Besonders gut gefällt mir die Idee und der Syntax um in bestimmten Pfaden zu navigieren (durch anhängen des “node-count”). Geniale Idee – hervorragende Umsetzung!
XML::Mini::Document kann mit allen XML-Strukturen umgehen und escaped Inhalte -wo benötigt- automatisch. Das Unescaping von Texten muß dagegen leider händisch vorgenommen werden – in meinen Augen ein klarer Bug der mich davon abhält dieses Modul produktiv zu nutzen.
XML::Mini::Document ist klar die Beste Wahl, wenn man mit etwas komplexeren XML-Strukturen umgehen muß. Da das unescaping “händisch” selbst gemacht werden muß empfehle ich jedoch dieses Modul nicht bei großen, komplexen und “empfindlichen” XMLs einzusetzen. Ansonsten ist XML::Mini::Document ein einfacher und schneller Weg mit XMLs zu arbeiten.
Im nächsten Artikel werde ich mich mit dem Thema “Kompfortabler Umgang mit XML::LibXML” beschäftigen.
Alle oben durchgeführten Tests hat die XML::LibXML ohne Schwächen oder Fehler bestanden. Dafür gibt es einige andere Probleme, die einem das Leben unter XML::LibXML schwer machen.
Tweet This Post