.. highlight:: rnc .. _rnc: Relax-NG Compact **************** .. warning:: Se presupone que el lector ya sabe describir la gramática con |DTD|. Si no es así, consulte el :ref:`epígrafe correspondiente `. `Relax-NG `_ es un lenguaje de esquemas para |XML|, esto es, un dialecto |XML| que sirve para definir la gramática de otros documentos |XML|. El `w3c `_ patrocina otro lenguaje de esquemas distinto: el `XML Schema `_, pero se le tilda por muchos de complejo y |RNG|, como respuesta a ello, pretende crear un lenguaje de esquemás más sencillo. En cualquier caso, como uno de los principales defectos de |XML| es ser exageradamente verborreico, *Relax-NG* presenta dos sintaxis diferentes: una |XML|, que notaremos |RNG|, y otra que no lo es y que recibe el adjetivo de **compacta**, y que notaremos como |RNC|. En estos apuntes, trataremos la versión compacta para evitar la insoportable verborrea del |XML|. Si comparamos *Relax-NG* con |DTD|\ [#]_ nos encontramos con: * **Ventajas**: - Soporta *tipado*, esto es, tiene la capacidad de ser más preciso en las definiciones del contenido textual. En |DTD| (salvo al definir atributos enumerables o *tokens*), nos tenemos que limitar a indicar que el contenido es texto libre sin más (``CDATA`` para atributos, ``#PCDATA`` para nodos). Con |RNC|, en cambio, podremos precisar que es un número entero o una palabra de ocho letras como máximo, por ejemplo. - Unifica la forma de definir el valor de los atributos y el contenido de los nodos de texto. Ya veremos que si el contenido de un atributo es un número entero deberemos definirlo como :code:`xsd:int`, que es la forma en que deberemos indicar que el contenido de un nodo lo es. - Soporta *espacios de nombres*, que es un concepto que trataremos más adelante. - La definición de los nodos depende de su contexto, por lo que es posible definir dos nodos con un mismo nombre, pero distinto contenido, siempre que estos nodos no sean nodos hermanos. Por ejemplo, en un |XML| podemos tener nodos *cliente* uno de cuyos hijos es el nodo *direccion* que expresa la dirección postal del cliente; y nodos *proveedor* uno de cuyos hijos también se llama *direccion* para expresar el mismo concepto. Con un |DTD| sería posible, pero sólo si la definición del contenido de ambos nodos *direccion* es idéntico. Si no lo fuera (p.e. porque nuestros proveedores son internacionales y necesitamos incluir en la dirección un nodo *país*, pero nuestros clientes son locales y no queremos hacer lo mismo con ellos), deberíamos usar nombres distintos. Con |RNC|, al depender la definición del contexto, es perfectamente posible que ambos nodos se llamen *direccion*. - Soporta contenido desordenado (el conector **&** que ya trataremos). * **Desventajas**: - No permite definir valores predeterminados para atributos. - No permite definir :ref:`entidades generales `. - No permite definir :ref:`notaciones `. - No hay forma de especificar que los espacios en blanco sean significativos. Por añadidura, el uso de la sintaxis compacta añade dos ventajas más: * Evita la verborrea del |XML|. * La sintaxis es concisa y, en algunos aspectos como la expresión de la cardinalidad, recuerda a |DTD|. Preliminares ============ Declaración ----------- A diferencia del |DTD|, no hay un modo estándar de relacionar un documento |XML| con el fichero |RNC| que describe su gramática. Un modo habitual de hacerlo es usar una instrucción de procesamiento: .. code-block:: xml .. _rnc-ejemplo-inicial: Ejemplo ------- Si retomamos el :ref:`ejemplo sobre el claustro de profesores `\ [#]_: .. dropdown:: XML de casilleros. .. literalinclude:: /01.intro/files/casilleros.xml :language: xml La definición mediante |RNC| podría ser la siguiente (buscando la mayor similitud sintáctica con |DTD|): .. dropdown:: Gramática con |RNC| .. literalinclude:: files/casilleros.rnc La sintaxis es bastante elocuente. Obsérvese cómo la cardinalidad la expresamos de la misma forma que en |DTD| y que hemos sido capaces de definir con bastante precisión cuál es el formato del identificador que tiene cada profesor. Validación ---------- Para validar documentos cuya gramática se ha definido mediante un |RNC| podemos valernos de `jing `_ (disponible a través del paquete :deb:`jing`), que soporta la sintaxis compacta directamente; o bien aplicar un traductor de |RNC| a |RNG| como `trang `_ (disponible a través del paquete :deb:`trang`) y posteriormente realizar la validación propiamente dicha con :command:`xmlstarlet` que sí soporta |RNG|. Consulte los detalles en el :ref:`apéndice dedicado a XMLStarlet `. Sintaxis básica =============== La sintaxis es, hasta cierto punto, semejante a la de un |DTD| Elementos --------- Se definen con la construcción:: element nombre_del_elemento { contenido } que se puede acompañar al final de la cardinalidad tal como se escribe en un |DTD|: * *Nada* implica que el elemento debe aparecer una vez * "?" que es optativo * "*" que puede no aparecer o aparecer un número arbitrario de veces. * "+" que debe aparecer al menos una vez. Por su parte, en el *contenido* se incluyen tanto los atributos (a diferencia de como se hace en |DTD|) como el contenido en sí del elemento. Los atributos pueden enumerase antes (como se hará a continuación) o después del contenido (como se hizo en el :ref:`ejemplo ilustrativo `). Otra diferencia fundamental respecto al |DTD| es que, en principio, las definiciones de cada elemento no se hacen por separado, sino que se van anidando unas dentro de otras:: claustro = element claustro { attribute centro { text }, element profesor { # La definición del elemento... }+ } Por tanto, la definición del elemento "*profesor*" se hace dentro de la definición del elemento "*claustro*". Como ello, puede dificultar la legibilidad se permite la posibilidad de dar nombre a los *patrones*, esto es, de dar nombre a elementos o atributos\ [#]_ para definirlos a continuación, lo que aproxima la sintaxis a la del |DTD|, ya que permite definir de forma independiente los elementos:: start = claustro claustro = element claustro { profesor+, attribute centro { text } } profesor = element profesor { # La definición de este elemento... } El patrón no tiene por qué tener el mismo nombre que el elemento, aunque aquí lo hayamos hecho coincidir. Por otro lado, cuando se usan *patrones* es necesario que el nodo raiz se define como el valor que recibe el patrón ``start``. Por otro lado, el contenido, contenido (o sea, el contenido exceptuando los atributos) puede ser: * ``empty``, o sea, el elemento está vacío (como ``EMPTY`` de |DTD|):: element xxx { empty } o una secuencia de: * ``text``, o sea, texto libre equivalente al ``#PCDATA`` de |DTD|:: element xxx { text } * Otro elemento:: element xxx { element yyy { ... } } .. note:: No hay soporte directo para ``ANY`` pero puede emularse creando este patrón:: any = (element * { attribute * { text }*, any } | text)* .. _rnc-solo-texto: Elementos de sólo texto """"""""""""""""""""""" Los elementos que sólo contienen texto, y que se corresponden con ``#PCDATA`` permiten una expresión muchísimo más rica que la que ofrece la palabra reservada *text*, de manera que al final el contenido no sea texto libre: * Podemos usar los tipos definidos en |XSD| usando el espacio de nombres *xsd*:: element numero { xsd:int } La consecuencia de esta definción es que ese elemento sólo podrá contener un número entero. .. seealso:: Vea más adelante el :ref:`epígrafe dedicado a estos tipos `. * Podemos restringir el contenido a una serie concreta de valores, tal como se hace en la enumeración de |DTD|:: element sexo { "varon" | "hembra" } Asimismo podemos también establecer cuál es el tipo del dato en una enumeración:: element iva { xsd:integer "4" | xsd:integer "10" | xsd:integer "21" } En este caso, tres son los valores fijos y, los tres, además, son números enteros. Es posible restringir los valores a uno solo:: element nacionalidad { "española" } lo que equivaldría a hacer fijo y obligatorio el valor del elemento. Si tratáramos de atributos. esta es la forma de definir un ``#FIXED``. Ahora bien, la enumeración no está restringida sólo a valores textuales. Esta enumeración también es válida:: element cantidad { xsd:int | "mucho" | "una pizca" } donde los posibles valores son "mucho", "una pizca" o cualquier número entero. * Podemos crear *listas*, que no son más que secuencias de palabras separadas por espacios. Por ejemplo, si queremos que el contenido sean dos números enteros:: element punto2D { list { xsd:int, xsd:int } } Por supuesto podemos usar cardinalidad:: element puntoND { list { xsd:int+ } } Secuencia """"""""" Ya se definió la :dfn:`secuencia` como el modo en que varios elementos se suceden dentro del contenido de otro y que |DTD| permitía definir dos: la sucesión representada por la coma, y la alternativa, representada por la tubería |RNC| permite definir estos dos mismos conceptos con los mismos símbolos y, además, uno adicional: **p,q** *p* seguido de *q* **p|q** O *p* o *q*. **p&q** *p* y *q*, pero en cualquier orden. Como ejemplo de definición, podemos traer la definición de profesor:: element profesor { apodo, nombre, apellidos, departamento, attribute id { xsd:ID { pattern = "p[0-9]+" } }, attribute sexo { "hombre" | "mujer" } } .. note:: Los atributos siempre se separan con comas, aunque ya sabemos que su orden es indiferente según las reglas generales del |XML|. Como en el caso del |DTD| podemos usar paréntesis y aplicar cardinalidad a varios elementos a la vez para complicar la secuencia. Atributos --------- La definición de atributos es idéntica a la de :ref:`elementos de sólo texto ` con la salvedad de que la palabra reservada para definirlos es ``attribute``:: attribute sexo { "hombre" | "mujer" } Para establecer la diferencia entre ``#IMPLIED`` y ``#REQUIRED`` basta usar la cardinalidad: un atributo declarado sin más es ``#REQUIRED``, como en el caso de los atributos del ejemplo; si se usa ``?`` entonces será ``#IMPLIED``. Por ejemplo, si en la definición anterior de profesor el atributo *sexo* fuera opcional, podríamos haber hecho:: element profesor { apodo, nombre, apellidos, departamento, attribute id { xsd:ID { pattern = "p[0-9]+" } }, attribute sexo { "hombre" | "mujer" }? } .. nota:: No es posible indicar que un atributo tendrá un valor predeterminado, si no se incluye en el |XML|. Para expresar los tipos especiales de atributos definidos en |DTD| (``ID``, ``IDREF``, ``NMTOKEN``, etc.), es necesario echar mano de los :ref:`tipos de datos `, como es el caso del atributo *id* del elemento *profesor*. Comentarios. Anotaciones ------------------------ .. todo:: Por escribir ¿Y las entidades? ----------------- Las entidades generales definidas por el usuario permitían expresar un contenido con un nombre: .. code-block:: dtd De esta manera, incluir en el texto la entidad ":code:`&HyG;`" equivale a escribir directamente "Historia y Geografía". Esto, en realidad, no es algo relativo a la gramática, sino más bien un mecanismo de sustitución de contenido. Por ese motivo, *Relax NG* no establece ningún mecanismo para definir entidades generales y no hay forma de hacerlo. Una argucia, si las usamos en el |XML|, es definirlas en un |DTD|, externo o interno. En el ejemplo de presentación hemos usado uno interno, pero podemos optar por la opción externa: .. literalinclude:: files/casilleros_v3.xml :language: xml :emphasize-lines: 3 donde :download:`/02.validacion/files/departamentos.ent` es el que ya definimos en la sección dedicada a |DTD|. .. _rnc-datatype: Tipos de datos ============== Ya se ha apuntado que |RNC| soporta tipos, pero no los define, sino que usa las definiciones ya establecidas para |XSD|. Estos tipos pueden consultarse en la `documentación de del propio W3C XML Schemas `_, o mejor aún, en el `apéndice correspondiente del libro sobre Relan-NG antes reseñado `_, que hace un resumen de la anterior. Relación -------- Sin ánimo de ser exahustivos, pueden sernos de mucha utilidad: * `xsd:ID `_, para definir identificadores. * `xsd:IDREF `_, para refefir identificadores ya definidos. * `xsd:IDREFS `_, para refefir una lista de identificadores ya definidos. * `xsd:NMTOKEM `_, equivalente al ``NMTOKEN`` de |DTD|. * `xsd:NMTOKENS `_, equivalente al ``NMTOKENS`` de |DTD|. * `xsd:integer `_, para definir números enteros. * `xsd:decimal `_, para definir números con decimales. * `xsd:string `_, para definir una cadena cualquiera\ [#]_. * `xsd:date `_, para definir fechas (p.e. "2010-08-01"). * `xsd:boolean `_, para definir un valor lógico (*true*/*false*). Una definición puede ser la siguiente:: element precio { xsd:decimal } Restricciones ------------- Al declarar el tipo también se pueden introducir restricciones. Hay distintos tipos de restricciones y dependiendo de la naturaleza del tipo de dato unas serán aplicables y otras no. Por ejemplo, en los tipos de datos de naturaleza numérica, tiene sentido establecer mínimos y máximos:: element precio { xsd:decimal { minInclusive="0" } } Pero para el caso particular de un precio en euros, también tiene sentido obligar a que haya dos decimales, así que podemos definir el contenido de este elemento de la siguiente forma\ [#]_:: element precio { xsd:decimal { minInclusive="0" fractionDigits="2" } } Pueden consultarse las referencias anteriores para ver qué restricciones pueden aplicarse a cada tipo. Una muy socorrida y que es aplicable a todos los tipos es la que permite comprobar si el valor cumple con una expresión regular\ [#]_:: attribute id { xsd:ID { pattern = "p[0-9]+" } }, En este caso obligamos a que el identificador comience por la letra "*p*" y añada un número después. Definición modular ================== Con lo visto hasta ahora, tenemos ya la mayor parte de los componentes necesarios para definir la gramática de un |XML| en un sólo documento |RNC|. Ahora bien, cuando la definición es larga o cuando queremos reaprovechar definiciones parciales hechas anteriormente para definir otro |XML|\ [#]_, es conveniente que podamos trocear en distintos ficheros |RNC|. Aunque no lo hemos declarado a las claras, cada fichero |RNC| definido hasta ahora nos han permitido fijarle una gramática completa al |XML| al que define. Ahora bien, podemos separar las definiciones en diversos ficheros de manera que el |XML| lo validen el conjunto de todos estos ficheros. Para ello, la sintaxis de |RNC| provee de dos directivas: * **external** que permite importar un patrón (o sea, un "trozo") de la gramática. * **include** qie permite importar una gramática completa Importación de patrones ----------------------- En este caso, requerimos el uso de ``external``. Un ejemplo tan ilustrativo como inútil es éste:: # ejemplo.rnc start = element raiz { external "aparte.rnc" } Y en :file:`aparte.rnc` incluímos el *patrón* al que queremos que responda el elemento *raiz*. Por ejemplo:: empty o cualquier otro más complicado:: list { xsd:int, xsd:float } Al incluir :file:`aparte.rnc` un patrón y no una gramática este fichero, por si sólo, es incapaz de servir para validar. Importación de gramáticas ------------------------- Podemos también con ``include`` importar una gramática definida en un archivo externo. Por ejemplo, consideremos el siguiente |XML|: .. literalinclude:: files/persona_nns.xml :language: xml **Mezclando dos gramáticas** Supongamos que escribimos un fichero :file:`direccion.rnc` para definir el elemento *direccion*:: start = direccion direccion = element direccion { via, cp, localidad } via = element via { text, attribute tipo { "calle"|"callejón"|"travesía"|"plaza"|"avenida"|"cuesta"|"costanilla" } } cp = element cp { xsd:int { minExclusive="1000" maxExclusive= "53000" }} localidad = element localidad { text } La definición es una gramática completa y serviría para validar documentos como éste: .. code-block:: xml Callo, 5 12345 Villaconejos Cierto es que documentos que definan una sóla dirección no son muy útiles, pero podría ser posible. Podríamos incorporar el |RNC| anterior a :file:`persona.rnc` de este modo:: include "direccion.rnc" start |= persona persona = element persona { nombre, apellidos, dni, tlfo, direccion } nombre = element nombre { text } apellidos = element apellidos { text } dni = element dni { xsd:string { pattern="[0-9]{8}[A-Za-z]" } } tlfo = element tlfo { xsd:int { minInclusive="600000000" maxInclusive="999999999" } } En este caso, lo que hacemos es combinar ambas gramáticas y obsérvese:: start |= persona que es equivalente a\ [#]_:: start = direccion | persona ya que en :file:`direccion.rnc` ya estaba definido que el elemento inicial era *direccion*. El efecto es que :file:`persona.rnc` permitirá tanto que el elemento raíz sea *persona* como que sea *dirección* y, en consecuencia, permitirá validar los dos ejemplos anteriores. Es probable que nuestra intención no fuera esa, sino que sólo pudiéramos validar documentos |XML| en que el elemento raíz fuera *persona*, pero |RNC| no nos permite redefinir por completo *start*. La solución más precisa en ese caso sería otra: anidar las gramáticas\ [#]_. .. rubric:: Anidando gramáticas En este caso, las dos gramáticas no se combinan, sino que una se incluya dentro de la otra:: start = persona persona = element persona { nombre, apellidos, dni, tlfo, direccion } nombre = element nombre { text } apellidos = element apellidos { text } dni = element dni { xsd:string { pattern="[0-9]{8}[A-Za-z]" } } tlfo = element tlfo { xsd:int { minInclusive="600000000" maxInclusive="999999999" } } direccion = grammar { include "direccion.rnc" } Escrito así, el único nodo raíz posible es *persona* y tras *tlfo* debe haber un nodo *direccion*, ya que este es el nodo raíz de la gramática anidada. Tanto si se combinan como si se anidan gramáticas, ``include`` permite modificar las definiciones hechas en el fichero original. Por ejemplo:: include "direccion.rnc" { localidad = element municipio { text } } En el caso que se requieran definir otros patrones para redefinir los existentes habrá que definirlos fuera del ``include``:: include "direccion.rnc" { localidad = nueva_definicion } # Nueva definición complicada nueva_definicion = ... Ahora bien, si se anido la gramática entonces habrá que hacer:: include "direccion.rnc" { localidad = parent nueva_definicion } .. _rnc-ns: Espacios de nombres ------------------- |RNC| soporta la definición de espacios de nombres. Por ejemplo, recordemos que antes jugamos con dos gramáticas: una que definía *persona* y otra que definía *direccion*. Supongamos que asociamos un espacio de nombres a cada gramnática: .. literalinclude:: files/persona_ns2.xml :language: xml Si consultamos el :ref:`epígrafe en el que introducíamos el concepto de espacio de nombres `, veremos que el |XML| podía adoptar distintas formas dependiendo de qué prefijos usáramos y como definiéramos los espacios de nombres predeterminados. Sin embargo, a efectos de definir el |RNC| nos es absolutamente indiferente cómo se hayan expresado los espacios de nombres en el |XML|. La forma de asignar un espacio de nombres a nuestra gramática es simple: basta con declararlo al principio del fichero:: default namespace = "urn:direccion" start = direccion direccion = element direccion { via, cp, localidad } via = element via { text, attribute tipo { "calle"|"callejón"|"travesía"|"plaza"|"avenida"|"cuesta"|"costanilla" } } cp = element cp { xsd:int { minExclusive="1000" maxExclusive= "53000" }} localidad = element localidad { text } Y en el otro fichero:: default namespace = "urn:persona" start = persona persona = element persona { nombre, apellidos, dni, tlfo, direccion } nombre = element nombre { text } apellidos = element apellidos { text } dni = element dni { xsd:string { pattern="[0-9]{8}[A-Za-z]" } } tlfo = element tlfo { xsd:int { minInclusive="600000000" maxInclusive="999999999" } } direccion = grammar { include "direccion.rnc" } En ambos casos se ha definido el espacio de nombres como el predeterminado para no tener que anteceder todos los nombres de elementos con un prefijo\ [#]_ .. note:: Es más que recomendable, cuando definimos la gramática para un tipo de |XML|, asociarla a un espacio de nombres. Si esta gramática es un poco larga, es conveniente hacerla modular, pero no definir espacios de nombres para cada fichero, a excepción de algún fichero que pudiera definir una parte de la gramática con utilidad en sí misma. .. note:: Los tipos de datos están en un espacio de nombre a parte, de ahí que usemos el prefijo *xsd*, pero no es necesarto definir cuál es este espacio, ya que las herramientas de validación lo dan por supuesto. Ejercicios resueltos ==================== #. Tomando el |XML| sobre recetas, :ref:`ya resuelto en un epígrafe anterior `, escriba un |RNC| para validarlo. .. literalinclude:: files/ejxml1.recetas.rnc #. Tomando el |XML| sobre una cadena de restaurantes :ref:`ya resuelto en un epígrafe anterior `, escriba un |RNC| para validarlo. Procure reaprovechar el |RNC| del ejercicio anterior. .. literalinclude:: files/ejxml2.cadena.rnc .. rubric:: Enlaces de interés * `Descripción del Relax NG Compact `_. * `Tutorial oficial `_. * `Libro completo "Relax NG" `_ .. rubric:: Notas al pie .. [#] Las ventajas y desventajas expresadas a continuación son una indisimulada reescritura de `esta comparación `_. .. [#] Hemos vuelto a la primera versión del ejemplo en el que se incluía la definición de las entidades dentro del |DTD|, porque |RNC| no las soporta. Lo que sí podríamos hacer es eliminar referencia a :file:`casillero.dtd`: .. code-block:: xml -- Resto de entidades -- ]> .. [#] En puridad, no sólo para elementos y atributos. Podemos hacer otro tipo de asignaciones que quizás también resultan útiles y que se entenderán más adelante:: tupla = xsd:int, xsd:int .. [#] O sea que equivale a ``text``. Pero la ventaja de este tipo de dato es que |XSD| pemite añadir restricciones a la definición general, como obligar a que esa cadena cumpla con una expresión regular. Se verá a continuación. .. [#] La definición propuesta obliga a poner siempre dos decimales, así que un precio de *1* o de *1.5* fallará. Para incluir estas posibilidades habría que haber recurrido a una expresión regular:: element precio { xsd:decimal { minInclusive="0" pattern="\.\d{1,2}|\d+(\.\d{1,2})?" } } .. [#] *Relax-NG* usa las expresiones definidas para *W3C XML Schema* que son expresiones regulares de tipo :abbr:`ERE (Extended Regular Expresion)`. .. [#] Por ejemplo, la definición de un domicilio postal puede servirnos en muchos |XML| que tengan distinto propósito. .. [#] Los patrones también pueden redefinirse con :code:`&=` o con :code:`=`, este último para sustituir totalmente la definición antigua. En el caso de *start*, no obstante*^esto último no es posible. .. [#] Por lo general, en las gramáticas de amplio uso, como |URN| se toma una |URL| caracerística. Por ejemplo, los datos de |XML| Schema están definidos en el espacio de nombres "*http://www.w3.org/2001/XMLSchema-datatypes*". .. [#] La declaración de un espacio de nombres y su prefijo cuando no es el predeterminado se hace así:: namespace p = "urn:persona" Ello obligaria a usar tal prefijo en las definiciones de los elementos. Por ejemplo:: apellidos = element p:apellidos { text } .. |RNG| replace:: :abbr:`RNG (Relax-NG)` .. |RNC| replace:: :abbr:`RNC (Relax-NG Compact)` .. |XSD| replace:: :abbr:`XSD (XML Schema Definition)` .. |DTD| replace:: :abbr:`DTD (Document Type Definition)` .. |URN| replace:: :abbr:`URN (Uniform Resource Name)`