Bug #4683

chained descendant axes gathering extra nodes

Added by Erica Coan about 1 month ago. Updated 24 days ago.

DOM Interface
Start date:
Due date:
% Done:


Estimated time:
Legacy ID:
Applies to branch:
10, 9.9
Fix Committed on Branch:
10, 9.9
Fixed in Maintenance Release:


I am getting unexpected results from the attached(greatly simplified) transform when run via Saxon HE for .NET. I would expect the 3 messages to have values of 0, 1, and 0. Instead, I get 3, 3, and 0. I've played around with different versions of the select, and it seems to me that the descendant axis is picking up ALL descendants, not just the descendants of the set of p nodes gathered up to that point.

select = "count(ancestor::p/preceding-sibling::p/descendant::sectPr)"

Goal: Find preceding p siblings of ancestor p nodes. Then gather sectPr descandants of those p nodes.

transform.xsl (843 Bytes) transform.xsl Erica Coan, 2020-08-13 19:25
input.xml (2.06 KB) input.xml Erica Coan, 2020-08-13 19:25


#1 Updated by Martin Honnen about 1 month ago

Are you sure the two files demonstrate the issue?

I wrote

            string xmlUri = "";
            string xsltUri = "";

            Processor processor = new Processor();

            Console.WriteLine("{0}: {1}", processor.ProductTitle, processor.ProductVersion);

            XsltCompiler compiler = processor.NewXsltCompiler();

            Xslt30Transformer xslt30Transformer = compiler.Compile(new Uri(xsltUri)).Load30();

            DocumentBuilder docBuilder = processor.NewDocumentBuilder();

            var input = docBuilder.Build(new Uri(xmlUri));

            xslt30Transformer.GlobalContextItem = input;

            xslt30Transformer.ApplyTemplates(input, processor.NewSerializer(Console.Out));

with .NET and using the version of Saxon you use and get

SAXON-HE from Saxonica:

#2 Updated by Erica Coan about 1 month ago

I confirmed that the files do have the issue for me. However, by you including your code, I was able to see where the difference is between your results and mine. I ran your code as-is, and also got 0 1 0 for results. Then I adjusted it to look more like my code, until the results switched to 3 3 0. The key difference turned out to be the way that I'm supplying the input:

Dim myInputDoc As New Xml.XmlDocument Dim myFileStream As FileStream = New FileStream(xmlUri,FileMode.Open,FileAccess.Read, FileShare.ReadWrite) Dim mByte() As Byte ReDim mByte(CInt(myFileStream.Length) - 1) myFileStream.Read(mByte, 0, CInt(myFileStream.Length)) myFileStream.Close() Dim myMemStream As MemoryStream = New MemoryStream(mByte) myInputDoc.Load(myMemStream) xslt30Transformer.ApplyTemplates(docBuilder.Wrap(myInputDoc), processor.NewSerializer(Console.Out))

When I load the input this way, I get 3 3 0 for results.

#3 Updated by Martin Honnen about 1 month ago

That might be a bug then in or at least in the code behind the Wrap method.

#4 Updated by Martin Honnen about 1 month ago

It seems that, with a wrapped XmlDocument, for the empty p elements, the XPath descendant::sectPr finds all descendant sectPr elements of the following sibling p elements instead of (simply?) returning an empty sequence.

#5 Updated by Martin Honnen about 1 month ago

A more simple test case is

<?xml version="1.0" encoding="utf-8" ?>
    <foo id="foo1"/>
    <foo id="foo2"/>


<?xml version="1.0" encoding="utf-8"?>
<xsl:transform version="3.0" xmlns:xsl=""
  xmlns:xs="" exclude-result-prefixes="#all">

  <xsl:param name="DEBUG" static="yes" as="xs:boolean" select="true()"/>

  <xsl:output method="text" item-separator="&#10;"/>

  <xsl:template match="/">
    <xsl:for-each select="//item">
      <xsl:assert test="not(*) eq not(descendant::foo)" use-when="$DEBUG">No child elements but descendants</xsl:assert>
      <xsl:value-of select="position(), not(*), descendant::foo/@id"/>



1 true
2 false foo1
3 true
4 false foo2

for Saxon's Xdm but

1 true foo1 foo2
2 false foo1
3 true foo2
4 false foo2

for a wrapped .NET XmlDocument.

#6 Updated by Michael Kay about 1 month ago

  • Category set to DOM Interface
  • Status changed from New to In Progress
  • Assignee set to O'Neil Delpratt

Thanks Martin for your help in diagnosing this. It does indeed look like a problem with the DOM wrapping code on .NET.

Erica, we will of course investigate and fix this, but you should be aware that running Saxon on a DOM tree is much slower (sometimes 5 - 10 times slower) than letting Saxon build the tree itself using its native TinyTree format.

I suspect that we aren't running a sufficient set of tests using the DOM interface on .NET. On the Java side running the full QT3 test suite against third party tree models is part of our standard prerelease checklist, but I'm not sure this is the case on .NET.

#7 Updated by Michael Kay about 1 month ago

I wrote a unit test for the example in comment #5 which shows that we are getting it right with the Java DOM, which uses almost identical code.

#8 Updated by Michael Kay about 1 month ago

For the Java DOM, the method DOMNodeWrapper.getSuccessorNode() has the following test:

        if (anchor != null && start.isSameNode(anchor)) {
            return null;

which is missing from the corresponding method for the .NET DOM, DotNetNodeWrapper.getSuccessorNode(). I suspect these lines need to be added - but we need to test this on .NET. The test ensures that if we are positioned at the "anchor" node (the one whose descendants we are seeking), we return "end of sequence" rather than moving on to the next following sibling,

#9 Updated by Michael Kay about 1 month ago

These lines were added to DOMNodeWrapper on 2013-04-07 (revision 2272): no bug number was recorded. It appears this revision fixed bugs in all the third-party tree models using the SteppingNode framework (which was introduced in 2012-09) with the exception of the .NET DOM code. So it looks like the bug has gone undetected for quite a while.

#10 Updated by O'Neil Delpratt about 1 month ago

  • Applies to branch 10 added


I have taken the repo from comment #5 and reproduced the bug issue on C# when using the XmlDocument (C# DOM node).

Another difference I have noticed between DOM on Java and C# is that the Java DOM Node supports DOM level 3, whereas C# DOM only support is DOM level 2. The method isSameNode is available since DOM level 3. This is unfortunate because on C# this method is not available on the class XmlNode.

Currently looking at workarounds.

#11 Updated by Michael Kay about 1 month ago

It's probably safe with the C# DOM to compare DOM nodes for identity using the "==" operator, that is to assume that node identity implies object identity. This isn't a safe assumption with the Java DOM, since nodes can be instantiated lazily.

#12 Updated by Martin Honnen about 1 month ago

Or use as that always compares object identity while == might have been overridden (although it doesn't seem to be the case in the XmlNode/XmlDocument classes).

#13 Updated by O'Neil Delpratt about 1 month ago

Yes I had thought I could use == operator. The DOM spec description is clear on what to expect from the isSameNode method.

Also looking at the Xerces implementation on Java it see to have the same implementation:

    public boolean isSameNode(Node other) {
        // we do not use any wrapper so the answer is obvious
        return this == other;

#14 Updated by O'Neil Delpratt about 1 month ago

  • Status changed from In Progress to Resolved
  • Fix Committed on Branch 10, 9.9 added

The bug fix identity using the "==" operator applied in the getSuccessorNode method of DotNetNodeWrapper class. Bug fix available in the next release of Saxon on .NET. This will be released on Saxon 10 before it is released on the 9.9 branch.

#15 Updated by O'Neil Delpratt about 1 month ago

Added a nunit test case for testing of this bug for regression purposes. Looking at the W3C test suites for better testing when have a C# DOM node as the tree model within Saxon.

#16 Updated by Michael Kay about 1 month ago

Added QT3 test case prod-AxisStep/K2-Axes-106

#17 Updated by O'Neil Delpratt 24 days ago

  • % Done changed from 0 to 100
  • Fixed in Maintenance Release 10.2 added

Bug fix applied in the Saxon 10.2 maintenance release.

Please register to edit this issue

Also available in: Atom PDF