Freitag, 10. Februar 2012 |
Das Entwicklerleben könnte so einfach sein. Der Einsatz der Extensible Markup Language führt ohne weiteres Zutun des Entwicklers zu erweiterbaren Formaten. Die Realität sieht anders aus: "Zutritt für Unbefugte verboten" heißt es bei vielen Dokumentformaten. Wer keine Einladung hat, kommt nicht rein.
Mir doch egal
Die Einladung kann sehr spezifisch sein - nur Elemente aus einem bestimmten Namensraum dürfen das Dokument an der dafür vorgesehenen Stelle, dem Erweiterungspunkt, betreten - oder sehr allgemein sein - beliebige Elemente aus einem beliebigen Namensraum dürfen das Dokument an dem dafür vorhergesehenen Erweiterungspunkt betreten. Der Türsteher oder vielmehr -öffner heißt any, genauer xs:any, und funktioniert ähnlich zu dem alten Bekannten xs:anyAttribute.
Ein Beispiel: Sie, leidenschaftlicher XML-Entwickler, schreiben Ihre Einkaufsliste stets in XML:
Einkaufsliste.xml - Version #1
<?xml version="1.0" encoding="UTF-8"?>
<einkaufsliste xmlns="urn:einkaufen" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="urn:einkaufen Einkaufsliste.xsd">
<posten ware="Kaffee">
<menge einheit="Gramm" anzahl="500" />
</posten>
<posten ware="Milch">
<menge einheit="Liter" anzahl="2" />
</posten>
</einkaufsliste>
Ihr Lebens-, Ehe- oder Wohnungspartner nimmt es etwas genauer als Sie und ergänzt:
Einkaufsliste.xml - Version #2
<?xml version="1.0" encoding="UTF-8"?>
<einkaufsliste xmlns="urn:einkaufen" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:qualität="urn:qualität" xmlns:diät="urn:diät" xsi:schemaLocation="urn:einkaufen Einkaufsliste.xsd">
<posten ware="Kaffee">
<menge einheit="Gramm" anzahl="500" />
<qualität:siegel>Bio</qualität:siegel>
<qualität:siegel>Fair Trade</qualität:siegel>
</posten>
<posten ware="Milch">
<menge einheit="Liter" anzahl="2" />
<diät:bedingung>fettarm</diät:bedingung>
</posten>
</einkaufsliste>
Sie verstehen von all dem zwar nichts, aber das müssen Sie auch nicht. Der Supermarkt Ihres Vertrauens kennt sich bestens mit Gütesiegeln und fettarmen Produkten aus und weiß die Daten entsprechend zu interpretieren. Doch ihr Schema mag davon nichts wissen:
Einkaufsliste.xsd - Version #1
<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified" attributeFormDefault="unqualified" targetNamespace="urn:einkaufen" xmlns="urn:einkaufen">
<xs:element name="einkaufsliste">
<xs:complexType>
<xs:sequence>
<xs:element name="posten" type="Posten" minOccurs="1" maxOccurs="unbounded" />
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:complexType name="Posten">
<xs:sequence>
<xs:element name="menge" type="Menge" minOccurs="1" maxOccurs="1" />
</xs:sequence>
<xs:attribute name="ware" use="required" type="xs:string" />
</xs:complexType>
<xs:complexType name="Menge">
<xs:attribute name="anzahl" use="required" type="xs:positiveInteger" />
<xs:attribute name="einheit" use="optional" default="Stück" type="xs:string" />
</xs:complexType>
</xs:schema>
Lascher Umgang
Das ist ein Problem und Problemen geht man bekanntlich am besten aus dem Weg. Sie ergänzen Ihr Schema um ein entsprechendes xs:any-Element an der (vermeintlich - siehe weiter unten) richtigen Stelle:
Einkaufsliste.xsd - Version #2
<xs:complexType name="Posten">
<xs:sequence>
<xs:element name="menge" type="Menge" minOccurs="1" maxOccurs="1" />
<xs:any namespace="##other" processContents="lax" minOccurs="0" maxOccurs="unbounded" />
</xs:sequence>
<xs:attribute name="ware" use="required" type="xs:string" />
</xs:complexType>
Und haben damit für die Zukunft ausgesorgt. Der Mengenangabe dürfen jetzt beliebige und beliebig viele Elemente folgen, sofern sie aus einem fremden Namensraum stammen. Ein lascher Umgang mit (Inhalts-)Regeln finden Sie? Es kommt noch besser: der XML-Parser muss die Elemente nicht einmal validieren, wenn er nicht kann (weil keine Schema für die jeweiligen Namensräume vorhanden sind). Das sagt processContents gleich "lax".
Macht das Sinn? Ja, denn wenn ein Programm für einen Namensraum kein Schema bereithält, kennt es die Elemente aus dem Namensraum in der Regel nicht und wird sie auch nicht verarbeiten. Und wenn die Elemente ohnehin nicht verarbeitet werden, brauchen Sie auch nicht validiert zu werden.
Komplizierte Vererbungslehre
Egal, was Ihrem Partner jetzt noch einfallen mag, Sie sind gewappnet - allerdings nicht für Ihren Chef, denn der ist zwar begeistert von der Idee einer XML-basierten Einkaufsliste und möchte Sie deshalb gleich zum unternehmensweiten XML-Designer befördern, aber vorher fordert er von Ihnen eine Erweiterung, selbstverständlich in einem separaten Schema:
Termingeschäfte.xsd - Version #1
<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified" attributeFormDefault="unqualified" targetNamespace="urn:termingeschäft" xmlns="urn:termingeschäft" xmlns:einkauf="urn:einkaufen">
<xs:import namespace="urn:einkaufen" schemaLocation="Einkaufsliste.xsd" />
<xs:element name="termingeschäfte">
<xs:complexType>
<xs:sequence>
<xs:element name="order" type="Order" minOccurs="1" maxOccurs="unbounded"/>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:complexType name="Order">
<xs:complexContent>
<xs:extension base="einkauf:Posten">
<xs:sequence>
<xs:element name="datum" type="xs:date" minOccurs="1" maxOccurs="1"/>
</xs:sequence>
</xs:extension>
</xs:complexContent>
</xs:complexType>
</xs:schema>
Doch schon bevor es richtig los geht, hagelt es erste Beschwerden und zwar von Ihrem XML-Schema-Editor. Der meckert allerdings nicht über die Datei Termingeschäfte.xsd sondern über Einkaufsliste.xsd: Das Element xs:any würde das Inhaltsmodell nicht-deterministisch machen.
Was heißt hier nicht-deterministisch? Ein weiteres Beispiel:
Termingeschäfte.xml - Version #1
<?xml version="1.0" encoding="UTF-8"?>
<termin:termingeschäfte xmlns:termin="urn:termingeschäft" xmlns:einkauf="urn:einkaufen" xmlns:qualität="urn:qualität" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="urn:termingeschäft Termingeschäfte.xsd">
<termin:order ware="Toner">
<einkauf:menge anzahl="10"/>
<qualität:marke>Besser geht's nicht</qualität:marke>
<termin:datum>2007-03-10</termin:datum>
</termin:order>
</termin:termingeschäfte>
Gemäß der beiden Schemas sieht die Elementreihenfolge in termin:order wie folgt aus:
Nicht-deterministisch meint hier: das Element datum weiß nicht, wohin es gehört. Ist es ein Erweiterungselement, wie Regel 2 behauptet, oder doch ein Element, das zu Termingeschäfte.xsd gehört?
Umständliche Lösung
Der XML-Parser ist mit dieser Frage überfordert und verlangt vom XML-Entwickler eine eindeutige Lösung:
Einkaufsliste.xsd - Version #3
<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified" attributeFormDefault="unqualified" targetNamespace="urn:einkaufen" xmlns="urn:einkaufen">
<xs:element name="einkaufsliste">
<xs:complexType>
<xs:sequence>
<xs:element name="posten" minOccurs="1" maxOccurs="unbounded">
<xs:complexType>
<xs:complexContent>
<xs:extension base="Posten">
<xs:sequence>
<xs:any namespace="##other" processContents="lax" minOccurs="0" maxOccurs="unbounded" />
</xs:sequence>
</xs:extension>
</xs:complexContent>
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:complexType name="Posten">
<xs:sequence>
<xs:element name="menge" type="Menge" minOccurs="1" maxOccurs="1" />
</xs:sequence>
<xs:attribute name="ware" use="required" type="xs:string" />
</xs:complexType>
<xs:complexType name="Menge">
<xs:attribute name="anzahl" use="required" type="xs:positiveInteger" />
<xs:attribute name="einheit" use="optional" default="Stück" type="xs:string" />
</xs:complexType>
</xs:schema>
Termingeschäfte.xsd - Version #2
<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified" attributeFormDefault="unqualified" targetNamespace="urn:termingeschäft" xmlns="urn:termingeschäft" xmlns:einkauf="urn:einkaufen">
<xs:import namespace="urn:einkaufen" schemaLocation="Einkaufsliste3.xsd" />
<xs:element name="termingeschäfte">
<xs:complexType>
<xs:sequence>
<xs:element name="order" minOccurs="1" maxOccurs="unbounded">
<xs:complexType>
<xs:complexContent>
<xs:extension base="Order">
<xs:sequence>
<xs:any namespace="##other" processContents="lax" minOccurs="0" maxOccurs="unbounded" />
</xs:sequence>
</xs:extension>
</xs:complexContent>
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:complexType name="Order">
<xs:complexContent>
<xs:extension base="einkauf:Posten">
<xs:sequence>
<xs:element name="datum" type="xs:date" minOccurs="1" maxOccurs="1"/>
</xs:sequence>
</xs:extension>
</xs:complexContent>
</xs:complexType>
</xs:schema>
Termingeschäfte.xml - Version #2
<?xml version="1.0" encoding="UTF-8"?>
<termin:termingeschäfte xmlns:termin="urn:termingeschäft" xmlns:einkauf="urn:einkaufen" xmlns:qualität="urn:qualität" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="urn:termingeschäft Termingeschäft.xsd">
<termin:order ware="Toner">
<einkauf:menge anzahl="10"/>
<termin:datum>2007-03-10</termin:datum>
<qualität:marke>Besser geht's nicht</qualität:marke>
</termin:order>
</termin:termingeschäfte>
Und die Moral von der (zugegebenermaßen langen) Geschicht': ein globales xs:any - lieber nicht! Besser: das xs:any aus der globalen Typdefinition nehmen und in die lokale Elementdeklaration verschieben. Das macht zwar mehr (Tipp-)Arbeit, aber dadurch bedeutet ##other endlich das, was es eigentlich meint: ein anderer Namensraum als der Zielnamensraum des Dokumentenformats beziehungsweise des Hauptschemas. Ein positiver Nebeneffekt: Erweiterungselemente stehen immer am Ende der Liste. Das erleichtert nicht nur das Verständnis, sondern auch die Programmierung.
Einfach gemacht
Klar, man kann es sich auch einfach machen und einfach auf Erweiterungspunkte verzichten. Dann hat es eben derjenige nicht gerade einfach, der mal eben einfach zusätzliche Daten in das Format packen möchte.
Oder noch einfacher: man verzichtet einfach auf XML Schema - das ist ja ohnehin nicht gerade einfach. Und dann kann jeder einfach seine Elemente dort hinstellen, wo er will. Das ist doch mal richtig einfach.
Auch klar ist, damit hat man es sich zu einfach gemacht. Denn das Programm, das das vermeintliche einfache Format lesen muss, ist alles andere als einfach zu programmieren. Es muss einfach alle Eventualitäten berücksichtigen - auch die nicht einfachen.
Damit wird klar, man darf es sich selbst nicht zu einfach machen, will man es den anderen einfach machen. Weil einfach eben nicht einfach einfach ist.
Martin Szugat macht sich so seine Gedanken zu und über XML und er weiß: XML ist nicht einfach, aber er versucht es einfach zu machen. Falls dennoch Fragen zu XML bleiben: entwickler-forum.de. Oder bei konkreten Fragen zu dieser und anderen Folgen von Naked XML: nakedxml@entwickler.de.
In eigener Sache: In den letzten Wochen war es auffällig ruhig im Entwickler-Forum, zumindest hatte ich den Eindruck. Doch der täuscht wie so oft: Die Forum-Software hatte gewechselt und die Benachrichtigung für neue Beiträge hätte neu eingestellt werden müssen. Das habe ich nachgeholt und falls Sie auf Ihre Frage im XML-Forum immer noch keine Antwort erhalten, dann liegt es schlichtweg daran, dass auch ich mit meinem Latein, pardon XML, am Ende bin. ;)