我有一个 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=""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
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:-命名空间中的任何内容,我不知道为什么。
谁能告诉我我在这里做错了什么?
郑重声明,我现在找到了一些有效的方法。我认为这并不理想,但它确实完成了工作。
基本上,我现在所做的就是依赖这样一个事实:一旦拥有节点,我就可以从节点读取 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
非常时髦,但显然这就是它的工作原理。
如果有人能想到一个更干净的解决方案来解决这个混乱,请将其发布在这里。
我认为您的问题源于没有意识到输入文档中的名称空间前缀不必与 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