$npx -y skills add agentscope-ai/QwenPaw --skill docx-zh当用户需要创建、读取、编辑或处理 Word 文档(.docx)时,使用此技能。触发场景包括提到“Word 文档”、“.docx”,或要求生成带目录、标题、页码、信头等格式的专业文档;也包括提取或重组 .docx 内容、插入或替换图片、在 Word 文件中查找替换、处理修订或批注,以及将内容整理为正式 Word 文档。如果用户要求生成“报告”“备忘录”“信函”“模板”等 Word / .docx 交付物,也应使用此技能。不要用于 PDF、电子表格、Google Docs,或与文档生成无关的一般编程任务。
| 1 | > **重要:** 所有 `scripts/` 路径均相对于本技能目录。 |
| 2 | > 运行方式: `cd {this_skill_dir} && python scripts/...` |
| 3 | > 或使用 `execute_shell_command` 的 `cwd` 参数。 |
| 4 | |
| 5 | # DOCX 创建、编辑与分析 |
| 6 | |
| 7 | ## 前置依赖 |
| 8 | |
| 9 | - **docx** (`npm install -g docx`): 新文档创建 |
| 10 | - **LibreOffice** (`soffice`): `.doc` -> `.docx` 转换、修订接受和 PDF 导出 |
| 11 | - **pandoc**: 文本提取 |
| 12 | - **pdftoppm** (poppler-utils): 文档转图片工作流 |
| 13 | - 如果 `pdftoppm` 不可用,Python 备用路径可能使用 `pdf2image`。 |
| 14 | - 在 Windows 上,依赖项必须已安装并在 `PATH` 中可用;如缺失,请报告依赖问题并停止(不要反复重试)。 |
| 15 | |
| 16 | ## 概述 |
| 17 | |
| 18 | .docx 文件是包含 XML 文件的 ZIP 压缩包。 |
| 19 | |
| 20 | ## 快速参考 |
| 21 | |
| 22 | | 任务 | 方法 | |
| 23 | |------|------| |
| 24 | | 读取/分析内容 | `pandoc` 或解压获取原始 XML | |
| 25 | | 创建新文档 | 使用 `docx-js` - 参见下方"创建新文档" | |
| 26 | | 编辑现有文档 | 解压 → 编辑 XML → 重新打包 - 参见下方"编辑现有文档" | |
| 27 | |
| 28 | ### 将 .doc 转换为 .docx |
| 29 | |
| 30 | 旧版 `.doc` 文件必须先转换才能编辑: |
| 31 | |
| 32 | ```bash |
| 33 | python scripts/office/soffice.py --headless --convert-to docx document.doc |
| 34 | ``` |
| 35 | |
| 36 | ### 读取内容 |
| 37 | |
| 38 | ```bash |
| 39 | # 提取包含修订的文本 |
| 40 | pandoc --track-changes=all document.docx -o output.md |
| 41 | |
| 42 | # 访问原始 XML |
| 43 | python scripts/office/unpack.py document.docx unpacked/ |
| 44 | ``` |
| 45 | |
| 46 | ### 转换为图片 |
| 47 | |
| 48 | ```bash |
| 49 | python scripts/office/soffice.py --headless --convert-to pdf document.docx |
| 50 | pdftoppm -jpeg -r 150 document.pdf page |
| 51 | ``` |
| 52 | |
| 53 | ### 接受修订 |
| 54 | |
| 55 | 生成接受所有修订后的干净文档(需要 LibreOffice): |
| 56 | |
| 57 | ```bash |
| 58 | python scripts/accept_changes.py input.docx output.docx |
| 59 | ``` |
| 60 | |
| 61 | --- |
| 62 | |
| 63 | ## 创建新文档 |
| 64 | |
| 65 | 使用 JavaScript 生成 .docx 文件,然后进行验证。安装: `npm install -g docx` |
| 66 | |
| 67 | ### 初始设置 |
| 68 | ```javascript |
| 69 | const { Document, Packer, Paragraph, TextRun, Table, TableRow, TableCell, ImageRun, |
| 70 | Header, Footer, AlignmentType, PageOrientation, LevelFormat, ExternalHyperlink, |
| 71 | TableOfContents, HeadingLevel, BorderStyle, WidthType, ShadingType, |
| 72 | VerticalAlign, PageNumber, PageBreak } = require('docx'); |
| 73 | |
| 74 | const doc = new Document({ sections: [{ children: [/* content */] }] }); |
| 75 | Packer.toBuffer(doc).then(buffer => fs.writeFileSync("doc.docx", buffer)); |
| 76 | ``` |
| 77 | |
| 78 | ### 验证 |
| 79 | 创建文件后进行验证。如果验证失败,解压、修复 XML 并重新打包。 |
| 80 | ```bash |
| 81 | python scripts/office/validate.py doc.docx |
| 82 | ``` |
| 83 | |
| 84 | ### 页面尺寸 |
| 85 | |
| 86 | ```javascript |
| 87 | // 关键: docx-js 默认为 A4,而非 US Letter |
| 88 | // 始终显式设置页面尺寸以获得一致的结果 |
| 89 | sections: [{ |
| 90 | properties: { |
| 91 | page: { |
| 92 | size: { |
| 93 | width: 12240, // 8.5 英寸(DXA 单位) |
| 94 | height: 15840 // 11 英寸(DXA 单位) |
| 95 | }, |
| 96 | margin: { top: 1440, right: 1440, bottom: 1440, left: 1440 } // 1 英寸边距 |
| 97 | } |
| 98 | }, |
| 99 | children: [/* content */] |
| 100 | }] |
| 101 | ``` |
| 102 | |
| 103 | **常见页面尺寸(DXA 单位,1440 DXA = 1 英寸):** |
| 104 | |
| 105 | | 纸张 | 宽度 | 高度 | 内容宽度(1 英寸边距) | |
| 106 | |------|------|------|----------------------| |
| 107 | | US Letter | 12,240 | 15,840 | 9,360 | |
| 108 | | A4(默认) | 11,906 | 16,838 | 9,026 | |
| 109 | |
| 110 | **横向方向:** docx-js 在内部会交换宽度/高度,因此传入纵向尺寸并让其处理交换: |
| 111 | ```javascript |
| 112 | size: { |
| 113 | width: 12240, // 将短边作为 width 传入 |
| 114 | height: 15840, // 将长边作为 height 传入 |
| 115 | orientation: PageOrientation.LANDSCAPE // docx-js 会在 XML 中交换它们 |
| 116 | }, |
| 117 | // 内容宽度 = 15840 - 左边距 - 右边距(使用长边) |
| 118 | ``` |
| 119 | |
| 120 | ### 样式(覆盖内置标题) |
| 121 | |
| 122 | 使用 Arial 作为默认字体(通用支持)。标题保持黑色以确保可读性。 |
| 123 | |
| 124 | ```javascript |
| 125 | const doc = new Document({ |
| 126 | styles: { |
| 127 | default: { document: { run: { font: "Arial", size: 24 } } }, // 默认 12pt |
| 128 | paragraphStyles: [ |
| 129 | // 重要: 使用精确的 ID 来覆盖内置样式 |
| 130 | { id: "Heading1", name: "Heading 1", basedOn: "Normal", next: "Normal", quickFormat: true, |
| 131 | run: { size: 32, bold: true, font: "Arial" }, |
| 132 | paragraph: { spacing: { before: 240, after: 240 }, outlineLevel: 0 } }, // outlineLevel 是目录所必需的 |
| 133 | { id: "Heading2", name: "Heading 2", basedOn: "Normal", next: "Normal", quickFormat: true, |
| 134 | run: { size: 28, bold: true, font: "Arial" }, |
| 135 | paragraph: { spacing: { before: 180, after: 180 }, outlineLevel: 1 } }, |
| 136 | ] |
| 137 | }, |
| 138 | sections: [{ |
| 139 | children: [ |
| 140 | new Paragraph({ heading: HeadingLevel.HEADING_1, children: [new TextRun("Title")] }), |
| 141 | ] |
| 142 | }] |
| 143 | }); |
| 144 | ``` |
| 145 | |
| 146 | ### 列表(绝对不要使用 Unicode 符号) |
| 147 | |
| 148 | ```javascript |
| 149 | // ❌ 错误 - 绝不手动插入项目符号字符 |
| 150 | new Paragraph({ children: [new TextRun("• Item")] }) // 错误 |
| 151 | new Paragraph({ children: [new TextRun("\u2022 Item")] }) // 错误 |
| 152 | |
| 153 | // ✅ 正确 - 使用 LevelFormat.BULLET 的编号配置 |
| 154 | const doc = new Document({ |
| 155 | numbering: { |
| 156 | config: [ |
| 157 | { reference: "bullets", |
| 158 | levels: [{ level: 0, format: LevelFormat.BULLET, text: "•", alignment: AlignmentType.LEFT, |
| 159 | style: { paragraph: { indent: { left: 720, hanging: 360 } } } }] }, |
| 160 | { reference: "numbers", |
| 161 | levels: [{ level: 0, format: LevelFormat.DECIMAL, text: "%1.", alignment: AlignmentType.LEFT, |
| 162 | style: { paragraph: { indent: { left: 720, hanging: 360 } } } }] }, |
| 163 | ] |
| 164 | }, |
| 165 | sections: [{ |
| 166 | children: [ |
| 167 | new Paragraph({ numbering: { reference: "bullets", level: 0 }, |
| 168 | children: [new TextRun("Bullet item")] }), |
| 169 | new Paragraph({ numbering: { reference: "numbers", level: 0 }, |
| 170 | children: [new TextRun("Numbered item")] }), |
| 171 | ] |
| 172 | }] |
| 173 | }); |
| 174 | |
| 175 | // ⚠️ 每个 reference 创建独立的编号序列 |
| 176 | // 相同 reference = 继续编号(1,2,3 然后 4,5,6) |
| 177 | // 不同 reference = 重新开始(1,2,3 然后 1,2,3) |
| 178 | ``` |