非命名空间感知DOM中带命名空间XML的XPath异常结果疑问
问题解析:XPath查询带命名空间节点的异常结果
代码复现
Document document = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(new InputSource(new StringReader(""" <ns:Msg xmlns:ns="abc"> <ns:MsgType> <ns:MsgType>TestType</ns:MsgType> </ns:MsgType> <ns:MsgType> <ns:MsgType>TestType</ns:MsgType> </ns:MsgType> </ns:Msg> """))); XPath xPath = XPathFactory.newInstance().newXPath(); NodeList nodelist = (NodeList) xPath.compile("//MsgType/MsgType").evaluate(document, XPathConstants.NODESET); nodelist.getLength(); // => 2 ok nodelist = (NodeList) xPath.compile("//MsgType").evaluate(document, XPathConstants.NODESET); nodelist.getLength(); // => 0 why?
原因分析
核心问题在于DocumentBuilderFactory的namespaceAware属性默认值为false,导致DOM解析器不处理XML命名空间:
- 节点名称处理:所有带前缀的元素(如
<ns:MsgType>)会被解析为节点全名(nodeName)为ns:MsgType的元素,解析器不会拆分前缀ns:和本地名MsgType,因此不存在名为MsgType的节点。 - XPath匹配逻辑:
//MsgType:XPath会查找节点全名**完全等于MsgType**的元素,但实际所有目标节点的全名都是ns:MsgType,因此返回0个节点。//MsgType/MsgType:Java内置XPath实现存在特殊行为——当父节点的全名不匹配时,会尝试匹配节点的本地名(即冒号后的MsgType)。内层的ns:MsgType节点父节点本地名是MsgType,自身本地名也是MsgType,因此被匹配到,返回2个节点。
解决方案
若要正确处理带命名空间的XML,建议开启namespaceAware并配置XPath的命名空间上下文:
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); factory.setNamespaceAware(true); // 开启命名空间支持 Document document = factory.newDocumentBuilder().parse(...); XPath xPath = XPathFactory.newInstance().newXPath(); xPath.setNamespaceContext(new NamespaceContext() { @Override public String getNamespaceURI(String prefix) { if ("ns".equals(prefix)) { return "abc"; } return XMLConstants.NULL_NS_URI; } @Override public String getPrefix(String namespaceURI) { return null; } @Override public Iterator<String> getPrefixes(String namespaceURI) { return null; } }); // 使用带前缀的XPath表达式 NodeList nodelist = (NodeList) xPath.compile("//ns:MsgType").evaluate(document, XPathConstants.NODESET); nodelist.getLength(); // => 4,符合预期
内容的提问来源于stack exchange,提问作者Matthias
相关产品推荐
相关产品推荐

