真實世界的 XML 文檔普遍包含命名空間,直接使用 XPath 查詢往往因前綴不匹配而失敗。本文介紹兩種在 Java 中處理帶命名空間 XML 的 XPath 查詢方案,並針對預設命名空間給出最佳實踐。
<ML:MREML xmlns:ML="MREML">
<EnvelopEntity>
<EnvelopID>GUID2006102000002</EnvelopID>
...
</EnvelopEntity>
</ML:MREML>
local-name() 忽略命名空間XPath 表達式:
//*[local-name()='MREML']/EnvelopEntity/EnvelopID/text()
NamespaceContext 映射前綴與 URIimport javax.xml.namespace.NamespaceContext;
import javax.xml.xpath.*;
import org.w3c.dom.Node;
import java.util.*;
public class XPathUtils {
public static Node evaluateXPath(String expr, Object source, QName returnType) throws Exception {
XPath xpath = XPathFactory.newInstance().newXPath();
xpath.setNamespaceContext(new SimpleNamespaceContext());
return (Node) xpath.evaluate(expr, source, returnType);
}
static class SimpleNamespaceContext implements NamespaceContext {
private final Map map = Map.of(
"ML", "MREML",
"RE", "https://herry.com.cn",
"RID", "ResourceID",
"RUI", "ResourceUniqueID",
"RMDI", "ResourceMetaDataInfo"
);
@Override public String getNamespaceURI(String prefix) { return map.getOrDefault(prefix, ""); }
@Override public String getPrefix(String uri) { return map.entrySet().stream().filter(e -> e.getValue().equals(uri)).map(Map.Entry::getKey).findFirst().orElse(""); }
@Override public Iterator getPrefixes(String uri) { return Collections.emptyIterator(); }
}
}
//ML:MREML/ML:EnvelopEntity/ML:EnvelopID/text()
XML:<MREML xmlns="MREML">...
XPath 1.0 無法直接查詢無前綴的預設命名空間,**必須**在 NamespaceContext 中為其指定一個**自定義前綴**(如 ml):
map.put("ml", "MREML"); // 自定義前綴 → URI
查詢時使用:
//ml:MREML/ml:ResourceEntity/ml:ResourceMetaDataInfo/...
若子元素重新宣告 xmlns:RMDI="ResourceMetaDataInfo",則該子樹下需使用 RMDI: 前綴;若改為預設命名空間 xmlns="ResourceMetaDataInfo",則需另行映射新前綴。
// 查詢 ResourceUniqueID
String xpath = "//ml:MREML/ml:ResourceEntity/rmdi:ResourceMetaDataInfo/rid:ResourceID/rui:ResourceUniqueID/text()";
// 對應 NamespaceContext 映射:
// ml → MREML, rmdi → ResourceMetaDataInfo, rid → ResourceID, rui → ResourceUniqueID
處理帶命名空間 XML 時,**強烈建議使用 NamespaceContext 顯式映射前綴**,避免 local-name() 的歧義與效能問題。預設命名空間務必分配自定義前綴,否則 XPath 1.0 無法匹配。