python-docx 進階手術室:從高階 API 到底層 XML (w:p, w:r, w:t) 完全解析; from docx.oxml import OxmlElement ; from docx.oxml.ns import qn

加入好友
加入社群
python-docx 進階手術室:從高階 API 到底層 XML (w:p, w:r, w:t) 完全解析; from docx.oxml import OxmlElement ; from docx.oxml.ns import qn - 儲蓄保險王

在使用 python-docx 時,我們通常使用 add_paragraph() 這種高階指令。但在處理複雜排版(如:插入到特定位置、合併儲存格、處理特殊樣式)時,往往需要「打開引擎蓋」,直接操作底層的 XML 元素。

這篇文章將帶你理解 Word XML 的核心三巨頭:w:p、w:rw: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:

python-docx 進階手術室:從高階 API 到底層 XML (w:p, w:r, w:t) 完全解析; from docx.oxml import OxmlElement ; from docx.oxml.ns import qn - 儲蓄保險王

第二步:實戰演練 —— 三種寫法比較

現在,我們要達成一個目標:在文件的「最開頭」插入一段文字:「這是插入的標題」。

請看以下三種方法的實作與比較:

# %%
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' 查看結果。")

程式輸出:

python-docx 進階手術室:從高階 API 到底層 XML (w:p, w:r, w:t) 完全解析; from docx.oxml import OxmlElement ; from docx.oxml.ns import qn - 儲蓄保險王

demo_result.docx

python-docx 進階手術室:從高階 API 到底層 XML (w:p, w:r, w:t) 完全解析; from docx.oxml import OxmlElement ; from docx.oxml.ns import qn - 儲蓄保險王

第三步:結構解析 (為什麼要這麼麻煩?)

在 方法二 中,我們手動建立了三個物件,這對應了 Word XML 的階層結構:

  1. w:p (Paragraph) – 段落
    對應變數:p_elem = OxmlElement(‘w:p’)
    意義:這是文件的一個「塊狀」容器。它包含了對齊方式、縮排、行距等資訊,但不包含文字內容。
    高階物件:doc.add_paragraph() 回傳的 Paragraph 物件。
  2. w:r (Run) – 樣式片段 (連續文字串)
    對應變數:r_elem = OxmlElement(‘w:r’)
    意義:這是 Word 中最難理解的概念。它是「具有相同格式的一連串文字」
    為什麼需要它?
    如果你的一句話裡,前半段是粗體,後半段是斜體,Word 必須把它拆成兩個 Run。
    Run 1 (Bold): “前半段”
    Run 2 (Italic): “後半段”
    高階物件:paragraph.add_run() 回傳的 Run 物件。
  3. 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:

python-docx 進階手術室:從高階 API 到底層 XML (w:p, w:r, w:t) 完全解析; from docx.oxml import OxmlElement ; from docx.oxml.ns import qn - 儲蓄保險王

方法四的重點解析

  1. 錨點 (Anchor):addnext 的核心就是「跟在誰後面」。
  2. 適用場景:當你不知道絕對索引 (Index),但知道「我要跟著標題走」或「我要跟著表格走」時,這個方法最強大。
  3. 注意事項:一樣要先 remove 再 addnext,不然會變成複製(或者在某些情況下出錯)。

推薦hahow線上學習python: https://igrape.net/30afN

加入好友
加入社群
python-docx 進階手術室:從高階 API 到底層 XML (w:p, w:r, w:t) 完全解析; from docx.oxml import OxmlElement ; from docx.oxml.ns import qn - 儲蓄保險王

儲蓄保險王

儲蓄險是板主最喜愛的儲蓄工具,最喜愛的投資理財工具則是ETF,最喜愛的省錢工具則是信用卡

You may also like...

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *