| 
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);
?>
|
 |