Introduction
Un document XML se doit d'être bien formé. Il s'agit de
pouvoir parser le document pour en extraire l'arbre
correspondant. Mais un document XML n'est en général pas un arbre
arbitraire: il encode une donnée structurée d'un langage
particulier. Par exemple, un arbre XHTML commence obligatoirement par
une racine <html>
qui a deux fils (et uniquement deux fils)
<head>
et <body>
. Il s'agit de la grammaire (ou du
schéma) du langage XHTML.
Un schéma décrit donc
- Un vocabulaire: ensemble de noms de balises et d'attributs
- Une structure: comment les choses sont agencées les unes aux autres.
Il existe plusieurs système de descriptions de schémas, plus ou moins expressifs. Lors de la création d'un schéma avec un système de description particulier, on essaie de s'approcher autant que possible du langage souhaité, dans la mesure des possibilités offertes par le système de description.
Dans la suite de ce tutoriel, nous allons voir les systèmes
- DTD
- XML Schema
DTDs
DTD est l'abréviation de "DocType Definition".
Il s'agit du système originel, développé conjointement avec SGML, qui décrit essentiellement un vocabulaire et qui permet une description de structure d'arbre en utilisant des expressions régulières.
Syntaxe d'une DTD
Il s'agit d'une suite de balises particulières sous la forme
<!BALISE paramètres>
Chaque balise définit un mot du vocabulaire. Les balises utilisées sont
<!ELEMENT ... >
pour déclarer un noeud de l'arbre XML<!ATTLIST ... >
pour déclarer les attributs possible d'un élément<!ENTITY ... >
pour déclarer des abbréviations
Les éléments
Contenu vide
<!ELEMENT nomÉlément EMPTY>
Contenu quelconque
<!ELEMENT nomÉlément ANY>
Contenu textuel (parsed character data)
<!ELEMENT nomÉlément (#PCDATA)>
Contenu composé de sous-éléments : Expression rationnelle qui définit l'organisation des sous-éléments
<!ELEMENT nomÉlément ((fils1,fils2)*|fils3?,fils4+)>
Contenu mixte (cas particulier des précédents)
<!ELEMENT nomÉlément (#PCDATA|fils1*)>
Expressions rationelles
La syntaxe utilisée pour décrire le contenu d'un élément utilise:
- Une séquence: "," (virgule)
- Une alternative: "|"
- Une occurence quelconque (0 ou plus): "*"
- Une occurence quelconque (1 ou plus): "+"
- 0 ou 1 occurence: "?"
Par exemple, pour décrire un format d'adresse:
<!ELEMENT société (#PCDATA)> <!ELEMENT boite_postale (#PCDATA)> <!ELEMENT ville (#PCDATA)> <!ELEMENT code_postale (#PCDATA)> <!ELEMENT code_cedex (#PCDATA)> <!ELEMENT pays (#PCDATA)> <!ELEMENT adresse (société, boite_postale?, ville, (code_postal|code_cedex), pays)>
Les attributs
On associe une liste d'attributs à un élément de la façon suivante:
<!ATTLIST nomÉlément attribut1 type défaut attribut2 type défaut ... >
Le champs "défaut" peut être:
#IMPLIED | optionnel |
#REQUIRED | obligatoire |
#FIXED 'valeur' | valeur imposée |
'valeur' | valeur par défaut |
Le champs "type" peut être:
CDATA
: type "chaîne de caractères"
<!ATTLIST dossier chemin CDATA #IMPLIED>
- Valeurs énumérées
<!ATTLIST Date mois (Janvier | Février | … | Décembre) #REQUIRED>
ID
: identifiant XML unique dans le document
<!ATTLIST dossier reference ID #REQUIRED>
IDREF
,IDREFS
: référence à un(des) attribut(s) de type ID
<!ATTLIST fichier dossier IDREF #REQUIRED>
NMTOKEN
,NMTOKENS
: identifiant(s) XML
Les abbréviations (ENTITY
)
Deux types d'abbréviations principales :
- Celles utilisées dans le document XML:
&abbrev;
- Celles utilisées pour l'écriture de la DTD
Le premier type d'abbréviation a comme format:
<!ENTITY moi "Benoît Valiron">
On l'utilise comme "'
", "<
"... (qui sont d'ailleurs définis exactement comme ça). Dans un document XML, on peut alors faire:
<texte>Je m'appelle &moi;</texte>
Par exemple, que produit ce document XML ? (Copiez ce texte dans un fichier "fichier.xml" puis ouvrez-le avec firefox)
<?xml version="1.0"?> <!DOCTYPE texte [ <!ELEMENT texte (#PCDATA)> <!ENTITY b1 "Tic "> <!ENTITY b2 "&b1; &b1;"> <!ENTITY b3 "&b2; &b2;"> <!ENTITY b4 "&b3; &b3;"> ]> <texte>&b4;</texte>
La deuxième sorte d'abbréviation est uniquement utilisée en interne dans la définition d'une DTD. Elle s'utilise comme suit;
<!ENTITY % nom_entité texte_remplacement>
Par exemple, à la place de
<!ELEMENT alphabet EMPTY> <!ATTLIST alphabet lettre (a | b | c | d) #REQUIRED>
on peut écrire
<!ENTITY % liste "(a | b | c | d) #REQUIRED"> <!ELEMENT alphabet EMPTY> <!ATTLIST alphabet lettre %liste;>
Notez comment dans ce type d'abbréviation interne à la DTD on utilise "%" en place de "&".
Inclusion dans un document XML
On peut soit inclure la spécification DTD dans un document externe, ou directemenr dans le document. On peut aussi mixer les deux.
En fichier externe
On place la DTD dans un document .dtd
et on y fait référence en
donnant le noeud racine
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE personne SYSTEM "personne.dtd"> <personne> <prenom>Gaston</prenom> <nom>Lagaffe</nom> </personne>
Le mot-clé SYSTEM indique que la DTD est locale. On peut indiquer
PUBLIC
si elle est publiée sur un site internet.
En fichier interne
<?xml version="1.0" encoding="utf-8"?> <!DOCTYPE personne [ <!ELEMENT personne (nom, prenom)> <!ELEMENT nom (#PCDATA)> <!ELEMENT prenom (#PCDATA)> <!ATTLIST personne ne CDATA #REQUIRED> <!ATTLIST personne mort CDATA #IMPLIED> ]> <personne ne="1912"> <nom>Turing</nom> <prenom>Alan</prenom> </personne>
Fichier externe étendu en interne
Utilisé pour augmenter la syntaxe localement. Par exemple pour ajouter une abbréviation:
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE personne SYSTEM "personne.dtd" [ <!ENTITY g "Gaston"> ]> <personne> <prenom>&g;</prenom> <nom>Lagaffe</nom> </personne>
Attention
Un document .dtd
n'est pas d'un document XML... Donc PAS de déclaration <?xml
en entête d'un document DTD.
Limitation
Les DTDs permettent d'exprimer des contraintes assez basiques
- Liste des éléments et de leurs attributs
- Règles de structuration des éléments
Une DTD est donc très limitée:
- Impossible de typer réellement les attributs et les champs de texte
- Il ne peut y avoir deux éléments de même nom dans deux contextes différents
- Pas d'héritage possible entre éléments: notation lourde
- Pas de prise en compte des espaces de noms (vus plus tard)
XML Schema
XML Schema, ou XSD, pour XML Schema Definition, est le
schéma standard actuel pour la validation XML, recommandé par le W3C
depuis 2001. Il permet des choses beaucoup plus riche que les DTDs, et
est basé sur une syntaxe XML. Comme il s'agit d'une application
XML,il y a un espace de nom pour le XML Schema:
http://www.w3.org/2001/XMLSchema
.
C'est une norme extrèmement riche et compliquée, qui permet de faire beaucoup: héritage, support pour multiples espaces de noms, ... Elle est aussi souple pour permettre la rédaction de schémas de façon très variée, ce qui peut aussi la rendre peu lisible (et rendre sa documentation absconse).
Dans ce tutoriel, nous allons en voir un sous-ensemble gérable. Ce sous-ensemble est néanmoins suffisamment riche pour que vous puissiez après si vous le souhaitiez approfondir sans trop de problèmes.
Format d'un document
Un document XSD a pour racine un noeud schema
, dans l'espace de
nom en question. Il est à noté que les attributs sont sans espace de
nom. Il faut donc en général mettre un préfixe. Ici, on utilisera
xs
, mais vous pouvez bien sûr choisir ce que vous voulez.
<?xml version="1.0" encoding="UTF-8" ?> <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"> ... </xs:schema>
Dans ce cas de figure, le XSD n'associe pas d'espace de nom au document XML créé. On peut si on veut le faire avec l'attribut targetNamespace
Sous la racine viennent, dans un ordre quelconque, des définitions:
- d'attributs
<xs:attribute ...>
- d'élément
<xs:element ...>
- de types dit simples:
<xs:simpleType ...>
- de types dit complexes:
<xs:complexType ...>
Il y a aussi la notion de content type, mais pour les besoins de ce tutoriel nous n'en parlerons pas. Il suffit juste de savoir qu'en général on peut s'en passer.
Lier un document XML à un XML Schema
Comme pour les DTDs, on souhaite pouvoir indiquer qu'un document XML
particulier est sensé valider une grammaire particulière donnée par
la XSD "monFichier.xsd
".
Le XML Schema est l'archétype de l'application XML: on va
ajouter à la racine les deux arguments
<racine ... xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="monFichier.xsd"> ... </racine>
Comme décrit dans le tuto XML, l'attribut xmlns:xsi
associe au préfixe xsi
l'espace de nom en argument, ici l'espace
de nom XML Schema. Le document est donc "augmenté" avec le vocabulaire
en question. L'attribut noNamespaceSchemaLocation
est donc une
composante propre des XML Schema (comme l'atteste le préfixe), et dans
ce cas de figure indique à un validateur éventuel où aller chercher le
fichier XSD.
La notion de type
L'un des problèmes avec les DTDs était leur absence de souplesse. En particulier l'impossibilité de donner une structure aux valeurs des attributs ou au texte entre les balises.
XSD permet de le faire en proposant l'utilisation de types. On a ainsi
accès à une liste de types pré-définis, comme date
, boolean
,
string
, decimal
... Mais XSD permets aussi de définir ses
propres types, pour demander une expression régulière particulière, ou
pour imposer des bornes à un entier par exemple.
Mais de façon plus générale, le type fait aussi référence aux attributs et fils potentiels d'un élément. D'où la distinction:
- Un élément est muni d'un type simple si il n'a pas d'attributs ni de fils, uniquement du texte
- Un élément est muni d'un type complexe dans le cas contraire
- Sur ce principe, un attribut ne peut avoir qu'un type simple !
Type simple et attribut
Avec un type prédéfini
Définissons un attribut de type nonNegativeInteger
. Notez qu'il
faut de fait utiliser le préfixe pour indiquer qu'on parle bien du
type préféfini. Dans ce premier cas, il s'agit d'un noeud sans fils:
<xs:attribute name="nombre" type="xs:nonNegativeInteger" />
Avec un type simple explicite
On peut aussi le faire en deux fois, en définissant d'abord un type simple (et en le nommant pour pouvoir y faire référence ensuite), puis en l'utilisant. L'exemple précédant est équivalent à
<xs:attribute name="nombre" type="typeDeNombre" /> <xs:simpleType name="typeDeNombre"> <xs:restriction base="xs:nonNegativeInteger" /> </xs:simpleType>
On définit un nouveau type simple typeDeNombre
, et on indique
qu'il est basé sur nonNegativeInteger
avec la balise
<restriction>
.
On aurait aussi pu ne pas nommer le type et directement le placer dans
la balise <attribute>
(celle-ci n'ayant alors pas d'argument
type
):
<xs:attribute name="nombre"> <xs:simpleType> <xs:restriction base="xs:nonNegativeInteger" /> </xs:simpleType> </xs:attribute>
Avec la restriction d'un type prédéfini
La balise <restriction>
peut avoir des fils pour indiquer,
justement, des restrictions. Par exemple, si on souhaite que les valeurs de l'argument soit comprises entre 42 et 84, on fera
<xs:attribute name="nombre"> <xs:simpleType> <xs:restriction base="xs:nonNegativeInteger"> <xs:minInclusive value="42"/> <xs:maxInclusive value="84"/> </xs:restriction> </xs:simpleType> </xs:attribute>
ou de façon équivalente, en deux fois avec un type nommé
<xs:attribute name="nombre" type="typeDeNombre" /> <xs:simpleType name="typeDeNombre"> <xs:restriction base="xs:nonNegativeInteger"> <xs:minInclusive value="42"/> <xs:maxInclusive value="84"/> </xs:restriction> </xs:simpleType>
Outre minInclusive
et maxInclusive
, on peut aussi spécifier
totalDigits
, length
, enumeration
, ...
Comme la liste, ou l'union d'un autre type
Un type simple peut décrire une liste d'un certain type:
<xs:attribute name="nombres"> <xs:simpleType> <xs:list itemType="xs:integer"/> </xs:simpleType> </xs:attribute>
Dans ce cas, on pourra avoir l'attribut
nombres="1 2 3 4 -2 4 100"
De façon similaire, un type union est donné par exemple avec
<xs:attribute name="temps"> <xs:simpleType> <xs:union memberTypes="xs:positiveInteger date"/> </xs:simpleType> </xs:attribute>
Dans ce cas, l'attribut "temps
" peut être soit une date, soit un
entier positif non-nul.
Type simple et élément
Comme dit plus haut, on peut aussi donner un type simple à un élément. Dans ce cas, l'élément se comporte essentiellement comme un attribut: il n'a pas de fils autre qu'un champ de texte du format décrit par le type, et surtout pas d'attributs.
Par exemple,
<xs:element name="nombre"> <xs:simpleType> <xs:restriction base="xs:positiveInteger"/> </xs:simpleType> </xs:element>
permet de spécifier
<nombre>12</nombre>
Types complexes
Les type complexes permettent de spécifier des attributs et des fils à un noeud:
<xs:element name="uneBalise"> <xs:complexType> ... </xs:complexType> </xs:element>
In type complexe a la forme
<xs:complexType mixed="true/false"> ... d'abord les attributs ... <xs:attribute name="att1" type="typ1" /> <xs:attribute name="att2" type="typ2" /> ... puis les elements ... <xs:element name="elt1" type="typ3" /> <xs:element name="elt2" type="typ4" /> ... </xs:complexType>
L'attribut mixed
indique si le noeud peut contenir un mélange de
texte et de balises. On peut donc typer quelque chose comme cela:
<p>Bonjour <em>ceci</em> est du <br /><b>contenu</b> mixte</p>
La description de l'agencement des fils se fait avec les balises
<all>
, <choice>
et <sequence>
.
<all>
Tous les éléments spécifiés doivent apparaitre exactement une fois,
quelque soit l'ordre. On peut ajuster la contrainte de l'ordre
d'apparition avec l'attribut minOccur="0"
si on veut uniquement
imposer "au plus une fois". Par exemple:
<xs:complexType> <xs:all> <xs:element name="nom" type="xs:string"/> <xs:element name="prenom" type="xs:string"/> </xs:all> </xs:complexType>
On impose que les fils de <all>
soit <element>
.
<choice>
Seule une des possibilité doit apparaitre en fils de l'élément. Par exemple:
<xs:complexType name="repas"> <xs:choice> <xs:choice> <xs:element name="entree" type="xs:string"/> <xs:element name="plat" type="xs:string"/> </xs:choice> <xs:choice> <xs:element name="plat" type="xs:string"/> <xs:element name="dessert" type="xs:string"/> </xs:choice> </xs:choice> </xs:complexType>
Les fils de <choice>
peuvent être <element>
, mais aussi
<choice>
et <sequence>
.
<sequence>
Pour spécifier une suite ordonnée de fils. Par exemple, en utilisant le type au dessus:
<xs:complexType> <xs:sequence> <xs:element name="matin" type="repas"/> <xs:element name="midi" type="repas"/> <xs:element name="soir" type="repas"/> </xs:sequence> </xs:complexType>
Nombre d'occurences
On peut spécifier dans un complexType
le nombre de fois que chaque composant doit apparaitre à l'aide des attributs maxOccurs
et minOccurs
.
Il peuvent apparaître en attribut de
<element>
<choice>
<all>
Nombre illimité d'éléments
Pour un nombre illimité d'éléments, on utilisera maxOccurs="unbounded"
.
Par exemple:
<xs:element name="listeDeNombre"> <xs:complexType> <xs:sequence> <xs:element name="unNombre" type="xs:decimal" maxOccurs="unbounded"/> </xs:sequence> </xs:complexType> </xs:element>
va valider
<listeDeNombre> <unNombre>12</unNombre> <unNombre>32</unNombre> <unNombre>545</unNombre> <unNombre>333</unNombre> </listeDeNombre>
mais aussi
<listeDeNombre> <unNombre>666</unNombre> </listeDeNombre>
et
<listeDeNombre> <unNombre>5</unNombre> <unNombre>66</unNombre> <unNombre>777</unNombre> <unNombre>8888</unNombre> <unNombre>99999</unNombre> <unNombre>10001</unNombre> <unNombre>2002</unNombre> <unNombre>303</unNombre> <unNombre>44</unNombre> <unNombre>5</unNombre> <unNombre>5</unNombre> <unNombre>77</unNombre> </listeDeNombre>