PHP rocks! wünscht allen Mitgliedern einen guten Rutsch ins neue Jahr 2017 !!!
Hinweis: Das Forum zieht um! Um keine Datenverluste zu haben, schalten wir zwecks Übernahme der Daten das Forum am Sonntag, den 24.04.2016 um ca. 21:00 Uhr offline und passen anschliessend die DNS-Einträge an.
www.php-rocks.de wird euch dann nach den Aktualisierungen der DNS-Server wieder wie gewohnt uneingeschränkt zur Verfügung stehen.
Danke für euer Verständnis!

Themabewertung:
  • 0 Bewertung(en) - 0 im Durchschnitt
  • 1
  • 2
  • 3
  • 4
  • 5
PHP Grundlagen Einführung DOMDocument / DOMXPath
#1
DOMDocument und DOMXPath... Äh... Was ist das denn?

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' ); 
Das ist schon alles? Ja, wir haben jetzt mit $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()
Fangen wir mit der Methode 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' ); 
Wir selektieren also das Element, mit dem Attribut 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 Klasse DOMElement 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
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 <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' ); 
Diese Liste von Elementen können wir bspw. mit foreach iterieren und haben so wieder Zugriff auf jedes einzelne Element:
PHP-Code:
foreach ( $oDomNodeList as $oDomNode ) {

    echo $oDomNode->nodeValue;


Für unsere Beispieldatei sollte dieser Code folgende Ausgabe erzeugen:
Ausgabe:
irgendein Text
erster Absatz
zweiter Absatz
dritter Absatz
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 <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 ); 
Das war's schon. Der Konstruktor von 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"]' ); 
Was war das denn jetzt?! Nun, wir haben auf ganz simple Art den Pfad nach einem <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
Man erkennt ein wenig die Ähnlichkeit mit Verzeichnispfaden im Dateisystem. Mit dieser Query erhalten wir sogar auch ein Ergebnis, nämlich alle <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
Aber wieso denn jetzt 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
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:
PHP-Code:
$oDomNodeList $oXPath->query'html/body/div[@class="footer"]/p[@class="zweiter"]' );
echo 
$oDomNodeList->item(0)->nodeValue
Ausgabe:
zweiter Absatz
Hier befindet sich unser gesuchtes Element nun als einziger in der Ergebnisliste, weshalb wir diesen jetzt mit 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
Das war schon das ganze Geheimnis der Query.
Hinweis
Wie in den Ausgaben zu erkennen ist, steht uns in der Property DOMNode::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.
Damit soll es auch genug sein in Bezug auf unser kleines Tutorial zu 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:


Gehe zu: