Watch at Ease看見世界,讀懂生活。

科技 · 實用指南

minidom 不斷增加空行?刪空白前先驗證正文未改變

XML 外觀更整齊,文字卻可能不同。重現儲存循環、核對 Python 版本,再比較語義內容後取代原檔。

XML 空白改變正文的原創示意圖
原創示意圖:解析成功仍可能掩蓋新增換行及被刪除的刻意空行。 Watch at Ease

不要靠刪除所有空行修復持續變大的 XML。先證明哪些空白可捨棄,並確認目前序列化器是否改變正文;檔案仍能解析,不代表含義未流失。

1. 保留儲存循環與執行環境

保留原檔及可丟棄副本。記錄 sys.version、解析器、序列化呼叫、indent/newl 參數,以及輸入是否已有縮排。不進行編輯,重複相同的解析、編輯、序列化流程;若輸出仍增加,問題指向格式化,而非新增元素。

我們的 CPython 3.12.14 樣本含已有縮排的根節點與一個 item。第一次 toprettyxml(indent=' ') 後有 4 個空白行,重新解析並輸出後有 8 個。這些數字只屬於這個小樣本,不適用所有 XML。

2. 把空白視為可能的正文

混合內容樣本 <p>Hello <b>world</b>!</p> 的後代文字從 Hello world! 變成含新增換行與縮排的文字。另一組 <p><b>A</b> <b>B</b></p> 也改變了原本一個空格的分隔。只計算標籤或確認可解析,皆會漏掉這兩種變化。

含刻意空行、帶 xml:space='preserve' 的樣本在 3.12.14 仍被重新排版;以 if line.strip() 篩選美化輸出又刪除了該空行。W3C 將 preserve 定義為向後代繼承、除非被覆寫的保留意圖。這不能證明每個已安裝的美化器都實作了該意圖。

3. 選擇有明確邊界的修復

在已測版本中,若無法確定空白含義,可用 toxml(encoding='utf-8') 序列化編輯後的 DOM,將位元組寫到新檔。它避免新增美化縮排,但不會刪除已有空白,也無法復原先前儲存時改掉的內容。4 組樣本皆保留解析後的後代文字,第二次解析並 toxml 產生相同序列化結果。

將排版閱讀副本與儲存文件分開。只有文件內容模型或應用程式約定證明某些節點的排版可忽略時,才刪除那些節點。lxml FAQ 明確說明,缺乏確定知識時 remove_blank_text 使用啟發式判斷;換函式庫不會自動證明內容保留。

4. 依含義驗收,而非外觀

Python 3.16 開發文件記錄了混合內容與 xml:space='preserve' 處理變更。不要把該說明當成 3.12 的行為,也別以為升級能復原已改變的文字。我們只執行了 3.12.14;預計改用哪個版本,就在該確切版本重跑代表樣本。

取代原檔前,重新解析候選檔,比較有序元素、屬性及每段文字,包含受保護或混合內容裡純空白的文字段;僅允許明確核准的排版例外。再測第二次儲存及實際讀取的應用程式,保留備份。toxml 不保證原位元組不變,也不是正規化或簽章、實體寫法及全部文件特性的保證。這些可信合成樣本並非不可信 XML 的安全處理方案。

XML 兩次儲存驗收原創示意圖
原創示意圖:核對執行版本、完整文字段及第二次儲存結果。 Watch at Ease