Tuesday, May 3, 2011

Blank xmlns="" Attributes From Import

Hi all,

I'm attempting to do a transform on an XML document. My XML transform can result in two different types of base element depending on the value of a certain element:

<xsl:template match="/">
  <xsl:choose>
    <xsl:when test="/databean/data[@id='pkhfeed']/value/text()='200'">
      <xsl:call-template name="StructureA">
        <xsl:with-param name="structure" select="//databean" />
      </xsl:call-template>
    </xsl:when>
    <xsl:otherwise>
      <xsl:call-template name="StructureB">
        <xsl:with-param name="structure" select="//databean" />
      </xsl:call-template>
    </xsl:otherwise>
  </xsl:choose>
</xsl:template>

StructureA or StructureB are then created with their own namespaces and schemaLocations:

<StructureA xmlns="http://...">

StructureA & B share some common elements so these are defined in a separate file called "xmlcommon.xslt" that both structures include templates from. This xmlcommon file doesn't have a default namespace defined as I want it to be useable from the namespace defined in either StructureA or StructureB. But when I run my transform, any templates pulled in from the common file result in blank xmlns attributes:

<StructureA xmlns="http://...">
  <SharedElement xmlns="">Something</SharedElement>
</StructureA>

When validating, the blank namespace is then used instead of the correct parent one. Does anyone know how I can stop my templates in my common file from adding those blank xmlns attributes?

Here's an snippet from the common file:

<xsl:stylesheet version="1.0" xmlns:fn="http://www.w3.org/2005/02/xpath-functions" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

  <xsl:template name="ControlledListStructure">
    <xsl:param name="xmlElem" />
    <xsl:param name="structure" />

    <xsl:element name="{$xmlElem}">
      <!-- Blah blah blah -->
    </xsl:element>
  </xsl:template>
</xsl:stylesheet>
From stackoverflow
  • The key thing to realize is that your stylesheet dictates the name of each element you add to the result tree. An element's name has two parts: the local name and the namespace URI. In your code above, you supply the local name (the value of $xmlElem), but you don't specify a namespace URI, which means it will default to the empty string. (Actually, it takes on the default namespace of that stylesheet module; since there is none, then it's the empty string.) In other words, the element will be not in a namespace. When serializing the document, the XSLT processor must include the xmlns="" un-declarations so as to undeclare the default namespace that appears at the top. Otherwise, the element would take on that namespace, which is not what your stylesheet dictated. The least intrusive way of fixing this would be to add another parameter (e.g. $namespaceURI), just as you have with $xmlElem. Then you'd write:

    <xsl:element name="{$xmlElem}" namespace="{$namespaceURI}">
    

    Now, the resulting element will take on whatever namespace you tell it to take on (which will have the effect of removing those default namespace un-declarations).

    That should answer your question. I offer the following as free bonus material. ;-)

    You should remove the text() node test in your value comparison. Very rarely will you need to directly compare the values of text nodes. Instead, you can just compare the string-value of the element itself (which is defined as the concatenation of the string-values of all its descendant text nodes). That would look like this:

    <xsl:when test="/databean/data[@id='pkhfeed']/value = '200'">
    

    The advantage of doing it this way is that your code won't break if there's a comment hiding in there:

    <value>2<!--test-->00</value>
    

    In this case, there are two text nodes ("2" and "00"). Your original test would fail, since it checks to see if any of them are equal to "200". Not very likely to happen in this case, but in any case testing the string-value of the element (as opposed to its text node children) is a good practice when that's your intention.

    Finally, I encourage you to learn about template rules and XPath context. I tend to avoid <xsl:choose>, <xsl:call-template>, and <xsl:with-param> whenever possible. For one thing, template rules can help you avoid a lot of the ugly, verbose parts of XSLT.

    <xsl:template match="/databean[data[@id='pkhfeed']/value = '200']" priority="1">
      <StructureA xmlns="http://...">
        ...
      </StructureA>
    </xsl:template>
    
    <xsl:template match="/databean">
      <StructureB xmlns="http://...">
        ...
      </StructureB>
    </xsl:template>
    

    Even if you keep using <xsl:call-template>, you shouldn't have to pass that $structure parameter, as the current node will remain unchanged in the called template. You can access //databean (or /databean, which I suspect is what you mean) just as easily from within either of your StructureA or StructureB templates, because the current node will still be "/" (the document node).

    If you're interested in learning more about XSLT's core processing model, and its most powerful features (template rules), then I encourage you to check out "How XSLT Works", the free sample chapter from my XSLT 1.0 Pocket Reference.

    I hope this has been helpful to you, even if it's more than you bargained for!

    Dimitre Novatchev : Good explanation, Evan. Nothing more to add. +1 from me. Welcome to SO.
    Lee Theobald : Thanks Evan. That's a big help :) And thank you for the extra info too. I don't touch XSLT's often so it's nice to hear the better ways to do things.

0 comments:

Post a Comment