Der Ablauf eines Parsevorgangs ist einfach:
Es werden zuerst sog. Handler (,,Behandler``) für bestimmte Ereignisse
definiert. Solche Ereignisse können sein:
<abschnitt titel="XML-Dokumente">
</abschnitt>
<?php echo "hello world!"; ?>
<!-- eXpat-abschnitt-Ende -->
<![CDATA[ (\$a <= \$b \&\& \$a >= \$c) ]]>
<abschnitt titel="XML-Dokumente">, so ruft er die passende, vordefinierte Funktion auf und übergibt dieser den Namen des Elements(abschnitt) sowie die Attribute (titel = XML-Dokumente).
<?xml version="1.0"?> <tutorial titel="expat"> <abschnitt titel="Einfuehrung"> <index keyword="expat" /> Text... </abschnitt> <abschnitt titel="Beispiele"> <index keyword="beispiele" /> Wie im <ref id="expat">1. Abschnitt</ref> gezeigt, ... </abschnitt> </tutorial>
// Zuerst definieren wir die Funktionen, die später auf // die diversen Ereignisse reagieren sollen /** * Diese Funktion behandelt ein öffnendes Element. * Alle Parameter werden automatisch vom Parser übergeben * * @param parser Object Parserobjekt * @param name string Name des öffnenden Elements * @param atts array Array mit Attributen */ function startElement($parser, $name, $atts) { global $html; // Die XML-Namen werden in Großbuchstaben übergeben. // Deshalb wandeln wir sie mit strtolower() in Klein- // buchstaben um. switch (strtolower($name)) { case "tutorial": // Wir fügen der globalen Variable eine Überschrift hinzu: $html .= "<h1>".$atts["TITEL"]."</h1>"; break; case "abschnitt"; $html .= "<h2>".$atts["TITEL"]."</h2>"; break; case "index": // Einen HTML-Anker erzeugen: $html .= "<a name=\"".$atts["KEYWORD"]."\"></a>"; break; case "ref": // Verweis auf einen HTML-Anker: $html .= "<a href=\"#".$atts["ID"]."\">"; break; default: // Ein ungültiges Element ist vorgekommen. $error = "Undefiniertes Element <".$name.">"; die($error . " in Zeile " . xml_get_current_line_number($parser)); break; } }
Wenn im XML-Dokument ein öffnendes Element gefunden wird, passiert folgendes:
/** * Diese Funktion behandelt ein abschließendes Element * Alle Parameter werden automatisch vom Parser übergeben * * @param parser Object Parserobjekt * @param name string Name des schließenden Elements */ function endElement($parser, $name) { global $html; switch (strtolower($name)) { case "ref": // Den HTML-Link schließen: $html .= "</a>"; break; } }
Diese Funktion schließt einen eventuell offenen HTML-Link.
/** * Diese Funktion behandelt normalen Text * Alle Parameter werden automatisch vom Parser übergeben * * @param parser Object Parserobjekt * @param text string Der Text */ function cdata($parser, $text) { global $html; // Der normale Text wird einfach an $html angehängt: $html .= $text; }
Die Funktion cdata() wird aufgerufen, wenn normaler Text im XML-Dokument gefunden wird. In diesem Fall wird dieser einfach an die Ausgabe angehängt.
// Die XML-Datei wird in die Variable $xmlFile eingelesen $xmlFile = implode("", file("tutorial.xml")); // Der Parser wird erstellt $parser = xml_parser_create(); // Setzen der Handler xml_set_element_handler($parser,"startElement","endElement"); // Setzen des CDATA-Handlers xml_set_character_data_handler($parser, "cdata"); // Parsen xml_parse($parser, $xmlFile); // Gibt alle verbrauchten Ressourcen wieder frei. xml_parser_free($parser); // Ausgabe der globalen Variable $html. print $html;
Wenn man das XML-Dokument etwas abändert und ein fehlerhaftes Element an einer beliebigen Stelle einfügt, gibt das Script einen Fehler aus und zeigt an, in welcher Zeile das falsche Element gefunden wurde.
<?xml version="1.0"?> <!DOCTYPE tutorial [ <!ENTITY auml "&auml;"> <!ENTITY ouml "&ouml;"> <!ENTITY uuml "&uuml;"> <!ENTITY Auml "&Auml;"> <!ENTITY Ouml "&Ouml;"> <!ENTITY Uuml "&Uuml;"> <!ENTITY szlig "&szlig;"> ]> <tutorial titel="expat"> <abschnitt titel="Beispiele"> <index keyword="beispiele" /> Im normalen Text können wir jetzt Umlaute verwenden: Ä ä Ü ü Ö ö Und auch ein scharfes S: ß </abschnitt> </tutorial>
Dieses Dokument ist etwas kompliziert. ä (ein ä) wird zu &auml; wenn man & wiederum auflöst, erhält man &auml; wird ä. Also den Ausgangszustand. Das ist aber gewünscht! Die Ausgabe soll nämlich in HTML gewandelt werden. Und dort verwenden wir wieder ein Entity...
Allerdings hat auch diese Methode ihre Nachteile. Zum Beispiel müssen &-Zeichen immer mit & geschrieben werden. Das ist ziemlich lästig, zum Beispiel bei Code-Beispielen:
... <tutorial ...> ... <code language="php"> if ($a && $b) { print "\$a und \$b sind nicht '0'"; } </code> ... </tutorial>
In diesem Fall kann man aber auch <![CDATA[ ... ]]> verwenden:
... <tutorial ...> ... <code language="php"> <![CDATA[ if ($a && $b) { print "\$a und \$b sind nicht '0'"; } ]]> </code> ... </tutorial>
Das Auflösen von Entities und CDATA-Abschnitten übernimmt expat.
Außerdem sollen keine globalen Funktionen/Variablen mehr benutzt werden.
Zunächst benötigen wir ein neues XML-Dokument:
<?xml version="1.0"?> <!DOCTYPE tutorial [ <!ENTITY auml "&auml;"> <!ENTITY ouml "&ouml;"> <!ENTITY uuml "&uuml;"> <!ENTITY Auml "&Auml;"> <!ENTITY Ouml "&Ouml;"> <!ENTITY Uuml "&Uuml;"> <!ENTITY szlig "&szlig;"> ]> <tutorial titel="Boolesche Werte"> <abschnitt titel="Beispiele"> Nachfolgend ein paar Beispiele zu booleschen Abfragen. <code language="php"> <![CDATA[ $a = 3; $b = 5; if ($a && $b) { print '$a und $b sind nicht "0"<br />'; } if ($a < $b) { print '$a ist kleiner als $b<br />'; } ]]> </code> Beim Ausfüren erhält man folgende Ausgabe. <?php $a = 3; $b = 5; if ($a && $b) { print '$a und $b sind nicht "0"<br />'; } if ($a < $b) { print '$a ist kleiner als $b<br />'; } ?> </abschnitt> </tutorial>
Um globale Funktionen/Variablen zu vermeiden, bleibt uns als einziger Ausweg die Verwendung einer Klasse.
class TutorialParser { var $html; // Erstellter HTML-Code function TutorialParser($file) { // Überprüfen, ob die Datei vorhanden ist: if (!file_exists($file)) { $this->error('Datei '.$file. ' kann nicht gefunden werden!'); } else { $buffer = implode('', file($file)); $p = xml_parser_create(); // Durch das Setzen dieser Option werden nicht alle // Element- und Attributnamen in Großbuchstaben // umgewandelt. xml_parser_set_option($p, XML_OPTION_CASE_FOLDING, 0); // Wichtig, daß der Parser nicht globale Funktionen // aufruft, sondern Methoden dieser Klasse. xml_set_object($p, $this); xml_set_element_handler($p, 'startElement', 'closeElement'); xml_set_character_data_handler($p, 'cdataHandler'); // Setzt den Handler für Processing Instructions. xml_set_processing_instruction_handler($p, 'piHandler'); xml_parse($p, $buffer); xml_parser_free($p); } } function startElement($parser, $name, $a) { // Wie im ersten Beispiel, allerdings wird die Klassen- // Variable $html anstelle der globalen benutzt. switch ($name) { case 'tutorial': $this->html .= '<h1>'.$a['titel'].'</h1>'; break; case 'abschnitt'; $this->html .= '<h2>'.$a['titel'].'</h2>'; break; case 'index': $this->html .= '<a name="'.$a['keyword'].'"></a>'; break; case 'ref': $this->html .= '<a href="#'.$a['id'].'">'; break; case 'code': $this->html .= '<pre>'; break; default: $error = 'Undefiniertes Element <'.$name.'>'; $line = xml_get_current_line_number($parser); $this->error($error.' in Zeile '.$line); } } function closeElement($parser, $name) { switch ($name) { case 'ref': $this->html .= '</a>'; break; case 'code': $this->html .= '</pre>'; break; } } function cdataHandler($parser, $cdata) { $this->html .= $cdata; } function piHandler($parser, $target, $data) { switch ($target) { case 'php': // Es wurde eine Codestelle gefunden, die // ausgeführt werden soll. Zuerst starten // wir den Ausgabepuffer, damit die gesamte // Ausgabe eingefangen werden kann. ob_start(); // Ausführen des PHP-Codes eval($data); // "Einsammeln" der Ausgabe $output = ob_get_contents(); // Ausgabe verwerfen ob_end_clean(); // Anhängen der Ausgabe an $html: $this->html .= '<b>Ausgabe:</b><br />'; $this->html .= $output; break; } } function error($str) { // Ausgeben einer Fehlermeldung: die('<b>Fehler:</b> '.$str); } function get() { // Gibt den erzeugten HTML-Code zurück. return $this->html; } function show() { // Gibt den erzeugten HTML-Code aus. print $this->get(); } }
Zuletzt brauchen wir noch ein kleines Script, das die Klassenfunktionen benutzt.
include_once "tp.php"; // Einbinden der Klasse $tp = new TutorialParser('tutorial.xml'); $tp->show();