docx

docx

熱門

當使用者想要建立、讀取、編輯或操作 Word 文件(.docx 檔案)時使用此技能。觸發條件包括:任何提及「Word 文件」、「word document」、「.docx」,或要求產出具有目錄、標題、頁碼或信頭等格式的專業文件。也可用於從 .docx 檔案中擷取或重新組織內容、在文件中插入或取代圖片、在 Word 檔案中執行尋找與取代、處理追蹤修訂或註解,或將內容轉換為精美的 Word 文件。如果使用者要求以 Word 或 .docx 檔案形式提供「報告」、「備忘錄」、「信件」、「範本」或類似交付物,請使用此技能。請勿用於 PDF、試算表、Google 文件或與文件產生無關的一般程式設計任務。

15萬星標
1.9萬分支
更新於 2026/6/16
SKILL.md
readonlyread-only
name
docx
description

當使用者想要建立、讀取、編輯或操作 Word 文件(.docx 檔案)時使用此技能。觸發條件包括:任何提及「Word 文件」、「word document」、「.docx」,或要求產出具有目錄、標題、頁碼或信頭等格式的專業文件。也可用於從 .docx 檔案中擷取或重新組織內容、在文件中插入或取代圖片、在 Word 檔案中執行尋找與取代、處理追蹤修訂或註解,或將內容轉換為精美的 Word 文件。如果使用者要求以 Word 或 .docx 檔案形式提供「報告」、「備忘錄」、「信件」、「範本」或類似交付物,請使用此技能。請勿用於 PDF、試算表、Google 文件或與文件產生無關的一般程式設計任務。

DOCX 建立、編輯與分析

概述

.docx 檔案是一個包含 XML 檔案的 ZIP 壓縮檔。

快速參考

任務 方法
讀取/分析內容 使用 pandoc 或解壓縮以取得原始 XML
建立新文件 使用 docx-js - 請參閱下方「建立新文件」
編輯現有文件 解壓縮 → 編輯 XML → 重新壓縮 - 請參閱下方「編輯現有文件」

將 .doc 轉換為 .docx

舊版 .doc 檔案必須先轉換才能編輯:

python scripts/office/soffice.py --headless --convert-to docx document.doc

讀取內容

# 含追蹤修訂的文字擷取
pandoc --track-changes=all document.docx -o output.md

# 原始 XML 存取
python scripts/office/unpack.py document.docx unpacked/

轉換為圖片

python scripts/office/soffice.py --headless --convert-to pdf document.docx
pdftoppm -jpeg -r 150 document.pdf page

接受所有追蹤修訂

若要產出已接受所有追蹤修訂的乾淨文件(需安裝 LibreOffice):

python scripts/accept_changes.py input.docx output.docx

建立新文件

使用 JavaScript 產生 .docx 檔案,然後進行驗證。安裝方式:npm install -g docx

設定

const { Document, Packer, Paragraph, TextRun, Table, TableRow, TableCell, ImageRun,
        Header, Footer, AlignmentType, PageOrientation, LevelFormat, ExternalHyperlink,
        InternalHyperlink, Bookmark, FootnoteReferenceRun, PositionalTab,
        PositionalTabAlignment, PositionalTabRelativeTo, PositionalTabLeader,
        TabStopType, TabStopPosition, Column, SectionType,
        TableOfContents, HeadingLevel, BorderStyle, WidthType, ShadingType,
        VerticalAlign, PageNumber, PageBreak } = require('docx');

const doc = new Document({ sections: [{ children: [/* 內容 */] }] });
Packer.toBuffer(doc).then(buffer => fs.writeFileSync("doc.docx", buffer));

驗證

建立檔案後,進行驗證。若驗證失敗,請解壓縮、修正 XML 後重新壓縮。

python scripts/office/validate.py doc.docx

頁面大小

// 重要:docx-js 預設為 A4,非 US Letter
// 務必明確設定頁面大小以確保結果一致
sections: [{
  properties: {
    page: {
      size: {
        width: 12240,   // 8.5 英吋(DXA 單位)
        height: 15840   // 11 英吋(DXA 單位)
      },
      margin: { top: 1440, right: 1440, bottom: 1440, left: 1440 } // 1 英吋邊距
    }
  },
  children: [/* 內容 */]
}]

常見頁面大小(DXA 單位,1440 DXA = 1 英吋):

紙張 寬度 高度 內容寬度(1 英吋邊距)
US Letter 12,240 15,840 9,360
A4(預設) 11,906 16,838 9,026

橫向方向: docx-js 內部會交換寬高,因此傳入直向尺寸,讓它自行處理交換:

size: {
  width: 12240,   // 傳入短邊作為寬度
  height: 15840,  // 傳入長邊作為高度
  orientation: PageOrientation.LANDSCAPE  // docx-js 會在 XML 中交換它們
},
// 內容寬度 = 15840 - 左邊距 - 右邊距(使用長邊)

樣式(覆蓋內建標題)

使用 Arial 作為預設字型(通用支援)。標題保持黑色以利閱讀。

const doc = new Document({
  styles: {
    default: { document: { run: { font: "Arial", size: 24 } } }, // 12pt 預設
    paragraphStyles: [
      // 重要:使用確切 ID 來覆蓋內建樣式
      { id: "Heading1", name: "Heading 1", basedOn: "Normal", next: "Normal", quickFormat: true,
        run: { size: 32, bold: true, font: "Arial" },
        paragraph: { spacing: { before: 240, after: 240 }, outlineLevel: 0 } }, // TOC 需要 outlineLevel
      { id: "Heading2", name: "Heading 2", basedOn: "Normal", next: "Normal", quickFormat: true,
        run: { size: 28, bold: true, font: "Arial" },
        paragraph: { spacing: { before: 180, after: 180 }, outlineLevel: 1 } },
    ]
  },
  sections: [{
    children: [
      new Paragraph({ heading: HeadingLevel.HEADING_1, children: [new TextRun("標題")] }),
    ]
  }]
});

清單(絕對不要使用 Unicode 項目符號)

// ❌ 錯誤 - 絕對不要手動插入項目符號字元
new Paragraph({ children: [new TextRun("• 項目")] })  // 錯誤
new Paragraph({ children: [new TextRun("\u2022 項目")] })  // 錯誤

// ✅ 正確 - 使用搭配 LevelFormat.BULLET 的編號設定
const doc = new Document({
  numbering: {
    config: [
      { reference: "bullets",
        levels: [{ level: 0, format: LevelFormat.BULLET, text: "•", alignment: AlignmentType.LEFT,
          style: { paragraph: { indent: { left: 720, hanging: 360 } } } }] },
      { reference: "numbers",
        levels: [{ level: 0, format: LevelFormat.DECIMAL, text: "%1.", alignment: AlignmentType.LEFT,
          style: { paragraph: { indent: { left: 720, hanging: 360 } } } }] },
    ]
  },
  sections: [{
    children: [
      new Paragraph({ numbering: { reference: "bullets", level: 0 },
        children: [new TextRun("項目符號項目")] }),
      new Paragraph({ numbering: { reference: "numbers", level: 0 },
        children: [new TextRun("編號項目")] }),
    ]
  }]
});

// ⚠️ 每個 reference 會建立獨立的編號
// 相同 reference = 連續編號(1,2,3 接著 4,5,6)
// 不同 reference = 重新編號(1,2,3 接著 1,2,3)

表格

重要:表格需要雙重寬度 - 同時設定表格的 columnWidths 和每個儲存格的 width。若缺少任一項,表格在某些平台上會顯示錯誤。

// 重要:務必設定表格寬度以確保一致呈現
// 重要:使用 ShadingType.CLEAR(非 SOLID)以避免黑色背景
const border = { style: BorderStyle.SINGLE, size: 1, color: "CCCCCC" };
const borders = { top: border, bottom: border, left: border, right: border };

new Table({
  width: { size: 9360, type: WidthType.DXA }, // 一律使用 DXA(百分比在 Google 文件中會失效)
  columnWidths: [4680, 4680], // 必須加總等於表格寬度(DXA:1440 = 1 英吋)
  rows: [
    new TableRow({
      children: [
        new TableCell({
          borders,
          width: { size: 4680, type: WidthType.DXA }, // 也設定在每個儲存格上
          shading: { fill: "D5E8F0", type: ShadingType.CLEAR }, // 使用 CLEAR 而非 SOLID
          margins: { top: 80, bottom: 80, left: 120, right: 120 }, // 儲存格內距(內部,不加入寬度)
          children: [new Paragraph({ children: [new TextRun("儲存格")] })]
        })
      ]
    })
  ]
})

表格寬度計算:

一律使用 WidthType.DXAWidthType.PERCENTAGE 在 Google 文件中會失效。

// 表格寬度 = columnWidths 總和 = 內容寬度
// US Letter 搭配 1 英吋邊距:12240 - 2880 = 9360 DXA
width: { size: 9360, type: WidthType.DXA },
columnWidths: [7000, 2360]  // 必須加總等於表格寬度

寬度規則:

  • 一律使用 WidthType.DXA — 絕對不要用 WidthType.PERCENTAGE(與 Google 文件不相容)
  • 表格寬度必須等於 columnWidths 的總和
  • 儲存格 width 必須對應 columnWidth
  • 儲存格 margins 是內部內距 - 會減少內容區域,不會增加儲存格寬度
  • 全寬表格:使用內容寬度(頁面寬度減去左右邊距)

圖片

// 重要:type 參數為必填
new Paragraph({
  children: [new ImageRun({
    type: "png", // 必填:png, jpg, jpeg, gif, bmp, svg
    data: fs.readFileSync("image.png"),
    transformation: { width: 200, height: 150 },
    altText: { title: "標題", description: "描述", name: "名稱" } // 三者皆為必填
  })]
})

分頁符號

// 重要:PageBreak 必須放在 Paragraph 內
new Paragraph({ children: [new PageBreak()] })

// 或使用 pageBreakBefore
new Paragraph({ pageBreakBefore: true, children: [new TextRun("新頁面")] })

超連結

// 外部連結
new Paragraph({
  children: [new ExternalHyperlink({
    children: [new TextRun({ text: "點此前往", style: "Hyperlink" })],
    link: "https://example.com",
  })]
})

// 內部連結(書籤 + 參照)
// 1. 在目標位置建立書籤
new Paragraph({ heading: HeadingLevel.HEADING_1, children: [
  new Bookmark({ id: "chapter1", children: [new TextRun("第一章")] }),
]})
// 2. 連結到它
new Paragraph({ children: [new InternalHyperlink({
  children: [new TextRun({ text: "參見第一章", style: "Hyperlink" })],
  anchor: "chapter1",
})]})

註腳

const doc = new Document({
  footnotes: {
    1: { children: [new Paragraph("來源:2024 年度報告")] },
    2: { children: [new Paragraph("詳見附錄的方法說明")] },
  },
  sections: [{
    children: [new Paragraph({
      children: [
        new TextRun("營收成長 15%"),
        new FootnoteReferenceRun(1),
        new TextRun(" 使用調整後指標"),
        new FootnoteReferenceRun(2),
      ],
    })]
  }]
});

定位點

// 在同一行靠右對齊文字(例如日期與標題相對)
new Paragraph({
  children: [
    new TextRun("公司名稱"),
    new TextRun("\t2025 年 1 月"),
  ],
  tabStops: [{ type: TabStopType.RIGHT, position: TabStopPosition.MAX }],
})

// 點線引導(例如目錄樣式)
new Paragraph({
  children: [
    new TextRun("簡介"),
    new TextRun({ children: [
      new PositionalTab({
        alignment: PositionalTabAlignment.RIGHT,
        relativeTo: PositionalTabRelativeTo.MARGIN,
        leader: PositionalTabLeader.DOT,
      }),
      "3",
    ]}),
  ],
})

多欄版面

// 等寬欄位
sections: [{
  properties: {
    column: {
      count: 2,          // 欄數
      space: 720,        // 欄間距(DXA,720 = 0.5 英吋)
      equalWidth: true,
      separate: true,    // 欄間垂直線
    },
  },
  children: [/* 內容會自然跨欄流動 */]
}]

// 自訂寬度欄位(equalWidth 必須為 false)
sections: [{
  properties: {
    column: {
      equalWidth: false,
      children: [
        new Column({ width: 5400, space: 720 }),
        new Column({ width: 3240 }),
      ],
    },
  },
  children: [/* 內容 */]
}]

使用 type: SectionType.NEXT_COLUMN 的新區段來強制分欄。

目錄

// 重要:標題必須僅使用 HeadingLevel - 不可使用自訂樣式
new TableOfContents("目錄", { hyperlink: true, headingStyleRange: "1-3" })

頁首/頁尾

sections: [{
  properties: {
    page: { margin: { top: 1440, right: 1440, bottom: 1440, left: 1440 } } // 1440 = 1 英吋
  },
  headers: {
    default: new Header({ children: [new Paragraph({ children: [new TextRun("頁首")] })] })
  },
  footers: {
    default: new Footer({ children: [new Paragraph({
      children: [new TextRun("第 "), new TextRun({ children: [PageNumber.CURRENT] }), new TextRun(" 頁")]
    })] })
  },
  children: [/* 內容 */]
}]

docx-js 的重要規則

  • 明確設定頁面大小 - docx-js 預設為 A4;美國文件請使用 US Letter(12240 x 15840 DXA)
  • 橫向:傳入直向尺寸 - docx-js 內部會交換寬高;傳入短邊作為 width,長邊作為 height,並設定 orientation: PageOrientation.LANDSCAPE
  • 絕對不要使用 \n - 請使用獨立的 Paragraph 元素
  • 絕對不要使用 Unicode 項目符號 - 請使用搭配編號設定的 LevelFormat.BULLET
  • PageBreak 必須放在 Paragraph 內 - 獨立使用會產生無效的 XML
  • ImageRun 需要 type - 務必指定 png/jpg 等
  • 務必使用 DXA 設定表格 width - 絕對不要使用 WidthType.PERCENTAGE(在 Google 文件中會失效)
  • 表格需要雙重寬度 - columnWidths 陣列和儲存格 width,兩者必須一致
  • 表格寬度 = columnWidths 總和 - 使用 DXA 時,確保它們完全相加
  • 務必加入儲存格邊距 - 使用 margins: { top: 80, bottom: 80, left: 120, right: 120 } 以獲得可讀的內距
  • 使用 ShadingType.CLEAR - 表格陰影絕對不要用 SOLID
  • 絕對不要使用表格作為分隔線/標尺 - 儲存格有最小高度,會顯示為空白方塊(包括在頁首/頁尾中);請改用 Paragraph 上的 border: { bottom: { style: BorderStyle.SINGLE, size: 6, color: "2E75B6", space: 1 } }。對於雙欄頁尾,請使用定位點(參見定位點章節),而非表格
  • TOC 僅需 HeadingLevel - 標題段落上不可使用自訂樣式
  • 覆蓋內建樣式 - 使用確切 ID:"Heading1"、"Heading2" 等
  • 包含 outlineLevel - TOC 需要此屬性(H1 為 0,H2 為 1,依此類推)

編輯現有文件

請依序執行以下 3 個步驟。

步驟 1:解壓縮

python scripts/office/unpack.py document.docx unpacked/

會擷取 XML、美化排版、合併相鄰的 run,並將智慧型引號轉換為 XML 實體(“ 等),以便在編輯時保留。使用 --merge-runs false 可跳過 run 合併。

步驟 2:編輯 XML

編輯 unpacked/word/ 中的檔案。請參閱下方 XML 參考以了解模式。

對於追蹤修訂和註解,請使用「Claude」作為作者,除非使用者明確要求使用其他名稱。

直接使用編輯工具進行字串取代。請勿撰寫 Python 指令碼。 指令碼會帶來不必要的複雜性。編輯工具會清楚顯示被取代的內容。

重要:新內容請使用智慧型引號。 在新增含有撇號或引號的文字時,請使用 XML 實體來產生智慧型引號:

<!-- 使用這些實體以獲得專業排版 -->
<w:t>這裡&#x2019;s 一個引號:&#x201C;你好&#x201D;</w:t>
實體 字元
&#x2018; '(左單引號)
&#x2019; '(右單引號/撇號)
&#x201C; "(左雙引號)
&#x201D; "(右雙引號)

新增註解: 使用 comment.py 來處理跨多個 XML 檔案的樣板(文字必須是預先跳脫的 XML):

python scripts/comment.py unpacked/ 0 "含有 &amp; 和 &#x2019; 的註解文字"
python scripts/comment.py unpacked/ 1 "回覆文字" --parent 0  # 回覆註解 0
python scripts/comment.py unpacked/ 0 "文字" --author "自訂作者"  # 自訂作者名稱

然後在 document.xml 中加入標記(請參閱 XML 參考中的註解)。

步驟 3:壓縮

python scripts/office/pack.py unpacked/ output.docx --original document.docx

會進行驗證並自動修復、壓縮 XML,然後建立 DOCX。使用 --validate false 可跳過驗證。

自動修復會修正:

  • durableId >= 0x7FFFFFFF(重新產生有效 ID)
  • 含有空白字元的 <w:t> 缺少 xml:space="preserve"

自動修復不會修正:

  • 格式錯誤的 XML、無效的元素巢狀結構、缺少的關係、違反結構描述

常見陷阱

  • 取代整個 <w:r> 元素:在新增追蹤修訂時,將整個 <w:r>...</w:r> 區塊取代為同層級的 <w:del>...<w:ins>...。請勿在 run 內部插入追蹤修訂標籤。
  • 保留 <w:rPr> 格式:將原始 run 的 <w:rPr> 區塊複製到您的追蹤修訂 run 中,以維持粗體、字型大小等。

XML 參考

結構描述遵循

  • <w:pPr> 中的元素順序<w:pStyle><w:numPr><w:spacing><w:ind><w:jc>、最後是 <w:rPr>
  • 空白字元:在含有前導/尾隨空白的 <w:t> 中加入 xml:space="preserve"
  • RSID:必須是 8 位十六進位(例如 00AB1234

追蹤修訂

插入:

<w:ins w:id="1" w:author="Claude" w:date="2025-01-01T00:00:00Z">
  <w:r><w:t>插入的文字</w:t></w:r>
</w:ins>

刪除:

<w:del w:id="2" w:author="Claude" w:date="2025-01-01T00:00:00Z">
  <w:r><w:delText>刪除的文字</w:delText></w:r>
</w:del>

<w:del> 內部:使用 <w:delText> 取代 <w:t>,使用 <w:delInstrText> 取代 <w:instrText>

最小化編輯 - 僅標記變更的部分:

<!-- 將「30 天」改為「60 天」 -->
<w:r><w:t>期限為 </w:t></w:r>
<w:del w:id="1" w:author="Claude" w:date="...">
  <w:r><w:delText>30</w:delText></w:r>
</w:del>
<w:ins w:id="2" w:author="Claude" w:date="...">
  <w:r><w:t>60</w:t></w:r>
</w:ins>
<w:r><w:t> 天。</w:t></w:r>

刪除整個段落/清單項目 - 當移除段落中的所有內容時,也請將段落標記標示為已刪除,以便與下一個段落合併。在 <w:pPr><w:rPr> 內部加入 <w:del/>

<w:p>
  <w:pPr>
    <w:numPr>...</w:numPr>  <!-- 清單編號(若存在) -->
    <w:rPr>
      <w:del w:id="1" w:author="Claude" w:date="2025-01-01T00:00:00Z"/>
    </w:rPr>
  </w:pPr>
  <w:del w:id="2" w:author="Claude" w:date="2025-01-01T00:00:00Z">
    <w:r><w:delText>正在刪除的整個段落內容...</w:delText></w:r>
  </w:del>
</w:p>

若未在 <w:pPr><w:rPr> 中加入 <w:del/>,接受修訂後會留下空白段落/清單項目。

拒絕另一位作者的插入 - 將刪除巢狀在他們的插入內部:

<w:ins w:author="Jane" w:id="5">
  <w:del w:author="Claude" w:id="10">
    <w:r><w:delText>他們插入的文字</w:delText></w:r>
  </w:del>
</w:ins>

還原另一位作者的刪除 - 在刪除之後加入插入(請勿修改他們的刪除):

<w:del w:author="Jane" w:id="5">
  <w:r><w:delText>刪除的文字</w:delText></w:r>
</w:del>
<w:ins w:author="Claude" w:id="10">
  <w:r><w:t>刪除的文字</w:t></w:r>
</w:ins>

註解

執行 comment.py 後(參見步驟 2),在 document.xml 中加入標記。對於回覆,請使用 --parent 旗標,並將標記巢狀在父註解內部。

重要:<w:commentRangeStart><w:commentRangeEnd><w:r> 的同層級元素,絕對不能放在 <w:r> 內部。

<!-- 註解標記是 w:p 的直接子元素,絕不在 w:r 內部 -->
<w:commentRangeStart w:id="0"/>
<w:del w:id="1" w:author="Claude" w:date="2025-01-01T00:00:00Z">
  <w:r><w:delText>已刪除</w:delText></w:r>
</w:del>
<w:r><w:t> 更多文字</w:t></w:r>
<w:commentRangeEnd w:id="0"/>
<w:r><w:rPr><w:rStyle w:val="CommentReference"/></w:rPr><w:commentReference w:id="0"/></w:r>

<!-- 註解 0 內部巢狀回覆 1 -->
<w:commentRangeStart w:id="0"/>
  <w:commentRangeStart w:id="1"/>
  <w:r><w:t>文字</w:t></w:r>
  <w:commentRangeEnd w:id="1"/>
<w:commentRangeEnd w:id="0"/>
<w:r><w:rPr><w:rStyle w:val="CommentReference"/></w:rPr><w:commentReference w:id="0"/></w:r>
<w:r><w:rPr><w:rStyle w:val="CommentReference"/></w:rPr><w:commentReference w:id="1"/></w:r>

圖片

  1. 將圖片檔案加入 word/media/
  2. word/_rels/document.xml.rels 中加入關係:
<Relationship Id="rId5" Type=".../image" Target="media/image1.png"/>
  1. [Content_Types].xml 中加入內容類型:
<Default Extension="png" ContentType="image/png"/>
  1. 在 document.xml 中參照:
<w:drawing>
  <wp:inline>
    <wp:extent cx="914400" cy="914400"/>  <!-- EMU:914400 = 1 英吋 -->
    <a:graphic>
      <a:graphicData uri=".../picture">
        <pic:pic>
          <pic:blipFill><a:blip r:embed="rId5"/></pic:blipFill>
        </pic:pic>
      </a:graphicData>
    </a:graphic>
  </wp:inline>
</w:drawing>

相依套件

  • pandoc:文字擷取
  • docxnpm install -g docx(新文件)
  • LibreOffice:PDF 轉換(透過 scripts/office/soffice.py 自動設定以適用於沙箱環境)
  • Popplerpdftoppm 用於圖片