
docx
熱門當使用者想要建立、讀取、編輯或操作 Word 文件(.docx 檔案)時使用此技能。觸發條件包括:任何提及「Word 文件」、「word document」、「.docx」,或要求產出具有目錄、標題、頁碼或信頭等格式的專業文件。也可用於從 .docx 檔案中擷取或重新組織內容、在文件中插入或取代圖片、在 Word 檔案中執行尋找與取代、處理追蹤修訂或註解,或將內容轉換為精美的 Word 文件。如果使用者要求以 Word 或 .docx 檔案形式提供「報告」、「備忘錄」、「信件」、「範本」或類似交付物,請使用此技能。請勿用於 PDF、試算表、Google 文件或與文件產生無關的一般程式設計任務。
當使用者想要建立、讀取、編輯或操作 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.DXA — WidthType.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>這裡’s 一個引號:“你好”</w:t>
| 實體 | 字元 |
|---|---|
‘ |
'(左單引號) |
’ |
'(右單引號/撇號) |
“ |
"(左雙引號) |
” |
"(右雙引號) |
新增註解: 使用 comment.py 來處理跨多個 XML 檔案的樣板(文字必須是預先跳脫的 XML):
python scripts/comment.py unpacked/ 0 "含有 & 和 ’ 的註解文字"
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>
圖片
- 將圖片檔案加入
word/media/ - 在
word/_rels/document.xml.rels中加入關係:
<Relationship Id="rId5" Type=".../image" Target="media/image1.png"/>
- 在
[Content_Types].xml中加入內容類型:
<Default Extension="png" ContentType="image/png"/>
- 在 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:文字擷取
- docx:
npm install -g docx(新文件) - LibreOffice:PDF 轉換(透過
scripts/office/soffice.py自動設定以適用於沙箱環境) - Poppler:
pdftoppm用於圖片





