Werbung einblenden Werbung ausblenden


Home / Tutorials / xml, xsl, xpath Handbuch / Perl Modul XML::LIBXML


Auslesen einer XML Datei mit dem Perl Modul XML::LIBXML
Auslesen des Dokumentes anhand der DOM Spezifikation
Zugriff auf eine XML Datei mit XPATH
Modifikation eines XML Dokumentes
Bestimmung eines Knotens mit XPATH und einfügen eines neuen Elementes
Validierung eines Dokumentes mit XML::LibXml anhand einer internen DTD
Validierung eines Dokumentes mit XML::LibXml anhand einer externen DTD
Unterschied is_valid und validate

Auslesen einer XML Datei mit dem Perl Modul XML::LIBXML

Auch das Modul XML::LIBXML setzt, wie die entsprechende PHP Extension auf die libxml2 Bibliothek von Matt Sergeant auf. Will man damit in Perl arbeiten, ist vor allem entscheidend, dass es noch nicht in der für ActiveState Module typischen ppm Version vorliegt. Man hat jetzt zwei Möglichkeiten. Entweder man installiert es mit makeinstall oder man lädt von der Seite http://theoryx5.uwinnipeg.ca/cgi-bin/ppmserver?urn:/PPMServer herunter. Die Befehle hierfür sehen, wenn man dier Version Activestate-5_6_1_630-MSWin32-x86 verwendet so aus.

C:\> ppm
ppm> set repository irgend_ein_Name http://theoryx5.uwinnipeg.ca/cgi-bin/ppmserver?urn:/PPMServer
ppm> set save
ppm> install XML::LibXML

Wie ersichtlich muss dafür natürlich die Activestate Distribution mit dem Perl Packet Manager bereits geladen sein. Hierfür wird die Distribution ActivePerl-5_6_1_630-MSWin32-x86.msi benötigt. Für die Activestate Distribution ActivePerl-5.8.0.804-MSWin32-x86.msi ist das Verfahren etwas anders, da der Perl Packet Manager wieder modifiziert wurde. Der alte Perl Packet Manager ist zwar noch vorhanden, wird aber mit ppm2 aufgerufen. Allerdings können bei Verwendung der Version 5.8, egal ob ppm oder ppm2 nur Module installiert werden, die mit der Version Perl-5.8 kompatibel sind. Bei Installation mit dem neuen Perl Packet Manager sieht die Syntax so aus.

C:\> ppm
ppm>add repository irgend_ein_Name http://theoryx5.uwinnipeg.ca/ppms/
ppm>install XML::LibXML

Die eigentlich C Bibliothek libxml2.dll muss separat runtergeladen werden, z.B. von hier. Entzippt man die Datei findet man im Ordner lib die Datei libxml2.dll. Diese wiederum ist in den Windows Ordner bzw. in Win/System32 zu kopieren. Alternativ könnte man auch den Pfad dahin in die autoexec.bat schreiben (nach dem Schema set Path=c:/irgend_ein_Name;%PATH%). Im übrigen stimmt das nur solange, wie sich nichts ändert. Wenn es sich ändert, wird irgendjemand irgendwo beschreiben wie es dann geht. XML::LibXML gilt, natürlich mit der zugrunde liegenden Bibliothek libxml2.dll als starker Konkurrent zu den anderen Modulen, die auf SAX oder Expat aufsetzen. Der Charme dieses Moduls besteht darin, dass sowohl XPATH als auch die DOM Spezifikation unterstützt wird. (By the way, auch die DOM Extension von PHP, die ebenfalls auf libxml2.dll aufsetzt, unterstützt XPATH). Im folgenden soll anhand eines einfachen Beispiels ein Dokument einmal mit der DOM Spezifikation und einmal mit XPATH ausgelesen werden. Wir legen das gleiche XML Dokument zugrunde, dass schon bei der Erklärung des Zugriffs von PHP auf ein XML Dokument verwendet wurde. Nochmal zur Erinnerung, es sah so aus.

<?xml version="1.0" encoding="ISO-8859-1"?>
<Projekte>
<Gruppe>
<Gruppenname stand="abgeschifft">Berliner Verwaltungsreform</Gruppenname>
<Kollegen>
<Mitarbeiter Firma="Preis Wasserhaus">Werner Lepinski</Mitarbeiter>
<Mitarbeiter>Erika Saufwech</Mitarbeiter>
<Mitarbeiter>Hans Geldfliech</Mitarbeiter>
</Kollegen>
<Ansprechpartner Telefon="030-435555" Ident="A_1">Otto Moltoimportante</Ansprechpartner>
<Adresse>
<Strasse Gegend="teures Pflaster">Kurfürstendamm 5</Strasse>
<Ort>13453 Berlin</Ort>
</Adresse>
<Budget>40 000000</Budget>
<Kommentar>Alles wird gut</Kommentar>
</Gruppe>
<Gruppe>
<Gruppenname>Hamburger Verwaltungschaos </Gruppenname>
<Kollegen>
<Mitarbeiter Firma="Artur der Kleine">Werner Nordflut</Mitarbeiter>
<Mitarbeiter>Marina Meimportauncarajo</Mitarbeiter>
<Mitarbeiter>Peter Wessnich</Mitarbeiter>
</Kollegen>
<Ansprechpartner Ident="A_2">Ludwig Noresponsable</Ansprechpartner>
<Adresse>
<Strasse>An der Waterkant 15</Strasse>
<Ort>45555 Hamburg</Ort>
</Adresse>
<Kostenvoranschlag>30 000000</Kostenvoranschlag>
</Gruppe>
<Team>
<Name>Controlling</Name>
<Ansprechpartner>Werner Kostfix</Ansprechpartner>
<Telefon>030-4544332</Telefon>
</Team>

Auslesen des Dokumentes anhand der DOM Spezifikation

Will man jetzt alle Namen der Mitarbeiter auslesen und dazu noch wissen, in welcher Firma sie arbeiten, dann sieht das mit der Dom Spezifikation so aus.

use XML::LibXML;

my $datei = 'teschto.xml';
my $mein_objekt = XML::LibXML->new();
my $Baumobjekt = $mein_objekt->parse_file($datei);
my $wurzel = $Baumobjekt->getDocumentElement;
my @knoten = $wurzel->getElementsByTagName('Mitarbeiter');

foreach my $einzelne_Knoten (@knoten)
{
$Firma_Mitarbeiter=$einzelne_Knoten->getAttribute('Firma');
$Name_des_Knotens=$einzelne_Knoten->getName();
$Inhalt_des_Knotens=$einzelne_Knoten->textContent;
print "$Name_des_Knotens ($Firma_Mitarbeiter) heisst $Inhalt_des_Knotens \n";
}

Man erhält dann, wenn man die XML Datei unter teschto.xml und dieses Skript im gleichen Ordner mit dem Namen irgendein_Name.pl abspeichert und dann den Perl Skript aus der Dos Box (Eingabeaufforderung) heraus aufruft folgendes Bild.

C:\sambar\cgi-bin>perl xmllib.pl
Mitarbeiter (Price Waterhouse) heisst Werner Lepinski
Mitarbeiter () heisst Erika Saufwech
Mitarbeiter () heisst Hans Geldfliech
Mitarbeiter (Arthur de Little) heisst Werner Nordflut
Mitarbeiter () heisst Marina Meimportauncarajo
Mitarbeiter () heisst Peter Wessnich

C:\sambar\cgi-bin>

Auch dieses Modul ist, wie unschwer zu erkennen, vollkommen objektorientiert, die Funktionen können nur über Objekte angesteuert werden. Zur objektorientierten Programmierung siehe Perl Handbuch. Mit XML::LibXML legen wir ein neues Objekt an. Mit getDocumentElement schnappen wir uns das Wurzelelement dieses Dokumentes und mit getElementsByTagName alle Knoten mit dem Namen Mitarbeiter, genauer gesagt generieren wir für jedes Element mit dem Namen Mitarbeiter ein Objekt. Mit getAttribute, getName und textContent liesen wir dann die verschiedenen Informationen zu den einzelnen Knoten aus.

Zugriff auf eine XML Datei mit XPATH

XPATH wurde schon ausführlich erklärt im Zusammenhang mit XSLT siehe Auslesen eines XML Dokumentes mit XSLT und Abfragen mit XPATH. Dasselbe wie mit der DOM Spezifikation lässt sich auch mit XPATH erreichen. Wir gehen vom gleichen XML Dokument aus.

use XML::LibXML;
my $datei = 'teschto.xml';
my $mein_objekt = XML::LibXML->new();
my $Baumobjekt = $mein_objekt->parse_file($datei);
@meine_Mitarbeiter=$Baumobjekt->findnodes("Projekte/Gruppe/Kollegen/child::*");

for($i=0;$i<6;$i++)
{
$Kollegen=$meine_Mitarbeiter[$i]->findvalue(".");
$mein_Attribut=$meine_Mitarbeiter[$i]->findvalue("attribute::Firma");
print "Mitarbeiter ($mein_Attribut) heisst $Kollegen \n";
}

Das führt zum exakt gleichen Ergebnis wie oben.

Modifikation eines XML Dokumentes

use XML::LibXML;
$datei = 'teschto.xml';
$mein_objekt = new XML::LibXML;
$mein_dokument_objekt = $mein_objekt->parse_file($datei);
$neuer_Mitarbeiter = $mein_dokument_objekt->createElement( "Mitarbeiter" );
$neuer_Mitarbeiter->appendText('Hans Donnerknall');
$neuer_Mitarbeiter->setAttribute('KBMC');
$wurzelknoten = $mein_dokument_objekt->documentElement();
@Gruppen=$wurzelknoten->getChildrenByTagName('Gruppe');
$die_zweite_Gruppe=$Gruppen[1];
@Kinder_dieser_Gruppe=$die_zweite_Gruppe->childNodes;
$Mitarbeiter=$Kinder_dieser_Gruppe[3];
print $Kinder_dieser_Gruppe[3]->localname;
$Mitarbeiter->appendChild($neuer_Mitarbeiter);
$fertisch= $mein_dokument_objekt->toString();
open(kirsche,">aprikose2.xml");
print kirsche $fertisch;
close(kirsche);

Der Ansatz ist identisch mit dem unter PHP Beschriebenen, siehe DomXML und PHP. Um den Skript laufen zu lassen, muss man die XML Datei unter dem Namen teschto.xml in den gleichen Ordner packen, in dem auch der Perl Skript abgespeichert wurde. Löst man ihn aus, erhält man in eben diesem Ordner eine Datei aprikose2.xml, die dann den neuen, zusätzlichen Knoten, also den Hans Donnerknall enthält. Hier nochmal der Skript im Detail.

1.) use XML::LibXML;
2.) $datei = 'teschto.xml';
3.) $mein_objekt = new XML::LibXML;
4.) $mein_dokument_objekt = $mein_objekt->parse_file($datei);
5.) $neuer_Mitarbeiter = $mein_dokument_objekt->createElement( "Mitarbeiter" );
6.) $neuer_Mitarbeiter->appendText('Hans Donnerknall');
7.) $neuer_Mitarbeiter->setAttribute('Firma','KBMC');
8.) $wurzelknoten = $mein_dokument_objekt->documentElement();
9.) @Gruppen=$wurzelknoten->getChildrenByTagName('Gruppe');
10.) $die_zweite_Gruppe=$Gruppen[1];
11.) @Kinder_dieser_Gruppe=$die_zweite_Gruppe->childNodes;
12.) $Mitarbeiter=$Kinder_dieser_Gruppe[3];
13.) print $Kinder_dieser_Gruppe[3]->localname;
14.) $Mitarbeiter->appendChild($neuer_Mitarbeiter);
15.) $fertisch= $mein_dokument_objekt->toString();
16.) open(kirsche,">aprikose2.xml");
17.) print kirsche $fertisch;
18.) close(kirsche);

In Zeile 1) teilen wir mit, dass wir vorhaben, dass XML::LibXML Modul zu verwenden. In Zeile 3) generieren wir ein neues XML::LibXML Objekt. In Zeile drei generieren wir ein Objekt, dass die XML Datei repräsentiert. An dieses Objekt hängen wir dann in 5) bis 7) einen neuen Knoten an und weisen diesem Werte zu. Unser Problem besteht jetzt darin, den Knoten an der richtigen Stelle einzufügen. Wir wollen ihn einfügen in der zweiten Gruppe innerhalb des Elementes Kollegen. Folglich brauchen wir ein Objekt, dass das Element Kollegen in der zweiten Gruppe repräsentiert. Um ein solches zu erstellen, hangeln wir uns in Zeile 9) bis 13) erstmal durch bis zur zweiten Gruppe. Das heisst, wir fischen uns in 9) erstmal alle Elemente mit dem Namen Gruppe raus, wählen davon die zweite ($Gruppen[1]). Von dieser zweiten Gruppe holen wir 11) alle Kindknoten. Von den Kindknoten wiederum ist der 4 das Element Kollegen. Folglich wird der Knoten Mitarbeiter der zweiten Gruppe durch das Objekt $Mitarbeiter repräsentiert. An dieses Objekt hängen wir dann den neuen Mitarbeiter. Da wir das ganze auf die Platte drucken wollen, müssen wir es in einen String umwandeln, was wir in 15) dann auch tun. Anschliessend 16) bis 18) drucken wir es. Im Detail gibt es Unterschiede zu PHP. Erstens funktionniert die Funktion getChildrenByTagName nur als Funktion eines Knotens, z.B. des Wurzelknotens und nicht wie bei PHP auch auf das Objekt, dass das gesamte XML Dokument repräsentiert. Zweitens werden die Textknoten mitgezählt, so dass der Knoten Mitarbeiter das vierte, und nicht wie bei PHP das zweite Element ist. Das Modul XML::LibXML hat eine gewaltige Menge an Funktionen, deren Namen aber sprechend ist. Eine Kurzbeschreibung der Funktionen findet sich in ../site/lib/xml/libxm (der Perl Distribution) in den verschiedenen pod Dateien (Document.pod, Element.pod,Node.pod etc.). Das hier dargestellte Problem hätte man auch mit XPATH lösen können, bzw. man hätte den Knoten, wo der neue Knoten eingehängt wird auch mit XPATH bestimmen können. Das sieht dann so aus.

Bestimmung eines Knotens mit XPATH und einfügen eines neuen Elementes

Das Problem oben hätte auch mit XPATH gelöst werden können, das wäre sogar einfacher gewesen und sieht so aus.

use XML::LibXML;

$datei = 'teschto.xml';
$mein_objekt = new XML::LibXML;
$mein_dokument_objekt = $mein_objekt->parse_file($datei);
$neuer_Mitarbeiter = $mein_dokument_objekt->createElement( "Mitarbeiter" );
$neuer_Mitarbeiter->appendText('Hans Donnerknall');
$neuer_Mitarbeiter->setAttribute('Firma','KBMC');
@Mitarbeiter=$mein_dokument_objekt->findnodes("/Projekte/Gruppe[2]/Kollegen");
print $Mitarbeiter[0]->localname;
$Mitarbeiter[0]->appendChild($neuer_Mitarbeiter);
$fertisch= $mein_dokument_objekt->toString();
open(kirsche,">aprikose3.xml");
print kirsche $fertisch;
close(kirsche);

Wir erinnern uns. Das zentrale Problem besteht darin, den Knoten nicht irgendwo, sondern an der richtigen Stelle einzufügen. Wir brauchen also eine Präsentation als Objekt des Knoten Mitarbeiters in der zweiten Gruppe. Dieser lässt sich mit dieser Zeile ermitteln.

@Mitarbeiter=$mein_dokument_objekt->findnodes("/Projekte/Gruppe[2]/Kollegen");

Diese XPATH Abfrage fischt uns sofort den den richtigen Knoten raus. Wir sind also durch einen Einzeiler genauso so weit, wie durch den komplizierten Mehrzeiler oben.

Validierung eines Dokumentes mit XML::LibXml anhand einer internen DTD

Um zu zeigen, wie man ein XML Dokument auf Gültigkeit prüft, braucht man erstmal ein XML Dokument, dass überhaupt eine DTD (bzw. ein Schema) hat. Wir gehen von diesem gültigen XML Dokument aus. Für Details siehe document type definition (DTD).

<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE Projekte
[
<!ELEMENT Projekte (Gruppe+)>
<!ELEMENT Gruppe (Gruppenname,Kollegen,Ansprechpartner,Adresse,(Budget|Kostenvoranschlag),Kommentar*)>
<!ELEMENT Gruppenname ANY>
<!ELEMENT Kollegen (Mitarbeiter+)>
<!ELEMENT Mitarbeiter (#PCDATA)>
<!ELEMENT Ansprechpartner (#PCDATA)>
<!ELEMENT Adresse (Strasse?,Ort?)>
<!ELEMENT Strasse (#PCDATA)>
<!ELEMENT Ort (#PCDATA)>
<!ELEMENT Budget (#PCDATA)>
<!ELEMENT Kostenvoranschlag (#PCDATA)>
<!ELEMENT Kommentar (#PCDATA)>
<!ATTLIST Mitarbeiter Firma CDATA "Arthur de Little">
<!ATTLIST Gruppenname stand CDATA "Wird bald abschiffen">
<!ATTLIST Ansprechpartner Telefon CDATA "030-453452344444" Ident ID #REQUIRED>
<!ATTLIST Strasse Gegend CDATA #IMPLIED>
<!ATTLIST Kommentar Sinn CDATA #FIXED "kurz und bündig">
<!ENTITY comment "Hauptsache die Kasse stimmt">
]>
<Projekte>
<Gruppe>
<Gruppenname stand="abgeschifft">Berliner Verwaltungsreform</Gruppenname>
<Kollegen>
<Mitarbeiter Firma="Price Waterhouse">Werner Lepinski</Mitarbeiter>
<Mitarbeiter>Erika Saufwech</Mitarbeiter>
<Mitarbeiter>Hans Geldfliech</Mitarbeiter>
</Kollegen>
<Ansprechpartner Telefon="030-435555" Ident="A_1">Otto Moltoimportante</Ansprechpartner>
<Adresse>
<Strasse Gegend="teures Pflaster">Kurfürstendamm 5</Strasse>
<Ort>13453 Berlin</Ort>
</Adresse>
<Budget>40 000000</Budget>
<Kommentar>&comment;</Kommentar>
</Gruppe>
<Gruppe>
<Gruppenname>Hamburger Verwaltungschaos </Gruppenname>
<Kollegen>
<Mitarbeiter Firma="Arthur de Little">Werner Nordflut</Mitarbeiter>
<Mitarbeiter>Marina Meimportauncarajo</Mitarbeiter>
<Mitarbeiter>Peter Wessnich</Mitarbeiter>
</Kollegen>
<Ansprechpartner Ident="A_2">Ludwig Noresponsable</Ansprechpartner>
<Adresse>
<Strasse>An der Waterkant 15</Strasse>
<Ort>45555 Hamburg</Ort>
</Adresse>
<Kostenvoranschlag>30 000000</Kostenvoranschlag>
</Gruppe>
</Projekte>

Wir können das Dokument dann mit diesem kleinen Skript auf Gültigkeit überprüfen.

use XML::LibXML;

$datei = 'teschto.xml';
$mein_objekt = new XML::LibXML;
$mein_dokument_objekt = $mein_objekt->parse_file($datei);

$wat_nu=$mein_dokument_objekt->is_valid ;
print $wat_nu;

Die Funktion is_valid hat den Wert 1, wenn das Dokument gültig ist und den Wert 0 wenn das Dokument ungültig ist. In unserem Fall hat sie den Wert eins, weil es ein gültiges Dokument ist.

Validierung eines Dokumentes mit XML::LibXml anhand einer externen DTD

Es gilt opus citatus. Um zu zeigen, wie man eine XML Datei auf Konformität mit einer externen DTD überprüft (validiert) braucht man erstmal eine DTD und eine XML Datei, die anhand dieser externen DTD überprüft werden soll. Im Beispiel legen wir die gleiche XML Datei wie oben, ohne interne DTD, zugrunde die wir anhand dieser externen DTD überprüfen wollen. Für Details siehe document type definition (DTD) . Es wird davon ausgegangen, was hier der Fall ist, dass die DTD vom XML Dokument aufgerufen wird, also etwas in dieser Art vorhanden ist.

<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE Projekte SYSTEM "Unternehmensberatung.dtd">

Die zweite Zeile ruft die externe DTD auf. Für Details siehe document type definition (DTD) . Die dazugehörige DTD speichern wir dann, am einfachsten im selben Ordner wo auch die XML Datei liegt, unter dem Namen Unternehmensberatung.dtd ab.

<?xml version="1.0" encoding="ISO-8859-1"?>

<!ELEMENT Projekte (Gruppe+)>
<!ELEMENT Gruppe (Gruppenname,Kollegen,Ansprechpartner,Adresse,(Budget|Kostenvoranschlag),Kommentar*)>
<!ELEMENT Gruppenname ANY>
<!ELEMENT Kollegen (Mitarbeiter+)>
<!ELEMENT Mitarbeiter (#PCDATA)>
<!ELEMENT Ansprechpartner (#PCDATA)>
<!ELEMENT Adresse (Strasse?,Ort?)>
<!ELEMENT Strasse (#PCDATA)>
<!ELEMENT Ort (#PCDATA)>
<!ELEMENT Budget (#PCDATA)>
<!ELEMENT Kostenvoranschlag (#PCDATA)>
<!ELEMENT Kommentar (#PCDATA)>
<!ATTLIST Mitarbeiter Firma CDATA "Arthur de Little">
<!ATTLIST Gruppenname stand CDATA "Wird bald abschiffen">
<!ATTLIST Ansprechpartner Telefon CDATA "030-453452344444" Ident ID #REQUIRED>
<!ATTLIST Strasse Gegend CDATA #IMPLIED>
<!ATTLIST Kommentar Sinn CDATA #FIXED "kurz und bündig">
<!ENTITY comment "Hauptsache die Kasse stimmt">

Um ein XML Dokument dann mit dieser externen DTD zu prüfen haben wir zwei Möglichkeiten.

use XML::LibXML;

# Eine Möglichkeit ....
$mein_dtd_objekt = XML::LibXML::Dtd->new("","Unternehmensberatung.dtd");
$mein_dokument_objekt = XML::LibXML->new->parse_file("teschto.xml");
print $mein_dokument_objekt->validate($mein_dtd_objekt);

print "\n";
# und noch eine Möglichkeit
$datei = 'teschto.xml';
$mein_objekt = new XML::LibXML;
$mein_dokument_objekt = $mein_objekt->parse_file($datei);
$wat_nu=$mein_dokument_objekt->is_valid ;
print $wat_nu;

Unterschied is_valid und validate

Der wesentliche und entscheidende Unterschied ist, dass validate genau anzeigt, wo der Fehler liegt, also nicht nur 0 oder 1. Der zweite Unterschied ist, dass sich mit validate die Gültigeit eines XML Dokumentes auch dann prüfen lässt, wenn dieses XML Dokument keine Referenz auf die DTD hat, also höchstens wohlgeformt, aber nicht gültig sein kann. Also wenn diese Zeilen

<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE Projekte SYSTEM "Unternehmensberatung.dtd">

fehlen. Man betrachte nochmal den Schnipsel unten

use XML::LibXML;

$mein_dtd_objekt = XML::LibXML::Dtd->new("","Unternehmensberatung.dtd");
$mein_dokument_objekt = XML::LibXML->new->parse_file("teschto.xml");
print $mein_dokument_objekt->validate($mein_dtd_objekt);

Hier wird der Zusammenhang zwischen der DTD und der XML Datei im Perl Skript selber hergestellt, einer Referenzierung der DTD aus der XML Datei bedarf es nicht. Zweitens wird genau angezeigt, wo der Fehler steckt. Fügt man zum Beispiel in die XML Datei ein Element banane ein, dass in der DTD nicht deklariert wird, dann erhält man sowas.

C:\sambar\cgi-bin>perl xmllib.pl
teschto.xml:0: Element Gruppe content does not follow the DTD
Expecting (Gruppenname , Kollegen , Ansprechpartner , Adresse , (Budget | Kosten
voranschlag) , Kommentar*), got (banane Gruppenname Kollegen Ansprechpartner Adr
esse Budget Kommentar )
teschto.xml:0: No declaration for element banane

C:\sambar\cgi-bin>

Die Fehlermeldungen sind also sogar sehr präzise. In Dokumentationen findet man oft, dass die Fehler in $@ abgespeichert werden. Offensichtlich ist dies aber nicht (mehr) richtig, man erhält sie sofort.

vorhergehendes Kapitel