Relax-NG Compact

Advertencia

Se presupone que el lector ya sabe describir la gramática con DTD. Si no es así, consulte el 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[1] 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 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 entidades generales.

    • No permite definir 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:

<?xml-model href="casilleros.rnc" type="application/relax-ng-compact-syntax"?>

Ejemplo

Si retomamos el ejemplo sobre el claustro de profesores[2]:

XML de casilleros.
<?xml version="1.0" encoding="UTF-8"?>

<claustro centro="IES Pepe Botella">
   <profesor id="p1">
      <apelativo>Pepe</apelativo>
      <nombre>José</nombre>
      <apellidos>Suárez Lantilla</apellidos>
      <departamento>Inglés</departamento>
   </profesor>

   <profesor id="p13">
      <apelativo>Cristina</apelativo>
      <nombre>María Cristina</nombre>
      <apellidos>Prieto Monagas</apellidos>
      <departamento>Biología y Geología</departamento>
   </profesor>

   <profesor id="p15">
      <apelativo>Manolo</apelativo>
      <nombre>Manuel</nombre>
      <apellidos>Páez Robledo</apellidos>
      <departamento>Matemáticas</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>Inglés</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="52">
      <apelativo>Miguel Ángel</apelativo>
      <nombre>Miguel Ángel</nombre>
      <apellidos>Campos Sánchez</apellidos>
      <departamento>Historia</departamento>
   </profesor>

   <!-- Profesor sin casillero (tienen el 0) -->

   <profesor id="p81" casillero="0">
      <apelativo>Verónica</apelativo>
      <nombre>Verónica</nombre>
      <apellidos>Martín Díaz</apellidos>
      <departamento>Biología y Geología</departamento>
   </profesor>

   <!-- Profesor sustituto -->

   <profesor id="p86" sustituye="p81">
      <apelativo>Roberto</apelativo>
      <nombre>Roberto</nombre>
      <apellidos>Mínguez Torralbo</apellidos>
   </profesor>
</claustro>

La definición mediante RNC podría ser la siguiente (buscando la mayor similitud sintáctica con DTD):

Gramática con RNC
start = claustro

claustro = element claustro { (profesor|asistente)+,
   attribute centro { text }
}

attrpers = attribute id { xsd:ID { pattern = "p[0-9]+" } },
           attribute sustituye { xsd:IDREF { pattern = "p[0-9]+" } }?,
           attribute casillero { list { xsd:positiveInteger+ } }?

profesor = element profesor { apelativo, nombre, apellidos, departamento?,
   attrpers
}

asistente = element asistente { apelativo, nombre, apellidos,
   attrpers
}

apelativo = element apelativo { text }
nombre = element nombre { text }
apellidos = element apellidos { text }
departamento = element departamento { text }

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 jing), que soporta la sintaxis compacta directamente; o bien aplicar un traductor de RNC a RNG como trang (disponible a través del paquete trang) y posteriormente realizar la validación propiamente dicha con xmlstarlet que sí soporta RNG.

Consulte los detalles en el 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 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[3] 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 { ... } }
    

Nota

No hay soporte directo para ANY pero puede emularse creando este patrón:

any = (element * { attribute * { text }*, any } | text)*

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.

    Ver también

    Vea más adelante el 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 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" }
}

Nota

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 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 tipos de datos, como es el caso del atributo id del elemento profesor.

Comentarios. Anotaciones

Por hacer

Por escribir

¿Y las entidades?

Las entidades generales definidas por el usuario permitían expresar un contenido con un nombre:

<!ENTITY HyG "Historia y Geografía">

De esta manera, incluir en el texto la entidad «&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:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE claustro SYSTEM "departamentos.ent">

<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>

   <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>

   <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>

donde /02.validacion/files/departamentos.ent es el que ya definimos en la sección dedicada a DTD.

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:

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[5]:

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[6]:

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[7], 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 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 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:

<?xml version="1.0" encoding="utf-8" ?>
<persona>
   <nombre>Perico</nombre>
   <apellidos>de los palotes</apellidos>
   <dni>41112233D</dni>
   <tlfo>953112233</tlfo>
   <direccion>
      <via tipo="calle">Callo, 5</via>
      <cp>12345</cp>
      <localidad>Villaconejos</localidad>
   </direccion>
</persona>

Mezclando dos gramáticas

Supongamos que escribimos un fichero 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:

<?xml version="1.0" encoding="UTF-8"?>
<direccion>
   <via tipo="calle">Callo, 5</via>
   <cp>12345</cp>
   <localidad>Villaconejos</localidad>
</direccion>

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 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[8]:

start = direccion | persona

ya que en direccion.rnc ya estaba definido que el elemento inicial era direccion.

El efecto es que 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[9].

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
}

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:

<?xml version="1.0" encoding="utf-8" ?>
<persona xmlns="urn:persona">
   <nombre>Perico</nombre>
   <apellidos>de los palotes</apellidos>
   <dni>41112233D</dni>
   <tlfo>953112233</tlfo>
   <direccion xmlns="urn:direccion">
      <via tipo="calle">Callo, 5</via>
      <cp>12345</cp>
      <localidad>Villaconejos</localidad>
   </direccion>
</persona>

Si consultamos el 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[10]

Nota

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.

Nota

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

  1. Tomando el XML sobre recetas, ya resuelto en un epígrafe anterior, escriba un RNC para validarlo.

    start = recetas
    
    recetas = element recetas { receta+ }
    
    receta = element receta { ingrediente+,
       attribute id { xsd:ID { pattern="r\d+" } },
       attribute nombre { text },
       attribute preparacion { xsd:decimal { minExclusive="0" } }?
    }
    
    ingrediente = element ingrediente { empty,
       attribute nombre { text },
       attribute unidad { "pieza" | "gramo" | "cc" },
       attribute cantidad { xsd:decimal { minExclusive="0" } | "lata" }
    }
    
  2. Tomando el XML sobre una cadena de restaurantes ya resuelto en un epígrafe anterior, escriba un RNC para validarlo. Procure reaprovechar el RNC del ejercicio anterior.

    start = cadena
    
    cadena = element cadena { recetas, restaurante,
       attribute nombre { text }
    }
    
    recetas = grammar { include "recetas.rnc" }
    
    restaurante = element restaurante { domicilio, carta,
       attribute id { xsd:ID { pattern="re\d+" } },
       attribute nombre { text }?
    }
    
    domicilio = element domicilio { direccion, municipio, cp }
    
    direccion = element direccion { text }
    municipio = element municipio { text }
    cp = element cp { xsd:positiveInteger { minExclusive="1000" maxExclusive="53000" } }
    
    carta = element carta { plato+ }
    
    plato = element plato { empty,
       attribute ref { xsd:IDREF { pattern="r\d+" } },
       attribute tipo { list { ("tapa" | "media" | "racion")+ } }
    }
    

Enlaces de interés

Notas al pie