Hola a tod@s, en este post les vengo a hablar de los famosos XML Schemas o bien llamados XSD. Esta es una introducción con fundamentos para crear XSD para validar una estructura XML.
Se asume que el que lee tiene conocimientos de HTML / XHTML, XML, XML Namespaces y un poco de conocimientos de DTD. Si no los tiene se sugiere lea un poco sobre estos temas antes de continuar leyendo.
¿Qué es un XML Schema ó XSD?
Un XML Schema tiene como propósito definir la estructura correcta de tags en un archivo XML como lo hacen los DTD.
Un XML Schema define elementos que pueden aparecer en un documento XML así como sus atributos. Define cuales elementos son hijos, el orden de los elementos, su tipo y el de sus atributos, si están vacíos, etc.
¿Por qué usarlos?
Se considera que los XML Schemas vienen a substituir a los DTD por que poseen varias ventajas sobre los anteriores.
Al ser un archivo con formato XML éste puede ser validado y saber si está bien formado. Además soportan datatypes y namespaces lo que los hace más poderosos y ricos que los DTD, y extensibles para posibles cambios.
Propone un ambiente seguro para la comunicación de datos ya que de esta forma podemos asegurarnos que al existir una transferencia de datos por XML ambos lados estarán transmitiendo la información en los formatos adecuados y así evitar diferencias en la información.
¿Cómo usarlos?
Como dijimos anteriormente un XSD existe para un XML, para definirlo y validarlo. A partir de esta premisa podemos partir de 2 cosas: podemos tener una XML el cual queramos validar y a partir de él crear un XSD ó podemos crear un XSD que defina la estructura de un XML y posterior a eso crear el archivo XML para validarlo. Por lo general es más sencillo verlo de la primer forma en la que ya existe un XML y lo validaremos y definiremos con un XSD ya que es más fácil pensar en un XML que en un definidor de XML.
XML
Tomando en cuenta el primer caso en que ya tenemos un XML que validar observe el siguiente código XML del archivo llamado XMLAgenda.xml
<?xml version="1.0" encoding="UTF-8"?>
<Contacts name="MyContacts" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="XSDAgendaNamedTypes.xsd">
<userID>msena</userID>
<info>
<name>Marcela Sena</name>
<age>25</age>
<tel type="Mobile">
<number>3312345678</number>
</tel>
</info>
<contact>
<name>Liliana Sena</name>
<address>15 California St.</address>
<tel type="Mobile">
<number>1234567890</number>
</tel>
</contact>
<contact>
<name>Juan Sandoval</name>
<email>jsandoval@domain.com</email>
<address>189 San Francisco St.</address>
<tel type="Home">
<number>0987654321</number>
</tel>
</contact>
</Contacts>
El código estructura la información de una lista de contactos iniciando con un nombre, nombre de usuario, datos personales del owner y una lista de elementos que engloban los datos de cada contacto.
XSD
Ahora que tenemos el XML debemos definir una estructura XSD para definir nuestro XML y así validarlo.
Lo primero que debemos hacer es definir nuestro archivo como un XML y crear la estructura base del schema con un namespace default xs de la siguiente forma:
<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
.
.
</xs:schema>
La URI asociada al xs namespace de nuetro XSD es el Lenguaje de Definición de Schemas con un valor estándar http://www.w3.org/2001/XMLSchema.
Ya que tenemos nuestra estructura base del Schema debemos crear los elementos y atributos que lo conforman, primero comenzaremos con "Contacts" que tiene un atributo y más elementos y es considerado como un complex type. Los elementos de Contacts estarán definidos dentro de xs:sequence que define una secuencia ordenada de sub elementos:
<xs:element name="Contacts">
<xs:complextype>
<xs:sequence>
.
.
</xs:sequence>
</xs:complextype>
</xs:element>
A continuación debemos definir el elemento "userID" como un tipo simple ya que no tiene atributos ni otros sub elementos. El type xs:string tiene el prefijo xs default del schema definiendo así que es un tipo base del schema:
<xs:element name="userID" type="xs:string"/>
Los siguientes elementos
complex type a definir son
"info" y
"contact", vamos a comenzar con info:
<xs:element name="info">
<xs:complexType>
<xs:sequence>
<xs:element name="name" type="xs:string"/>
<xs:element name="age" type="xs:positiveInteger"/>
<xs:element name="tel">
<xs:complexType>
<xs:sequence>
<xs:element name="number" type="xs:string"/>
</xs:sequence>
<xs:attribute name="type" type="xs:string" use="required"/>
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>
El elemento "info" tiene 2 elementos de tipo simple y un complex type es por eso que <tel> es definido como un complex type con un atributo y un elemento simple.
Para el elemento "contact" tenemos 3 elementos simples y el mismo complex type <tel> que en "info". En el caso de "contact" utilizamos maxOccurs como "unbounded" que indica que este elemento puede haber muchas ocurrencias de éste. Nosotros podemos utilizar maxOccurs y minOccurs para indicar la cantidad máxima y mínima de veces que puede aparecer un elemento, el valor por default para ambos es 1.
<xs:element name="contact" maxOccurs="unbounded">
<xs:complexType>
<xs:sequence>
<xs:element name="name" type="xs:string"/>
<xs:element name="email" type="xs:string" minOccurs="0"/>
<xs:element name="address" type="xs:string"/>
<xs:element name="tel">
<xs:complexType>
<xs:sequence>
<xs:element name="number" type="xs:string"/>
</xs:sequence>
<xs:attribute name="type" type="xs:string" use="required"/>
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>
Ahora podemos definir el atributo "name" del elemento "Contacts", y como es un atributo requerido lo definimos como use=required.
Nota: Los atributos deben ser definidos siempre al final.
<xs:attribute name="name" type="xs:string" use="required"/>
Listo, ahora tenemos el código completo de nuestro schema XSDAgendaAnidada.xsd
<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:element name="Contacts">
<xs:complexType>
<xs:sequence>
<xs:element name="userID" type="xs:string"/>
<xs:element name="info">
<xs:complexType>
<xs:sequence>
<xs:element name="name" type="xs:string"/>
<xs:element name="age" type="xs:positiveInteger"/>
<xs:element name="tel">
<xs:complexType>
<xs:sequence>
<xs:element name="number" type="xs:string"/>
</xs:sequence>
<xs:attribute name="type" type="xs:string" use="required"/>
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="contact" maxOccurs="unbounded">
<xs:complexType>
<xs:sequence>
<xs:element name="name" type="xs:string"/>
<xs:element name="email" type="xs:string" minOccurs="0"/>
<xs:element name="address" type="xs:string"/>
<xs:element name="tel">
<xs:complexType>
<xs:sequence>
<xs:element name="number" type="xs:string"/>
</xs:sequence>
<xs:attribute name="type" type="xs:string" use="required"/>
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:sequence>
<xs:attribute name="name" type="xs:string" use="required"/>
</xs:complexType>
</xs:element>
</xs:schema>
Schema Dividido
Hemos visto un ejemplo muy básico de como crear un XSD, como ven el código es muy estructurado y anidado y no permite que podamos reutilizar elementos. Aquí presentamos otra versión con un código dividido que permite la reutilización de elementos. Esta forma de dividir define primero los elementos de tipo simple, en seguida los atributos y por último los elementos complex type que harán referencia a los elementos simples antes declarados con ref. Observe que ahora el elemento <tel> se puede reutilizar en "info" y en "contact".
<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<!--************Elements************-->
<xs:element name="userID" type="xs:string"/>
<xs:element name="name" type="xs:string"/>
<xs:element name="age" type="xs:positiveInteger"/>
<xs:element name="email" type="xs:string"/>
<xs:element name="address" type="xs:string"/>
<!--************Attributes************-->
<xs:attribute name="name" type="xs:string"/>
<xs:attribute name="type" type="xs:string"/>
<!--************Elements with ComplexType************-->
<xs:element name="tel">
<xs:complexType>
<xs:sequence>
<xs:element name="number" type="xs:string"/>
</xs:sequence>
<xs:attribute name="type" use="required"/>
</xs:complexType>
</xs:element>
<xs:element name="info">
<xs:complexType>
<xs:sequence>
<xs:element ref="name"/>
<xs:element ref="age"/>
<xs:element ref="tel"/>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="contact">
<xs:complexType>
<xs:sequence>
<xs:element ref="name"/>
<xs:element ref="email" minOccurs="0"/>
<xs:element ref="address" />
<xs:element ref="tel"/>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="Contacts">
<xs:complexType>
<xs:sequence>
<xs:element ref="userID" />
<xs:element ref="info"/>
<xs:element name="contact" maxOccurs="unbounded"/>
</xs:sequence>
<xs:attribute ref="name" use="required"/>
</xs:complexType>
</xs:element>
</xs:schema>
Schema de Named Types
Otra forma de reutilizar es definiendo tipos nombrados ó Named Types. Esto es que podemos definir tipos simples y complejos (simple and complex types), tipos simples que definan mejor nuestras necesidades basados en tipos de schemas, y tipos complejos que permitan agrupar y simplificar código como en el caso de <tel>.
<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:simpleType name="myString">
<xs:restriction base="xs:string"/>
</xs:simpleType>
<xs:simpleType name="myIntegerAge">
<xs:restriction base="xs:positiveInteger">
<xs:totalDigits value="2"/>
</xs:restriction>
</xs:simpleType>
<xs:simpleType name="myStringEmail">
<xs:restriction base="xs:string">
<xs:pattern value="\w+([-+.']\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*"/>
</xs:restriction>
</xs:simpleType>
<xs:simpleType name="myStringTel">
<xs:restriction base="xs:string">
<xs:pattern value="[0-9]{10}"/>
</xs:restriction>
</xs:simpleType>
<xs:simpleType name="myStringTelAttribute">
<xs:restriction base="xs:string">
<xs:enumeration value="Mobile"/>
<xs:enumeration value="Home"/>
<xs:enumeration value="Work"/>
</xs:restriction>
</xs:simpleType>
<xs:complexType name="teltype">
<xs:sequence>
<xs:element name="number" type="myStringTel"/>
</xs:sequence>
<xs:attribute name="type" type="myStringTelAttribute" use="required"/>
</xs:complexType>
<xs:complexType name="infotype">
<xs:sequence>
<xs:element name="name" type="myString"/>
<xs:element name="age" type="myIntegerAge"/>
<xs:element name="tel" type="teltype"/>
</xs:sequence>
</xs:complexType>
<xs:complexType name="contacttype">
<xs:sequence>
<xs:element name="name" type="myString"/>
<xs:element name="email" type="myStringEmail" minOccurs="0"/>
<xs:element name="address" type="myString" />
<xs:element name="tel" type="teltype"/>
</xs:sequence>
</xs:complexType>
<xs:element name="Contacts">
<xs:complexType>
<xs:sequence>
<xs:element name="userID" type="myString"/>
<xs:element name="info" type="infotype"/>
<xs:element name="contact" type="contacttype" maxOccurs="unbounded"/>
</xs:sequence>
<xs:attribute name="name" type="myString" use="required"/>
</xs:complexType>
</xs:element>
</xs:schema>
Note que existe un nuevo tag llamado restriction para los tipos simples que nos ayuda a definir a partir de un tipo base una restricción que nos ayuda a hacer más específica nuestra definición. El tag restriction puede tener diferentes sub tags como pattern, enumeration o totalDigits según sea el tipo base. En el caso del tipo base string xs:string tenemos que pattern nos ayuda a definir una expresión regular con la cual definir específicamente un valor (vease "myStringEmail") en caso de no hacer match con la expresión marcará error. También es posible definir una enumeración de valores que puede contener un tipo definiendo dentro de restriction una serie de enumeration tags con los valores válidos; en caso de que el valor existente en el XML no coincida con los valores de la enumeración marcará un error. Para el caso del tipo xs:positiveInteger tenemos que podemos definir el número total de Dígitos que puede tener con totalDigits; en caso de no tener los dígitos especificados marcará error.
<xs:restriction base="xs:string">
<xs:pattern value="\w+([-+.']\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*"/>
<!-- OR -->
<xs:enumeration value="Mobile"/>
<xs:enumeration value="Home"/>
<xs:enumeration value="Work"/>
</xs:restriction>
<!--*****************************************************************-->
<xs:restriction base="xs:positiveInteger">
<xs:totaldigits value="2"/>
</xs:restriction>
Este post esta basado en el
Tutorial de Esquemas de w3school.
Espero este post con fundamentos sea de utilidad para alguien y como siempre invito a todos a colaborar con sus comentarios, sugerencias, dudas y demás. ¡Hasta pronto!