Parser
Parser sind, wie gesagt, für die APIs von untergeordneter Bedeutung.
Es ist aber wichtig, zu wissen, dass es zwei verschiedene Atren von
Parsern gibt:
validierende und nichtvalidierende.
Ein validierender Parser liest das Dokument, die DTD und alle externen
Entitäten (salopp ausgedrückt die XML-Version von
#include
-Dateien) und berücksichtig alle darin
definierten Dafaultwerte von Arrtibuten und allgemeinen Entitäten.
Außerdem testet er, dass das Dokument tatsächlich den in der DTD
aufgestellen formalen Anforderungen entspricht (daher hat er auch
seinen Namen.
Ein nichtvalidierender Parser führt diese Tests nicht durch. Er kann aber trotzdem die DTD lesen und dort definierte Entitäten berücksichtigen, und auch externe Entitäten lesen. Diese Freiheit kann dazu führen, dass das gleiche Dokument sich einem Programm sehr verschieden präsentiert, je nachdem, mit was für einem Parser es gelesen wurde. Für den an Details interessierten Leser: Es gibt wenigstens zwei Versuche, eine Lösung für dieses Problem zu finden. Der eine ist SML, der auch durch viele andere Dinge motivierte Vorschlag, XML radikal zu vereinfachen. SML soll eine absolut minimale Teilsprache von XML werden, die praktisch nur richtig verschachtelte öffnende und schließende Tags ohne gemischten Inhalt und in Unicode-Kodierung zulässt. Der andere, Common XML, beschreibt dagegen, wie man im Rahmen des "normalen" XML diese Probleme durch geeignete Beschränkung bei der Wahl der benutzten Features umgehen kann.
Ereignisbasierte APIs -- SAX
Das Vorbild für die meisten ereignisbasierten APIs ist in Java
definierte die Simple API for XML
SAX, weshalb
ereignisbasiert und SAX-artig oft synonym gebraucht werden.
Bei einer ereignisbasierten API gibt es zwei wichtige
Programmkomponenten:
Einen Treiber der Ereignisse erzeugt, und ein Handler, der diese
Ereignisse verarbeitet.
In einen typischen Programm ist der SAX-Treiber ein XML-Parser, und
der Handler enthält die eigentliche Anwendungslogik des Programms.
Wenn der Parser dann ein Dokument liest, wird jeder Teil des Dokuments
in ein Ereignis übersetzt und an den Handler übergeben.
In objektorientierten Sprachen sin Treiber und Handler Objekte, und er
Treiber schickt dem Handler den Ereignissen entsprechende Nachrichten
(Smalltalk-Schule) bzw. ruft die entsprechenden Objektmethoden auf
(SIMULA-Schule).
In C ist der Handler einfach ein struct
mit
Funktionspointern, und der Treiber ruft die Funktionen auf.
Der Treiber muss kein Parser sein, sondern kann ein beliebiges Stück Code sein, das einem Handler die Ereignisse mitteilen kann. Es gibt zum Beispiel Multiplexer, die sich bei einem SAX-Treiber als Handler registrieren, und dann alle Ereignisse, die sie mitgeteilt erhalten, an mehrere andere Handler weiterleiten, die sich bei ihnen registriert haben. Im Prinzip kann auch eine Funktion, die direkt Ereignisse erzeugt, als Treiber auftreten, dem Handler ist das egal. Das Beispieldokument könnte etwas Vereinfacht in Perl auszugsweise so aussehen:
Aus didaktischen Gründen habe ich dabei die Daten für den Inhalt des
Ein zum ereignisbasierten Ansatz kompämentärer wird von den
baumbasierten APIs repräsentiert.
Teile eines Dokuments werden dabei nicht als Ereignisse, sondern als
Knoten in einem Baum betrachtet.
Das paradigmatische Beispiel ist das Document Object Model DOM, ein
offizieller Standard des W3C.
Bei baumbasierten APIs wird (normalerweise) das gesamte Dokument als
ein Baum im Speicher gehalten, und die API stellt Funktionen zur
Verfügung, um in diesem Baum zu navigieren und Elemente nach
bestimmten Kriterien zu suchen.
Auch bei DOM gibt es keine Gewähr dafür, dass logisch oder auch
physikalisch zusammengehörende Textabschnitte als ein einziger
Textknoten dargestellt werden.
Bis auf Kommentare sieht die Baumdarstellung des
Beispieldokuments so aus:
Das einfache Perl-Skript, das dise Grafik erstellt hat, indem es sich
rekursiv durch den Baum hangelt, ist auch ein nettes
Beispiel für die Benutzung von DOM.
Viele reale Implementationen bieten beide APIs an.
Für eine DOM-Implementation ist es oft sinnvoll, auf einem SAX-Parser
aufzusetzen und die eigenen Routinen, die aus den Ereignissen einen
Baum aufbauen, als Standardhandler zu registrieren.
Dieser kann dann von einem Benutzer, der DOM nicht benutzen will,
durch einen eigenen Handler ersetzt werden.
Umgekehrt kann auch eine DOM-Implementation als SAX-Treiber fungiern,
indem der Baum einfach rekursiv durchlaufen wird und man an jedem
Knoten die entsprechenden Ereignisse erzeugt.
Daneben gibt es aber auch noch viele andere APIs, die den Paradigmen
SAX und DOM mehr oder weniger ähnlich sind, aber unterschiedliche
Schwerpunkte bei Einfachkeit, Ressourcenbedarf oder Flexibilität
setzen.
Es ist vielleicht nicht völlig überraschend, dass es gerade bei Perl
eine große Vielfalt von APIs gibt, und mit dem Ways to
Rome-Dokument gibt es in dem Umfeld auch einen interessante
Vergleich zwischen den verschiedenen Herangehensweisen an XML.
Die meisten verbreiteten
Implementationen stehen den Standards aber recht nahe.
sub ein_SAX_Treiber {
my $handler=shift;
$handler->start_document();
$handler->start_element((Name => "sect"));
$handler->start_element((Name => "title",
Attributes => (id => "start")));
$handler->characters((Data => "Generisches Mark"));
$handler->characters((Data => "up"));
$handler->end_element((Name => "title"));
[...]
$handler->end_element((Data => "sect"));
$handler->end_document();
}
title
-Elements auf zwei Aufrufe von
characters
verteilt.
Es gibt keine Gewähr dafür, dass zusammenhängende Textabschnitte in
einem Aufruf übergeben werden, ein Treiber kann sie auch in Blöcken zu
je 16 Zeichen übergeben.
Wie oft bei XML hat auch bei SAX die Einfachheit der Implementation
Vorrang vor der Einfachheit der Benutzung.
Baumbasierte APIs -- DOM
Andere APIs