Last update: 04.04.2007 | © 2024 Julian von Mendel | Datenschutz
Die Klasse xml stellt alle benötigten Funktionen bereit um einzelne Tags in einem XML-Dokument ggf. abhängig von ihren Kind-Elementen und Attributen durch andere Ausgaben zu ersetzen. So lassen sich theoretisch ganze XML-Dokumente in andere Formate konvertieren, aber auch einfach nur ein XML-Dialekt in einen anderen umschreiben. Ich habe die Klasse mit der Intention geschrieben, mir das Erstellen sehr großer XHTML-Dokumente zu vereinfachen, indem die Klasse einige Abkürzungen und Vereinfachungen bereitstellt. Außerdem werden die Templates in meinem Content-Management-System übersichtlicher, da dort kein Programmcode und keine Platzhalter nach frei erfundenem Schema enthalten sind, sondern normale XML-Elemente ersetzt werden, die durch ihre Attribute weitere Optionen spezifizieren können. Zusammengefasst macht die Klasse nach der Integration das Schreiben von normalem XHTML durch ihre Erweiterungen einfacher und übersichtlicher, und am Ende kommt valides, sauberes XHTML1 heraus.
Die xml-Klasse besitzt folgende Methoden:
"autoid".$this->xml->getFreeID()
kann man so im Code eines Tags sich eine freie ID erzeugen.return $this->parseNode($node, True, False, True, $attributes);
Sie ist dazu gedacht, sich innerhalb der Tags per $this->xml->attributes($this->node, array("attribut1", "attribut2"))
die gewünschten Attribute auslesen zu lassen.Es wird weiterhin das Interface «xml_widget» spezifiziert, das angibt, wie die Klassen der Tags aufgebaut zu sein haben müssen: sie müssen einen Konstruktor enthalten, dem $node und die xml-Klasse übergeben wird, eine statische dtd-Funktion enthalten, die den Teil der zum Tag gehörenden DTD zurückgibt und eine output-Funktion besitzen, die als einzigen Parameter den Rückgabetyp (z. B. «xhtml») erhält. Die output-Funktion muss dann die Ausgabe des Tags im entsprechenden Format zurückgeben.
Möchte man ein XML-Dokument verarbeiten erzeugt man sich also eine Instanz der xml-Klasse und ruft die output-Methode mit dem XML-Dokument als Parameter auf. Die Klasse prüft dann, was für Klassen existieren, die mit dem Namen «xml_» beginnen, und nimmt den anschließend folgenden Namen als Tag-Bezeichnung. Diese Elemente werden dann von der jeweiligen Klasse verarbeitet. Es werden rekursiv alle XML-Elemente verarbeitet und die «ohne Sonderbehandlung» einfach übernommen, wobei das href-Attribut und das parentattr-Kindelement mit enbezogen wird. Möchte man ein neues Tag spezifizieren muss man nichts weiter tun, als eine neue xml_TAGNAME-Klasse zu erzeugen, die den Ansprüchen des Interfaces xml_widget genügt.
Möchte man die Klasse in ein Dokument integrieren, muss man erst dessen Inhalt nicht direkt an den Browser senden, sondern per Output Buffering für die spätere Verarbeitung aufheben. Am Ende kann man dann die Klasse mit den aufgezeichneten Daten aufrufen. Macht man das in mehreren Dateien, ist es sinnvoll, die Befehle auszulagern und per include() einzubinden. Ein Beispieldokument könnte so aussehen:
<?php
ob_start();
?>
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="de">
<head>
<title>Test</title>
</head>
<body>
<p>test<seperator />bla</p>
</body>
</html>
<?php
include("xml.php");
$path = "xml/";
$tags = scandir($path);
foreach($tags as $tag)
if ($tag[0] != "." && substr($tag, -1) != "~" && !is_dir($path.$tag))
include($path.$tag);
$xml = new xml;
echo $xml->output(ob_get_clean());
?>
Hier werden die Tags aus dem Unterverzeichnis «xml» geladen, es wird angenommen dass sich die xml-Klasse im gleichen Verzeichnis wie das Dokument, das sie inkludiert befindet. Es ist abhängig von den Dateien im Unterverzeichnis, wie das XML-Dokument modifziert wird. Das Ergebnis, das an den Browser geschickt wird, sieht bei diesem Code so aus:
<?xml version="1.0" encoding="utf-8" ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="de">
<head>
<title>Test</title>
</head>
<body>
<p>test<hr />bla</p>
</body>
</html>
Damit das Beispiel funktioniert muss die «replacement.php»-Erweiterung installiert sein.
Das Eingabe-Dokument muss ein valides XML-Dokument sein, andernfalls beendet die Klasse mit einer Exception! Damit man den Fehler besser nachvollziehen kann, gibt die Klasse weiterhin das Eingabedokument mit Zeilennummern aus.
<?php
class xml_seperator extends xmlreplacement
{
public function xhtml($attributes)
{
return "<hr".$attributes." />";
}
}
?>
Der oben stehende Programmcode der xml_seperator-Klasse gibt, wenn das seperator-Tag im Dokument vorkommt <hr /> aus, wobei alle Attribute des Eingangselements übernommen werden, d. h. <seperator style="width:50%" /> führt zur Ausgabe <hr style="width:50%" />.
So kann man leicht Tags durch andere Ausgaben ersetzen lassen, was z. B. für ein Template-System hilfreich ist, bei dem an der Stelle, an der das Tag <content /> vorkommt der Inhalt eingesetzt werden soll. Per
<?php
könnte man sich das Tag durch einen anderen Inhalt ersetzen lassen, den man vor dem Aufruf der output-Funktion der xml-Klasse per
class xml_content extends xmlreplacement
{
public function xhtml($attributes)
{
$content = $this->xml->getDataItem("content");
$parse = $this->xml->parse($content);
return $parse["content_string"];
}
}
?>
<?php
verfügbar machen muss.
$xml->setDataItem("content", $lustigerinhalt);
?>
Damit das funktioniert, muss die Erweiterung «replacement.php» installiert sein.
Manchmal ist es wünschenswert, einem Element als Attribut den Wert eines anderen Elements zuzuweisen. Das geht wie folgt:
<input type="submit">
Das würde einem Submit-Button als Beschriftung den Rückgabewert des lang-Tags zuweisen, das die entsprechende Beschriftung abhängig von der Sprache aus einer Datenbank laden kann.
<parentattr name="value">
<lang key="send" />
</parentattr>
</submit>
Das «parentattr»-Element wird von der xml-Klasse selbst unterstützt, man muss keine zusätzliche Erweiterung installieren.
In einem mehrsprachigen Dokument sind zwei Punkte wünschenswert:
Beides ist mit dem lang-Tag möglich. Zuerst muss man Sprachdaten zur Verfügung stellen, die man vorher aus einer Datenbank laden kann:
<?php
Anschließend hat man die Möglichkeit, einfach auf diese Daten zuzugreifen:
$lang = array
(
"send" => array
(
"de" => "Absenden",
"en" => "Submit"
),
"reset" => array
(
"de" => "Zurücksetzen",
"en" => "Reset"
)
);
$xml->setDataItem("lang", "de");
$xml->setDataItem("lang", $lang);
?>
<ul>
Wird über das lang-Attribut die Standardeinstellung nicht explizit überschrieben, wird das DataItem «lang» als Standardsprache verwendet. Das DataItem «lang_var» muss die Keys enthalten, über die auf den jeweiligen Textabschnitt zugegriffen werden kann, jedem Key wird ein Array zugeordnet, welches als Index wiederum die Sprache verwendet und als Wert den Text in der jeweiligen Sprache. Die Daten werden nicht maskiert ausgegeben, d. h. man kann in den Texten auch XML-Tags verwenden. Schreibt man in den Texten «%s» wird das «%s» durch den Wert des Attributs «replace» ersetzt, wenn dieses angegeben ist.
<li>Englisch: <lang key="reset" lang="en" /></li>
<li>Deutsch: <lang key="reset" lang="de" /></li>
<li>Momentane Standard-Sprache: <lang key="reset" /></li>
</ul>
Um einen Text nur einzublenden, wenn die momentan eingestellte Sprache Deutsch ist, könnte man die Attribute des Tags so setzen:
<lang if="de">Hallo!</lang><lang if="en">Hello!</lang>
So wird «Hallo!» angezeigt, wenn Deutsch als Sprache eingestellt ist-
Damit dieses Tag verfügbar ist, muss die Erweiterung «lang.php» installiert sein.
Das href-Attribut habe ich von XHTML2 geklaut. Es sagt aus, dass ein Element ein Link ist, ohne ein a-Tag zu verwenden. Dann kann man z. B. sowas schreiben:
<ul>
<li href="http://google.de">Google ist böse!</li>
<li href="/witze-seite">Viele lustige Witze</li>
<li href="http://php.net">RTFM</li>
</ul>
Dieses Attribut muss von dem jeweiligen Erweiterungs-Tag unterstützt werden. Für «normale» XHTML-Elemente unterstützt die xml-Klasse das Attribut selbst bereits. Folgende standardmäßig in XHTML1 verfügbaren Tags werden unterstützt:
Das seperator-Tag gibt einfach nur ein hr aus, erzeugt also eine Trennlinie. Ich habe den Namen von XHTML2 geklaut und mag es, weil es einen sehr einfachen Programmcode hat. Es ist in der «replacements.php»-Erweiterung enthalten.
XHTML2 kennt section- und h-Tags. Mit ihnen sparrt man sich die Markierung des Überschriften-Levels über eine Nummer, man gibt das Level durch die Verschachtelung der section-Tags an. Ein mit toc="no" gekennzeichnetes h-Tag taucht nicht in der Inhaltsübersicht auf (s. U.).
<h>Interessantes Dokument</h>
<section>
<h>1. Kapitel</h>
<p>Einleitungstext</p>
<section>
<h>Unterüberschrift</h>
<p>foo</p>
</section>
</section>
<section>
<h>2. Kapitel</h>
<p>bar</p>
</section>
Diese Tags sind über die Erweiterungen «h.php» und «section.php» verteilt.
Um eine Inhaltsübersicht zu erzeugen, wie sie z. B. am Anfang dieser Dokumentation zu sehen ist, kann man einfach ein
<toc />
verwenden. Damit das geht muss die «toc.php»-Erweiterung installiert sein. Es werden jedoch nur h-Überschriften in section-Elementen dargestellt, keine normalen h1-, h2-, …-Tags
<hyperlink page="home" />
Dieser Code soll einen Link zur Startseite erzeugen und automatisch deren Seitentitel einlesen. Existiert keine «home»-Seite wird dem Link eine «error404»-Klasse zugewiesen, damit er per CSS anders formatiert werden kann (z. B. rot-Färben von Links wie in gängigen Wiki-Systemen). Damit das geht muss eine Funktion spezifiziert werden, die ermittelt, ob eine Seite existiert und eine, die den Seitentitel bestimmt:
<?php
Das page-Attribut ist für die Verlinkung zu einer internen Seite gedacht. Verwendet man mod_rewrite und möchte z. B. vor jedem Seitenpfad ein «/inhalt/» in die URL einfügen, kann man das so machen:
$seiten = array
(
"home" => "Startseite",
"witze-seite" => "Lustige Witze"
);
function title($page)
{
global $seiten;
if (!isset($seiten[$page]))
return "Kein Titel.";
return $seiten[$page];
}
function exists($page)
{
global $seiten;
return isset($seiten[$page]);
}
$xml->setDataItem("hyperlink_exists", "exists"));
$xml->setDataItem("hyperlink_title", "title"));
?>
<?php
Man kann den Titel eines Links natürlich auch nicht-automatisch bestimmen lassen:
$xml->setDataItem("hyperlink_url", "/inhalt/%s");
?>
<hyperlink page="witze-seite">Anderer lustiger Seitentitel</hyperlink>
Möchte man auf eine externe Seite verlinken, geht das so:
<hyperlink extern="http://google.de">Google</hyperlink> hat ein Monopol!
Über das anchor-Attribut kann man einen Anker festlegen, wie mit «home#anker» — schreibt man das jedoch ins page-Attribut rein, müsste die title- und exists-Funktion das erst wegfiltern, außerdem ist ein extra Attribut schöner.
<hyperlink anchor="kapitel3">Drittes Kapitel</hyperlink>
Das blank-Attribut legt fest, ob die Zielseite in einem neuen Fenster geöffnet werden soll. Damit das geht, muss erstens ein Transitional-Doctype gesetzt werden, und zweitens muss dies explizit im PHP-Code erlaubt werden:
<?php
Dies gibt die Möglichkeit, für eine komplette Internetseite das Öffnen neuer Seiten auf einmal zu konfigurieren, da ich z. B. für kommerzielle Seiten in der Regel neue Fenster öffne, für meine eigenen hingegen nicht. Mit dieser Einstellung kann ich das Öffnen neuer Seiten dann für meine gesamte Internetseite an- oder ausschalten. Setzt man blank="force" wird diese Einstellung umgangen und es wird aufjedenfall immer ein neues Fenster geöffnet.
$xml->setDataItem("hyperlink_blank", True);
?>
Wird ein Link im neuen Fenster geöffnet erhält er zusätzlich die Klasse «blank». Wird er per extern-Attribut erzeugt, ist er auch Mitglied der «extern»-Klasse. So kann man Links abhängig von ihren Eigenschaften auch schon mit CSS2 formatieren.
Die Erweiterung befindet sich in der Datei «hyperlink.php».
Dieses Tag kann Bilder ausgeben, Bildunterschriften drunter setzen und die Bilder automatisch resizen, d. h. serverseitig Thumbnails erzeugen. Folgende Attribute:
Unterscheidet sich die URL, die der Browser aufrufen soll vom lokalen Pfad, kann diese wie folgt eingestellt werden:
<?php
Statt «%s» kann man auch «%u» verwenden, welches für den per urlencode() kodierten Pfad steht. Den Pfad, in dem die Thumbnails gespeichert werden stellt man so ein:
$xml->setDataItem("image_remote", "/%s");
?>
<?php
Es wird automatisch gecached. Beispiel:
$xml->setDataItem("image_prefix", "cache/thumbnail_");
?>
<image path="lustigesbild.jpg" href="lustigesbild.jpg" text="Sehr witziges Bild" signature="signature" width="100px" height="200px" float="right" resize="scale" />
<!-- Angezeigt wird ein max. 100x200 Pixel großes Thumbnail mit der Bildunterschrift "Sehr witziges Bild", es wird links von Text umflossen und auf Klick erscheint das große Bild (href!). -->
Diese Erweiterung findet sich in der Datei «image.php».
Damit dieses Tag funktioniert muss meine image-Klasse installiert und per include() eingebunden werden!
Möchte man Edit area oder TinyMCE in seine Website integrieren, geht das mit diesen beiden Tags sehr einfach. Einfach ins form-Tag sowas einfügen:
<wysiwyg name="html" value="abc" /> <jstextarea name="code" value="abc" />
Der nötige Javascript-Code wird automatisch eingebunden. Damit das geht muss man jedoch die Dateien von TinyMCE oder Edit area in ein Verzeichnis schieben und den Klassen verraten, wo dieses denn liegt:
<?php
Die Tags werden durch normale textarea-Tags ersetzt, es wird ihnen eine eindeutige ID zugewiesen und das jeweilige Javascript eingebunden.
$xml->setProtocol("tinymce://", "/resources/javascript/tiny_mce/%s");
$xml->setProtocol("editarea://", "/resources/javascript/edit_area/%s");
?>
Die Erweiterungen befinden sich in den Dateien «jstextarea.php».und «wysiwyg.php»
<date format="d.m.Y" />
gibt das aktuelle Datum aus. Man kann das Tag auch einen Wert enthalten lassen, dieser muss als Unix-Timestamp formatiert sein. Ich verwende diese Konstruktion in meinem Templates, um das Datum der letzten Aktualisierung auszugeben:
<date format="d.m.Y"><option type="content" name="lastchange" /></date>
Das option-Tag gibt den Wert einer vorher gespeicherten Variable aus. Man kann wie folgt Daten zur Verfügung stellen:
<?php
So kann man unmittelbar Werte von Get- oder Post-Parametern ausgeben oder auf beliebige anderen Variablen zugreifen. Standardmäßig werden die Ausgaben maskiert, setzt man html="html" werden in den Variablen vorkommende Sonderzeichen nicht maskiert. (Bitte nur mit sicheren Werten machen, andernfalls wird man gegen Cross-Site-Scripting-Attacken anfällig.) Setzt man urlencode="urlencode" wird die Ausgabe vorher per urlencode() verarbeitet.
$xml->setDataItem("option" , array
(
"get" => $_GET,
"post" => $_POST,
"content" => array("lastchange" => date("U")
));
?>
Die Tags sind unabhängig voneinander und befinden sich in den Erweiterungen «date.php» und «option.php».
Man kann nicht nur mit dem option-Tag Variableninhalte ausgeben, sondern auch mit dem if-Tag andere Elemente abhängig von dem Inhalt der Variablen anzeigen oder ausblenden.
<if type="get" name="showthis" equal="yes">Hehe, ich werde nur angezeigt wenn man mich mit "?showthis=yes" aufruft.</if>
Statt dem equal-Attribut kann man auch das unequal-Attribut verwenden.
Das if-Tag befindet sich in der «if.php»-Erweiterung.
Häufig benötigt man Pfade, die sich öfters mal ändern, oder möchte bestimmte Daten von einer Funktion oder Datenbank beziehen. Dafür hab ich mir sowas ausgedacht: Per
<?php
spezifiziert man das Protokoll «res://», das eine Abkürzung für den Pfad «/resources/» darstellt. Damit missbraucht man zwar etwas den Sinn von solchen Protokollangaben, aber der Client bekommt davon ja nix mit, sondern nur sauberes XHTML ausgeliefert, mit korrekten Protokollangaben ;)
Man kann auch angeben, dass erst eine Callback-Funktion die eigentlichen Daten bestimmt:
$xml->setProtocol("res://", "/resources/%s");
?>
<?php
function protocol_file($url)
{
if (!file_exists($url))
return "#";
return file_get_contents("dateien/".basename($url));
}
$xml->setProtocol("callback:file://", "protocol_file");
?>
Mit solchen Tags muss man sehr aufpassen. Sollte jemand fremdes z. B. über ein kaputtes Gästebuch XML-Code einschleußen können, bedeutet das gleichzeitig, dass er dann beliebige Dateien auslesen könnte!
Diese Funktion wird von der xml-Klasse selbst unterstützt, es werden keine zusätzlichen Erweiterungen benötigt.
Die Klasse selbst: Quellcode anzeigen | Herunterladen
Die Erweiterungen der Klasse sollten in einem Unterverzeichnis, z. B. mit dem Namen «xml» gespeichert werden.
Wenn man eigene Erweiterungen entwickeln möchte, ist es sinnvoll, erstmal auf Basis einer anderen, einfachen Erweiterung anzufangen (z. B. option). Dazu kopiert man sich eine Datei im Verzeichnis mit den Tags, ändert den Namen der Klasse auf «xml_TAGNAME» ab. Anschließend muss man die Werte benötigter Attribute auslesen lassen, ggf. die Ausgaben der Kindelemente einlesen und sinnvolle Ausgaben an die xml-Klasse zurückgeben. Es ist sinnvoll, den Programmcode dabei in einen allgemeinen Teil und einen ausgabespezifischen Teil zu gliedern, d. h. das Verarbeiten der Parameter und Bestimmen des Inhalts in der jeweiligen output-Funkton zu erledigen, die Ausgaben für den Ausgabetyp «xhtml» hingegen in der xhtml-Funktion zusammenzubauen. Wer Fragen hat, darf gerne fragen ;)
Vor der Veröffentlichung sollte der Programmcode noch einmal durchgegangen und auf Kompatibilität mit dem bereits veröffentlichtem Code geprüft werden. Wer Interesse an den folgenden Features hat, kann mich benachrichtigen, und ich werde mich um baldige Veröffentlichung kümmern.
Das Validieren des Eingangsdokuments mit einer DTD ist vorgesehen, jedoch nicht fertig implementiert. Um die Aufgabe zu erfüllen müsste man eine Ausgangs-DTD, wie z. B. die von XHTML1, mit den Eigenschaften der zusätzlich «erfundenen» Tags und Attributen ergänzen. Dazu muss erst die dtd-Funktion jeder Tag-Klasse mit Inhalt gefüllt werden und dann eine Funktion implementiert werden, die die fertige DTD aus der Eingangs-DTD und der DTD aller erweiterten Elemente konstruiert. Wer daran Interesse hat, darf mir gerne schreiben.
© 2009 Julian von Mendel (http://derjulian.net) | Datum: 09.09.2024