XPath 是在 XML 文件中導航、查詢節點的語言,類似資料庫的 SQL 或前端的 jQuery 選擇器。DOM4J 內建支援 XPath,配合命名空間處理更顯強大。
Excel 匯出的 XML 常帶有預設命名空間:
<Workbook xmlns="urn:schemas-microsoft-com:office:spreadsheet" ...>
<Worksheet ss:Name="Sheet1">...</Worksheet>
</Workbook>
直接寫 //Workbook/Worksheet/Table/Row[1]/Cell[1]/Data[1] 會查無結果,因為節點實際屬於 urn:schemas-microsoft-com:office:spreadsheet 這個命名空間。
String xpath = "//*[local-name()='Workbook' and namespace-uri()='urn:schemas-microsoft-com:office:spreadsheet']"
+ "/*[local-name()='Worksheet']"
+ "/*[local-name()='Table']"
+ "/*[local-name()='Row'][4]"
+ "/*[local-name()='Cell'][3]"
+ "/*[local-name()='Data'][1]";
List<Element> nodes = doc.selectNodes(xpath);
優點:無需額外設定。缺點:XPath 冗長、難維護。
setNamespaceURIs(Map)Map<String, String> map = new HashMap<>();
map.put("wb", "urn:schemas-microsoft-com:office:spreadsheet");
XPath xpathObj = doc.createXPath("//wb:Row[4]/wb:Cell[3]/wb:Data[1]");
xpathObj.setNamespaceURIs(map);
List<Element> nodes = xpathObj.selectNodes(doc);
setXPathNamespaceURIs(Map)(推薦)SAXReader reader = new SAXReader();
reader.getDocumentFactory().setXPathNamespaceURIs(map); // 同方法二的 map
Document doc = reader.read(inputStream);
// 後續直接用簡潔 XPath
List<Element> nodes = doc.selectNodes("//wb:Row[4]/wb:Cell[3]/wb:Data[1]");
一次設定,全局生效,XPath 乾淨易讀。
同方法三,但 XPath 寫完整路徑 //wb:Worksheet/wb:Table/...,可測試是否影響查詢速度。
// 原生 DOM + XPath
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
dbf.setNamespaceAware(false); // 注意:這裡未處理命名空間,僅示範語法
Document doc = builder.parse(inputStream);
XPath xpath = XPathFactory.newInstance().newXPath();
XPathExpression expr = xpath.compile("//Workbook/Worksheet/Table/Row[4]/Cell[3]/Data[1]");
NodeList nl = (NodeList) expr.evaluate(doc, XPathConstants.NODESET);
實測 DOM4J 通常更快且程式碼更簡潔。
見原文件 TestDom4jXpath.java,包含 read1()~read5() 五種寫法與計時輸出,可直接複製到專案跑測試。
dom4j-2.x.jar 及 jaxen-x.x.jar(缺少會拋 NoClassDefFoundError: org/jaxen/JaxenException)。SAXReader 建立後、read() 前,透過 DocumentFactory 設定命名空間前綴映射。