Hintergrund
Du möchtest News auf deiner Seite bereitstellen und diese im XML Format bereithalten. Da die XML Datei auch von fernen Servern aus abgerufen werden kann, können andere Webmaster, die wissen, wo genau die XML Datei liegt und wie sie aufgebaut ist, deine News auf ihren Seiten einbinden - jedoch in ihrem Design.
Die Datenstruktur
Am Anfang einer XML Anwendung steht immer das Design einer Datenstruktur: "Wie soll meine XML Datei aufgebaut sein, welche Tags verschachtele ich wie und wie soll ich sie nennen?" Fehler im Design ziehen wie überall im Softwaredesign Schwierigkeiten nach sich, deswegen sollte man hier schon einen Moment überlegen. Und auch wenn PHP nichts mit einer DTD anfangen kann, man selbst muß sich auf jeden Fall auf das zuvor festgelegte Datenstruktur halten, denn sonst klappt es nie mit dem Nachbarn.
Folgende XML Datei genügt meinen Ansprüchen als Newslieferant erst einmal:
<?xml version="1.0"?>
<news>
<item>
<meta>
<author>
<name type="official">Sebastian Will</name>
<name type="nick">junx</name>
<email>junx@gmx.net</email>
</author>
<title>Schema offiziell vom W3C freigegeben</title>
<date day="02" month="05" year="2001" />
</meta>
<text>
<short>Das W3C hat am 02. Mai 2001 die Recommendation für die XML Schema freigegeben.</short>
<long>Das W3C hat am 02. Mai 2001 die Recommendation für die XML Schema freigegeben. Sie soll
die bisherigen DTD's, die bisher für die Struktur eines XML Dokumentes sorgten ablösen.</long>
</text>
</item>
</news>
Die grobe Bedeutung sollte aufgrund der sprechenden Namen der Tags eigentlich klar sein: ein item stellt eine Newsnachricht dar, die meta Informationen wie Autor mit Namen und Nickname sowie Email Adresse, den Titel der Nachricht und ihr Erstellungsdatum bilden die Hintergrundinformationen, während short einen Kurztext der News und long den zugehörigen Langtext bietet.
Event handler und Layoutüberlegungen
PHP benutzt den XML Parser Expat von James Clark ( http://www.jclark.com/xml/expat.html ), um XML Dokumente verarbeiten zu können, und wie im Einführungsartikel Parsertypen erwähnt, handelt es sich dabei um einen SAX Parser. Demnach muß ich genau die event handler definieren, die ich für meine Aufgabe benötige. In meinem Fall wäre das nur der Handler für sich öffnende (und schließende) Tags und für character data.
Die News sollen auf meiner Seite etwa so erscheinen, was natürlich nur ein grober Entwurf ist, um das Prinzip zu verdeutlichen. Jetzt mit HTML Trickkisten um sich zu werfen, würde die Leserschaft unnötigerweise verwirren:
Schema offiziell vom W3C freigegeben
Von junx
Das W3C hat am 02. Mai 2001 die Recommendation für die XML Schema freigegeben.
<table border width="250">
<tr>
<td><strong>Schema offiziell vom W3C freigegeben</strong></td>
</tr>
<tr>
<td><small>Von junx <<a href="mailto:junx@gmx.net">junx@gmx.net</a></small></td>
</tr>
<tr>
<td>Das W3C hat am 02. Mai 2001 die Recommendation für
die XML Schema freigegeben. <a>[mehr...]</a></td>
</tr>
</table>
Der Newscontainer: Die Klasse News_item
Zur einfacheren Verwaltung der einzelnen News Beiträge lege ich jeden einzelnen in ein Objekt der Klasse News_item, die einfach die relevanten Daten speichert und auf Befehl in der oben gezeigten Tabellenform ausgibt. Alle News Beiträge werden in einem array gehalten, den ich ganz am Schluß durchlaufen kann:
<?php
class News_item {
var $title;
var $author_name = null;
var $author_nick = null;
var $author_email = null;
var $created_at = null;
var $short_text = null;
var $long_text = null;
function News_item($title, $author_name, $author_nick, $author_email, $created_at, $short_text, $long_text) {
$this->title = $title;
$this->author_name = $author_name;
$this->author_nick = $author_nick;
$this->author_email = $author_email;
$this->created_at = $created_at;
$this->short_text = $short_text;
$this->long_text = $long_text;
}
function print_news() {
?>
<table border width="250">
<tr>
<td><strong>
<?php echo $this->title; ?>
</strong></td>
</tr>
<tr>
<td><small>Von
<?php echo $this->author_nick; ?>
<
<a href="mailto:
<?php echo $this->author_email; ?>
">
<?php echo $this->author_email; ?>
</a>></small></td>
</tr>
<tr>
<td>
<?php echo $this->short_text; ?>
<a>[mehr...]</a></td>
</tr>
</table>
<?php
}
}
Der Konstruktor füllt einfach die privaten Membervariablen der Klasse mit tatsächlichen Werten, die Methode print_news() bringt die Tabelle mit diesen Daten dann auf die Webseite.
Die eigentlichen Handler
Da wir einen SAX Parser benutzen, muß eine etwas kompliziertere Logik erfunden werden, damit wir unsere Newsitems aus der Datei gezogen und in den Speicher geladen bekommen. Wir haben handler für Start- und Endtags, sowie einen handler für Text zwischen den Tags, den sogenannten character data handler. Im character data handler bekommen wir nicht mit, zwischen welchen Tags wir uns gerade befinden, zu welchem Tag diese Zeichen also letztendlich gehören, das müssen wir uns selbst merken, indem wir eine entsprechende Variable im Start handler setzen.
Die Handler haben also grob beschrieben folgende Funktionalität:
Start handler: Merken des aktuellen Tags in der globalen Variable state, sowie das Zusammenbasteln des Datums. Dieses wird nur als Attribut des Tags date mitgeliefert und ist somit nur in diesem Handler verfügbar.
End handler: Erstellen eines neuen News_item Objects, wenn man auf den Tag trifft, denn dann sollte man alle nötigen Informationen zusammen haben.
Character data handler: Anhand der Variable state wird entschieden, zu welchem Tag die Daten gehören.
Globale Variablen
Bevor ein neues News_item Objekt erstellt wird, werden die relevanten Daten in globalen Variablen zwischengespeichert, die wie folgt benannt sind:
<?
$news_file = "news.xml";
$news_items = null;
$state = null;
$title = null;
$author_name = null;
$author_nick = null;
$author_email = null;
$created_at = null;
$short_text = null;
$long_text = null;
?>
$news_file verweist auf das XML Dokument, daß als Datenquelle herhalten wird, $news_items ist der Array auf News_items Objekten, der später bei der Ausgabe durchlaufen wird und $state ist die Merkvariable, die im Start handler gesetzt und im character data handler gelesen wird. Die Verwendung der anderen Variablen sollte eigentlich klar sein.
Der Start handler
<?
function start_hdl($parser, $tag, $attribs) {
global $state;
global $created_at;
$my_tag = strtoupper($tag);
$state = $my_tag;
if ($my_tag == "NAME") {
// depending on the attribute, we have the nickname or the realname of the author
$state = strtoupper($attribs["TYPE"]);
} else if ($my_tag == "DATE") {
// The second tag we're reacting on in the starting element handler.
// the others will be dealt with in the cdata handler, depending on
// the variable $state
$state = "CREATED_AT";
$created_at = $attribs["day"].".".$attribs["month"].".".$attribs["year"];
} else {
// unsupported or irrelevant tag found
// do nothing
}
}
?>
Wir bekommen den Namen des Tags mit dem Parameter $tag und dessen Attribute, sofern vorhanden, im Parameter $attribs als Array mitgeliefert. Wenn man sich unsere XML Datenstruktur ins Gedächtnis ruft, bemerkt man, daß es nur zwei Tags gibt, die mit Attributen bestückt sind: name und date. Nur diese zwei Tags muß ich im Start handler auch gezielt weiter bearbeiten, da ich nur in diesem handler an die Attribute herankomme.
Handelt es sich um den Tag name, bekommen wir nur durch das zusätzliche Attribute type heraus, ob es sich um den offiziellen (Real-) Namen oder den Spitznamen (nick) handelt, dementsprechend setzen wir den Status auch nicht auf name, sondern auf den Wert dieses Attributes.
Handelt es sich um das Datum, basteln wir uns das direkt in diesem handler zusammen. Ich habe die Datumsangabe als Sammlung von Attributen gewählt, da man so einfach das amerikanische Datumsformat abbilden kann, wenn das gewünscht sein sollte.
Der character data handler
<?
function cdata_hdl($parser, $data) {
global $state;
global $title;
global $author_name;
global $author_nick;
global $author_email;
global $created_at;
global $short_text;
global $long_text;
if (trim($data) != "") {
if ($state == "TITLE") {
$title = $data;
} else if ($state == "OFFICIAL") {
$author_name = $data;
} else if ($state == "NICK") {
$author_nick = $data;
} else if ($state == "EMAIL") {
$author_email = $data;
} else if ($state == "SHORT") {
$short_text = $data;
} else if ($state == "LONG") {
$long_text = $data;
}
}
}
?>
Da auch whitespaces zwischen den Tags als character data gelten, und ich die Tags durch Tabulatoren so eingerückt habe, daß das menschliche Auge auch etwas davon hat, werden wir auf Unmengen von character data treffen, die einfach aus einer gewissen Anzahl von Tabulatoren oder anderen whitespaces besteht. Durch ein trim($data) kann ich diese nutzlosen Informationen herausfiltern und mir so ein wenig Rechenzeit sparen.
Ansonsten bekomme ich durch die Variable $state heraus, in welchem Tag ich mich gerade befinde und kann so der entsprechenden Variable das aktuelle Datenpaket zuweisen.
Der End handler
<?
function end_hdl($parser, $tag) {
global $news_items;
global $title;
global $author_name;
global $author_nick;
global $author_email;
global $created_at;
global $short_text;
global $long_text;
$my_tag = strtoupper($tag);
// We react only if we find a , because then we have gathered enough
// data to create a new news-object in our array
if ($my_tag == "ITEM") {
$news_items[] = new News_item($title, $author_name, $author_nick, $author_email, $created_at, $short_text, $long_text);
$title = null;
$author_name = null;
$author_nick = null;
$author_email = null;
$created_at = null;
$short_text = null;
$long_text = null;
}
}
?>
Hier warte ich nur darauf, daß der Tag item sich schließt, da ich dann alle notwendigen Daten haben sollte und ich dem Array $news_items ein neues Objekt News_item mit den gesammelten Daten hinzufügen kann. Anschließend werden alle globalen Variablen zurückgesetzt, damit ich nicht durcheinanderkomme, falls in der XML Datei z.B. der Autor-Tag im zweiten News Beitrag vergessen wurde.
Zusammenfassung
Wichtig ist eigentlich nur, die Zuordnung zwischen character data und dem dazugehörenden Tag nicht zu vermasseln. Wenn man, wie ich hier, dazu eine globale Variable heranzieht, stellt das überhaupt kein Problem dar und man kann auf einfache Art und Weise auf der eigenen Seite und auf fremden Seiten News bereitstellen. Damit fremde Webseiten von den eigenen News profitieren können, sollte man den gesamten Quelltext als include-Datei weitergeben, um die Optik anzupassen, muß lediglich die Methode print_news() angepaßt werden - alles weitere läuft dann automatisch ab.
Partnerseiten
