2.1. DTD¶
Para definir gramáticas de documentos XML se usan distintos lenguajes, pero los más habituales son:
DTD XML, derivado del DTD que se usa para validar documentos SGML. Básicamente es un documento SGML usado para definir la gramática de un dialecto XML concreto.
W3C XML Schema, que es un lenguaje con sintaxis XML desarrollado por el W3C.
Relax-NG, que es otro lenguaje de esquemas XML más sencillo que el anterior y que, además de la sintaxis XML, dispone de una sintaxis alternativa llamada Compacta.
Desarrollaremos sólo el primer lenguaje, que es el más extendido, aunque también el más limitado y menos expresivo. Los otros dos, los desarrollaremos como material adicional en los apéndices.
2.1.1. Preliminares¶
2.1.1.1. Declaración¶
Antes de comenzar, es preciso indicar cómo declarar en un XML cuál es el DTD que lo define. Ello se logra mediante la línea de definición de tipo de documento ya mencionada:
<!DOCTYPE nodo_raiz SYSTEM "gramatica.dtd">
Esta línea, no obstante, no siempre tiene la misma estructura, porque ésta depende de dos factores:
La ubicación del DTD, esto es, dónde se encuentren definidas las reglas que constituyen el DTD: si dentro de la propia declaración de documento o en archivo aparte.
El carácter público o privado de la definición. Un DTD que construyamos en nuestro disco duro para describir un XML particular tiene carácter privado. En cambio, el DTD que describe XHTML es de carácter público.
Combinando estos dos aspectos tenemos las distintas formas que adquiere la declaración:
Interno privado
<!DOCTYPE nodo_raiz [ regla1 regla2 etc... ]>
Externo privado, que será el que habitualmente usemos en estos apuntes:
<!DOCTYPE claustro SYSTEM "claustro.dtd">
En este caso, entre comillas debe incluirse la ruta y el nombre de archivo correspondiente al DTD. En ausencia de ruta, se sobreentiende que el DTD se encuentra en el mismo directorio que el XML.
Externo público
<!DOCTYPE nodo_raiz PUBLIC "FPI" "URL">
En este caso la URL es, por lo general, una dirección web en la que se encuentra el propio DTD, mientras que el FPI (Identificador Público Formal) es un modo de identificar inequívocamente ese DTD público, por lo que cumple el papel de un URI. Que en este caso se use FPI y no URI es una cuestión heredada de tiempos en que no existía aún el segundo concepto, por lo que los FPIs constituyen un sistema heredado[1].
Un ejemplo de declaración es la que define la versión 1.0 de XHTML:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
Híbrido privado, que mezclan reglas definidas en un archivo externo con reglas incluidas en la propia declaración:
<!DOCTYPE claustro SYSTEM "claustro.dtd" [ <!ENTITY SAN "Sanitaria"> ]>
Híbrido público
<!DOCTYPE nodo_raiz PUBLIC "FPI" "URL" [ regla1 regla2 ]>
2.1.1.2. Ejemplo¶
Antes de entrar en harina, no está de más ver qué aspecto tiene un DTD. Para
ello tomemos el documento XML usado como ejemplo introductorio, aunque con una ligera variación[2] (descárguelo de aquí
):
XML de casilleros
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE claustro SYSTEM "casilleros.dtd">
<claustro centro="IES Pepe Botella">
<profesor id="p1">
<apelativo>Pepe</apelativo>
<nombre>José</nombre>
<apellidos>Suárez Lantilla</apellidos>
<departamento>&ING;</departamento>
</profesor>
<profesor id="p13">
<apelativo>Cristina</apelativo>
<nombre>María Cristina</nombre>
<apellidos>Prieto Monagas</apellidos>
<departamento>&ByG;</departamento>
</profesor>
<!-- Si se sustituye a sí mismo es que está de baja -->
<profesor id="p15">
<apelativo>Manolo</apelativo>
<nombre>Manuel</nombre>
<apellidos>Páez Robledo</apellidos>
<departamento>&MAT;</departamento>
</profesor>
<!-- Profesor con casillero(s) adicional(es) -->
<profesor id="p17" casillero="17 43">
<apelativo>Lucía</apelativo>
<nombre>Lucía</nombre>
<apellidos>Gálvez Ruiz</apellidos>
<departamento>&ING;</departamento>
</profesor>
<!-- En principio, si el casillero está vacío
la aplicación les asigna el que indica su identificador,
pero se puede asignar uno distinto.
-->
<profesor id="p28" casillero="28">
<apelativo>Miguel Ángel</apelativo>
<nombre>Miguel Ángel</nombre>
<apellidos>Campos Sánchez</apellidos>
<departamento>&HIS;</departamento>
</profesor>
<profesor id="p81" casillero="">
<apelativo>Verónica</apelativo>
<nombre>Verónica</nombre>
<apellidos>Martín Díaz</apellidos>
<departamento>&ByG;</departamento>
</profesor>
<!-- Profesor sustituto -->
<profesor id="p86" sustituye="p13">
<apelativo>Roberto</apelativo>
<nombre>Roberto</nombre>
<apellidos>Mínguez Torralbo</apellidos>
</profesor>
</claustro>
La definición de su gramática hecha en DTD es la siguiente
:
DTD de casilleros
<!ENTITY % departamentos SYSTEM "departamentos.ent">
<!ENTITY % datospers "(apelativo,nombre,apellidos)">
<!--
id: Identificador del profesor (formato i<N>)
sustituye: A quién sustituye al profesor.
casillero: Número de casillero asignado. Si no se especifica:
- En caso de profesor titular, la <N> de "id"
- En caso de sustituto, el casillero del titular.
Si está vacío, el profesor no tiene asignado casillero.
-->
<!ENTITY % attrpers
"id ID #REQUIRED
casillero CDATA #IMPLIED
sustituye IDREF #IMPLIED">
%departamentos;
<!ELEMENT claustro (profesor|asistente)+>
<!ATTLIST claustro centro CDATA #REQUIRED>
<!ELEMENT profesor (%datospers;,departamento?)>
<!ELEMENT asistente %datospers;>
<!ATTLIST profesor %attrpers;>
<!ATTLIST asistente %attrpers;>
<!ELEMENT nombre (#PCDATA)>
<!ELEMENT apellidos (#PCDATA)>
<!ELEMENT apelativo (#PCDATA)>
<!ELEMENT departamento (#PCDATA)>
Este DTD, a su vez, llama a otro que contiene exclusivamente las
entidades que definen los departamentos
[3]:
DTD de definición de entidades
<!ENTITY FyQ "Física y Química">
<!ENTITY EFI "Educación Física">
<!ENTITY ING "Inglés">
<!ENTITY INF "Informática">
<!ENTITY FRA "Francés">
<!ENTITY ORI "Orientación">
<!ENTITY MUS "Música">
<!ENTITY LEN "Lengua Castellana y Literatura">
<!ENTITY MAT "Matemáticas">
<!ENTITY ByG "Biología y Geología">
<!ENTITY FIL "Filosofía">
<!ENTITY HIS "Geografía e Historia">
<!ENTITY LAT "Latín">
<!ENTITY GRI "Griego">
<!ENTITY DIB "Dibujo">
<!ENTITY FOL "Formación y Orientación Laboral">
<!ENTITY TEC "Tecnología">
<!ENTITY REL "Religión">
2.1.1.3. Validación¶
Antes de entrar en harina, conviene que sepamos cómo llevar a cabo las validaciones. Se propone:
Una solución en línea como xmlvalidation.com, que permite validar documentos XML a partir de su DTD.
Visual Studio Code, cuya extensión XML, consulta la gramática definida en el DTD que se refiera en la declaración[4] e indica las violaciones a ella según se escribe.
XMLStarlet, que tiene paquete en las distribuciones basadas en Debian (xmlstarlet).
2.1.2. Sintaxis básica¶
2.1.2.1. Elementos¶
Para definir un elemento se usa la sintaxis:
<!ELEMENT nombre_elemento contenido>
La expresión del contenido puede ser:
ANY
Representa cualquier contenido. Por tanto, deja libertad absoluta, lo cual significa en realidad no definir nada. En consecuencia, en la versión definitiva de un DTD no debería aparecer nunca, pero puede ser útil en versiones preliminares en las que aún no hemos definido toda la gramática:
<!ELEMENT profesor ANY>
EMPTY
Representa un elemento vacío, esto es, un elemento sin contenido (aunque puede tener atributos):
<!ELEMENT hr EMPTY>
(#PCDATA)
El nodo contiene texto:
<!ELEMENT apodo (#PCDATA)>
(elemento_hijo)
El nodo contiene dentro de sí un nodo hijo:
<!ELEMENT claustro (profesor)>
Ahora bien, por lo general los elementos no contienen únicamente un único hijo, sino varios lo que lleva a definir dos conceptos:
- Secuencia
que representa cómo se suceden los nodos hijos dentro del padre. DTD define dos secuencias:
Un elemento seguido a continuación por otro, mediante la coma:
<!ELEMENT profesor (apodo, nombre, apellidos, departamento)>
O un elemento y otro elemento, mediante la tubería:
<!ELEMENT claustro (profesor|lector)>
En este caso, estamos afirmando que un claustro está compuesto por o un profesor o por un lector, pero no por ambos. Sí, es algo estúpido, pero aún no sabemos cardinalidad.
Por supuesto, podemos hacer composiciones de ambos tipos de secuencias e, incluso, usar paréntesis para agruparlas. Suponiendo dos nodos hijos llamados a y b:
(a,b) (a|b) (a,(b|c)) ((a,b)|c) (a,(b|c),d) (a,((b,c)|d))
- Cardinalidad
que representa la posible repetición de un elemento y que DTD la significa añadiendo tras el elemento un modificador. El modificador permite expresar:
Una y sólo una aparición, si no se añade modificador, que es lo que hemos hecho en las expresiones del contenido incluidas en la explicación sobre la secuencia.
Una o ninguna aparición, que se expresa con una interrogación. Por ejemplo:
<!ELEMENT profesor (apodo?, nombre, apellidos, departamento?)>
En esta definición, cada profesor obligatoriamente tendrá un nombre y unos apellidos, pero podrá no tener apodo o no pertenecer a un departamento. Por tanto, este nodo es válido:
<profesor id="p9" sexo="mujer"> <nombre>María Isabel</nombre> <apellidos>Peinado Sanjuán</apellidos> </profesor>
Una o más apariciones, que se expresa con el signo de la suma:
<!ELEMENT claustro (profesor)+>
Por tanto, un claustro está constituido por profesores, pero al menos debe haber uno.
Ninguna, una o más apariciones, que se expresa con el asterisco:
<!ELEMENT claustro (profesor)*>
Con esta definición, el claustro podría estar vacío.
Nota
Pueden existir nodos de contenido mixto, es decir, nodos que mezclan texto con nodos hijos. En este caso, se debe escribir así:
<!ELEMENT p (#PCDATA|em|b|strong|i)*>
o sea, poner #PCDATA
al principio de una secuencia de elementos
alternativos y añadir una cardinalidad con el asterisco.
2.1.2.2. Atributos¶
Los atributos se definen para el elemento al que pertenecen con la siguiente sintaxis:
<!ATTLIST nombre_elemento nombre_atributo1 tipo_atributo1 valor_defecto1
nombre_atributo2 tipo_atributo2 valor_defecto2
.... >
Nota
Es posible definir atributos de un mismo elemento en ATTLIST distintos, pero lo habitual es verlos en el mismo.
2.1.2.2.1. Tipos¶
El valor de un atributo puede ser de uno de los siguientes tipos:
CDATA
Texto libre:
<!ATTLIST claustro centro CDATA #REQUIRED>
(valor1|valor2|valor3|...)
El valor debe ser uno de los incluidos en la lista de opciones:
<!ATTLIST profesor sexo (hombre|mujer) #REQUIRED>
ID
El valor del atributo es un identificador único, por lo que no podrá haber otro atributo identificador que tenga el mismo valor. El identificador es una palabra, el primero de cuyos caracteres debe ser una letra, un subrayado o dos puntos:
<!ATTLIST profesor id ID #REQUIRED>
IDREF
El valor del atributo es una referencia a un identificador del documento, es decir, el valor debe coincidir con el valor de otro atributo que haya sido definido como identificador:
<!ATTLIST grupo tutor IDREF #REQUIRED>
Si ampliáramos nuestro XML para que se pudieran definir los grupos de alumnos del centro, el elemento grupo podría tener un atributo que indicase cuál es su tutor. En este caso, ese atributo debería referir a un profesor que esté definido en el documento.
IDREFS
El valor del atributo es una lista de referencias a identificadores separados por espacios. Por ejemplo:
<!ATTLIST grupo profesores IDREFS #REQUIRED>
En este caso, el atributo profesores representaría todos los profesores que dan clase al grupo.
NMTOKEN
El valor del atributo debe ser una palabra que contenga letras, números, puntos, guiones, subrayados o dos puntos:
<!ATTLIST grupo letra NMTOKEN #IMPLIED>
NMTOKENS
El valor del atributo será una lista de tokens tal como se han definido antes:
<!ATTLIST grupo asignaturas NMTOKENS #REQUIRED>
que traduciría un XML de este tipo:
<grupo letra="A" tutor="p1" profesores="p2 p5 p8" asignaturas="lengua inglés sociales" />
ENTITY
El valor del atributo debe ser una entidad no procesable definida en la gramática (lo cual requiere a su vez haber definido también una notación):
<!NOTATION JPEG SYSTEM "image/jpeg"> <!ENTITY foto_carne1 SYSTEM "careto1.jpg" NDATA JPEG> <!ENTITY foto_carne2 SYSTEM "careto2.jpg" NDATA JPEG> <!ATTLIST profesor foto ENTITY #IMPLIED>
Lo que llevaría a que en el XML hubiera algo así:
<profesor id="p5" sexo="hombre" foto="foto_carne2">
ENTITIES
El valor del atributo debe ser una lista de entidades no procesables.
NOTATION
El valor del atributo es una de las notaciones definidas en la gramática:
<!NOTATION JPG SYSTEM "image/jpeg"> <!NOTATION PNG SYSTEM "image/png"> <!NOTATION GIF SYSTEM "image/gif"> <!ATTLIST profesor formato_foto NOTATION (JPEG|PNG|GIF) "JPEG">
2.1.2.2.2. Valor por defecto¶
La última parte de la definición del atributo se dedica a definir cuál es el valor predeterminado del atributo, para lo cual hay cuatro posibilidades:
Un valor entre comillas, que implica que ése es el valor en caso de que dentro del XML no se incluya el atributo:
<!ATTLIST profesor nacionalidad NMTOKEN "española">
#IMPLIED
, que significa que no hay ningún valor predeterminado y que además el valor no es estrictamente necesario, por lo que si no se consigna en el XML, su valor queda indefinido.#REQUIRED
, que significa que no hay valor predeterminado, pero que obligatoriamente se necesita un valor, por lo que forzosamente habrá que incluir el atributo en el XML.#FIXED
, que significa que el valor obligatoriamente debe ser el que se indica en el propio DTD, pero en el XML debe aparecer de todas formas:<!ATTLIST profesor especie NMTOKEN #FIXED "humana">
2.1.2.3. Notaciones¶
Se usan para definir formatos distintos al XML y pueden ser tanto públicas como privadas:
<!NOTATION nombre SYSTEM "IDSistema">
<!NOTATION nombre PUBLIC "IDPublico" "IDSistema">
El identificador del sistema suele ser el tipo MIME asociado a ese formato[5]:
<!NOTATION JPG SYSTEM "image/jpeg">
<!NOTATION PNG SYSTEM "image/png">
<!NOTATION GIF SYSTEM "image/gif">
o bien:
<!NOTATION JPG PUBLIC "JPEG 1.0" "image/jpeg">
<!NOTATION PNG SYSTEM "PNG 1.0" "image/png">
<!NOTATION GIF SYSTEM "GIF 1.0" "image/gif">
2.1.2.4. Entidades¶
Las entidades son mecanismos de sustitución. Las hay de dos tipos:
Entidades generales, que hacen las sustituciones en el documento XML.
Entidades parámetro, que operan la sustitución en el propio DTD.
2.1.2.4.1. Generales¶
Las entidades generales pueden ser:
- Procesables
que son aquellas de las que se puede hacer sustitución. Como XML es texto plano, sólo son procesables aquellas cuando la sustitución es texto plano. Pueden ser internas en que se escribe directamente cuál debe ser la sustitución:
<!ENTITY ING "Inglés">
o externas en que el texto sustitutorio se encuentra en un archivo aparte:
<!ENTITY ING SYSTEM "ingles.txt">
Nota
Para que la sustitución fuera equivalente a la interna el archivo
ingles.txt
debería contener la palabra «Inglés».Las entidades procesables externas también pueden ser públicas (la del ejemplo anterior es privada):
<!ENTITY nombre PUBLIC "FPI" "URL">
- No procesables
que son aquellas en las que por no ser de texto no puede operarse la sustitución. Obviamente, siempre son externas. Su definición se hace de este modo:
<!ENTITY nombre SYSTEM "URL" NDATA tipo> <!ENTITY nombre PUBLIC "FPI" "URL" NDATA tipo>
donde tipo es un formato definido previamente mediante una notación. Ya se puso un ejemplo de uso al hablar de los atributos cuyo tipo es una entidad.
2.1.2.4.2. Parámetro¶
Operan su sustitución en el propio DTD, por lo que sólo tiene sentido que sean procesables (internas, externas privadas o externas públicas):
<!ENTITY % nombre "valor"> <!-- interna -->
<!ENTITY % nombre SYSTEM "URL"> <!-- externa privada -->
<!ENTITY % nombre PUBLIC "FPI" "URL"> <!-- externa pública -->
Las entidades parámetro internas tienen la utilidad de hacer definiciones que podemos usar repetidamente, ahorrando código y errores. Por ejemplo:
<!ENTITY % datospers "(nombre,apellidos,sexo)"> <!-- Datos personales comunes -->
<!ELEMENT adulto (%datospers;, dni)>
<!ELEMENT niño (%datospers;, dni?)>
Las externas privadas, por su parte, nos permiten hacer modular la definición de la gramática, esto es, dividir el DTD en partes bien diferenciadas y meter cada una de estas partes en un archivo independiente. Lea cuidadosamente el siguiente ejercicio para ver un ejemplo de lo expresado en este párrafo.
2.1.2.5. Espacios de nombres¶
DTD no soporta espacios de nombres, pero una etiqueta con el nombre c:claustro es válida. Por ello, podemos intentar definir DTDs que validen un XML que use espacios de nombres. Ahora bien, si revisamos el epígrafe que introduce los espacios de nombres, veremos que tenemos libertad en el XML para escoger prefijos y espacios de nombres predeterminados. En métodos de definición de la gramática como XSD o Relax-NG esto no es un problema, por lo que podremos escribir el XML como mejor nos convenga, pero sí lo es en DTD, ya que al entender c:apelativo como un nombre, y no como un nombre con un prefijo, la etiqueta siempre tendrá que ser c:apelativo.
Para el caso particular del ejemplo de profesores y direcciones, dado que no se
entremezclan los elementos de los espacios de nombres, lo más fácil es escribir
siempre el XML de la última forma que se propuso, o sea, cambiando el espacio
de nombres predeterminado y definir casilleros.dtd
así:
DTD de casilleros
<!ENTITY % departamentos SYSTEM "departamentos.ent">
<!ENTITY % direccion SYSTEM "direccion_ns.dtd">
<!ENTITY % datospers "(apelativo,nombre,apellidos)">
<!--
id: Identificador del profesor (formato i<N>)
sustituye: A quién sustituye al profesor.
casillero: Número de casillero asignado. Si no se especifica:
- En caso de profesor titular, la <N> de "id"
- En caso de sustituto, el casillero del titular.
Si está vacío, el profesor no tiene asignado casillero.
-->
<!ENTITY % attrpers
"id ID #REQUIRED
casillero NMTOKEN #IMPLIED
sustituye IDREF #IMPLIED">
%departamentos;
%direccion;
<!ELEMENT claustro (profesor|asistente)+>
<!ATTLIST claustro centro CDATA #REQUIRED
xmlns CDATA #FIXED "urn:profesores">
<!ELEMENT profesor (%datospers;,departamento?,direccion)>
<!ELEMENT asistente (%datospers;,direccion)>
<!ATTLIST profesor %attrpers;>
<!ATTLIST asistente %attrpers;>
<!ELEMENT nombre (#PCDATA)>
<!ELEMENT apellidos (#PCDATA)>
<!ELEMENT apelativo (#PCDATA)>
<!ELEMENT departamento (#PCDATA)>
y direccion.dtd
de este otro modo:
DTD de direccion
<!ELEMENT direccion (via, cp, localidad)>
<!ATTLIST direccion xmlns CDATA #FIXED "urn:direccion">
<!ELEMENT via (#PCDATA)>
<!ATTLIST via tipo (calle|callejón|travesía|plaza|avenida|cuesta|costanilla) #REQUIRED>
<!ELEMENT cp (#PCDATA)>
<!ELEMENT localidad (#PCDATA)>
En cualquier caso, cuando hay espacios de nombres, lo más juicioso es evitar DTD.
2.1.3. Ejercicios resueltos¶
Tomando el XML sobre recetas, ya resuelto en la unidad anterior, escriba un DTD para validarlo.
DTD para el XML propuesto
<!ELEMENT recetas (receta+)> <!ELEMENT receta (ingrediente+)> <!ATTLIST receta id ID #REQUIRED nombre CDATA #REQUIRED preparacion CDATA #IMPLIED> <!ELEMENT ingrediente EMPTY> <!ATTLIST ingrediente nombre CDATA #REQUIRED unidad (pieza|gramo|cc) #REQUIRED cantidad NMTOKEN #REQUIRED>
Tomando el XML sobre una cadena de restaurantes ya resuelto en el epígrafe anterior, escriba un DTD para validarlo. Procure reaprovechar el DTD del ejercicio anterior.
DTD para el XML propuesto
<!ENTITY % recetas SYSTEM "recetas.dtd"> %recetas; <!ELEMENT cadena (recetas, restaurante+)> <!ATTLIST cadena nombre CDATA #REQUIRED> <!ELEMENT restaurante (domicilio, carta)> <!ATTLIST restaurante id ID #REQUIRED nombre CDATA #IMPLIED> <!ELEMENT domicilio (direccion, municipio, cp)> <!ELEMENT direccion (#PCDATA)> <!ELEMENT municipio (#PCDATA)> <!ELEMENT cp (#PCDATA)> <!ELEMENT carta (plato+)> <!ELEMENT plato EMPTY> <!ATTLIST plato ref IDREF #REQUIRED tipo NMTOKENS #REQUIRED>
2.1.4. Conclusiones¶
Escribir la gramática de los XML con DTD presenta una serie de ventajas e inconvenientes.
- Ventajas
Su sintaxis no es XML, por lo que es más compacta. Alguno quizás podría considerar el hecho de que no sea XML un inconveniente, pero no hay más que leer una gramática escrita en XSD (o incluso en RNG que es más sencillo), para darse cuenta que una sintaxis no-XML es, por lo general, infinitamente más legible.
Es bastante sencillo de aprender y aplicar.
Es probable que cualquier validador de XML tenga soporte para DTD.
- Inconvenientes
No tiene soporte para tipos de datos, por lo que la definición del contenido de elementos de texto y atributos es muy vaga.
La definición de cada elemento no depende de su contexto (o sea, de cuál sea su padre), por lo que no puede haber dos elementos de distinto padre con igual nombre, si su definición es diferente.
No soporta espacios de nombres.
La cardinalidad está limitada a cuatro casos. No hay forma de expresar (al menos de modo simple aplicable) cardinalidades más complejas como que un elemento se pueda repetir entre 3 y 25 veces.
2.1.5. Ejercicios propuestos¶
Tome de la relación de ejercicios propuestos para la primera unidad las soluciones XML que diseñó a partir del enunciado 5, y escriba para ellas la gramática DTD.
Nota
Quizás el profesor prefiera proporcionar sus propias soluciones para evitar que el alumno defina la gramática de una solución incompleta o deficiente.
Notas al pie