提问人:Kira Resari 提问时间:10/20/2023 最后编辑:Kira Resari 更新时间:10/23/2023 访问量:40
使用 XPath 在 Java 的 d:-namespace 中查找 XML 节点
Find XML node in d:-namespace in Java using XPath
问:
我有一个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=""3"">
<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=""6"">
<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 工作方式的理解已经存在错误?
答:
郑重声明,我现在找到了一些有效的东西。我不认为这是理想的,但它确实完成了工作。
基本上,我现在所做的是依赖于这样一个事实,即一旦我拥有了 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);
}
评论
String searchString = ".//*[local-name()='Modified']";
String searchString = ".//d:Modified";
//entry
//entries
/feed/entry
.//d:Modified
".//*[local-name()='Modified']"
我认为您的问题源于没有意识到输入文档中的命名空间前缀不必与 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
评论
//d:Modified
MyNamespaceContext
//b:Modified
/a:feed/a:entry
上一个:为什么我不能添加库?
下一个:有条件地扩展接口
评论
xmlns="http://www.w3.org/2005/Atom"
entry
//entry
entry
//*[local-name()='Modified']
.//d:Modified
.//*[local-name() = 'Modified']