Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

NamespaceContext and using namespaces with XPath

Tags:

java

xml

xpath

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?

like image 772
Jherico Avatar asked May 27 '09 04:05

Jherico


4 Answers

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.

like image 73
McDowell Avatar answered Oct 05 '22 12:10

McDowell


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

like image 41
Suppressingfire Avatar answered Oct 05 '22 10:10

Suppressingfire


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.

like image 7
Asaf Mesika Avatar answered Oct 05 '22 11:10

Asaf Mesika


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.

like image 2
bartolom Avatar answered Oct 05 '22 11:10

bartolom