XML structuren vastleggen
Door Michiel van Otegem
26 april 2007
XML heeft een vrije vorm. In principe kun je een XML document de structuur geven die je wilt, met
de elementen en attributen die je wilt. In veel gevallen is het echter veel handiger als we precies
weten hoe een XML document gestructureerd moet zijn, zodat we kunnen controleren of dit daadwerkelijk
zo is.
Als je in en applicatie met XML werkt, zullen de gegevens die opgeslagen worden in XML een bepaalde
structuur hebben en een bepaald vocabulaire gebruiken. Voor veel applicaties ligt dit XML formaat
vast, het zal niet snel gebeuren dat de structuur en/of het vocabulaire snel aangepast zullen worden.
Door de XML parser die de applicatie gebruikt op de hoogte te stellen van het verwachtte XML formaat,
kan de parser controleren of een document voldoet aan dat formaat. De parser controleert dan niet
alleen of het document aan de XML grammatica voldoet ("well-formed" is), maar ook of het document
geldig ("valid") is volgens de definitie. Hierdoor hoeft een applicatie niet meer de moeite te
nemen dit zelf te controleren, omdat een ingelezen document altijd "well-formed" en "valid" is. Is
een document niet conform het verwachtte formaat, dan wordt het document niet ingelezen, en wordt
de applicatie op de hoogte gesteld van de fout(en) in het document.
Om een document te kunnen controleren, moet er eerst een definitie zijn van het formaat. Die
definitie kan een Document Type Definitie (DTD) zijn, of een XML Schema. DTD is al sinds 1983 een
standaard die gebruikt wordt voor document definities voor de Standard Generalized Markup Language
(SGML). Omdat XML een afgeleide is van SGML is DTD ook van toepassing op XML. In tegenstelling tot
DTD is XML Schema een recente standaard. Het W3 Consortium (W3C) heeft de XML Schema standaard in
mei 2001 zogenaamde “Recommendation” status gegeven, waardoor het een officiële standaard is. De
XML standaard stamt echter uit februari 1998, waardoor tot het verschijnen van XML Schema DTD de
gangbare standaard was. Je kunt XML documenten waarvoor een DTD gedefinieerd is herkennen aan een
declaratie die er als volgt uit ziet:
1: <!DOCTYPE html
2: PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
3: "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
De oplettende geest ziet dat de bovenstaande declaratie gaat over HTML(!). Dat klopt ook want zowel
HTML als XML zijn afgeleiden van SGML. Waardoor DTD op beide van toepassing is. Dit betekent
overigens niet dat HTML een XML formaat is. XML is qua structuur strikter dan HTML, dus niet ieder
HTML document voldoet aan de grammaticale eisen van XML. Het is echter wel zo dat een XML document
dat het vocabulaire van HTML gebruikt een "valid" HTML document is. XHTML, het nieuwste HTML
formaat, is overigens wel conform de grammatica regels van XML, en kan daardoor ook als XML
ingelezen worden. De bovenstaat DTD referentie verwijst naar een van de XHTML 1.0 definities.
XML Schema
Hoewel DTD nog steeds zeer gangbaar is, heeft het enkele nadelen voor applicaties die gebruik maken
van XML. Ten eerste is DTD relatief rigide, en is het daardoor niet eenvoudig om complexe
structuren te definiëren. Ten tweede is het aantal gegevenstypen dat men kan koppelen aan een
element of attribuut zeer beperkt. DTD kent bijvoorbeeld geen datum type, wat ronduit lastig kan
zijn. Ten derde is DTD zelf geen XML, wat erg makkelijk zou zijn voor de verwerking, omdat dan
onder andere de parser ook gebruikt kan worden om de definitie in te lezen. Met deze beperkingen
in het achterhoofd is XML Schema ontwikkeld. XML Schema is zelf XML, ondersteunt allerlei
gegevenstypen en staat ook toe om zelf types te definiëren, en is bovendien zeer flexibel.
Een XML Schema document, kortweg een schema, is een XML document dat gebruik maakt van een bepaalde
namespace (XML Namespaces zijn behandeld in deel 2). Zonder deze namespace zal een parser het
document niet herkennen als een schema, dus het is erg belangrijk de namespace in te voegen.
Spelling is daarbij erg belangrijk, want heb je ook maar één letter verkeerd, dan werkt het niet.
Hoe XML Schema verder werkt, kunnen we het beste zien als we een voorbeeld XML document nemen en
daarvoor een schema te maken. De voorbeeld XML zie je in afbeelding 1.
1: <?xml version="1.0" encoding="utf-8"?>
2: <contacten>
3: <contact>
4: <naam voornaam="Peter" achternaam="Jansen" />
5: <adres>
6: <straat>Dorpsstraat</straat>
7: <huisnummer>2</huisnummer>
8: <toevoeging>C</toevoeging>
9: <postcode>1043AB</postcode>
10: <plaats>Amsterdam</plaats>
11: </adres>
12: <telefoon>020-1234567</telefoon>
13: </contact>
14: <contact>
15: <naam voornaam="Klaas" tussenvoegsel="de"
16: achternaam="Bruin" />
17: <adres>
18: <straat>Polderplein</straat>
19: <huisnummer>98</huisnummer>
20: <postcode>2132BA</postcode>
21: <plaats>Hoofddorp</plaats>
22: </adres>
23: <email>klaas@hotmail.com</email>
24: </contact>
25: </contacten>
Afbeelding 1, Voorbeeld XML voor het opslaan van contactgegevens
De contactgegevens in afbeelding 1 bestaan uit meerdere contacten, die ieder een naam en adres
hebben. Verder heeft de eerste een telefoonnummer, en de tweede een email adres. Voor het voorbeeld
mag een contact een telefoonnummer of een email hebben, maar niet allebei. Andere zaken die van
belang zijn is dat het element "toevoeging" optioneel is, en we willen uiteraard zorgen dat de
postcode, telefoonnummer en het email adres juist zijn. Het schema in afbeelding 2 zorgt dat aan
de bovenstaande eisen voldaan is.
1: <?xml version="1.0"?>
2: <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">
3:
4: <xsd:simpleType name="emailadres">
5: <xsd:restriction base="xsd:string">
6: <xsd:pattern
6: value="^[\w-]+(?:\.[\w-]+)*@(?:[\w-]+\.)+[a-zA-Z]{2,7}$" />
7: </xsd:restriction>
8: </xsd:simpleType>
9:
10: <xsd:simpleType name="huisnummer">
11: <xsd:restriction base="xsd:int">
12: <xsd:minInclusive value="1" />
13: <xsd:maxInclusive value="100000" />
14: </xsd:restriction>
15: </xsd:simpleType>
16:
17: <xsd:simpleType name="postcode">
18: <xsd:restriction base="xsd:string">
19: <xsd:pattern value="\d{4} ?[A-Z]{2}" />
20: </xsd:restriction>
21: </xsd:simpleType>
22:
23: <xsd:simpleType name="telefoonnummer">
24: <xsd:restriction base="xsd:string">
25: <xsd:pattern value="(\d{3}-\d{7})|(\d{4}-d{6})" />
26: </xsd:restriction>
27: </xsd:simpleType>
28:
29: <xsd:complexType name="naam">
30: <xsd:attribute name="voornaam" type="xsd:string" />
31: <xsd:attribute name="tussenvoegsel" type="xsd:string" />
32: <xsd:attribute name="achternaam" type="xsd:string" />
33: </xsd:complexType>
34:
35: <xsd:complexType name="adres">
36: <xsd:sequence>
37: <xsd:element name="straat" maxOccurs="1"
37: minOccurs="1" type="xsd:string" />
38: <xsd:element name="huisnummer" maxOccurs="1"
38: minOccurs="1" type="huisnummer" />
39: <xsd:element name="toevoeging" maxOccurs="1"
39: minOccurs="0" type="xsd:string" />
40: <xsd:element name="postcode" maxOccurs="1"
40: minOccurs="1" type="postcode" />
41: <xsd:element name="plaats" maxOccurs="1"
41: minOccurs="1" type="xsd:string" />
42: </xsd:sequence>
43: </xsd:complexType>
44:
45: <xsd:element name="contacten">
46: <xsd:complexType>
47: <xsd:sequence>
48: <xsd:element name="contact" minOccurs="0"
48: maxOccurs="unbounded">
49: <xsd:complexType>
50: <xsd:sequence>
51: <xsd:element name="naam" type="naam"
51: minOccurs="1" maxOccurs="1" />
52: <xsd:element name="adres" type="adres"
52: minOccurs="1" maxOccurs="1" />
53: <xsd:choice>
54: <xsd:element name="telefoon"
54: type="telefoonnummer"
54: minOccurs="1"
54: maxOccurs="unbounded" />
55: <xsd:element name="email"
55: type="emailadres"
55: minOccurs="1"
55: maxOccurs="unbounded" />
56: </xsd:choice>
57: </xsd:sequence>
58: </xsd:complexType>
59: </xsd:element>
60: </xsd:sequence>
61: </xsd:complexType>
62: </xsd:element>
63: </xsd:schema>
Afbeelding 2, XML Schema voor de XML in afbeelding 1
Het schema in afbeelding 2 begint met een xsd:schema element. Dit is het hoofdelement van ieder
schema. In het element staat de namespace referentie die nodig is voor XML Schema. Merk op dat de
namespace prefix "xsd" daaraan gekoppeld is. Dit is een conventie, maar je zou net zo goed een
andere prefix kunnen gebruiken, of zelfs geen als je dat liever hebt. De eigenlijke structuur wordt
pas aan het eind van het schema vastgelegd. Het schema begint met een aantal type definities die
later gebruikt worden. Voor de XML in afbeelding 1 is dit niet per sé nodig, omdat ieder type maar
één keer voorkomt, maar deze manier van werken is wel zo netjes, en zorgt dat je de types die je
zelf definieert kunt hergebruiken voor verschillende elementen en attributen die je definieert in
het schema. Het is overigens ook mogelijk om type definities in een apart schema op te slaan en aan
dat schema te refereren met een <xsd:include> element (zie voor meer informatie de XML Schema referentie
bij de handige links).
De eerste type definities zijn allemaal zogenaamde "simple type" definities. Simpele types zijn de
types die gebruikt kunnen worden als waarde van een attribuut, of voor de tekst waarde van een
element. Een "complex type" daarentegen is een type dat een element definieert dat attributen
en/of sub-elementen bevat. De definitie voor een email adres ziet er complex uit, maar in feite
wordt daar bepaald dat een email adres een string is die moet voldoen aan het gedefinieerde patroon.
Het patroon is een reguliere expressie die onder andere aangeeft dat een email adres een @ moet
bevatten. De reguliere expressie die gebruikt wordt voor postcode en telefoonnummer is een stuk
simpeler. De eerste geeft aan dat een postcode moet bestaan uit vier cijfers, een spatie en twee
letters. Een telefoonnummer mag bestaan uit drie cijfers, een streepje, en zeven cijfers, of vier
cijfers, een streepje, en zes cijfers. Zie de handige links voor meer handige reguliere expressies.
De definitie voor huisnummer maakt geen gebruik van reguliere expressies. Het betreffende type
geeft aan dat het gebaseerd is op het type int (een heel getal tussen – 126789675 en 126789675),
maar dat de waarde moet liggen tussen 1 en 100000.
Het schema in afbeelding 2 definieert twee complexe types, waarvan het naam type verreweg het
eenvoudigste is. Eigenlijk zegt de definitie alleen dat een naam element drie attributen kan hebben:
voornaam, tussenvoegsel, en achternaam. Alleen zijn optioneel, dus een naam element zou theoretisch
gezien ook leeg kunnen zijn. De definitie voor het adres type is beduidend ingewikkelder, en bestaat
allereerst uit een sequentie van elementen. Een sequentie is een opeenvolging van elementen,
waarbij de volgorde van wezenlijk belang is. Keer je straat en postcode om in een XML document,
dan voldoet dat document niet aan het schema. Dat hier geen attributen bij gedefinieerd worden is
overigens toeval, je zou zowel attributen als een sequentie kunnen definiëren binnen een complex
type. Van de verschillende elementen in de sequentie wordt aangegeven van welk type ze zijn. Een
aantal daarvan zijn types die binnen XML Schema bekend zijn, maar huisnummer en postcode gebruiken
de types die eerder gedefinieerd zijn. Bij ieder van de elementen staan ook de attributen
minOccurs en maxOccurs. Deze attributen geven aan hoe vaak een element mag voorkomen binnen een
element van het type adres. In de meeste gevallen is de waarde van minOccurs 0 of 1, om aan te
geven of een element verplicht is of niet. maxOccurs is over het algemeen 1 of "unbounded". In het
laatste geval mag je net zoveel elementen toevoegen als je wilt.
Na de definitie van het adres type begint de eigenlijk document definitie. Die begint altijd met
<xsd:element>, omdat ieder XML document een root element moet hebben. De definitie zegt in dit
geval dat dit het <contacten> element is, en dat dit een complex type is. Omdat dit niet een
standaard type is, wordt er geen gebruik gemaakt van een type referentie, maar wordt het type
gedefinieerd binnen de element definitie, en krijgt deze ook geen naam. Het element mag een
sequentie van contact elementen bevatten, en dat mogen er 0 of meer zijn. Ook voor het contact
element is de definitie genest in de element definitie zelf, en deze bevat naam, adres, en een
<xsd:choice> element. De elementen binnen een <xsd:choice> element zijn exclusief. Dit wil zeggen
dat je één van de elementen mag kiezen, niet meerdere. In dit geval mag dus een telefoonnummer óf
een emailadres ingevoegd worden.
Topje van de ijsberg
In dit artikel zijn alleen de basisbegrippen van XML Schema aan bod gekomen. Er is echter nog veel
meer mogelijk. Zo zijn er veel meer opties bij het definiëren van types, en kun je een schema
vergevingsgezinder laten omgaan met een document, zodat het mogelijk is om op bepaalde plaatsen
XML in te voegen in een vrije vorm. Een goed voorbeeld waarbij dit handig zou kunnen zijn, is het
Simple Object Access Protocal (SOAP). SOAP wordt gebruikt om een XML object representatie te
versturen. De structuur van het SOAP bericht (de SOAP envelope) ligt vast, en kan dus gecontroleerd
worden met een schema. De inhoud van het SOAP bericht is echter een XML structuur die niets met
SOAP te maken heeft. Je wilt dus voorkomen dat die XML niet gecontroleerd wordt, en dat kun je
(op meerdere manieren) in een schema verwerken. Wil je echt verder met XML Schema, dan loont het
de moeite eens te kijken op de referentie van het W3 Consortium (zie handige links). Je zult zien
dat XML Schema zeer krachtig is, en daardoor in de meeste XML scenario's uitstekend te gebruiken
is. Wil je een bepaald XML document controleren, dan kun je dat doen door zelf code te schrijven,
maar in sommige gevallen is dat niet nodig (bijvoorbeeld als je XML als configuratiebestand gebruikt).
In dat geval is een kleine applicatie al voldoende, zoals de XML Schema Validator (zie handige
links).
Handige links:
XML Schema Tutorial: http://www.w3.org/TR/xmlschema-0/
XML Schema Validator: http://apps.gotdotnet.com/xmltools/xsdvalidator/Default.aspx
Reguliere expressie bibliotheek: http://regxlib.com
Dit artikel is eerder verschenen in NetOpus, mei 2004.
|