Aufschlussreich
Das PHP Extension and Application Repository enthält eine Reihe von Werkzeugen, die die Arbeit mit PHP erleichtern. Eins davon erlaubt die Verarbeitung von XML-Daten ohne XSLT.
- Sebastian Bergmann
XML mit PHP zu verarbeiten ist durch die Integration von James Clarks XML-Parser Expat schon länger recht einfach. Aber nicht jeder will seine XML-Daten mit XSLT in andere Formate, meist HTML, wandeln. XML Transformer, ein Bestandteil des PEAR (PHP Extension and Application Repository, siehe „Online-Ressourcen“), verfolgt die Lösung zweier Aufgaben, die nur auf den ersten Blick unkorreliert erscheinen. Zum einen suchen viele nach einem eleganten Weg, um PHP-Objekte „ins Web zu bringen“. Zum anderen ist die Transformation von XML-Daten aus einem Format in ein anderes bei der Entwicklung von Webapplikationen omnipräsent.
| Online-Ressourcen | |
| XML Transformer | pear.php.net/package-info.php?pacid=37 |
| W3C: | |
| XSL | www.w3.org/Style/XSL/ |
| XSLT | www.w3.org/TR/xslt |
| XPath | www.w3.org/TR/xpath |
| Weiteres: | |
| PEAR-Site | pear.php.net/ |
| iX-Tutorial zu XSLT | www.heise.de/ix/artikel/2001/01/167/ |
| K. Köhntopps und S. Bergmanns Slides zum XML Transformer | www.koehntopp.de/kris/artikel/xml-transformer/ |
| S. Bergmanns Slides zum Vortrag auf der PHP-Konferenz, Amsterdam 2003 | www.sebastian-bergmann.de/talks/adam-2003-xml.pdf |
Vom Arbeitsprinzip her ist die Extensible Stylesheet Language Transformations (XSLT) des W3C mit awk verwandt: Bedingungen (Template Match) oder Funktionen (Template Name) werden nacheinander auf die Eingabe angewendet. Diese hat bei awk Zeilen, bei XSLT (siehe dazu das iX-Tutorial von 2001) liegt sie als Baumstruktur vor. Wie awk kann XSLT nicht auf seine Ausgabe zugreifen.
Stylesheets, wie in XSLT geschriebene Programme aus historischen Gründen heißen, sind ihrerseits XML-Dokumente. Daher ist XSLT geschwätzig, sogar einfache Sprachkonstrukte erfordern viel Schreibarbeit. Listing 1 zeigt oben den nötigen Code, um ein if-then-else in XSLT zu realisieren, unten das hierzu äquivalente PHP-Schnipsel.
Listing 1: if-then-else in XSLT und PHP
<xsl:template match="/">
<xsl:choose>
<xsl:when test="$foobar = 'foo'">
<xsl:call-template name="foo"/>
</xsl:when>
<xsl:otherwise>
<xsl:call-template name="bar"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
_______________________________________
<?php
if ($foobar == 'foo') foo() else bar();
?>
XSLT ist eine funktionale Transformationssprache und bringt daher die Nachteile funktionaler Programmiersprachen mit sich, beispielsweise den Umweg ĂĽber Rekursion fĂĽr das AusdrĂĽcken von Schleifen oder Variablen, die eher konstant als variabel sind. AuĂźerdem kann man die Vorteile der funktionalen Programmierung, etwa das Rechnen mit Funktionen als Datentypen oder die Programmierung mit Closures, nicht nutzen. Die bietet XSLT nicht.
XSLT weiĂź nur alles ĂĽber XML
Ein weiterer Nachteil von XSLT liegt in der Domainbeschränkung: Während XSLT „alles über die Extensible Markup Language weiß“ und diesbezügliche Funktionen anbietet, weiß es relativ wenig von Dingen, die nichts mit XML zu tun haben. Ohne die Einbeziehung von in anderen Programmiersprachen, beispielsweise Java, geschriebenen Programmen über Callbacks können XSLT-Stylesheets nicht mit der Welt jenseits von XML interagieren. Daten aus anderen Quellen zu beziehen, etwa aus einer Datenbank, oder nicht textbasierte Ausgabeformaten (Grafiken oder PDF) zu erzeugen, ist ohne Fremdhilfe nicht machbar.
XML Transformer (gegenwärtig in der Version 0.8.2.) hingegen erlaubt die prozedurale Definition von Transformationen, wobei sich sämtliche Fähigkeiten von PHP wie der Zugriff auf Datenbanken oder die Erzeugung von Grafiken, nutzen lassen. Das Ergebnis eines Zwischenschritts kann im Gegensatz zu XSLT als Eingabe für den nächsten dienen.
Im Wesentlichen besteht die Kodierung mit dem XML Transformer aus der Nutzung vorhandener Namespace Handler oder dem Schreiben eigener. In Listing 2 kommt der im Paket enthaltene Image Namespace Handler zum Tragen. Dieser bietet unter anderem ein „intelligentes“ <img />-Element, das die width- und height-Attribute automatisch auf Basis der per getimagesize erkannten Bildinformationen setzt.
Listing 2: Namensraum: Image
<?php
require_once 'XML/Transformer.php';
require_once 'XML/Transformer/Namespace/Image.php';
$t = new XML_Transformer(
array(
'debug' => true,
'overloadedNamespaces' => array(
'img' => new XML_Transformer_Namespace_Image
)
)
);
$t->transform('<img:img src="iX.teapot.png" />');
?>
Listing 3 lädt zunächst die XML_Transformer- und XML_Transformer_Namespace_Image-Klassen. Danach erzeugt es ein Objekt der Klasse XML_Transformer, und der img-Namensraum wird im Konstruktor an die Klasse XML_Transformer_Namespace_Image gebunden. Die eigentliche Transformation leistet die Methode transform() (siehe Listing 2), die als einzigen Parameter die XML-Eingabe als String übergeben erhält.
Listing 3: Debug-Variante
<?php
require_once 'XML/Transformer.php';
require_once 'XML/Transformer/Namespace/Image.php';
$t = new XML_Transformer;
$t->setDebug(true);
$t->overloadNamespace(
'img',
new XML_Transformer_Namespace_Image
);
?>
Für das Setzen von Parametern und die Definition von Transformationen hält die XML_Transformer-Klasse set-Methoden bereit. So erzeugt Listing 3 (das keine Ausgabe generiert) beispielsweise zunächst ein Objekt der XML_Transformer-Klasse, aktiviert im Anschluss den Debugmodus und lädt die Transformationen des Namensraum-Handler für das Bild.
Parameter und Transformationen lassen sich auch zum Zeitpunkt der Objekterzeugung ĂĽber den Konstruktor der XML_Transformer-Klasse setzen (siehe Listing 2).
Treiberklassen erweitern Verhalten
Neben der direkten Verwendung der XML_Transformer-Klasse und der Transformation der XML-Eingabe ĂĽber deren transform()-Methode bietet das Paket so genannte Treiberklassen an, die das Standardverhalten erweitern. Listing 4 zeigt die Verwendung der Treiberklasse XML_Transformer_Driver_OutputBuffer. Sie benutzt den Output-Buffering-Mechanismus von PHP, um die gesamte Ausgabe des Scripts, in dem sie verwendet wird, zu puffern und anschlieĂźend als XML-Eingabedokument zu benutzen.
Listing 4: Image: Output-Buffer
<?php
require_once 'XML/Transformer/Driver/OutputBuffer.php';
require_once 'XML/Transformer/Namespace/Image.php';
$t = new XML_Transformer_Driver_OutputBuffer(
array(
'overloadedNamespaces' => array(
'img' => new XML_Transformer_Namespace_Image
)
)
);
?>
<img:img src="image.png" />
Eine weitere Treiberklasse ist XML_Transformer_Driver_Cache, die das Ergebnis der Transformation in einem Cache speichert. Zukünftige Transformationen für identische XML-Eingabe und die verwendeten Transformationen lassen sich kostensparend aus ihm bedienen. Mit der Klasse XML_Transformer_Namespace_Image hat Listing 2 schon einen der zum Paket gehörenden Namensraum-Handler genutzt. Der gleichnamige Kasten zeigt einen Überblick über alle im Paket enthaltenen.
Mitgelieferte Namensraum-Handler
Anchor stellt eine Reihe von Tags zur Verfügung, die, normalerweise an den <a>-Namespace gebunden, indirekte, benannte Links, so genannte URNs, ermöglichen.
DocBook bietet Transformationen für ein Subset von DocBook XML nach HTML an. Er stellt eine Beispielimplementierung für Namespace Handler dar, deren Transformationen mehrere Passes benötigen. Querverweise werden, wie bei Latex, in einem ersten Pass gesammelt und in einem anschließenden zweiten verarbeitet.
Image Neben dem <img />-Element bietet er Elemente fĂĽr die dynamische Generierung von Grafiken an. Funktionen und Verwendung orientieren sich am <gtext /> der Roxen-Plattform. Der Image Namespace Handler benutzt einen Cache, um Grafiken fĂĽr <img:gtext/>-Elemente mit denselben Attributen nicht fĂĽr jeden Request neu erzeugen zu mĂĽssen.
PHP erlaubt unter anderem die Einbettung von PHP-Code direkt in ein XML-Dokument sowie die Deklaration neuer Transformationen on the fly.
Widget stellt unter anderem Transformationen für Tags ähnlich dem <obox> der Roxen-Plattform zur Verfügung.
Eigene Namespace Handler programmieren
Im Folgenden soll es um die Programmierung eigener Namespace Handler gehen. Wie die vorangegangenen Listings gezeigt haben, bindet XML Transformer PHP-Funktionen an XML-Elemente, indem eine Klasse an einen Namensraum gebunden wird. Der Pseudo-Namespace &MAIN dient dazu, dies ohne assoziierten Namespace zu tun. Für jedes Element des mit ihr assoziierten Namensraums implementiert die PHP-Klasse, die sich von XML_Transformer_Namespace ableitet, zwei Methoden, die die Transformation beschreiben: start_ELEMENT($attributes) wird für das öffnende Tag des Elements mit den Attributen als Parameter, end_ELEMENT($cdata) dagegen für das schließende Tag des Elements mit den CDATA-Daten als Parameter aufgerufen.
Beide Methoden können, müssen aber nicht, ein XML-Fragment als String zurückgeben, das anstelle des gerade bearbeiteten Elementes in die Ausgabe eingefügt werden soll. Hierbei ist zu beachten, dass die zurückgegebenen Fragmente die Wohlgeformtheit des XML-Dokuments nicht verletzen.
Listing 5 zeigt mit der HelloWorld-Klasse einen Namespace Handler, der mit den Methoden start_helloWorld() und end_helloWorld() eine Transformation fĂĽr das <helloWorld />-Element zur VerfĂĽgung stellt.
Listing 5: Hello World
<?php
require_once 'XML/Transformer/Driver/OutputBuffer.php';
require_once 'XML/Transformer/Namespace.php';
class HelloWorld extends XML_Transformer_Namespace {
function start_helloWorld($attributes) {
return '<html><body>';
}
function end_helloWorld($cdata) {
return $cdata . 'Hello World!</body></html>';
}
}
$t = new XML_Transformer_Driver_OutputBuffer(
array(
'overloadedNamespaces' => array(
'&MAIN' => new HelloWorld
)
)
);
?>
<helloWorld />
Bei der Entwicklung eines Namespace Handlers sollte man stets bedenken, dass der XML Transformer intern mit dem SAX-Parser arbeitet. So sind beispielsweise weder umordnende Transformationen noch die Erzeugung von Inhaltsverzeichnissen in einem Durchgang durchführbar. Sollte man solche Operationen dennoch benötigen, transformiert man die XML-Eingabe in zwei Durchgängen. Durch Setzen des Attributs $secondPassRequired auf true signalisiert der Namespace Handler den Bedarf eines zweiten Durchlaufs.
Zur UnterstĂĽtzung bei der Entwicklung neuer Namespace Handler bietet der XML Transformer einen Debug-Modus an. Dieser kann, wie in Listing 2 und Listing 3 zu sehen, sowohl ĂĽber einen Parameter im Konstruktor der XML_Transformer-Klasse als auch ĂĽber die Methode setDebug(true) aktiviert werden.
Bei Verwendung der Standardeinstellungen werden Debug-Nachrichten an das Fehlerprotokoll des Webservers gesendet. Eine Bildschirmausgabe, die allerdings bei Verwendung der Treiberklasse XML_Transformer_Driver_OutputBuffer nicht verwendet werden kann, ist ebenfalls möglich.
Zwei weitere Namespace Handler
Listing 6 beinhaltet eine Mini-Anwendung. Sie ist datenbankgestützt, wertet mit $cdata das durch den Parameter übergebene $myterm aus und durchsucht eine Datenbank nach Einträgen, deren Kürzel $myterm enthält. Ist das der Fall, füllt die while-Schleife die Variable $dbbuffer mit Kürzel und Bedeutung, auch bei mehreren Treffern. $dbbuffer ist Bestandteil des return, der wiederum eine ganze HTML-Seite ausgibt.
Listing 6: Wörterbuch mit DB
<?php
// Datenbankzugriff:
include "div.inc.php";
require_once 'XML/Transformer/Driver/OutputBuffer.php';
require_once 'XML/Transformer/Namespace.php';
class Woerterbuch extends XML_Transformer_Namespace {
function start_Woerterbuch($attributes) {
return '';
}
function end_Woerterbuch($cdata) {
$dbbuffer = "";
$query = "select kuerzel, bedeutung from dictionary";
$query .= " where kuerzel like '%" . $cdata . "%'";
$result = mysql_query($query);
while(list($myterm,$description) = mysql_fetch_row($result))
{
$dbbuffer .= sprintf(" <dt>%s</dt>\n" .
" <dd>%s</dd>\n", $myterm, $description);
}
return sprintf(
"<html>\n <head>\n <title>Woerterbuch</title>\n" .
" <link rel=\"stylesheet\" type=\"text/css\"" .
" href=\"dict.css\"></head>\n <body>\n" .
" <div id=\"main\">\n <dl>\n" .
"%s" .
" </dl>\n </div>\n </body>\n</html>",
$dbbuffer
);
}
}
$t = new XML_Transformer_Driver_OutputBuffer(
array(
'overloadedNamespaces' => array(
'&MAIN' => new Woerterbuch
)
)
);
?>
<Woerterbuch><?php echo $myterm ?></Woerterbuch>
Leicht ist aus diesem Script eine Anwendung gestrickt, die sämtliche Einträge als Menü zeigt und auf Mausklick die Erklärung für den gewünschten Begriff liefert. XML Transformer lässt sich jedoch außerdem und vor allem nutzen, indem ein solcher Namespace Handler Teile von XML-Dokumenten verarbeitet. Als letzten Namespace Handler enthält Listing 7 eine veränderte Funktion end_Woerterbuch, die das Element Woerterbuch, wie es Listing 8 enthält, in ein <acronym> wandelt. $dbbuffer bekommt den übergebenen Wert von $cdata sowie die dazugehörende Erklärung aus der Datenbank als Attribut title zugewiesen.
Listing 7: Akronymausgabe
function end_Woerterbuch($cdata) {
$query = "select kuerzel, bedeutung from dictionary";
$query .= " where kuerzel = '" . $cdata . "'";
$result = mysql_query($query);
$mytermdesc = mysql_result($result, 0, 1);
$dbbuffer = sprintf("<acronym title=\"%s\">" .
"%s</acronym>\n", $mytermdesc, $cdata);
return $dbbuffer;
}
In einem wohlgeformten, beliebig umfangreichen XML-Dokument (das kann durchaus HTML sein), wie es Listing 8 andeutet, führt das Einbinden von Woerterbuch.php dazu, dass alle Woerterbuch-Elemente zu Akronymen mit title-Attribut mutieren. Für die wiederum zeigen einige Browser die Bedeutung des Akronyms an, sowie der Mauszeiger über dem Kürzel „schwebt“.
Listing 8: Akronymverwendung
<?php
include "Woerterbuch.php";
?>
<html>
<head>
<title>Ratschlag für Gesprächsteilnehmer</title>
</head>
<body>
<h1>Ratschlag für Gesprächsteilnehmer</h1>
<p>Wer den Begriff
<Woerterbuch>tanstaafl</Woerterbuch> verwendet,
muss sich nicht wundern, wenn ihn/sie nicht alle
verstehen und ungläubig dreinblicken... In solchen
Fällen ist dringend vom
<Woerterbuch>rotfl</Woerterbuch> oder gut gemeinten
Aufforderungen wie <Woerterbuch>rtfm</Woerterbuch>
abzuraten...</p>
<p><Woerterbuch>hth</Woerterbuch> :-)</p>
</body>
</html>
Listing 6 und 7 machen sich die Namensraumsache einfach, indem sie den Default nutzen:
array(
'overloadedNamespaces' => array(
'&MAIN' => new Woerterbuch
)
)
Spätestens wenn sie einen weiteren Namensraum im selben Dokument nutzen wollen, müssen Entwickler darauf achten, dass beim Erzeugen der Driver_OutputBuffer-Klasse das Namespace-Array ein anderes Präfix erhält. Statt &MAIN etwa dummy. Im Dokument ist in einem solchen Fall dieses Präfix dem Element voranzustellen: <dummy:Dummy>oops </dummy:Dummy> (siehe den Screenshot). Was unter anderem bedeutet, dass der Verarbeitung komplexer Dokumente nur etwas Programieraufwand entgegensteht.
Sebastian Bergmann
studiert Informatik in Bonn und ist aktives Mitglied der internationalen PHP-Entwicklergemeinde. Sein Buch „Professionelle Softwareentwicklung mit PHP 5“ erscheint demnächst im Dpunkt-Verlag.
iX-TRACT
- Wer XSLT für die Wandlung von XML in HTML als zu umständlich empfindet, kann mit dem XML Transformer PHP-Klassen nutzen und schreiben, die das erledigen.
- Im PEAR-Paket XML Transformer sind ein paar Namespace Handler schon enthalten (wie Image und Anchor).
- PHP-Entwickler können leicht eigene Namespace Handler schreiben, die wohl geformte XML/HTML-Dokumente bearbeiten.