.. highlight:: dtd .. _dtd: ***** |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. * :ref:`W3C XML Schema `, que es un lenguaje con sintaxis |XML| desarrollado por el |W3C|. * :ref:`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. Preliminares ************ .. _dtd-doctype: 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:: 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** .. code-block:: xml #. **Externo privado**, que será el que habitualmente usemos en estos apuntes: .. code-block:: xml 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** .. code-block:: xml 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 |FPI|\ s constituyen un `sistema heredado `_\ [#]_. Un ejemplo de declaración es la que define la versión 1.0 de |XHTML|: .. code-block:: xml #. **Híbrido privado**, que mezclan reglas definidas en un archivo externo con reglas incluidas en la propia declaración: .. code-block:: xml ]> #. **Híbrido público** .. code-block:: xml Ejemplo ======= Antes de entrar en harina, no está de más ver qué aspecto tiene un |DTD|. Para ello tomemos el :ref:`documento XML usado como ejemplo introductorio `, aunque con una ligera variación\ [#]_ (descárguelo de :download:`aquí `): .. dropdown:: XML de casilleros .. literalinclude:: files/casilleros_v2.xml :language: xml La definición de su gramática hecha en |DTD| es :download:`la siguiente `: .. dropdown:: DTD de casilleros .. literalinclude:: files/casilleros_v2.dtd Este |DTD|, a su vez, llama a otro que contiene exclusivamente :download:`las entidades que definen los departamentos `\ [#]_: .. dropdown:: DTD de definición de entidades .. literalinclude:: files/departamentos.ent .. _xml-valid: Validación ========== Antes de entrar en harina, conviene que sepamos cómo llevar a cabo las validaciones. Se propone: .. rst-class:: simple - 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\ [#]_ e indica las violaciones a ella según se escribe. - :ref:`xmlstarlet`, que tiene paquete en las distribuciones basadas en *Debian* (:deb:`xmlstarlet`). Sintaxis básica *************** .. _dtd-ele: Elementos ========= Para definir un elemento se usa la sintaxis:: 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:: ``EMPTY`` Representa un elemento vacío, esto es, un elemento sin contenido (aunque puede tener atributos):: ``(#PCDATA)`` El nodo contiene texto:: ``(elemento_hijo)`` El nodo contiene dentro de sí un nodo hijo:: 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:: - O un elemento y otro elemento, mediante la tubería:: 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*: .. code-block:: none (a,b) (a|b) (a,(b|c)) ((a,b)|c) (a,(b|c),d) (a,((b,c)|d)) .. _dtd-cardinalidad: **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:: 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: .. code-block:: xml María Isabel Peinado Sanjuán - **Una o más apariciones**, que se expresa con el signo de la **suma**:: 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**:: Con esta definición, el *claustro* podría estar vacío. .. note:: Pueden existir nodos de contenido mixto, es decir, nodos que mezclan texto con nodos hijos. En este caso, se debe escribir así:: o sea, poner ``#PCDATA`` al principio de una secuencia de elementos alternativos y añadir una cardinalidad con el asterisco. .. _dtd-attr: Atributos ========= Los atributos se definen para el elemento al que pertenecen con la siguiente sintaxis:: .. note:: Es posible definir atributos de un mismo elemento en *ATTLIST* distintos, pero lo habitual es verlos en el mismo. Tipos ----- El valor de un atributo puede ser de uno de los siguientes tipos: ``CDATA`` Texto libre:: ``(valor1|valor2|valor3|...)`` El valor debe ser uno de los incluidos en la lista de opciones:: ``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:: ``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:: 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:: 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:: ``NMTOKENS`` El valor del atributo será una lista de *tokens* tal como se han definido antes:: que traduciría un |XML| de este tipo: .. code-block:: xml .. _dtd-ej-ent-no-p: ``ENTITY`` El valor del atributo debe ser una :ref:`entidad no procesable ` definida en la gramática (lo cual requiere a su vez haber definido también una notación):: Lo que llevaría a que en el |XML| hubiera algo así: .. code-block:: xml ``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:: 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:: #. ``#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:: .. _dtd-not: Notaciones ========== Se usan para definir formatos distintos al |XML| y pueden ser tanto públicas como privadas:: El identificador del sistema suele ser el tipo |MIME| asociado a ese formato\ [#]_:: o bien:: .. _dtd-ent: 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|. .. _dtd-ent-gen: Generales --------- Las entidades generales pueden ser: .. _dtd-ent-p: **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:: o **externas** en que el texto sustitutorio se encuentra en un archivo aparte:: .. note:: Para que la sustitución fuera equivalente a la interna el archivo :file:`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):: .. _dtd-ent-no-p: **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:: donde *tipo* es un formato definido previamente mediante una :ref:`notación `. Ya se puso un :ref:`ejemplo de uso ` al hablar de los atributos cuyo tipo es una *entidad*. .. _dtd-ent-par: 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):: Las *entidades parámetro internas* tienen la utilidad de hacer definiciones que podemos usar repetidamente, ahorrando código y errores. Por ejemplo:: 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. 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 |DTD|\ s que validen un |XML| que use espacios de nombres. Ahora bien, si revisamos :ref:`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 :ref:`XSD ` o :ref:`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 :file:`casilleros.dtd` así: .. dropdown:: DTD de casilleros .. literalinclude:: files/casilleros_ns.dtd :emphasize-lines: 2, 22, 25-28 y :file:`direccion.dtd` de este otro modo: .. dropdown:: DTD de direccion .. literalinclude:: files/direccion_ns.dtd :emphasize-lines: 2 En cualquier caso, cuando hay espacios de nombres, lo más juicioso es evitar |DTD|. .. _dtd-resueltos: Ejercicios resueltos ******************** #. Tomando el |XML| sobre recetas, :ref:`ya resuelto en la unidad anterior `, escriba un |DTD| para validarlo. .. dropdown:: DTD para el XML propuesto .. literalinclude:: files/ejxml1.recetas.dtd #. Tomando el |XML| sobre una cadena de restaurantes :ref:`ya resuelto en el epígrafe anterior `, escriba un |DTD| para validarlo. Procure reaprovechar el |DTD| del ejercicio anterior. .. dropdown:: DTD para el XML propuesto .. literalinclude:: files/ejxml2.cadena.dtd 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. Ejercicios propuestos ********************* Tome de la relación de :ref:`ejercicios propuestos para la primera unidad ` las soluciones |XML| que diseñó a partir del :ref:`enunciado 5 `, y escriba para ellas la gramática |DTD|. .. note:: 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. .. rubric:: Notas al pie .. [#] De hecho, para la definición de :ref:`espacios de nombres `, que son más modernos, ya sólo se usa una |URI| para identificarlos. .. [#] La variación es que hemos incluido la definición del tipo de documento con :code:``. .. [#] Podríamos haber introducido esas definiciones dentro del propio :file:`casilleros.dtd` o incluso haberlas incluido como adición a la propia declaración del documento en el |XML|: .. dropdown:: XML de casilleros con entidades empotradas .. literalinclude:: files/casilleros_v1.xml :language: xml .. [#] También soporta :ref:`Relax-NG Compact ` y :ref:`XML Schemas `. .. [#] Los tipos |MIME| identifican de forma única los distintos tipos de archivos y se crearon en un principio para los adjuntos al correo electrónico. Véase una explicación más detallada `aquí `_. En los sistemas *linux* hay una lista de tipos mime en :file:`/etc/mime.types`. .. |FPI| replace:: :abbr:`FPI (Formal Public Identifier)` .. |URI| replace:: :abbr:`URI (Uniform Resource Identifier)` .. |RNG| replace:: :abbr:`RNG (Relax-NG XML)` .. |RNC| replace:: :abbr:`RNC (Relax-NG Compact)` .. |XSD| replace:: :abbr:`XSD (XML Schema Definition)` .. |MIME| replace:: :abbr:`MIME (Multipurpose Internet Mail Extensions)` .. |W3C| replace:: :abbr:`W3C (W3 Consortium)` .. |DTD| replace:: :abbr:`DTD (Document Type Definition)` .. |SGML| replace:: :abbr:`SGML (Standard Generalized Markup Language)` .. |XHTML| replace:: :abbr:`XHTML (eXtensible HyperText Markup Language)` .. |XSLT| replace:: :abbr:`XSLT (Extensible Stylesheet Language Transformations)` .. _Visual Studio Code: https://code.visualstudio.com