2004.12.30 11:53 AM

.NET XSL and XPath Bugs

In my last post, I described an XSLT style sheet that normalizes Excel's SpreadsheetML by calculating the row and column of mss:Cell elements. I had only run the style sheet through Microsoft's MSXML 4.0 parser yesterday (without any problems), so I figured today I would run it through .NET's System.Xml.Xsl.XslTransform class just to be sure it worked there as well. It doesn't:

Unhandled Exception: System.NullReferenceException: Object reference not set to an instance of an object.
   at System.Xml.XPath.FilterQuery.SetXsltContext(XsltContext input)
   at System.Xml.XPath.FilterQuery.SetXsltContext(XsltContext input)
   at System.Xml.XPath.MergeFilterQuery.SetXsltContext(XsltContext input)
   at System.Xml.XPath.BaseAxisQuery.SetXsltContext(XsltContext context)
   at System.Xml.XPath.BaseAxisQuery.SetXsltContext(XsltContext context)
   at System.Xml.XPath.NumberFunctions.SetXsltContext(XsltContext context)
   at System.Xml.XPath.NumericExpr.SetXsltContext(XsltContext context)
   at System.Xml.XPath.NumericExpr.SetXsltContext(XsltContext context)
   at System.Xml.XPath.CompiledXpathExpr.SetContext(XmlNamespaceManager nsManager)
   at System.Xml.Xsl.Processor.GetValueQuery(Int32 key)
   at System.Xml.Xsl.Processor.ValueOf(ActionFrame context, Int32 key)
   at System.Xml.Xsl.ValueOfAction.Execute(Processor processor, ActionFrame frame)
   at System.Xml.Xsl.ActionFrame.Execute(Processor processor)
   at System.Xml.Xsl.Processor.Execute()
   at System.Xml.Xsl.XslTransform.Transform(IXPathNavigable input, XsltArgumentList args, XmlWriter output, XmlResolver resolver)
   at ewbi.xslt.Main(String[] args)

Bug #1

Apparently, the .NET Framework's XPath implementation can't handle XSLT node-set variables and template parameters followed by two or more predicates in an XPath expression. That's a problem for my style sheet, as it does just that in the xsl:value-of in the third xsl:when in both the row and column calculation xsl:choose blocks. In both cases, the $prevRows and $prevCols node-set variables are followed by the predicates [@ss:Index] and [position()=1].

I can work around this pretty easily by eliminating the xsl:variables, just using their XPath expressions directly (I only added them to improve readibility anyway). Or, I can create additional variables that each incorporate one of the two predicates used later in the xsl:value-of XPath expressions, leaving only one predicate to be evaluated there later. Testing shows that both approaches successfully eliminate the exception. Unfortunately, neither approach results in a correct transformation (see bug #2).

I looked around on MSDN but couldn't find a mention of this particular problem, but I did find a few newsgroup discussions. I don't have access to a Whidbey build right now, so I don't know if this problem has been resolved yet in the next release of .NET.

Bug #2

My style sheet relies on an XPath expression which includes a position-based filter in a predicate against a reverse axis node-set (in this case, preceding-sibling). The W3C's XPath v1.0 recommendation is pretty clear about how predicates are supposed to evaluate node position in this situation:

The proximity position of a member of a node-set with respect to an axis is defined to be the position of the node in the node-set ordered in document order if the axis is a forward axis and ordered in reverse document order if the axis is a reverse axis.

The good news is that MSXML 4.0 and .NET both evaluate the node position correctly when the reverse axis node-set is followed by a single predicate. So, for example, given this XML:

<root>
  <n>1</n>
  <n k="B">2</n>
  <n k="C">3</n>
  <n>4</n>
  <n>5</n>
</root>

And this XSLT style sheet, which has only one reverse axis predicate (specifically, one that filters on the node's position in the reverse axis node-set):

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:template match="/">
    <test>
      <xsl:copy-of select="//n[position()=4]/preceding-sibling::n[position()=1]"/>    
      <xsl:copy-of select="//n[position()=4]/preceding-sibling::n[position()=last()]"/>
    </test>
  </xsl:template>
</xsl:stylesheet>

Both MSXML 4.0 and .NET produce the correct result based on a reverse document ordered node-set position evaluation:

<test>
  <n k="C">3</n>
  <n>1</n>
</test>

But, if you toss in another predicate, like restricting the reverse axis node-set to just those having a "k" attribute, as follows:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:template match="/">
    <test>
      <xsl:copy-of select="//n[position()=4]/preceding-sibling::n[@k][position()=1]"/>    
      <xsl:copy-of select="//n[position()=4]/preceding-sibling::n[@k][position()=last()]"/>
    </test>
  </xsl:template>
</xsl:stylesheet>

Then only MSXML 4.0 produces the correct result:

<test>
  <n k="C">3</n>
  <n k="B">2</n>
</test>

.NET incorrectly produces a result based on a document ordered evaluation of the position:

<test>
  <n k="B">2</n>
  <n k="C">3</n>
</test>

This behavior has negative consequences for my style sheet, as its calculation of mss:Cell rows and columns relies on the position 1 node in the preceding-sibling axis being the one closest to the context node. I suppose I could revamp my style sheet to honor .NET's bad behavior, but I've already wasted enough time on this.

I searched around on MSDN and Google for some mention of this problem, too, but didn't find anything relevant. I'm sure I'm not the first to notice this, so hopefully someone will leave a comment with more info. Again, as I mentioned above, I don't have Whidbey loaded anywhere right now, so I can't check to see if they've fixed this problem either.


Comments

I have run across this error as well. .NET was not matching a where I had multiple predicates, ie. [Type = '1'][1], and therefore applying the default template which is just a stream of the text values.

Roger | 2005.05.04 08:40 AM


TrackBack

TrackBack URL:  http://www.typepad.com/services/trackback/6a00d8341c7bd453ef00d834429c4c53ef

Listed below are links to weblogs that reference .NET XSL and XPath Bugs: