Tag Archives: XSLT

XSLT: Load XML or other text from external sources

Just discovered you can pull in external XML and other kinds of text directly through standard XSLT 2.0 methods. So here’s a note to self on how.

Documentation

Example usage

<!-- Single string URI -->
<copy-of select="doc('http://www.w3schools.com/xml/note.xml')"/>

<!-- One or more URI from items, in this case a variable -->
<variable name="uri" select="'http://www.w3schools.com/xml/note.xml'"/>
<copy-of select="document($uri)"/>

<!-- Unparsed text, in this case escaped HTML -->
<copy-of select="unparsed-text('http://example.com')"/>

XSLT: Pull duplicate namespace declarations up towards root node

Sometimes XML becomes a bit weird with namespace declarations all over the place. This XSLT cleans that up. Stumbled upon in a StackOverflow answer I don’t find anymore and put here so I know where to find it in the future.

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

    <xsl:template match="@* | text() | processing-instruction() | comment()">
        <xsl:copy/>
    </xsl:template>

    <xsl:template match="*">
        <xsl:copy copy-namespaces="no">
            <xsl:for-each-group group-by="local-name()" select="descendant-or-self::*/namespace::*">
                <xsl:copy-of select="."/>
            </xsl:for-each-group>
            <xsl:apply-templates select="@* , node()"/>
        </xsl:copy>
    </xsl:template>

</xsl:stylesheet>

Example

Input

<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:wsa="http://schemas.xmlsoap.org/ws/2004/08/addressing">
   <soap:Header>
      <wsa:MessageID soap:mustUnderstand="0">uuid:7fa12310-5db4-11e3-ae24-a3c913f2629d</wsa:MessageID>
      <wsa:To soap:mustUnderstand="0">http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous/</wsa:To>
   </soap:Header>
   <soap:Body>
      <ns1:getTicket xmlns:ns1="http://api.example.com/some-webservice">
         <cus:msisdn xmlns:cus="http://api.example.com/some-webservice" xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">00000000</cus:msisdn>
         <cus:ticket xmlns:cus="http://api.example.com/some-webservice" xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">171</cus:ticket>
      </ns1:getTicket>
   </soap:Body>
</soap:Envelope>

Output

<soap:Envelope xmlns:wsa="http://schemas.xmlsoap.org/ws/2004/08/addressing"
        xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"
        xmlns:ns1="http://api.example.com/some-webservice"
        xmlns:cus="http://api.example.com/some-webservice"
        xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
    <soap:Header>
        <wsa:MessageID soap:mustUnderstand="0">uuid:7fa12310-5db4-11e3-ae24-a3c913f2629d</wsa:MessageID>
        <wsa:To soap:mustUnderstand="0">http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous/</wsa:To>
    </soap:Header>
    <soap:Body>
        <ns1:getTicket>
            <cus:msisdn>00000000</cus:msisdn>
            <cus:ticket>171</cus:ticket>
        </ns1:getTicket>
    </soap:Body>
</soap:Envelope>

If anyone know how to adjust it to also merge the duplicated prefixes ns1 and cus, do let me know 🙂

XSLT: Generate comma-separated string from element values

This is one way to create a comma-separated list of values from a set of elements.

<xsl:for-each select="$orders/cm:Error">
    <xsl:value-of select="cm:errorCode"/>
    <xsl:if test="position() != last()">
        <xsl:text>,</xsl:text>
    </xsl:if>
</xsl:for-each>
<!--source http://stackoverflow.com/a/668850/39321 -->

This is another.

<xsl:for-each select="$elements/Error[1]">
    <xsl:value-of select="errorCode"/>
    <xsl:for-each select="following-sibling::Error">
        <xsl:value-of select="concat(',',errorCode)"/>
    </xsl:for-each>
</xsl:for-each>
<!--source http://stackoverflow.com/a/666600/39321 -->

XSLT: Change order of apply-templates to process some elements before others

I had some XML given to me where the elements in a rather random order. I then needed to convert those to a different format, using XSLT 2.0. A couple of these elements should become attributes, one should become a special first element and the rest should become generic key value elements after that special first one. So:

<Metadata>
    <id>11</id>
    <type>Boolean</type>
    <value>true</value>
    <name>foobar</name>
    <label>Foobar</label>
    <hidden>false</hidden>
</Metadata>

Should become:

<Option id="11" name="foobar">
    <Value>true</Value>
    <Meta name="type">Boolean</Meta>
    <Meta name="label">Foobar</Meta>
    <Meta name="hidden">false</Meta>
</Metadata>

A failing first draft

This is roughly what I tried first:

<template match="Metadata>
  <element name="
Option">
    <apply-templates mode="
option" />
  </element>
</template>

<template match="
id|name" mode="option">
  <attribute name="
{local-name()}" select="." />
</template>

<template match="
value" mode="option">
  <element name="
Value"><value-of select="." /></element>
</template>

<template match="
*" mode="option">
  <element name="
Meta">
    <attribute name="
name" select="local-name()" />
    <value-of select="
." />
  </element>
</template>

The order of the elements is the problem here since the input order is random. The `Value` element won’t be the first in the output, and the whole transformation will actually crash because it’ll try to add attributes to the `Option` node after child nodes has been added, which is not allowed.

So we need to process the id and name nodes first, then the value node and finally the rest. You could use several apply-templates to first do ‘id’, then ‘name’ and so on, but the last would be a bit annoying as that would have to be ‘not id and not name’ and so on. So I found a different way to do it.

Solution

You can tell apply-templates to go through the elements in a different order by giving it some sorting instructions. Here’s how I took advantage of that:

<template match="Metadata">
  <element name="cm:Option">
    <apply-templates mode="option">
      <sort data-type="number" order="descending"
            select=" 3 * number(local-name()='id')
                   + 2 * number(local-name()='name')
                   + 1 * number(local-name()='value')"
/>
    </apply-templates>
  </element>
</template>

I check the name of the node, take advantage of the numeric value of booleans (true=1, false=0) and multiply with a constant. Those not mentioned will get the value 0. With data-type set to ‘number’ and order to ‘descending’, we then end up with elements being processed in the order we want first and those we don’t care about after that. Pretty neat, if I may say so myself 🙂

Java: Simple XSLT transformation without external libraries

Here’s how to do a simple XSLT transformation using only classes in vanilla Java 1.5 (maybe even 1.4?), no external libraries or anything. The classes are found in the javax.xml.transform package.

// Create a factory
TransformerFactory tf = TransformerFactory.newInstance();
if (!tf.getFeature(SAXTransformerFactory.FEATURE))
    throw new RuntimeException("Did not find a SAX-compatible TransformerFactory.");
SAXTransformerFactory stf = (SAXTransformerFactory) tf;

// Create a reusable template for the XSLT
Templates xslt = stf.newTemplates(new SourceStream(inputStreamWithXslt));

// Use the template to transform some XML
templates.newTransformer().transform(
        new StreamSource(inputStreamWithXml),
        new StreamResult(System.out));

Source: XML and XSLT Tips and Tricks for Java