dom4j 是基於 Java 的開源 XML 框架,以介面導向設計、極致易用性、原生 XPath 支援 著稱,效能優於 Sun 官方 DOM 實作。本文涵蓋核心 API、常見操作模式、XPath 查詢、命名空間處理與效能優化建議。
<dependency>
<groupId>org.dom4j</groupId>
<artifactId>dom4j</artifactId>
<version>2.1.4</version> <!-- 最新穩定版 -->
</dependency>
<!-- XPath 需額外引入 jaxen -->
<dependency>
<groupId>jaxen</groupId>
<artifactId>jaxen</artifactId>
<version>1.2.0</version>
</dependency>
核心介面體系:Document、Element、Attribute、Node 皆為介面,實作類由 DocumentFactory 建立,支援可插拔實作。
SAXReader reader = new SAXReader();
// 開啟驗證 (需 XSD/DTD)
reader.setValidation(true);
reader.setEntityResolver(new MyEntityResolver());
Document doc = reader.read(new File("books.xml"));
// 或
Document doc = reader.read(new StringReader(xmlString));
Document doc = reader.read(inputStream);
// 根元素 Element root = doc.getRootElement(); //// 遞迴遍歷 (DOM 風格) void traverse(Element element) { for (Iterator<Element> it = element.elementIterator(); it.hasNext(); ) { Element child = it.next(); System.out.println(child.getName() + " = " + child.getTextTrim()); traverse(child); } } // 或使用 XPath 一次到位 (見下節)
Document doc = DocumentHelper.createDocument();
Element root = doc.addElement("bookstore");
Element book = root.addElement("book")
.addAttribute("category", "JAVA");
book.addElement("title").addAttribute("lang", "zh").setText("Effective Java");
book.addElement("author").setText("Joshua Bloch");
book.addElement("price").setText("59.00");
// 修改
Element title = book.element("title");
title.setText("Effective Java 3rd Edition");
// 刪除
book.element("author").detach(); // 或 title.getParent().remove(title);
// 輸出格式化
OutputFormat fmt = OutputFormat.createPrettyPrint();
fmt.setEncoding("UTF-8");
XMLWriter writer = new XMLWriter(new FileOutputStream("out.xml"), fmt);
writer.write(doc);
writer.close();
| 表達式 | 含義 |
|---|---|
//book | 所有 book 元素 (任意深度) |
/bookstore/book[1] | 第一個 book 子元素 |
//book[@category='WEB'] | category 屬性為 WEB 的 book |
//book/title/@lang | 所有 title 的 lang 屬性值 |
//book[price < 40] | 價格小於 40 的 book |
// 編譯 XPath (可重複使用)
XPath xpath = doc.createXPath("//book[@category='WEB']/title");
// 單節點查詢
Node titleNode = xpath.selectSingleNode(doc);
System.out.println(titleNode.getText()); // "XQuery Kick Start"
// 節點集合查詢
List<Node> titles = xpath.selectNodes(doc);
for (Node n : titles) System.out.println(n.getText());
// 直接取文字內容
String price = doc.valueOf("//book[@category='COOKING']/price"); // "30.00"
// 取屬性值
String lang = doc.valueOf("//book[1]/title/@lang"); // "en"
// 計數
Number count = (Number) xpath.evaluate("count(//book)", doc); // 4.0
XML 若含命名空間,XPath 必須 註冊前綴映射,否則查無結果。
<root xmlns:ns="https://example.com/ns">
<ns:item>value</ns:item>
</root>
Map<String, String> nsMap = new HashMap<>();
nsMap.put("ns", "https://example.com/ns");
XPath xpath = doc.createXPath("//ns:item");
xpath.setNamespaceURIs(nsMap); // 關鍵步驟
Node node = xpath.selectSingleNode(doc);
XPath 物件可重用,避免重複編譯正則。XMLStreamWriter (StAX) 邊生成邊輸出,而非建立完整 Document 樹。reader.setValidation(false) 避免網路下載 DTD 導致卡頓。element.getText() 返回所有子孫文字內容拼接,單純取當前元素文字請用 element.getTextTrim() 或 element.node(0).getText()。detach() 移除節點後,該節點仍可操作但不再屬於樹,若需彻底丟棄請置 null。SAXReader 不支援 XML 1.1,需 reader.setFeature("https://apache.org/xml/features/allow-java-encodings", true)。OutputFormat.setExpandEmptyElements(true) 讓空元素輸出 <tag></tag> 而非 <tag/>,相容舊系統。