使用 XPath 在 Java 中的 d:-namespace 中查找 XML 节点

问题描述 投票:0回答:2

我有一个 XML 文件,我想使用 XPath 来处理 Java 中的某个元素。问题是该元素位于 d:- 命名空间中,并且我尝试根据我发现的主题将命名空间添加到 XPath 的所有操作都不起作用。 d:-命名空间是一个遵循不同规则的特殊命名空间吗?

作为参考,这是我正在尝试使用的 XML:

<?xml version="1.0" encoding="utf-8"?>
<feed xml:base="https://company.com/organisation/_api/"
    xmlns="http://www.w3.org/2005/Atom"
    xmlns:d="http://schemas.microsoft.com/ado/2007/08/dataservices"
    xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata">
    <entry m:etag="&quot;3&quot;">
        <id>067d7924-2a19-4094-b588-347b0869a19c</id>
        <content type="application/xml">
            <m:properties>
                <d:Modified m:type="Edm.DateTime">2023-10-06T11:02:47Z</d:Modified>
            </m:properties>
        </content>
    </entry>
    <entry m:etag="&quot;6&quot;">
        <id>c0a9aca5-2a1e-41e5-9da8-95fcd46d3109</id>
        <content type="application/xml">
            <m:properties>
                <d:Modified m:type="Edm.DateTime">2023-10-16T06:46:11Z</d:Modified>
            </m:properties>
        </content>
    </entry>
</feed>

实际上,我首先通过 XPath

XPathNodes
获取两个条目的列表作为
//entries
,然后迭代它们并尝试通过 XPath
//d:Modified
获取修改日期。理论上,这应该可行,但实际上,它总是返回一个空字符串。

我已尝试以下方法将命名空间添加到 XPath,但到目前为止没有任何成功:

选项A(我在其他线程上找到的答案):

        XPathFactory xf = XPathFactory.newInstance();
        XPath xpath = XPathFactory.newInstance().newXPath();
        xpath.setNamespaceContext(new NamespaceContext() {
            @Override
            public String getNamespaceURI(String prefix) {
                if ("d".equals(prefix)) {
                    return "http://schemas.microsoft.com/ado/2007/08/dataservices";
                }
                return null; // Return null for other prefixes
            }

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

            @Override
            public Iterator<String> getPrefixes(String namespaceURI) {
                throw new UnsupportedOperationException();
            }
        });

选项B(我自己尝试过的):

        XPathFactory xf = XPathFactory.newInstance();
        SimpleNamespaceContext namespaceContext = new SimpleNamespaceContext();
        namespaceContext.bindNamespaceUri("d", "http://schemas.microsoft.com/ado/2007/08/dataservices");
        xPath = xf.newXPath();
        xPath.setNamespaceContext(namespaceContext);

选项 C(如果我这样做,我用来获取条目的代码将不再起作用,并且 XPathNodes 包含 0 个条目)

        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
        factory.setNamespaceAware(true);
        DocumentBuilder builder = factory.newDocumentBuilder();

我也尝试通过 XPath

//*[local-name()='Modified']
访问它,但问题是,即使我已经在一个特定条目中,它仍然返回给我 both 条目的修改节点(这让我一开始感到困惑)直到我意识到所有节点显然仍然包含整个文档树)。如果我尝试访问节点内的某些内容,例如 id(通过
//id
),它会很好地工作并仅返回一个正确的节点。它只是不适用于那个奇怪的 d:-命名空间中的任何内容,我不知道为什么。

谁能告诉我我在这里做错了什么?

java xml xpath namespaces
2个回答
0
投票

郑重声明,我现在找到了一些有效的方法。我认为这并不理想,但它确实完成了工作。

基本上,我现在所做的就是依赖这样一个事实:一旦拥有节点,我就可以从节点读取 ID,然后使用该 ID 通过命名空间-忽略-hack 构建完整的 XPath。

整个混乱看起来有点像这样:

public String getTargetNodeModified(XPathNodes entries) {
    Node targetEntry = getTargetNode(entries);
    String targetEntryId = evaluteXpath(latestEntry, "*", String.class);
    String searchString = String.format(
        "//entry[id='%s']//*[local-name()='Modified']",
        targetEntryId
    );
    return evaluteXpath(targetEntry, searchString, String.class);
}

public <T> T evaluteXpath(Object object, String xPathString, Class<T> type) {
    XPathExpression xPathExpression = xPath.compile(xPathString);
    return xPathExpression.evaluateExpression(object, type);
}   

再次,我发现当我基于

//entry[id='%s']
进行搜索时需要添加
targetEntry
非常时髦,但显然这就是它的工作原理。

如果有人能想到一个更干净的解决方案来解决这个混乱,请将其发布在这里。


0
投票

我认为您的问题源于没有意识到输入文档中的名称空间前缀不必与 XPath 表达式中使用的前缀匹配。只要前缀解析为相同的命名空间 URI,一切都应该没问题。这是一个例子:

public static void main(String[] args) throws Exception {
    Document doc = ....

    XPathFactory xpf = XPathFactory.newDefaultInstance();

    XPath xp = xpf.newXPath();
    xp.setNamespaceContext(new MyNamespaceContext());
    String template = "//a:entry[a:id='%s']//b:Modified";
    String expr = String.format(template, "c0a9aca5-2a1e-41e5-9da8-95fcd46d3109");
    Element m = (Element) xp.evaluate(expr, doc, XPathConstants.NODE);
    if (m != null) {
        System.out.println(m.getTextContent());
    }
}

static class MyNamespaceContext implements NamespaceContext {

    private final Map<String, String> mappings = Map.of(
            "a", "http://www.w3.org/2005/Atom",
            "b", "http://schemas.microsoft.com/ado/2007/08/dataservices",
            "c", "http://schemas.microsoft.com/ado/2007/08/dataservices/metadata"
            );

    @Override
    public String getNamespaceURI(String prefix) {
        return mappings.get(prefix);
    }
    
    // other methods returns null
}

针对您的样本打印执行此操作:

2023-10-16T06:46:11Z
© www.soinside.com 2019 - 2024. All rights reserved.