Java – why can only some XPath expressions find nodes when XML has a namespace prefix
In the following example code, any XPath of the form '/ / elementname' will return null when the source XML has a namespace prefix (see testwithns()) in the bottom code)
When the source XML does not have a namespace prefix, all listed XPath expressions return a node (see testnos())
I know I can solve this problem by setting the namespacecontext (as in testwithnscontext()), parsing XML into namespace aware documents, and using namespace prefixes in XPath But I don't want to, because my actual code needs to deal with XML with and without namespace prefixes
My question is why it just:
>/ / test > / / Child1 > / / Child1 > / / child2
Returns null, but all other examples in testwithns() return nodes?
yield
testNoNS() test = found /test = found //test = found //test/* = found //test/child1 = found //test/child1/grandchild1 = found //test/child2 = found //child1 = found //grandchild1 = found //child1/grandchild1 = found //child2 = found testWithNS() test = found /test = found //test = *** NOT FOUND *** //test/* = found //test/child1 = found //test/child1/grandchild1 = found //test/child2 = found //child1 = *** NOT FOUND *** //grandchild1 = *** NOT FOUND *** //child1/grandchild1 = found //child2 = *** NOT FOUND *** testWithNSContext() ns1:test = found /ns1:test = found //ns1:test = found //ns1:test/* = found //ns1:test/ns1:child1 = found //ns1:test/ns1:child1/ns1:grandchild1 = found //ns1:test/ns1:child2 = found //ns1:child1 = found //ns1:grandchild1 = found //ns1:child1/ns1:grandchild1 = found //ns1:child2 = found
code
import java.io.StringReader; import java.util.Iterator; import javax.xml.XMLConstants; import javax.xml.namespace.NamespaceContext; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.xpath.XPath; import javax.xml.xpath.XPathConstants; import javax.xml.xpath.XPathFactory; import org.junit.Test; import org.w3c.dom.Document; import org.xml.sax.InputSource; public class XPathBugTest { private String xmlDec = "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>"; private String xml = xmlDec + "<test>" + " <child1>" + " <grandchild1/>" + " </child1>" + " <child2/>" + "</test>"; private String xmlNs = xmlDec + "<ns1:test xmlns:ns1=\"http://www.wfmc.org/2002/XPDL1.0\">" + " <ns1:child1>" + " <ns1:grandchild1/>" + " </ns1:child1>" + " <ns1:child2/>" + "</ns1:test>"; final XPathFactory xpathFactory = XPathFactory.newInstance(); final XPath xpath = xpathFactory.newXPath(); @Test public void testNoNS() throws Exception { System.out.println("\ntestNoNS()"); final Document doc = getDocument(xml); isFound("test",xpath.evaluate("test",doc,XPathConstants.NODE)); isFound("/test",xpath.evaluate("/test",XPathConstants.NODE)); isFound("//test",xpath.evaluate("//test",XPathConstants.NODE)); isFound("//test/*",xpath.evaluate("//test/*",XPathConstants.NODE)); isFound("//test/child1",xpath.evaluate("//test/child1",XPathConstants.NODE)); isFound("//test/child1/grandchild1",xpath.evaluate("//test/child1/grandchild1",XPathConstants.NODE)); isFound("//test/child2",xpath.evaluate("//test/child2",XPathConstants.NODE)); isFound("//child1",xpath.evaluate("//child1",XPathConstants.NODE)); isFound("//grandchild1",xpath.evaluate("//grandchild1",XPathConstants.NODE)); isFound("//child1/grandchild1",xpath.evaluate("//child1/grandchild1",XPathConstants.NODE)); isFound("//child2",xpath.evaluate("//child2",XPathConstants.NODE)); } @Test public void testWithNS() throws Exception { System.out.println("\ntestWithNS()"); final Document doc = getDocument(xmlNs); isFound("test",XPathConstants.NODE)); } @Test public void testWithNSContext() throws Exception { System.out.println("\ntestWithNSContext()"); final Document doc = getDocumentNS(xmlNs); xpath.setNamespaceContext(new MyNamespaceContext()); isFound("ns1:test",xpath.evaluate("ns1:test",XPathConstants.NODE)); isFound("/ns1:test",xpath.evaluate("/ns1:test",XPathConstants.NODE)); isFound("//ns1:test",xpath.evaluate("//ns1:test",XPathConstants.NODE)); isFound("//ns1:test/*",xpath.evaluate("//ns1:test/*",XPathConstants.NODE)); isFound("//ns1:test/ns1:child1",xpath.evaluate("//ns1:test/ns1:child1",XPathConstants.NODE)); isFound("//ns1:test/ns1:child1/ns1:grandchild1",xpath.evaluate("//ns1:test/ns1:child1/ns1:grandchild1",XPathConstants.NODE)); isFound("//ns1:test/ns1:child2",xpath.evaluate("//ns1:test/ns1:child2",XPathConstants.NODE)); isFound("//ns1:child1",xpath.evaluate("//ns1:child1",XPathConstants.NODE)); isFound("//ns1:grandchild1",xpath.evaluate("//ns1:grandchild1",XPathConstants.NODE)); isFound("//ns1:child1/ns1:grandchild1",xpath.evaluate("//ns1:child1/ns1:grandchild1",XPathConstants.NODE)); isFound("//ns1:child2",xpath.evaluate("//ns1:child2",XPathConstants.NODE)); } private void isFound(String xpath,Object object) { System.out.println(xpath + " = " + (object == null ? "*** NOT FOUND ***" : "found")); } private Document getDocument(final String xml) throws Exception { final DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); return factory.newDocumentBuilder().parse(new InputSource(new StringReader(xml))); } private Document getDocumentNS(final String xml) throws Exception { final DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); factory.setNamespaceAware(true); return factory.newDocumentBuilder().parse(new InputSource(new StringReader(xml))); } public class MyNamespaceContext implements NamespaceContext { @Override public String getNamespaceURI(String prefix) { if ("ns1".equals(prefix)) { return "http://www.wfmc.org/2002/XPDL1.0"; } return XMLConstants.NULL_NS_URI; } @Override public String getPrefix(String uri) { throw new UnsupportedOperationException(); } @Override public Iterator getPrefixes(String uri) { throw new UnsupportedOperationException(); } } }
Update after Saxon test
I now use Saxon to change the xpahtfactory line to test the same code
final XPathFactory xpathFactory = new net.sf.saxon.xpath.XPathFactoryImpl();
Use all lines in Saxon testwithns() to return * * * not found * * * instead of returning the default xalan implementation like '/ / elementname'
Given that I'm using a non namespace aware document builder factory to parse XML, why don't these xpaths work and only some use xalan?
Solution
If you want to ignore namespaces, you can use the local name XPath function:
//*[local-name()='grandchild1']