【问题标题】:NamespaceContext and using namespaces with XPathNamespaceContext 和使用 XPath 的命名空间
【发布时间】:2009-05-27 04:57:04
【问题描述】:

在 Java 中解析包含命名空间的 xpath 似乎需要使用 NamespaceContext 对象,将前缀映射到命名空间 url,反之亦然。但是,除了自己实现之外,我找不到任何获得NamespaceContext 的机制。这似乎违反直觉。

问题: 是否有任何简单的方法可以从文档中获取NamespaceContext,或者创建一个,或者失败,完全放弃前缀并指定具有完全限定名称的xpath?

【问题讨论】:

  • 您的问题有一些基本的误解:您可以使用 XPath 从 XML 获取所有命名空间 URI(SO 中有此答案),但您不能简单地填充前缀命名空间URI 绑定类,因为对于每个节点,它可能是不同的绑定。选择节点时,您应该提前知道所需元素的名称其中包括它们的命名空间 URI

标签: java xml xpath


【解决方案1】:

无需编写自己的类即可获得 NamespaceContext 实例。它的class-use 页面显示您可以使用javax.xml.stream 包获得一个。

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));

完全放弃前缀可能会导致表达不明确 - 如果您想删除命名空间前缀,则需要更改文档格式。从文档创建上下文不一定有意义。前缀必须匹配 XPath 表达式中使用的前缀,而不是任何文档中的前缀,如下代码所示:

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);

StAX API 返回的实现和上面的实现都没有实现文档中定义的完整的class/method 合约。您可以获得基于地图的完整实现here

【讨论】:

  • 这证实了我的恐惧。除非我自己实现该类,否则我不能拥有一个执行任意映射的 NamespaceContext。但是 XML 流示例至少为我提供了从静态函数创建 NamespaceContext 的路径,该静态函数可以构建微文档,就像您在第一个示例中所做的那样。我会试试的。
  • 是的,没有默认实现有点痛苦。
  • 我认为那是愚蠢的。公共 API 中使用的其他多少 java 接口没有公开可用的具体实现或工厂?
【解决方案2】:

我自己一直在使用 xpath 和 NamespaceContexts。我遇到了一个很好的问题on developerworks

【讨论】:

  • 在阅读了 IBM 的这篇文章后,我可以理解为什么没有默认实现了。有多种方法可以解决这个问题。那就是说:为什么像 Apache 这样的开源组织不创建一个包含不同实现的库?
【解决方案3】:

我在“Apache WebServices Common Utilities”中找到了一个方便的实现,名为 NamespaceContextImpl。

你可以使用下面的maven依赖来获取这个类:

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

我以以下方式使用它(我知道它是为 sax 构建的,但在阅读代码后,它没问题):

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

你不需要调用 endPrefixMapping。

【讨论】:

  • 您仍在对命名空间进行硬编码!
  • 正是我想要的。 javadoc:ws.apache.org/commons/util/apidocs/index.html
  • Meitham - 我没有任何方法可以避免对名称空间进行硬编码。您通过创建此命名空间上下文来引导 XPath 搜索,通过将短前缀映射到文档中包含的每个命名空间 you know 来初始化它,然后在通过 XPath 搜索时,在定义路径时使用这些前缀.
【解决方案4】:

如果您使用的是 Spring 框架,您可以重用它们的 NamespaceContext 实现 org.springframework.util.xml.SimpleNamespaceContext

这是一个类似于 Asaf Mesika 的答案。因此,它不会根据您的文档自动为您提供 NamespaceContext。您必须自己构建它。它仍然对您有所帮助,因为它至少为您提供了一个开始的实现。

当我们遇到类似问题时,spring SimpleNamespaceContext 和“Apache WebServices Common Utilities”都起作用了。我们想避免对 Apache WebServices Common Utilities 的附加 jar 依赖并使用 Spring 之一,因为我们的应用程序是基于 Spring 的。

【讨论】:

    【解决方案5】:

    如果您使用的是Jersey 2,并且只有一个默认的 XML 命名空间 (xmlns="..."),则可以使用 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
    

    您也可以根据需要手动指定xmlns

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-08-07
      • 1970-01-01
      • 1970-01-01
      • 2011-07-24
      • 2014-04-19
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多