使用 JAXP (Java API for XML Processing) 操作 XML 時,常見困惑:修改了記憶體中的 DOM 樹,為何原始 XML 檔案沒變?因為 JAXP 解析器只負責讀取並建立樹狀結構,不會自動寫回磁碟。本文介紹四種同步更新策略,並探討輸出格式化控制。
遞迴遍歷 DOM 節點,逐一寫入 FileOutputStream。缺點極其明顯:需自行處理編碼、轉義、縮排、宣告等 XML 序列化細節,極易出錯,僅適合教學或自寫解析器參考。
JAXP 透過 Pluggability Layer 支援多種解析器(Crimson, Xerces 等)。Crimson 提供 org.apache.crimson.tree.XmlDocument 類別,含 write(OutputStream) 方法可直接序列化 DOM 樹。
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder parser = factory.newDocumentBuilder();
Document doc = parser.parse("user.xml");
// 修改 DOM 樹
Element newElem = doc.createElement("fancy");
doc.getDocumentElement().appendChild(newElem);
// 強制轉型為 Crimson 專屬類別並寫入
((XmlDocument) doc).write(new FileOutputStream("user_updated.xml"));
缺點:程式碼綁定特定解析器實作,違反 JAXP 「面向介面」原則,換解析器需改碼。
JAXP 內建 javax.xml.transform 套件,提供解析器中立的序列化機制:
TransformerFactory tf = TransformerFactory.newInstance();
Transformer transformer = tf.newTransformer();
// 格式化輸出設定
transformer.setOutputProperty(OutputKeys.INDENT, "yes");
transformer.setOutputProperty("{https://xml.apache.org/xslt}indent-amount", "4");
transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
DOMSource source = new DOMSource(doc);
StreamResult result = new StreamResult(new FileOutputStream("user_updated.xml"));
transformer.transform(source, result);
優點:標準 API、支援格式化控制(縮排、編碼、省略 XML 宣告等)、不綁定具體解析器。
編寫一個「身份轉換」 XSLT 樣式表,配合 Transformer 執行,同樣能達到序列化效果,且可在轉換過程中進一步處理節點。
<?xml version="1.0"?>
<xsl:stylesheet version="1.0" xmlns:xsl="https://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes" encoding="UTF-8"/>
<xsl:template match="@*|node()">
<xsl:copy>
<xsl:apply-templates select="@*|node()"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
| 屬性鍵 | 說明 | 典型值 |
|---|---|---|
OutputKeys.INDENT | 是否縮排 | yes / no |
{https://xml.apache.org/xslt}indent-amount | 縮排空格數 (Xalan/Xerces 專屬) | 2, 4 |
OutputKeys.ENCODING | 輸出編碼 | UTF-8, GBK |
OutputKeys.OMIT_XML_DECLARATION | 是否省略 XML 宣告 | yes / no |
OutputKeys.STANDALONE | standalone 屬性 | yes / no |
INDENT=yes 與編碼,避免輸出單行難讀或中文亂碼。XMLStreamWriter) 流式寫入,避免記憶體壓力。