NamespaceContext and using namespaces with XPath

21,701

Solution 1

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.

Solution 2

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

Solution 3

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.

Solution 4

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.

Share:
21,701
Jherico
Author by

Jherico

Java, C++, OpenGL, VR Author of *[Oculus Rift in Action][1]*

Updated on July 09, 2022

Comments

  • Jherico
    Jherico almost 2 years

    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?

    • Admin
      Admin over 13 years
      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
  • Jherico
    Jherico about 15 years
    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.
  • McDowell
    McDowell about 15 years
    Yes, it is a bit of a pain that there is no default implementation.
  • Jherico
    Jherico about 15 years
    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?
  • Meitham
    Meitham almost 13 years
    you're still hardcoding the namespaces!
  • Eric Bréchemier
    Eric Bréchemier almost 12 years
    Exactly what I was looking for. The javadoc: ws.apache.org/commons/util/apidocs/index.html
  • Asaf Mesika
    Asaf Mesika almost 12 years
    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.
  • kevinarpe
    kevinarpe about 11 years
    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?