重要前提
安装AI Skills的关键前提是:必须科学上网,且开启TUN模式,这一点至关重要,直接决定安装能否顺利完成,在此郑重提醒三遍:科学上网,科学上网,科学上网。查看完整安装教程 →
docx by jackiexiao/jackie-skills-starter
npx skills add https://github.com/jackiexiao/jackie-skills-starter --skill docx.docx 文件是一个包含 XML 文件的 ZIP 归档。
| 任务 | 方法 |
|---|---|
| 读取/分析内容 | 使用 pandoc 或解包以获取原始 XML |
| 创建新文档 | 使用 docx-js - 参见下文“创建新文档” |
| 编辑现有文档 | 解包 → 编辑 XML → 重新打包 - 参见下文“编辑现有文档” |
旧版 .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/
广告位招租
在这里展示您的产品或服务
触达数万 AI 开发者,精准高效
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 Letter | 12,240 | 15,840 | 9,360 |
| A4(默认) | 11,906 | 16,838 | 9,026 |
横向方向: docx-js 内部会交换宽度和高度,因此传递纵向尺寸并让它处理交换:
size: {
width: 12240, // 将短边作为宽度传递
height: 15840, // 将长边作为高度传递
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: "标题 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: "标题 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("标题")] }),
]
}]
});
// ❌ 错误 - 切勿手动插入项目符号字符
new Paragraph({ children: [new TextRun("• 项目")] }) // 错误
new Paragraph({ children: [new TextRun("\u2022 项目")] }) // 错误
// ✅ 正确 - 使用带有 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("项目符号项")] }),
new Paragraph({ numbering: { reference: "numbers", level: 0 },
children: [new TextRun("编号项")] }),
]
}]
});
// ⚠️ 每个引用创建独立的编号序列
// 相同引用 = 继续编号(1,2,3 然后是 4,5,6)
// 不同引用 = 重新开始编号(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("单元格")] })]
})
]
})
]
})
表格宽度计算:
始终使用 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: "标题", description: "描述", name: "名称" } // 三项均为必需
})]
})
// 关键:PageBreak 必须放在 Paragraph 内部
new Paragraph({ children: [new PageBreak()] })
// 或者使用 pageBreakBefore
new Paragraph({ pageBreakBefore: true, children: [new TextRun("新页面")] })
// 关键:标题必须仅使用 HeadingLevel - 不能在标题段落上使用自定义样式
new TableOfContents("目录", { 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("页眉")] })] })
},
footers: {
default: new Footer({ children: [new Paragraph({
children: [new TextRun("第 "), new TextRun({ children: [PageNumber.CURRENT] })]
})] })
},
children: [/* content */]
}]
width 传递,长边作为 height 传递,并设置 orientation: PageOrientation.LANDSCAPE\n - 使用单独的 Paragraph 元素LevelFormat.BULLETtype - 始终指定 png/jpg 等width - 切勿使用 WidthType.PERCENTAGE(在 Google Docs 中会出错)columnWidths 数组和单元格的 width,两者必须匹配margins: { top: 80, bottom: 80, left: 120, right: 120 } 以获得可读的内边距ShadingType.CLEAR - 表格底纹切勿使用 SOLIDoutlineLevel - 为目录所必需(H1 为 0,H2 为 1,依此类推)请按顺序遵循以下 3 个步骤。
python scripts/office/unpack.py document.docx unpacked/
提取 XML,进行格式化打印,合并相邻的 run,并将智能引号转换为 XML 实体(“ 等),以便它们在编辑后得以保留。使用 --merge-runs false 可跳过 run 合并。
编辑 unpacked/word/ 中的文件。有关模式,请参见下文的 XML 参考。
对于修订跟踪和批注,使用 "Claude" 作为作者,除非用户明确要求使用其他名称。
直接使用编辑工具进行字符串替换。不要编写 Python 脚本。 脚本会引入不必要的复杂性。编辑工具会准确显示正在替换的内容。
关键:为新内容使用智能引号。 当添加带有撇号或引号的文本时,使用 XML 实体来生成智能引号:
<!-- 使用这些实体以获得专业的排版效果 -->
<w:t>Here’s a quote: “Hello”</w:t>
| 实体 | 字符 |
|---|---|
‘ | ‘(左单引号) |
’ | ’(右单引号 / 撇号) |
“ | “(左双引号) |
” | ”(右双引号) |
添加批注: 使用 comment.py 来处理跨多个 XML 文件的样板代码(文本必须是预先转义的 XML):
python scripts/comment.py unpacked/ 0 "批注文本包含 & 和 ’"
python scripts/comment.py unpacked/ 1 "回复文本" --parent 0 # 回复批注 0
python scripts/comment.py unpacked/ 0 "文本" --author "自定义作者" # 自定义作者名称
然后将标记添加到 document.xml(参见 XML 参考中的“批注”部分)。
python scripts/office/pack.py unpacked/ output.docx --original document.docx
使用自动修复功能进行验证,压缩 XML,并创建 DOCX。使用 --validate false 可跳过验证。
自动修复将修复:
durableId >= 0x7FFFFFFF(重新生成有效的 ID)<w:t> 上缺少 xml:space="preserve"自动修复无法修复:
<w:r> 元素:当添加修订跟踪时,将整个 <w:r>...</w:r> 块替换为 <w:del>...<w:ins>... 作为兄弟元素。不要在 run 内部注入修订跟踪标签。<w:rPr> 格式:将原始 run 的 <w:rPr> 块复制到您的修订跟踪 run 中,以保持加粗、字体大小等格式。<w:pPr> 中的元素顺序:<w:pStyle>、<w:numPr>、<w:spacing>、<w:ind>、<w:jc>、<w:rPr> 最后<w:t> 上添加 xml:space="preserve"00AB1234)插入:
<w:ins w:id="1" w:author="Claude" w:date="2025-01-01T00:00:00Z">
<w:r><w:t>插入的文本</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>删除的文本</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>正在删除的整个段落内容...</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>他们插入的文本</w:delText></w:r>
</w:del>
</w:ins>
恢复另一位作者的删除 - 在其删除之后添加插入(不要修改他们的删除):
<w:del w:author="Jane" w:id="5">
<w:r><w:delText>删除的文本</w:delText></w:r>
</w:del>
<w:ins w:author="Claude" w:id="10">
<w:r><w:t>删除的文本</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>已删除</w:delText></w:r>
</w:del>
<w:r><w:t> 更多文本</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>文本</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>
word/media/word/_rels/document.xml.rels:<Relationship Id="rId5" Type=".../image" Target="media/image1.png"/>
[Content_Types].xml:<Default Extension="png" ContentType="image/png"/>
<w:drawing>
<wp:inline>
<wp:extent cx="914400" cy="914400"/> <!-- EMUs: 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>
npm install -g docx(新文档)scripts/office/soffice.py 为沙盒环境自动配置)pdftoppm 用于图像生成每周安装次数
66
代码仓库
GitHub 星标数
1
首次出现
2026年2月20日
安全审计
已安装于
cursor65
github-copilot65
codex65
amp65
kimi-cli65
gemini-cli65
Skills CLI 使用指南:AI Agent 技能包管理器安装与管理教程
48,700 周安装