Cannot add namespace prefix to children using XSL

UbuntuEGGHead picture UbuntuEGGHead · Jun 4, 2010 · Viewed 8.3k times · Source

I checked many answers here and I think I am almost there. One thing that is bugging me (and for some reason my peer needs it) follows:

I have the following input XML:

<?xml version="1.0" encoding="utf-8"?>
<MyRoot>
  <MyRequest CompletionCode="0" CustomerID="9999999999"/>
  <List TotalList="1">
    <Order CustomerID="999999999" OrderNo="0000000001" Status="Shipped">
      <BillToAddress ZipCode="22221"/>
      <ShipToAddress ZipCode="22222"/>
      <Totals Tax="0.50" SubTotal="10.00" Shipping="4.95"/>
    </Order>
  </List>
  <Errors/>
</MyRoot>

I was asked to produce this:

<ns:MyNewRoot xmlns:ns="http://schemas.foo.com/response"  
xmlns:N1="http://schemas.foo.com/request"  
xmlns:N2="http://schemas.foo.com/details">
    <N1:MyRequest CompletionCode="0" CustomerID="9999999999"/>
    <ns:List TotalList="1">
            <N2:Order CustomerID="999999999" Level="Preferred" Status="Shipped">
                    <N2:BillToAddress ZipCode="22221"/>
                    <N2:ShipToAddress ZipCode="22222"/>
                    <N2:Totals Tax="0.50" SubTotal="10.00" Shipping="4.95"/>
            </N2:Order>
    </ns:List>
    <ns:Errors/>
</ns:MyNewRoot>

Note the children of the N2:Order also needs N2: prefix as well as the ns: prefix for the rest of the elements.

I use the XSL transformation below:

<xsl:stylesheet version="1.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
 <xsl:output omit-xml-declaration="yes" indent="yes"/>

  <xsl:template match="@* | node()">
  <xsl:copy>
   <xsl:apply-templates select="@* | node()"/>
  </xsl:copy>
 </xsl:template>


<xsl:template match="/MyRoot">
 <MyNewRoot xmlns="http://schemas.foo.com/response"
   xmlns:N1="http://schemas.foo.com/request"
   xmlns:N2="http://schemas.foo.com/details">
     <xsl:apply-templates/>
 </MyNewRoot>
 </xsl:template>

<xsl:template match="/MyRoot/MyRequest">
  <xsl:element name="N1:{name()}" namespace="http://schemas.foo.com/request">
    <xsl:copy-of select="namespace::*"/>
    <xsl:apply-templates select="@* | node()"/>
  </xsl:element>
 </xsl:template>

 <xsl:template match="/MyRoot/List/Order">
  <xsl:element name="N2:{name()}" namespace="http://schemas.foo.com/details">
    <xsl:copy-of select="namespace::*"/>
    <xsl:apply-templates select="@* | node()"/>
  </xsl:element>
 </xsl:template>

</xsl:stylesheet>

This one doesn't process the ns (I couldn't figure this out). When I process thru the above the XSL transformation with AltovaXML I end up with below:

<MyNewRoot xmlns="http://schemas.foo.com/response"  
xmlns:N1="http://schemas.foo.com/request"  
xmlns:N2="http://schemas.foo.com/details">
    <N1:MyRequest CompletionCode="0" CustomerID="9999999999"/>
    <List xmlns="" TotalList="1">
            <N2:Order CustomerID="999999999" Level="Preferred" Status="Shipped">
                    <BillToAddress ZipCode="22221"/>
                    <ShipToAddress ZipCode="22222"/>
                    <Totals Tax="0.50" SubTotal="10.00" Shipping="4.95"/>
            </N2:Order>
    </List>
    <Errors/>
</MyNewRoot>

Note that N2: prefix for the children of Order is not there after the XSL transformation. Also additional xmlns="" in the Order header (for some reason). I couldn't figure out putting the ns: prefix for the rest of the elements (like Errors and List).

First of all, why would I need to put the prefix for the children if the parent already has it. Doesn't the parent namespace dictate the children nodes/attribute namespaces?

Secondly, I want to add the prefixes in the above XML as expected, how can I do that with XSL?

Answer

Dimitre Novatchev picture Dimitre Novatchev · Jun 5, 2010

This transformation (only 42 lines):

<xsl:stylesheet version="1.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
 xmlns:ns="http://schemas.foo.com/response"
 xmlns:N1="http://schemas.foo.com/request"
 xmlns:N2="http://schemas.foo.com/details"
 >
 <xsl:output omit-xml-declaration="yes" indent="yes"/>

 <xsl:template match="*">
  <xsl:element name="ns:{name()}"
       namespace="http://schemas.foo.com/response">
    <xsl:apply-templates select="node()|@*"/>
  </xsl:element>
 </xsl:template>

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

 <xsl:template match="/MyRoot">
  <xsl:element name="ns:{name()}"
       namespace="http://schemas.foo.com/response">
    <xsl:copy-of select=
     "document('')/*/namespace::*[name()='N1' or name()='N2']"/>
    <xsl:apply-templates select="node()|@*"/>
  </xsl:element>
 </xsl:template>

 <xsl:template match="MyRequest">
  <xsl:element name="N1:{name()}"
       namespace="http://schemas.foo.com/request">
    <xsl:apply-templates select="node()|@*"/>
  </xsl:element>
 </xsl:template>

 <xsl:template match="*[ancestor-or-self::Order]">
  <xsl:element name="N2:{name()}"
       namespace="http://schemas.foo.com/details">
    <xsl:apply-templates select="node()|@*"/>
  </xsl:element>
 </xsl:template>
</xsl:stylesheet>

when applied on the provided XML document:

<MyRoot>
  <MyRequest CompletionCode="0" CustomerID="9999999999"/>
  <List TotalList="1">
    <Order CustomerID="999999999" OrderNo="0000000001" Status="Shipped">
      <BillToAddress ZipCode="22221"/>
      <ShipToAddress ZipCode="22222"/>
      <Totals Tax="0.50" SubTotal="10.00" Shipping="4.95"/>
    </Order>
  </List>
  <Errors/>
</MyRoot>

produces the wanted result:

<ns:MyRoot xmlns:N1="http://schemas.foo.com/request" xmlns:N2="http://schemas.foo.com/details" xmlns:ns="http://schemas.foo.com/response">
    <N1:MyRequest CompletionCode="0" CustomerID="9999999999"/>
    <ns:List TotalList="1">
        <N2:Order CustomerID="999999999" OrderNo="0000000001" Status="Shipped">
            <N2:BillToAddress ZipCode="22221"/>
            <N2:ShipToAddress ZipCode="22222"/>
            <N2:Totals Tax="0.50" SubTotal="10.00" Shipping="4.95"/>
        </N2:Order>
    </ns:List>
    <ns:Errors/>
</ns:MyRoot>

Do note:

  1. The use of <xsl:element> and its name and namespace attributes.

  2. How the identity template has been evolved into the first two templates of the transformation -- this decision was based on the fact that only in exceptional cases an element must not be in the ns: namespace.

  3. How the N2: namespace is specified for the Order element or any of its descendent elements.