4

Given the following XML (basic.xml):

<rdr>
  <details>
    <detail>
        <name>version</name>
        <value>15.0</value>
    </detail>
    <detail>
        <name>resolution</name>
        <value>1080X1920</value>
    </detail>
  </details>
</rdr>

I want to get the name and versions out, so I have the following code. This isn't very tidy, but I have created this for illustrative purposes, but the code does fully function:

import java.io.FileInputStream;
import java.io.IOException;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;

import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

public class Example {

    private static XPath factoryXpath = null;

    public static void main(String[] args) throws ParserConfigurationException, SAXException, IOException, XPathExpressionException {
        FileInputStream fin = new FileInputStream("basic.xml");
        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
        DocumentBuilder builder = factory.newDocumentBuilder();
        Document document = builder.parse(fin);

        XPathFactory xPathFactory = XPathFactory.newInstance();
        factoryXpath = xPathFactory.newXPath();

        printDetails(document);
    }

    public static void printDetails(Node node) throws XPathExpressionException {
        NodeList nodes = (NodeList) factoryXpath.evaluate("//detail", node, XPathConstants.NODESET);

        printNameAndValue(nodes.item(0));
        printNameAndValue(nodes.item(1));

    }

    public static void printNameAndValue(Node node) throws XPathExpressionException {
        System.out.println("Name=" + (String) factoryXpath.evaluate("//name", node, XPathConstants.STRING));
        System.out.println("Value=" + (String) factoryXpath.evaluate("//value", node, XPathConstants.STRING));
    }

}

This outputs the following:

Name=version
Value=15.0
Name=version
Value=15.0

Why does it output the same Name and Value both times?

If I clone the node first, so that printNameAndValue now looks like this:

public static void printNameAndValue(Node node) throws XPathExpressionException {
    Node clonedNode = node.cloneNode(true);
    System.out.println("Name=" + (String) factoryXpath.evaluate("//name", clonedNode, XPathConstants.STRING));
    System.out.println("Value=" + (String) factoryXpath.evaluate("//value", clonedNode, XPathConstants.STRING));
}

I get the following output:

Name=version
Value=15.0
Name=resolution
Value=1080X1920

Why does a cloned node act differently?

I removed the cloned node and reverted to the original example where it doesn't work and added the method described here https://stackoverflow.com/a/2325407/211560 but with it taking a Node instead of a Document in its attributes. This prints out the following result:

<?xml version="1.0" encoding="UTF-8"?><detail>
        <name>version</name>
        <value>15.0</value>
    </detail>
Name=version
Value=15.0
<?xml version="1.0" encoding="UTF-8"?><detail>
        <name>resolution</name>
        <value>1080X1920</value>
    </detail>
Name=version
Value=15.0

It is clear from this that the node is the one we would expect; but it is applying the XPath to the first node, or maybe to the original document. I'm OK with cloning a node and using that but I'd really like to know why this is happening.

Community
  • 1
  • 1
Arthur
  • 1,332
  • 2
  • 19
  • 39

1 Answers1

3

The XPath expression //name is an absolute path (beginning with a /), so selects a node set containing all name elements in the document to which the context node belongs. Thus evaluating that expression as a string according to the XPath 1.0 data model will give you the string value of the first such node in document order.

The crucial part of that first sentence is "the document to which the context node belongs" - a cloned node is not attached to a document, so the XPath evaluator treats the node itself as the root of a document fragment and evaluates the expression against that fragment (which contains only one name element) instead of against the original document (which contains two).

If in printNameAndValue you instead used relative XPath expressions

public static void printNameAndValue(Node node) throws XPathExpressionException {
    System.out.println("Name=" + (String) factoryXpath.evaluate("name", node, XPathConstants.STRING));
    System.out.println("Value=" + (String) factoryXpath.evaluate("value", node, XPathConstants.STRING));
}

(or .//name if the name element might be a grandchild or deeper rather than an immediate child) then you should get the output you expect, i.e. the value of the first name (respectively value) element child of the specified node.

Ian Roberts
  • 120,891
  • 16
  • 170
  • 183
  • Thank-you. I assumed that a node would discard the document from which it is part of; but this does explain why. – Arthur Dec 18 '12 at 12:04