ESQUEMAS XML

Como ya hemos visto, los DTD sirven para poder validar documentos XML sin tener que crear un programa específico para cada lenguaje. En 1999 W3C empezó a desarrollar los esquemas XML, debido a la necesidad creciente un nuevo formato que permitiera describir de forma más avanzada los documentos XML. Durante el desarrollo de los esquemas, varios miembros de la W3C diseñaron un lenguaje que simplificaba los esquemas, pero al margen de la organización. El resultado más importante es RELAX NG (que veremos más adelante). En la actualidad, los esquemas son una tecnología madura que se usa en gran variedad de aplicaciones XML.

Un esquema es, en XML, un tipo de documento de modelado que define la estructura de un documento XML. Hasta ahora hemos empleado los DTD para crear definiciones complejas, modulares, "maravillosas", etcétera para un cierto vocabulario. Sin embargo los esquemas, además de hacer lo mismo, aportan beneficios:

  • Se emplea XML para crear esquemas, en vez de una sintaxis a parte.
  • Soportan completamente la recomendación sobre Namespaces, ya que fueron diseñados después de la recomendación de Namespaces.
  • Permite crear más facilmente modelos complejos y reutilizables.
  • Permiten modelar aspectos ventajosos de programación, como herencia y sustitución de tipos (de esto ya hablaremos en el ciclo de programación ;)).

TIPOS DE DATOS EN LOS ESQUEMAS XML

Cuando estuvimos trabajando con DTDs, se podía especificar que un elemento tenía contenido mixto, elementos o estar vacíos. Desafortunadamente, cuando los elementos tenían sólo texto, no se podía añadir restricciones sobre la forma del texto. La declaración de atributos daba algo de control, pero aun así los tipos a usar eran bastante limitados. Los esquemas permiten declarar un tipo de dato textual permitido en atributos y elementos, usando declaraciones simples. Por ejemplo, usando estos tipos se puede especificar que un elemento puede contener solo valores de fecha, solo números positivos o números de un cierto rango. Esta es posiblemente la característica más importante de los esquemas XML. Especificando los tipos permitidos en elementos y atributos, se pude ejercer un control más rígido sobre los documentos.

ENTONCES... ¿PARA QUÉ LOS DTD?

Los esquemas XML no proporcionan la funcionalidad ENTITY. En muchos documentos y aplicaciones, la declaración de entidades es muy importante. A pesar de que los esquemas afinan más que DTD en muchos aspectos, solo por las ENTITY, a los DTD les quedan mucho que guerrear. A demás tienen una presencia muy importante ya que son el único mecanismo que permite incrustar descripciones directamente en el documento. Los analizadores sintácticos con validación están preparados por lo general para analizar los DTD incrustados, mientras que los analizadores sin validación pueden ignorar el DTD.

EL DOCUMENTO DE ESQUEMA XML

La mayoría de los esquemas se almacenan en un documento XML separado. A este respecto, los esquemas son iguales que los DTD externos, de forma que el documento tiene una referencia hacia el esquema. Un documento XML que se acoge a un esquema XML en particular, es llamado Instancia del Esquema.

Para empezar, vamos a ver un esquema ejemplo:

<?xml version="1.0" encoding="UTF-8"?> <schema xmlns="http://www.w3.org/2001/XMLSchema" xmlns:target="http://www.example.com/persona" targetNamespace="http://www.example.com/persona" elementFormDefault="qualified"> <element name="persona"> <complexType> <sequence> <element name="nombre" type="string"/> <element name="apellido1" type="string"/> <element name="apellido2" type="string"/> </sequence> <attribute name="título" type="string"/> </complexType> </element> </schema>

Vamos a empezar con este esquema. Podemos crear un documento llamado esquema1.xsd, copiar en el esquema anterior y guardarlo. Ahora, vamos a crear una instancia del esquema. En vez de referirnos a un DTD, nos referiremos al esquema, como lo hace el ejemplo siguiente:

<?xml version="1.0"?> <persona xmlns="http://www.example.com/persona" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.example.com/persona esquema1.xsd" titulo="D."> <nombre>Juan</nombre> <apellido1>García</apellido1> <apellido2>Pérez</apellido2> </persona>

Tratemos de validar la instancia mediante un analizador sintáctico con validación de esquemas... En nuestro caso estamos usando XML Copy Editor.

Ahora vamos a estudiar las partes de un esquema XML.

LAS DECLARACIONES <SCHEMA>

Como ya hemos visto, el elemento <schema> es el elemento raíz de un esquema XML. Éste elemento permite declarar información sobre namespaces así como declaraciones por defecto para todo el documento. Se puede también incluir un atributo "version" para identificar el esquema y la versión del vocabulario:

<schema targetNamespace="URI" attributeFormDefault="qualified ó unqualified" elementFormDefault="qualified ó unqualified" version="número de versión">

Vamos a ver cada parte de esta declaración.

EL NAMESPACE DEL ESQUEMA XML

En el ejemplo anterior, el namespace era http://www.w3.org/2001/XMLSchema. Esto permite inidicar que el elemento <schema> es parte del vocabulario de los Esquemas XML (sí, Esquema con E mayúscula, porque es un nombre propio). Recordemos que XML es sensible a las mayúsculas, por lo que los namespaces son sensibles a las mayúsculas. Si no lo escribimos igual, el validador puede rechazar un esquema.

Se puede declarar el namespace de varias formas. Por ejemplo, las siguientes declaraciones son iguales:

<schema xmlns="http://www.w3.org/2011/XMLSchema"> <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"> <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">

Tal como se vio en el tema sobre namespaces, el prefijo del mismo es insignificante (solo es un atajo para la declaración del namespace). La recomendación de los Esquemas XML usa el prefijo xs, y de hecho se trata del uso más común. Si no usamos prefijo, no pasa nada. El prefijo que se use es cuestión de preferencias. Si usamos un editor XML (como XML Copy Editor) puede que se añadan prefijos automáticamente.

TARGET NAMESPACE

El objetivo principal de los esquemas XML es declarar vocabularios. Estos vocabularios pueden ser identificados por un namespace que es especificado en el atributo targetNamespace. No todos los esquemas tienen por qué tener un targetNamespace.

Cuando se declara un targetNamespace, es importante incluir una declaración de namespace que concuerde con ella, u obtendremos un error del validador.

Por ejemplo, algunas definiciones válidas de targetNamespace pueden ser las siguientes:

<schema xmlns="http://www.w3.org/2001/XMLSchema" targetNamespace="http://www.example.com/persona" xmlns:target="http://www.example.com/persona"> <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" targetNamespace="http://www.example.com/persona" xmlns="http://www.example.com/persona">

Si se observa con cuidado se entenderá por qué en una declaración el "target namespace" aparece como "xmlns:target" y en la otra como "xmlns" solamente. En la primera declaración, el elemento <schema> utiliza el namespace por defecto. Debido a esto el "target namespace" requiere el uso de un prefijo. Sin embargo el segundo caso es el opuesto: el elemento <schema> necesita usar un prefijo ya que el "target namespace" está usando el namespace por defecto. Una vez más, es cuestión de gustos.

NOTA: En principio puede resultar bastante confuso el uso simultáneo de "targetNamespace" y "xmlns:target". Cualquier mente pensante diría que si tienen que tener el mismo valor y se parecen mucho, ¿Por qué poner lo mismo dos veces? La razón es la siguiente:

Vemas los siguientes atributos de la etiqueta "schema":

  1. xmlns="http://www.w3.org/2001/XMLSchema"
  2. xmlns:target="http://www.example.com/name"
  3. targetNamespace="http://www.example.com/name"

Los atributos xmlns sirven para distinguir las cosas de los esquemas y las cosas del leguaje que estamos definiendo.

El atributo targetNamespace permite especificar el espacio de nombres para el que se están defininiendo los elementos. De este modo, si se incluyen más espacios de nombres, sigue quedando claro que el esquema describe uno concreto.

CUALIFICACIÓN DE ELEMENTOS Y ATRIBUTOS

Dentro de un documento instancia, los elementos y los atributos pueden estar cualificados o no. Un elemento (o atributo) se dice cualificado cuando tiene asociado un namespace. Por ejemplo, los elementos siguientes están cualificados:

<name xmlns="http://www.example.com/persona"> <nombre> Juan </nombre> <apellido1> García </apellido1> <apellido2> Pérez </apellido2> </name>

A pesar de que los elementos no tienen prefijos de namespace, están asociados a htp://www.example.com/persona", de modo que están cualificados no prefijados. Cada uno de los hijos de estos elementos también están cualificados, ya que el namespace por defecto está siendo declarado en el elemento <name>.

También es posible cualificar elementos usando prefijos de namespace. En el ejemplo siguiente, todos los elementos están cualificados y prefijados:

<pref:name xmlns:pref="http://www.example.com/persona"> <pref:nombre> Juan </pref:nombre> <pref:apellido1> García </pref:apellido1> <pref:apellido2> Pérez </pref:apellido2> </pref:name>

Los elementos no cualificados no tienen asociado un namespace. Por ejemplo, en el siguiente código el elemento <name> está cualificado, pero <nombre>, <apellido1> y <apellido2> no.

<pref:name xmlns:pref="http://www.example.com/persona"> <nombre> Juan </nombre> <apellido1> García </apellido1> <apellido2> Pérez </apellido2> </pref:name>

Dentro del elemento <schema> se pude modificar el comportamiento por defecto de los esquemas para indicar cómo se debe cualificar a los elementos. Esto se consigue con el atributo "elementFormDefault" y "attributeFormDefault". El valor para ambos atributos es "qualified" o "unqualified", y su valor por defecto es "unqualified". Salvo que se necesite expresamente, siempre es recomendable incluir los siguientes valores:

elementFormDefault="qualified" attributeFormDefault="unqualified"

(no pongas esa cara de miedo , que ahora después aclaramos esto).

DECLARACIONES <ELEMENT>

Cuando se declara un elemento, se llevan a cabo dos tareas: especificar el nombre del elemento y su contenido permitido:

<element name="nombre del elemento" type="tipo global" ref="declaración global del elemento" form="qualified ó unqualified" minOccurs="número no negativo" maxOccurs="número no negativo ó unbounded" default="valor por defecto" fixed="valor fijo">

El atributo name está claro ¿No? Vamos a ver los demás atributos del elemento "name".

LOS ATRIBUTOS DE <ELEMENT>: EL ATRIBUTO TYPE

El contenido permitido del elemento se determina mediante su tipo. Como ya ha aparecido en los ejemplos anteriores, los tipos se dividen en simples y complejos. Recordemos el siguiente ejemplo:

<?xml version="1.0" encoding="UTF-8"?> <schema xmlns="http://www.w3.org/2001/XMLSchema" xmlns:target="http://www.example.com/persona" targetNamespace="http://www.example.com/persona" elementFormDefault="qualified"> <element name="persona"> <complexType> <sequence> <element name="nombre" type="string"/> <element name="apellido1" type="string"/> <element name="apellido2" type="string"/> </sequence> <attribute name="título" type="string"/> </complexType> </element> </schema>

Si nos fijamos, aparece varias veces el tipo "string" (cadena de texto). Éste es un tipo simple. En concreto, los elementos llamados "nombre", "apellido1" y "apellido2" son de este tipo. Pero también aparece el tipo del elemento "persona". Es un tipo complejo, formado por tres elementos de tipo simple, es decir, "nombre", "apellido1" y "apellido2".

Los Esquemas XML permiten especificar el tipo de un elemento de dos maneras:

  • Crear un tipo local
  • Usar un tipo global

Además de estos dos métodos, se pueden reutilizar declaraciones de elementos en vez de crear otros nuevos. Esto se hace referenciando a una declaración de elemento global en vez de especificar un tipo en la referencia. Hablando en cristiano, o decimos que un elemento es de "este o aquel" tipo, o sencillamente decimos que su tipo es igual que la de "este o aquel" elemento global. El problema que tenemos es que todavía no sabemos qué es un elemento global... pero es lo siguiente que veremos. Si optamos por el segundo camino, el tipo del elemento debe estar declarado globalmente.

Vamos a aclarar esto de elemento global o local.

ELEMENTO GLOBAL O LOCAL

Declaración global: son declaraciones que aparecen como hijos directos del elemento <schema>. Los elementos globales pueden ser reusados a lo largo de todo el esquema.

Declaración local: no tienen a <schema> como elemento padre directo y solo pueden ser usados en su ámbito.

Veamos un ejemplo:

<?xml version="1.0"?> <schema xmlns="http://www.w3.org/2001/XMLSchema" xmlns:target="http://www.example.com/persona" targetNamespace="http://www.example.com/persona" elementFormDefault="qualified"> <element name="persona"> <complexType> <sequence> <element name="nombre" type="string"/> <element name="apellido1" type="string"/> <element name="apellido2" type="string"/> </sequence> <attribute name="título" type="string"/> </complexType> </element> </schema>

Este esquema tiene cuatro declaraciones <element>. La primera declaración es global. Las declaraciones de <nombre>, <apellido1> y <apellido2> son locales, por lo que son válidas solamente dentro de la declaración <sequence>, y no pueden ser usadas en otra parte del esquema.

TIPO GLOBAL

Ahora volvamos con lo del tipo local y global. Un tipo local es un tipo definido para un elemento local.

Un modo de aprovechar esto de la globalidad es la siguiente. Es común que muchos de los elementos declarados tengan el mismo contenido. En vez de declarar por duplicado los tipos locales, podemos crear un tipo global y reusarlo una y otra vez a lo largo de la declaración. Por ejemplo:

<schema xmlns="http://www.w3.org/2001/XMLSchema" xmlns:target="http://www.example.com/persona" targetNamespace="http://www.example.com/persona" elementFormDefault="qualified"> <complexType name="TipoNombre"> <sequence> <element name="nombre" type="string"/> <element name="apellido1" type="string"/> <element name="apellido2" type="string"/> </sequence> <attribute name="título" type="string"/> </complexType> <element name="persona" type="target:TipoNombre"/> </schema>

NOTA: ¡Buenooo! Por fin aparece el uso del prefijo "target". Está ahí para indicar que el tipo "TipoNombre" es un tipo del espacio de nombres "http://www.example.com/persona". Espero que quede aclarado el tema del targetNamespace comentado en una nota anterior.

Como se puede ver, se reutiliza el tipo global "TipoNombre". No quiero liar mucho con los namespaces, pero hay que pensar que el prefijo "target" se utiliza en el tipo del elemento "persona", porque el espacio de nombres por defecto es el de los Esquemas XML. Podríamos hacer lo anterior también del siguiente modo:

<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns="http://www.example.com/persona" targetNamespace="http://www.example.com/persona" elementFormDefault="qualified"> <xs:complexType name="TipoNombre"> <xs:sequence> <xs:element name="nombre" type="xs:string"/> <xs:element name="apellido1" type="xs:string"/> <xs:element name="apellido2" type="sx:string"/> </xs:sequence> <xs:attribute name="título" type="xs:string"/> </xs:complexType> <xs:element name="persona" type="TipoNombre"/> </xs:schema>

COMO REFERENCIAR A UN ELEMENTO GLOBAL EXISTENTE

Antes comenté que era posible definir el tipo de un elemento haciendo referencia a otro elemento global. Un ejemplo tonto: En vez de decir "yo soy de tipo friki", podría decir "yo soy de tipo Sheldon Cooper", haciendo así referencia una persona global que es del mismo tipo que yo.

Observa el siguiente ejemplo:

<?xml version="1.0"?> <schema xmlns="http://www.w3.org/2001/XMLSchema" xmlns:target="http://www.example.com/persona" targetNamespace="http://www.example.com/persona" elementFormDefault="qualified"> <element name="nombre" type="string"/> <element name="apellido1" type="string"/> <element name="apellido2" type="string"/> <complexType name="TipoNombre"> <sequence> <!-- Aquí hacemos referencia a los elementos globales anteriormente definidos, nombre, apellido1 y apellido2 --> <element ref="target:nombre"/> <element ref="target:apellido1"/> <element ref="target:apellido2"/> </sequence> <attribute name="título" type="string"/> </complexType> <element name="persona" type="target:TipoNombre"/> </schema>

Si se observa el comentario, se observa que se está haciendo referencia a elementos globales (definidos bajo <schema>) mediante ref="target:nombre_elemento". Recuerdo otra vez lo de los espacios de nombres. Hay que poner "target" como prefijo, porque el espacio de nombres por defecto es el de los esquemas XML.

PONIENDO NOMBRE A LOS ELEMENTOS

Poner nombre a un elemento es tan difícil como dar un valor al atributo "name". Lo único a tener en cuenta es que no se pueden usar ":", no se puede empezar por número, y se puede incluir los símbolos ".", "-" y "_" siempre y cuando empiece por una letra o por "_".

Actividad 1. Partiendo de la siguiente definición de esquema, crea un documento XML válido que lo utilice. Para el contenido de los elementos, puedes inventarte los valores.Una vez crees una instancia, valida el ejemplo.

<?xml version="1.0" encoding="UTF-8"?> <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns="http://www.ejemplo.com/agenda" targetNamespace="http://www.ejemplo.com/agenda" elementFormDefault="qualified"> <!-- Definición del tipo global "TipoNombre" que será reutilizado más adelante"--> <xs:complexType name="TipoNombre"> <xs:sequence> <xs:element name="nombre" type="xs:string"/> <xs:element name="apellido1" type="xs:string"/> <xs:element name="apellido2" type="xs:string"/> </xs:sequence> </xs:complexType> <!-- Definición del tipo global "TipoDirección que será reutilizado más adelante --> <xs:complexType name="TipoDirección"> <xs:sequence> <xs:element name="calle" type="xs:string"/> <xs:element name="ciudad" type="xs:string"/> </xs:sequence> </xs:complexType> <!-- Definición del elemento raíz "agenda" --> <xs:element name="agenda"> <xs:complexType> <xs:sequence> <xs:element name="persona" type="TipoNombre"/> <xs:element name="teléfono" type="xs:string"/> <xs:element name="dirección" type="TipoDirección"/> </xs:sequence> </xs:complexType> </xs:element> </xs:schema>

Actividad 2. Modifca el esquema anterior, haciendo ahora que el espacio de nombres por defecto sea el de los esqumas XML. Utiliza el prefijo "target" donde creas necesario. Una vez termines, vuelve a validar.

Entregar con el nombre Act2-esquemas.xsd. Entrega también el documento obtenido en el ejercicio anterior, para poder ser validado, con el nombre Act2-instancia-esquemas.xml

saludos.