Esquemas XML

Introducción

XML Schema es un lenguaje de esquema utilizado para describir la estructura y las restricciones de los contenidos de los documentos XML de una forma muy precisa, más allá de las normas sintácticas impuestas por el propio lenguaje XML. Se consigue así una percepción del tipo de documento con un nivel alto de abstracción. Fue desarrollado por el World Wide Web Consortium (W3C) y alcanzó el nivel de recomendación en mayo de 2001.

XML Schema está pensado para proporcionar una mayor potencia expresiva que las DTD, menos capaces al describir los documentos a nivel formal.

Las ventajas que aporta son:

  • Se basa en XML, por lo tanto tiene todas las ventajas de un documento XML.
  • Tiene una gran variedad de tipos de datos.
  • Tiene un modelo de datos abierto.
  • Soporta espacios de nombres (singularidad de los nombres, algo que no podíamos tener con los DTDs).
  • Soporta tipos en los atributos.

Nodo raíz schema

Los esquemas mantienen la estructura y la sintaxis de los documentos XML por lo tanto deben tener un nodo raíz y dentro de él contener todo el esquema. Por supuesto debe tener el prólogo (es decir, la definición de la versión XML).

<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
  ...
</xs:schema>

Fíjese en que hace referencia a una URL, esa dirección contiene la definición de todos los elementos y atributos que se pueden utilizar en un esquema. Eso no quiere decir que para programar en XML se necesite estar conectado a Internet.

Utiliza un atributo xmlns que significa XML namespace para crear un espacio de nombres de XML vinculado a un prefijo que en este caso es xs, pero que puede ser cualquier cosa ya que sólo hace la función de variable (también se usa xsd). Sirve para que cada vez que se utilice un elemento o tipo de esa “librería” se le ponga ese prefijo, en el caso de que se utilizasen más referencias a otras URLs se distinguiría claramente los elementos de cada uno de ellos por medio de los prefijos.

Utilizando este atributo xmlns, crea un espacio de nombres para cada URL referenciada, así si hubiese dos elementos con el mismo nombre se diferencian claramente.

Declaración de elementos

Tipos de elementos

Existen tres tipos de elementos a definir en un esquema:

Elementos estándar
Sin hijos y sin restricciones ni atributos.
Elementos simples
Elementos sin hijos pero con restricciones y sin atributos.
Elementos complejos
Elementos con hijos y/o atributos.
../_images/personal_interno.png

Jerarquía de ejemplo.

En este ejemplo tenemos los siguientes tipos de elementos:

  • Elementos complejos: PERSONAL e INTERNO (con hijos).
  • Elementos simples: TELEFONO y DIRECCIÓN (con restricciones).
  • Elementos estándar: NOMBRE (sin hijos ni restricciones).

Estructuras

Las dos estructuras posibles son:

<xs:element name="nombre_del_elemento" type="tipo" />

Esta estructura es vacía, es decir se define un nombre de elemento y un tipo, el tipo puede ser un estándar de XML o un tipo definido por nosotros que describiría aquellos elementos que tengan hijos (complejos o aquellos elementos que no tienen hijos pero tienen restricciones).

Seguido de esta definición vendría debajo la definición del tipo. Esta estructura es la más aconsejable pero no la única.

<xs:element name="nombre_del_elemento">
  ...
</xs:element>

Esta estructura sería equivalente a la anterior sólo que en este caso no se pone el tipo del elemento como atributo sino que esa definición se haría entre las etiquetas de inicio y cierre de element.

Puede contener atributos cuyos valores siempre van entre comillas:

name
Nombre del elemento.
type
Tipo simple predefinido, ya sean los estándares o unos propios.
maxOccurs
Número máximo de veces que puede aparecer [0..unbounded].
minOccurs
Número mínimo de veces que puede aparecer.
ref
Para importar de otros esquemas o hacer referencia a un elemento ya declarado anteriormente en este mismo esquema.

Nota

Sin poner maxOccurs ni minOccurs, este elemento aparece siempre exactamente una sola vez.

Tipos de datos

En las dos tablas siguientes se enumeran los tipos de datos primitivos y derivados que podemos usar en los esquemas XML.

Primitivos

Tipo de dato Descripción
string Representa cadenas de caracteres.
boolean Representa valores booleanos, que son true o false.
decimal Representa números de precisión arbitraria.
float Representa números de punto flotante de 32 bits de precisión simple.
double Representa números de punto flotante de 64 bits de doble precisión.
duration Representa una duración de tiempo. El modelo de duration es PnYnMnDTnHnMnS, donde nY representa el número de años, nM el número de meses, nD el número de días, T el separador de fecha y hora, nH el número de horas, nM el número de minutos y nS el número de segundos.
dateTime Representa una instancia específica de tiempo. El modelo de dateTime es CCYY-MM-DDThh:mm:ss donde CC representa el siglo, YY el año, MM el mes y DD el día, precedido por un carácter negativo (-) inicial opcional para indicar un número negativo. Si se omite el carácter negativo, se supone positivo (+). La T es el separador de fecha y hora, y hh, mm y ss representan la hora, minutos y segundos, respectivamente. Se pueden utilizar dígitos adicionales para aumentar la precisión de los segundos decimales, si se desea. Por ejemplo, se admite el formato ss.ss... con cualquier número de dígitos después del separador decimal. Es opcional la parte de segundos decimales.
time Representa una instancia de tiempo que se repite cada día. El modelo de time es hh:mm:ss.sss con un indicador opcional de zona horaria.
date Representa una fecha de calendario. El modelo de date es CCYY-MM-DD con un indicador opcional de zona horaria como el de dateTime.
hexBinary Representa datos binarios arbitrarios codificados en hexadecimal. hexBinary es el conjunto de secuencias de longitud finita de octetos binarios. Cada octeto binario se codifica como una tupla de caracteres que se compone de dos dígitos hexadecimales ([0-9a-fA-F]) y representa el código del octeto.
base64Binary Representa datos binarios arbitrarios codificados en Base64. base64Binary es el conjunto de secuencias de longitud finita de octetos binarios.
anyURI Representa un identificador URI como lo define RFC 2396. Un valor anyURI puede ser absoluto o relativo, y puede tener un identificador de fragmento opcional.
QName Representa un nombre completo, que se compone de un prefijo y un nombre local separados por un signo de dos puntos. Tanto el prefijo como los nombres locales deben ser un NCName. El prefijo debe estar asociado con una referencia a un identificador URI de espacio de nombres, mediante una declaración de espacio de nombres.
NOTATION Representa un conjunto de QName.

Derivados

Tipo de dato Descripción
normalizedString Representa cadenas normalizadas de espacios en blanco. Este tipo de datos se deriva de string.
token Representa cadenas convertidas en símbolos. Este tipo de datos se deriva de normalizedString.
language Representa identificadores de lenguaje natural (definidos por RFC 1766). Este tipo de datos se deriva de token.
IDREFS Representa el tipo de atributo IDREFS. Contiene un conjunto de valores de tipo IDREF.
ENTITIES Representa el tipo de atributo ENTITIES. Contiene un conjunto de valores de tipo ENTITY.
NMTOKEN Representa el tipo de atributo NMTOKEN. NMTOKEN es un conjunto de caracteres de nombres (letras, dígitos y otros caracteres) en cualquier combinación. A diferencia de Name y NCName, NMTOKEN, no tiene restricciones del carácter inicial. Este tipo de datos se deriva de token.
NMTOKENS Representa el tipo de atributo NMTOKENS. Contiene un conjunto de valores de tipo NMTOKEN.
Name Representa nombres en XML. Name es un símbolo que empieza con una letra, carácter de subrayado o signo de dos puntos, y continúa con caracteres de nombre (letras, dígitos y otros caracteres). Este tipo de datos se deriva de token.
NCName Representa nombres sin el signo de dos puntos. Este tipo de datos es igual que Name, excepto en que no puede empezar con el signo de dos puntos. Este tipo de datos se deriva de Name.
ID Representa el tipo de atributo ID definido en la recomendación de XML 1.0. El ID no debe incluir un signo de dos puntos (como NCName) y debe ser único en el documento XML. Este tipo de datos se deriva de NCName.
IDREF Representa una referencia a un elemento que tiene un atributo ID que coincide con el ID especificado. IDREF debe ser un NCName y tener un valor de un elemento o atributo de tipo ID dentro del documento XML. Este tipo de datos se deriva de NCName.
ENTITY Representa el tipo de atributo ENTITY definido en la recomendación de XML 1.0. Es una referencia a una entidad sin analizar con un nombre que coincide con el especificado. ENTITY debe ser un NCName y declararse en el esquema como nombre de entidad sin analizar. Este tipo de datos se deriva de NCName.
integer Representa una secuencia de dígitos decimales con un signo inicial (+ o -) opcional. Este tipo de datos deriva de decimal.
nonPositiveInteger Representa un número entero menor o igual que cero. nonPositiveInteger consta de un signo negativo (-) y una secuencia de dígitos decimales. Este tipo de datos se deriva de integer.
negativeInteger Representa un número entero menor que cero. Consta de un signo negativo (-) y una secuencia de dígitos decimales. Este tipo de datos se deriva de nonPositiveInteger.
long Representa un número entero con un valor mínimo de -9223372036854775808 y un valor máximo de 9223372036854775807. Este tipo de datos se deriva de integer.
int Representa un número entero con un valor mínimo de -2147483648 y un valor máximo de 2147483647. Este tipo de datos se deriva de long.
short Representa un número entero con un valor mínimo de -32768 y un valor máximo de 32767. Este tipo de datos se deriva de int.
byte Representa un número entero con un valor mínimo de -128 y un valor máximo de 127. Este tipo de datos se deriva de short.
nonNegativeInteger Representa un número entero mayor o igual que cero. Este tipo de datos se deriva de integer.
unsignedLong Representa un número entero con un valor mínimo de cero y un valor máximo de 18446744073709551615. Este tipo de datos se deriva de nonNegativeInteger.
unsignedInt Representa un número entero con un valor mínimo de cero y un valor máximo de 4294967295. Este tipo de datos se deriva de unsignedLong.
unsignedShort Representa un número entero con un valor mínimo de cero y un valor máximo de 65535. Este tipo de datos se deriva de unsignedInt.
unsignedByte Representa un número entero con un valor mínimo de cero y un valor máximo de 255. Este tipo de datos se deriva de unsignedShort.
positiveInteger Representa un número entero mayor que cero. Este tipo de datos se deriva de nonNegativeInteger.

Tipo complejo: complexType

Sirve para definir elementos que tienen sub-elementos y/o atributos.

<xs:complexType name="nombre_del_tipo_complejo">
  <xs:sequence> <!-- sequence/all/choice -->
    ... subelementos ...
  </xs:sequence>
  ... atributos ...
</xs:complexType>

Puede contener elementos secundarios:

sequence
Implica que deben aparecer todos los elementos y en ese orden (AND).
all
Implica que deben aparecer todos los elementos, sin importar el orden.
choice
Implica que sólo debe aparecer uno de esos elementos (OR).
attribute
Para definir atributos.

Puede tener los siguientes atributos:

name
Nombre del tipo complejo.
mixed
Puede tener dos valores true o false.
type
Tipo de datos con el que se identifica.

Dos posibles estructuras:

  • La primera contiene al tipo dentro de la estructura element:

    <xs:element name="contacto">
      <xs:complexType>
        <xs:sequence>
          <xs:element name="destinatario" type="xs:string" />
          <xs:element name="remitente" type="xs:string" />
          <xs:element name="titulo" type="xs:string" />
          <xs:element name="contenido" type="xs:string" />
        </xs:sequence>
        <xs:attribute name="fecha" type="xs:date"/>
      </xs:complexType>
    </xs:element>
    
  • La segunda estructura lo que hace es primero definir el elemento con un tipo, y después definir fuera ese tipo:

    <xs:element name="contacto" type="tipo_contacto"/>
    
    <xs:complexType name="tipo_contacto">
      <xs:sequence>
        <xs:element name="destinatario" type="xs:string" />
        <xs:element name="remitente" type="xs:string" />
        <xs:element name="titulo" type="xs:string" />
        <xs:element name="contenido" type="xs:string" />
      </xs:sequence>
      <xs:attribute name="fecha" type="xs:date"/>
    </xs:complexType>
    

Nota

La mejor opción es la segunda porque permite reutilizar ese tipo para otros elementos. Además los parsers toleran mejor esta estructura.

Elementos sequence, all y choice

Estos tres elementos nunca se utilizan juntos, aparece tan sólo uno de ellos en el elemento complexType. Sirve para describir en qué orden y cómo deben aparecer los subelementos del complexType.

Es equivalente a, en el DTD, poner comas o barras verticales en la descripción de un elemento con hijos.

sequence

Este elemento indica que es obligatorio que aparezcan todos los elementos especificados y en el orden en que se definen. Es el equivalente a un AND.

En este ejemplo se define el elemento libro, con tres subelementos obligatorios y que deben aparecer exactamente en este orden (1º titulo, 2º autor y 3º editorial) y no aparecen es este orden o uno de ellos no aparece, el parser produciría un error.

<xs:element name="libro" type="tipo_libro"/>

<xs:complexType name="tipo_libro">
  <xs:sequence>
    <xs:element name="titulo" type="xs:string" />
    <xs:element name="autor" type="xs:string" />
    <xs:element name="editorial" type="xs:string" />
  </xs:sequence>
</xs:complexType>

<libro>
  <titulo>El señor de los anillos</titulo>
  <autor>John Ronald Ruelen Tolkien</autor>
  <editorial>Tirant Lo Blanch</editorial>
</libro>

all

Este elemento indica que es obligatorio que aparezcan todos los elementos especificados y pero NO en el orden en que se definen.

En este ejemplo se define el elemento libro, con tres subelementos obligatorios.

<xs:element name="libro" type="tipo_libro"/>

<xs:complexType name="tipo_libro">
  <xs:all>
    <xs:element name="titulo" type="xs:string" />
    <xs:element name="autor" type="xs:string" />
    <xs:element name="editorial" type="xs:string" />
  </xs:all>
</xs:complexType>

<libro>
  <titulo>El señor de los anillos</titulo>
  <editorial>Tirant Lo Blanch</editorial>
  <autor>John Ronald Ruelen Tolkien</autor>
</libro>

choice

Este elemento indica que de todos los elementos especificados sólo debe aparecer uno de ellos. Es el equivalente al OR.

En este ejemplo se define el elemento libro, con tres posibles subelementos. Puede tener o un titulo o un autor o una editorial.

<xs:element name="libro" type="tipo_libro"/>

<xs:complexType name="tipo_libro">
  <xs:choice>
    <xs:element name="titulo" type="xs:string" />
    <xs:element name="autor" type="xs:string" />
    <xs:element name="editorial" type="xs:string" />
  </xs:choice>
</xs:complexType>

<libro>
  <titulo>El señor de los anillos</titulo>
</libro>

Elemento attribute

Para definir los atributos de un elemento o tipo de elemento utilizamos la siguiente estructura:

<xs:attribute name="nombre_atributo" type="tipo_atributo" use="modificador" />

Puede contener los siguientes atributos:

name
Es el nombre del atributo.
type
Es el tipo del atributo.
use
Para definir si es un atributo obligatorio u opcional. Para definir un atributo como obligatorio le asignaremos el valor required. Por defecto es opcional.

La localización del atributo no puede ir por sí solo, ya que con esta estructura no sabríamos a que elemento se refiere. Para ello se pone siempre dentro de una estructura complexType.

Tipo simple: simpleType

Un tipo simple sirve para definir una serie de restricciones a un elemento o a un atributo. Es muy útil para definir rangos, tipos enumerados, etc.

<xs:simpleType name="nombre_del_tipo_simple">
  <xs:restriction>
    ... restricciones ...
  </xs:restriction>
</xs:simpleType>

Puede contener elementos secundarios:

restriction
Para poner rangos, patrones, enumerar posibles valores etc.
list
Para definir un tipo de lista.
union
Para unir varios tipos definidos anteriormente en uno.

Puede contener atributos:

name
Para poner el nombre al tipo simple.

Elemento restriction

Se utiliza para poner rangos, patrones enumerar posibles valores, etc.

<xs:restriction base="xs:string">
  <xs:nombre_restriccion value=""/>
</xs:restriction>

Tiene el atributo base. Es el tipo predefinido de datos sobre el que se construye la restricción.

Puede contener las siguientes restricciones:

enumeration
Se ponen los valores que puede tomar el elemento.
maxExclusive, minExclusive
Valores mínimos o máximos que puede tomar el elemento, sin incluir el último valor.
maxInclusive, minInclusive
Valores mínimos o máximos que puede tomar el elemento, incluyendo el último valor.
pattern

Expresión regular que expresa la restricción.

<xs:pattern value="([a-zA-Z0-9])*"/>

En este caso decimos que el patrón es de longitud indefinida, y que puede contener letras mayúsculas, minúsculas y números.

<xs:pattern value="\d{2}-\d{4}"/>

En este caso decimos que el patrón es de dos dígitos seguido de un guión y otros 4 dígitos. Por ejemplo, 25-6789.

length, maxLength, minLength
Longitud de un elemento de tipo texto.
totalDigits
Número exacto de dígitos permitidos.
fractionDigits
Número máximo de decimales permitidos.

También en este caso hay dos posibles estructuras:

  • La primera contiene al tipo dentro de la estructura element:

    <xs:element name="lista_de_enteros">
      <xs:simpleType>
        <xs:restriction base="xs:integer">
          <xs:minInclusive value="100"/>
          <xs:maxInclusive value="200"/>
        </xs:restriction>
      </xs:simpleType>
    </xs:element>
    
  • La segunda estructura lo que hace es 1º definir el elemento con un tipo, y después definir fuera ese tipo:

    <xs:element name="lista_de_enteros" type="tipo_lista_enteros"/>
    
    <xs:simpleType name="tipo_lista_enteros">
      <xs:restriction base="xs:integer">
        <xs:minInclusive value="100"/>
        <xs:maxInclusive value="200"/>
      </xs:restriction>
    </xs:simpleType>
    

Nota

La mejor opción es la segunda porque permite reutilizar ese tipo para otros elementos. Además los parsers toleran mejor esta estructura.

Ejemplos de restricciones

<xs:element name="sexo" type="tipo_sexo"/>

<xs:simpleType name="tipo_sexo">
  <xs:restriction base="xs:string">
    <xs:enumeration value="mujer"/>
    <xs:enumeration value="hombre"/>
  </xs:restriction>
</xs:simpleType>
<xs:element name="codigo_postal" type="tipo_cp"/>

<xs:simpleType name="tipo_cp">
  <xs:restriction base="xs:string">
    <xs:length value="5"/>
  </xs:restriction>
</xs:simpleType>
<xs:element name="password" type="tipo_password"/>

<xs:simpleType name="tipo_password">
  <xs:restriction base="xs:string">
    <xs:pattern value="\d{3}-[A-Z]{2}"/>
  </xs:restriction>
</xs:simpleType>

Elemento list

Permite definir un tipo simple compuesto por una lista de otros tipos simples separados siempre por espacios:

<xs:simpleType name="lista_numeros">
  <xs:list itemType="xs:integer"/>
</xs:simpleType>

Elemento union

Permite combinar varios tipos simples en uno solo:

<xs:simpleType name="entero_o_fecha">
  <xs:union memberTypes="xs:integer xs:date"/>
</xs:simpleType>

Extender un tipo

Utilizando xs:extension podemos ampliar un simpleType o complexType, añadiendo elementos o atributos extra a un tipo base definido anteriormente.

<xs:complexType name="tipo_persona">
  <xs:sequence>
    <xs:element name="nombre" type="xs:string"/>
    <xs:element name="edad" type="xs:integer"/>
  </xs:sequence>
  <xs:attribute name="id" type="xs:integer"/>
</xs:complexType>

<xs:complexType name="tipo_contacto">
  <xs:complexContent>
    <xs:extension base="tipo_persona">
      <xs:sequence>
        <xs:element name="email" type="xs:string"/>
        <xs:element name="telefono" type="xs:string"/>
      </xs:sequence>
    </xs:extension>
  </xs:complexContent>
</xs:complexType>

Elementos sin subelementos

Mediante simpleContent podemos definir un elemento que solo pueda contener texto y atributos, no subelementos.

<xs:complexType name="tipo_documento">
  <xs:simpleContent>
    <xs:extension base="xs:string">
      <xs:attribute name="plantilla" type="xs:string" use="required"/>
      <xs:attribute name="revisado" type="xs:boolean" default="false"/>
    </xs:extension>
  </xs:simpleContent>
</xs:complexType>

Elementos vacíos

Para definir un elemento vacío, que no pueda tener ni texto ni subelementos, basta con no poner ningún subelemento en la declaración del tipo:

<xs:complexType name="tipo_evento">
  <xs:attribute name="nombre" type="xs:string" use="required"/>
  <xs:attribute name="activo" type="xs:boolean" default="false"/>
</xs:complexType>

Convertir DTDs en esquemas XML

En un principio, con la creación de XML, se empezó empleando las DTDs como modo de especificación de modelos; la existencia de más herramientas para ello hizo que gran parte de las empresas que empezaron a trabajar con XML adoptasen el uso de las DTDs. Actualmente el uso de estas ha quedado más restringido en su uso, y se está empezando a desarrollar de acuerdo al estándar de XML Schema; por ello, a continuación, presentaremos las transformaciones que deberían realizarse para convertir una DTD en un Schema.

En principio mostraremos a que elemento de XML Schema corresponden que elementos de las DTDs, aunque existen herramientas de traducción (DTD2HTML en Perl, XMLSpy, …) entre estos dos lenguajes, la siguiente tabla intenta expresar como funciona con el fin de una mejor comprensión.

DTD Schema Comentarios
ELEMENT <element> Crea un vínculo entre un nombre y unos atributos, modelos de contenido y anotaciones.
#PCDATA   Soportado como parte de un tipo simple
ANY <any> Posee distintos comodines para un mayor conjunto de posibilidades. Existe también <anyAttribute> con comodines similares.
EMPTY Soportado Se elimina la existencia de elementos descendientes del actual, diferenciando de la presencia de un string vacío en un elemento.
Modelo de contenido <complexType>  
Conector de secuencia (,) <sequence>  
Conector de alternativa (|) <choice>  
Opcional (?) Soportado Se han de emplear los atributos predefinidos de maxOccurs y minOccurs
Requerido y Repetible (+) Soportado Se han de emplear los atributos predefinidos de maxOccurs y minOccurs
Opcional y Repetible (*) Soportado Se han de emplear los atributos predefinidos de maxOccurs y minOccurs
ATTLIST <attributeGroup> Se pueden agrupar declaraciones de <attributes>
Tipo de atributo CDATA, ID, IDREF, NOTATION , … Tipos <simpleType> predefinidos  
ENTITY No soportado Las entidades son declaradas en declaraciones de marcas en el XML.
ENTITY%Parameter No soportada  

Utilización del esquema

Para utilizar el esquema desde un documento XML, tenemos que tener en cuenta si está en nuestro sistema de ficheros local o es un esquema público.

  • En caso de que el esquema esté en un sitio público:

    <nodo_raiz xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:schemaLocation="http://www.miempresa.com/mi_esquema.xsd">
    
  • En caso de que el esquema esté en local:

    <nodo_raiz xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:noNamespaceSchemaLocation="mi_esquema.xsd">