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.
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)+> <!ELEMENT book (title, author, year, publisher, isbn, url?)> <!ATTLIST book key NMTOKEN #REQUIRED> <!ATTLIST book lang (fr | en) #REQUIRED> <!ELEMENT title (#PCDATA)> <!ELEMENT author (#PCDATA)> <!ELEMENT year (#PCDATA)> <!ELEMENT publisher (#PCDATA)> <!ELEMENT isbn (#PCDATA)> <!ELEMENT url (#PCDATA)>
Déclaration de l'élément | |
Déclaration de l'élément | |
Déclarations des attributs obligatoires | |
Déclaration de l'élément |
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.
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 ']'
.
<!DOCTYPEroot-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)> ]>
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.
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.
<!DOCTYPEroot-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">
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
'"'
.
<!DOCTYPEroot-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">
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 ']'
.
<!DOCTYPEroot-element
SYSTEM "url
" [declarations
]> <!DOCTYPEroot-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?)>
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 '>'
.
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?)> ...
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.
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.
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.
<!ENTITYname
"fragment
"> <!ENTITYname
SYSTEM "url
"> <!ENTITYname
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
&
où le nom
name
;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>
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.
Les trois entités <
,
>
et &
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
'
et "
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
'
ou "
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, '"')">
Les nombreuses entités prédéfinies en XHTML comme
€
pour le symbole '€' n'existent pas en
XML. La seule façon d'inclure ce caractère est d'utiliser les
notations &#
ou point de code
décimal
;&#x
. Il est cependant possible
de définir ces propres entités (cf. ci-dessous).point
de code hexadécimal
;
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 "€"> <!ENTITY rceil '<phrase condition="html">⌉</phrase> <phrase condition="fo" role="symbolfont"></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 €
. 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 (€)">30 €</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.
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.
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
%
où le nom de
l'entité est encadré les caractères name
;'%'
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;
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.
<!ELEMENTelement
type
>
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.
<!ELEMENTelement
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
s'il est formé d'un contenu
valide pour block-1
,
block-2
block-1
suivi d'un contenu valide
pour block-2
. Un contenu est valide pour une
expression de la forme
s'il est formé d'un contenu
valide pour block-1
|
block-2
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
s'il est vide ou
formé d'un contenu valide pour block
?block
. Un
contenu est valide pour une expression de la forme
(respectivement
block
*
) s'il est formé
d'une suite éventuellement vide (respectivement suite non vide) de
contenus valides pour block
+block
. La
signification des cinq opérateurs est récapitulée par la table
suivante.
Opérateur | Signification |
---|---|
, | 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
.
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>
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)>
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 < et ></text>
</texts>
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.
<!ELEMENTelement
(#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>
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.
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>
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.
<!ATTLISTelement
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.
<!ATTLISTelement
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.
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.
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).
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.
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 document | Valeur |
---|---|
<tag meta="Hello World!"> | Hello World! |
<tag> | Valeur par défaut default |
<tag meta=""> | Chaîne vide |
<tag meta="=='"=="> | =='"== |
<tag meta='=='"=='> | =='"== |
<tag
meta="==<&>=="> | ==<&>== |
<tag
meta="==<&>=="> | ==<&>== |
<!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 document | Valeur |
---|---|
<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 document | Valeur |
---|---|
<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 document | Valeur |
---|---|
<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 document | Valeur |
---|---|
<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 document | Valeur |
---|---|
<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 document | Valeur |
---|---|
<rule> | Valeur fixée free |
<rule style="free"> | Valeur fixée free |
<rule style="libre"> | non valide car valeur différente |
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.