CtrlK
BlogDocsLog inGet started
Tessl Logo

docx

当用户需要创建、读取、编辑或处理 Word 文档(.docx)时,使用此技能。触发场景包括提到“Word 文档”、“.docx”,或要求生成带目录、标题、页码、信头等格式的专业文档;也包括提取或重组 .docx 内容、插入或替换图片、在 Word 文件中查找替换、处理修订或批注,以及将内容整理为正式 Word 文档。如果用户要求生成“报告”“备忘录”“信函”“模板”等 Word / .docx 交付物,也应使用此技能。不要用于 PDF、电子表格、Google Docs,或与文档生成无关的一般编程任务。

SKILL.md
Quality
Evals
Security

重要: 所有 scripts/ 路径均相对于本技能目录。 运行方式: cd {this_skill_dir} && python scripts/... 或使用 execute_shell_commandcwd 参数。

DOCX 创建、编辑与分析

前置依赖

  • docx (npm install -g docx): 新文档创建
  • LibreOffice (soffice): .doc -> .docx 转换、修订接受和 PDF 导出
  • pandoc: 文本提取
  • pdftoppm (poppler-utils): 文档转图片工作流
  • 如果 pdftoppm 不可用,Python 备用路径可能使用 pdf2image
  • 在 Windows 上,依赖项必须已安装并在 PATH 中可用;如缺失,请报告依赖问题并停止(不要反复重试)。

概述

.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,
        TableOfContents, HeadingLevel, BorderStyle, WidthType, ShadingType,
        VerticalAlign, PageNumber, PageBreak } = require('docx');

const doc = new Document({ sections: [{ children: [/* content */] }] });
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: [/* content */]
}]

常见页面尺寸(DXA 单位,1440 DXA = 1 英寸):

纸张宽度高度内容宽度(1 英寸边距)
US Letter12,24015,8409,360
A4(默认)11,90616,8389,026

横向方向: docx-js 在内部会交换宽度/高度,因此传入纵向尺寸并让其处理交换:

size: {
  width: 12240,   // 将短边作为 width 传入
  height: 15840,  // 将长边作为 height 传入
  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 } }, // 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("Title")] }),
    ]
  }]
});

列表(绝对不要使用 Unicode 符号)

// ❌ 错误 - 绝不手动插入项目符号字符
new Paragraph({ children: [new TextRun("• Item")] })  // 错误
new Paragraph({ children: [new TextRun("\u2022 Item")] })  // 错误

// ✅ 正确 - 使用 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("Bullet item")] }),
      new Paragraph({ numbering: { reference: "numbers", level: 0 },
        children: [new TextRun("Numbered item")] }),
    ]
  }]
});

// ⚠️ 每个 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 Docs 中会出问题)
  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("Cell")] })]
        })
      ]
    })
  ]
})

表格宽度计算:

始终使用 WidthType.DXA -- WidthType.PERCENTAGE 在 Google Docs 中会出问题。

// 表格宽度 = columnWidths 之和 = 内容宽度
// US Letter 配合 1 英寸边距: 12240 - 2880 = 9360 DXA
width: { size: 9360, type: WidthType.DXA },
columnWidths: [7000, 2360]  // 必须加起来等于表格宽度

宽度规则:

  • 始终使用 WidthType.DXA -- 绝不使用 WidthType.PERCENTAGE(与 Google Docs 不兼容)
  • 表格宽度必须等于 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: "Title", description: "Desc", name: "Name" } // 三个都是必需的
  })]
})

分页符

// 关键: PageBreak 必须在 Paragraph 内部
new Paragraph({ children: [new PageBreak()] })

// 或者使用 pageBreakBefore
new Paragraph({ pageBreakBefore: true, children: [new TextRun("New page")] })

目录

// 关键: 标题必须仅使用 HeadingLevel - 不使用自定义样式
new TableOfContents("Table of Contents", { 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("Header")] })] })
  },
  footers: {
    default: new Footer({ children: [new Paragraph({
      children: [new TextRun("Page "), new TextRun({ children: [PageNumber.CURRENT] })]
    })] })
  },
  children: [/* content */]
}]

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 Docs 中会出问题)
  • 表格需要双重宽度 - columnWidths 数组和单元格 width,两者必须匹配
  • 表格宽度 = columnWidths 之和 - 对于 DXA,确保它们精确相加
  • 始终添加单元格边距 - 使用 margins: { top: 80, bottom: 80, left: 120, right: 120 } 获得可读的内边距
  • 使用 ShadingType.CLEAR - 表格底纹绝不使用 SOLID
  • 目录需要仅使用 HeadingLevel - 标题段落不使用自定义样式
  • 覆盖内置样式 - 使用精确的 ID: "Heading1"、"Heading2" 等
  • 包含 outlineLevel - 目录所必需(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" 作为作者来添加修订和批注,除非用户明确要求使用其他名称。

**直接使用 Edit 工具进行字符串替换。不要编写 Python 脚本。**脚本会引入不必要的复杂性。Edit 工具能准确显示被替换的内容。

**关键: 新内容使用智能引号。**添加包含撇号或引号的文本时,使用 XML 实体生成智能引号:

<!-- 使用这些实体生成专业的排版效果 -->
<w:t>Here&#x2019;s a quote: &#x201C;Hello&#x201D;</w:t>
实体字符
&#x2018;' (左单引号)
&#x2019;' (右单引号 / 撇号)
&#x201C;" (左双引号)
&#x201D;" (右双引号)

添加批注: 使用 comment.py 处理跨多个 XML 文件的样板代码(文本必须是预转义的 XML):

python scripts/comment.py unpacked/ 0 "Comment text with &amp; and &#x2019;"
python scripts/comment.py unpacked/ 1 "Reply text" --parent 0  # 回复批注 0
python scripts/comment.py unpacked/ 0 "Text" --author "Custom 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"
  • RSIDs: 必须是 8 位十六进制数(例如 00AB1234

修订

插入:

<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>

删除:

<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>

<w:del> 内部: 使用 <w:delText> 替代 <w:t>,使用 <w:delInstrText> 替代 <w:instrText>

最小化编辑 - 只标记变更的部分:

<!-- 将 "30 days" 改为 "60 days" -->
<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>

删除整个段落/列表项 - 当删除段落中的所有内容时,还需将段落标记标记为已删除,使其与下一段落合并。在 <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>Entire paragraph content being deleted...</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>their inserted text</w:delText></w:r>
  </w:del>
</w:ins>

恢复其他作者的删除 - 在之后添加插入(不修改其删除标记):

<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>

批注

运行 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>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>

<!-- 批注 0 及其嵌套的回复 1 -->
<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>

图片

  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>
Repository
agentscope-ai/QwenPaw
Last updated
First committed

Is this your skill?

If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.