views:

273

answers:

2

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"&gt;
    <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"&gt;
 <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"&gt;
     <xsl:apply-templates/>
 </MyNewRoot>
 </xsl:template>

<xsl:template match="/MyRoot/MyRequest">
  <xsl:element name="N1:{name()}" namespace="http://schemas.foo.com/request"&gt;
    <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"&gt;
    <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"&gt;
    <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?

+2  A: 

If you really care about what the namespace prefixes are in the output then you will want to use literal-result elements in your templates, rather than the xsl:element constructor:

<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"&gt;
    <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">
        <ns:MyNewRoot>
            <xsl:apply-templates/>
        </ns:MyNewRoot>
    </xsl:template>

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

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

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

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

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

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

    <xsl:template match="Errors" />

</xsl:stylesheet>

You should know that the namespace is important, the namespace-prefix is not. It is syntactic sugar. You can have multiple namespace-prefixes bound to the same namespace-uri, or not have a namespace prefix and still produce the same type of elements(bound to a particular namespace-uri).

Mads Hansen
+1 for saying prefix doesn't matter. I go further: any code that requires a particular prefix is badly broken and needs to fixed and/or publicly ridiculed for not following basic XML standards.
John Saunders
Unfortunately, this transformation *doesn't* produce the wanted output -- the MyRequest element is not in the required namespace. Also, it is too long.
Dimitre Novatchev
whoops, corrected the namespace for `MyRequest`. Thanks, @Dimitre. - for some reason I thought I remembered Ken Holman warning that specifying the namespace prefix in the `@name` of `xsl:element` declaration was not guaranteed to work, which is why I was using literal element declarations(and why it is so long). If that's not the case, then your solution would definitely be preferred.
Mads Hansen
@Mads-Hansen: If you specify a prefix, the XSLT processor will use it, even if it is necessary to redefine a namespace binding in the result document.
Dimitre Novatchev
Thanks for correcting your answer. I reversed my downvote.
Dimitre Novatchev
This is great. Kudos to you that I now have a better understanding on what I should be doing as well rather than just making one transformation work. Thanks Mads/John/Dimitre
Erdal
This one worked as well. The issue with it was the child nodes that exist didn't get the prefix. For the example, it certainly worked. Thanks again!
Erdal
+1  A: 

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"&gt;
    <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"&gt;
    <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"&gt;
    <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"&gt;
    <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"&gt;
    <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.

Dimitre Novatchev
+1 More succinct answer. Although, if I understand wording of the XSLT spec, the namespace prefix specified in the `@name` of `xsl:element` **is not guaranteed to work**, but probably will. http://www.w3.org/TR/xslt#section-Creating-Elements-with-xsl:element "XSLT processors may make use of the prefix of the QName specified in the name attribute when selecting the prefix used for outputting the created element as XML; however, they are not required to do so.".
Mads Hansen
@Mads-Hansen: I have checked with three different XSLT processors (only three because I am travelling and using my daughter's laptop right now) and they all use the prefix without problems.
Dimitre Novatchev
So, that's why I've been able to sneak in a few answers before you in the last few days! With regards to preserving the prefix, I suspect that it will work in just about every processor. I have worked with Ken Holman and attended his classes, and I do recall him pointing it out - but he will admit to being pedantic at times. Has also provided a similar solution to a similar question: http://www.stylusstudio.com/xsllist/200309/post80960.html
Mads Hansen
@Mads-Hansen: Yes, Ken explained it really well. And now I understand why you are so good in XSLT -- being trained by Ken Holman shows.
Dimitre Novatchev
This is an elegant solution as well. Thanks Dimitre. I am evaluating what I should do for a more complicated (multi-level) input XML now. IT looks like I will go with yours. Once I am sure about it, I will post the result. Thanks again.
Erdal
This worked for our XSL processors. Thank you all again!
Erdal
@Erdal: You are welcome. :)
Dimitre Novatchev