18

Resolving an xpath that includes namespaces in Java appears to require the use of a NamespaceContext object, mapping prefixes to namespace urls and vice versa. However, I can find no mechanism for getting a NamespaceContext other than implementing it myself. This seems counter-intuitive.

The question: Is there any easy way to acquire a NamespaceContext from a document, or to create one, or failing that, to forgo prefixes altogether and specify the xpath with fully qualified names?

jszumski
  • 7,430
  • 11
  • 40
  • 53
Jherico
  • 28,584
  • 8
  • 61
  • 87
  • 1
    There is some basic missundertood from your question: you can get all the namespaces URI from an XML with XPath (there are answers for this in SO), but you can't simple populate a prefix-namespace URI binding class because **for each node** it might be a different bound. When selecting nodes you should know in advance the names of your desired elements **and that includes their namespace URI** –  Dec 06 '10 at 15:28

5 Answers5

16

It is possible to get a NamespaceContext instance without writing your own class. Its class-use page shows you can get one using the javax.xml.stream package.

String ctxtTemplate = "<data xmlns=\"http://base\" xmlns:foo=\"http://foo\" />";
NamespaceContext nsContext = null;

XMLInputFactory factory = XMLInputFactory.newInstance();
XMLEventReader evtReader = factory
    .createXMLEventReader(new StringReader(ctxtTemplate));
while (evtReader.hasNext()) {
  XMLEvent event = evtReader.nextEvent();
  if (event.isStartElement()) {
    nsContext = ((StartElement) event)
        .getNamespaceContext();
    break;
  }
}

System.out.println(nsContext.getNamespaceURI(""));
System.out.println(nsContext.getNamespaceURI("foo"));
System.out.println(nsContext
    .getNamespaceURI(XMLConstants.XMLNS_ATTRIBUTE));
System.out.println(nsContext
    .getNamespaceURI(XMLConstants.XML_NS_PREFIX));

Forgoing prefixes altogether is likely to lead to ambiguous expressions - if you want to drop namespace prefixes, you'd need to change the document format. Creating a context from a document doesn't necessarily make sense. The prefixes have to match the ones used in the XPath expression, not the ones in any document, as in this code:

String xml = "<data xmlns=\"http://base\" xmlns:foo=\"http://foo\" >"
    + "<foo:value>"
    + "hello"
    + "</foo:value>"
    + "</data>";
String expression = "/stack:data/overflow:value";
class BaseFooContext implements NamespaceContext {
  @Override
  public String getNamespaceURI(String prefix) {
    if ("stack".equals(prefix))
      return "http://base";
    if ("overflow".equals(prefix))
      return "http://foo";
    throw new IllegalArgumentException(prefix);
  }

  @Override
  public String getPrefix(String namespaceURI) {
    throw new UnsupportedOperationException();
  }

  @Override
  public Iterator<String> getPrefixes(
      String namespaceURI) {
    throw new UnsupportedOperationException();
  }
}
XPathFactory factory = XPathFactory.newInstance();
XPath xpath = factory.newXPath();
xpath.setNamespaceContext(new BaseFooContext());
String value = xpath.evaluate(expression,
    new InputSource(new StringReader(xml)));
System.out.println(value);

Neither the implementation returned by the StAX API nor the one above implement the full class/method contracts as defined in the doc. You can get a full, map-based implementation here.

McDowell
  • 107,573
  • 31
  • 204
  • 267
  • This sort of confirms my fears. I can't have have a NamespaceContext that does arbitrary mapping unless I implement the class myself. But the XML streams example at least gives me a path for creating a NamespaceContext from a static function that builds a micro document as you do in the first example. I'll try that. – Jherico May 28 '09 at 15:17
  • Yes, it is a bit of a pain that there is no default implementation. – McDowell May 28 '09 at 15:25
  • I submit that that is stupid. How many other java interfaces which are used in public APIs have no publicly available concrete implementations or factories? – Jherico May 28 '09 at 19:13
8

I've just been working through using xpath and NamespaceContexts myself. I came across a good treatment of the issue on developerworks.

Suppressingfire
  • 3,246
  • 23
  • 17
  • 4
    After reading this essay from IBM, I can understand why there isn't a default implementation. There are a variety of ways to solve this problem. That said: Why doesn't an open source group, like Apache, create a library of different implementations? – kevinarpe Apr 25 '13 at 07:11
8

I found a convenient implementation in "Apache WebServices Common Utilities" called NamespaceContextImpl.

You can use the following maven dependency to obtain this class:

<dependency>
    <groupId>org.apache.ws.commons</groupId>
    <artifactId>ws-commons-util</artifactId>
    <version>1.0.1</version>
</dependency>

I've use it in the following manner (I know its built for sax, but after reading the code, its o.k):

NamespaceContextImpl nsContext = new NamespaceContextImpl();
nsContext.startPrefixMapping("foo", "my.name.space.com");

You don't need to called endPrefixMapping.

Yash
  • 9,250
  • 2
  • 69
  • 74
Asaf Mesika
  • 1,643
  • 4
  • 20
  • 33
  • 2
    you're still hardcoding the namespaces! – Meitham Aug 16 '11 at 16:55
  • Exactly what I was looking for. The javadoc: http://ws.apache.org/commons/util/apidocs/index.html – Eric Bréchemier Aug 03 '12 at 20:39
  • Meitham - I don't any way that avoids hardcoding the name space. You are bootstrapping the XPath search by creating this namespace context, initializing it by mapping a short prefix to each namespace *you know* contained in the document, and then when searching by XPath you use those prefixes when defining the path. – Asaf Mesika Aug 04 '12 at 07:25
  • "Apache WebServices Common Utilities" is exactly what I looking for. +1 – Jach Apr 18 '23 at 08:13
2

If you are using the Spring framework you can reuse their NamespaceContext implementation org.springframework.util.xml.SimpleNamespaceContext

This is a similar answer like the one from Asaf Mesika. So it doesn't give you automatic a NamespaceContext based on your document. You have to construct it yourself. Still it helps you because it at least gives you an implementation to starts with.

When we faced a similar problem, Both the spring SimpleNamespaceContext and the "Apache WebServices Common Utilities" worked. We wanted to avoid to the addition jar dependency on Apache WebServices Common Utilities and used the Spring one, because our application is Spring based.

bartolom
  • 301
  • 2
  • 5
0

If you are using Jersey 2 and only have a default XML namespace (xmlns="..."), you can use SimpleNamespaceResolver:

<?xml version="1.0" encoding="UTF-8"?>
<Outer xmlns="http://host/namespace">
    <Inner />
</Outer>
DocumentBuilderFactory docBuilderFactory = DocumentBuilderFactory.newInstance();
docBuilderFactory.setNamespaceAware(true);
DocumentBuilder docBuilder = docBuilderFactory.newDocumentBuilder();

Document document = docBuilder.parse(new File("document.xml"));
String query = "/t:Outer/t:Inner";

XPath xpath = XPathFactory.newInstance().newXPath();
String xmlns = document.getDocumentElement().getAttribute("xmlns");
xpath.setNamespaceContext(new SimpleNamespaceResolver("t", xmlns));

NodeList nodeList = (NodeList) xpath.evaluate(query, document, XPathConstants.NODESET);

//nodeList will contain the <Inner> element

You can also specify xmlns manually if you want.

Ben Hutchison
  • 4,823
  • 4
  • 26
  • 25