Friday, October 5, 2012

element namespace inheritance with XSLT

Ok, here's the task - an XML with namespaces, these namespaces change over time but rarely significantly enough to require changes to the XSL, and we want to have the XSL in such a way that these changes to namespace do not require us to go update the XSLs with the new ones. If we are not careful we can end up with the well known empty/blank namespace declarations like xmlns="" and these break XML validation

Let's take an example:

<?xml version='1.0' encoding='UTF-8'?>

<server xmlns="urn:jboss:domain:1.3">
   <extensions>
      <extension module="org.jboss.as.clustering.infinispan"/>
   </extensions>
</server>

We want to add an element "system-properties" next to extensions. So here we got this XSL:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
      <xsl:output method="xml" indent="yes"/>

      <!-- Append <system-properties/> element. -->
      <xsl:template match="//*[local-name()='extensions'">
         <xsl:variable name="thisns" select="namespace-uri()"/>
         <xsl:copy>
            <xsl:apply-templates select="@*|node()"/>
         </xsl:copy>
         <xsl:element name="system-properties" namespace="{$thisns}"/>
      </xsl:template>

      <!-- Copy everything else untouched. -->
      <xsl:template match="@*|node()">
         <xsl:copy>
            <xsl:apply-templates select="@*|node()"/>
         </xsl:copy>
      </xsl:template>

</xsl:stylesheet>

Now this transforms the initial XML into:

<?xml version="1.0" encoding="UTF-8"?>
<server xmlns="urn:jboss:domain:1.3">
   <extensions>
      <extension module="org.jboss.as.clustering.infinispan"/>
   </extensions>
   <system-properties/>
</server>

What is special about this is that <system-properties/> does not have `xmlns=""'. If you change XSL template to <system-properties/> instead of specifying NS with <xsl:element name="system-properties" namespace="{$thisns}"/> then you will see that empty namespace decaration added in output XML by any namespace aware XSLT processor like xalan, saxon, xsltproc, etc.

Now the magic bit so one doesn't need to hardcode the namespace URI is <xsl:variable name="thisns" select="namespace::*[name()='']"/>. This puts our matched element namespace into a variable we can later use to create the elements we want. Be careful to avoid copying or creating elements by just inlining them into the templete like a raw <system-properties/>, otherwise they will be considered no namespace instead of inheriting parent namespace. I wish there was an option to have elements inherit namespace but until then, we are stuck with complications like this one.

Credits to: http://stackoverflow.com/a/123005/520567

No comments:

Post a Comment