CentraleSupélecDépartement informatique
Gâteau du Glouton
3 rue Joliot-Curie
F-91192 Gif-sur-Yvette cedex
Validation XML

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:

#IMPLIEDoptionnel
#REQUIREDobligatoire
#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 "&apos;", "&lt;"... (qui sont d'ailleurs définis exactement comme ça). Dans un document XML, on peut alors faire:

<texte>Je m&apos;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>