Werbung einblenden Werbung ausblenden


Home / Tutorials / xml, xsl, xpath Handbuch / eventorientiertes Auslesen


Eventorientiertes Auslesen eines XML Dokumentes (mit Perl und PHP)
Ein XML Dokument eventorientiert mit Perl auslesen
Eventorientiertes Auslesen eines XML Dokumentes mit PHP

Eventorientiertes Auslesen eines XML Dokumentes (mit Perl und PHP)

Es gibt im wesentlichen drei Ansätze XML Dokumente auszulesen und zu modifizieren. Den document object model (DOM) Ansatz, XSLT und den eventorientierten Ansatz. Wir vernächlässigen jetzt die Tatsache, dass es mit Perl noch ein paar Ansätze mehr gibt und beschränken uns auf diese drei, die wohl die wichtigsten sind und in den meisten Programmierprachen implementiert sind. Für den DOM orientierten Ansatz siehe DomXML und PHP und Auslesen einer XML Datei mit dem Perl Modul XML::LIBXML, Details zum Bearbeiten eines XML Dokumentes mit XSLT siehe Auslesen eines XML Dokumentes mit XSLT unter Verwendung von Sablotron und PHP und Auslesen eines XML Dokumentes mit XSLT unter Verwendung von Perl XSLT. Während sich der DOM Ansatz und XSLT insofern ähneln, als das gesamte XML Dokument ausgelesen wird, in eine Baumstruktur konvertiert und man entlang dieser Baumstruktur navigieren kann, ist der Event orientierte Ansatz ein völlige anderes Modell. Bei dem eventorientierten Ansatz hat man die Möglichkeit, an bestimmte Events, das Auftreten eines Starttags, eines Endtags oder eines Textknotens bestimmte Funktionen auszuführen und so das Dokument zu konvertieren. Dieser Ansatz ist nicht so leistungsfähig wie DOM oder XSLT, dafür aber schneller und für sehr grosse XML Dokumente eher geeignet, da nicht das ganze XML Dokument gelesen werden muss, bevor mit der Abarbeitung begonnen werden kann. Beim DOM oder XSLT Ansatz muss das gesamte Dokument geparst werden, bevor mam damit arbeiten kann, weil nur dann ein Baum gebildet werden kann, wenn es vollkommen ausgelesen wurde. Dies kann erhebliche Rechnerressourcen beanspruchen. Beim eventorientierten Ansatz muss nur ein Teil des Dokumentes zwischengespeichert werden, ist es abgearbeitet, kann dieser Teil wieder "vergessen" werden. Dieses Verfahren ist also Ressourcen schonender.

Ein XML Dokument eventorientiert mit Perl auslesen

Wir zeigen den Ansatz anhand des unten stehenden XML Dokumentes.

<personendaten>
<persona><name>Andres Ehmann</name>
<telefon> 03047301388 </telefon>
<beruf>Diplom Volkswirt / Magister Artium</beruf>
<adresse>Hallandstrasse 2, 13189 Berlin</adresse>
</persona>
<persona>
<name>Manuel Landivar</name>
<telefon>03045654566</telefon>
<beruf>Licenciado en letras</beruf>
<adresse>Schonhauser Allee 23, 13178 Berlin</adresse>
</persona>

<persona><name>Maria Sedlemayer</name>
<telefon> 089 49499444</telefon>
<beruf>Rechtanwältin</beruf>
<adresse>Krumme Strasse 5, 456545 Muenchen</adresse>
</persona>
<persona><name>Suleika Isnegrim</name>
<telefon>07623 555844 </telefon>
<beruf>Zahnärztin</beruf>
<adresse>Krozinger Strasse 12, 7867 Freiburg</adresse>
</persona>
</personendaten>

Dieses Dokument wollen wir mit einem Ereignis orientierten Ansatz auswerten. Es wurde schon erwähnt, dass dies vielleicht nicht der leistungsfähigste Ansatz ist, dafür aber ein Ansatz, den man sofort versteht. Ereignis orientiert heisst, dass der Perl Skript nach Ereignissen sucht, das sind für ihn in diesem Zusammenhang ein öffnender Tag, der Inhalt eines Tags und ein schliessender Tag. Hierfür kann man das Perl Modul XML::Parser verwenden, das fester Bestandteil der ActiveState Distribution ist. Wer das Problem an sich für trivial hält und der Meinung ist, das könnte man auch ohne ein Modul machen, indem man ein bisschen mit regular expression rumprogrammiert, der irrt. Der XML::Parser, der selber wiederum auf expat aufbaut, liest das Dokument in lightning speed, so dass auch sehr grosse Dokumente verarbeitet werden können. Es ist eine andere Kategorie als das übliche Auslesen eines Flatfiles. Erstmal ein Beispiel, dass lediglich die Datei so wieder auf den Schirm setzt, wie sie am Anfang aussah. Anschliessend ein komplexeres Beispiel, dass tatsächlich eine Transformation nach HTML vornimmt.

#!/usr/bin/perl
use XML::Parser;
my $zeiger = new XML::Parser ();

$zeiger->setHandlers (Start => \&anfang,End => \&ende,Char=>\&inhalt );

$zeiger->parsefile ("test.xml");

sub anfang
{
$wert_des_zeigers = shift;
$starttag= shift;
print "<$starttag>";
print "\n";
}
sub ende
{
($wert_des_zeigers,$endtag) = @_;
print "</$endtag>\n";
}
sub inhalt
{
($wert_des_zeigers,$inhalt)=@_;
print " $inhalt";
}

Um es zum laufen zu bringen, muss man beide Dateien in einen Ordner werfen. Die XML Datei sollte hierbei text.xml heissen, weil der Perlscript nach einer solchen sucht.
$zeiger->parsefile ("test.xml");
Dieses Skript verwendet das Modul XML::Parser (genau genommen das Modul XML und davon das Package Parser). Dieses modul wird mit
use XML::Parser

, in das Programm eingebunden. Anschliessend wird von dem Package XML::Parser ein Objekt gebildet. Eine Einführung zur objektorientierten Programmierung befindet sich im Perl Handbuch. Etwas ungewöhnlich ist hier die Zeile
my $zeiger = new XML::Parser ();
. Das ist aber das gleiche wie das bereits bekannte
my $zeiger=XML::Parser ->new();
$zeiger ist jetzt also eine Referenz auf ein Dingsda, höchstwahrscheinlich auf einen anonymous Hash, weil die nächste Zeile einen Hash übergibt. Das müssen wir aber nicht wissen, den Objekte sind von der Natur der Sache her black boxes. Dies nächste Zeile
$zeiger->setHandlers (Start => \&anfang,End => \&ende,Char=>\&inhalt );
ist ebenfalls eigentümlich. Gehen wir mal alle Eigentümlichkeiten Schritt für Schritt durch. Die erste Eigentümlichkeit ist die Parameterübergabe. Übegeben wird ein Hash, mit Start, End, Char als Schlüssel und Referenzen auf die Funktion anfang,ende,inhalt als Wert. Man muss wohl akzeptieren, dass das funzt. Man kann sich lediglich nochmal an einem Beispiel klarmachen, wie es funzt.

#!/usr/bin/perl

testen("Ehmann"=>"Andres","Maier"=>"Müller","Binsengrün"=>"Igor");
sub testen
{
%banane=@_;
foreach (keys(%banane))
{
print "Nachname ist $_ und Vorname ist $banane{$_} \n";
}
}

Wir nehmen also mit Erschütterung zu Kenntniss, dass man
%banane=@_;
machen kann. Der Sonderarray @_, der also erstmal aussieht wie ein Array, offensichtlich auch Hashs verarzten kann und das man einen kompletten Hash auf die oben dargestellte Weise übergeben kann. Die zweite Eigentümlichkeit ist, dass man, dies wird im Kapitel zu Referenzen im Perl Handbuch beschrieben, nicht nur eine Referenz auf eine Variable, einen Hash und einen Array bilden kann, sondern auch auf eine Subroutine. Mit der Zeile
$zeiger->parsefile ("test.xml");
fangen wir an, das XML Dokument, in unserem Falle also test.xml, auszuwerten. Beim Auswerten stößt der XML Parser auf öffnende Tags
<irgendwas>
, auf schliessende Tags
</irgendwas>
und auf den eigentlichen Inhalt zwischen den Tags. Wenn er auf einen öffnenden Tag stößt, ruft er die Funktion anfang auf und übergibt zwei Parameter, erstens den Name des $zeigers (das referenzierte Dingsda,
das weiss zu wem es gehört, siehe Objektorientierte Programmierung, und den Tag den er gefunden hat. Der
Zeiger interessiert uns nicht, wir eliminieren ihn mit shift aus dem Sonderarray @_. Der Tag allerdings interessiert uns. Wir machen die Tag Zeichen drum rum und setzen ihn auf den Schirm. Nachdem er einen öffnenden Tag gefunden hat, findet er den Inhalt. Foglich wird die Subroutine Inhalt aufgerufen, der wiederum zwei Parameter übergeben werden, der Zeiger auf das Objekt, den mit shift abgefangen wird, und der Inhalt, den wir auch ausdrucken. Das gleiche machen wir mit den schliessenden Tags. Wir erhalten also das Orginal Dokument auf dem Schirm. Zugeben, das ist noch nicht besonders aufregend. Wie das Dokument aussieht, wussten wir schon vorher. Die gleiche Technik kann aber genutzt werde, um ein XML Dokument in ein HTML Dokument zu konvertieren. Das machen wir anschliessend. Vorher machen wir uns aber nochmal klar, wie genau der Aufruf der Subroutinen anfang, inhalt und start aus dem Objekt heraus erfolgt, bzw. wie er erfolgen könnte. Genau müssen wir es nicht wissen, da es in der Logik der objektorientierten Programmmierung steckt, dass man lediglich wissen muss, was der Input ist, was der Output ist und wie man das Objekt einbindet. Der unten stehende Programmschnipsel dient also lediglich der Illustrierung, wie man eine Referenz auf eine Subroutine übergeben könnte.

package test;
sub new
{
$zeiger={};
bless($zeiger);
}
sub create_love
{
$zeiger=shift;
%werte=@_;

while(($schluessel,$wert)=each(%werte))
{
&$wert("$schluessel");
}
}
sub hello
{
print "Hallo $_[0] \n";
}
sub how_do_you_do
{
print "Ist dein Name $_[0] ?";
}

$love=test-> new();
$love->create_love("Knopf"=>\&hello,"Shokufeh"=>\&how_do_you_do);

Entscheidend ist die Zeile

$love->create_love("Knopf"=>\&hello,"Shokufeh"=>\&how_do_you_do);

Hier wird, mit der gleichen Syntax wie in dem Beispiel oben, eine Referenz auf eine Subroutine und ein Wert übergeben. Diese Variablen werden an die Subroutine create_love übergeben, allerdings nicht als Werte des Objektes, sondern als globale Variablen. In den Zeilen

$zeiger=shift;
%werte=@_;

sehen wir uns wieder mit der skurrilen Tatsache konfrontiert, dass @_ erstmal einen isolierten Wert überträgt, nämlich den Zeiger auf das Objekt, den wir mit shift in die Variable $zeiger stecken, und dann noch einen Hash, ("Knopf"=>\&hello,"Shokufeh"=>\&how_do_you_do), den wir in den Hash %Werte packen. In der while Schleife rufen wir dann über die Referenz auf die Subroutine die Subroutine auf und übergeben den Schlüssel des Hash als Parameter. Damit ist dann der oben vorgestellte Perlschnipsel, der die XML Datei ausliest, so einigermassen erläutert. Jetzt noch ein Perl Schnipsel, der tatsächlich mit diesem Ereignis orientierten Ansatz die XML Datei in eine HTML Datei konvertiert.

#!/usr/bin/perl

print "Content-type:text/html\n\n";

use XML::Parser;
my $zeiger = new XML::Parser ();

$zeiger->setHandlers (
Start => \&anfang,
End => \&ende,Char=>\&inhalt );
$zeiger->parsefile ("test.xml");

print "<html><head><body>";

sub anfang
{
%watnu1=("persona"=>"<table border=1 bgclor=yellow>","name"=>"<tr><td>","telefon"=>"<td>","beruf"=>"<td>","adresse"=>"<td>");
$wert_des_zeigers = shift;
$starttag=shift;
print $watnu1{$starttag};
print "\n";
}

sub ende
{
%watnu2=
("persona"=>"</table>","name"=>"</td>","telefon"=>"</td>","beruf"=>"</td>","adresse"=>"</td></tr>");
($wert_des_zeigers,$endtag) = @_;
print "$watnu2{$endtag}";
}

sub inhalt
{
($wert_des_zeigers,$inhalt)=@_;
print " $inhalt";
}

print "</body></html>";

Das Programm hat, bis auf einige kleine Änderungen, den gleichen Aufbau, wie das erste Programm. Man kann es, wie gewohnt, mit der Eingabeaufforderung (Dos Box) aufrufen , oder tatsächlich im Browser, was hier natürlich mehr Sinn macht. In der Dos Box sieht man natürlich nur die HTML Tags, aber es gibt niemanden, der sie interpretiert. Um es im Browser aufzurufen packt man am besten beide Dateien, sowohl das Perl Programm als auch die XML Datei in das cgi-bin Verzeichnis und ruft es dann nach dem üblichen Schema auf (http://127.0.0.1/cgi-bin/mein_Skriptname.pl). Wir sehen dann, daß für jeden Datensatz, also für das was zwischen <persona> </persona> eine eigene Tabelle generiert wird. Das wurde erreicht in dem drei, relativ leicht zu verstehende, Änderungen vorgenommen wurden. Bei der Subroutine anfang wurde dieser Hash hinzugefügt

%watnu1=("persona"=>"<table border=1 bgclor=yellow>","name"=>"<tr><td>","telefon"=>"<td>","beruf"=>"<td>","adresse"=>"<td>");

und bei der Subroutine ende dieser >

%watnu2=
("persona"=>"</table>","name"=>"</td>","telefon"=>"</td>","beruf"=>"</td>","adresse"=>"</td></tr>");

Was passiert ? Nachdem das Objekt $zeiger die Subroutine parsefile aufgerufen hat, knattert diese ( parst ! ) die Datei test.xml durch. Wenn sie einen öffnenden Tag findet, ruft sie die Subroutine anfang auf und übergibt zwei Parameter, den Zeiger auf das Objekt, dasss sie aufgerufen hat, in diesem Falle $zeiger, was uns aber hier nicht interessiert, und den Tag, den sie gefunden hat. Jedem XML Tag wiederum wurden über einen Hash eine Reihe von HTML Tags zugeordnet. Über diesen Hash wird nun ermittelt, welche HTML Tags gedruckt werden sollen, wenn der korrespondierende XML Tag übergeben wird. Diese HTML Tags werden dann gedruckt. Stößt parsefile auf Inhalt, wird der Inhalt gedruckt. Das gleiche wie bei start passiert auch bei ende. Wir können uns das Verfahren nochmal verdeutlichen. Die Subroutine stößt nacheinander auf

<personendaten> => keine Verknüpfung, nix passiert
<persona> =><table border=1 bgcolor=yellow>
<name> =><tr><td>
Inhalt =>Andres Ehmann
</name> =></td>
<telefon> =><td>
Inhalt => 03047301388
</telefon> =></td>
<beruf> =><td>
Inhalt =>Diplom Volkswirt / Magister Artium
</beruf> =></td>
<adresse> =><td>
Inhalt =>Hallandstrasse 2, 13189 Berlin
</adresse> =></td></tr>
<persona> =></table>

Wir sehen also links den Quellbaum, das XML Orginal, und rechts den Ergebnisbaum, die transformierte Datei. Soviel zum Auslesen von XML Dokumenten mit einem Ereignis orientierten Ansatz.

Eventorientiertes Auslesen eines XML Dokumentes mit PHP

Auch PHP setzt auf den Expat Parser auf. Diese Extension gehört zum Standart von PHP und braucht nicht extra eingerichtet zu werden. Wir gehen wieder vom gleichen XML Dokument aus und schreiben ein Skript, dass nichts anderes macht, also die Orginal XML Datei wieder auf den Schirm zu setzen.

<?
$datei_handle = fopen("test.xml", "r");
$daten = fread($datei_handle, filesize("test.xml"));
fclose ($datei_handle);

$parser_object=xml_parser_create();

xml_set_element_handler($parser_object,startfunktion,endfunktion);
xml_set_character_data_handler($parser_object, "textfunktion");
xml_parse($parser_object,$daten);

function startfunktion($parser_object,$elementname,$attribute)
{
print "<$elementname>";
}
function endfunktion($parser_object,$elementname)
{
print "</$elementname>";
}

function textfunktion($parser_object,$text)
{
print "$text";
}
xml_parser_free($parser_object);
?>

Wer es laufen lassen will, muss das XML Dokument in einen Ordner werfen, wo der HTTP Server zugreifen kann, die document_root, das Skript oben in den gleichen Ordner und dann dieses Skript via http://127.0.0.1/name_des_skriptes.php aufrufen. Der Quellbaum, den man erhält wenn man das in den Browser schickt und dann mit rechte Maustaste-> Quelltext anzeigen den Quelltext aufblendet, sieht so aus.

<PERSONENDATEN>
<PERSONA><NAME>Andres Ehmann</NAME>
<TELEFON> 03047301388 </TELEFON>
<BERUF>Diplom Volkswirt / Magister Artium</BERUF>
<ADRESSE>Hallandstrasse 2, 13189 Berlin</ADRESSE>
</PERSONA>
<PERSONA>
<NAME>Manuel Landivar</NAME>
<TELEFON>03045654566</TELEFON>
<BERUF>Licenciado en letras</BERUF>
<ADRESSE>Schonhauser Allee 23, 13178 Berlin</ADRESSE>
</PERSONA>

<PERSONA><NAME>Maria Sedlemayer</NAME>
<TELEFON> 089 49499444</TELEFON>
<BERUF>Rechtanwältin</BERUF>
<ADRESSE>Krumme Strasse 5, 456545 Muenchen</ADRESSE>
</PERSONA>
<PERSONA><NAME>Suleika Isnegrim</NAME>
<TELEFON>07623 555844 </TELEFON>
<BERUF>Zahnärztin</BERUF>
<ADRESSE>Krozinger Strasse 12, 7867 Freiburg</ADRESSE>
</PERSONA>
</PERSONENDATEN>

Wie deutlich zu erkennen, wurden alle Tagnamen in Großbuchstaben umgewandelt. Wer das nicht will, kann es verhindern, indem er zu dem Skript diese Zeile hinzufügt.

$parser_object=xml_parser_create();
xml_parser_set_option($parser_object,XML_OPTION_CASE_FOLDING,FALSE);

Schauen wir uns den Skript nochmal im Detail an.

  <?
1) $datei_handle = fopen("test.xml", "r");
2) $daten = fread($datei_handle, filesize("test.xml"));
3) fclose ($datei_handle);
4) $parser_object=xml_parser_create();
5) xml_set_element_handler($parser_object,startfunktion,endfunktion);
6) xml_set_character_data_handler($parser_object, "textfunktion");
7) xml_parse($parser_object,$daten);
8) function startfunktion($parser_object,$elementname,$attribute)
9) {
10) print "<$elementname>";
11) }
12) function endfunktion($parser_object,$elementname)
13) {
14) print "</$elementname>";
15) }
16) function textfunktion($parser_object,$text)
17) {
18) print "$text";
19) }
20) xml_parser_free($parser_object);
  ?>

In Zeile 1) bis 3) holen wir uns die Datei in die Variable $daten. Was hier genau passiert, ist im PHP Handbuch beschrieben unter Arbeiten mit Flatfiles. Als nächstes bilden wir eine Instanz der Klasse xml_parser_create. Anschliessend definieren wir in 4) mit xml_set_element_handler was passieren soll, wenn der Parser auf einen öffnender Tag bzw. schliessenden Tag stößt. Die Funktion hat drei Parameter, das Objekt, dass das neue XML Dokument repräsentiert, den Namen der Funktion, die ausgeführt werden soll, wenn er auf einen öffnenden Tag stößt und den Namen der Funktion, die ausgeführt werden soll, wenn er auf einen schliessenden Tag stößt. Mit xml_set_character_data definieren wir in 6) noch eine Funktion für das Abarbeiten von Textknoten. Der Rest ist dann selbserklärend. Es werden die Funktionen definiert. $elementname und $attribute wie auch $text können nich willkürlich gewählt werden. Wer will, könnte nach diesem Schema das Dokument auch verändern, allerdings bietet sich für die Modifizierung eines XML Dokumentes wohl eher der DOM Ansatz oder XML an. Angenommen wir möchten unserem Dokument noch eine weitere Adresse hinzufügen, könnten wir aber sowas machen.

<?
$datei_handle = fopen("test.xml", "r");
$daten = fread($datei_handle, filesize("test.xml"));
fclose ($datei_handle);

$parser_object=xml_parser_create();
xml_parser_set_option($parser_object,XML_OPTION_CASE_FOLDING,FALSE);

xml_set_element_handler($parser_object,startfunktion,endfunktion);
xml_set_character_data_handler($parser_object, "textfunktion");


xml_parse($parser_object,$daten);

function startfunktion($parser_object,$elementname,$attribute)
{

global $nochmal;

if ($elementname =="persona" and $nochmal !="ne")
{
print "<persona><name>Heiner Geissler</name>
<telefon> 068 34584884</telefon>
<beruf>Politiker</beruf>
<adresse>Allee der Christenheit 55</adresse>
</persona>
<persona>";
$nochmal="ne";
}
else
{
print "<$elementname>";
}
}
function endfunktion($parser_object,$elementname)
{
print "</$elementname>";
}

function textfunktion($parser_object,$text)
{
print "$text";
}
xml_parser_free($parser_object);

?>

Das Ergebnis sieht dann, im Quelltext, so aus.

<personendaten>
<persona><name>Heiner Geissler</name>
<telefon> 068 34584884</telefon>
<beruf>Politiker</beruf>
<adresse>Allee der Christenheit 55</adresse>
</persona>
<persona><name>Andres Ehmann</name>
<telefon> 03047301388 </telefon>
<beruf>Diplom Volkswirt / Magister Artium</beruf>
<adresse>Hallandstrasse 2, 13189 Berlin</adresse>
</persona>
<persona>
<name>Manuel Landivar</name>
<telefon>03045654566</telefon>
<beruf>Licenciado en letras</beruf>
<adresse>Schonhauser Allee 23, 13178 Berlin</adresse>
</persona>

<persona><name>Maria Sedlemayer</name>
<telefon> 089 49499444</telefon>
<beruf>Rechtanwältin</beruf>
<adresse>Krumme Strasse 5, 456545 Muenchen</adresse>
</persona>
<persona><name>Suleika Isnegrim</name>
<telefon>07623 555844 </telefon>
<beruf>Zahnärztin</beruf>
<adresse>Krozinger Strasse 12, 7867 Freiburg</adresse>
</persona>
</personendaten>

Das Programm oben hat ein Problem, das darin besteht, dass die gesamte XML Datei geladen wird, bevor sie geparst wird. Besser ist, man lädt nur einen Teil davon und parst dann den Teil, dann den nächsten etc. Das sieht dann so aus.

<?
print "hallo";
$datei_handle = fopen("test.xml", "r");

$parser_object=xml_parser_create();
xml_parser_set_option($parser_object,XML_OPTION_CASE_FOLDING,FALSE);

xml_set_element_handler($parser_object,startfunktion,endfunktion);
xml_set_character_data_handler($parser_object, "textfunktion");


while($daten=fread($datei_handle,4096))
{
xml_parse($parser_object,$daten);
}

function startfunktion($parser_object,$elementname,$attribute)
{
print "<$elementname>";

}
function endfunktion($parser_object,$elementname)
{
print "</$elementname>";
}

function textfunktion($parser_object,$text)
{
print "$text";
}

fclose ($datei_handle);
xml_parser_free($parser_object);
?>

vorhergehendes Kapitel