If you’re working with XML, as I currently am, XSLT can sometimes be a godsend. Something that would take ages to do in a structured, procedural way can be reduced to two or three lines of functional XSL code.
So it was with a growing sense of consternation that I noticed that adding XML namespaces to the original document seemed to break XSL’s ability to recognise elements! Consider:
<elem/>
This document can be processed by an out-of-the-box <xsl:template match=”elem”/> instruction. However, if we add a default namespace:
<elem xmlns=”http://example.com/foobar” />
then suddenly the template can no longer see the “elem” element. What’s gone wrong? If we replace the contents of our @match attribute with “*[name(.) = 'elem']” then the XSL template works as before, so there’s clearly an element there and it’s clearly “called” “elem”. So how
The solution is: add to the top of the XSL template an attribute of @xmlns:[something]=”http://example.com/foobar” to match the incoming document.
The confusion (on XSL’s part and mine) arises because of the way that the default namespace is treated. When an XSL stylesheet’s tags are carted off to the xsl: namespace and we define xmlns:xsl, we’re keeping them out of the way of the original document’s own default namespace (the tag name “elem” might be considered to be equivalent to “[blank]:elem”). This means that the document can flow through the XSL parser without having to worry about the XSL tags getting in the way: input and output namespaces can both be undefined.
However, when the incoming document has an @xmlns defining the default namespace for all its tags, then XSL no longer sees this as the “default” namespace, unless you tell it to. If you don’t provide an @xmlns:[something] attribute for XSL, it won’t recognise the tags at all. It can still see them as having a tagname “elem”, but no XPath search will find that tag directly.
Because you don’t want your output to necessarily have @xmlns=”http://example.com/foobar” peppered throughout (maybe your output is just plain (X)HTML?) then you should tell XSL to treat the incoming default namespace as actually having a prefix, so when it reads “elem” it actually thinks of it as, say “in:elem”. Then change your references to anything in the incoming document to have “in:” in front of the nodes and it all works:
<?xml version=”1.0″ encoding=”utf-8″?>
<xsl:stylesheet version=”1.0″
xmlns:xsl=”http://www.w3.org/1999/XSL/Transform”
xmlns:in=”http://example.com/foobar”><xsl:template match=”/”>
<xsl:apply-templates/>
</xsl:template><xsl:template match=”in:elem”>
<found/>
</xsl:template>
</xsl:stylesheet>
Thanks to Dimitre Novatchev via Mark Bosley, for the solution to this particularly knotty puzzle.