Question

I have an xml document which looks like this:

<document>
  <primitive name="password" type="xs:string" path="" mandatory="false" />
  <structure name="username" mandatory="true">
    <primitive name="first-name" type="xs:string" path="" mandatory="true" />
    <primitive name="last-name" type="xs:string" path="" mandatory="true" />
  </structure>
  <list name="addresses" path="" mandatory="true">
    <structure name="address" mandatory="true">
      <primitive name="country" type="xs:string" path="" mandatory="true" default-value="HU"/>
      <primitive name="po-box" type="xs:string" path="" mandatory="true" />
    </structure>
  </list>
  <list name="owned-phones" path="" mandatory="true">
    <structure name="phone" mandatory="true">
      <primitive name="number" type="xs:int" path="" mandatory="true" />
      <primitive name="device-name" type="xs:string" path="" mandatory="true" />
      <list name="similar-devices" path="" mandatory="true">
        <structure name="device" mandatory="true">
          <primitive name="device-name" type="xs:string" path="" mandatory="true" />
        </structure>
      </list>
    </structure>
  </list>
  <list name="aliases" path="" mandatory="true">
    <primitive name="alias" type="xs:string" path="" mandatory="true" />
  </list>
  <template name="tou">
    <![CDATA[
      wombat
    ]]>
  </template>

This describes rules for a data structure which looks like this:

<?xml version="1.0" encoding="UTF-8"?>
<document>
  <password>ajshakjsdh</password>
  <username>
    <first-name>Kevin</first-name>
    <last-name>Smith</last-name>
  </username>

  <addresses>
    <address>
      <country>HU</country>
      <po-box>12234</po-box>
    </address>
    <address>
      <country>US</country>
      <po-box>666</po-box>
    </address>
  </addresses>

  <owned-phones>
    <phone>
      <number>2431241</number>
      <device-name>Nokia</device-name>
      <similar-devices>
        <device>
          <device-name>Windozfon</device-name>
        </device>
      </similar-devices>
    </phone>
    <phone>
      <number>68741</number>
      <device-name>Samsung</device-name>
      <similar-devices>
        <device>
          <device-name>Android</device-name>
        </device>
        <device>
          <device-name>Blackberry</device-name>
        </device>
      </similar-devices>
    </phone>
  </owned-phones>

  <aliases>
    <alias>Alias1</alias>
    <alias>Alias2</alias>
  </aliases>
  <!-- tou is missing but not mandatory -->
</document>

I have a set of transformation rules which can be used to convert the first xml file to an xsd document which can be used to validate the data structure (second code block):

<primitive name="$primitive_name" type="$primitive_type" mandatory="$primitive_mandatory" />

transforms to

<xs:element name="$primitive_name" type="$primitive_type" minOccurs="$primitive_mandatory ? 1 : 0" maxOccurs="1" />


<structure name="$structure_name" mandatory="$structure_mandatory">
<!-- anything inside recursively transformed -->
</structure>

transforms to

<xs:element name="$structure_name" minOccurs="$structure_mandatory ? 1 : 0" maxOccurs="1">
  <xs:complexType>
    <xs:all>
      <!-- anything inside recursively transformed -->
    </xs:all>
  </xs:complexType>
</xs:element>


<list name="$list_name" mandatory="$list_mandatory">
    <!-- anything inside recursively transformed -->
</list>

transforms to

<xs:element name="$list_name" minOccurs="$list_mandatory ? 1 : 0">
  <xs:complexType>
    <xs:sequence maxOccurs="unbounded">
    <!-- anything inside recursively transformed --> 
    </xs:sequence>
  </xs:complexType>
</xs:element>

My question is what tool should I use for these simple transformations? I've heard that XSLT is the way to go here. I did a little research about it and it seems overkill to me. At least I was not able to find the correct transformation rules in XSLT. Can you provide a few pointers where should I start?

Was it helpful?

Solution

XSLT is very a good solution for your problem. You can easily map your structure to individual templates which will be called recursively.

Here is an example of a XSLT 2.0 stylesheet with templates for just the the examples you provided. It should provide a good starting point.

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:xs="http://www.w3.org/2001/XMLSchema" version="2.0">

    <xsl:output method="xml" indent="yes"/>
    <xsl:strip-space elements="*"/>

    <xsl:template match="document">
        <xs:schema elementFormDefault="qualified">
            <xsl:apply-templates/>
        </xs:schema>
    </xsl:template>

    <xsl:template match="primitive">
        <xs:element name="{@name}" type="{@type}" minOccurs="{if(@mandatory eq 'true') then 1 else 0}" maxOccurs="1" />
    </xsl:template>

    <xsl:template match="structure">
        <xs:element name="{@name}" minOccurs="{if(@mandatory eq 'true') then 1 else 0}" maxOccurs="1">
            <xs:complexType>
                <xs:all>
                    <xsl:apply-templates/>
                </xs:all>
            </xs:complexType>
        </xs:element>
    </xsl:template>

    <xsl:template match="list">
        <xs:element name="{@name}" minOccurs="{if(@mandatory eq 'true') then 1 else 0}">
            <xs:complexType>
                <xs:sequence maxOccurs="unbounded">
                    <xsl:apply-templates/>
                </xs:sequence>
            </xs:complexType>
        </xs:element>
    </xsl:template>

</xsl:stylesheet>

There are many ways to achieve the same results. Look for a good XSLT tutorial that makes efficient use of XSLT templates. Avoid the ones that focus on single-template solutions using for-each. You can achieve everything you need using templates (and not using <xsl:for-each> which will make it excessively more complicated).

In case your support is limited to XSLT 1.0, you won't be able to use the (XPath 2.0) code in the minOccurs attributes, but you can generate the attribute with a nested <xsl:attribute> and use <xsl:choose> to calculate its contents.

The stylesheet above could be rewritten in XSLT 1.0 as below:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:xs="http://www.w3.org/2001/XMLSchema" version="1.0">

    <xsl:output method="xml" indent="yes"/>
    <xsl:strip-space elements="*"/>

    <xsl:template match="document">
        <xs:schema elementFormDefault="qualified">
            <xsl:apply-templates/>
        </xs:schema>
    </xsl:template>

    <xsl:template match="primitive">
        <xs:element name="{@name}" type="{@type}" maxOccurs="1">
            <xsl:attribute name="minOccurs">
                <xsl:choose>
                    <xsl:when test="@mandatory = 'true'">1</xsl:when>
                    <xsl:otherwise>0</xsl:otherwise>
                </xsl:choose>
            </xsl:attribute>
        </xs:element>
    </xsl:template>

    <xsl:template match="structure">
        <xs:element name="{@name}" maxOccurs="1">
            <xsl:attribute name="minOccurs">
                <xsl:choose>
                    <xsl:when test="@mandatory = 'true'">1</xsl:when>
                    <xsl:otherwise>0</xsl:otherwise>
                </xsl:choose>
            </xsl:attribute>
            <xs:complexType>
                <xs:all>
                    <xsl:apply-templates/>
                </xs:all>
            </xs:complexType>
        </xs:element>
    </xsl:template>

    <xsl:template match="list">
        <xs:element name="{@name}">
            <xsl:attribute name="minOccurs">
                <xsl:choose>
                    <xsl:when test="@mandatory = 'true'">1</xsl:when>
                    <xsl:otherwise>0</xsl:otherwise>
                </xsl:choose>
            </xsl:attribute>
            <xs:complexType>
                <xs:sequence maxOccurs="unbounded">
                    <xsl:apply-templates/>
                </xs:sequence>
            </xs:complexType>
        </xs:element>
    </xsl:template>

</xsl:stylesheet>

OTHER TIPS

you are right, XSLT is the tool to use.

For your case it should be quite fast to create your xsl file as you don't have to much statements to convert.

You will need XSL and XPATH (Xml query language) to do the job

To start with, I suggest to go here http://www.w3schools.com/xsl/ and you will find the reference doc to XSLT there: http://www.w3.org/TR/xslt

For XPATH: http://www.w3.org/TR/xpath/ and http://www.w3schools.com/XPath/

Hope it will help. Enjoy ;)

Yann

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top