Extraer un XSD a partir de un XML existente

A veces me he encontrado en la tesitura de tener un XML maravilloso con toda la información que necesito y querer explotar su información mediante Java.

Si utilizamos JAXB, el estándar de Java para el tratamiento XML-JAVA, veremos que el compilador xjc, incluido en la JDK (a partir de la versión 1.6.0_3), necesita un XSD para generar los objetos java correspondientes.

Por tanto, el primer paso, y el motivo de esta entrada, es tratar las diferentes maneras que hay de convertir el XML en un XSD.

Voy a utilizar para la generación del XSD un XML de ejemplo sencillo, pero suficiente, para ver las diferencias entre las herramientas presentadas.

<?xml version="1.0" encoding="UTF-8"?>
<servers>
	<server id="svnserver.com">
		<ip>192.168.1.1</ip>
		<osuser>souser</osuser>
		<ospass>sopass</ospass>
		<repositories>
			<repository name="a53">
				<url>http://svnserver.com/svn/a53</url>
				<svnuser>user</svnuser>
				<svnpass>pass</svnpass>
			</repository>
			<repository name="i07">
				<url>http://svnserver.com/svn/i07</url>
				<svnuser>user</svnuser>
				<svnpass>pass</svnpass>
			</repository>
		</repositories>
	</server>
	<server id="svnserver2.com">
		<ip>192.168.1.2</ip>
		<osuser>souser</osuser>
		<ospass>sopass</ospass>
		<repositories>
			<repository name="a53_backup">
				<url>http://svnserver.com/svn/a53_backup</url>
				<svnuser>user</svnuser>
				<svnpass>pass</svnpass>
			</repository>
		</repositories>
	</server>
</servers>

Trang.jar (http://www.thaiopensource.com/relaxng/trang.html)

Este programa no se actualiza desde 2008, pero tampoco es preocupante porque tampoco ha cambiado la especificación W3C de los XML Schemas. Se puede descargar directamente desde su página web y es muy sencillo de utilizar.

usage: java -jar trang.jar 
		[-I rng|rnc|dtd|xml] 
		[-O rng|rnc|dtd|xsd] 
		[-i input-param] 
		[-o output-param] 
		inputFileOrUri ... outputFile

Partiendo del XML de ejemplo propuesto, la respuesta de trang ha sido la siguiente:

<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified">
  <xs:element name="servers">
    <xs:complexType>
      <xs:sequence>
        <xs:element maxOccurs="unbounded" ref="server"/>
      </xs:sequence>
    </xs:complexType>
  </xs:element>
  <xs:element name="server">
    <xs:complexType>
      <xs:sequence>
        <xs:element ref="ip"/>
        <xs:element ref="osuser"/>
        <xs:element ref="ospass"/>
        <xs:element ref="repositories"/>
      </xs:sequence>
      <xs:attribute name="id" use="required" type="xs:NCName"/>
    </xs:complexType>
  </xs:element>
  <xs:element name="ip" type="xs:NMTOKEN"/>
  <xs:element name="osuser" type="xs:NCName"/>
  <xs:element name="ospass" type="xs:NCName"/>
  <xs:element name="repositories">
    <xs:complexType>
      <xs:sequence>
        <xs:element maxOccurs="unbounded" ref="repository"/>
      </xs:sequence>
    </xs:complexType>
  </xs:element>
  <xs:element name="repository">
    <xs:complexType>
      <xs:sequence>
        <xs:element ref="url"/>
        <xs:element ref="svnuser"/>
        <xs:element ref="svnpass"/>
      </xs:sequence>
      <xs:attribute name="name" use="required" type="xs:NCName"/>
    </xs:complexType>
  </xs:element>
  <xs:element name="url" type="xs:anyURI"/>
  <xs:element name="svnuser" type="xs:NCName"/>
  <xs:element name="svnpass" type="xs:NCName"/>
</xs:schema>

El XSD generado es un XSD básico, sencillo y útil. Lo único que no termina de convencerme es la tipificación xs:NCName (Non Colonized Name) que le ha dado a las cadenas de texto. No es una tipificación incorrecta, pero limita el uso de qualified names (nombres con namespace) en el contenido del texto.

inst2xsd (http://xmlbeans.apache.org/index.html)

La herramienta inst2xsd viene incluida en el software XMLBEANS de Apache Foundation que se puede descargar directamente desde su página web. Esta herramienta es más completa que Trang, aunque sin ser difícil también es más compleja de utilizar.

Usage: inst2xsd [opts] [instance.xml]*
Options include:
    -design [rd|ss|vb] - XMLSchema design type
             rd  - Russian Doll Design - local elements and local types
             ss  - Salami Slice Design - global elements and local types
             vb  - Venetian Blind Design (default) - local elements and global complex types
    -simple-content-types [smart|string] - Simple content types detection (leaftext). Smart is the default
    -enumerations [never|NUMBER] - Use enumerations. Default value is 10.
    -outDir [dir] - Directory for output files. Default is '.'
    -outPrefix [file_name_prefix] - Prefix for output file names. Default is 'schema'
    -validate - Validates input instances agaist generated schemas.
    -verbose - print more informational messages
    -license - print license information
    -help - help imformation

Para generar el siguiente XML he utilizado la opción -enumerations never para evitar que los valores que se incluyen en el ejemplo se convierten en enumeraciones en el XSD final.

<?xml version="1.0" encoding="UTF-8"?>
<xs:schema attributeFormDefault="unqualified" elementFormDefault="qualified" xmlns:xs="http://www.w3.org/2001/XMLSchema">
  <xs:element name="servers" type="serversType"/>
  <xs:complexType name="repositoryType">
    <xs:sequence>
      <xs:element type="xs:anyURI" name="url"/>
      <xs:element type="xs:string" name="svnuser"/>
      <xs:element type="xs:string" name="svnpass"/>
    </xs:sequence>
    <xs:attribute type="xs:string" name="name" use="optional"/>
  </xs:complexType>
  <xs:complexType name="repositoriesType">
    <xs:sequence>
      <xs:element type="repositoryType" name="repository" maxOccurs="unbounded" minOccurs="0"/>
    </xs:sequence>
  </xs:complexType>
  <xs:complexType name="serverType">
    <xs:sequence>
      <xs:element type="xs:string" name="ip"/>
      <xs:element type="xs:string" name="osuser"/>
      <xs:element type="xs:string" name="ospass"/>
      <xs:element type="repositoriesType" name="repositories"/>
    </xs:sequence>
    <xs:attribute type="xs:string" name="id" use="optional"/>
  </xs:complexType>
  <xs:complexType name="serversType">
    <xs:sequence>
      <xs:element type="serverType" name="server" maxOccurs="unbounded" minOccurs="0"/>
    </xs:sequence>
  </xs:complexType>
</xs:schema>

El XSD generado con esta herramienta es aún más simple que el generado con Trang. Los objetos que contienen texto los ha tipificado como xs:string que puede contener cualquier tipo de texto.

Conclusión

Existen más herramientas en el mercado para obtener ficheros XML Schema a partir de ficheros XML. Algunas son de pago, otras son más complejas, otras tienen interfaz gráfico, pero las dos herramientas descritas en esta entrada son suficientes para obtener XSD de calidad de forma fácil y gratuita.

Si bien me ha gustado más la generación de la herramienta inst2xsd por la generación menos restrictiva de las cadenas de texto, ambas herramientas han generado XSD aptos para utilizar con JAXB y poder generar las clases Java necesarias para tratar la información del XML.

Links

Xml Schema W3C
NCName

WSDL (III) – wsdl:types

Esta entrada es la tercera que estoy dedicando al interesante mundo del WSDL. En esta entrada encontrarás en detalle el elemento wsdl:types, que contiene la definición de los tipos de datos que se utilizan en las operaciones del servicio web. Es muy importante definir estos tipos de datos para garantizar que la comunicación entre el cliente y el proveedor del servicio se entienden sin errores.

Descripción

Wsdl:types forma parte del objeto wsdl:definitions.

El elemento wsdl:types define directamente los tipos de datos que se utilizan en las operaciones del servicio web. Esta definición de tipos se realiza mediante Esquemas XML (XSD) que son los encargados de validar el correcto formato de los mensajes en la comunicación entre cliente y proveedor.

Gramática

La gramática del elemento wsdl:types es la siguiente (? –> es opcional, * –> pueden ser de 0 a N elementos):

<wsdl:types> ?
   <wsdl:documentation .... />?
     <xsd:schema .... />*
     <-- extensibility element --> *
</wsdl:types>

SubElementos

El elemento wsdl:documentation es opcional y muestra la información general de los tipos de datos que se definen en este apartado. 

El elemento xsd:schema define los tipos de datos haciendo uso de esquemas XML. Esta definición puede hacerse de dos maneras: 

  • Directamente en el WSDL utilizando el lenguaje XSD.
  • Importando un XSD ya existente en el WSDL.

Ejemplo de WSDL con tipos de datos incluidos implícitamente.

Este ejemplo muestra la definición del tipo de dato ConsultaDuplicidadesTratamientoNuevoPet que es la entrada (input) de la operación ConsultaDuplicidadesTratamientoNuevo que proporciona el servicio web AsistenteProductoWS. 

<xsd:schema

   xmlns:Q1=»http://VDM/esquemas/comun»

   xmlns:this=»http://VDM/esquemas/asistenteproducto»

   xmlns:xsd=»http://www.w3.org/2001/XMLSchema»

   targetNamespace=»http://VDM/esquemas/asistenteproducto»>

     

  <xsd:complexType nametipoCabecera«>

    <xsd:annotation>
    <xsd:documentation>
   Todas las peticiones que reciba el sistema universal de prescripción tendrán una cabecera    fija. Los datos de esta cabecera están en este tipo.
      </xsd:documentation>
      </xsd:annotation>
      <xsd:sequence>
      <xsd:element nameFechaHoraPet« typexsd:dateTime«>
      <xsd:annotation>
      <xsd:documentation>Fecha y hora de la petición</xsd:documentation>
      </xsd:annotation>
      </xsd:element>
      <xsd:element nameIdioma« typethis:tipoIdioma«>
      <xsd:annotation>
      <xsd:documentation>
     Idioma de la petición. Pudiendo ser los valores indicados en el tipo tipoIdioma: Euskera y Castellano.
      </xsd:documentation>
      </xsd:annotation>
      </xsd:element>
      <xsd:element nameIdTransaccion« typexsd:string«>
      <xsd:annotation>
       <xsd:documentation>
      El id de transacción es único para cada petición y generado por la aplicación cliente de la plataforma SUPRE.
       </xsd:documentation>
      </xsd:annotation>
      </xsd:element>
      </xsd:sequence>
     </xsd:complexType>

     <xsd:complexType nametipoElementoConsultaDuplicidades«>
     <xsd:annotation>
      <xsd:documentation>Información para la consulta de duplicidades</xsd:documentation>
     </xsd:annotation>
     <xsd:sequence>
     <xsd:element nameIdPrescripcion« typexsd:string«>
      <xsd:annotation>
       <xsd:documentation>Identificador de prescripción</xsd:documentation>
      </xsd:annotation>
     </xsd:element>
     <xsd:element nameIdUnicoProducto« typexsd:long«>
      <xsd:annotation>
       <xsd:documentation>Identificador único de producto de la prescripción</xsd:documentation>
      </xsd:annotation>
     </xsd:element>
     </xsd:sequence>
     </xsd:complexType>

     <xsd:complexType name=»tipoConsultaDuplicidadesTratamientoNuevoPet»>

       <xsd:annotation>

          <xsd:documentation>Información de la petición</xsd:documentation>

       </xsd:annotation>

       <xsd:sequence>

          <xsd:element name=»Cabecera» type=»this:tipoCabecera»>

             <xsd:annotation>

               <xsd:documentation>Información de la cabecera</xsd:documentation>

             </xsd:annotation>

          </xsd:element>

          <xsd:element name=»TratamientoNuevo» type=»this:tipoElementoConsultaDuplicidades»>

            <xsd:annotation>

              <xsd:documentation>

              Datos del tratamiento nuevo para comprobar su duplicidad

              </xsd:documentation>

            </xsd:annotation>

          </xsd:element>

          <xsd:element 

              maxOccurs=»unbounded» 

              name=»TratamientosComparacion» 

              type=»this:tipoElementoConsultaDuplicidades»>

             <xsd:annotation>

               <xsd:documentation>

                 Lista de datos de tratamiento que ya tenía el paciente para comprobar su duplicidad

               </xsd:documentation>

             </xsd:annotation>

          </xsd:element>

       </xsd:sequence>

     </xsd:complexType>


     <xsd:element 

         name=»ConsultaDuplicidadesTratamientoNuevoPet»

         type=»this:tipoConsultaDuplicidadesTratamientoNuevoPet»>

        <xsd:annotation>

           <xsd:documentation>Consulta de duplicidades que permite comprobar si un Principio Activo ATC no está duplicado en otros con el resto de prescripciones ya creadas. La respuesta esperada es una lista de pares del Tratamiento Nuevo con alguno de los tratamientos que ya tenía el paciente

           </xsd:documentation>

        </xsd:annotation>

     </xsd:element>

</xsd:schema>

 

El elemento base que define el tipo de dato ConsultaDuplicidadesTratamientoNuevoPet está definido en verde.

<xsd:element 
     name="ConsultaDuplicidadesTratamientoNuevoPet"
     type="this:tipoConsultaDuplicidadesTratamientoNuevoPet"
</xsd:element>

 

Entre sus atributos destacan el nombre del tipo de dato y el tipo, que en este caso es un tipo de dato complejo definido en el elemento tipoConsultaDuplicidadesTratamientoNuevoPet de wsdl:types. Este elemento, tal y como indica el prefijo this, se tiene que encontrar en este mismo fichero. Si nos fijamos en los namespaces incluidos en el elemento xsd:schema veremos que hace referencia a sí mismo http://VDM/esquemas/asistenteproduto

xsd:schema
   targetNamespace="http://VDM/esquemas/asistenteproducto"
   xmlns:Q1="http://VDM/esquemas/comun"
   xmlns:this="http://VDM/esquemas/asistenteproducto"
   xmlns:xsd="http://www.w3.org/2001/XMLSchema"   

 

El objeto tipoConsultaDuplicidadesTratamientoNuevoPet está definido por una secuencia de tres elementos, una cabecera, un tratamiento nuevo y una lista de tratamientos de comparación. 

<xsd:complexType name="tipoConsultaDuplicidadesTratamientoNuevoPet">
  <xsd:sequence>
    <xsd:element name="Cabecera" type="this:tipoCabecera">
    <xsd:element name="TratamientoNuevo" type="this:tipoElementoConsultaDuplicidades">
    <xsd:element 
       maxOccurs="unbounded" 
       name="TratamientosComparacion
       type="this:tipoElementoConsultaDuplicidades">
  </xsd:sequence>
</xsd:complexType>


El elemento Cabecera es un elemento complejo definido en un objeto this.tipoCabecera que está definido en el propio WSDL. El elemento TratamientoNuevo es un elemento complejo definido en el objeto this.tipoElementoConsultaDuplicidades también definido en el propio WSDL. El elemento TratamientosComparacion es un elemento complejo definido por el mismo objeto que TratamientoNuevo, sólo que esta vez se repite ilimitadamente (maxOccurs=»unbounded»). 

Si un elemento no tiene definido el atributo maxOccurs por defecto tomará el valor «1«. El valor «unbounded» indica que el elemento puede repetirse de forma ilimitada.

La definición de los elementos tipoCabecera y tipoElementoConsultaDuplicidades se encuentran también en el elemento wsdl:types. En el ejemplo se muestran en azul. Estos objetos complejos están definidos por elementos primitivos. Por ejemplo, el tipo complejo tipoElementoConsultaDuplicidades está formado por dos tipos primitivos, IdPrescripcion que es un xsd:string y IdUnicoProducto que es un xsd:long.  

     <xsd:complexType name="tipoElementoConsultaDuplicidades">
     <xsd:sequence>
     <xsd:element nameIdPrescripcion« typexsd:string«>
   <xsd:element nameIdUnicoProducto« typexsd:long«>
  </xsd:sequence>
     </xsd:complexType>

Los tipos primitivos válidos se pueden encontrar en el apartado datatypes de la especificación Schema XML. 

Ejemplo de WSDL con tipos de datos importados.

Importar el tipo de datos aporta sencillez al diseño del WSDL facilitando la identificación a primera vista de los tipos de datos. Si se quiere profundizar en los mismos se podrá acceder a través de la URI de schemaLocation al detalle del tipo de dato que está definido en su correspondiente XSD.

En el siguiente ejemplo se define el tipo de dato ConsultaDuplicidadesTratamientoNuevoPet pero utilizando ésta vez un fichero xsd. 

<wsdl:types>
  <xsd:schema 
     xmlns:asp="http://VDM/esquemas/asistenteproducto"
     xmlns:ns1="http://cxf.apache.org/bindings/xformat"       
     xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
     xmlns:tns="http://VDM/wsdls/AsistenteProductoWSDL" 
     xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
     xmlns:xs="http://www.w3.org/2001/XMLSchema"     
     xmlns:xsd="http://www.w3.org/2001/XMLSchema"
     elementFormDefault="unqualified" 
     targetNamespace="http://VDM/wsdls/AsistenteProductoWSDL" 
     version="1.0">
   
   <xsd:import 
                      xsd=xsd/ConsultaDuplicidadesTratamientoNuevoPet.xsd«/>
 </xs:schema>
</wsdl:types>

Claramente se ve que el diseño del WSDL es mucho más sencillo que en el ejemplo anterior. Sin embargo, si queremos ver el detalle de la información se hace necesario navegar a la URI del atributo schemaLocation y acceder al XSD.

Este XSD contendrá un código similar al del ejemplo anterior, pudiendo tener a su vez importaciones a otros XSDs que especifiquen los objetos complejos tipoCabecera y tipoElementoConsultaDuplicidades que podrían estar definidos en sus propios XSDs.

Ejemplo de generación de objeto de entrada a partir de los wsdl:types de ejemplo anteriores.

Si generamos el cliente para cualquiera de las operaciones del servicio web obtendremos un código similar al siguiente: 

<soapenv:Envelope 
xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
    xmlns:asis="http://VDM/esquemas/asistenteproducto">
   <soapenv:Header/>
   <soapenv:Body>
      <asis:ConsultaDuplicidadesTratamientoNuevoPet>
         <Cabecera>
             <FechaHoraPet>?</FechaHoraPet>
             <Idioma>?</Idioma>
             <IdTransaccion>?</IdTransaccion>
         </Cabecera>
         <TratamientoNuevo>
             <IdPrescripcion>?</IdPrescripcion>
             <IdUnicoProducto>?</IdUnicoProducto>
         </TratamientoNuevo>
         <!--1 or more repetitions:-->
         <TratamientosComparacion>
             <IdPrescripcion>?</IdPrescripcion>
             <IdUnicoProducto>?</IdUnicoProducto>
         </TratamientosComparacion>
      </asis:ConsultaDuplicidadesTratamientoNuevoPet>
   </soapenv:Body>
</soapenv:Envelope>

Conclusión

El apartado wsdl:types es uno de los más importantes en la descripción del Web Service ya que permite generar una correcta comunicación entre proveedor y cliente. 

Esta claro que hay que encontrar un equilibrio en la definición de los tipos de datos a través de XSDs. Si hay pocos XSDs, nos encontraremos con un fichero inmenso difícil de manejar, y si por el contrario, definimos demasiados XSDs, tendremos que navegar por todos ellos para enterarnos de los datos que participan en el servicio web, y si no disponemos de un buen mapa de objetos podemos perder la visión de lo datos. 

Links