在根元素的语法中使用名称空间前缀解析 XML - Java

Parse XML using namespace prefixes in syntax for root elements - Java

我有一个 XML 的形式:

<?xml version="1.0" encoding="UTF-8"?>
<semseg:Envelope xmlns:semseg="http://a-random-URL" xmlns="http://another-random-URL">
    <semseg:subject>Subject</semseg:subject>
    <semseg:Sender>
        <semseg:name>Me</semseg:name>
    </semseg:Sender>
    <Triangle>
        <Triangle time='2017-11-29'>
            <Triangle key='a' value='b'/>
            <Triangle key='c' value='d'/>
            <Triangle key='e' value='f'/>
            <Triangle key='g' value='h'/>
        </Triangle>
    </Triangle>
</semseg:Envelope>

我正在尝试检索元素 <Triangle> 不是 <Triangle time='2017-11-29'> - 在此 XML 中元素名称有点重复)使用 XPath。部分代码如下:

DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
documentBuilderFactory.setNamespaceAware(true);
DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder();
Document doc = documentBuilder.parse("file.xml");

XPathFactory xPathFactory = XPathFactory.newInstance();
XPath xPath = xPathFactory.newXPath();
XPathExpression xpr = xPath.compile("/semseg:Envelope/Triangle");
NodeList nodes = (NodeList)xpr.evaluate(doc, XPathConstants.NODESET);

我已经为 XPath 尝试了许多可能的组合,不幸的是没有任何运气,因为没有选择任何元素。尽管如此,使用 this 在线 XPath 检查器和相同的 XML 文件测试相同的 XPath 会产生我正在寻找的结果。它甚至可以使用像

这样的 XPath 进行属性检索
/semseg:Envelope/Triangle/Triangle/@time

名称空间前缀似乎有问题。解析没有任何命名空间前缀的 XMLs 对 XPath.

工作得很好

这对我有用

/\*[local-name()='Envelope']/\*[local-name()='Triangle']/\*[local-name()='Triangle']/@time

您的 XML 输入实际上有两个命名空间。

默认命名空间

第一个是默认的,声明如下:

<semseg:Envelope ... xmlns="http://another-random-URL" ...

作为默认命名空间,任何 XML 上没有命名空间的元素都属于此默认命名空间。

semseg 命名空间

这样定义:

<semseg:Envelope xmlns:semseg="http://a-random-URL" ...

意味着每个 XML 前缀为 semseg 的元素都属于这个命名空间。

翻译您的要求

所以您的目标是一个 XPath 表达式,它将以

为目标
  • 任何 Triangle 元素(无前缀,因此实际上转换为 来自 http://another-random-URL 命名空间 的任何 Triangle 元素)。
  • 这是根 semseg:Enveloppe 元素的直接子元素(实际上转换为属于“http://a-random-URL 的本地名称 Enveloppe 根元素" 命名空间).

在 XPath 中对此进行编程。

我们创建一个 NamespaceContext 来描述我们正在使用的命名空间: 我定义了我希望使用的前缀,并将它们映射到命名空间。这些前缀将由 XPath 引擎使用。我映射:

  • http://a-random-URL 命名空间的 main 前缀
  • http://another-random-URL 命名空间的 secondary 前缀

使用我定义的这个映射,我可以将您的要求转换为这个 XPath:

/main:Envelope/secondary:Triangle

这有效:

XPathFactory xPathFactory = XPathFactory.newInstance();
XPath xPath = xPathFactory.newXPath();
xPath.setNamespaceContext(new NamespaceContext() {
    @Override
    public String getNamespaceURI(String prefix) {
        if ("main".equals(prefix)) {
            return "http://a-random-URL";
        }
        if ("secondary".equals(prefix)) {
            return "http://another-random-URL";
        }
        return null;
    }
    @Override
    public String getPrefix(String namespaceURI) {
        // This should be implemented but I'm lazy and this sample works without it
        return null;
    }

    @Override
    public Iterator getPrefixes(String namespaceURI) {
        // This should be implemented but I'm lazy and this sample works without it
        return null;
    }
});
XPathExpression xpr = xPath.compile("/main:Envelope/secondary:Triangle");
NodeList nodes = (NodeList)xpr.evaluate(doc, XPathConstants.NODESET);
System.out.println(nodes.getLength());

输出:

1

我在这里实现了一个非常愚蠢的命名空间上下文,但是如果你有 Spring 框架、CXF、番石榴(我认为)或其他可用的框架,你通常会有 SimpleNamespaceContextMapBasedNamespaceContext 可能是更好的选择。