Indirektionen

Dass ein XML-Dokument sich über mehrere Dateien verschiedenen Orten erstrecken kann, ist Insidern durchaus bekannt. Doch wegen der damit verbundenen Komplexität schrecken sogar diese oft vor der Anwendung von „externen Entitäten“ zurück. Eine kleine Hilfestellung.

vorlesen Druckansicht 2 Kommentare lesen
Lesezeit: 8 Min.
Von
  • Oliver Fischer
Inhaltsverzeichnis

Einer der meist nicht besonders hervorgehobenen Vorteile der Extensible Markup Language ist die Aufhebung der Einheit von Datei und Dokument: Ein XML-Dokument muss nicht identisch mit einer bestimmten Datei im Dateisystem sein, sondern kann sich ĂĽber viele physische Speichereinheiten erstrecken, die sich letztendlich nicht einmal alle auf einem bestimmten Rechner befinden mĂĽssen.

Wie stellt sich das praktisch dar? Betrachtet man den Anfang einer nicht untypischen XML-Datei, so sieht dieser meist aus wie in Listing 1.

Mehr Infos

Listing 1

<?xml version='1.0' ?>
<!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN"
"http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd"
[<!ENTITY kap1 SYSTEM "http://utopia.uto/morus.xml">]
>
<book>&kap1;</book>

Die Deklaration des Dokumententyps in der zweiten Zeile weist mittels PUBLIC den öffentlichen Bezeichner mit anschließendem Systemliteral aus. Was sich in der Spezifikation des W3C noch formaler liest, ist im Grunde nichts anderes als der Name der verwendeten DTD und deren „Adresse“. Zusätzlich wurde noch kap1 als externe Entität deklariert, eingebunden in das Element <book>. Somit erfüllt dieses kleine XML-Dokument alles vorher Gesagte - ein logisches Dokument, dessen einzelne Bestandteile auf verschiedene Quellen im Netz verteilt sind.

Validierende Parser können diese Informationen der Dokumentendeklaration nutzen, um die dem Dokument zu Grunde liegende DTD zu laden und externe Quellen in das Dokument einzubinden. Allerdings sagt ein Name noch nichts über eine Adresse aus, und eine zu einem Zeitpunkt gültige Adresse muss keineswegs für immer erreichbar oder gültig sein. Es können also die gleichen Probleme auftreten wie im richtigen Leben. Man hat die Adresse, sie stimmt aber nicht mehr.

Dazu zwei mehr oder weniger fiktive Beispiele zur Verdeutlichung. Kollege Paschulke legt im CVS-Archiv ein XML-Dokument ab, dessen Systemliteral auf c:/paschi/foobar.dtd verweist. Sofern ein anderer Mitarbeiter nun dieses Dokument aus dem Archiv holt, kann er bei dessen Verarbeitung Schwierigkeiten bekommen, da mit hoher Wahrscheinlichkeit sein Arbeitsverzeichnis anders heiĂźt als das vom Kollegen Paschulke.

Ein anderes Szenario ist der Abruf von Daten als XML-Dokument über eine externe Schnittstelle wie einen Webserver, auf dem auch die entsprechende DTD bereitsteht. Um das Dokument zu verarbeiten, muss ein Parser diese zusätzlich über das Netz laden, was ebenfalls Zeit kostet. Zudem ist nicht garantiert, dass die DTD noch am genannten Ort vorhanden ist oder der Parser beispielsweise Zugriffe via HTTP-Proxy unterstützt und nicht an der firmeneigenen Feuerwand scheitert.

Obwohl vielleicht etwas theoretisch, kommt erschwerend hinzu, dass als Wert für das Systemliteral zwar meist eine URL angegeben wird, es aber eigentlich als URI definiert ist, als Uniform Resource Identifier im Gegensatz zum Unified Resource Locator also. Wie die Bezeichnung vermuten lässt, ist im Gegensatz zu einer URL ein URI nur ein eindeutiger Bezeichner, der nicht zwingend eine direkte physische Entsprechung hat [RFC 2396].

Es muss also noch andere Wege geben, um die Abbildung aller Entitäten auf existierende Ressourcen sicherstellen zu können. Und in der Tat existieren, abhängig von den verwendeten Werkzeugen und Sprachen, verschiedene Möglichkeiten, dem Parser helfend unter die Arme zu greifen.

Sun hat bei der Spezifikation der Java API for XML Processing (JAXP) den für die Auflösung externer Entitäten und URIs zuständigen Teil in zwei eigene Interfaces ausgelagert. Der EntityResolver übernimmt diese Aufgabe für die Verarbeitung von XML-Daten, der URIResolver stellt vergleichbare Funktionen für XSL-Transformatoren wie Saxon und Xalan bereit, da es auch hier mittels Anweisungen wie xsl:include oder xsl:import möglich ist, externe Quellen durch einen URI zu referenzieren.

Für Java-basierte Programme ist die einfachste Möglichkeit, das Wiederfinden von DTDs oder deren Fragmente sicherzustellen, sie als zusätzliche Ressourcen in die verteilten JAR-Archive aufzunehmen. Alternativ kann man das Verzeichnis, in dem sie lokal liegen, dem Klassenpfad hinzufügen und dem verwendeten Parser eine eigene EntityResolver-Implementierung übergeben, wie sie Listing 2 beispielhaft zeigt.

Mehr Infos

Listing 2

// Einfache eigene Implementierung eines EntityResolvers

class MeinEntityResolver implements EntityResolver {
final static String SYSTEM_ID_START = "http://firma.de/xml/dtd/";

public MeinEntityResolver() { }

public InputSource resolveEntity(String pub, String sys)
throws SAXException, IOException
{
if (sys != null && sys.startsWith(SYSTEM_ID_START)) {
String f = extrahiereDateiname(sys);
URL jarPath = getClass().getResource("/xml/dtd/intern/" + f);
return jarPath != null
? new InputSource(jarPath.toString())
: null;
} else return null;
}

private String extrahiereDateiname(String systemId)
{
int lp = systemId.lastIndexOf('/');

return systemId.substring(++lp);
}
}

Übergibt man eine Instanz dieser Klasse via setEntityResolver() an einen XML-Parser, der das JAXP-Parser-Interface implementiert, gehen an sie alle Anforderungen von Entitäten. Dazu übergibt der Parser als ersten Parameter den öffentlichen Bezeichner und als zweiten das Systemliteral. Das Beispiel geht vereinfachend davon aus, dass die Systemliterale aller eigenen DTDs mit einer bestimmten Zeichenkette beginnen (SYSTEM_ID_START). Ist dies nicht der Fall, ist der Rückgabewert NULL. Per Definition soll der Parser dann direkt versuchen, die referenzierte Ressource zu laden, anderenfalls den Dateinamen extrahieren und den Classloader zur Suche der benötigten Datei einschalten. Somit hat man die vollständige Kontrolle darüber, woher die benötigten Daten geladen werden.

Diese sinnvolle Aufgabenteilung haben ĂĽbrigens alle XML-Parser des Apache-Projektes ĂĽbernommen. Die oben gezeigte Quellen wĂĽrden mit dem Perl-Pedant XML::Xerces wie in Listing 3 aussehen.

Mehr Infos

Listing 3

package MeinResolver;

@ISA = qw(XML::Xerces::PerlEntityResolver);

sub new {
my $type = shift;
my $self = {};
bless $self, $type;
return $self;
}

sub resolve_entity {
my ($self, $pub, $sys) = @_;
my $file = substr($sys, rindex($sys, "/") + 1);

return XML::Xerces::LocalFileInputSource->new(qw(/pfad//) . $file);
}
package main;

use XML::Xerces;

$parser = XML::Xerces::XercesDOMParser->new();
$parser->setEntityResolver(MeinResolver->new());
$parser->parse(qw(test.xml));

All diese Vorgehensweisen setzen allerdings voraus, dass man selbst Zugriff auf die Quellen hat und diese modifizieren kann. Eine bessere Trennung zwischen Anwendung und Konfiguration wäre eine externe Definition solcher Mappings.

Zu diesem Zweck hat das OASIS-Konsortium eine spezielle Arbeitsgruppe namens „Entity Resolution“ gegründet, die einen frei implementierbaren Standard für die Definition solcher Mappings als auf XML basierter „Kataloge“ erarbeitet hat.

Ein Programm mit Unterstützung solcher Kataloge muss bei der Auflösung von Entitäten und URIs zunächst püfen, ob Kataloge verwandt und darin Vorgaben für bestimmte URIs, Systemliterale et cetera getroffen werden. Ganz neu ist diese Idee übrigens nicht, schon für SGML gab es zu diesem Zweck ein festgelegtes Katalogformat, auf dessen Erfahrungen der neue Standard aufbaut.

Einen Beispielkatalog zeigt Listing 4, mit zwei Funktionen; dem Abbilden eines Bezeichners und eines Systemliterals auf eine URI und der Ăśbersetzung einer URI fĂĽr ein Stylesheet, Bild oder Dokument in eine andere URI. (Dass ein Systemliteral auch eine URI ist, macht die Unterscheidung zwischen Systemliteral und URI eigentlich ĂĽberflĂĽssig; sie wurde aber dennoch vorgenommen.)

Mehr Infos

Listing 4

<?xml version="1.0"?>
<!DOCTYPE catalog PUBLIC "-//OASIS//DTD Entity Resolution XML Catalog V1.0//EN"
"http://www.oasis-open.org/committees/entity/release/1.0/catalog.dtd">

<catalog xmlns="urn:oasis:names:tc:entity:xmlns:xml:catalog" prefer="system">

<public
publicId="-//Norman Walsh//DTD Website V2.2//EN"
uri="file:///home/plexus/data/cm_share/website/2.3/website.dtd" />

<system
systemId="http://docbook.sourceforge.net/release/website/2.2/website-full.dtd"
uri="file:///usr/local/share/website/2.2/website-full.dtd" />

<rewriteSystem
systemIdStartString="http://docbook.sourceforge.net"
rewritePrefix="/use/local/share/docbook" />

<uri name="http://www.oasis-open.org/committees/docbook/"
uri="file:///projects/oasis/docbook/website/"/>

<rewriteURI
uriStartString="http://docbook.sourceforge.net/release/website/2.3/xsl/"
rewritePrefix="file:///home/plexus/data/cm_share/website/2.3/xsl/" />

<delegatePublic
publicIdStartString="-//OASIS//DTD DocBook EBNF"
catalog="docbook_dtd.xml" />

</catalog>

Ein Katalog beginnt immer mit dem Tag catalog und kann die folgenden Elemente enthalten:

  • public, system und rewriteSystem zur Verwaltung von Namen und Systemliteralen;
  • URI und rewriteURI als deren Entsprechung fĂĽr URIs und
  • delegate[URI|Public|System] und nextCatalog zur Organisation von Katalogdateien.

Muss nun das betreffende Programm einen externen Bezeichner auflösen, untersucht es den Katalog auf seine public-, system- und rewriteSystem-Elemente. Findet es einen public-Eintrag, dessen publicId mit dem öffentlichen Bezeichner übereinstimmt, so wird die mittels uri festgelegte URI als Ergebnis zurückgegeben. Ebenso ist das Verfahren bei Systemliteralen (Element system). Oft ist es aber zu aufwendig, für jedes Element ein separates system-Element in einen Katalog aufzunehmen. In solchen Fällen kann man mit rewriteSystem arbeiten, so es der Aufbau des Systemliterals zulässt, wobei der mit systemIdStartString übereinstimmende Anfang eines Systemliterals durch den Wert von rewritePrefix ersetzt wird. Bei URIs ist die Vorgehensweise analog, mittels uri respektive rewriteURI.

Die restlichen Elemente eines Katalogs dienen der besseren Strukturierung von großen Katalogen. Die drei verschiedenen delegate*-Elemente können genutzt werden, um ein Programm anzuweisen, die Suche in anderen Katalogen fortzusetzen, falls ein Bezeichner, Systemliteral oder eine URI mit einer bestimmten Zeichenkette beginnt.

Ein Standard ist gut, doch er braucht eine Implementierung. Java-Anwendungen, die JAXP-konform sind, können auf eine fertige des xml-commons-Projekts zurückgreifen. XalanJ, XercesJ and Saxon lassen sich neben eigenen Anwendungen damit nachträglich ausrüsten. Auch die weit verbreitete libxml2 des Gnome-Projekts unterstützt XML-Kataloge.

Die Welt ist mit XML nicht nur um ein Format reicher geworden, sondern ebenfalls um eine Indirektionsebene - externe Entitäten. Zwar sollen sich ja (fast) alle Probleme der Informatik mit einer weiteren Indirektionsebene lösen lassen, doch erhöht sich damit leider auch die Komplexität.

[1] xml-commons; xml.apache.org/commons/

[2] TC Entity Resolution; www.oasis-open.org/committees/entity/

Download der Listings (js)