Recently I ran into an issue with a XSD that I had generated by using our modelling tool. It appeared the XSD did not match with my wishes so I had to modify the XSD. Now I could do this by hand quite easily but that would mean I had to redo it every time I regenerated the XSD after a model change. So I decided to use XSLT for the modifications. I have used XSLT quite extensively at previous projects but these where about 8 years ago and it just made me realize how fast one looses his knowledge if you don't use it anymore.
Anyway, to avoid this situation for the next few years I decided to post about the basics of XSLT that I used in this situation so I always have a reference to get back at. The situation was that an XSD was generated but it was lacking several things:

  • no use of namespaces (both import and prefixes)
  • names of custom defined types didn't end with the suffix 'Type'
  • some complex types were missing

And several more things but the idea stays the same.
Here is an example XSD that could be generated by the tool:

XML:
  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
  3.     <xs:complexType name="Address">
  4.         <xs:sequence>
  5.             <xs:element name="street" type="xs:string"/>
  6.             <xs:element name="nr" type="xs:integer"/>
  7.             <xs:element name="postalCode" type="myPostalCode"/>
  8.         </xs:sequence>
  9.     </xs:complexType>
  10.     <xs:complexType name="Customer">
  11.         <xs:sequence>
  12.             <xs:element name="firstName" type="xs:string" minOccurs="0"/>
  13.             <xs:element name="lastName" type="xs:string"/>
  14.             <xs:element name="address" type="Address"/>
  15.         </xs:sequence>
  16.     </xs:complexType>
  17. </xs:schema>

Here is how I would like the XSD to be look like:

XML:
  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <xs:schema xmlns:tns="htpp://www.redstream.nl/customer" xmlns:xs="http://www.w3.org/2001/XMLSchema" targetNamespace="htpp://www.redstream.nl/customer">
  3.     <xs:element name="customer" type="tns:CustomerType"/>
  4.     <xs:complexType name="AddressType">
  5.         <xs:sequence>
  6.             <xs:element name="street" type="xs:string"/>
  7.             <xs:element name="nr" type="xs:integer"/>
  8.             <xs:element name="postalCode" type="tns:myPostalCodeType"/>
  9.         </xs:sequence>
  10.     </xs:complexType>
  11.     <xs:complexType name="CustomerType">
  12.         <xs:sequence>
  13.             <xs:element name="firstName" type="xs:string" minOccurs="0"/>
  14.             <xs:element name="lastName" type="xs:string"/>
  15.             <xs:element name="address" type="tns:AddressType"/>
  16.         </xs:sequence>
  17.     </xs:complexType>
  18.     <xs:complexType name="myPostalCodeType">
  19.         <xs:sequence>
  20.             <xs:element name="firstPart">
  21.                 <xs:simpleType>
  22.                     <xs:restriction base="xs:string">
  23.                         <xs:length value="4"/>
  24.                     </xs:restriction>
  25.                 </xs:simpleType>
  26.             </xs:element>
  27.             <xs:element name="secondPart">
  28.                 <xs:simpleType>
  29.                     <xs:restriction base="xs:string">
  30.                         <xs:length value="2"/>
  31.                     </xs:restriction>
  32.                 </xs:simpleType>
  33.             </xs:element>
  34.         </xs:sequence>
  35.     </xs:complexType>
  36. </xs:schema>

To get to this last XSD I used the following XSLT (see inline documentation for explanation):

XML:
  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xs="http://www.w3.org/2001/XMLSchema">
  3.     <xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
  4.     <!-- first copy the root and apply templates-->
  5.     <xsl:template match="/">
  6.         <xsl:copy>
  7.             <!-- copy the attributes (if any) of the root element -->
  8.             <xsl:copy-of select="@*"/>
  9.             <xsl:apply-templates/>
  10.         </xsl:copy>
  11.     </xsl:template>
  12.     <!-- match any other element and copy it with the attributes -->
  13.     <xsl:template match="*">
  14.         <xsl:copy>
  15.             <xsl:copy-of select="@*"/>
  16.             <xsl:apply-templates/>
  17.         </xsl:copy>
  18.     </xsl:template>
  19.     <!-- Add the necessary namespaces to the root of the schema -->
  20.     <xsl:template match="xs:schema">
  21.         <!-- Recreate the xs:schema element-->
  22.         <xsl:element name="xs:schema">
  23.             <!-- Copy the existing attributes in the original xs:schema element to this one -->
  24.             <xsl:copy-of select="@*"/>
  25.             <!-- add Namespace -->
  26.             <xsl:namespace name="tns">htpp://www.redstream.nl/customer</xsl:namespace>
  27.             <xsl:attribute name="targetNamespace">htpp://www.redstream.nl/customer</xsl:attribute>
  28.             <!-- add toplevel element -->
  29.             <xsl:element name="xs:element">
  30.                 <xsl:attribute name="name">customer</xsl:attribute>
  31.                 <xsl:attribute name="type">tns:CustomerType</xsl:attribute>
  32.             </xsl:element>
  33.             <!-- continue with matching al child elements of the xs:schema element-->
  34.             <xsl:apply-templates/>
  35.             <!-- call a custom template to add an extra complexType to the schema that couldn't be generated by the modelling tool-->
  36.             <xsl:call-template name="addPostalCodeType"/>
  37.         </xsl:element>
  38.     </xsl:template>
  39.     <!-- suffix all complexType names with 'Type' -->
  40.     <xsl:template match="xs:complexType">
  41.         <xsl:choose>
  42.             <!-- Check if the type already has the suffix 'Type' -->
  43.             <xsl:when test="substring(@name, (string-length(@name)-3)) = 'Type'">
  44.                 <!-- if so, just copy the existing attributes -->
  45.                 <xsl:copy>
  46.                     <xsl:copy-of select="@*"/>
  47.                     <xsl:apply-templates/>
  48.                 </xsl:copy>
  49.             </xsl:when>
  50.             <xsl:otherwise>
  51.                 <!-- if not, copy all attribute and overwrite the attribute 'name' so the suffix 'Type' is added-->
  52.                 <xsl:copy>
  53.                     <xsl:copy-of select="@*"/>
  54.                     <xsl:attribute name="name"><xsl:value-of select="@name"/>Type</xsl:attribute>
  55.                     <xsl:apply-templates/>
  56.                 </xsl:copy>
  57.             </xsl:otherwise>
  58.         </xsl:choose>
  59.     </xsl:template>
  60.     <!-- Prefix all types with 'tns:'. All default xs: types are untouched.    -->
  61.     <xsl:template match="xs:element">
  62.         <!-- Create a variable with the name of the type of the element in it. It appears this 'type' attribute
  63.             can be of different types so take this into account -->
  64.         <xsl:variable name="elementType">
  65.             <xsl:choose>
  66.                 <xsl:when test="@type instance of xs:string or @type instance of xs:untypedAtomic">
  67.                     <xsl:choose>
  68.                         <xsl:when test="contains(@type,':')">
  69.                             <xsl:value-of select="substring-after(@type,':')"/>
  70.                         </xsl:when>
  71.                         <xsl:otherwise>
  72.                             <xsl:value-of select="@type"/>
  73.                         </xsl:otherwise>
  74.                     </xsl:choose>
  75.                 </xsl:when>
  76.                 <xsl:otherwise>
  77.                     <xsl:value-of select="@type"/>
  78.                 </xsl:otherwise>
  79.             </xsl:choose>-->
  80.         </xsl:variable>
  81.         <!-- Create a variable with the namespace prefix of the attribute if any. -->
  82.         <xsl:variable name="elementTypePrefix">
  83.             <xsl:choose>
  84.                 <xsl:when test="@type instance of xs:QName">
  85.                     <xsl:value-of select="prefix-from-QName(@type)"/>
  86.                 </xsl:when>
  87.                 <xsl:when test="@type instance of xs:string">
  88.                     <xsl:value-of select="substring-before(@type,':')"/>
  89.                 </xsl:when>
  90.                     <xsl:otherwise>
  91.                                        <xsl:value-of select="substring-before(@type,':')"/>
  92.                                 </xsl:otherwise>
  93.             </xsl:choose>
  94.         </xsl:variable>
  95.         <!-- Now the variables are filled we can use it to determine if we need to add a prefix -->
  96.         <xsl:choose>
  97.             <!-- if type starts with xs: the prefix must not be changed -->
  98.             <xsl:when test="$elementTypePrefix = 'xs'">
  99.                 <xsl:copy>
  100.                     <xsl:copy-of select="@*"/>
  101.                     <xsl:apply-templates/>
  102.                 </xsl:copy>
  103.             </xsl:when>
  104.             <xsl:otherwise>
  105.                 <xsl:copy>
  106.                     <xsl:copy-of select="@*"/>
  107.                     <!-- A few steps back we renamed complextypes so they ended with 'Type'. Now we have to suffix the places
  108.               where the types are reffered also with 'Type' -->
  109.                     <xsl:if test="substring($elementType, (string-length($elementType)-3)) != 'Type'">
  110.                         <xsl:attribute name="type">tns:<xsl:value-of select="@type"/>Type</xsl:attribute>
  111.                     </xsl:if>
  112.                     <xsl:if test="substring($elementType, (string-length($elementType)-3)) = 'Type'">
  113.                         <xsl:attribute name="type">tns:<xsl:value-of select="@type"/></xsl:attribute>
  114.                     </xsl:if>
  115.                     <xsl:apply-templates/>
  116.                 </xsl:copy>
  117.             </xsl:otherwise>
  118.         </xsl:choose>
  119.     </xsl:template>
  120.     <!-- The next template generates the complexType 'myPostalCodeType' when the template is called -->
  121.     <xsl:template name="addPostalCodeType">
  122.         <xsl:element name="xs:complexType">
  123.             <xsl:attribute name="name">myPostalCodeType</xsl:attribute>
  124.             <xsl:element name="xs:sequence">
  125.                 <xsl:element name="xs:element">
  126.                     <xsl:attribute name="name">firstPart</xsl:attribute>
  127.                     <xsl:element name="xs:simpleType">
  128.                         <xsl:element name="xs:restriction">
  129.                             <xsl:attribute name="base">xs:string</xsl:attribute>
  130.                             <xsl:element name="xs:length">
  131.                                 <xsl:attribute name="value">4</xsl:attribute>
  132.                             </xsl:element>
  133.                         </xsl:element>
  134.                     </xsl:element>
  135.                 </xsl:element>
  136.                 <xsl:element name="xs:element">
  137.                     <xsl:attribute name="name">secondPart</xsl:attribute>
  138.                     <xsl:element name="xs:simpleType">
  139.                         <xsl:element name="xs:restriction">
  140.                             <xsl:attribute name="base">xs:string</xsl:attribute>
  141.                             <xsl:element name="xs:length">
  142.                                 <xsl:attribute name="value">2</xsl:attribute>
  143.                             </xsl:element>
  144.                         </xsl:element>
  145.                     </xsl:element>
  146.                 </xsl:element>
  147.             </xsl:element>
  148.         </xsl:element>
  149.     </xsl:template>
  150. </xsl:stylesheet>

Although this example might be very specific and there are better ways to obtain similar results, to me this might be a handy reminder how to solve an issue with XSLT and perhaps it might even help you in some way. In another post I will show how I do this translation in Maven and also validate the outcome.