Translate

Image of Android Wireless Application Development
Image of Beginning Google Maps API 3
Image of XSLT 2.0 and XPath 2.0 Programmer's Reference (Programmer to Programmer)
Image of Advanced Programming in the UNIX Environment, Second Edition (Addison-Wesley Professional Computing Series)

XSLT Namespace Handling

This post assumes that you are familiar with XML, XSLT and XPath. It shows two methods to handle multiple default namespaces using XSLT1.0 and another method using XSLT2.0.

Consider the following simple XML document:

<Month xmlns="urn:murphy/ws/account/10/2007">
<LastPage>true</LastPage>
<Accounts xmlns="urn:murphy/xml/account">
    <Account>
        <AccountId>6289-F891T</AccountId>
        <Owner>6DS</Owner>
        <AccountType>Customer</AccountType>
        <Country>Philippines</Country>
        <SalesPerson>Joe Goon</SalesPerson>
    </Account>
    <Account>
        <AccountId>7142-J219F</AccountId>
        <Owner>IISC</Owner>
        <AccountType>Customer</AccountType>
        <Country>Canada</Country>
        <SalesPerson>Sarah Cergeo</SalesPerson>
    </Account>
</Accounts>
</Month>


As you can see it has two namespaces, i.e urn:murphy/ws/account/10/2007 and urn:murphy/xml/account.

The requirement is to transform this input document into the following output document using a stylesheet:

<?xml version="1.0"?>
<Month xmlns="urn:murphy/ws/account/10/2007">
  <LastPage>true</LastPage>
  <Accounts xmlns="urn:murphy/xml/account">
    <Account>
      <AccountId>6289-F891T</AccountId>
      <SalesPerson>Joe Goon</SalesPerson>
    </Account>
    <Account>
      <AccountId>7142-J219F</AccountId>
      <SalesPerson>Sarah Cergeo</SalesPerson>
    </Account>
  </Accounts>
</Month>


Note that both namespaces are required to be preserved.

Example 1

Here is the traditional way of handling multiple default namespaces by assigning each default namespace a prefixed namespace in the stylesheet.

<?xml version="1.0"?>
<xsl:stylesheet version="1.0"
   xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
   xmlns:mon="urn:murphy/ws/account/10/2007"
   xmlns:acc="urn:murphy/xml/account">

   <xsl:output method="xml" indent="yes"/>

   <xsl:template match="mon:Month">
       <xsl:element name="mon:{name()}" namespace="{namespace-uri()}">
          <xsl:element name="LastPage"><xsl:value-of select="mon:LastPage"/></xsl:element>
          <xsl:apply-templates select="acc:Accounts" />
       </xsl:element>
   </xsl:template>

   <xsl:template match="acc:Accounts">
       <xsl:element name="acc:{name()}" namespace="{namespace-uri()}">
           <xsl:apply-templates select="acc:Account" />
       </xsl:element>
   </xsl:template>

   <xsl:template match="acc:Account">
       <xsl:element name="{name()}">
        <AccountId><xsl:value-of select="acc:AccountId"/></AccountId>
        <SalesPerson><xsl:value-of select="acc:SalesPerson"/></SalesPerson>
       </xsl:element>
   </xsl:template>
</xsl:stylesheet>


This stylesheet transforms the input document into the following output document:

<?xml version="1.0"?>
<mon:Month xmlns:mon="urn:murphy/ws/account/10/2007">
  <LastPage>true</LastPage>
  <acc:Accounts xmlns:acc="urn:murphy/xml/account">
    <Account>
      <AccountId>6289-F891T</AccountId>
      <SalesPerson>Joe Goon</SalesPerson>
    </Account>
    <Account>
      <AccountId>7142-J219F</AccountId>
      <SalesPerson>Sarah Cergeo</SalesPerson>
    </Account>
  </acc:Accounts>
</mon:Month>


While this output document is semantically equivalent to the required output, it is not exactly what was required.

Example 2

The XPath 1.0 recommendation doesn’t allow * as a namespace prefix. The workaround is to use local-name() to retrieve the name of a node without the namespace prefix and match against that as shown in the following stylesheet:

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

   <xsl:output method="xml" indent="yes"/>

   <xsl:template match="/">
      <xsl:apply-templates select="*[local-name()='Month']"/>
   </xsl:template>

   <xsl:template match="*[local-name()='Month']">
      <xsl:copy>
         <xsl:apply-templates select="*[local-name()='LastPage']"/>
         <xsl:apply-templates select="*[local-name()='Accounts']"/>
      </xsl:copy>
   </xsl:template>

   <xsl:template match="*[local-name()='Accounts']">
      <xsl:copy>
         <xsl:apply-templates select="*[local-name()='Account']"/>
      </xsl:copy>
   </xsl:template>

   <xsl:template match="*[local-name()='Account']">
      <xsl:copy>
         <xsl:apply-templates select="*[local-name()='AccountId']"/>
         <xsl:apply-templates select="*[local-name()='SalesPerson']"/>
      </xsl:copy>
   </xsl:template>


   <xsl:template match="*">
      <xsl:copy>
         <xsl:value-of select="."/>
      </xsl:copy>
   </xsl:template>

</xsl:stylesheet>


This stylesheet outputs the required document.

Example 3

The restriction on namespace prefix wildcard does not apply to XSLT2.0. The following stylesheet uses namespace prefix wildcards to achieve the required transformation.

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

   <xsl:output method="xml" indent="yes"/>

   <xsl:template match="/">
      <xsl:apply-templates select="*:Month"/>
   </xsl:template>

   <xsl:template match="*:Month">
      <xsl:copy>
         <xsl:apply-templates select="*:LastPage"/>
         <xsl:apply-templates select="*:Accounts"/>
      </xsl:copy>
   </xsl:template>

   <xsl:template match="*:Accounts">
      <xsl:copy>
         <xsl:apply-templates select="*:Account"/>
      </xsl:copy>
   </xsl:template>

   <xsl:template match="*:Account">
      <xsl:copy>
         <xsl:apply-templates select="*:AccountId"/>
         <xsl:apply-templates select="*:SalesPerson"/>
      </xsl:copy>
   </xsl:template>

   <xsl:template match="*">
      <xsl:copy>
         <xsl:value-of select="."/>
      </xsl:copy>
   </xsl:template>

</xsl:stylesheet>


This stylesheet also outputs the required document.

As you can see, there are alternatives ways to using stylesheet prefixes when you have to transform input documents which contain multiple default namespaces.
 

Comments are closed.