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!

4 comentarios

  1. Hola podrias explicarme en la parte del email como lo has hecho
    o mejor dicho porque
    <xs:pattern value= w+(........ etc

    ResponderEliminar
  2. Hola Marcela Betancourt, que grato es encontrar una tocaya!
    Gracias por escribir tu duda y una disculpa por contestar hasta hoy.

    Contestando a tu pregunta sobre el porque hice lo del email, la respuestas es que fue para ejemplificar las restricciones que se pueden agregar a un tipo simple que en este caso es String. Si bien sabemos String es un tipo de dato que acepta, letras, números, caracteres especiales y casi cualquier caracter, pero algunas veces nosotros queremos tener un subtipo muy específico porque no nos interesa tener un campo que acepte cualquier caracter sino una secuencia de caracteres específica como lo es el email. Es por esto que decidí usar la restricción para String de "pattern" en la cuál quiero especificar que mi cadena de caracteres a validar va a ser la que coincida con el patrón que ponga en el tag de restriction. El patrón que formé fue el de un email valido estandarizado que consta de un nombre de correo ("\w+([-+.']\w+)*") el símbolo @ y un dominio ("\w+([-.]\w+)*\.\w+([-.]\w+)*").







    Como ves sacar esta restriction de String como un tipo simple me permite poder reutilizarla después, si llegara a crecer mi XML a tener más campos y existiera otro campo a validar que sea un email podré utilizar este tipo simple para validarlo.

    Espero te sirva la respuesta, cualquier otra duda o sugerencia aquí estoy. Saludos!

    ResponderEliminar
  3. Hola, ¿Alguien sabe que es un XSD con y sin los tipos de datos embebidos?

    ResponderEliminar
  4. No estoy familiarizada con ese término de Tipos de Datos embebidos. ¿Tienes alguna otra referencia por la cual buscar? porque no creo que sea un término estándar.

    ResponderEliminar

Seguidores

Google+ Followers

MarceStarlet. Con la tecnología de Blogger.