mcpField
D
docx

docx

github.com/makerjackie文档处理043 次安装36 周前更新
代码仓库

关于 docx

docx 是一项用于创建、编辑和分析 DOCX 文档的技能,面向需要通过脚本处理文档的用户。它涵盖文本提取、docx-js 文档生成,以及解包和编辑 XML 后重新打包的流程。

主要功能

  1. 01提取 DOCX 文本与原始 XML
  2. 02使用 docx-js 创建文档
  3. 03解包、编辑并重新打包文档
  4. 04转换 DOCX 为 PDF 或图像
  5. 05处理修订、批注与文档校验

适用场景

  1. 01从 DOCX 提取文本以供分析
  2. 02通过 JavaScript 生成 DOCX 文件
  3. 03修改现有文档的 XML 内容
  4. 04将文档转换为图像或 PDF

安装

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

$npx skills add https://github.com/makerjackie/jackie-skills-cn-top50 --skill docx

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

详细介绍

(中文)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,
        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")] })
目录
// 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
  • 目录只能使用 HeadingLevel——标题段落上不要使用自定义样式
  • 覆盖内置样式——使用确切的 ID:"Heading1"、"Heading2" 等
  • 包含 outlineLevel——目录必需(H1 为 0,H2 为 1,以此类推)

编辑现有文档

请按顺序完成全部 3 个步骤。

第 1 步:解压
python scripts/office/unpack.py document.docx unpacked/

提取 XML、进行美化格式化、合并相邻的文本运行,并将智能引号转换为 XML 实体(“ 等),使其在编辑后仍能保留。使用 --merge-runs false 可跳过文本运行合并。

第 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