ASPNL logo (1 kb)
vrijdag 22 augustus 2008




Microsoft MVP

.NET Codewise Community
<< vorige | overzicht | volgende >>

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.

<< vorige | ^ naar boven | overzicht | volgende >>
copyright 2000-2007 ASPNL