XSL Transformations with Maven

In my last post I told about the XSLT processing that I have to do to get the XSD as I wanted it to be. Unfortunately there is not just one XSD that has to be processed but there are actually several of them. And for every change in our CDM (Common Data Model) I had to perform all these transformations by hand. Now that doesn’t feel good so I decided to automate that process. And since we are already heavily using Maven I also wanted to do this with Maven, which actuallly is rather easy to do. I even added a validation step so I can test the created XSD at the same time.
Here is my project setup:

As you can see I only have XML files in this project. I have grouped the following files together in directories in the resources folder:

  • xml
  • Contains xml file that respects the xsd that is generated. This file is used to validate it against the generated xsd to make sure it is valid. In this example ‘customer-msg.xml’ it will look like:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    
    <?xml version="1.0" encoding="UTF-8"?>
     
    <ns0:customer xmlns:ns0="htpp://www.redstream.nl/customer">
        <ns0:firstName>Pascal</ns0:firstName>
        <ns0:lastName>Alma</ns0:lastName>
        <ns0:address>
            <ns0:street>sesamestreet</ns0:street>
            <ns0:nr>13</ns0:nr>
            <ns0:postalCode>
                <ns0:firstPart>1234</ns0:firstPart>
                <ns0:secondPart>AB</ns0:secondPart>
            </ns0:postalCode>
        </ns0:address>
    </ns0:customer>
  • xsd
  • Contains the xsd’s, both the ‘concept’ one as well as the generated ‘final’ one.

    • concept
    • As said earlier this one contains the ‘concept’ xsd. In this example ‘customer_concept.xsd’ it looks like:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      
      <?xml version="1.0" encoding="UTF-8"?>
      <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
          <xs:complexType name="Address">
              <xs:sequence>
                  <xs:element name="street" type="xs:string"/>
                  <xs:element name="nr" type="xs:integer"/>
                  <xs:element name="postalCode" type="myPostalCode"/>
              </xs:sequence>
          </xs:complexType>
          <xs:complexType name="Customer">
              <xs:sequence>
                  <xs:element name="firstName" type="xs:string" minOccurs="0"/>
                  <xs:element name="lastName" type="xs:string"/>
                  <xs:element name="address" type="Address"/>
              </xs:sequence>
          </xs:complexType>
      </xs:schema>
    • final
    • Here is the output put. In this example ‘customer.xsd’ it looks like:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      
      <?xml version="1.0" encoding="UTF-8"?>
      <xs:schema xmlns:tns="htpp://www.redstream.nl/customer"
                 xmlns:xs="http://www.w3.org/2001/XMLSchema"
                 targetNamespace="htpp://www.redstream.nl/customer"
                 elementFormDefault="qualified">
         <xs:element name="customer" type="tns:CustomerType"/>
          <xs:complexType name="AddressType">
              <xs:sequence>
                  <xs:element name="street" type="xs:string"/>
                  <xs:element name="nr" type="xs:integer"/>
                  <xs:element name="postalCode" type="tns:myPostalCodeType"/>
              </xs:sequence>
          </xs:complexType>
          <xs:complexType name="CustomerType">
              <xs:sequence>
                  <xs:element name="firstName" type="xs:string" minOccurs="0"/>
                  <xs:element name="lastName" type="xs:string"/>
                  <xs:element name="address" type="tns:AddressType"/>
              </xs:sequence>
          </xs:complexType>
         <xs:complexType name="myPostalCodeType">
            <xs:sequence>
               <xs:element name="firstPart">
                  <xs:simpleType>
                     <xs:restriction base="xs:string">
                        <xs:length value="4"/>
                     </xs:restriction>
                  </xs:simpleType>
               </xs:element>
               <xs:element name="secondPart">
                  <xs:simpleType>
                     <xs:restriction base="xs:string">
                        <xs:length value="2"/>
                     </xs:restriction>
                  </xs:simpleType>
               </xs:element>
            </xs:sequence>
         </xs:complexType>
      </xs:schema>
  • xslt
  • And finally the one that does all the work. In this example ‘transform_customer.xslt’ it looks like:

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

And of course the pom file. This one looks like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
 
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>net.pascalalma.xsd</groupId>
    <artifactId>xslt-project</artifactId>
    <packaging>jar</packaging>
    <version>1.0-SNAPSHOT</version>
    <name>${artifactId}</name>
 
    <!-- Use relative paths otherwise stylesheet includes will fail miserably -->
    <properties>
		<xsd.path>src/main/xsd</xsd.path>
        <xsd.path.concept>${xsd.path}/concept</xsd.path.concept>
		<xsd.path.final>${xsd.path}/final</xsd.path.final>
		<xslt.path>src/main/xslt</xslt.path>
		<xml.path>src/main/xml</xml.path>
	</properties>
    <build>
        <plugins>
            <!-- Copy the basic-types.xsd to the target location. This one doesn't
            need any translation -->
            <plugin>
                <artifactId>maven-resources-plugin</artifactId>
                <executions>
                    <execution>
                        <id>copy-resources</id>
                        <phase>compile</phase>
                        <goals>
                            <goal>copy-resources</goal>
                        </goals>
                        <configuration>
                            <outputDirectory>${xsd.path.final}</outputDirectory>
                            <resources>
                                <resource>
                                    <directory>${xsd.path}</directory>
                                    <includes>
                                        <include>basic-types.xsd</include>
                                    </includes>
                                    <filtering>false</filtering>
                                </resource>
                            </resources>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
            <!-- Transform all 'concept' xsd to the correct 'final' xsd's -->
            <plugin>
                <groupId>org.codehaus.mojo</groupId>
                <artifactId>xml-maven-plugin</artifactId>
                <executions>
                    <execution>
                        <goals>
                            <goal>transform</goal>
                        </goals>
                    </execution>
                </executions>
                <configuration>
                    <transformationSets>
                        <transformationSet>
                            <dir>${xsd.path.concept}</dir>
                            <includes>
                                <include>customer_concept.xsd</include>
                            </includes>
                            <stylesheet>${xslt.path}/transform_customer.xslt</stylesheet>
                            <outputDir>${xsd.path.final}</outputDir>
                            <fileMappers>
                                <fileMapper implementation="org.codehaus.plexus.components.io.filemappers.MergeFileMapper">
                                    <targetName>customer.xsd</targetName>
                                </fileMapper>
                            </fileMappers>
                        </transformationSet>
 
                    </transformationSets>
                </configuration>
                <!-- make the translation working with xslt2.0 -->
                <dependencies>
                    <dependency>
                        <groupId>net.sf.saxon</groupId>
                        <artifactId>saxon-B</artifactId>
                        <version>9.1</version>
                    </dependency>
                </dependencies>
            </plugin>
            <!-- Validate the generated XSD against valid XML files to make
            sure nothing broke-->
            <plugin>
                <groupId>org.codehaus.mojo</groupId>
                <artifactId>xml-maven-plugin</artifactId>
                <executions>
                    <execution>
                        <goals>
                            <goal>validate</goal>
                        </goals>
                    </execution>
                </executions>
                <configuration>
                    <validationSets>
                        <validationSet>
                            <dir>${xml.path}/</dir>
                            <includes>
                                <include>customer-msg.xml</include>
                            </includes>
                            <validating>true</validating>
                            <systemId>${xsd.path.final}/customer.xsd</systemId>
                        </validationSet>
                    </validationSets>
                </configuration>
                <dependencies>
                    <dependency>
                        <groupId>net.sf.saxon</groupId>
                        <artifactId>saxon-B</artifactId>
                        <version>9.1</version>
                    </dependency>
                </dependencies>
            </plugin>
            <!-- Plugin to clean the generated XSD's when rebuilding the project -->
            <plugin>
                <artifactId>maven-clean-plugin</artifactId>
                <version>2.2</version>
                <configuration>
                    <filesets>
                        <fileset>
                            <directory>${xsd.path.final}</directory>
                            <includes>
                                <include>**/*.xsd</include>
                            </includes>
                        </fileset>
                    </filesets>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

This completes the example of how you can perform xslt transformations within the Maven build cycle.

tags:

About Pascal Alma

Pascal started as an Oracle Developer in 1997 and developed numerous applications with Oracle Designer/Developer and PL/SQL. Since 2001 Pascal becomes more and more active with the development of software at the Java/J2EE platform. Nowadays Pascal is a senior JEE Developer/ Architect and has a lot of experience with several open source initiatives/ frameworks especially within the Enterprise Integration area. Besides these technical skills Pascal is a big Scrum enthusiastic.

6 Responses to XSL Transformations with Maven

  1. Mads says:

    What Maven repo do you have the Saxon-B jars hosted? I haven’t been able to find anywhere that has them.

  2. Mads says:

    What Maven repo do you have the Saxon-B jars hosted? I haven’t been able to find anywhere that has them.

  3. Pascal Alma says:

    Hmm, I can’t recall where I got them. Possibly I downloaded it here:http://saxon.sourceforge.net/ and then installed it manually in our Artifactory (http://sourceforge.net/projects/artifactory/).

  4. Pascal Alma says:

    Hmm, I can’t recall where I got them. Possibly I downloaded it here:http://saxon.sourceforge.net/ and then installed it manually in our Artifactory (http://sourceforge.net/projects/artifactory/).

  5. Pingback: XSLT2 translations with Mule2 | Redstream Blog

  6. Pingback: XSLT2 translations with Mule2 | Redstream Blog