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

Find XML node in d:-namespace in Java using XPath

提问人:Kira Resari 提问时间:10/20/2023 最后编辑:Kira Resari 更新时间:10/23/2023 访问量:40

问:

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

作为参考,以下是我尝试使用的 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 获取两个条目的列表,然后遍历它们并尝试通过 XPath 获取修改日期。从理论上讲,这应该有效,但在实践中,它总是返回一个空字符串。XPathNodes/feed/entry//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 访问它,但问题是,即使我已经在一个特定条目中,它仍然会向我返回两个条目的 Modified 节点(起初这让我感到困惑,直到我意识到所有节点显然仍然包含整个文档树)。如果我尝试访问节点内的某些内容,例如 id (via ),它运行良好并仅返回一个正确的节点。它只是不适用于那个奇怪的 d:-namespace 中的任何东西,我不知道为什么。//*[local-name()='Modified']//id

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


编辑:

澄清一下,我的目标是找到所有条目,遍历它们并获取它们的“修改”日期,以便我可以使用它。从本质上讲,这就是我想象的样子,但我尝试过,但它不起作用,迫使我使用节点 ID 和命名空间中断的解决方法。

Node getMostRecentNode(){
    Document document = getDocument();
    XPathNodes entries = evaluteXpath(itemsDocument, "/feed/entry", XPathNodes.class);
    for (int index = 1; index < entries.size(); index++) {
        Node entry = entries.get(index);
        String modifiedString = evaluteXpath(entry, "//d:Modified", String.class);
        [...logic for getting most recent node ...]
    }
}

如果我的命名空间正确,这是否可以工作?还是我在这个阶段对 XPath 工作方式的理解已经存在错误?

Java XML XPath 命名空间

评论

0赞 Martin Honnen 10/20/2023
我宁愿认为您忽略了根元素上的默认命名空间声明,该声明也在您的尝试范围内,因为它没有选择任何命名空间中的元素。xmlns="http://www.w3.org/2005/Atom"entry//entryentry
0赞 Martin Honnen 10/20/2023
当然,问题在于它浏览了整个上下文文档,所以你想要或者,如果你喜欢那个技巧。//*[local-name()='Modified'].//d:Modified.//*[local-name() = 'Modified']
0赞 Kira Resari 10/20/2023
@MartinHonnen :我尝试添加默认命名空间,但也许我做错了。你能给我举个例子,说明在这种情况下会是什么样子吗?整个命名空间的事情对我来说非常令人困惑。

答:

0赞 Kira Resari 10/20/2023 #1

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

基本上,我现在所做的是依赖于这样一个事实,即一旦我拥有了 ID,我就可以从节点中读取它,然后使用该 ID 通过 namespace-ignore-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

如果有人能想出一个更干净的解决方案来解决这个烂摊子,请在这里发布。


编辑:

感谢 @Michael Kay 在下面的评论之一,我现在能够将其简化为:

public String getTargetNodeModified(XPathNodes entries) {
    Node targetEntry = getTargetNode(entries);
    return evaluteXpath(targetEntry, ".//*[local-name()='Modified']", String.class);
}

评论

1赞 Michael Kay 10/20/2023
您应该能够使用String searchString = ".//*[local-name()='Modified']";
0赞 Michael Kay 10/20/2023
事实上,你应该能够使用String searchString = ".//d:Modified";
0赞 Michael Kay 10/20/2023
但是我不期望选择任何东西(因为元素在命名空间中),我当然也不期望选择任何东西(因为没有这样的元素)。我怀疑在你的挫败感中,你犯了一些小错误:试着在上面过夜。//entry//entries
0赞 Kira Resari 10/23/2023
@MichaelKay我在帖子中犯了一个错误,则用于获取条目的 XPath 是 ,这对于获取所有条目都很好。但是,XPath 始终返回空结果。同时,XPath 似乎可以工作,并且无需搜索 entryId,所以谢谢你!=^,^=/feed/entry.//d:Modified".//*[local-name()='Modified']"
0赞 forty-two 10/21/2023 #2

我认为您的问题源于没有意识到输入文档中的命名空间前缀不必与 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

评论

0赞 Kira Resari 10/23/2023
但这仍然不能消除对 ID 的需求。 理想情况下,我想要搜索的内容就像我遍历节点一样。我将编辑我的问题以明确这一点。我尝试用 然后搜索 调整我的代码,但没有找到任何东西,当我调整迭代器以用于搜索条目时,它不再找到任何条目。//d:ModifiedMyNamespaceContext//b:Modified/a:feed/a:entry