W3C XML Schema¶
Introducción¶
Las XML Schemas (esquemas XML, en castellano) son un método alternativo a los para la definición de la gramática de un dialecto XML. Su utilidad radica en que tienen una serie de ventajas sobre ellos:
Son en sí mismos, también dialectos XML.
Comparados con DTD, permiten una definición mucho más precisa del tipo de contenido de los atributos y elementos. Por ejemplo, podremos especificar que el contenido es un número entero o una cadena de caracteres.
Soportan espacios de nombres.
Son más modulares y extensibles.
Ahora bien, no todo son ventajas. También presentan desventajas frente a los DTD:
Son más complejos de entender que los DTD.
Son dialectos XML. Sí esto es una ventaja, pero también una desventaja. Ya se expondrá más adelante por qué.
No permiten definir entidades. Las entidades parámetro se pueden sustituir por la definición de tipos y atributos derivados, pero no hay modo de emular a las entidades generales.
No son el único dialecto XML que existe para esta tarea: Relax NG, por ejemplo, también se creo para lo mismo.
Vistazo rápido¶
Antes de meternos en harina, miremos por encima cómo es la definición de un documento con XML Schemas. Sea el siguiente documento:
<?xml version="1.0" encoding="utf-8" ?>
<persona>
<nombre>Perico</nombre>
<apellidos>de los palotes</apellidos>
<dni>41112233</dni>
<tlfo>953112233</tlfo>
</persona>
Para el cual podríamos tener la siguiente definición DTD:
<!ELEMENT persona (nombre,apellidos,dni,tlfo)>
<!ELEMENT nombre (#PCDATA)>
<!ELEMENT apellidos (#PCDATA)>
<!ELEMENT dni (#PCDATA)>
<!ELEMENT tlfo (#PCDATA)>
Y un XML schema apropiado podría ser:
<?xml version="1.0" encoding="utf-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:element name="persona">
<xs:complexType>
<xs:sequence>
<xs:element name="nombre" type="xs:string" />
<xs:element name="apellidos" type="xs:string" />
<xs:element name="dni" type="xs:integer" />
<xs:element name="tlfo" type="xs:integer" />
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:schema>
Tipos simples¶
En las definiciones mediante XML schemas, elementos y atributos pueden ser de tipo simple o de tipo compuesto. Bajo este epígrafe se desarrollarán ambos.
Elementos simples¶
Los elementos simples son aquellos que contienen únicamente texto y no tienen ningún atributo. En el ejemplo anterior nombre, apellidos, dni y tlfo. Sin embargo, a diferencia de un DTD, puede definirse con más exactitud de qué tipo será ese texto. Algunos de estos tipos, sin ser muy exhaustivos, son:
xs:string
Cadenas de caracteres.
xs:integer
Números enteros.
xs:decimal
Números con decimales.
xs:boolean
Valores lógicos, que pueden ser 0,1,``true`` o
false
.xs:date
Fechas en formato AAAA-MM-DD.
xs:time
Horas en formato HH:MM:SS.
Una relación completa puede ser consultada en este pdf
.
Además de definir el tipo, se puede indicar si el elemento tendrá un valor predeterminado para el caso de que no se incluya:
<xs:element name="color" type="xs:string" default="rojo"/>
o un valor fijo, que no puede ser modificado:
<xs:element name="color" type="xs:string" fixed="rojo"/>
Atributos simples¶
Al igual que los elementos su valor es puro texto. Se definen exactamente igual
que los elementos, excepto por el hecho que se usan la cláusula xs:attribute
en vez de xs:element
. Para los casos en los que el atributo era de tipo
CDATA
en la definición mediante DTD, se pueden usar los tipos que se han
indicado para los elementos. Ahora bien, los atributos pueden ser de otro tipo
(identificadores, etc.) y ello también puede hacerse con XML schemas:
xs:ID
El valor del atributo será un identificador.
xs:IDREF
El valor del atributo hará referencia a un identificador.
xs:IDREFS
El valor del atributo hará referencia a una lista de identificadores.
xs:NMTOKEN
El valor del atributo es una palabra XML válida.
xs:NMTOKENS
El valor del atributo es una lista de palabras XML válidas.
Asimismo, los atributos pueden tener un valor predeterminado o tener un valor fijo, cosa que se hace exactamente del mismo modo que para los elementos. Los atributos son, en principio, opcionales, así que se puede especificar si son obigatorios:
<xs:attribute name="idper" type="xs:ID" use="required" />
Restricciones¶
Las restricciones (tanto en elementos como en atributos) sirven para eliminar libertad en los valores asignados. Cuando se quieren incluir restricciones, la sintaxis se enrevesa un poco:
<xs:element name="edad">
<xs:simpleType>
<xs:restriction base="xs:integer">
<xs:minInclusive value="0" />
<xs:maxInclusive value="120" />
</xs:restriction>
</xs:simpleType>
</xs:element>
Básicamente la definición de un elemento (o un atributo) que tenga restricciones consiste en definirlo como tipo simple, indicar que tipo base ya definido se usa, y a continuación indicar cuáles son las restricciones que han de aplicarse. En el PDF anterior están expuestan también cuáles son estar restricciones posibles. No obstante, se enunciarán algunas muy recurrentes:
Para restringir los valores a unos concretos, tal como se hacía en los DTD, debe usarse la cláusla
xs:enumeration
. Por ejemplo:<xs:attribute name="rango_edad"> <xs:simpleType> <xs:restriction base="xs:string"> <xs:enumeration value="niño" /> <xs:enumeration value="joven" /> <xs:enumeration value="adulto" /> <xs:enumeration value="anciano" /> </xs:restriction> </xs:simpleType> </xs:attribute>
Una forma poderosa de restrindir cadenas de caracteres, es mediante el uso de expresiones regulares con
xs:pattern
. Por ejemplo, si quisiésemos que sólo se pudiesen escribir identificadores con la forma «id»+numero podríamos hacer:<xs:attribute name="idper"> <xs:simpleType> <xs:restriction base="xs:ID"> <xs:pattern value="id[0-9]+" /> </xs:restriction> </xs:simpleType> </xs:attribute>
Para determinar cómo trataremos los espacios que aparezcan, puede usarse
xs:whiteSpace
con tres posibles valores: preserve, que los dejará tal cual; replace, que hará que sean sustituidos todos las tabulaciones, cambios de línea y retornos de carro por espacios; y collapse que hace lo mismo, pero además reduce a un sólo espacio varios seguidos y elimina los espacios con los que empieza y acaba una cadena. Por ejemplo:<xs:element name="nombre"> <xs:simpleType> <xs:restriction base="xs:string"> <xs:whiteSpace value="collapse" /> </xs:restriction> </xs:simpleType> </xs:element>
Pueden, obviamente, incluirse varias restricciones en la misma definición. De hecho, en el primer ejemplo se usaron dos:
xs:minInclusive
yxs:maxInclusive
. Es también necesario notar que todas las restricciones no tiene significado para un tipo determinado. Por ejemplo paraxs:string
no tiene sentido la de valor mínimo ni la de valor máximo.
XML schemas tiene definidos muchos tipos de valores, algunos derivados de
otros y cuyas definiciones podría haber hecho el usuario mismo definiendo
restricciones. Por ejemplo, el tipo xs:normalizedString
es un tipo
xs:string
con la restricción del último ejemplo.
Tipos derivados¶
Un usuario también puede definir tipos derivados de tipos simples ya definidos. Por ejemplo, podríamos hacer la siguiente definición (basándonos en este ejemplo anterior):
<xs:attribute name="idper" type="ident.numerico" />
<xs:simpleType name="ident.numerico">
<xs:restriction base="xs:ID">
<xs:pattern value="id[0-9]+" />
</xs:restriction>
</xs:simpleType>
La gracia de esta definición es que este nuevo tipo se podría usar para la definición de todos los atributos que quisiésemos.
Tipos complejos¶
Elementos complejos son aquellos que contienen elementos o tienen atributos. O ambos, claro. Sólo tiene sentido hablar de complejos en el caso de los elementos, porque el valor de los atributos siempre es texto, más o menos restringido, pero sólo texto.
Un elemento complejo es un ComplexType
y puede contener sólo texto o contener a
su vez otros elementos. Si contiene sólo texto su contenido será simpleContent
:
<xs:element name="tlfo">
<xs:complexType>
<xs:simpleContent>
<!-- Ya veremos qué se pone aquí -->
</xs:simpleContent>
</xs:complexType>
</xs:element>
Si contiene otros elementos, su contenido será complexContent
:
<xs:element name="persona">
<xs:complexType>
<xs:complexContent>
<!-- Ya veremos qué se pone aquí -->
</xs:complexContent>
</xs:complexType>
</xs:element>
Pero ¿qué es lo que se pone dentro de simpleContent
o complexContent
?
Simplemente una restricción o una extensión a un tipo que se toma como base. Al
restringirlo, limitamos lo que en un principio se podía incluir en el elemento
y, si lo extendemos, lo que haremos es añadir componentes a los originales del
elemento. Analicémoslo por separado.
Contenido simple (texto solo)¶
Los tipos que se pueden tomar como base son los que se vieron ya cuando el elemento era simple: xs:string, xs:integer, etc. Y a estos tipos base se le pueden añadir restricciones o extensiones (pero no ambas a la vez). Por ejemplo, si añadimos restricciones:
<xs:element name="edad">
<xs:complexType>
<xs:simpleContent>
<xs:restriction base="xs:integer">
<xs:minInclusive value="0" />
<xs:maxInclusive value="120" />
</xs:restriction>
</xs:simpleContent>
</xs:complexType>
</xs:element>
De esta definición resulta un elemento llamado edad, que es de tipo complejo, cuyo contenido puede ser un número entero entre 0 y 120 y que tiene atributos, porque no aparecen por ningún sitio definidos. Ahora bien, esto no es un tipo simple con una restricción, tal como se hizo en este ejemplo? Sí, efectivamente, hemos venido a definir más rocambolescamente lo mismo. Es más, si hubiésemos definido así:
<xs:element name="entero">
<xs:complexType>
<xs:simpleContent>
<xs:restriction base="xs:integer" />
</xs:simpleContent>
</xs:complexType>
</xs:element>
habríamos definido exactamente lo mismo que si hubiésemos hecho simplemente esto:
<xs:element name="entero" type="xs:integer" />
Mucho código para casi nada. En realidad, si el contenido es simple, lo interesante es extender, no restringir; porque extender nos permite definir atributos para el elemento:
<xs:element name="edad">
<xs:complexType>
<xs:simpleContent>
<xs:extension base="xs:integer">
<xs:attribute name="unidad" fixed="años" />
</xs:extension>
</xs:simpleContent>
<xs:complexType>
</xs:element>
Ahora sería válido que en el xml encontrásemos la siguiente línea:
<edad unidad="años">15</edad>
En el ejemplo, se ha extendido el tipo para añadir un atributo. Obviamente se
pueden añadir todos los atributos que se quiera: basta con incluir más elementos
xs:attribute
dentro de xs:extension
. Ahora bien, ¿qué se puede hacer si
queremos restringir y extender a la vez: restringir para limitar el contenido
del elemento (en el ejemplo, al rango 0-120) y extender para poder incluir
atributos? No se pueden incluir xs:restriction
y xs:extension
a la vez,
así que la solución pasa por hacer un tipo derivado de xs:integer
con la
restricción y usarlo como base luego en la extensión:
<xs:simpleType name="tipo.edad">
<xs:restriction base="xs:integer">
<xs:minInclusive value="0" />
<xs:maxInclusive value="120" />
</xs:restriction>
</xs:simpleType>
<xs:element name="edad">
<xs:complexType>
<xs:simpleContent>
<xs:extension base="tipo.edad">
<xs:attribute name="unidad" fixed="años" />
</xs:extension>
</xs:simpleContent>
<xs:complexType>
</xs:element>
Contenido complejo¶
Ya sabemos que son elementos con contenido complejo aquellos contienen otros
elementos. El principio para definir el contenido complejo es el mismo que para
el simple: se toma un tipo base y se definen restricciones o extensiones para
él. Ahora bien, ¿qué tipo complejo tomamos como base? Evidentemente podemos
tomar uno que ya definiésemos antes, pero ¿y si quiero hacer una definición
partiendo de cero? Entonces podemos partir de un tipo llamado xs:anyType
,
que permite absolutamente cualquier atributo y cualquier elemento dentro de él,
y definirle restricciones. Las restricciones de un tipo complejo consisten en
volver a especificar su contenido, pero añadiéndole restricciones. Como en
xs:anyType
cabe cualquier cosa, si se incluyen como restricción los
elementos y atributos que se desea que contenga el elemento, esos serán los
únicos elementos y atributos que podrá contener.
Así, pues, puedo hacer lo siguiente:
<xs:element name="persona">
<xs:complexType>
<xs:complexContent>
<xs:restriction base="xs:anyType">
<!-- Primero se definen los elementos -->
<!-- Después se definen los atributos -->
</xs:restriction>
</xs:complexContent>
<xs:complexType>
</xs:element>
La definición de atributos ya se sabe cómo es: con xs:attribute
tal y como
se ha venido haciendo hasta ahora. Lo que merece apunte aparte es cómo se
definen los elementos. Recuérdese que en los DTD se habló de la secuencia y
cardinalidad de elementos. En realidad hay otro concepto implícito, que es la
agrupación de elementos. Fijémonos en esto:
(a|(b,c)+)
En la expresión está el concepto de secuencia, representado por la tubería (|
) y
la coma (,
); está el concepto de la cardinalidad, representado por el signo
más (+), pero también está el concepto de la agrupación: la cardinalidad del
+
se aplica a la secuencia b,c
, porque hay un paréntesis que así lo
determina. En los XML Schemas hay que habilitar un mecanismo equivalente,
para que se puedan especificar estos tres conceptos.
Cardinalidad¶
Recordemos que indica el número de veces que se puede repetir un elemento. Para
notarlo se añaden como atributos a la definición xs:element
, los atributos
minOccurs (mínimo de ocurrencias) y maxOccurs (número máximo de
ocurrencias). Por ejemplo:
<xs:element name="hijo" type="tipo.persona" minOccurs="0" maxOccurs="unbounded">
unbounded significa que no hay límite máximo. Esto es equivalente en DTD a
nombre_elemento*
. Cuando no se especifican minOccurs o maxOccurs, se
sobreentiende que valen 1.
Secuencia¶
Hay tres secuencias diferentes:
xs:sequence
Determina que los elementos deben aparecer en el orden que se especifica (equivale a lo coma en DTD):
<xs:complexType name="tipo.persona"> <xs:complexContent> <xs:restriction base="xs:anyType"> <xs:sequence> <xs:element name="nombre" type="xs:string" /> <xs:element name="apellidos" type="xs:string" /> <xs:element name="dni" type="xs:integer" /> <xs:element name="tlfo" type="xs:integer" /> <xs:element name="hijo" type="tipo.persona" minOccurs="0" maxOccurs="unbounded"> </xs:sequence> </xs:restriction> </xs:complexContent> </xs:complexType>
xs:choice
Provoca que deba aparecer uno (y solo uno) de los elementos que se especifican como alternativa (equivale a la tubería del DTD):
<xs:complexType name="tipo.educador"> <xs:complexContent> <xs:extension base="tipo.persona"> <xs:choice> <xs:element name="colegio" type="tipo.centro"/> <xs:element name="instituto" type="tipo.centro"/> </xs:choice> </xs:extension> </xs:complexContent> </xs:complexType>
xs:all
Fuerza a que aparezcan todos los elementos que se especifican en el orden que se desee, pero como máximo una vez (no tiene equivalencia en DTD):
<xs:complexType name="tipo.persona"> <xs:complexContent> <xs:restriction base="xs:anyType"> <xs:all> <xs:element name="nombre" type="xs:string" /> <xs:element name="apellidos" type="xs:string" /> <xs:element name="dni" type="xs:integer" /> <xs:element name="tlfo" type="xs:integer" /> </xs:all> </xs:restriction> </xs:complexContent> </xs:complexType>
En este caso, no puede especificarse maxOccurs (porque siempre debe valer 1), y minOccurs sólo puede valer 1 ó 0
Agrupación¶
Para emular el paréntesis de los dtd, es posible incluir los atributos
minOccurs y maxOccurs dentro de etiquetas xs:sequence
o xs:choice
.
De este modo, la expresión (a,(b|c)*)
podría escribirse del siguiente
modo:
<xs:complexType name="tipo.foo">
<xs:complexContent>
<xs:restriction base="xs:anyType">
<xs:sequence>
<xs:element name="a" type="xs:string" />
<xs:choice minOccurs="0" maxOccurs="unbounded">
<xs:element name="b" type="xs:string" />
<xs:element name="c" type="xs:string" />
</xs:choice>
</xs:sequence>
</xs:restriction>
</xs:complexContent>
</xs:complexType>
Obsérvese cómo se ha anidado un xs:choice
, dentro de un xs:sequence
. Es
posible también darle nombre a determinadas agrupaciones para que se puedan
reusar varias veces. Esto se logran con el elemento xs:group
:
<xs:group name="parentesis">
<xs:choice>
<xs:element name="b" type="xs:string" />
<xs:element name="c" type="xs:string" />
</xs:choice>
</xs:group>
<xs:complexType name="tipo.foo">
<xs:complexContent>
<xs:restriction base="xs:anyType">
<xs:sequence>
<xs:element name="a" type="xs:string" />
<xs:group ref="parentesis" minOccurs="0" maxOccurs="unbounded" />
</xs:sequence>
</xs:restriction>
</xs:complexContent>
</xs:complexType>
Esto último permite reutilizar esta definición de grupo en otras definiciones y es un mecanismo que sustituye a las entidades parámetro de DTD.
Del mismo modo que pueden definirse grupos de elementos, también pueden definirse grupos de atributos, que no sirven para aplicar cardinalidad (porque los atributos no la tienen), pero sí para reutilizar sus definiciones en distintos elementos:
<xs:attributeGroup name="idti">
<xs:attribute name="id" type="xs:ID" use="required">
<xs:attribute name="tipo" type="xs:NMTOKEN">
</xs:attributeGroup>
<xs:element name="coche">
<xs:complexType>
<xs:complexContent>
<xs:restriction base="xs:anyType">
<xs:attributeGroup ref="idti" />
<xs:attribute name="matricula" type="xs:string" />
</xs:restriction>
</xs:complexContent>
</xs:complexType>
<xs:element>
Nota
Obsérvese que en este caso, no se usó xs:sequence
o xs:choice
:
con atributos no tiene sentido porque los atributos sólo pueden aparecer como
máximo una vez y en cualquier orden.
Simplificación¶
Ya se ha visto que, cuando se quiere definir un tipo complejo con contenido
complejo, se puede recurrir a la restricción del tipo base complejo xs:anyType
.
Esto habría de hacerse así, pero se admite una simplificación de la sintaxis: se
puede incluir dentro de xs:complexType
lo que se habría incluido dentro de
xs:restriction
. Así pues, esta definición, podría
haber sido simplemente:
<xs:element name="persona">
<xs:complexType>
<!-- Primero se definen los elementos -->
<!-- Después se definen los atributos -->
</xs:complexType>
</xs:element>
y la forma larga sólo se usará cuando estemos restringiendo o derivando un tipo complejo ya definido, tal como se tratará en un epígrafe posterior.
Tipos complejos mixtos¶
Un elemento de tipo complejo mixto es aquel que a la vez contiene texto y elementos. Por ejemplo, supongamos este fragmento de XML:
<p>Esto es un ejemplo de párrafo en HTML, en los que puede
haber palabras en <i>cursiva</i> o en <b>negrita</b>.</p>
En el elemento p se observa que hay texto, pero arrebujado dentro elementos
i y b. Para definir contenido mixto, basta con incluir el atributo
.code:mixed=»true» en xs:complexType
(o a xs:complexContent
, si se usa
la versión larga de la definición):
<xs:element name="p">
<complexType mixed="true">
<xs:sequence>
<xs:group minOccurs="0" maxOccurs="unbounded">
<xs:choice>
<xs:element name="i" type="xs:string"/>
<xs:element name="b" type="xs:string"/>
</xs:choice>
</xs:group>
</xs:sequence>
</complexType>
</xs:element>
Tipos derivados¶
De la misma manera que con los tipos simples es posible generar tipos derivados a partir de tipos complejos: basta con darle un nombre al tipo cuando se define. Por ejemplo, si en un XML voy a usar varios elementos que representan personas, quizás me convenga definir un tipo complejo persona para a partir de él definir los elementos:
<xs:complexType name="tipo.persona">
<xs:sequence>
<xs:element name="nombre" type="xs:string" />
<xs:element name="apellidos" type="xs:string" />
<xs:element name="dni" type="xs:integer" />
<xs:element name="tlfo" type="xs:integer" />
<xs:element name="hijo" type="tipo.persona" minOccurs="0" maxOccurs="unbounded">
</xs:sequence>
<xs:attribute name="id" type="xs:ID" use="required" />
</xs:complexType>
A partir de este tipo complejo, puedo definir elementos que sea exactamentede este tipo, elementos que sean una extensión de este tipo o elementos que sean una restricción de este tipo. Por supuesto, también puedo definir otro tipo a partir de él.
Imaginemos que queremos definir tres elementos: adulto, niño y anciano. Los tres elementos son personas, así que tendrán nombre, apellidos, etc. Para el elemento anciano, podemos considerar que esta definición de tipo.persona es perfecta; así que podemos hacer, simplemente:
<xs:element name="anciano" type="tipo.persona" />
Al querer definir el elemento adulto nos podemos topar con que nos interesaría almacenar infomarción también de su sueldo, si trabaja. En ese caso podemos ampliar tipo.persona:
<xs:element name="adulto">
<xs:complexType>
<xs:complexContent>
<xs:extension base="tipo.persona">
<xs:sequence>
<xs:element name="sueldo" type="xs:integer" minOccurs="0" />
</xs:sequence>
</xs:extension>
</xs:complexContent>
</xs:complexType>
</xs:element>
Con el niño en cambio nos sucede justo lo contrario: el elemento hijo nos sobra, porque no tendrá ninguno. Incluso puede tener o no DNI. Así que en este caso tenemos que restringir la definición del tipo base. Para restringir tenemos que volver a definir todos los elementos y atributos que queremos que contenga el nuevo tipo: los que no aparezcan no serán válidos en el nuevo tipo. También podemos afinar las restricciones en elementos que sí citemos:
<xs:element name="niño">
<xs:complexType>
<xs:complexContent>
<xs:restriction base="tipo.persona">
<xs:sequence>
<xs:element name="nombre" type="xs:string" />
<xs:element name="apellidos" type="xs:string" />
<xs:element name="dni" type="xs:integer" minOccurs="0" />
<xs:element name="tlfo" type="xs:integer" />
</xs:sequence>
<xs:attribute name="id" type="xs:ID" use="required" />
</xs:restriction>
<xs:complexContent>
</xs:complexType>
</xs:element>
Obsérvese que las modificaciones sobre el tipo base han sido dos: se ha
eliminado el elemento hijo y se permite que dni pueda no aparecer. Sin
embargo, esto no es correcto. Cuando se restringe un elemento, no se pueden
crear restricciones que no existiesen ya en el tipo base. Por ese motivo, es
válido eliminar el elemento hijo, ya que en el elemento base estaba recogido el
caso de que no hubiese elementos hijo. Sin embargo, en el tipo base dni
debía aparecer una vez y aquí indicamos que puede no aparecer ninguna. Esto no
puede hacerse a menos que en tipo.persona incluyamos también
minOccurs="0"
para dni
Casos particulares¶
Tipo vacío sin atributos¶
<xs:complexType name="empty">
<xs:complexContent>
<xs:restriction base="xs:anyType" />
</xs:complexContent>
</xs:complexType>
o de forma más sucinta:
<xs:complexType name="empty" />
Tipo vacío con atributos¶
<xs:complexType name="empty.attr">
<xs:attribute name="atributo1" type="xs:string"/>
<xs:attribute name="atributo2" type="xs:integer"/>
</xs:complexType>
Tipo texto con atributos¶
<xs:complexType name="texto.attr">
<xs:simpleContent>
<xs:extension base="xs:string">
<xs:attribute name="atributo1" type="xs:string"/>
<xs:attribute name="atributo2" type="xs:integer"/>
</xs:extension>
</xs:simpleContent>
</xs:complexType>
Consejos de escritura¶
Los documentos XML permiten estructurar muy eficazmente los información y permiten su fácil procesado. En el caso particular de los XSD, permiten ser mucho más preciso al definir el contenido de elementos y atributos que los DTD. Sin embargo, no todo son ventajas: los documentos XML son fatigosos de leer por un humano, fundamentalmente porque son muy prolijos y gran parte del documento del contenido es metainformación y no información.
Imaginemos un XML como éste:
<?xml version="1.0" encoding="utf-8" standalone="no"?>
<!DOCTYPE gente SYSTEM "gente.dtd">
<gente xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="gente.xsd">
<persona id="i1">
<nombre apodo="Pepe">Jose</nombre>
<apellidos>Moro Mayo</apellidos>
<dni>11223344</dni>
<flota>
<vehiculo tipo="turismo">
<matricula numeracion="antigua">J-5464-AP</matricula>
<color>azul</color>
<marca>Citroën</marca>
<modelo>Picasso</modelo>
</vehiculo>
<vehiculo tipo="furgoneta">
<matricula>5464-BBB</matricula>
<color>verde</color>
<marca>Ford</marca>
<modelo>Transit</modelo>
</vehiculo>
<!-- Resto de vehículos posea -->
</flota>
</persona>
<!-- Resto de personas -->
</gente>
Si intentamos definir su gramática con un DTD, no podremos ser muy precisos en el formato de los contenidos de elementos y atributos, pero a cambio es fácil de leer, porque todo es muy esquemático:
<!ELEMENT gente (persona+)>
<!ATTLIST gente xmlns CDATA #IMPLIED>
<!ELEMENT persona (nombre,apellidos,dni,flota)>
<!ATTLIST persona id ID #REQUIRED>
<!ELEMENT nombre (#PCDATA)>
<!ATTLIST nombre apodo CDATA #IMPLIED>
<!ELEMENT apellidos (#PCDATA)>
<!ELEMENT dni (#PCDATA)>
<!ELEMENT flota (vehiculo*)>
<!ELEMENT vehiculo (matricula,color,marca,modelo)>
<!ATTLIST vehiculo tipo (turismo|furgoneta|moto|camión|autobús) "turismo">
<!ELEMENT matricula (#PCDATA)>
<!ATTLIST matricula numeracion (antigua|nueva) "nueva">
<!ELEMENT color (#PCDATA)>
<!ELEMENT marca (#PCDATA)>
<!ELEMENT modelo (#PCDATA)>
Es fácil saber, de un único vistazo, cuáles son los elementos que puede contener el elemento persona o qué atributos y de qué tipo puede contener el atributo matricula. En cambio, una definición igual de imprecisa con un XSD (sin afinar el formato que debe tener un DNI o una matrícula) tiene esta pinta:
Un XSD que parece la carretera de la sierra…
<?xml version="1.0" encoding="utf-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:element name="gente">
<xs:complexType>
<xs:sequence>
<xs:element name="persona" maxOccurs="unbounded">
<xs:complexType>
<xs:sequence>
<xs:element name="nombre">
<xs:complexType>
<xs:simpleContent>
<xs:extension base="xs:string">
<xs:attribute name="apodo" type="xs:string" />
</xs:extension>
</xs:simpleContent>
</xs:complexType>
</xs:element>
<xs:element name="apellidos" type="xs:string" />
<xs:element name="dni" type="xs:string" />
<xs:element name="flota">
<xs:complexType>
<xs:sequence>
<xs:element name="vehiculo" minOccurs="0" maxOccurs="unbounded">
<xs:complexType>
<xs:sequence>
<xs:element name="matricula">
<xs:complexType>
<xs:simpleContent>
<xs:extension base="xs:string">
<xs:attribute name="numeracion" default="nueva">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:enumeration value="antigua"/>
<xs:enumeration value="nueva"/>
</xs:restriction>
</xs:simpleType>
</xs:attribute>
</xs:extension>
</xs:simpleContent>
</xs:complexType>
</xs:element>
<xs:element name="color" type="xs:string" />
<xs:element name="marca" type="xs:string" />
<xs:element name="modelo" type="xs:string" />
</xs:sequence>
<xs:attribute name="tipo" default="turismo">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:enumeration value="turismo" />
<xs:enumeration value="furgoneta" />
<xs:enumeration value="moto" />
<xs:enumeration value="camión" />
<xs:enumeration value="autobús" />
</xs:restriction>
</xs:simpleType>
</xs:attribute>
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:sequence>
<xs:attribute name="id" type="xs:ID" />
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:schema>
Y, ahora, ¿qué elementos puede contener el elemento flota? Es difícil decirlo con un vistazo. Incluso con diez. Es tanta la cantidad de metainformación, tan larga y tan prolija, que es incluso difícil de escribir el documento sin que olvidemos por qué atributo o elemento íbamos definiendo. Esto, en realidad, es un problema intrínseco al XML: no puede solucionarse, pero sí al menos paliarse. En los XSD la mejor forma de tener unas definiciones más compactas, es intentar no anidar las definición de tipos dentro de elementos, sino definir aparte los tipos complejos e ir incorporando esa definición a la definición de otros tipos y elementos. No se logra con esto que el documento sea más corto, al contrario; pero se consigue separar unas definiciones de otras, de modo que cada una de ellas sea más compacta.
Partiendo de esta premisa se puede obrar del siguiente modo: empezamos definiendo a partir del elemento raíz y, cuando nos topamos con la definición de un tipo complejo (o un tipo simple con restricciones), no lo definimos hay mismo sino que le damos un nombre a ese tipo y lo definimos aparte:
Una solución más legible…
<?xml version="1.0" encoding="utf-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:element name="gente">
<xs:complexType>
<xs:sequence>
<xs:element name="persona" type="tipo.persona" maxOccurs="unbounded" />
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:complexType name="tipo.persona">
<xs:sequence>
<xs:element name="nombre" type="tipo.nombre" />
<xs:element name="apellidos" type="xs:string" />
<xs:element name="dni" type="xs:string" />
<xs:element name="flota" type="tipo.flota" minOccurs="0" maxOccurs="unbounded" />
</xs:sequence>
<xs:attribute name="id" type="xs:ID" />
</xs:complexType>
<xs:complexType name="tipo.nombre">
<xs:simpleContent>
<xs:extension base="xs:string">
<xs:attribute name="apodo" type="xs:string"/>
</xs:extension>
</xs:simpleContent>
</xs:complexType>
<xs:complexType name="tipo.flota">
<xs:sequence>
<xs:element name="vehiculo" type="tipo.vehiculo" minOccurs="0" maxOccurs="unbounded" />
</xs:sequence>
</xs:complexType>
<xs:complexType name="tipo.vehiculo">
<xs:sequence>
<xs:element name="matricula" type="tipo.matricula" />
<xs:element name="color" type="xs:string" />
<xs:element name="marca" type="xs:string" />
<xs:element name="modelo" type="xs:string" />
</xs:sequence>
<xs:attribute name="tipo" type="tipo.tipo" />
</xs:complexType>
<xs:complexType name="tipo.matricula">
<xs:simpleContent>
<xs:extension base="xs:string">
<xs:attribute name="numeracion" type="tipo.numeracion" />
</xs:extension>
</xs:simpleContent>
</xs:complexType>
<xs:simpleType name="tipo.numeracion">
<xs:restriction base="xs:string">
<xs:enumeration value="antigua"/>
<xs:enumeration value="nueva"/>
</xs:restriction>
</xs:simpleType>
<xs:simpleType name="tipo.tipo">
<xs:restriction base="xs:string">
<xs:enumeration value="turismo" />
<xs:enumeration value="furgoneta" />
<xs:enumeration value="moto" />
<xs:enumeration value="camión" />
<xs:enumeration value="autobús" />
</xs:restriction>
</xs:simpleType>
</xs:schema>
El documento resultante no sale más corto, ni siquiera hay menos etiquetas, pero las definiciones de cada elemento y de cada tipo están separadas, de modo que mediante un vistazo se pueden entender más fácilmente. Además, permite reaprovechar las definiciones de tipos para usarlas en otro XSD, tal como se verá a continuación.
Reutilización de definiciones¶
Hasta ahora hemos considerado que la definición de la gramática de un XML se encontraba toda dentro de un archivo XSD. Sin embargo, esto no es siempre así: puede ser que queramos separarla en varios ficheros, o bien que queramos tener ficheros en donde definimos tipos de elementos y atributos que vayan a encontrarse en distintos XML y que queramos usar recurrentemente. Por ejemplo, un tipo para los elementos que representan personas, puesto que este tipo podrá encontrarse en muchos XML distintos, desde uno para alquiler de coches (los compradores son personas) a otro para organizar los préstamos de una biblioteca (los lectores también lo son).
Para ello existen dos mecanismos distintos: uno que consiste únicamente en yuxtaponer distintos ficheros XSD y otro que hace uso de los llamados espacios de nombres. La ventaja del segundo es que permite definir tipos con un mismos nombre dentro de espacios de nombres distintos.
Composición de ficheros XSD¶
Imaginemos que queremos escribir definir la gramática del XML del apartado anterior de manera que queremos crear dos archivos XSD: uno que incluya las definiciones relativas a los vehículos (o sea, todo lo incluido dentro del elemento flota) y otro que incluya el resto.
Para ello podemos crear el siguente archivo flota.xsd
:
<?xml version="1.0" encoding="utf-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:complexType name="tipo.flota">
<xs:sequence>
<xs:element name="vehiculo" type="tipo.vehiculo" minOccurs="0" maxOccurs="unbounded" />
</xs:sequence>
</xs:complexType>
<xs:complexType name="tipo.vehiculo">
<xs:sequence>
<xs:element name="matricula" type="tipo.matricula" />
<xs:element name="color" type="xs:string" />
<xs:element name="marca" type="xs:string" />
<xs:element name="modelo" type="xs:string" />
</xs:sequence>
<xs:attribute name="tipo" type="tipo.tipo" />
</xs:complexType>
<xs:complexType name="tipo.matricula">
<xs:simpleContent>
<xs:extension base="xs:string">
<xs:attribute name="numeracion" type="tipo.numeracion" />
</xs:extension>
</xs:simpleContent>
</xs:complexType>
<xs:simpleType name="tipo.numeracion">
<xs:restriction base="xs:string">
<xs:enumeration value="antigua"/>
<xs:enumeration value="nueva"/>
</xs:restriction>
</xs:simpleType>
<xs:simpleType name="tipo.tipo">
<xs:restriction base="xs:string">
<xs:enumeration value="turismo" />
<xs:enumeration value="furgoneta" />
<xs:enumeration value="moto" />
<xs:enumeration value="camión" />
<xs:enumeration value="autobús" />
</xs:restriction>
</xs:simpleType>
</xs:schema>
Y el siguiente gente.xsd
que haga referencia a
file:flota.xsd:
<?xml version="1.0" encoding="utf-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:include schemaLocation="flota.xsd" />
<xs:element name="gente">
<xs:complexType>
<xs:sequence>
<xs:element name="persona" type="tipo.persona" maxOccurs="unbounded" />
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:complexType name="tipo.persona">
<xs:sequence>
<xs:element name="nombre" type="tipo.nombre" />
<xs:element name="apellidos" type="xs:string" />
<xs:element name="dni" type="xs:string" />
<xs:element name="flota" type="tipo.flota" minOccurs="0" maxOccurs="unbounded" />
</xs:sequence>
<xs:attribute name="id" type="ident.persona" />
</xs:complexType>
<xs:simpleType name="ident.persona">
<xs:restriction base="xs:ID" />
</xs:simpleType>
<xs:complexType name="tipo.nombre">
<xs:simpleContent>
<xs:extension base="xs:string">
<xs:attribute name="apodo" type="xs:string"/>
</xs:extension>
</xs:simpleContent>
</xs:complexType>
</xs:schema>
Además del elemento xs:include
se puede usar el elemento xs:redefine
que
permite, a la vez que se incluyen los elementos del XSD enlazado, redefinir
aquellos que consideremos oportunos. La redefinición, desgraciadamente, está
limitada a que restrinjamos o extendamos el tipo original y no a que hagamos una
redefinición totalmente nueva. Para esto último deberíamos usar el elemento
xs:override
, que existe en XML Schemas 1.1:
<?xml version="1.0" encoding="utf-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<!--
- gente2.xsd es igual que gente.xsd salvo
- por la redefinición del tipo "ident.persona".
-->
<xs:redefine schemaLocation="gente.xsd">
<xs:simpleType name="ident.persona">
<xs:restriction base="ident.persona">
<xs:pattern value="p[0-9]+" />
</xs:restriction>
</xs:simpleType>
</xs:redefine>
</xs:schema>
Espacios de nombres¶
Concepto¶
Lo visto en el epígrafe anterior soluciona nuestra necesidad de modularizar las definiciones. Sin embargo, nos topamos con un problema nada desdeñable: los nombres de los tipos no pueden repetirse, de modo que, cuando estuviéramos creando un nuevo XSD que reaprovechase definiciones de otros XSD, deberíamos comprobar que los nombres que elegimos para los nuevos tipos no fueran ya usados dentro de los otros ficheros. Esto puede llegar a ser engorroso y la solución es usar espacios de nombres.
Los espacios de nombres son un mecanismo que permite convivir en un mismo XML a elementos y atributos que tienen idéntico nombre, pero distintos significado porque pertenecen a distinto campo semántico.
Para usarlos en nuestro XML es necesario hacer uso del atributo xmlns:
<?xml version="1.0" encoding="utf-8" ?>
<p:persona xmlns:p="urn:persona">
<p:nombre>Perico</p:nombre>
<p:apellidos>de los palotes</p:apellidos>
<p:dni>41112233</p:dni>
<p:tlfo>953112233</p:tlfo>
</p:persona>
En este caso, xmlns:p
indica que el prefijo para el espacio de nombres será
p. El valor es indiferente: simplemente tiene que cumplirse que sea único. Ni
siquiera tiene que existir realmente el enlace. Esta definición del espacio de
nombres tiene validez dentro el propio elemento en que se define (incluidos los
subelementos que contiene). Por ese motivo, si se definen espacios de nombres se
suele hacer en la etiqueta del elemento raíz.
Es posible definir varios espacios de nombres a la vez:
<?xml version="1.0" encoding="utf-8" ?>
<p:persona xmlns:p="urn:persona" xmlns:d="urn:direccion">
<p:nombre>Perico</p:nombre>
<p:apellidos>de los palotes</p:apellidos>
<p:dni>41112233</p:dni>
<p:tlfo>953112233</p:tlfo>
<p:direccion>
<d:via tipo="calle">Callo, 5</d:via>
<d:cp>12345</d:cp>
<d:localidad>Villaconejos</d:localidad>
</p:direccion>
</p:persona>
Es posible definir un espacio de nombres por defecto, de manera que los elementos y atributos que no tengan prefijo, pertenezca a este espacio de nombres por defecto:
<?xml version="1.0" encoding="utf-8" ?>
<persona xmlns="urn:persona" xmlns:d="urn:direccion">
<nombre>Perico</nombre>
<apellidos>de los palotes</apellidos>
<dni>41112233</dni>
<tlfo>953112233</tlfo>
<direccion>
<d:via tipo="calle">Callo, 5</d:via>
<d:cp>12345</d:cp>
<d:localidad>Villaconejos</d:localidad>
</direccion>
</persona>
Desgraciadamente los DTD existen desde antes de la aparición de los espacios de nombres, así que no puede validarse con ellos un documento que use dos DTD diferentes y dos espacios de nombres, cada uno para referenciar los elementos de cada DTD. Sin embargo, esto sí es posible con XML Schemas.
Definición del espacio de nombres¶
Retomemos el ejemplo de la definición de persona (en principio sin la dirección, que lo dejaremos para el siguiente epígrafe):
<?xml version="1.0" encoding="utf-8" ?>
<persona xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="urn:persona"
xsi:schemaLocation="urn:persona persona.xsd">
<nombre>Perico</nombre>
<apellidos>de los palotes</apellidos>
<dni>41112233</dni>
<tlfo>953112233</tlfo>
</persona>
Observemos las diferencias con el código que con el que abrimos el tema y para el que no nos preocupamos de la definición del espacio de nombres: el atributo xmlns para definir un espacio de nombres predeterminado, y el atributo xsi:schemaLocation en el que indicamos el fichero XSD que sirve para la validación de tal espacio de nombres. El XSD, por su parte, quedará así:
<?xml version="1.0" encoding="utf-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns="urn:persona"
targetNamespace="urn:persona"
elementFormDefault="qualified">
<xs:element name="persona">
<xs:complexType>
<xs:sequence>
<xs:element name="nombre" type="xs:string" />
<xs:element name="apellidos" type="xs:string" />
<xs:element name="dni" type="xs:integer" />
<xs:element name="tlfo" type="xs:integer" />
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:schema>
Como se ve, sólo hay que indicar cuál es el espacio de nombres que se define (targetNamespace) y elementFormDefault, que indica si los elementos pertenecerán a este espacio de nombres o no. Al ponerlo como qualified, sí pertenecerán (el valor predeterminado es unqualified). Obsérvese que en la definición del XML todos los elementos pertenecen al espacio de nombres urn:persona, así que al hacer la definición con XSD los elementos deben pertenecer al mismo.
Mezclando espacios de nombres¶
Llega el momento de saber cómo se pueden definir por separado los elementos de distintos espacios de nombres (cada uno en un XSD diferente) y cómo se pueden usar todos esos espacios de nombres a la vez en un único XML.
Supongamos que la definición anterior de persona la queremos enriquecer con su
dirección postal. Ahora bien, la dirección postal (que incluye el nombre de la
calle, el número y el código postal, por ejemplo) se puede usar también cuando
tratamos de definir el domicilio de una empresa. Así que supongamos que
definimos en el archivo direccion.xsd
el
siguiente tipo complejo:
<?xml version="1.0" encoding="utf-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns="urn:direccion"
targetNamespace="urn:direccion"
elementFormDefault="qualified">
<xs:complexType name="tipo.direccion">
<xs:sequence>
<xs:element name="via" type="tipo.via" />
<xs:element name="cp" type="tipo.cp" />
<xs:element name="localidad" type="xs:string" />
</xs:sequence>
</xs:complexType>
<xs:complexType name="tipo.via">
<xs:simpleContent>
<xs:extension base="xs:string">
<xs:attribute name="tipo" type="tipo.clase.via" default="calle" />
</xs:extension>
</xs:simpleContent>
</xs:complexType>
<xs:simpleType name="tipo.clase.via">
<xs:restriction base="xs:string">
<xs:enumeration value="calle" />
<xs:enumeration value="callejón" />
<xs:enumeration value="travesía" />
<xs:enumeration value="plaza" />
<xs:enumeration value="avenida" />
<xs:enumeration value="cuesta" />
<xs:enumeration value="costanilla" />
</xs:restriction>
</xs:simpleType>
<xs:simpleType name="tipo.cp">
<xs:restriction base="xs:integer">
<xs:pattern value="[0-9]{5}" />
</xs:restriction>
</xs:simpleType>
</xs:schema>
En este fichero queda perfectamente definido el tipo dirección de manera que podríamos escribir nuestro XML como sigue:
<?xml version="1.0" encoding="utf-8" ?>
<persona xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="urn:persona"
xmlns:d="urn:direccion"
xsi:schemaLocation="urn:persona persona.xsd">
<nombre>Perico</nombre>
<apellidos>de los palotes</apellidos>
<dni>41112233</dni>
<tlfo>953112233</tlfo>
<direccion>
<d:via tipo="calle">Callo, 5</d:via>
<d:cp>12345</d:cp>
<d:localidad>Villaconejos</d:localidad>
</direccion>
</persona>
de manera que el nuevo elemento dirección haga uso de la definición. Por último
alteramos persona.xsd
para que haga uso del tipo direccion, que está
definido en direccion.xsd
:
<?xml version="1.0" encoding="utf-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns="urn:persona"
xmlns:d="urn:direccion"
targetNamespace="urn:persona"
elementFormDefault="qualified">
<xs:import schemaLocation="direccion.xsd"
namespace="urn:direccion" />
<xs:element name="persona">
<xs:complexType>
<xs:sequence>
<xs:element name="nombre" type="xs:string" />
<xs:element name="apellidos" type="xs:string" />
<xs:element name="dni" type="xs:integer" />
<xs:element name="tlfo" type="xs:integer" />
<xs:element name="direccion" type="d:tipo.direccion" />
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:schema>
Cuando se usan espacios de nombres es posible usar también xs:include
y
xs:redefine
. Sin embargo, los tipos de elementos y atributos que se definan
en el XSD enlazados deberán tener el mismo espacio de nombres de destino
(targetNamespace) que el fichero desde el que se enlazan.
Ejercicios resueltos¶
Por hacer
Resolver los ejercicios de recetas y cadena.