20.09.2015, 15:01
Dieser Beitrag wurde zuletzt bearbeitet: 01.01.2016, 15:23 von Arne Drews.
DOMDocument und DOMXPath... Äh... Was ist das denn?
Mit
Auch die Manipulation des Dokumentes ist bspw. mit dem Erstellen und Einhängen neuer Elemente im Dokumentenbaum selbst möglich, sowie bspw. auch das Erstellen eines neuen XML-Dokumentes.
Auf dieser Pfadebene lassen sich per
Den Vorteil gegenüber den
DOMDocument
Ich möchte zu Beginn kurz anhand einiger kleinen Beispiele die Möglichkeiten von
Als Grundlage für das gesamte Tutorial verwende ich folgende Beispiel HTML-Seite (
Die Seite ist nur mit für das Tutorial nützlichen Elementen gefüllt und stellt nicht unbedingt ein sinnvolles oder konformes Dokument dar!
Wir fangen an!
Zu Beginn erstellen wir eine Instanz der Klasse
Das ist schon alles? Ja, wir haben jetzt mit
Aber was können wir jetzt damit anfangen? Nun, wir können bspw. Elemente selektieren und weiter verarbeiten.
Elemente/Nodes selektieren und verwenden
Dazu stehen uns mehrere Möglichkeiten zur Verfügung, wovon ich die folgenden gerne kurz aufzeigen möchte:
Die Methode ist also quasi ein Server basiertes Äquivalent zu der gleichnamigen JavaScript-Methode:
Wir selektieren also das Element, mit dem Attribut
Bspw. könnten wir uns nun mit der Methode
Ausführliche Informationen dazu und weitere Möglichkeiten können in der offiziellen Dokumentation nachgelesen werden:
Element-/NodeList selektieren
Wir gehen einen Schritt weiter und möchten jetzt bspw. alle
Diese Liste von Elementen können wir bspw. mit foreach iterieren und haben so wieder Zugriff auf jedes einzelne Element:
Für unsere Beispieldatei sollte dieser Code folgende Ausgabe erzeugen:
Das sieht doch schon mal gut aus. Aber irgendetwas stört und bereitet unter bestimmten Anforderungen Probleme. Mit den uns bekannten Methoden kommen wir nämlich bspw. nicht so ohne weiteres direkt an ein bestimmtes
Einfacher geht's mit DOMXPath!
Mit
Fangen wir nochmal ganz vorne an, um uns eine Instanz der Klasse
Das war's schon. Der Konstruktor von
Uns stehen jetzt alle Möglichkeiten von
Was war das denn jetzt?! Nun, wir haben auf ganz simple Art den Pfad nach einem
Aber so eine Abfrage sieht kompliziert aus!
Eigentlich nicht, wenn man weiß, was da steht. Um das zu klären, wollen wir die grundsätzlichen Merkmale einer Query betrachten.
Wir haben bereits gelernt, daß
Man erkennt ein wenig die Ähnlichkeit mit Verzeichnispfaden im Dateisystem. Mit dieser Query erhalten wir sogar auch ein Ergebnis, nämlich alle
Was wir aber wollen, ist nur das zweite Element, daher könnte man jetzt einfach hergehen und aus der
Aber wieso denn jetzt
Grundsätzlich haben wir keinen Fehler gemacht. Uns war vermutlich nur nicht bewußt, daß es ja auch noch andere
Ganz einfach, dazu verwendet man unmittelbar hinter dem Tag-Bezeichner eckige Klammern, um auf dessen Attribute zugreifen zu können. Dabei sollten wir nur wissen, daß ein Attributname mit einem
Prima! Jetzt sind wir zumindest im richtigen Container und erhalten auch unser gewünschtes Element an der erwarteten Stelle der Ergebnisliste. Ein wenig unzufrieden sind wir allerdings schon noch, denn wir wollen ja eigentlich speziell nur das eine Element haben.
Mit dem Wissen über Attribute, das wir gerade erlernt haben, können wir das auch relativ einfach umsetzen:
Hier befindet sich unser gesuchtes Element nun als einziger in der Ergebnisliste, weshalb wir diesen jetzt mit
Geht das auch kürzer?
Ja, das geht natürlich auch kürzer, denn
Mit Angabe zweier Slashes als Pfadtrenner sagen wir der Query, daß es vollkommen egal ist, wo wir uns im Pfad gerade befinden, er soll alles, was danach kommt einfach suchen.
Klingt erstmal einfach, aber doch verwirrend. Deshalb wollen wir das direkt an unserem Beispiel zeigen.
Wir wissen, unser
Das war schon das ganze Geheimnis der Query.
Wäre
Damit soll es auch genug sein in Bezug auf unser kleines Tutorial zu
Es gibt noch einige weitere Feinheiten zu
Mit
DOMDocument
steht uns ein Werkzeug zur Verfügung, HTML
oder XML
Dokumente zu untersuchen und auf bestimmte Elemente darin zuzugreifen.Auch die Manipulation des Dokumentes ist bspw. mit dem Erstellen und Einhängen neuer Elemente im Dokumentenbaum selbst möglich, sowie bspw. auch das Erstellen eines neuen XML-Dokumentes.
DOMXPath
ist ein zusätzliches, mächtiges Selektionswerkzeug, mit dem ein Dokument vom Typ DOMDocument
auf Pfad-Ebene bereitgestellt wird.Auf dieser Pfadebene lassen sich per
DOMXPath
sog. Queries anwenden, die das Selektieren von Elementen zum Kinderspiel machen.Den Vorteil gegenüber den
DOMDocument
Boardmitteln möchte ich in diesem Tutorial anhand von kleinen Beispielen kurz aufzeigen.DOMDocument
Ich möchte zu Beginn kurz anhand einiger kleinen Beispiele die Möglichkeiten von
DOMDocument
aufzeigen, um dann zu DOMXPath
zu wechseln.Als Grundlage für das gesamte Tutorial verwende ich folgende Beispiel HTML-Seite (
startseite.html
):Die Seite ist nur mit für das Tutorial nützlichen Elementen gefüllt und stellt nicht unbedingt ein sinnvolles oder konformes Dokument dar!
Code:
<!DOCTYPE html>
<html lang="de">
<head>
<title>Das Forum mit Tutorials rund um Webentwicklung mit Schwerpunkt PHP</title>
</head>
<body>
<div id="header">
<img id="logo" src="image.png" alt="" border="0">
</div>
<div class="content">
<h1>Seitentitel</h1>
<p class="absatz">irgendein Text</p>
</div>
<div id="footer">
<p class="erster">erster Absatz</p>
<p class="zweiter">zweiter Absatz</p>
<p class="dritter">dritter Absatz</p>
</div>
</body>
</html>
Wir fangen an!
Zu Beginn erstellen wir eine Instanz der Klasse
DOMDocument
und laden unsere Beispiel HTML-Datei dort hinein:PHP-Code:
$oDom = new DOMDocument;
$oDom->loadHTMLFile( 'startseite.html' );
$oDom
Zugriff auf den gesamten Dokumentenbaum unserer Beispiel-Datei.Aber was können wir jetzt damit anfangen? Nun, wir können bspw. Elemente selektieren und weiter verarbeiten.
Elemente/Nodes selektieren und verwenden
Dazu stehen uns mehrere Möglichkeiten zur Verfügung, wovon ich die folgenden gerne kurz aufzeigen möchte:
DOMDocument::getElementById()
DOMDocument::getElementsByTagName()
getElementById()
an. Mit dieser können wir das Element selektieren, welches den angegebenen Wert in dem Attribut id
besitzt.Die Methode ist also quasi ein Server basiertes Äquivalent zu der gleichnamigen JavaScript-Methode:
PHP-Code:
// selektieren des Elements mit id="logo"
$oDomElement = $oDom->getElementById( 'logo' );
id="logo"
. Die Methode DOMDocument::getElementById()
liefert als Rückgabe im Erfolgsfall eine Instanz der Klasse DOMElement
, über die wir auf bestimmte Eigenschaften und Methoden verfügen, um das selektierte Element weiter zu verarbeiten.Bspw. könnten wir uns nun mit der Methode
DOMElement::getAttribute()
den Wert der Attribute unseres Elements zurückgeben lassen, buw. über DOMElement:: setAttribute()
auch Attribute neu setzen.PHP-Code:
// Attribut auslesen
$oDomElement->getAttribute( 'src' );
// Attribut setzen/überschreiben
$oDomElement->setAttribute( 'src', 'images/anotherone.jpg' );
Hinweis
Die KlasseDOMElement
erbt Eigenschaften und Methoden der Klasse DOMNode
, so daß uns bspw. die Möglichkeit zur Verfügung steht, den Inhalt unseres Elementes anzeigen zu lassen: PHP-Code:
echo $oDomElement->nodeValue;
DOMElement
( http://php.net/manual/de/class.domelement.php )DOMNode
( http://php.net/manual/de/class.domnode.php )
Element-/NodeList selektieren
Wir gehen einen Schritt weiter und möchten jetzt bspw. alle
<p>
-Tags unseres Dokumentes selektieren. Diese werden bspw. über die Methode DOMDocument::getElementsByTagName()
selektiert und als Instanz der Klasse DOMNodeList
zur Verfügung gestellt:PHP-Code:
$oDomNodeList = $oDom->getElementsByTagName( 'p' );
PHP-Code:
foreach ( $oDomNodeList as $oDomNode ) {
echo $oDomNode->nodeValue;
}
Ausgabe:
irgendein Text
erster Absatz
zweiter Absatz
dritter Absatz
<p>
-Tag aus dem Footer ran. Wir müssten immer eine Liste von Elementen selektieren und mittels Iteration und Prüfungen unser gesuchtes Element aufspüren.Einfacher geht's mit DOMXPath!
Mit
DOMXPath
wird diese Anforderung über eine sogenannte Abfrage ( Query ) umgesetzt. Dazu bildet DOMXPath
den Dokumentenbaum als Pfad ab, in dem wir uns quasi wie in Verzeichnissen ( mit einigen Besonderheiten natürlich ) bewegen können.Fangen wir nochmal ganz vorne an, um uns eine Instanz der Klasse
DOMXPath
für unser Dokument zu erstellen:PHP-Code:
$oDom = new DOMDocument;
$oDom->load( 'startseite.html' );
// jetzt kommt DOMXPath
$oXPath = new DOMXPath( $oDom );
DOMXPath
benötigt lediglich das DOMDocument
-Objekt zur Initialisierung.Uns stehen jetzt alle Möglichkeiten von
DOMXPath
zur Verfügung. Nehmen wir doch gleich mal ein Beispiel, das den Vorteil an unserem Dokument deutlich zeigt:PHP-Code:
$oDomNodeList = $oXPath->query( '//p[@class="zweiter"]' );
<p>
-Tag mit der CSS-Klasse zweiter
abgefragt. Wurde ein solches Element gefunden, wird dies in einer DOMNodeList
zurückgeliefert. Wir könnten jetzt über das Ergebnis iterieren, genau wie wir es bei dem Beispiel von DOMDocument::getLementsByTagName()
gemacht haben. Da wir in diesem Fall aber wissen, daß wir nur ein Element in der Liste haben, können wir auch direkt darauf zugreifen. Ermöglichen tut uns dies die Methode DOMNodeList::item()
, die uns eine Instanz der Klasse DOMNode
liefert:PHP-Code:
echo $oDomNodeList->item(0)->nodeValue;
Ausgabe:
zweiter Absatz
Hinweis
DOMXPath::query()
unterscheidet erstmal nicht zwischen der Menge der gefunden Elemente und gibt erstmal alles als DOMNodeList
zurück, auch wenn es nur ein Element beinhaltet! Über die Methode DOMNodeList::item()
haben wir jedoch direkten Zugriff auf jedes einzelne Element darin.Aber so eine Abfrage sieht kompliziert aus!
Eigentlich nicht, wenn man weiß, was da steht. Um das zu klären, wollen wir die grundsätzlichen Merkmale einer Query betrachten.
Wir haben bereits gelernt, daß
DOMXPath
unser Dokument als Pfad zur Verfügung stellt. Als Pfad betrachtet, schauen wir uns mal den Weg zu den <p>
-Tag Elementen des Footers an:Pfad:
/html/body/div/p
<p>
-Tag Elemente als DMONodeList
.Was wir aber wollen, ist nur das zweite Element, daher könnte man jetzt einfach hergehen und aus der
DOMNodeList
das zweite item
extrahieren:PHP-Code:
$oDomNodeList = $dXPath->query( '/html/body/div/p' );
echo $oDomNodeList->item(1)->nodeValue;
Ausgabe:
erster Absatz
erster Absatz
? Was haben wir falsch gemacht?Grundsätzlich haben wir keinen Fehler gemacht. Uns war vermutlich nur nicht bewußt, daß es ja auch noch andere
<p>
-Tag Elemente gibt, die über genau den gleichen Pfad abgebildet werden. In unserem Fall befindet sich ein weiterer <p>
-Tag in dem <div>
-Tag mit der Klasse content
. Und als Ergebnismenge liefert uns DOMXPath->query()
alle Elemente, die es mit der Pfad-Angabe findet. Also müssen wir genauer werden, und genau da kommen die besonderen Möglichkeiten einer Query ins Spiel. Wir sagen einfach, daß wir nur die <p>
-Tag Elemente haben möchten, die sich im <div>
-Tag mit der CSS-Klasse footer
befinden. Aber wie?Ganz einfach, dazu verwendet man unmittelbar hinter dem Tag-Bezeichner eckige Klammern, um auf dessen Attribute zugreifen zu können. Dabei sollten wir nur wissen, daß ein Attributname mit einem
@
eingeleitet wird. Die Anforderung sieht damit dann nun so aus:PHP-Code:
$oDomNodeList = $oXPath->query( 'html/body/div[@class="footer"]/p' );
echo $oDomNodeList->item(1)->nodeValue;
Ausgabe:
zweiter Absatz
Mit dem Wissen über Attribute, das wir gerade erlernt haben, können wir das auch relativ einfach umsetzen:
PHP-Code:
$oDomNodeList = $oXPath->query( 'html/body/div[@class="footer"]/p[@class="zweiter"]' );
echo $oDomNodeList->item(0)->nodeValue;
Ausgabe:
zweiter Absatz
item(0)
ansprechen müssen.Geht das auch kürzer?
Ja, das geht natürlich auch kürzer, denn
DOMXPath::query()
kennt mehr als nur einen Slash. Er kennt den Doppelslash!Mit Angabe zweier Slashes als Pfadtrenner sagen wir der Query, daß es vollkommen egal ist, wo wir uns im Pfad gerade befinden, er soll alles, was danach kommt einfach suchen.
Klingt erstmal einfach, aber doch verwirrend. Deshalb wollen wir das direkt an unserem Beispiel zeigen.
Wir wissen, unser
<p>
-Tag hat die CSS-Klasse zweiter
. Also sagen wir der Query einfach, sie soll im gesamten Dokument nach jedem <p>
-Tag mit der Klasse zweiter
suchen:PHP-Code:
$oDomNodeList = $oXPath->query( '//p[@class="zweiter"]' );
echo $oDomNodeList->item(0)->nodeValue;
Ausgabe:
zweiter Absatz
Hinweis
Wie in den Ausgaben zu erkennen ist, steht uns in der PropertyDOMNode::nodeValue
lediglich der PlainText zur Verfügung. Möchten wir den Node als HTML haben, müssen wir diesen zunächst über die Methode DOMDocument:: saveHTML()
HTML formatiert speichern. Ab PHP v5.3.6 kann man hier allerdings auch den optionalen Parameter von DOMDocument:: saveHTML([DOMNode $node])
verwenden, um den DOM direkt auszugeben. Bezogen auf unser letztes Beispiel könnte eine Ausgabe in HTML-Form so aussehen:PHP-Code:
$oDomNode = $oXPath->query( '//p[@class="zweiter"]' )->item(0);
echo $oDomNode->ownerDocument->saveHTML( $oDomNode );
DOMNode::ownerDocument
liefert uns das referenzierte DOMDocument
-Objekt, so daß wir das wie oben zu sehen bequem über chaining lösen können.Wäre
$oDomNode
jetzt bspw. eine Tabelle, hätten wir als Ausgabe diese komplett in HTML Form.DOMDocument/DOMXPath
.Es gibt noch einige weitere Feinheiten zu
DOMXPath
zu sagen, aber das werde ich in einem weiterführenden Tutorial noch mal ausführlicher beschreiben.Hinweis
Alle in diesem Tutorial besprochenen Klassen und deren weitergehende Nutzung sind in der offiziellen Dokumentation nachlesbar:DOMDocument
( http://php.net/manual/en/class.domdocument.php )DOMElement
( http://php.net/manual/en/class.domelement.php )DOMNode
(http://php.net/manual/en/class.domnode.php )DOMNodeList
( http://php.net/manual/en/class.domnodelist.php )DOMXPath
( http://php.net/manual/en/class.domxpath.php )