在使用 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
「方法四:相對定位法 (addnext)」。
這個方法特別適合用在「我要插在某個特定段落後面」的情境,例如:插在標題後面、插在表格後面。
以下是更新後的完整教學程式碼,包含了方法四:
# %%
# %%
from docx import Document
from docx.oxml import OxmlElement
# from docx.oxml.ns import qn # 這裡沒用到,先註解掉
def run_demo():
# 讀取剛剛生成的檔案
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("--- 執行方法二 ---")
# 我們要手動組裝 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) - 先建後移 (絕對位置 insert)
# =====================================================
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 (剛好在方法二的後面)
# =====================================================
# 方法四:相對定位法 (Relative) - 使用 addnext 插隊
# =====================================================
print("--- 執行方法四 ---")
# 情境:我想要插在「原始文件標題」的後面
# 1. 先建立要插入的段落 (一樣用高階 API 偷懶)
p4 = doc.add_paragraph('方法四:我是用 addnext 插隊的段落 (在標題後面)')
p4.runs[0].font.italic = True # 設定斜體
p4_elem = p4._p # 取得 XML 元素
# 2. 找到錨點 (Anchor) - 也就是我們要插在誰後面
# 在這個 demo 中,我們知道原本的第一個段落是標題 (Heading 1)
# 因為前面插了 方法二(idx 0) 和 方法三(idx 1),所以原本的標題現在應該是 idx 2
anchor_paragraph = body[2]
# 驗證一下是不是標題 (這行只是為了 demo 顯示用)
# 注意:body[2] 是 XML 元素,要看它的文字要鑽進去 w:t
# 這裡簡單用 doc.paragraphs 來對照 (doc.paragraphs 會自動過濾掉非段落元素)
# 但最保險還是直接操作 XML
# 3. 執行插隊
body.remove(p4_elem) # 一樣要先從尾巴拔出來
anchor_paragraph.addnext(p4_elem) # 叫標題把我們排在他後面
# 存檔看結果
doc.save(r'D:\Temp\demo_result.docx')
print("操作完成!請開啟 'demo_result.docx' 查看結果。")
if __name__ == "__main__":
run_demo()demo_result.docx:

方法四的重點解析
- 錨點 (Anchor):addnext 的核心就是「跟在誰後面」。
- 適用場景:當你不知道絕對索引 (Index),但知道「我要跟著標題走」或「我要跟著表格走」時,這個方法最強大。
- 注意事項:一樣要先 remove 再 addnext,不然會變成複製(或者在某些情況下出錯)。
推薦hahow線上學習python: https://igrape.net/30afN










近期留言