views:

32

answers:

1

Hello,

After scouring the net for answers, coming up with "almost" solutions... I decided to reduce the problem to a very simple case.

Consider the following XML snippet:

<me:root xmlns:me="http://stackoverflow.com/xml"
  xmlns="http://www.w3.org/1999/xhtml"&gt;
    <me:element>
        <p>Some HTML code here.</p>
    </me:element>
</me:root>

Note that the p element is of XHTML's namespace, which is the default one for this doc.

Now consider the following simple stylesheet. I want to create an XHTML document, with the contents of me:element as the body.

<xsl:stylesheet version="2.0"
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  xmlns:me="http://stackoverflow.com/xml"
  xmlns="http://www.w3.org/1999/xhtml"
  exclude-result-prefixes="me">
    <xsl:template match="/">
        <html xmlns="http://www.w3.org/1999/xhtml"&gt;
            <head>
                <title>My Title</title>
            </head>
            <body>
                <xsl:copy-of select="me:root/me:element/node()"/>
            </body>
        </html>
    </xsl:template>
</xsl:stylesheet>

Note that I included exclude-result-prefixes... But see what I get:

<html xmlns="http://www.w3.org/1999/xhtml"&gt;
    <head>
        <title>My Title</title>
    </head>
    <body>
        <p xmlns:me="http://stackoverflow.com/xml"&gt;Some HTML code here.</p>
    </body>
</html>

And what's driving me insane here is why, oh why does xmlns:me appears inside the p element?

No matter what I tried, I couldn't get stuff to work. I have a strange feeling that the problem is with my xsl:copy-of statement.

+4  A: 

I have a strange feeling that the problem is with my xsl:copy-of statement.

This is exactly the reason.

The source XML document contains this fragment:

<me:element> 
    <p>Some HTML code here.</p> 
</me:element> 

In the XPath data model, namespace nodes are propagated from a root of a subtree to all of its descendents. Therefore, the <p> element has the following namespaces:

  1. "http://www.w3.org/1999/xhtml"

  2. "http://stackoverflow.com/xml"

  3. "http://www.w3.org/XML/1998/namespace"

  4. http://www.w3.org/2000/xmlns/

The last two are reserved namespaces (for the prefixes xml: and xmlns) and are available to any named node.

The reported problem is due to the fact that by definition the <xsl:copy-of> instruction copies all nodes and their complete subtrees with all namespaces belonging to each of the nodes.

Remember: the prefixes specified as the value of the exclude-result-prefixes attribute are excluded only from literal-result elements!

Solution:

<xsl:stylesheet version="2.0"
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  xmlns:me="http://stackoverflow.com/xml"
  xmlns="http://www.w3.org/1999/xhtml"
  exclude-result-prefixes="me">

  <xsl:output method="xml" omit-xml-declaration="yes"
  indent="yes"/>
  <xsl:strip-space elements="*"/>

    <xsl:template match="/">
     <html xmlns="http://www.w3.org/1999/xhtml"&gt;
      <head>
        <title>My Title</title>
      </head>
      <body>
        <xsl:apply-templates select="me:root/me:element/*"/>
      </body>
     </html>
    </xsl:template>

    <xsl:template match="*">
      <xsl:element name="{name()}">
       <xsl:copy-of select="@*"/>
       <xsl:apply-templates/>
      </xsl:element>
    </xsl:template>
</xsl:stylesheet>

when this transformation is applied on the provided XML document:

<me:root xmlns:me="http://stackoverflow.com/xml"
  xmlns="http://www.w3.org/1999/xhtml"&gt;
    <me:element>
        <p>Some HTML code here.</p>
    </me:element>
</me:root>

the wanted, correct result is produced:

<html xmlns="http://www.w3.org/1999/xhtml"&gt;
   <head>
      <title>My Title</title>
   </head>
   <body>
      <p>Some HTML code here.</p>
   </body>
</html>
Dimitre Novatchev
Couldn't be more helpful even if you tried. Thanks Dimitre - excellent explanation. Right to the point.
Isaac
I did realize, though, a flaw with this solution: attributes in the HTML elements will not be copied... so I'm trying to cope with that now.
Isaac
@Isaac: I just edited my answer to also copy all attributes of an element. Instead of `<xsl:copy-of>` you can use `<xsl:apply-templates>` here and this gives you more flexibility -- you can have templates for processing specific attributes.
Dimitre Novatchev
@Dimitre: +1 Excellent answer!
Alejandro