| name | docx |
| description | Use this skill whenever the user wants to create, read, edit, or manipulate Word documents (.docx files). Triggers include: any mention of 'Word doc', 'word document', '.docx', or requests to produce professional documents with formatting like tables of contents, headings, page numbers, or letterheads. Also use when extracting or reorganizing content from .docx files, inserting or replacing images in documents, performing find-and-replace in Word files, working with tracked changes or comments, or converting content into a polished Word document. If the user asks for a 'report', 'memo', 'letter', 'template', or similar deliverable as a Word or .docx file, use this skill. Do NOT use for PDFs, spreadsheets, Google Docs, or general coding tasks unrelated to document generation. |
| license | Proprietary. LICENSE.txt has complete terms |
DOCX creation, editing, and analysis
本系统无 shell/Python/Node 环境。所有操作通过内置工具完成:
office_read_to_markdown、ooxml_unpack、ooxml_pack、docx_comment、docx_accept_changes、skill_run(docx 库已内置)。skill_run API 见 skill_read {"skill":"runtime"}。
Overview
A .docx file is a ZIP archive containing XML files.
Quick Reference
| Task | Tool |
|---|
| Read/analyze content | office_read_to_markdown {"path": "doc.docx"} |
| Raw XML access | ooxml_unpack {"path": "doc.docx"}(省略 out_dir,用返回路径) |
| Create new document | skill_run + docx 库 — see Creating New Documents below |
| 含数学公式 | 先 skill_read {"skill":"docx","doc":"math.md"},再 skill_run(Math / MathRun / MathFraction 等) |
| Edit existing document | 先 skill_read {"skill":"docx","doc":"editing.md"},再 ooxml_unpack → edit XML → ooxml_pack |
| Accept tracked changes | docx_accept_changes {"path": "in.docx", "out_path": "clean.docx"} |
| Add comments | docx_comment(需先 unpack,见 editing.md) |
| Extract tables to CSV | docx_extract_table {"path": "doc.docx", "out_dir": "tables/"} |
旧格式 .doc
默认:不转换。 能用读取完成的,不要新建 -converted 文件。
| 任务 | 工具 | 是否新建文件 |
|---|
| 阅读、摘要、提取内容 | office_read_to_markdown {"path": "memo.doc"} | 否 |
| 提取表格 | docx_extract_table 仅支持 .docx;旧格式须先读内容或见下方转换 | — |
| OOXML 解包编辑、从零生成 docx | 须先 office_convert → memo-converted.docx,再 ooxml_unpack 或 skill_run | 是 |
仅在必要时调用 office_convert(用户明确要求 .docx、或下游工具只认 OOXML)。转换可能丢失版式/样式(实测可完成但格式不保真);输出文件名含 -converted 后缀,如 memo-converted.docx。
不支持的操作
- 渲染为图片/PDF 视觉校验:无 LibreOffice;降级为
ooxml_pack 自动校验 + office_read_to_markdown 文本自检。
Creating New Documents
Generate .docx files with JavaScript via skill_run(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 } = docx;
async function main() {
const doc = new Document({ sections: [{ children: [
new Paragraph({ heading: HeadingLevel.HEADING_1, children: [new TextRun("标题")] }),
new Paragraph({ children: [new TextRun("正文")] }),
] }] });
const b64 = await Packer.toBase64String(doc);
doc_write("doc.docx", b64);
return { ok: true };
}
长脚本失败恢复
skill_run 会把 inline code 保存到本 session 的 scratch 目录(工具返回 script_path;同一会话内各 turn 路径不变)。若执行失败,目录会保留,错误会指出行列号与引号类型(ASCII " vs 弯引号 “/”)。
修复:fs_read + fs_patch 局部替换(不要用 fs_write 整文件重写),然后:
{ "path": "<script_path from prior skill_run>", "timeout_secs": 60 }
清理:script.js 在同 session 内跨 turn 保留,供后续 fs_patch + path 重跑;新的 inline code 会覆盖写入。error.json 仅在失败时写入,修复成功后自动删除。用户 cancel turn 时才会删除整个 scratch 目录。含大量中文引号的字符串优先用 JS 单引号 '...' 包裹。
Validation
ooxml_pack 在打包时自动校验。skill_run 写出 .docx 后会自动返回 style_warnings(排版告警);若有告警 MUST 修正后重新生成。也可 office_read_to_markdown 自检文本内容。
中文文档排版(CRITICAL)
中文内容必须遵守以下规则,否则字体回退、版式坍塌。skill_run 的 style_warnings 会检测常见违规。
- 必须配置 eastAsia 字体——
font: "Arial" 这类纯西文设置会让中文回退到默认衬线字体:
styles: {
default: { document: { run: {
font: { ascii: "Calibri", eastAsia: "微软雅黑", hAnsi: "Calibri" },
size: 24,
} } },
}
-
必须用 Heading 样式分层——禁止整篇连续大段;每 3~6 段内容应有一个标题;标题编号用中文习惯(一、/(一)/ 1. / (1))写入标题文本或 numbering 配置。
-
中文文档用 A4(11906 × 16838 DXA),页边距常用上下 2.54cm / 左右 3.18cm(1440 / 1800 DXA)。
-
正文段落设置——首行缩进两字符 + 适度行距:
new Paragraph({
indent: { firstLine: 480 },
spacing: { line: 360, lineRule: "auto" },
children: [new TextRun("正文内容……")],
})
- 列表必须用 numbering config——禁止手打
• · 1.(见下方 Lists 章节)。
风格菜单
以下四套风格是参考下限,按文档内容选择并调整颜色与细节——不要每次套用同一风格。若你的配色换到另一份文档里依然成立,说明选得不够贴合内容。
公文(政府/机关/正式函件)
- 标题:黑体;正文:仿宋_GB2312 三号(32);居中大标题;首行缩进;无彩色
const styles = {
default: { document: { run: {
font: { ascii: "Times New Roman", eastAsia: "仿宋_GB2312", hAnsi: "Times New Roman" },
size: 32,
} } },
paragraphStyles: [
{ id: "Heading1", name: "Heading 1", basedOn: "Normal", next: "Normal", quickFormat: true,
run: { size: 44, bold: true, font: { eastAsia: "黑体" } },
paragraph: { alignment: AlignmentType.CENTER, spacing: { before: 480, after: 480 }, outlineLevel: 0 } },
{ id: "Heading2", name: "Heading 2", basedOn: "Normal", next: "Normal", quickFormat: true,
run: { size: 32, bold: true, font: { eastAsia: "黑体" } },
paragraph: { spacing: { before: 240, after: 120 }, outlineLevel: 1 } },
],
};
商务报告(企业介绍/方案/汇报)
- 标题:微软雅黑加粗 + 主题色;正文:微软雅黑小四(24);无缩进、段后距、封面页
const ACCENT = "1F4E79";
const styles = {
default: { document: { run: {
font: { ascii: "Calibri", eastAsia: "微软雅黑", hAnsi: "Calibri" }, size: 24,
} } },
paragraphStyles: [
{ id: "Heading1", name: "Heading 1", basedOn: "Normal", next: "Normal", quickFormat: true,
run: { size: 32, bold: true, color: ACCENT, font: { ascii: "Calibri", eastAsia: "微软雅黑" } },
paragraph: { spacing: { before: 360, after: 180 }, outlineLevel: 0,
border: { bottom: { style: BorderStyle.SINGLE, size: 6, color: ACCENT, space: 4 } } } },
{ id: "Heading2", name: "Heading 2", basedOn: "Normal", next: "Normal", quickFormat: true,
run: { size: 28, bold: true, font: { ascii: "Calibri", eastAsia: "微软雅黑" } },
paragraph: { spacing: { before: 240, after: 120 }, outlineLevel: 1 } },
],
};
学术(论文/研究报告)
- 标题:黑体;正文:宋体 + Times New Roman 五号(21);两端对齐
const styles = {
default: { document: { run: {
font: { ascii: "Times New Roman", eastAsia: "宋体", hAnsi: "Times New Roman" },
size: 21,
} } },
paragraphStyles: [
{ id: "Heading1", name: "Heading 1", basedOn: "Normal", next: "Normal", quickFormat: true,
run: { size: 32, bold: true, font: { eastAsia: "黑体" } },
paragraph: { spacing: { before: 240, after: 120 }, outlineLevel: 0 } },
{ id: "Heading2", name: "Heading 2", basedOn: "Normal", next: "Normal", quickFormat: true,
run: { size: 28, bold: true, font: { eastAsia: "黑体" } },
paragraph: { spacing: { before: 180, after: 90 }, outlineLevel: 1 } },
],
};
现代简洁(宣传/介绍/轻量文档)
- 标题:微软雅黑 Light 大字号;正文:微软雅黑小四;大留白、浅灰分隔线
const ACCENT = "2D6A4F";
const styles = {
default: { document: { run: {
font: { ascii: "Calibri Light", eastAsia: "微软雅黑 Light", hAnsi: "Calibri Light" },
size: 24, color: "333333",
} } },
paragraphStyles: [
{ id: "Heading1", name: "Heading 1", basedOn: "Normal", next: "Normal", quickFormat: true,
run: { size: 40, color: ACCENT, font: { eastAsia: "微软雅黑 Light" } },
paragraph: { spacing: { before: 480, after: 240 }, outlineLevel: 0 } },
{ id: "Heading2", name: "Heading 2", basedOn: "Normal", next: "Normal", quickFormat: true,
run: { size: 28, bold: true, color: ACCENT, font: { eastAsia: "微软雅黑" } },
paragraph: { spacing: { before: 360, after: 120 }, outlineLevel: 1,
border: { bottom: { style: BorderStyle.SINGLE, size: 4, color: "CCCCCC", space: 2 } } } },
],
};
Page Size
sections: [{
properties: {
page: {
size: { width: 11906, height: 16838 },
margin: { top: 1440, right: 1800, bottom: 1440, left: 1800 }
}
},
children: []
}]
Common page sizes (DXA units, 1440 DXA = 1 inch):
| Paper | Width | Height | Content Width (1" margins) | 适用 |
|---|
| A4(中文默认) | 11,906 | 16,838 | 9,026 | 中文公文/报告 |
| US Letter(西文文档适用) | 12,240 | 15,840 | 9,360 | 英文信函/报告 |
Landscape orientation: docx-js swaps width/height internally, so pass portrait dimensions and let it handle the swap:
size: {
width: 12240,
height: 15840,
orientation: PageOrientation.LANDSCAPE
},
Styles (Override Built-in Headings)
中文文档 MUST 使用 eastAsia 字体(见上方「中文文档排版」)。西文文档可用 Calibri/Arial。
const doc = new Document({
styles: {
default: { document: { run: {
font: { ascii: "Calibri", eastAsia: "微软雅黑", hAnsi: "Calibri" },
size: 24,
} } },
paragraphStyles: [
{ id: "Heading1", name: "Heading 1", basedOn: "Normal", next: "Normal", quickFormat: true,
run: { size: 32, bold: true, font: { ascii: "Calibri", eastAsia: "微软雅黑" } },
paragraph: { spacing: { before: 240, after: 240 }, outlineLevel: 0 } },
{ id: "Heading2", name: "Heading 2", basedOn: "Normal", next: "Normal", quickFormat: true,
run: { size: 28, bold: true, font: { ascii: "Calibri", eastAsia: "微软雅黑" } },
paragraph: { spacing: { before: 180, after: 180 }, outlineLevel: 1 } },
]
},
sections: [{
children: [
new Paragraph({ heading: HeadingLevel.HEADING_1, children: [new TextRun("标题")] }),
]
}]
});
Lists (NEVER use unicode bullets)
new Paragraph({ children: [new TextRun("• Item")] })
new Paragraph({ children: [new TextRun("\u2022 Item")] })
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("Bullet item")] }),
new Paragraph({ numbering: { reference: "numbers", level: 0 },
children: [new TextRun("Numbered item")] }),
]
}]
});
Tables
CRITICAL: Tables need dual widths - set both columnWidths on the table AND width on each cell. Without both, tables render incorrectly on some platforms.
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 },
columnWidths: [4680, 4680],
rows: [
new TableRow({
children: [
new TableCell({
borders,
width: { size: 4680, type: WidthType.DXA },
shading: { fill: "D5E8F0", type: ShadingType.CLEAR },
margins: { top: 80, bottom: 80, left: 120, right: 120 },
children: [new Paragraph({ children: [new TextRun("Cell")] })]
})
]
})
]
})
Table width calculation:
Always use WidthType.DXA — WidthType.PERCENTAGE breaks in Google Docs.
width: { size: 9360, type: WidthType.DXA },
columnWidths: [7000, 2360]
Width rules:
- Always use
WidthType.DXA — never WidthType.PERCENTAGE (incompatible with Google Docs)
- Table width must equal the sum of
columnWidths
- Cell
width must match corresponding columnWidth
- Cell
margins are internal padding - they reduce content area, not add to cell width
- For full-width tables: use content width (page width minus left and right margins)
Images
new Paragraph({
children: [new ImageRun({
type: "png",
data: fs.readFileSync("image.png"),
transformation: { width: 200, height: 150 },
altText: { title: "Title", description: "Desc", name: "Name" }
})]
})
Page Breaks
new Paragraph({ children: [new PageBreak()] })
new Paragraph({ pageBreakBefore: true, children: [new TextRun("New page")] })
Hyperlinks
new Paragraph({
children: [new ExternalHyperlink({
children: [new TextRun({ text: "Click here", style: "Hyperlink" })],
link: "https://example.com",
})]
})
new Paragraph({ heading: HeadingLevel.HEADING_1, children: [
new Bookmark({ id: "chapter1", children: [new TextRun("Chapter 1")] }),
]})
new Paragraph({ children: [new InternalHyperlink({
children: [new TextRun({ text: "See Chapter 1", style: "Hyperlink" })],
anchor: "chapter1",
})]})
Footnotes
const doc = new Document({
footnotes: {
1: { children: [new Paragraph("Source: Annual Report 2024")] },
2: { children: [new Paragraph("See appendix for methodology")] },
},
sections: [{
children: [new Paragraph({
children: [
new TextRun("Revenue grew 15%"),
new FootnoteReferenceRun(1),
new TextRun(" using adjusted metrics"),
new FootnoteReferenceRun(2),
],
})]
}]
});
Tab Stops
new Paragraph({
children: [
new TextRun("Company Name"),
new TextRun("\tJanuary 2025"),
],
tabStops: [{ type: TabStopType.RIGHT, position: TabStopPosition.MAX }],
})
new Paragraph({
children: [
new TextRun("Introduction"),
new TextRun({ children: [
new PositionalTab({
alignment: PositionalTabAlignment.RIGHT,
relativeTo: PositionalTabRelativeTo.MARGIN,
leader: PositionalTabLeader.DOT,
}),
"3",
]}),
],
})
Multi-Column Layouts
sections: [{
properties: {
column: {
count: 2,
space: 720,
equalWidth: true,
separate: true,
},
},
children: []
}]
sections: [{
properties: {
column: {
equalWidth: false,
children: [
new Column({ width: 5400, space: 720 }),
new Column({ width: 3240 }),
],
},
},
children: []
}]
Force a column break with a new section using type: SectionType.NEXT_COLUMN.
Table of Contents
new TableOfContents("Table of Contents", { hyperlink: true, headingStyleRange: "1-3" })
Headers/Footers
sections: [{
properties: {
page: { margin: { top: 1440, right: 1440, bottom: 1440, left: 1440 } }
},
headers: {
default: new Header({ children: [new Paragraph({ children: [new TextRun("Header")] })] })
},
footers: {
default: new Footer({ children: [new Paragraph({
children: [new TextRun("Page "), new TextRun({ children: [PageNumber.CURRENT] })]
})] })
},
children: []
}]
Critical Rules for docx-js
- Set page size explicitly - 中文文档用 A4 (11906 x 16838 DXA);西文文档可用 US Letter (12240 x 15840 DXA)
- Landscape: pass portrait dimensions - docx-js swaps width/height internally; pass short edge as
width, long edge as height, and set orientation: PageOrientation.LANDSCAPE
- Never use
\n - use separate Paragraph elements
- Never use unicode bullets - use
LevelFormat.BULLET with numbering config
- PageBreak must be in Paragraph - standalone creates invalid XML
- ImageRun requires
type - always specify png/jpg/etc
- Always set table
width with DXA - never use WidthType.PERCENTAGE (breaks in Google Docs)
- Tables need dual widths -
columnWidths array AND cell width, both must match
- Table width = sum of columnWidths - for DXA, ensure they add up exactly
- Always add cell margins - use
margins: { top: 80, bottom: 80, left: 120, right: 120 } for readable padding
- Use
ShadingType.CLEAR - never SOLID for table shading
- Never use tables as dividers/rules - cells have minimum height and render as empty boxes (including in headers/footers); use
border: { bottom: { style: BorderStyle.SINGLE, size: 6, color: "2E75B6", space: 1 } } on a Paragraph instead. For two-column footers, use tab stops (see Tab Stops section), not tables
- TOC requires HeadingLevel only - no custom styles on heading paragraphs
- Override built-in styles - use exact IDs: "Heading1", "Heading2", etc.
- Include
outlineLevel - required for TOC (0 for H1, 1 for H2, etc.)
Editing Existing Documents
完整操作说明见 editing.md(skill_read {"skill":"docx","doc":"editing.md"})。
勿使用 word_edit;批量替换用 skill_run + fs.readFileSync / fs.writeFileSync。
Follow all 3 steps in order.
Step 1: Unpack
ooxml_unpack {"path": "document.docx"}
Extracts XML to an auto-generated out_dir under .cache/ooxml/ (returned in tool result). Pretty-prints, merges adjacent runs, and converts smart quotes to XML entities (“ etc.) so they survive editing. Pass "merge_runs": false to skip run merging.
Step 2: Edit XML
Edit files in <out_dir>/word/. See XML Reference below for patterns.
两种编辑方式(任选):
skill_run 批量替换(占位符多时推荐):fs.readFileSync(path, 'utf-8') → replace → fs.writeFileSync(path, xml, 'utf-8')
fs_read + fs_write:替换少、需逐处确认时
Use "Claude" as the author for tracked changes and comments, unless the user explicitly requests use of a different name.
CRITICAL: Use smart quotes for new content. When adding text with apostrophes or quotes, use XML entities to produce smart quotes:
<w:t>Here’s a quote: “Hello”</w:t>
| Entity | Character |
|---|
‘ | ‘ (left single) |
’ | ’ (right single / apostrophe) |
“ | “ (left double) |
” | ” (right double) |
Adding comments: docx_comment writes the <w:comment> into comments.xml and inserts the commentRangeStart/End + commentReference anchors into document.xml itself — do NOT add markers by hand. paragraph_index is required. Pass raw comment text: the tool XML-escapes < > & " ' for you, so do NOT pre-escape (passing & would render as &). For typographic punctuation pass the actual character (e.g. ' " —), not an entity:
docx_comment {"dir": "<out_dir>", "id": 0, "text": "See R&D budget — note the 'revised' figure", "paragraph_index": 1}
docx_comment {"dir": "<out_dir>", "id": 0, "text": "Text", "author": "Custom Author", "paragraph_index": 1, "text_hint": "substring the paragraph must contain"}
docx_comment {"dir": "<out_dir>", "id": 1, "text": "Reply text", "parent": 0, "paragraph_index": 1}
paragraph_index (required): 0-based index over top-level <w:p> children of <w:body>.
text_hint (optional): assert the target paragraph contains this substring; a mismatch is an error (guards against off-by-one paragraph counts).
parent (optional): id of the comment being replied to. author (optional): comment author.
Step 3: Pack
ooxml_pack {"dir": "<out_dir>", "out_path": "output.docx", "original": "document.docx"}
Validates with auto-repair, condenses XML, and creates DOCX.
Auto-repair will fix:
durableId >= 0x7FFFFFFF (regenerates valid ID)
- Missing
xml:space="preserve" on <w:t> with whitespace
Auto-repair won't fix:
- Malformed XML, invalid element nesting, missing relationships, schema violations
Common Pitfalls
- 占位符跨 run 匹配不到:Word 常把一段文字拆成多个
<w:r>/<w:t>(如 【内容 和 分析】 分属两个 run)。解包默认 merge_runs: true 已缓解;若 replace 仍未命中,先 fs_read 查看 XML 实际切分,再按实际片段替换。
- 替换后核对命中数:
skill_run 脚本里统计每条 replace 是否生效(xml.includes(old) 检查),返回未命中清单,避免静默漏改。
- Replace entire
<w:r> elements: When adding tracked changes, replace the whole <w:r>...</w:r> block with <w:del>...<w:ins>... as siblings. Don't inject tracked change tags inside a run.
- Preserve
<w:rPr> formatting: Copy the original run's <w:rPr> block into your tracked change runs to maintain bold, font size, etc.
XML Reference
Schema Compliance
- Element order in
<w:pPr>: <w:pStyle>, <w:numPr>, <w:spacing>, <w:ind>, <w:jc>, <w:rPr> last
- Whitespace: Add
xml:space="preserve" to <w:t> with leading/trailing spaces
- RSIDs: Must be 8-digit hex (e.g.,
00AB1234)
Tracked Changes
Insertion:
<w:ins w:id="1" w:author="Claude" w:date="2025-01-01T00:00:00Z">
<w:r><w:t>inserted text</w:t></w:r>
</w:ins>
Deletion:
<w:del w:id="2" w:author="Claude" w:date="2025-01-01T00:00:00Z">
<w:r><w:delText>deleted text</w:delText></w:r>
</w:del>
Inside <w:del>: Use <w:delText> instead of <w:t>, and <w:delInstrText> instead of <w:instrText>.
Minimal edits - only mark what changes:
<w:r><w:t>The term is </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> days.</w:t></w:r>
Deleting entire paragraphs/list items - when removing ALL content from a paragraph, also mark the paragraph mark as deleted so it merges with the next paragraph. Add <w:del/> inside <w:pPr><w:rPr>:
<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>Entire paragraph content being deleted...</w:delText></w:r>
</w:del>
</w:p>
Without the <w:del/> in <w:pPr><w:rPr>, accepting changes leaves an empty paragraph/list item.
Rejecting another author's insertion - nest deletion inside their insertion:
<w:ins w:author="Jane" w:id="5">
<w:del w:author="Claude" w:id="10">
<w:r><w:delText>their inserted text</w:delText></w:r>
</w:del>
</w:ins>
Restoring another author's deletion - add insertion after (don't modify their deletion):
<w:del w:author="Jane" w:id="5">
<w:r><w:delText>deleted text</w:delText></w:r>
</w:del>
<w:ins w:author="Claude" w:id="10">
<w:r><w:t>deleted text</w:t></w:r>
</w:ins>
Comments
docx_comment (see Step 2) inserts these markers into document.xml for you — you normally do NOT author them by hand. The markup below is reference only (for inspecting tool output, or for hand-targeting a span paragraph_index cannot reach, e.g. a sub-paragraph range).
CRITICAL: <w:commentRangeStart> and <w:commentRangeEnd> are siblings of <w:r>, never inside <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>deleted</w:delText></w:r>
</w:del>
<w:r><w:t> more text</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>
<w:commentRangeStart w:id="0"/>
<w:commentRangeStart w:id="1"/>
<w:r><w:t>text</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>
Images
- Add image file to
word/media/
- Add relationship to
word/_rels/document.xml.rels:
<Relationship Id="rId5" Type=".../image" Target="media/image1.png"/>
- Add content type to
[Content_Types].xml:
<Default Extension="png" ContentType="image/png"/>
- Reference in document.xml:
<w:drawing>
<wp:inline>
<wp:extent cx="914400" cy="914400"/>
<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>
doc-agent 系统约束
- 文本提取:用
office_read_to_markdown(替代 pandoc/markitdown)。
- 渲染校验:无 LibreOffice/Poppler;以
ooxml_pack 校验 + 文本自检为准,必要时请用户用 Word/WPS 打开确认。
- 扫描 PDF:无 OCR 能力。