PHP/DOM
PHP 5 hält sich weitgehend an den DOM-Standard - die definierten Funktionsnamen wurden also unverändert übernommen.
Erste Schritte
Zuerst muss eine XML-Datei beschafft werden. Weblogs bieten ihre Daten meist im RSS-Format an, das sich gut für eigene Experimente eignet. Von der Tagesanzeiger-Website (http://www.tagesanzeiger.ch/rss.html) kann man ein Datenpaket der folgenden Art erhalten.
<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
<channel>
<title>Tagesanzeiger Front</title>
<link>http://tagesanzeiger.ch</link>
<description>RSS von Tagesanzeiger</description>
<language>de-de</language>
<copyright>Tagesanzeiger</copyright>
<atom:link href="http://tagesanzeiger.ch/rss.html" rel="self" type="application/rss+xml" />
<image>
<title>Tagesanzeiger Front</title>
<link>http://tagesanzeiger.ch</link>
<url>http://files.newsnetz.ch//rss_logos/2.png</url>
</image>
<item>
<title>Bush unterzeichnet Rettungsplan</title>
<description>Grosse Erleichterung für die Finanzmärkte in aller Welt: Nach zweiwöchigem dramatischen Tauziehen ist der 700 Milliarden Dollar schwere US-Rettungsplan unter Dach und Fach.</description>
<category>UnternehmenKonjunktur</category>
<link>http://tagesanzeiger.ch/wirtschaft/unternehmen-und-konjunktur/story/17156322</link>
<guid isPermaLink="false">http://tagesanzeiger.ch/wirtschaft/unternehmen-und-konjunktur/story/17156322</guid>
<pubDate>Fri, 3 Oct 2008 21:14:38 +0200</pubDate>
</item>
<item>
<title>Schweiz soll Russland in Georgien vertreten</title>
<description>Die Schweiz ist bereit, die diplomatischen Interessen Russlands in Georgien zu vertreten. Der Bundesrat hat eine entsprechende Anfrage Russlands im Grundsatz gutgeheissen.</description>
<category>Standard</category>
<link>http://tagesanzeiger.ch/schweiz/story/31131884</link>
<guid isPermaLink="false">http://tagesanzeiger.ch/schweiz/story/31131884</guid>
<pubDate>Fri, 3 Oct 2008 19:50:43 +0200</pubDate>
</item>
<item>
<title>Kalifornien geht das Geld aus</title>
<description>Wegen des Kreditengpasses auf den Finanzmärkten kann Kalifornien seine Rechnungen bald nicht mehr bezahlen.</description>
<category>UnternehmenKonjunktur</category>
<link>http://tagesanzeiger.ch/wirtschaft/unternehmen-und-konjunktur/story/12571967</link>
<guid isPermaLink="false">http://tagesanzeiger.ch/wirtschaft/unternehmen-und-konjunktur/story/12571967</guid>
<pubDate>Fri, 3 Oct 2008 21:18:59 +0200</pubDate>
</item>
</channel>
</rss>
|
Mit einer solchen Datei kann nun gearbeitet werden. In der Praxis würde statt der lokalen Version direkt der neueste Feed von der jeweiligen Website gelesen werden - fertig ist ein Weblog, das sich mit Daten des yi>Tagesanzeigers füllt. Aufgrund des Standards RSS funktioniert das dann auch mit tausenden von anderen Seiten.
Diese Daten sollen nun geladen und verarbeitet werden. Das folgende Skript "dom.readtitles.php" liest die Titel der RSS-Datei aus und gibt sie aus.
<?php
## Name dom_readtitles.php
## Erstellen einer Instanz des DOM-Dokuments
$dom = new DomDocument();
## Lesen der XML-Datei
$dom->load("data/articles.xml");
## Mit der DOM-Funktion "getElementsByTagName" werden alle Elemente mit
## dem betreffenden Namen ermittelt.
$titles = $dom->getElementsByTagName("title");
## Zurückgegeben wird ein Array aus Knoten, die mit der Funktion "foreach"
## durchlaufen werden können.
foreach($titles as $node)
{
## Die Ausgabe erfolgt mit der Eigenschaft "textContent".
echo "<b>Title:</b> {$node->textContent}\n<hr noshade>\n";
}
?>
|
Title: Tagesanzeiger Front Title: Tagesanzeiger Front Title: Bush unterzeichnet Rettungsplan Title: Schweiz soll Russland in Georgien vertreten Title: Kalifornien geht das Geld aus |
Die Basisklassen und Funktionslisten
Die folgende Tabelle ziegt die wichtigsten zur Verfügung stehenden Klassen.
Klasse | Beschreibung | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
DomAttr | Repräsentiert ein Attribut. Die wichtigsten Methoden dieser Klasse sind:
| ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
DomCData | Ein CDATA-Abschnitt. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
DomComment | Ein Kommentar. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
DomDocument | Die wichtigste Klasse - das Dokument selbst. Die wichtigsten Methoden dieser Klasse sind:
| ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
DomDocumentType | Die DOCTYPE-Deklaration des Dokuments. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
DomElement | Ein vollständiges Element. Die wichtigsten Methoden dieser Klasse sind:
| ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
DomEntity | Eine Entität (z. B. "&"). | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
DomEntityReference | Eine Verweis-Entität (auf eine externe Datei). | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
DomNode | Allgemeiner Knoten (kann alles ausser die Klasse "DomDocument" sein). | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
DomProcessingInstruction | Eine Prozessanweisung (z. B. "<?id?>"). | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
DomText | Freistehender Text. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
DomXPath | Repräsentiert eine XPath-Abfrage. |
Je nach Abfrage entstehen durch Anwendung bestimmter Methoden Objekte dieser Klassen. Über dieses Objekt erhält man dann Zugang zum Dokument, seinen Elementen und deren Attributen. Bestimmte Methoden (die wiederum dem W3C-DOM-Standard entsprechen) werden dann benutzt, um Teile des Dokuments zu lesen, zu ändern oder zu löschen.
Konstanten
Beim Umgang mit DOM sind folgende Konstanten wichtig, welche die Lesbarkeit der Skripte deutlcih steigern.
Konstante | Beschreibung |
---|---|
XML_ELEMENT_NODE | Element |
XML_ATTRIBUTE_NODE | Attribut |
XML_TEXT_NODE | Textknoten |
XML_CDATA_SECTION_NODE | CDATA-Abschnitt |
XML_ENTITY_REF_NODE | Entitätsverweis |
XML_ENTITY_NODE | Entitätsknoten |
XML_PI_NODE | Prozessanweisung |
XML_COMMENT_NODE | Kommentar |
XML_DOCUMENT_NODE | Dokument |
XML_DOCUMENT_TYPE_NODE | Dokumententyp-Deklaration |
XML_DOCUMENT_FRAG_NODE | Dokumentenfragment |
XML_NOTATION_NODE | Notation (in einer DTD) |
XML_GLOBAL_NAMESPACE | Globaler Namensraum |
XML_LOCAL_NAMESPACE | Lokaler Namensraum |
XML_HTML_DOCUMENT_NODE | Dokument im HTML-Modus |
XML_DTD_NODE | Document Type Definition |
XML_ELEMENT_DECL_NODE | Element-Deklaration einer DTD |
XML_ATTRIBUTE_DECL_NODE | Attribut-Deklaration einer DTD |
XML_ENTITY_DECL_NODE | Entitäts-Deklaration einer DTD |
XML_NAMESPACE_DECL_NODE | Namensraum-Deklaration |
XML_ATTRIBUTE_CDATA | CDATA-Abscnitt eines Attributs |
XML_ATTRIBUTE_ID | ID-Attribut |
XML_ATTRIBUTE_IDREF | IDREF-Attribut |
XML_ATTRIBUTE_IDREFS | Kollektion von IDRF-Attributen |
XML_ATTRIBUTE_ENTITY | Entität in einem Attribut |
XML_ATTRIBUTE_NMTOKEN | NMTOKEN |
XML_ATTRIBUTE_NMTOKENS | Kollektion von NMTOKEN |
XML_ATTRIBUTE_ENUMERATION | Attribut-Aufzählung |
XML_ATTRIBUTE_NOTATION | Attribut-Notation |
Umgang mit Elementen
Das folgende Skript zeigt, wie der Zugriff erfolgen kann.
<html>
<head>
<title>News-Artikel erfassen</title>
</head>
<body>
<?php
$dom = new DomDocument();
$dom->load("data/articles.xml");
## Sequentielles Durchlaufen aller Elemente mit dem Namen "item".
foreach ($dom->getElementsByTagName("item") as $articles)
{
## Für jedes Element werden alle Kindelemente durchlaufen.
foreach ($articles->childNodes as $item)
{
## Filterung der Elementknoten. Normalerweise sind in der Testdatei
## keine anderen Knotentypen, allerdings können Texte oder
## Kommentare eingetragen worden sein, die ebenfalls Knoten sind.
if ($item->nodeType == XML_ELEMENT_NODE)
{
## Suche der Elemente "title" und "link" und Umsetzung ihres
## Inhalts für die Ausgabe.
switch ($item->nodeName)
{
case "title":
echo "<b>Knoteninhalt:</b> {$item->textContent}";
break;
case "link":
printf(" <a href=\"%s\">Mehr...</a><br />\n", $item->textContent);
}
}
}
}
?>
</body>
</html>
|
Knoteninhalt: Bush unterzeichnet Rettungsplan Mehr... |
XML mit DOM schreiben
Zum Schreiben einer XML-Datei werden die Methoden "saveXML" oder "save" der Klasse "DomDocument" benutzt.
- "saveXML" erzeugt eine Zeichenkettenausgabe des Dokuments, die zur Anzeige oder zum Schreiben mit Stream- oder Dateifunktionen genutzt werden kann.
- "save" schreibt direkt in die angegebene Datei.
Das folgende Beispiel erweitert die oben verwendete Datei um einen über ein Formular erfassten Datensatz und gibt diesen direkt als XML an den Webbrowser aus, wenn das Formular abgeschikt wurde. Das Skript besteht aus vier Teilen:
- Zugriff auf die Datenquelle
- Erfassen neuer Daten, wenn das Formular abgeschickt wurde
- Ausgabe des neuen Zustands
- Formular
<html>
<head>
<title>News-Artikel erfassen</title>
</head>
<body>
<?php
## Die angegebene Datei muss schreibbar sein
define ("PATH", "data/articles.xml");
$dom = new DomDocument();
$dom->load(PATH);
## Es folgen einige Fehlerabfragen, die sich leicht erweitern lassen,
## um zu prüfen, ob der Benutzer plausible Daten ins Formular
## eingetragen hat.
if (isset($_POST['Submit']))
{
$error = FALSE;
if (isset($_POST['Titel']))
{
$title = $_POST['Titel'];
}
else
{
$error = TRUE;
}
if (isset($_POST['Link']))
{
$link = $_POST['Link'];
}
else
{
$error = TRUE;
}
if (isset($_POST['Text']))
{
$text = $_POST['Text'];
}
else
{
$error = TRUE;
}
## Wenn keine Fehler auftraten
if (!$error)
{
## Die Struktur der RSS-Datei verlangt folgenden Aufbau des Artikels:
## <item>
## <title></title>
## <link></link>
## <description></description>
## </item>
## Elemente können grundsätzlich nur auf Ebene des Dokuments erzeugt
## werden. Wenn sie existieren, können sie irgendwo angehängt werden.
## Begonnen wird mit dem Element "item".
$newItem = $dom->createElement("item");
## Erzeugen des Elements "title".
$newTitle = $dom->createElement("title");
## Zum Element "title" wird ein Textknoten hinzugefügt, der die
## Daten aus dem Formular aufnimmt.
$newTitle->appendChild($dom->createTextNode($_POST['Titel']));
## Anhängen des Elements "title" an das Element "item".
$newItem->appendChild($newTitle);
## Erzeugen des Elements "link".
$newLink = $dom->createElement("link");
$newLink->textContent = $_POST['Link'];
$newLink->appendChild($dom->createTextNode($_POST['Link']));
$newItem->appendChild($newLink);
## Erzeugen des Elements "description".
$newDesc = $dom->createElement("description");
$newDesc->textContent = $_POST['Text'];
$newDesc->appendChild($dom->createTextNode($_POST['Text']));
$newItem->appendChild($newDesc);
## Beschaffen des Hauptelements "channel", am einfachsten durch
## Zugriff auf die Kollektion aller Elemente mit diesem Namen.
$channels = $dom->getElementsByTagName("channel");
## Da das Hauptelement "channel" nur einmal vorhanden ist, wird aus
## der Kollektion das erste Element ("item(0)") entnommen und
## diesem das Element "item" sofort angefügt.
$channels->item(0)->appendChild($newItem);
## Öffnen eines Schreibzugriffs
$fh = fopen (PATH, "w");
## Das erweiterte Dokument wird in den Schreibzugriff geschrieben.
fwrite($fh, $dom->saveXML());
fclose($fh);
}
}
## Ausgabe der Titel
echo "vorhandene Titel:<hr>\n";
## Sequentielles Durchlaufen aller Elemente mit dem Namen "item".
foreach ($dom->getElementsByTagName("item") as $articles)
{
## Für jedes Element werden alle Kindelemente durchlaufen.
foreach ($articles->childNodes as $item)
{
## Filterung der Elementknoten. Normalerweise sind in der Testdatei
## keine anderen Knotentypen, allerdings können Texte oder
## Kommentare eingetragen worden sein, die ebenfalls Knoten sind.
if ($item->nodeType == XML_ELEMENT_NODE && $item->nodeName == "title")
{
echo "<b>Titel:</b> {$item->textContent}<br />";
}
}
}
?>
<!-- Formular zur Erfassung -->
<form action="<?=$_SERVER['PHP_SELF']?>" method="post">
<table>
<tr>
<td>Titel</td>
<td><input type="text" name="Titel" /></td>
</tr>
<tr>
<td>Link</td>
<td><input type="text" name="Link" /></td>
</tr>
<tr>
<td>Text</td>
<td><input type="text" name="Text" /></td>
</tr>
<tr>
<td> </td>
<td><input type="submit" value="Senden" name="Submit"/></td>
</tr>
</table>
</form>
</body>
</html>
|
Attribute verwenden
Attribute sind wie Elemente, Text oder andere Teiles des XML-Baumes Knoten. Sie werden mit der Methode "setAttribute" der Klasse "DomElement" erzeugt bzw. gesetzt und mit "getAttribute" gelesen. Um das Attribut als Knotenobjekt weiterzuverarbeiten (die zugrundeliegende Klasse ist dann "DomAttr") bietet sich "getAttributeNode" an.
Erweiterung der Klassen
Das Erzeugen einzelner Elemente mit den Methoden "createElement" und "appendChild" ist zwar recht einfach, in der Masse der möglicherweise benötigten Elemente aber ausgesprochen lästig. Fast immer lohnt es sich, eine spezialisierte Klasse zu erstellen, welche diese Arbeiten übernimmt. Statt die Funktionen einfach nur in einer Methode zu kapseln, ist eine Erweiterung der Grundklassen besser. Die erweiterte Klasse wird dann nämlich den Regeln der Grundklasse folgen und muss nicht oder nur teilweise dokumentiert werden.
Am einfachsten ist die Erweiterung der Grundklasse "DomDocument". Das folgende Beispiel zeigt eine universellere und kompaktere Schreibweise des vorangehenden Beispiels.
<?php
## Die neue Klasse "Article" erbt direkt von der Klasse "DomDocument".
class Article extends DomDocument
{
## Wichtig ist, dass der originale Konstruktor aufgerufen wird.
public function __construct()
{
parent::__construct();
}
public function addArticle($title, $link, $text)
{
$newItem = $this->createElement('item');
$newTitle = $this->createElement('title');
$newTitle->appendChild($this->createTextNode($title));
$newItem->appendChild($newTitle);
$newLink = $this->createElement('link');
$newLink->appendChild($this->createTextNode($link));
$newItem->appendChild($newLink);
$newDesc = $this->createElement('description');
$newDesc->appendChild($this->createTextNode($text));
$newItem->appendChild($newDesc);
$channels = $this->getElementsByTagName('channel');
$channels->item(0)->appendChild($newItem);
}
}
?>
<html>
<head>
<title>News-Artikel erfassen</title>
</head>
<body>
<?php
## Angegebene Datei muss schreibbar sein
define ("PATH", "data/articles.xml");
## Nach Instanziierung der Klasse "Article" steht ein Objekt bereit,
## das sich nicht von "DomDocuemnt" unterscheidet.
$dom = new Article();
$dom->load(PATH);
## Hinzufügen eines neuen Titels
if (isset($_POST['Submit']))
{
$error = FALSE;
if (isset($_POST['Titel']))
{
$title = $_POST['Titel'];
}
else
{
$error = TRUE;
}
if (isset($_POST['Link']))
{
$link = $_POST['Link'];
}
else
{
$error = TRUE;
}
if (isset($_POST['Text']))
{
$text = $_POST['Text'];
}
else
{
$error = TRUE;
}
if (!$error)
{
$dom->addArticle($_POST['Titel'], $_POST['Link'], $_POST['Text']);
$dom->save(PATH);
}
}
## Ausgabe der Titel
echo "Vorhandene Titel:<hr>\n";
foreach ($dom->getElementsByTagName("item") as $articles)
{
foreach ($articles->childNodes as $item)
{
if ($item->nodeType == XML_ELEMENT_NODE && $item->nodeName == 'title')
{
echo "<b>Titel:</b> {$item->textContent}<br />";
}
}
}
## Formular zur Erfassung
?>
<form action="<?=$_SERVER['PHP_SELF']?>" method="post">
<table>
<tr>
<td>Titel</td>
<td><input type="text" name="Titel" /></td>
</tr>
<tr>
<td>Link</td>
<td><input type="text" name="Link" /></td>
</tr>
<tr>
<td>Text</td>
<td><input type="text" name="Text" /></td>
</tr>
<tr>
<td> </td>
<td><input type="submit" value="Senden" name="Submit"/></td>
</tr>
</table>
</form>
</body>
</html>
|
Prüfung von XML-Dateien
Zum Umgang mit XML gehört auch die Prüfung von XML-Dateien. Dazu können grundsätzlich drei Verfahren benutzt werden.
- DTD: Das klassische Verfahren mit der Document Type Definition. Diese Definition beschreibt die zulässigen Strukturen einer XML-Seite.
- XSD: Das neue XML-Schema ist umfassend, selbst in XML implementiert und sehr gut für Daten geeignet, wie sie in relationalen Datenbanken vorkommen.
- RelaxNG: Da XSD einigen zu komplex war, wurde eine vereinfachte Fassung entwickelt, die sich jedoch nicht durchsetzte.
Angenommen, es wurde eine Instanz der Klasse "DomDocument" mit dem Namen "$doc" erstellt, so kann die Überprüfung auf eine der drei folgenden Arten durchgeführt werden.
$doc->validate("checkit.dtd");
$doc->schemaValidate("checkit.xsd");
$doc->relaxNGValidate("checkit.rng");
|
Grundsätzlich geben alle drei Methoden "TRUE" oder "FALSE" zurück. Im Fehlerfall wird ausserdem eine Warnung ausgegeben. Das ist allerdings fatal, da man damit wenig anfangen kann. Das folgende Skript fängt diese Meldung ab und zeigt den Fehler gezielt und damit kontrolliert an.
<?php
## Aktivierung der internen Fehlerverfolgung.
ini_set("track_errors", 1);
## Laden des Dokuments.
$dom = new DomDocument();
$dom->load("data/lib.xml");
## Validierung des Dokuments.
if (@$dom->schemaValidate("data/lib.xsd"))
{
echo "OK";
}
else
{
## Tritt nun ein Fehler auf, so wird dieser gezielt ausgegeben.
echo "Fehler: " . $php_errormsg;
}
?>
|
|
Das Schema verlangt bei diesem Test, dass innerhalb eines Elements mit dem Namen "<book>" ein Element mit dem Namen "<isbn>" auftauchen muss. Um einen Fehler zu provozieren, kann stattdessen "<ISBN>" in die Datei "lib.xml" geschrieben werden.
Die verwendete Datei "lib.xml" sieht wie folgt aus.
<?xml version="1.0" ?>
<library xmlns="http://tempuri.org/lib.xsd">
<book>
<isbn>3-334-17548-9</isbn>
<title>Das blaue Buch</title>
<keywords />
<stock>1</stock>
<lendout>3</lendout>
</book>
<book>
<isbn>3-7493-8934-0</isbn>
<title>Webserverprogrammierung</title>
<keywords />
<stock>3</stock>
<lendout>5</lendout>
</book>
<book>
<isbn>1-74749-3932-3</isbn>
<title>Der PC</title>
<keywords>pc, computer</keywords>
<stock>2</stock>
<lendout>0</lendout>
</book>
<book>
<isbn>1-3954-654965-4</isbn>
<title>Neues Buch</title>
<keywords>buch</keywords>
<stock>2</stock>
<lendout>0</lendout>
</book>
</library>
|
Die verwendete Datei "lib.xsd" sieht wie folgt aus.
<?xml version="1.0" ?>
<xs:schema id="library" targetNamespace="http://tempuri.org/lib.xsd"
xmlns="http://tempuri.org/lib.xsd" xmlns:xs="http://www.w3.org/2001/XMLSchema"
attributeFormDefault="qualified" elementFormDefault="qualified">
<xs:element name="library">
<xs:complexType>
<xs:choice maxOccurs="unbounded">
<xs:element name="book">
<xs:complexType>
<xs:sequence>
<xs:element name="isbn" type="xs:string" minOccurs="1" maxOccurs="1" />
<xs:element name="title" type="xs:string" minOccurs="1" />
<xs:element name="keywords" type="xs:string" minOccurs="0" />
<xs:element name="stock" type="xs:string" minOccurs="0" />
<xs:element name="lendout" type="xs:string" minOccurs="0" />
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:choice>
</xs:complexType>
</xs:element>
</xs:schema>
|