dominikm
dominikm

Reputation: 55

How to get xml element with nested default namespace with XPath

I have the following xml

<?xml version="1.0" encoding="UTF-8" standalone="no" ?>
<abc:root-example xmlns:abc="http://www.example.com/abc">
  <abc:test-object Name="DesiredObject">
    <root xmlns="http://www.example.com">
      <value>Hello world</value>
    </root>
  </abc:test-object>
</abc:root-example>

and the following java code

DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
dbf.setNamespaceAware(true);
DocumentBuilder db = dbf.newDocumentBuilder();
Document doc = db.parse(xmlFilePath.toFile());

XPath xpath = XPathFactory.newInstance().newXPath();

xpath.setNamespaceContext(new NamespaceContext() {
    @Override
    public String getNamespaceURI(String s) {
        if (s.equals("abc")) {
            return "http://www.example.com/abc";
        }
        return null;
    }

    @Override
    public String getPrefix(String s) {
        return null;
    }

    @Override
    public Iterator<String> getPrefixes(String s) {
        return null;
    }
});

XPathExpression expr = xpath.compile("//abc:test-object/root");
Node node = (Node) expr.evaluate(doc, XPathConstants.NODE);

The final node here always ends up being null. If I remove xmlns="http://www.example.com" from the root node, then the evaluation returns the expected node. It's unclear to me how I properly format the XPath request to get the node. I believe it has something to do with root using a different default namespace, but am having difficult figuring it out.

Upvotes: 1

Views: 841

Answers (1)

zb226
zb226

Reputation: 10539

I've found the following:

  1. In getNamespaceURI(...), the value null is returned if the namespace prefix is not abc. But the default namespace's URI is http://www.example.com instead.

  2. In the XPath query, no namespace prefix given for root. This is reasonable since it should use the default namespace, however it seems necessary to explicitly give an empty prefix now: :root.

Applying both changes will make the code return the node.

xpath.setNamespaceContext(new NamespaceContext() {
    @Override
    public String getNamespaceURI(String s) {
        if (s.equals("abc")) {
            return "http://www.example.com/abc";
        }
        return "http://www.example.com";
    }

    @Override
    public String getPrefix(String s) {
        return null;
    }

    @Override
    public Iterator<String> getPrefixes(String s) {
        return null;
    }
});

XPathExpression expr = xpath.compile("//abc:test-object/:root");
Node node = (Node) expr.evaluate(doc, XPathConstants.NODE);
System.out.println(node == null ? "null" : node);  // just to show

Output:

[root: null]

Update: As Martin Honnen points out, although accepted by javax.xml.xpath, :root is not a valid XPath expression. Also, javax.xml.xpath is only XPath 1.0 compliant. I've found another, rather verbose way to select the node with //abc:test-object/*[local-name()=\"root\"], but it probably makes more sense to either just

  • use namespace prefixes or
  • use an XPath 2.0 compliant library

Upvotes: 1

Related Questions