mcpField
D
docx

docx

github.com/eigent-ai文档处理2539 次安装2 周前更新
代码仓库

关于 docx

用于创建、读取、编辑和验证 DOCX 文件的 Skill 指南。它结合文档库用法与 ZIP/XML 工作流,支持精确修改现有文件。

主要功能

  1. 01使用 docx-js 创建 DOCX
  2. 02用 pandoc 提取文本
  3. 03解包并编辑文档 XML
  4. 04将 DOCX 转为 PDF 或图像
  5. 05验证并重新打包文档
  6. 06处理修订记录和批注

适用场景

  1. 01生成格式化的 DOCX 文档
  2. 02提取文本或检查原始 XML
  3. 03编辑现有 DOCX 并保留标记

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/eigent-ai/agent-skills --skill docx

安装后在 Claude Code 中输入/docx确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

LICENSE.txt
scripts/
__init__.py
accept_changes.py
comment.py
scripts/office/
pack.py
scripts/office/helpers/
__init__.py
merge_runs.py
simplify_redlines.py
scripts/office/schemas/ecma/fouth-edition/
opc-contentTypes.xsd
opc-coreProperties.xsd
opc-digSig.xsd
opc-relationships.xsd
scripts/office/schemas/ISO-IEC29500-4_2016/
dml-chart.xsd
dml-chartDrawing.xsd
dml-diagram.xsd
dml-lockedCanvas.xsd
dml-main.xsd
dml-picture.xsd
dml-spreadsheetDrawing.xsd
dml-wordprocessingDrawing.xsd
pml.xsd
shared-additionalCharacteristics.xsd
shared-bibliography.xsd
shared-commonSimpleTypes.xsd
shared-customXmlDataProperties.xsd
shared-customXmlSchemaProperties.xsd
shared-documentPropertiesCustom.xsd
shared-documentPropertiesExtended.xsd
shared-documentPropertiesVariantTypes.xsd
shared-math.xsd

详细介绍

DOCX 创建、编辑和分析

概述

.docx 文件是包含 XML 文件的 ZIP 存档。

快速参考

任务方法
读取/分析内容使用 pandoc 或解压以查看原始 XML
创建新文档使用 docx-js——参见下文“创建新文档”
编辑现有文档解压 → 编辑 XML → 重新打包——参见下文“编辑现有文档”
将 .doc 转换为 .docx

编辑旧版 .doc 文件前,必须先进行转换:

python scripts/office/soffice.py --headless --convert-to docx document.doc
读取内容
# Text extraction with tracked changes
pandoc --track-changes=all document.docx -o output.md

# Raw XML access
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: [/* content */] }] });
Packer.toBuffer(doc).then(buffer => fs.writeFileSync("doc.docx", buffer));
验证

创建文件后,对其进行验证。如果验证失败,请解压文件、修复 XML,然后重新打包。

python scripts/office/validate.py doc.docx
页面大小
// CRITICAL: docx-js defaults to A4, not US Letter
// Always set page size explicitly for consistent results
sections: [{
  properties: {
    page: {
      size: {
        width: 12240,   // 8.5 inches in DXA
        height: 15840   // 11 inches in DXA
      },
      margin: { top: 1440, right: 1440, bottom: 1440, left: 1440 } // 1 inch margins
    }
  },
  children: [/* content */]
}]

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

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

横向: docx-js 会在内部交换宽度和高度,因此请传入纵向尺寸,由它处理交换:

size: {
  width: 12240,   // Pass SHORT edge as width
  height: 15840,  // Pass LONG edge as height
  orientation: PageOrientation.LANDSCAPE  // docx-js swaps them in the XML
},
// Content width = 15840 - left margin - right margin (uses the long edge)
样式(覆盖内置标题样式)

默认字体使用 Arial(普遍支持)。标题保持黑色,以便阅读。

const doc = new Document({
  styles: {
    default: { document: { run: { font: "Arial", size: 24 } } }, // 12pt default
    paragraphStyles: [
      // IMPORTANT: Use exact IDs to override built-in styles
      { 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 required for TOC
      { 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 项目符号)
// ❌ WRONG - never manually insert bullet characters
new Paragraph({ children: [new TextRun("• Item")] })  // BAD
new Paragraph({ children: [new TextRun("\u2022 Item")] })  // BAD

// ✅ CORRECT - use numbering config with 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")] }),
    ]
  }]
});

// ⚠️ Each reference creates INDEPENDENT numbering
// Same reference = continues (1,2,3 then 4,5,6)
// Different reference = restarts (1,2,3 then 1,2,3)
表格

重要:表格需要同时设置两种宽度——既要为表格设置 columnWidths,也要为每个单元格设置 width。缺少任一设置,表格在某些平台上的渲染都会不正确。

// CRITICAL: Always set table width for consistent rendering
// CRITICAL: Use ShadingType.CLEAR (not SOLID) to prevent black backgrounds
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 }, // Always use DXA (percentages break in Google Docs)
  columnWidths: [4680, 4680], // Must sum to table width (DXA: 1440 = 1 inch)
  rows: [
    new TableRow({
      children: [
        new TableCell({
          borders,
          width: { size: 4680, type: WidthType.DXA }, // Also set on each cell
          shading: { fill: "D5E8F0", type: ShadingType.CLEAR }, // CLEAR not SOLID
          margins: { top: 80, bottom: 80, left: 120, right: 120 }, // Cell padding (internal, not added to width)
          children: [new Paragraph({ children: [new TextRun("Cell")] })]
        })
      ]
    })
  ]
})

表格宽度计算:

始终使用 WidthType.DXA——WidthType.PERCENTAGE 在 Google Docs 中会出错。

// Table width = sum of columnWidths = content width
// US Letter with 1" margins: 12240 - 2880 = 9360 DXA
width: { size: 9360, type: WidthType.DXA },
columnWidths: [7000, 2360]  // Must sum to table width

宽度规则:

  • 始终使用 WidthType.DXA——切勿使用 WidthType.PERCENTAGE(与 Google Docs 不兼容)
  • 表格宽度必须等于 columnWidths 的总和
  • 单元格 width 必须与对应的 columnWidth 一致
  • 单元格 margins 是内部填充——会减少内容区域,不会增加单元格宽度
  • 对于全宽表格:使用内容宽度(页面宽度减去左右页边距)
图像
// CRITICAL: type parameter is REQUIRED
new Paragraph({
  children: [new ImageRun({
    type: "png", // Required: png, jpg, jpeg, gif, bmp, svg
    data: fs.readFileSync("image.png"),
    transformation: { width: 200, height: 150 },
    altText: { title: "Title", description: "Desc", name: "Name" } // All three required
  })]
})
分页符
// CRITICAL: PageBreak must be inside a Paragraph
new Paragraph({ children: [new PageBreak()] })

// Or use pageBreakBefore
new Paragraph({ pageBreakBefore: true, children: [new TextRun("New page")] })
超链接
// External link
new Paragraph({
  children: [new ExternalHyperlink({
    children: [new TextRun({ text: "Click here", style: "Hyperlink" })],
    link: "https://example.com",
  })]
})

// Internal link (bookmark + reference)
// 1. Create bookmark at destination
new Paragraph({ heading: HeadingLevel.HEADING_1, children: [
  new Bookmark({ id: "chapter1", children: [new TextRun("Chapter 1")] }),
]})
// 2. Link to it
new Paragraph({ children: [new InternalHyperlink({
  children: [new TextRun({ text: "See Chapter 1", style: "Hyperlink" })],
  anchor: "chapter1",
})]})
脚注
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),
      ],
    })]
  }]
});
制表位
// Right-align text on same line (e.g., date opposite a title)
new Paragraph({
  children: [
    new TextRun("Company Name"),
    new TextRun("\tJanuary 2025"),
  ],
  tabStops: [{ type: TabStopType.RIGHT, position: TabStopPosition.MAX }],
})

// Dot leader (e.g., TOC-style)
new Paragraph({
  children: [
    new TextRun("Introduction"),
    new TextRun({ children: [
      new PositionalTab({
        alignment: PositionalTabAlignment.RIGHT,
        relativeTo: PositionalTabRelativeTo.MARGIN,
        leader: PositionalTabLeader.DOT,
      }),
      "3",
    ]}),
  ],
})
多栏布局
// Equal-width columns
sections: [{
  properties: {
    column: {
      count: 2,          // number of columns
      space: 720,        // gap between columns in DXA (720 = 0.5 inch)
      equalWidth: true,
      separate: true,    // vertical line between columns
    },
  },
  children: [/* content flows naturally across columns */]
}]

// Custom-width columns (equalWidth must be false)
sections: [{
  properties: {
    column: {
      equalWidth: false,
      children: [
        new Column({ width: 5400, space: 720 }),
        new Column({ width: 3240 }),
      ],
    },
  },
  children: [/* content */]
}]

使用 type: SectionType.NEXT_COLUMN 新建分节符,以强制换栏。

目录
// CRITICAL: Headings must use HeadingLevel ONLY - no custom styles
new TableOfContents("Table of Contents", { hyperlink: true, headingStyleRange: "1-3" })
页眉/页脚
sections: [{
  properties: {
    page: { margin: { top: 1440, right: 1440, bottom: 1440, left: 1440 } } // 1440 = 1 inch
  },
  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
  • 切勿将表格用作分隔线/横线 - 单元格有最小高度,会渲染为空框(包括在页眉/页脚中);请在 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" 作为作者 进行修订跟踪和评论,除非用户明确要求使用其他名称。

直接使用 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  # reply to comment 0
python scripts/comment.py unpacked/ 0 "Text" --author "Custom Author"  # custom author name

然后向 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>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>。

最小编辑 - 仅标记发生变化的内容:


<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: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>
图片
  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"/>  
    <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 用于图片

评论

支持 Markdown