Anzeige
Smartes Cloud Hosting für anspruchsvolle Projekte.
↬ Loslegen und Spaces testen ↬ Jetzt testen!
Gastautor 15. Dezember 2007

Express-Einstieg in die neuen DOM- und XSL-Features von PHP5

Kein Beitragsbild

von Wolfgang Kurt Bock

Mit der Version 5 gibt es in PHP inzwischen recht solide Sprachmittel zum objektorientierten Programmieren. Es ist daher Zeit, sich auf weitergehenden Baustellen umzuschauen, und einmal die DOM-, XML- und XSL-Funktionalitäten auszuprobieren.

Dieser Beitrag versucht, einen zielgrichteten Einstieg zu finden, setzt aber Erfahrung in der Programmierung mit PHP, einige Kenntnisse zur objektorientierten Programmierung voraus und am besten auch schon prinzipielle Kenntnisse zu DOM, XML und XSLT.

Anzeige

Betrachtete Abschnitte und Arbeitsmittel
Mit PHP5 wurde die objektorientierte Programmierung auf vollkommen neue Füße gestellt. Glücklicherweise ist das so, die neuen Sprachmittel sind recht solide und daher geht dieser Artikel nicht mehr auf PHP4 ein. In Handbuch (deutsche Ausgabe) finden wir XSL-Funktionalitäten alternativ in zwei verschiedenen Abschnitten „XSLT Functions“ und in „XSL functions“. Feine Unterschiede in den Überschriften – trotzdem sind das zwei verschiedene Schuhe. Ebenso in Abschnitten zu DOM: „DOM Functions“ und „DOM XML Functions“. Ausgehend von der Ausgabe des Handbuchs vom 26.Oktober 2007 betrachten wir die Funktionen der folgenden Abschnitte, die auf den Systembibliotheken libxml und libxslt beruhen.

Falls Sie zum Probieren noch kein aktuelles PHP5 haben, würde ich XAMPP von apachefriends.org empfehlen. Ich kenne keinen schnelleren Weg, um eine komplette Webserverumgebung mit PHP und MySQL wahlweise für Windows oder Linux zu erhalten.

Die nachfolgenden Übungen habe ich mit XAMPP für Linux ausgeführt, der Pfad zum PHP-Interpreter ist dort /opt/lampp/bin/php. Sie müssten dafür den Pfad ihres Interpreters kennen, vielleicht genügt auch schon der Aufruf des Kommandos php ohne Pfad.

 # Hilft vielleicht auch:
$ which php

Wenn Sie die Übungen wie eine Webseite aufrufen wollen, bauen Sie um die angegebenen PHP-Quelltexte einfach folgenden Rahmen herum:

      <html><body><pre>
... hier der PHP-Code ...
</pre></body></html>

Für die Versuche genügt das. Beispieldateien herunterladen

Der XSLT-Prozessor in PHP
Die Verwendung eines XSLT-Prozessors in PHP ist überraschend einfach. Zunächst brauchen wir eine XML-Datei, hier ist der Quelltext von dokument.xml.

      <?xml version="1.0" encoding="iso-8859-1" ?>
<dok>
 <liste title="Beliebte Webseiten">
   <zeile>drweb.de</zeile>
   <zeile>mysql.com</zeile>
   <zeile>php.net</zeile>
 </liste>
 <liste title="Beliebte Tools">
   <zeile>Apache</zeile>
   <zeile>Irfanview</zeile>
   <zeile>Notepad++</zeile>
 </liste>
</dok>

Über die inhaltliche Aussage des Dokuments könnte man veilleicht länger diskutieren, uns interessiert aber mehr die Struktur. Besonders dann, wenn man das XML-File in ein HTML-Dokument überführen möchte. Als mögliches Beispiel dient dok2html.xsl.

      <?xml version="1.0" encoding="iso-8859-1" ?>
<xsl:stylesheet version="1.0"
     xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

<xsl:output
  method="html"
  version="4.01"
  indent="yes"
  encoding="iso-8859-1"
  omit-xml-declaration="yes"
  doctype-public="-//W3C//DTD HTML 4.01 Transitional//EN"
  doctype-system="http://www.w3.org/TR/html4/loose.dtd"
/>

<!-- Leere anonyme Textknoten entfernen -->
<xsl:strip-space elements="*" />

<xsl:template match="dok">
  <xsl:element name="html">
  <xsl:element name="body">
    <xsl:apply-templates select="liste" />
  </xsl:element>
  </xsl:element>
</xsl:template>

<xsl:template match="liste">
  <xsl:if test="@title">
    <xsl:element name="h3">
      <xsl:value-of select="@title"/>
    </xsl:element>
  </xsl:if>

  <xsl:element name="ul">
  <xsl:apply-templates select="zeile"/>
  </xsl:element>
</xsl:template>

<xsl:template match="zeile">
  <xsl:element name="li">
  <xsl:value-of select="."/>
  </xsl:element>
</xsl:template>

</xsl:stylesheet>

Mit Hilfe des XSL-Files können wir die Transformation ausführen. Wie das mit PHP5 geht, zeigt das folgenden Listing der Datei xsltprozessor.php

      <?php
$xml = new DOMDocument;
$xml->load('dokument.xml');

$xsl = new DOMDocument;
$xsl->load('dok2html.xsl');

$proc = new XSLTProcessor;
$proc->importStyleSheet($xsl);
$proc->transformToURI($xml, './dokument.html');
?>

Mehr ist wirklich nicht nötig. Ein ähnliches Beispiel findet man im PHP-Handbuch. Man muss das PHP-Programm nur noch ausführen, etwa in der Art:

      $ /opt/lampp/bin/php xsltprozessor.php

Auf der Kommandozeile gibt das Programm nichts aus, dafür entsteht die Datei dokument.html.

      <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
  "http://www.w3.org/TR/html4/loose.dtd">
<html><body>
<h3>Beliebte Webseiten</h3>
<ul>
<li>drweb.de</li>
<li>mysql.com</li>
<li>php.net</li>
</ul>
<h3>Beliebte Tools</h3>
<ul>
<li>Apache</li>
<li>Irfanview</li>
<li>Notepad++</li>
</ul>
</body></html>

Damit dürfte es kein Problem sein, XSL-Transformationen mit PHP5 auch auf der Serverseite auszuführen.

Hinweis: Freunde der Kommandozeile unter Linux kennen natürlich xsltproc, hier haben wir dieselbe Leistung in PHP.

Verbreitete foreach-Lösung

Soweit so gut. Was aber, wenn wir ein XML-Dokument laden und selbst auswerten wollen? Unser Ziel könnte es beispielsweise sein, den Wert des Attributs title aus dem zweiten Element liste zu selektieren. Ein wenig gegoogelt und man findet recht schnell brauchbare Ansätze. Sehr viele arbeiten mit „Simple XML“, einem alternativen Paket, das XML direkt in eine Array-Struktur überführt, die man dann herkömmlich auswertet. Schön und gut, aber wir wollen uns ja mit DOM anfreunden. Außer Beispielen mit foreach habe ich keine weiteren Ansätze finden können. Ein solches Beispiel, an unser Ziel angepasst, zeigt das Listing foreachread.php.

        1  <?php
 2  $doc = new DOMDocument();
 3  $doc->load('dokument.xml');
 4
 5  $mynodes=$doc->getElementsByTagname('liste');
 6
 7  foreach($mynodes as $index => $myelement)
 8  {
 9    print "$index, ".$myelement->getAttribute('title')."\n";
10
11    if ($index == 1)
12    {
13      $mytitle = $myelement->getAttribute('title');
14    }
15  }
16
17  print "Gesuchter Attributwert: $mytitle \n";
18  ?>

Und so sieht das Ergebnis aus:

      $ /opt/lampp/bin/php foreachread.php
0, Beliebte Webseiten
1, Beliebte Tools
Gesuchter Attributwert: Beliebte Tools

Wie man ein XML-Dokument in ein DOM-Objekt einliest, das erklärt sich in den Zeilen 2 und 3 von selbst. Die Methode getElementsByTagname (Zeile 5) ist auch schnell im PHP-Handbuch zu finden. Dort findet man auch die Erklärung, dass der Rückgabewert vom Objekttyp DOMNodeList sein muss (wer nicht weiß, was das bedeutet – im Moment ignorieren). Es könnte überraschen, dass man darauf eine foreach-Schleife anwenden kann. Ist $mynodes etwa doch ein Array? Eine zusätzliche Anweisung unter Zeile 5 in der Art

      print_r($mynodes); # Denkste!

liefert keineswegs das Erwartete.

      DOMNodeList Object
(
)

Die Schleife funktioniert aber ohne jeden Zweifel, ist also nicht nur auf Arrays, sondern auch auf diesen Objekttyp anwendbar. Aber, sind wir denn immer auf die Schleife angewiesen, wenn wir nur mal einen Attributwert gezielt lesen wollen?

Mit dem DOM-Objekt arbeiten

Beim Lösen der Rätsel hilft uns der W3C-Standard zu DOM weiter. Bitte betrachten Sie beim weiteren Lesen dieses Artikels parallel dazu die „IDL Definitions“ im Anhang F des aktuellen DOM-Core-Standards, und verfolgen Sie die in den nächsten Schritten genannten Stellen, sonst sind die Erklärungen nicht zu verstehen.

Hinweis: Was wir in diesem Abschnitt zeigen, gilt nicht nur unter PHP, sondern in jeder Programmiersprache, sofern DOM ausreichend genau implementiert wurde.

Im Standard finden wir alle Basis-Klassen der in DOM vorkommenden Objekte. Es sollte bekannt sein, dass alle Methoden (außer private-Methoden) einer Basisklasse in jeder abgeleiteten Klasse enthalten sind.

Eine besondere Klasse ist das interface Node (allem, was dort „interface“ heißt, unterstellen wir, das es in PHP eine Klasse ist). Gleich zu Anfang der Definition finden wir eine Liste von Konstanten, die verschiedene Knotentypen bezeichnen. XML betrachtet jeden Dokumentteil als Knoten eines bestimmten Typs, und alle in XML vorkommenden sind hier aufgelistet. Bekannte Typen wie Element (Wert: 1) und Attribut (Wert: 2) stehen gleich am Anfang. Wir können daher zurecht annehmen, dass die Klasse Node die „Ur-Klasse“ jedes Knotens ist und versuchen, mit den Möglichkeiten, die Node bietet, auszukommen.

Weiter unten finden wir das Attribut nodeType vom Typ unsigned short. Was in DOM „Attribute“ heißt, betrachten wir als Member einer Klasse und wenn da noch readonly davor steht, können wir den Member zumindest lesen; und unsigned short erscheint einfach als numerischer Typ. Probieren wir gleich mal aus. Ist $doc (Zeile 2 im Listing) ein Knoten, und wenn ja, welcher Typ? Die folgende Anweisung unter Zeile 3 eingefügt, zeigt es uns:

      print "Knotentyp: ".$doc->nodeType."\n";
# Ergebnis:
Knotentyp: 9

$doc ist also ein Document Node. XML definiert diesen Knoten als den höchsten Knoten eines XML-Dokuments, der keinen speziellen Namen hat. Daher brauchen wir gar nicht erst nodeName (vom Typ DOMString) abzurufen.

Hinweis: Nein, der Wurzelknoten ist nicht identisch mit dem höchsten Element.

DOMString ist in PHP ein ganz normaler String. Ansonsten machen wir keinen Fehler, wenn wir uns an die Groß- und Kleinschreibweise der Bezeichner wie im Standard vorgegeben halten.

Wie kommen wir vom Dokumentknoten aus nun weiter? Wir finden in Node den Member childNodes vom Typ NodeList. Dieser Typ ist weiter unten in interface Nodelist definiert. Wir finden dort die Zahl length und die Methode item(), die einen Knoten (Node) zurückgibt. Es kann eigentlich nur sein, dass wir mit item() die untergeordneten Knoten (die Childs) abrufen können, und length die Anzahl der Childs liefert. Das klingt jetzt vielleich nach kühner Behauptung, man kann es aber im Standard genau nachlesen.

Und so kommen wir ran: Wir verstehen den Ausdruck $doc->childNodes als Objekt vom Typ NodeList und rufen davon length ab.

      # Erweiterung der Einfügung unter Zeile 3
print "Anzahl Childs: ".$doc->childNodes->length."\n";
# Ergebnis:
Anzahl Childs: 1

Was, es gibt nur einen untergeordneten Knoten? Und wie heißt der? Um das herauszufinden müssten wir item() mit dem Indexwert 0 (Zählung beginnt bei 0) bemühen. Da erhalten wir wieder ein Objekt vom Typ Node (Typ des Rückgabewerts) und davon rufen wir nodeName ab. Alles klar?

      # Erweiterung der Einfügung unter Zeile 3
print "Der Name des Child-Knotens: ".$doc->childNodes->item(0)->nodeName."\n";
# Ergebnis:
Der Name des Child-Knotens: dok

Aha, jetzt sind wir beim Wurzel-Element angelangt.

Hinweis: Sollte bei Ihnen jetzt der Forscherdrang ausgebrochen sein, dann lassen Sie sich nicht bremsen. Sie können auch später hier weiterlesen.

Der Knoten ist natürlich ein Element-Knoten, das interface Element zeigt uns Näheres. Genauer finden wir die Zeilen:

      interface Element : Node {
  readonly attribute DOMString       tagName;
  ...

Auf „Doppelpunkt Node“ möchte ich aufmerksam machen. Das bedeutet, dass die Klasse Element eine Ableitung der Klasse Node ist.

Wir haben daher zwei Möglichkeiten. Entweder Member und Methoden von Element verwenden oder den Knoten einfach weiter wie Node verwenden. Da wir uns mit Node schon einigermaßen angefreundet haben, bleiben wir beim Letzterem. Das heißt, auch der Elementknoten besitzt alle Member und Methoden von Node. Wenn wir vom gefundenen Knoten dok wieder die childNodes abrufen, müssten wir die Elemente mit Namen „liste“ finden.

      # Neue Schleife unter Zeile 2:
for ($i=0; $i < $doc->childNodes->item(0)->childNodes->length; $i++)
{
  print "$i, "
  .$doc->childNodes->item(0)->childNodes->item($i)->nodeName
  .','
  .$doc->childNodes->item(0)->childNodes->item($i)->nodeType."\n";
}
# Ergebnis:
0, #text,3
1, liste,1
2, #text,3
3, liste,1
4, #text,3

Interessant erst einmal, dass fünf Knoten gefunden werden. Wir finden die beiden „liste“-Knoten vom Typ 1 (also Element), aber auch „#text“ mit dem Typ 3 (Textknoten) dazwischen. Das sind die so genannten anonymen Textknoten, in unserem Fall sind Whitespace-Zeichen (insbesondere Zeilenwechsel) enthalten.

Hinweis: Der XML-Standard betrachtet jede geschlossene Textpassagen in Inhalten von Elementen immer als anonymen Textknoten.

Um an das gewünschte Attribut zu kommen, brauchen wir das zweite Listenelement, also den Knoten mit Index 3. Das wäre dann dieser Ausdruck:

      $doc->childNodes->item(0)->childNodes->item(3)

Ganz klar, dass wir auch dieses Objekt wieder wie ein Objekt Node behandeln können. Davon ausgehend wäre nun das Attribut „title“ zu finden. In Node finden wir den Member attributes, und zwar vom Typ NamedNodeMap. Es muss also ein interface NamedNodeMap geben, und genau darin finden wir die Methode getNamedItem() mit einem Zeichenkettentyp als Argument. Das kann nur ein Name ein, in unserem Fall der unseres Attributes. (Nicht ausgeschlossen, das wir auch einen anderen, alternativen Weg einschlagen könnten). Was gibt getNamedItem() zurück? Natürlich einen Knoten. Wie wir den Wert dieses Knotens finden, ist mittels nodeValue kein Problem mehr. Wir sind am Ziel, und das Programm domread.php enthält dieses kurze Listing.

      <?php
$doc = new DOMDocument();
$doc->load('dokument.xml');

print "Gesuchter Attributwert: "
.$doc->childNodes->item(0)->childNodes->item(3)->attributes->getNamedItem('title')->nodeValue."\n";
?>

# Ergebnis:
Gesuchter Attributwert: Beliebte Tools

Damit kein Missverständnis entsteht:Wir haben jetzt DOM verstanden. Das war das Ziel und nicht, den effektivsten Weg zum Erhalten des Attributwerts zu finden. Das geht natürlich besser.

Perfekte Lösung
Beim bisherigen Weg gibt es ein paar Unsicherheiten: Wenn das Ausgangsdokument umgebaut wird, etwa durch Entfernen aller überflüssigen Zeilenwechsel und Leerzeichen oder durch Hinzufügen eines Kommentars oder einer Processing Instruction für ein XSL-Stylesheet, dann wird unser Attributwert an anderer Stelle im DOM-Baum zu finden sein.

Kehren wir noch einmal zur foreach-Lösung zurück, und zwar vor die Schleife. Da hatten wir in Zeile 5:

      $mynodes=$doc->getElementsByTagname('liste');

Und wir hatten bereits festgestellt, dass $mynodes ein Objekt vom Typ DOMNodeList ist. Das ist natürlich genau das implementierte interface NodeList des Standards. Der Methodeaufruf getElementsByTagname(‚liste‘) gibt nur die „liste“-Knoten zurück (hatte uns bereits die Schleife gezeigt), egal, was sonst noch im Dokument an Knoten herumschwirrt.

Mit den Kenntnissen des DOM-Standards kombiniert, brauchen wir die Schleife nun nicht mehr (kompletter Quelltext in perfectread.php).

      $mynodes=$doc->getElementsByTagname('liste');
$mytitle = $mynodes->item(1)->attributes->getNamedItem('title')->nodeValue;
print "Gesuchter Attributwert: $mytitle \n";

# Ergebnis:
Gesuchter Attributwert: Beliebte Tools

Mit XPath geht es ganz gezielt
Es gibt nichts, was man nicht besser machen könnte. Wer mit XPath vertraut ist, kommt ganz schnell an sein Ziel. Hier erst einmal der Quelltext von xpathread.php

       1  <?php
 2  $doc = new DOMDocument();
 3  $doc->load('dokument.xml');
 4
 5  $xpathdoc = new DOMXPath($doc);
 6
 7  $mynodes = $xpathdoc->query('//liste[2]/@title');
 8  $mytitle = $mynodes->item(0)->nodeValue;
 9
10  print "Gesuchter Attributwert: $mytitle \n";
11  ?>

Die Zeilen bis 3 laden, wie gehabt, das XML-Dokument in ein DOM-Objekt. Zeile 5 formt das DOM-Objekt in ein so genanntes XPath-Objekt um. Näheres ist darüber im PHP-Handbuch bei den „DOM Functions“ zu finden. Zeile 7 verwendet die Methode query() des XPath-Objekts. Dieser Methode kann man einen kompletten XPath-Pfad mit allen möglichen Ausdrücken übergeben. Der Pfad //liste[2]/@title selektiert zielgerichtet das „title“-Attribut im zweiten „liste“-Element.

Hinweis: Nein, kein Fehler: Die Index-Zählung in XPath beginnt für diesen Fall bei 1, nicht bei 0. Allerdings enthält $mynodes ein Objekt vom Typ DOMNodeList, das allerdings nur einen Knoten, nämlich den selektierten Attributknoten, enthält.

Mit unseren DOM-Kenntnissen holen wir den gesuchten Attributwert heraus (Zeile 8) – ja und das war’s. Zum Abschluss lohnt es sich, die interface-Definitionen im Standard noch einmal anzuschauen, und die Teile mit den Objekten, Membern und Methoden der „DOM Functions“ zu vergleichen. Ich glaube, nun erklärt sich fast alles weitere von allein. ™

Material zum Artikel:

  • Alle Beispieldateien

2 Kommentare

  1. Sorry Martin,

    habe deinen Kommentar wirklich erst jetzt entdeckt,
    weil ich mit Kommentaren zu meinem Artikel
    schon nicht mehr gerechnet hatte :-(

    Also, die dok2html.xsl habe ich selbst programmiert.
    Wie man so etwas macht, habe ich in meinen XML-Buch
    beschrieben: http://wbock-privat.de/xmlbuch

    Beste Grüße Wolfgang

  2. Hallo,

    Meine Frage, in dem Tutorial verwendest du dok2html.xsl – wie kommst du zu dem xsl file dass du ja zur Konvertierung in Richtung HTML brauchst..?

    bin schon lange am suchen…bitte um Hilfe..

    mfg Martin

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.