在使用 python-docx 時,我們通常使用 add_paragraph() 這種高階指令。但在處理複雜排版(如:插入到特定位置、合併儲存格、處理特殊樣式)時,往往需要「打開引擎蓋」,直接操作底層的 XML 元素。
這篇文章將帶你理解 Word XML 的核心三巨頭:w:p、w:r、w:t。
第一步:準備測試環境
首先,我們執行這段程式碼,生成一個乾淨的 demo_origin.docx 作為手術對象。
# %%
from docx import Document
def create_demo_doc():
doc = Document()
doc.add_heading('原始文件標題', 0)
doc.add_paragraph('這是原始文件的第一段內容。')
doc.add_paragraph('這是原始文件的第二段內容。')
doc.save(r'D:\Temp\demo_origin.docx')
print("測試檔案 'demo_origin.docx' 已生成!")
if __name__ == "__main__":
create_demo_doc()demo_origin.docx:
第二步:實戰演練 —— 三種寫法比較
現在,我們要達成一個目標:在文件的「最開頭」插入一段文字:「這是插入的標題」。
請看以下三種方法的實作與比較:
# %%
from docx import Document
from docx.oxml import OxmlElement
from docx.oxml.ns import qn
# 讀取剛剛生成的檔案
doc = Document(r'D:\Temp\demo_origin.docx')
body = doc.element.body # 取得 XML 的 Body 元素
# =====================================================
# 方法一:高階 API (High-Level) - 最簡單,但只能插在最後
# =====================================================
print("--- 執行方法一 ---")
# 限制:add_paragraph 預設只能 Append 到文件「最後面」
p = doc.add_paragraph('方法一:我是高階 API 產生的段落 (在最後面)')
# =====================================================
# 方法二:底層 XML (Low-Level) - 最靈活,可插在任意位置
# =====================================================
print("--- 執行方法二 ---")
# 這是你在 split_diagnostics.py 看到的寫法
# 我們要手動組裝 XML 結構: <w:p> -> <w:r> -> <w:t>
# 1. 建立段落 (Paragraph)
p_elem = OxmlElement('w:p')
# 2. 建立樣式片段 (Run)
r_elem = OxmlElement('w:r')
# 3. 建立文字節點 (Text)
t_elem = OxmlElement('w:t')
t_elem.text = '方法二:我是底層 XML 組裝的段落 (插在最前面)'
# 4. 層層組裝 (Append)
r_elem.append(t_elem) # 把文字裝進 Run
p_elem.append(r_elem) # 把 Run 裝進 Paragraph
# 5. 插入到 Body 的指定位置 (Index 0 = 最前面)
body.insert(0, p_elem)
# =====================================================
# 方法三:混合技 (Hybrid) - 先建後移 (推薦!)
# =====================================================
print("--- 執行方法三 ---")
# 結合高階的便利與底層的靈活
# 1. 用高階 API 快速建立 (此時它在最後面)
new_p = doc.add_paragraph('方法三:我是混合技產生的段落 (原本在後,被移到前)')
new_p.runs[0].bold = True # 還可以順便設定樣式,超方便
# 2. 取得底層 XML 元素
p_element = new_p._element
# 3. 乾坤大挪移:先移除,再插入到指定位置
body.remove(p_element) # 從尾巴拔出來
body.insert(1, p_element) # 插在 index 1 (剛好在方法二的後面)
# 存檔看結果
doc.save(r'D:\Temp\demo_result.docx')
print("操作完成!請開啟 'demo_result.docx' 查看結果。")程式輸出:
demo_result.docx
第三步:結構解析 (為什麼要這麼麻煩?)
在 方法二 中,我們手動建立了三個物件,這對應了 Word XML 的階層結構:
- w:p (Paragraph) – 段落
對應變數:p_elem = OxmlElement(‘w:p’)
意義:這是文件的一個「塊狀」容器。它包含了對齊方式、縮排、行距等資訊,但不包含文字內容。
高階物件:doc.add_paragraph() 回傳的 Paragraph 物件。 - w:r (Run) – 樣式片段 (連續文字串)
對應變數:r_elem = OxmlElement(‘w:r’)
意義:這是 Word 中最難理解的概念。它是「具有相同格式的一連串文字」。
為什麼需要它?
如果你的一句話裡,前半段是粗體,後半段是斜體,Word 必須把它拆成兩個 Run。
Run 1 (Bold): “前半段”
Run 2 (Italic): “後半段”
高階物件:paragraph.add_run() 回傳的 Run 物件。 - w:t (Text) – 文字內容
對應變數:t_elem = OxmlElement(‘w:t’)
意義:這才是真正存放文字字串的地方。
注意:它必須被包在 w:r 裡面,不能直接放在 w:p 裡。
結構圖解
<w:body> (文件主體)
|
+-- <w:p> (段落 1)
| |
| +-- <w:r> (樣式片段 1: 正常文字)
| | |
| | +-- <w:t> "Hello" </w:t>
| |
| +-- <w:r> (樣式片段 2: 粗體文字)
| |
| +-- <w:rPr><w:b/></w:rPr> (屬性: 粗體)
| +-- <w:t> "World" </w:t>
|
+-- <w:tbl> (表格)...總結
高階 API (add_paragraph)
幫你自動包裝好了 p -> r -> t 的過程,適合一般寫入(預設加在最後面)。
底層 XML (OxmlElement)
當你需要「插入到特定位置」(例如 body.insert(0, …))或修改特殊屬性時使用。
混合技 (Hybrid)
利用高階 API 建立內容,再利用底層 API 移動位置。這是 CP 值最高
的寫法,既有高階的便利,又有底層的靈活。
推薦hahow線上學習python: https://igrape.net/30afN