Chapitre 3. DTD

Le rôle d'une DTD (Document Type Definition) est de définir précisément la structure d'un document. Il s'agit d'un certain nombre de contraintes que doit respecter un document pour être valide. Ces contraintes spécifient quels sont les éléments qui peuvent apparaître dans le contenu d'un élément, l'ordre éventuel de ces éléments et la présence de texte brut. Elles définissent aussi, pour chaque élément, les attributs autorisés et les attributs obligatoires.

Les DTD ont l'avantage d'être relativement simples à utiliser mais elles sont parfois aussi un peu limitées. Les schémas XML permettent de décrire de façon plus précise encore la structure d'un document. Ils sont plus sophistiqués mais plus difficiles à mettre en œuvre. Les DTD sont donc particulièrement adaptées pour des petits modèles de documents. En revanche, leur manque de modularité les rend plus difficiles à utiliser pour des modèles plus conséquents.

3.1. Un premier exemple

On reprend la petite bibliographie du fichier bibliography.xml déjà utilisée au chapitre précédent. La troisième ligne de ce fichier est la déclaration de la DTD qui référence un fichier externe bibliography.dtd. Le nom bibliography de l'élément racine du document apparaît dans cette déclaration juste après le mot clé DOCTYPE.

<!DOCTYPE bibliography SYSTEM "bibliography.dtd">

On présente maintenant le contenu de ce fichier bibliography.dtd qui contient la DTD du fichier bibliography.xml. La syntaxe des DTD est héritée de SGML et elle est différente du reste du document XML. Il n'y a pas de balises ouvrantes et fermantes. La DTD contient des déclarations d'éléments et d'attributs délimitées par les chaînes de caractères '<!' et '>'. Un mot clé juste après la chaîne '<!' indique le type de la déclaration. La syntaxe et la signification précise de ces déclarations sont explicitées dans ce chapitre.

<!ELEMENT bibliography (book)+>1
<!ELEMENT book (title, author, year, publisher, isbn, url?)>2
<!ATTLIST book key   NMTOKEN  #REQUIRED>3
<!ATTLIST book lang (fr | en) #REQUIRED>4
<!ELEMENT title     (#PCDATA)>5
<!ELEMENT author    (#PCDATA)>
<!ELEMENT year      (#PCDATA)>
<!ELEMENT publisher (#PCDATA)>
<!ELEMENT isbn      (#PCDATA)>
<!ELEMENT url       (#PCDATA)>

1

Déclaration de l'élément bibliography devant contenir une suite non vide d'éléments book.

2

Déclaration de l'élément book devant contenir les éléments title, author, …, isbn et url.

3 4

Déclarations des attributs obligatoires key et lang de l'élément book.

5

Déclaration de l'élément title devant contenir uniquement du texte.

3.2. Déclaration de la DTD

La déclaration de la DTD du document doit être placée dans le prologue. La DTD peut être interne, externe ou mixte. Elle est interne si elle est directement incluse dans le document. Elle est externe si le document contient seulement une référence vers un autre document contenant la DTD. Elle est finalement mixte si elle est constituée d'une partie interne et d'une partie externe.

Une DTD est généralement prévue pour être utilisée pour de multiples documents. Elle est alors utilisée comme DTD externe. En revanche, il est pratique d'inclure directement la DTD dans le document en phase de développement. La déclaration de la DTD est introduite par le mot clé DOCTYPE et a la forme générale suivante où root-element est le nom de l'élément racine du document.

<!DOCTYPE root-element ... >

Le nom de l'élément racine est suivi du contenu de la DTD dans le cas d'une DTD interne ou de l'URL du fichier contenant la DTD dans le cas d'une DTD externe.

3.2.1. DTD interne

Lorsque la DTD est incluse dans le document, sa déclaration prend la forme suivante où son contenu est encadré par des caractères crochets '[' et ']'.

<!DOCTYPE root-element [ declarations ]>

Les déclarations declarations constituent la définition du type du document. Dans l'exemple suivant de DTD, le nom de l'élément racine est simple. La DTD déclare en outre que cet élément ne peut contenir que du texte (Parsed Characters DATA) et pas d'autre élément.

<!DOCTYPE simple [
  <!ELEMENT simple (#PCDATA)>
]>

3.2.2. DTD externe

Lorsque la DTD est externe, celle-ci est contenue dans un autre fichier dont l'extension est généralement .dtd. Le document XML se contente alors de donner l'adresse de sa DTD pour que les logiciels puissent y accéder. L'adresse de de la DTD peut être donnée explicitement par une URL ou par un FPI (Formal Public Indentifier). Les FPI sont des noms symboliques donnés aux documents. Ils sont utilisés avec des catalogues qui établissent les correspondances entre ces noms symboliques et les adresses réelles des documents. Lorsqu'un logiciel rencontre un FPI, il parcourt le catalogue pour le résoudre, c'est-à-dire déterminer l'adresse réelle du document. Les catalogues peuvent contenir des adresses locales et/ou des URL. Ils constituent donc une indirection qui facilite la maintenance. Lorsqu'un document, une DTD par exemple, est déplacé, il suffit de modifier les catalogues plutôt que tous les documents qui référencent le document.

3.2.2.1. Adressée par FPI

Les FPI (Formal Public Identifier) sont des identifiants de documents hérités de SGML. Ils sont plutôt remplacés en XML par les URI qui jouent le même rôle. Ils sont constitués de quatre parties séparées par des chaînes '//' et ils obéissent donc à la syntaxe suivante.

  type//owner//desc//lang

Le premier caractère type du FPI est soit le caractère '+' si le propriétaire est enregistré selon la norme ISO 9070 soit le caractère '-' sinon. Le FPI continue avec le propriétaire owner et la description desc du document. Cette description est formée d'un mot clé suivi d'un texte libre. Ce mot clé est DTD pour les DTD mais il peut aussi être DOCUMENT, ELEMENTS ou ENTITIES. Le FPI se termine par un code de langue lang de la norme ISO 639. Lorsque le document appartient à une norme ISO, le premier caractère '+' ainsi que la chaîne '//' suivante sont supprimés. Des exemples de FPI sont donnés ci-dessous.

  -//W3C//DTD XHTML 1.0 Strict//EN
  -//OASIS//DTD Entity Resolution XML Catalog V1.0//EN
  ISO/IEC 10179:1996//DTD DSSSL Architecture//EN

Un FPI peut être converti en URI en utilisant l'espace de noms publicid des URN et en remplaçant chaque chaîne '//' par le caractère ':' et chaque espace par le caractère '+' comme dans l'exemple ci-dessous.

  urn:publicid:-:W3C:DTD+XHTML+1.0+Strict:EN

La déclaration d'une DTD externe peut utiliser un FPI pour désigner la DTD. La référence à un FPI est introduite par le mot clé PUBLIC suivi du FPI et d'une URL délimitée par des apostrophes ''' ou des guillemets '"'. L'URL est utilisée dans le cas où le FPI ne permet pas à l'application de retrouver la DTD.

<!DOCTYPE root-element PUBLIC "fpi" "url">

L'exemple suivant est la déclaration typique d'une page HTML qui utilise une des DTD de XHTML.

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

3.2.2.2. Adressée par URL

La référence à une URL est introduite par le mot clé SYSTEM suivi de l'URL délimitée par des apostrophes ''' ou des guillemets '"'.

<!DOCTYPE root-element SYSTEM "url">

L'URL url peut être soit une URL complète commençant par http:// ou ftp:// soit plus simplement le nom d'un fichier local comme dans les exemples suivants.

<!DOCTYPE bibliography SYSTEM 
    "http://www.omega-one.org/~carton/Enseignement/bibliography.dtd">
<!DOCTYPE bibliography SYSTEM "bibliography.dtd">

3.2.3. DTD mixte

Il est possible d'avoir simultanément une DTD externe adressée par URL ou FPI et des déclarations internes. La DTD globale est alors formée des déclarations internes suivies des déclarations externes. La déclaration prend alors une des deux formes suivantes On retrouve un mélange de la syntaxe des DTD externes avec les mots clés SYSTEM et PUBLIC et de la syntaxe des DTD internes avec des déclarations encadrées par les caractères '[' et ']'.

<!DOCTYPE root-element SYSTEM "url" [ declarations ]>
<!DOCTYPE root-element PUBLIC "fpi" "url" [ declarations ]>

Il n'est pas possible de déclarer plusieurs fois le même élément dans une DTD. Lorsque la DTD est mixte, tout élément doit être déclaré dans la partie interne ou dans la partie externe mais pas dans les deux. En revanche, il est possible de déclarer plusieurs fois le même attribut. La première déclaration a priorité. Il est ainsi possible de donner une nouvelle déclaration d'un attribut dans la partie interne puisque celle-ci est placée avant la partie externe.

Les entités paramètres peuvent également être déclarées plusieurs fois dans une même DTD et c'est encore la première déclaration qui l'emporte. La DTD suivante déclare l'entité paramètre book.entries égale à la chaîne vide. Cette entité est ensuite utilisée dans la déclaration de l'élément book. Cette déclaration laisse la possibilité au document principal d'ajouter des enfants à l'élément book en donnant une nouvelle valeur à l'entité book.entries. La première ligne de cette DTD est une entête XML qui permet de déclarer le codage des caractères.

<?xml version="1.0" encoding="iso-8859-1"?>
<!-- DTD externe -->
<!-- Entité paramètre pour les enfants à ajouter à l'élément book -->
<!ENTITY % book.entries "">
<!ELEMENT bibliography (book)+>
<!ELEMENT book (title, author, year, publisher, isbn %book.entries;)>
<!ATTLIST book key NMTOKEN #REQUIRED>
<!ATTLIST book lang (fr | en) #IMPLIED>
<!ELEMENT title     (#PCDATA)>
<!ELEMENT author    (#PCDATA)>
<!ELEMENT year      (#PCDATA)>
<!ELEMENT publisher (#PCDATA)>
<!ELEMENT isbn      (#PCDATA)>

Le document suivant a une DTD mixte dont la partie externe est la DTD précédente. La partie interne de la DTD contient la déclaration de l'élément url. Elle contient également des nouvelles déclarations de l'entité paramètre book.entries et de l'attribut lang de l'élément book, qui devient ainsi obligatoire.

<?xml version="1.0" encoding="iso-8859-1" standalone="no"?>
<!DOCTYPE bibliography SYSTEM "mixed.dtd" [
  <!-- Ajout d'un enfant url à l'élément book -->
  <!ENTITY % book.entries ", url?">
  <!-- Redéclaration de l'attribut lang de l'élément book -->
  <!-- L'attribut devient obligatoire -->
  <!ATTLIST book lang (fr | en) #REQUIRED>
  <!-- Déclaration de l'élément url -->
  <!ELEMENT url (#PCDATA)>
]>
<bibliography>
  <book key="Michard01" lang="fr">
    <title>XML langage et applications</title>
    <author>Alain Michard</author>
    <year>2001</year>
    <publisher>Eyrolles</publisher>
    <isbn>2-212-09206-7</isbn>
    <url>http://www.editions-eyrolles/livres/michard/</url>
  </book>
  <book key="Marchal00" lang="fr">
    <title>XML by Example</title>
    <author>Benoît Marchal</author>
    <year>2000</year>
    <publisher>Macmillan Computer Publishing</publisher>
    <isbn>0-7897-2242-9</isbn>
  </book>
</bibliography>

Comme la valeur donnée à l'entité paramètre book.entries est la chaîne ", url?" (sans les guillemets '"'), la déclaration de l'élément book dans le fichier mixed.dtd devient équivalente à la déclaration suivante qui autorise un enfant url optionnel.

<!ELEMENT book (title, author, year, publisher, isbn, url?)>

3.3. Contenu de la DTD

Une DTD est constituée de déclarations d'éléments, d'attributs et d'entités. Elle peut aussi contenir des déclarations de notations mais celles-ci ne sont pas abordées dans cet ouvrage. Chacune de ces déclarations commence par la chaîne '<!' suivi d'un mot clé qui indique le type de déclaration. Les mots clés possibles sont ELEMENT, ATTLIST et ENTITY. La déclaration se termine par le caractère '>'.

3.4. Commentaires

Une DTD peut contenir des commentaires qui utilisent la syntaxe des commentaires XML délimités par les chaînes de caractères '<!--' et '-->'. Ceux-ci sont placés au même niveau que les déclarations d'éléments, d'attributs et d'entités. Ils ne peuvent pas apparaître à l'intérieur d'une déclaration.

 <!-- DTD pour les bibliographies -->
 <!ELEMENT bibliography (book)+>
 <!-- Déclaration de l'élément book avec des enfants title, ..., isbn et url -->
 <!ELEMENT book (title, author, year, publisher, isbn, url?)>
 ...

3.5. Entités

Les entités constituent un mécanisme hérité de SGML. Elles sont des macros semblables aux #define du langage C. Elles permettent également de réaliser des inclusions de documents comme la directive #include du langage C. Les entités sont définies dans la DTD du document. Il existe deux types d'entités. Les entités générales, appelées simplement entités dans cet ouvrage, sont destinées à être utilisées dans le corps du document. Les entités paramètres sont destinées à être utilisées au sein de la DTD.

Une entité est, en quelque sorte, un nom donné à un fragment de document. Ce fragment peut être donné explicitement à la définition de l'entité dans la DTD. Il peut également provenir d'un fichier externe. Dans ce cas, la définition de l'entité donne un FPI et/ou une URL permettant d'accéder au document. Le fragment de document peut être inséré dans le document en utilisant simplement le nom de l'entité. Lorsque le fragment provient d'un autre fichier, l'utilisation de l'entité provoque l'inclusion du fichier en question. Le nom de chaque entité générale ou paramètre doit être un nom XML.

3.5.1. Entités générales

Les entités générales sont les entités les plus courantes et les plus utiles puisqu'elles peuvent être utilisées dans le corps du document.

3.5.1.1. Déclaration et référence

La déclaration d'une entité commence par <!ENTITY suivi du nom de l'entité. Elle prend une des trois formes suivantes où name est le nom de l'entité, fragment est un fragment de document, fpi et url sont un FPI et une URL.

<!ENTITY name "fragment">
<!ENTITY name SYSTEM "url">
<!ENTITY name PUBLIC "fpi" "url">

Lorsque l'entité est déclarée avec la première syntaxe, elle est dite interne car le fragment est explicitement donné dans la DTD du document. Lorsqu'elle est déclarée avec une des deux dernières syntaxes, elle est dite externe car le fragment provient d'un autre document. Ces deux dernières syntaxes sont semblables à la déclaration d'une DTD externe dans un document XML. Les règles pour l'utilisation des apostrophes ''' et des guillemets '"' sont identiques à celles pour les valeurs d'attributs. Le fragment, le FPI et l'URL doivent être délimités par une paire d'apostrophes ou de guillemets. Si le fragment est délimité par des apostrophes, les guillemets peuvent être introduits directement sans entité et inversement.

Une entité de nom name est référencée, c'est-à-dire utilisée, par &name; où le nom name de l'entité est encadré par les caractères '&' et ';'. Lorsque le document est traité, la référence à une entité est remplacée par le fragment de document correspondant. Une entité interne peut être référencée dans les contenus d'éléments et dans les valeurs d'attribut alors qu'une entité externe peut seulement être référencée dans les contenus d'éléments.

<tag meta="attribute: &name;">Content: &name;</tag>

3.5.1.2. Entités prédéfinies

Il existe des entités prédéfinies permettant d'inclure les caractères spéciaux '<', '>', '&', ''' et '"' dans les contenus d'éléments et dans les valeurs d'attributs. Ces entités sont les suivantes.

EntitéCaractère
&lt;<
&gt;>
&amp;&
&apos;'
&quot;"

Tableau 3.1. Entités prédéfinies


Les trois entités &lt;, &gt; et &amp; doivent être utilisées aussi bien dans le contenu des éléments que dans les valeurs des attributs puisque les caractères '<', '>' et '&' ne peuvent pas y apparaître directement. Les deux entités &apos; et &quot; sont uniquement nécessaires pour inclure le caractère ''' ou le caractère '"' dans la valeur d'un attribut délimitée par le même caractère. Lors de l'utilisation XPath avec XSLT, il n'est pas rare d'inclure ces deux caractères dans la valeur d'un attribut. Une des deux entités &apos; ou &quot; devient alors indispensable. L'exemple suivant est extrait d'une feuille de style XSLT. La valeur de l'attribut test est une expression XPath qui teste si la valeur de la variable string contient le caractère '"'.

    <xsl:when test="contains($string, '&quot;')">

Les nombreuses entités prédéfinies en XHTML comme &euro; pour le symbole '€' n'existent pas en XML. La seule façon d'inclure ce caractère est d'utiliser les notations &#point de code décimal; ou &#xpoint de code hexadécimal;. Il est cependant possible de définir ces propres entités (cf. ci-dessous).

3.5.1.3. Entités internes

La valeur d'une entité interne est le fragment de document associé à celle-ci lors de sa déclaration. Cette valeur peut contenir des caractères ainsi que des éléments avec leurs balises. Lorsqu'elle contient des éléments, le fragment doit être bien formé. À toute balise ouvrante doit correspondre une balise fermante et l'imbrication des balises doit être correcte. Quelques exemples d'entités internes sont donnés ci-dessous. Le dernier exemple utilise des apostrophes ''' pour délimiter le fragment de document qui contient des guillemets '"' pour encadrer les valeurs des attributs.

<!ENTITY aka   "also known as">
<!ENTITY euro  "&#x20AC;">
<!ENTITY rceil '<phrase condition="html">&#x2309;</phrase>
                <phrase condition="fo" role="symbolfont">&#xF8F9;</phrase>'>

Si la DTD contient les déclarations d'entités ci-dessus, Il est possible d'inclure le texte also known as en écrivant seulement &aka; ou d'inclure un symbole en écrivant &euro;. Les entités internes peuvent être référencées dans les contenus d'éléments mais aussi dans les valeurs d'attributs.

<price currency="Euro (&euro;)">30 &euro;</price>

Il est possible d'utiliser des entités dans la définition d'une autre entité pourvu que ces entités soient également définies. L'ordre de ces définitions est sans importance car les substitutions sont réalisées au moment où le document est lu par l'analyseur de l'application. Les définitions récursives sont bien sûr interdites.

<!DOCTYPE book [
  <!-- Entités internes -->
  <!ENTITY mci "Michel Colucci &aka; 'Coluche'">
  <!ENTITY aka "also known as">
]>
<book>&mci;</book>

Il faut faire attention au fait que certaines applications ne gèrent pas ou gèrent mal les entités définies. La solution est d'ajouter à la chaîne de traitement une première étape consistant à substituer les entités par leurs valeurs pour obtenir un document intermédiaire sans entités. Le logiciel xmllint peut par exemple réaliser cette opération. Avec l'option --noent, il écrit sur la sortie standard le document en remplaçant chaque entité par sa valeur.

3.5.1.4. Entités externes

Une entité peut désigner un fragment de document contenu dans un autre fichier. Ce mécanisme permet de répartir un même document sur plusieurs fichiers comme dans l'exemple suivant. La déclaration utilise alors le mot clé SYSTEM suivi d'une URL qui peut, simplement, être le nom d'un fichier local.

Les entités externes peuvent êtres utilisées pour scinder un document en plusieurs fichiers. Le fichier principal inclut les différentes parties en définissant une entité externe pour chacune de ces parties. Les entités sont alors utilisées pour réaliser l'inclusion comme dans l'exemple ci-dessous.

<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE book [
  <!-- Entités externes -->
  <!ENTITY chapter1 SYSTEM "chapter1.xml">
  <!ENTITY chapter2 SYSTEM "chapter2.xml">
]>
<book>
  <!-- Inclusion du fichier chapter1.xml -->
  &chapter1;
  <!-- Inclusion du fichier chapter2.xml -->
  &chapter2;
</book>

Chacun des fichiers contenant une entité externe peut avoir une entête. Celle-ci permet par exemple de déclarer un encodage des caractères différents du fichier principal. Ce mécanisme pour répartir un document en plusieurs fichiers est à abandonner au profit de XInclude qui est plus pratique.

3.5.2. Entités paramètres

Les entités paramètres sont des entités qui peuvent uniquement être utilisées à l'intérieur de la DTD. La terminologie est historique et provient de SGML. Ces entités ont le même rôle que les entités générales. Elles sont surtout utilisées pour apporter de la modularité aux DTD. La déclaration d'une entité paramètre prend une des trois formes suivantes où name est le nom de l'entité, fragment est un fragment de document, fpi et url sont un FPI et une URL.

<!ENTITY % name "fragment">
<!ENTITY % name SYSTEM "url">
<!ENTITY % name PUBLIC "fpi" "url">

La seule différence avec la déclaration d'une entité générale est la présence du caractère '%' entre le mot clé ENTITY et le nom de l'entité déclarée. Comme pour les entités générales, l'entité est dite interne lorsqu'elle est déclarée avec la première syntaxe. Elle est dite externe lorsqu'elle est déclarée avec une des deux dernières syntaxes. Les règles pour l'utilisation des apostrophes ''' et des guillemets '"' sont identiques à celles pour les valeurs d'attributs ou les entités générales.

L'entité name ainsi déclarée peut être référencée, c'est-à-dire utilisée, par %name; où le nom de l'entité est encadré les caractères '%' et ';'. Les entités paramètres peuvent uniquement être utilisées au sein de la DTD. Lorsque l'entité est interne, elle peut être utilisée dans les déclarations d'éléments, d'attributs et d'autres entités. Cette utilisation est limitée à la partie externe de la DTD. Lorsque l'entité est externe, elle est utilisée en dehors des déclarations pour inclure des déclarations provenant du document référencé par l'entité. L'exemple suivant définit deux entités paramètres idatt et langatt permettant de déclarer des attributs id et xml:lang facilement.

<!-- Déclaration de deux entités paramètres -->
<!ENTITY % idatt   "id ID #REQUIRED">
<!ENTITY % langatt "xml:lang NMTOKEN 'fr'">

<!-- Utilisation des deux entités paramètres -->
<!ATTLIST chapter %idatt; %langatt;>
<!ATTLIST section %langatt;>

Les entités paramètres ajoutent de la modularité qui est surtout nécessaire dans l'écriture de DTD de grande taille. Dans l'exemple précédent, l'attribut id pourrait être remplacé partout par un attribut xml:id en changeant uniquement la définition de l'entité paramètre idatt. Un autre exemple d'utilisation des entités paramètres est donné avec les DTD mixtes. Les entités externes permettent d'inclure une partie de DTD provenant d'un document externe comme dans l'exemple suivant.

<!-- Entité paramètre pour inclure la DTD principale -->
<!ENTITY % main SYSTEM "main.dtd">
<!-- Inclusion du fichier main.dtd -->
%main;

3.6. Déclaration d'élément

Les déclarations d'éléments constituent le cœur des DTD car elles définissent la structure des documents valides. Elles spécifient quels doivent être les enfants de chaque élément et l'ordre de ces enfants.

La déclaration d'un élément est nécessaire pour qu'il puisse apparaître dans un document. Cette déclaration précise le nom et le type de l'élément. Le nom de l'élément doit être un nom XML et le type détermine les contenus valides de l'élément. On distingue les contenus purs uniquement constitués d'autres éléments, les contenus textuels uniquement constitués de texte et les contenus mixtes qui mélangent éléments et texte.

De manière générale, la déclaration d'un élément prend la forme suivante où element et type sont respectivement le nom et le type de l'élément. Le type de l'élément détermine quels sont ses contenus autorisés.

<!ELEMENT element type>

3.6.1. Contenu pur d'éléments

Le contenu d'un élément est pur lorsqu'il ne contient aucun texte et qu'il est uniquement constitué d'éléments qui sont ses enfants. Ces enfants peuvent, à leur tour, avoir des contenus textuels, purs ou mixtes. Un élément de contenu pur peut donc indirectement contenir du texte si celui-ci fait partie du contenu d'un de ses descendants. La déclaration d'un élément de contenu pur détermine quels peuvent être ses enfants et dans quel ordre ils doivent apparaître. Cette description est indépendante du contenu de ces enfants. Elle dépend uniquement des noms des enfants. Le contenu de chaque enfant est décrit par sa propre déclaration. Une déclaration d'élément prend la forme suivante.

<!ELEMENT element regexp>

Le nom de l'élément est donné par l'identifiant element. L'expression rationnelle regexp décrit les suites autorisées d'enfants dans le contenu de l'élément. Cette expression rationnelle est construite à partir des noms d'éléments en utilisant les opérateurs ',', '|', '?', '*' et '+' ainsi que les parenthèses '(' et ')' pour former des groupes. Les opérateurs ',' et '|' sont binaires alors que les opérateurs '?', '*' et '+' sont unaires et postfixés. Ils se placent juste après leur opérande, c'est-à-dire derrière le groupe auquel ils s'appliquent.

Le nom d'un élément signifie que cet élément doit apparaître dans le contenu. Les opérateurs principaux sont les deux opérateurs ',' et '|' qui expriment la mise en séquence et le choix. Un contenu est valide pour une expression de la forme block-1, block-2 s'il est formé d'un contenu valide pour block-1 suivi d'un contenu valide pour block-2. Un contenu est valide pour une expression de la forme block-1 | block-2 s'il est formé d'un contenu valide pour block-1 ou d'un contenu valide pour block-2. Les trois opérateurs '?', '*' et '+' permettent d'exprimer des répétitions. Un contenu est valide pour une expression de la forme block? s'il est vide ou formé d'un contenu valide pour block. Un contenu est valide pour une expression de la forme block* (respectivement block+) s'il est formé d'une suite éventuellement vide (respectivement suite non vide) de contenus valides pour block. La signification des cinq opérateurs est récapitulée par la table suivante.

OpérateurSignification
,Mise en séquence
|Choix
?0 ou 1 occurrence
*Répétition d'un nombre quelconque d'occurrences
+Répétition d'un nombre non nul d'occurrences

Tableau 3.2. Opérateurs des DTD


Ces définitions sont illustrées par les exemples suivants.

<!ELEMENT elem (elem1, elem2, elem3)>

L'élément elem doit contenir un élément elem1, un élément elem2 puis un élément elem3 dans cet ordre.

<!ELEMENT elem (elem1 | elem2 | elem3)>

L'élément elem doit contenir un seul des éléments elem1, elem2 ou elem3.

<!ELEMENT elem (elem1, elem2?, elem3)>

L'élément elem doit contenir un élément elem1, un ou zéro élément elem2 puis un élément elem3 dans cet ordre.

<!ELEMENT elem (elem1, elem2*, elem3)>

L'élément elem doit contenir un élément elem1, une suite éventuellement vide d'éléments elem2 et un élément elem3 dans cet ordre.

<!ELEMENT elem (elem1, (elem2 | elem4), elem3)>

L'élément elem doit contenir un élément elem1, un élément elem2 ou un élément elem4 puis un élément elem3 dans cet ordre.

<!ELEMENT elem (elem1, elem2, elem3)*>

L'élément elem doit contenir une suite d'éléments elem1, elem2, elem3, elem1, elem2, … jusqu'à un élément elem3.

<!ELEMENT elem (elem1 | elem2 | elem3)*>

L'élément elem doit contenir une suite quelconque d'éléments elem1, elem2 ou elem3.

<!ELEMENT elem (elem1 | elem2 | elem3)+>

L'élément elem doit contenir une suite non vide d'éléments elem1, elem2 ou elem3.

3.6.1.1. Caractères d'espacement

La prise en compte des caractères d'espacement lors de validation d'un élément de contenu pur dépend du caractère interne ou externe de la DTD et de la valeur de l'attribut standalone de l'entête. Bien que le contenu d'un élément soit pur, il est, néanmoins, possible d'insérer des caractères d'espacement entre ses enfants. La validation ignore ces caractères lorsque la DTD est interne ou lorsque la valeur de l'attribut standalone est no. Ce traitement particulier rend possible l'indentation du document. Si la DTD est externe et que la valeur de l'attribut standalone est yes, la validation devient stricte et elle n'autorise aucun caractère d'espacement dans un contenu pur.

La DTD suivante déclare un élément list de contenu pur. Il contient une suite non vide d'éléments item contenant chacun du texte.

<!-- Fichier "standalone.dtd" -->
<!ELEMENT list (item)+>
<!ELEMENT item (#PCDATA)>

Le document suivant est valide pour la DTD précédente car la valeur de l'attribut standalone de l'entête est no. Si cette valeur était yes, le document ne serait plus valide car l'élément list contient des caractères d'espacement entre ses enfants item.

<?xml version="1.0" encoding="iso-8859-1" standalone="no"?>
<!DOCTYPE list SYSTEM "standalone.dtd">
<list>
  <item>Item 1</item>
  <item>Item 2</item>
</list>

3.6.1.2. Contenu déterministe

Afin de simplifier la validation de documents, les expressions rationnelles qui décrivent les contenus purs des éléments doivent être déterministes. Cette contrainte signifie qu'en cours d'analyse du contenu, le nom d'un élément ne peut apparaître que sur une seule branche de l'expression. Un exemple typique d'expression ne respectant pas cette règle est la suivante.

<!ELEMENT item ((item1, item2) | (item1, item3))>

Lors de l'analyse du contenu de l'élément item, le validateur ne peut pas déterminer en lisant l'élément item1 si celui-ci provient de la première alternative (item1, item2) ou de la seconde alternative (item1, item3). L'expression précédente peut, cependant, être remplacée par l'expression équivalente suivante qui a l'avantage d'être déterministe.

<!ELEMENT item (item1, (item2 | item3))>

Le non-déterminisme d'une expression peut aussi provenir d'un des deux opérateurs '?' ou '*'. L'expression (item1?, item1) n'est pas déterministe car il n'est pas possible de déterminer immédiatement si un élément item1 correspond à la première ou à la seconde occurrence de item1 dans l'expression. Cette expression est équivalente à l'expression (item1, item1?) qui est déterministe. Il existe toutefois des expressions sans expression déterministe équivalente comme l'expression suivante.

<!ELEMENT item ((item1, item2)*, item1)>

3.6.2. Contenu textuel

La déclaration de la forme suivante indique qu'un élément peut uniquement contenir du texte. Ce texte est formé de caractères, d'entités qui seront remplacées au moment du traitement et de sections littérales.

<!ELEMENT element (#PCDATA)>

Dans l'exemple suivant, l'élément text est de type #PCDATA.

<?xml version="1.0" encoding="iso-8859-1"?>
<!DOCTYPE texts [
  <!ELEMENT texts (text)*>
  <!ELEMENT text  (#PCDATA)>
]>
<texts>
  <text>Du texte simple</text>   
  <text>Une <![CDATA[ Section CDATA avec < et > ]]></text>   
  <text>Des entités &lt; et &gt;</text>   
</texts>

3.6.3. Contenu mixte

La déclaration de la forme suivante indique qu'un élément peut uniquement contenir du texte et les éléments element1, …, elementN. C'est la seule façon d'avoir un contenu mixte avec du texte et des éléments. Il n'y a aucun contrôle sur le nombre d'occurrences de chacun des éléments et sur leur ordre d'apparition dans le contenu de l'élément ainsi déclaré. Dans une telle déclaration, le mot clé #PCDATA doit apparaître en premier avant tous les noms des éléments.

<!ELEMENT element (#PCDATA | element1 | ... | elementN)*>

Dans l'exemple suivant, l'élément book possède un contenu mixte. Il peut contenir du texte et des éléments em et cite en nombre quelconque et dans n'importe quel ordre.

<?xml version="1.0" encoding="iso-8859-1"?>
<!DOCTYPE book [
  <!ELEMENT book (#PCDATA | em | cite)*>
  <!ELEMENT em   (#PCDATA)>
  <!ELEMENT cite (#PCDATA)>
]>
<book>
  Du <em>texte</em>, une <cite>citation</cite> et encore du <em>texte</em>.
</book>

3.6.4. Contenu vide

La déclaration suivante indique que le contenu de l'élément element est nécessairement vide. Cet élément peut uniquement avoir des attributs. Les éléments vides sont souvent utilisés pour des liens entre éléments.

<!ELEMENT element EMPTY>

Des exemples d'utilisation d'éléments de contenu vide sont donnés à la section traitant des attributs de type ID, IDREF et IDREFS.

<!ELEMENT ref EMPTY>
<!ATTLIST ref idref IDREF #REQUIRED>

Dans un souci de portabilité, il est conseillé de contracter les balises ouvrante et fermante lorsqu'un élément est déclaré de contenu vide et de le faire uniquement dans ce cas.

3.6.5. Contenu libre

La déclaration suivante n'impose aucune contrainte sur le contenu de l'élément element. En revanche, ce contenu doit, bien entendu, être bien formé et les éléments contenus doivent également être déclarés. Ce type de déclarations permet de déclarer des éléments dans une DTD en cours de mise au point afin de procéder à des essais de validation. En revanche, ces déclarations sont déconseillées dans une DTD terminée car elles conduisent à des modèles de documents trop laxistes.

<!ELEMENT element ANY>

3.7. Déclaration d'attribut

Pour qu'un document soit valide, tout attribut présent dans la balise ouvrante d'un élément doit être déclaré. La déclaration d'attribut prend la forme générale suivante où attribut est le nom de l'attribut et element le nom de l'élément auquel il appartient. Cette déclaration comprend également le type type et la valeur par défaut default de l'attribut. Le nom de l'attribut doit être un nom XML.

<!ATTLIST element attribut type default>

Il est possible de déclarer simultanément plusieurs attributs pour un même élément. Cette déclaration prend alors la forme suivante où l'indentation est bien sûr facultative.

<!ATTLIST element attribut1 type1 default1 
                  attribut2 type2 default2 
                  ...
                  attributN typeN defaultN>

Les différents types possibles pour un attribut ainsi que les valeurs par défaut autorisées sont détaillés dans les sections suivantes.

3.7.1. Types des attributs

Le type d'un attribut détermine quelles sont ses valeurs possibles. Les DTD proposent uniquement un choix fini de types pour les attributs. Le type doit en effet être pris dans la liste suivante. Les types les plus utilisés sont CDATA, ID et IDREF.

CDATA

Ce type est le plus général. Il n'impose aucune contrainte à la valeur de l'attribut. Celle-ci peut être une chaîne quelconque de caractères.

(value-1 | value-2 | ... | value-N)

La valeur de l'attribut doit être un des jetons value-1, value-2, … value-N. Comme ces valeurs sont des jetons, celles-ci ne sont pas délimitées par des apostrophes ''' ou des guillemets '"'.

NMTOKEN

La valeur de l'attribut est un jeton.

NMTOKENS

La valeur de l'attribut est une liste de jetons séparés par des espaces.

ID

La valeur de l'attribut est un nom XML. Un élément peut avoir un seul attribut de ce type.

IDREF

La valeur de l'attribut est une référence à un élément identifié par la valeur de son attribut de type ID.

IDREFS

La valeur de l'attribut est une liste de références séparées par des espaces.

NOTATION

La valeur de l'attribut est une notation

ENTITY

La valeur de l'attribut une entité externe non XML

ENTITIES

La valeur de l'attribut une liste d'entités externes non XML

Le type le plus général est CDATA puisque toutes les valeurs correctes d'un point de vue syntaxique sont permises. Cet type est très souvent utilisé car il est approprié dès qu'il n'y a aucune contrainte sur la valeur de l'attribut.

Les types NMTOKEN et NMTOKENS imposent respectivement que la valeur de l'attribut soit un jeton ou une suite de jetons séparés par des espaces. Il est aussi possible d'imposer que la valeur de l'attribut soit dans une liste fixe de jetons. Il est impossible, avec une DTD, de restreindre les valeurs d'un attribut à une liste fixe de valeurs qui ne sont pas des jetons.

L'utilisation des trois types NOTATION, ENTITY et ENTITIES est réservée à l'usage des entités externes non XML et elle n'est pas détaillée dans cet ouvrage. L'utilisation des trois types ID, IDREF et IDREFS est développée à la section suivante.

3.7.2. Attributs de type ID, IDREF et IDREFS

Il est fréquent qu'il existe des liens entre les données d'un document XML. Il peut s'agir, par exemple, de références à d'autres parties du document. Les attributs de types ID, IDREF et IDREFS s'utilisent conjointement pour matérialiser ces liens au sein d'un document. Un attribut de type ID permet d'identifier de façon unique un élément du document. Les éléments ainsi identifiés peuvent alors être référencés par d'autres éléments grâce aux attributs de types IDREF et IDREFS. Ces attributs créent ainsi des liens entre des éléments ayant les attributs de types IDREF ou IDREFS et des éléments ayant les attributs de type ID. Ce mécanisme permet uniquement de créer des liens entre des éléments d'un même document. La norme XLink généralise ce mécanisme. Elle permet de créer des liens entre deux, voire même plus de fragments de documents XML provenant éventuellement de documents différents.

La valeur d'un attribut de type ID doit être un nom XML. La valeur de cet attribut doit être unique dans tout le document. Ceci signifie qu'un autre attribut de type ID d'un autre élément ne peut pas avoir la même valeur pour que le document soit valide. Un élément ne peut avoir qu'un seul attribut de type ID.

Les attributs de type ID sont utilisés par la fonction XPath id() qui retourne les nœuds identifiés par la valeur de leur attribut de type ID.

La valeur d'un attribut de type IDREF doit être un nom XML. Ce nom doit, en outre, être la valeur d'un attribut de type ID d'un (autre) élément pour que le document soit valide. La valeur d'un attribut de type IDREFS doit être une suite de noms séparés par des espaces. Chacun de ces noms doit, en outre, être la valeur d'un attribut de type ID d'un élément pour que le document soit valide.

Le document suivant illustre l'utilisation des attributs de type ID, IDREF et IDREFS qui est faite par DocBook pour les références internes. Son contenu est scindé en sections délimitées par les éléments section. Chacun de ces éléments a un attribut id de type ID. Le contenu des éléments section est constitué de texte et d'éléments ref et refs ayant respectivement un attribut idref de type IDREF et un attribut idrefs de type IDREFS. Ces éléments permettent, dans le contenu d'une section, de référencer une (par ref) ou plusieurs (par refs) autres sections. Il faut remarquer que les éléments ref et refs n'ont jamais de contenu. Ils sont déclarés vides en utilisant le mot clé EMPTY. Il appartient à l'application qui génère le document final d'ajouter du contenu qui peut être, par exemple, le numéro ou le titre de la section référencée.

<?xml version="1.0" encoding="iso-8859-1" standalone="no"?>
<!DOCTYPE book [
  <!ELEMENT book    (section)*>
  <!ELEMENT section (#PCDATA | ref | refs)*>
  <!ATTLIST section id ID #IMPLIED>
  <!ELEMENT ref     EMPTY>
  <!ATTLIST ref     idref  IDREF  #REQUIRED>
  <!ELEMENT refs    EMPTY>
  <!ATTLIST refs    idrefs IDREFS #REQUIRED>
]>
<book>
  <section id="sec0">Une référence <ref idref="sec1"/></section>
  <section id="sec1">Des références <refs idrefs="sec0 sec2"/></section>
  <section id="sec2">Section sans référence</section>
  <section id="sec3">Une auto-référence <refs idrefs="sec3"/></section>
</book>

Les attributs de type ID et IDREF permettent également de structurer un document. Si l'adresse et d'autres informations sont ajoutées à l'éditeur dans le document bibliography.xml, celles-ci sont recopiées dans chaque livre publié par l'éditeur. Cette duplication de l'information est bien sûr très mauvaise. Une meilleure approche consiste à scinder la bibliographie en deux parties. Une première partie contient les livres et une seconde partie les éditeurs avec les informations associées. Ensuite, chaque livre se contente d'avoir une référence sur son éditeur. Un attribut id de type ID est ajouté à chaque élément publisher de la seconde partie. Chaque élément publisher contenu dans un élément book est remplacé par un élément published ayant un attribut by de type IDREF.

<?xml version="1.0" encoding="iso-8859-1"?>
<bibliography>
  <books>
    <book key="Michard01" lang="fr">
      <title>XML langage et appplications</title>
      <author>Alain Michard</author>
      <year>2001</year>
      <isbn>2-212-09206-7</isbn>
      <url>http://www.editions-eyrolles/livres/michard/</url>
      <published by="id2680397"/>
    </book>
    <book key="Marchal00" lang="en">
      <title>XML by Example</title>
      <author>Benoît Marchal</author>
      <year>2000</year>
      <isbn>0-7897-2242-9</isbn>
      <published by="id2680427"/>
    </book>
    ...
  </books>
  <publishers>
    <publisher id="id2680397">
      <name>Eyrolles</name>
      <address>Paris</address>
    </publisher>
    <publisher id="id2680427">
      <name>Macmillan Computer Publishing</name>
      <address>New York</address>
    </publisher>
    ...
  </publishers>
</bibliography>

Beaucoup d'applications ne prennent pas en compte la DTD pour traiter un document. Il leur est alors impossible de savoir quels attributs sont de type ID, IDREF ou IDREFS. Elles utilisent souvent la convention qu'un attribut de nom id est implicitement de type ID. Une meilleure solution consiste à utiliser l'attribut xml:id qui est toujours de type ID (de type xsd:ID en fait).

3.7.3. Valeur par défaut

Chaque déclaration d'attribut précise une valeur par défaut pour celui-ci. Cette valeur par défaut peut prendre une des quatre formes suivantes.

"value" ou 'value'

La valeur value est une chaîne quelconque de caractères délimitée par des apostrophes ''' ou des guillemets '"'. Si l'attribut est absent pour un élément du document, sa valeur est implicitement la chaîne value. Cette valeur doit, bien sûr, être du type donné à l'attribut.

#IMPLIED

L'attribut est optionnel et il n'a pas de valeur par défaut. Si l'attribut est absent, il n'a pas de valeur.

#REQUIRED

L'attribut est obligatoire et il n'a pas de valeur par défaut.

#FIXED "value" ou #FIXED 'value'

La valeur value est une chaîne quelconque de caractères délimitée par des apostrophes ''' ou des guillemets '"'. La valeur de l'attribut est fixée à la valeur value donnée. Si l'attribut est absent, sa valeur est implicitement value. Si l'attribut est présent, sa valeur doit être value pour que le document soit valide. Cette valeur doit, bien sûr, être du type donné à l'attribut.

Beaucoup d'applications ne prennent pas en compte la DTD pour traiter un document XML. Ce comportement pose problème avec les valeurs par défaut et les valeurs fixes (utilisation de #FIXED) des attributs. Si l'attribut est absent pour un élément du document, l'application va considérer que l'attribut n'a pas de valeur bien que la DTD déclare une valeur par défaut. L'utilisation des valeurs par défaut est donc à éviter pour une portabilité maximale.

3.7.4. Exemples

Voici quelques exemples de déclarations d'attributs avec, pour chacune d'entre elles, des valeurs valides et non valides pour l'attribut.

<!ATTLIST tag meta CDATA "default">

La valeur de l'attribut meta peut être une chaîne quelconque et sa valeur par défaut est la chaîne default Les exemples illustrent l'utilisation des entités prédéfinies pour inclure des caractères spéciaux dans les valeurs d'attribut.

Attribut dans le documentValeur
<tag meta="Hello World!">Hello World!
<tag>Valeur par défaut default
<tag meta="">Chaîne vide
<tag meta="=='&quot;==">=='"==
<tag meta='==&apos;"=='>=='"==
<tag meta="==&lt;&amp;&gt;==">==<&>==
<tag meta="==&#60;&#38;&#62==">==<&>==

<!ATTLIST book lang (fr | en) "fr">

La valeur de l'attribut lang peut être soit le jeton fr soit le jeton en et sa valeur par défaut est le jeton fr.

Attribut dans le documentValeur
<book>Valeur par défaut fr
<book lang="fr">Valeur par défaut fr
<book lang="en">Jeton en
<book lang="de">non valide car la valeur de n'est pas énumérée

<!ATTLIST book name NMTOKEN #IMPLIED>

L'attribut name est optionnel et sa valeur doit être un jeton. Il n'a pas de valeur par défaut.

Attribut dans le documentValeur
<book>Pas de valeur
<book name="en">Jeton en
<book name="-id234"/>Jeton -id234
<book name="Hello World!">non valide car Hello World! n'est pas un jeton

<!ATTLIST entry id ID #REQUIRED>

L'attribut id est obligatoire et sa valeur doit être un nom unique. Il n'a pas de valeur par défaut.

Attribut dans le documentValeur
<entry>non valide attribut absent
<entry id="id-234">Nom id-234
<entry id="Hello World!">non valide car Hello World! n'est pas un nom

<!ATTLIST xref ref IDREF #REQUIRED>

L'attribut ref est obligatoire et sa valeur doit être un nom XML. Pour que le document soit valide, ce nom doit être la valeur d'un attribut de type ID d'un (autre) élément. Cet attribut n'a pas de valeur par défaut.

Attribut dans le documentValeur
<xref ref="id-234"/>id-234
<xref ref="-id234"/>non valide car -id234 n'est pas nom.

<!ATTLIST xrefs refs IDREFS #REQUIRED>

L'attribut refs est obligatoire et sa valeur doit être une suite de noms XML séparés par des espaces. Pour que le document soit valide, chacun des noms de la liste doit être la valeur d'un attribut de type ID d'un (autre) élément. Il n'a pas de valeur par défaut.

Attribut dans le documentValeur
<xrefs refs="id-234"/>Nom id-234
<xrefs refs="id-234 id-437"/>Noms id-234 et id-437

<!ATTLIST rule style CDATA #FIXED "free">

La valeur de l'attribut style de l'élément rule est fixée à la valeur free.

Attribut dans le documentValeur
<rule>Valeur fixée free
<rule style="free">Valeur fixée free
<rule style="libre">non valide car valeur différente

3.8. Outils de validation

Il existe plusieurs outils permettant de valider un document XML par rapport à une DTD. Il existe d'abord des sites WEB dont l'avantage est une interface conviviale. L'utilisateur peut fournir son document par un simple copier/coller ou en le téléchargeant. Comme la validation est réalisée sur une machine distante, la DTD doit être, soit accessible via une URL, soit directement incluse dans document.

L'inconvénient majeur de ces sites WEB est la difficulté de les intégrer à une chaîne de traitement automatique puisqu'ils requièrent l'intervention de l'utilisateur. Dans ce cas, il est préférable d'utiliser un logiciel comme xmllint. Avec l'option --valid, il réalise la validation du document passé en paramètre. Avec cette option, la DTD soit être précisée par une déclaration de DTD dans le prologue du document. Sinon, il faut donner explicitement la DTD après l'option --dtdvalid. La DTD peut être donnée par le nom d'un fichier local ou par une URL qui permet à xmllint d'accéder à celle-ci.