Project

Profile

Help

Calling setContextItem on an extracted XdmNode does not work as expected

Added by Darren Manuel over 4 years ago

Hello, We are using net Saxon API 9.91 for XPath processing. Below is an XML document quite similar to what we have:

<data>
   <row>
       <name>Michael</name>
   </row>
   <row>
      <address>address here</address>
   </row>
   <row>
      <phone>911</phone>
   </row>
</data>

We create the XdmNode object from this String with the DocumentBuilder class. Next, we get an ArrayList of XdmNode objects by grouping them by "row" element.

XPathExecutable exec = compiler.compile(/data/row);
XPathSelector selector  = exec.load();
selector.setContextItem(rootDocument);
XdmValue xdmValue = selector.evaluate();

We iterate over the XdmValue to extract the XdmNode objects and store each into the ArrayList. As an example, the first XdmNode object looks like this. Let's call this object, nameXdmNode.

<row>
   <name>Michael</name>
</row>

Now here is the interesting part. We still need to parse the XdmNode object list for further processing. Let's say I want to get the name of the attributes inside the current XdmNode. So in order to do that, I expect this code to work:

XPathExecutable exec = compiler.compile(/row/*/name());
XPathSelector selector  = exec.load();
selector.setContextItem(nameXdmNode);
XdmValue xdmValue = selector.evaluateSingle();

Then I repeat the code above for phoneXdmNode and addressXdmNode. The problem is, the XPath indicated above is not found. I tested this path (and other paths I'm using) in an online XPath tester so I'm sure they are correct.

Next I tried to include the root element. XPath: /data/row/*/name()

For some reason, this works even if my context item was clearly set to ONLY the containing the element. In other words it seems the XdmNode object still points to the original object built with the DocumentBuilder. Any explanation or workaround here? I cannot find an explanation.

Thank you.


Replies (6)

Please register to reply

RE: Calling setContextItem on an extracted XdmNode does not work as expected - Added by Michael Kay over 4 years ago

An XPath expression beginning with "/" selects relative to the root of the document containing the context item. If the context item is a "row" element and you want the names of its children, then the expression you need is */name() (or *!name() - we should get into the habit of using ! rather than / when it makes sense). Writing /row/*/name() will go to the document node, then look for its row children, and find there aren't any, because the outermost element is named data.

RE: Calling setContextItem on an extracted XdmNode does not work as expected - Added by Michael Kay over 4 years ago

Incidentally, you might find that the XdmValue.select() API is both more usable and more efficient for this kind of thing. Along the lines:

        List<XdmNode> rows = doc.select(child().then(child(isElement()))).asList();
        for (XdmNode row : rows) {
            for (XdmNode name : row.children("name")) {
                System.out.println(name.getStringValue());
            }
        }

This assumes imports of

import static net.sf.saxon.s9api.streams.Steps.child;
import static net.sf.saxon.s9api.streams.Predicates.isElement;

This approach (inspired by Microsoft's Linq and the Java streams API) avoids the cost of parsing XPath expressions, and gives you better Java type-checking. And of course you can mix this with the XPath API, e.g. using the XPath API for more complex selections and the select() API for simpler ones.

RE: Calling setContextItem on an extracted XdmNode does not work as expected - Added by Darren Manuel over 4 years ago

Thanks for the insights and the very prompt response! I thought that the / always pertains to the root of the current XdmNode object, and not the entire original document.

I will look into this.

RE: Calling setContextItem on an extracted XdmNode does not work as expected - Added by Darren Manuel over 4 years ago

Hello again, I tried using this XPath value as suggested: /row//name() instead of /row/*/name().

Again this is the code processing the XPath:

XPathExecutable exec = compiler.compile(xpath);
XPathSelector selector  = exec.load();
selector.setContextItem(nameXdmNode);
XdmValue xdmValue = selector.evaluateSingle();

The output was an empty string. What we want is to process elements (children) inside the containing the element. And we will repeat this for all other groups through iteration.

By the way, my actual XPath is a little more complicated than getting a single element name but I just wanted to point out that I'm not getting what I expected.

RE: Calling setContextItem on an extracted XdmNode does not work as expected - Added by Michael Kay over 4 years ago

If the context item is a row, then */row isn't going to select anything.

    (1-6/6)

    Please register to reply