xslt : namespace, move nodes, cdata
Every now and then I have to re-learn xslt to transform xml documents. My mantra is to use the right tool for the right job… so I’m here again struggling with xslt. There is a bit of a love/hate relation between me and this language. I’ve used a lot during my master thesis transforming an enormous - industry size - software specification (in SDL) to a formal language. This was a very painful way of learning a new language, After that I just used it to perform small tasks, but I always manage to forget part of the specification…
Anyway, just to avoid repeating this error again, the lesson today is about xml namespaces. | Here you can find a lengthy explanation about namespace and all possible related problems to xslt. I stumbled on a very simple case. Why my xslt stylesheet does not work in the presence of namespace ?
A small motivating example. Suppose you have a very simple xml document
<doc xmlns="http://mynamespace.org">
<a><b>aaa</b><c>bbb</c></a>
</doc>
and you want to transform it in a different xml document as :
<?xml version="1.0"?>
<newdoc xmlns="http://othernamespace.org">
<a xmlns="http://mynamespace.org"><c>bbb</c></a>
<b xmlns="http://mynamespace.org"><![CDATA[aaa]]></b></newdoc>
Therefore you want to
- change the document root (and namespace)
- copy all the content of the element but the
element <code lang="xml"><b>
- move the element below <code
lang="xml"><a>
- embed the text content of ``` in a CDATA section.
We go one step at the time. First we transform the root element and we
change the default namespace.
The header of the xsl file is standard except for the declaration of
the attribute xmlns:ns="http://mynamespace.org"
.
Since our source xml document as a namespace, we have to match
elements in this namespace. In other to do so, we associate a label
nsto the namespace
http://mynamespace.organd the we use
it to match the element <code lang="xml"><doc>
.
<?xml version="1.0"?>
<xsl:transform
xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"
xmlns:ns="http://mynamespace.org">
The second part is the standard way of copying nodes and contents…
<xsl:template match="@*|node()">
<xsl:copy>
<xsl:apply-templates select="@*|node()"/>
</xsl:copy>
</xsl:template>
In the third part we match the root element, we create a new element
and we copy everything. Since
<xsl:template match="ns:doc">
<xsl:element name="newdoc" namespace="http://mynamespace.org">
<xsl:apply-templates/>
</xsl:element>
</xsl:template>
</xsl:transform>
The result :
<?xml version="1.0"?>
<newdoc xmlns="http://othernamespace.org">
<a xmlns="http://mynamespace.org"><b>aaa</b><c>bbb</c></a>
</newdoc>
Now we want to move the element . We add a new
template to match <code lang="xml"><a>
and copy all its content
except the node ```
<xsl:template match="ns:a">
<xsl:copy>
<xsl:apply-templates select="*[not(self::ns:b)]"/>
</xsl:copy>
</xsl:template>
This template being more specific then the default template we
specified at the beginning of the document will be applied to . Then we have to modify the template for the root
element in order to copy the content of <code lang="xml"><a>
and
the the content of ``` below.
<xsl:template match="ns:doc">
<xsl:element name="newdoc" namespace="http://mynamespace.org">
<xsl:apply-templates/>
<xsl:apply-templates select="ns:a/ns:b"/>
</xsl:element>
</xsl:template>
This will give something like this :
<?xml version="1.0"?>
<newdoc xmlns="http://othernamespace.org">
<a xmlns="http://mynamespace.org"><c>bbb</c></a>
<b xmlns="http://mynamespace.org">aaa</b></newdoc>
Last we want to embed the content of ``` in a
cdata section. This is easy as we just need to add an output directive
at the beginning of the file. The complete example :
<?xml version="1.0"?>
<xsl:transform
xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"
xmlns:ns="http://mynamespace.org">
<xsl:output method="xml" indent="yes" cdata-section-elements="ns:b" />
<xsl:template match="@*|node()">
<xsl:copy>
<xsl:apply-templates select="@*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="ns:a">
<xsl:copy>
<xsl:apply-templates select="*[not(self::ns:b)]"/>
</xsl:copy>
</xsl:template>
<xsl:template match="ns:doc">
<xsl:element name="newdoc" namespace="http://othernamespace.org">
<xsl:apply-templates/>
<xsl:apply-templates select="ns:a/ns:b"/>
</xsl:element>
</xsl:template>
</xsl:transform>
```xml
And the final result :
<?xml version="1.0"?>
<newdoc xmlns="http://othernamespace.org">
<a xmlns="http://mynamespace.org"><c>bbb</c></a>
<b xmlns="http://mynamespace.org"><![CDATA[aaa]]></b></newdoc>