如何在dom4j的Node对象内搜索指定XPath节点
解决dom4j中获取当前节点子元素的XPath问题
你遇到的问题很典型——// 这个XPath轴是从整个文档的根节点开始递归搜索,所以哪怕你在某个Product节点上调用selectSingleNode("//*[local-name()='ProductID']"),它依然会返回文档中第一个匹配的ProductID,而不是当前节点下的。
两种快速解决方法
1. 使用相对路径的后代轴(.//)
把你的XPath表达式改成从当前节点开始搜索后代:
node.selectSingleNode(".//*[local-name()='ProductID']").getText()
.// 表示"从当前节点的所有后代节点中查找",这样就只会在当前Product节点的子树里找ProductID了。
2. 直接定位直接子节点(更高效)
因为ProductID是Product节点的直接子元素,不需要递归搜索所有后代,可以直接用相对路径定位直接子节点:
// 两种写法等价,./可以省略 node.selectSingleNode("*[local-name()='ProductID']").getText() // 或者更明确的写法 node.selectSingleNode("./*[local-name()='ProductID']").getText()
更规范的方案:注册命名空间
你的XML带有命名空间,一直用local-name()写XPath会很繁琐,推荐注册命名空间后使用前缀,代码更清晰易读:
修改main方法里的代码,在创建SAXReader后注册命名空间:
org.dom4j.io.SAXReader reader = new SAXReader(); // 注册命名空间,前缀ns对应XML里的命名空间URI reader.getDocumentFactory().setXPathNamespaceURIs(Map.of("ns", "http://api.onecommunications.com/pricing_availability/")); Document document = reader.read(new StringReader(mainDocumentXML));
然后后续的XPath都可以用前缀简化:
比如获取Products节点的路径可以改成:
String PRODUCTS_NODE_PATH = "/ns:GetAvailableProductsResponse/ns:GetAvailableProductsResult/ns:Products"; String PRODUCTS_PATH = "/ns:Product";
而获取当前Product节点的ProductID就更简洁了:
node.selectSingleNode("ns:ProductID").getText()
修改后的完整代码示例
package xpath; import java.io.StringReader; import java.util.List; import java.util.Map; import org.dom4j.Document; import org.dom4j.Node; import org.dom4j.io.SAXReader; public class TryXPath { public static void say(String message) { System.out.println(message); } public static String mainDocumentXML = "<GetAvailableProductsResponse xmlns=\"http://api.onecommunications.com/pricing_availability/\">" + " <GetAvailableProductsResult>" + " <Products>" + " <Product>" + " <ProductID>101236004</ProductID>" + " <BaseProductName>Widget1</BaseProductName>" + " </Product>" + " <Product>" + " <ProductID>101236005</ProductID>" + " <BaseProductName>Widget2</BaseProductName>" + " </Product>" + " <Product>" + " <ProductID>101236006</ProductID>" + " <BaseProductName>Widget3</BaseProductName>" + " </Product>" + " </Products>" + " </GetAvailableProductsResult>" + "</GetAvailableProductsResponse>"; public static void main(String[] args) { // 注册命名空间后的XPath更简洁 String PRODUCTS_NODE_PATH = "/ns:GetAvailableProductsResponse/ns:GetAvailableProductsResult/ns:Products"; String PRODUCTS_PATH = "/ns:Product"; try { org.dom4j.io.SAXReader reader = new SAXReader(); // 注册命名空间 reader.getDocumentFactory().setXPathNamespaceURIs(Map.of("ns", "http://api.onecommunications.com/pricing_availability/")); Document document = reader.read(new StringReader(mainDocumentXML)); Node root = document.selectSingleNode(PRODUCTS_NODE_PATH); if (root == null) { say("No root"); } else { List<Node> productsNodeList = root.selectNodes(PRODUCTS_PATH); say("products node list size=" + productsNodeList.size()); for (Node node : productsNodeList) { say("node: " + node.asXML()); say("name: " + node.getName()); // 用命名空间前缀直接获取子节点 say("id: " + node.selectSingleNode("ns:ProductID").getText()); } } } catch (Exception e) { e.printStackTrace(); } } }
这样运行后,就能正确获取每个Product节点对应的ProductID了。
内容的提问来源于stack exchange,提问作者arcy
相关产品推荐
相关产品推荐

