@dealops/doxy

中文文档
0.1.5发布于 2026/10/27MITTypeScriptESM + CJS近 30 天下载 2288

填充.docx模板:替换QQtags}},重复表格行和段落,插入图像. 包括编辑.docx文件的Google Docs API兼容适配器(documents.get / 批量Update). 运行于节点,浏览器,和Cloudflare Workers.

中文使用文档
文档目录14 节
  1. 模板语法
  2. 再说一遍
  3. 来自数据的表格行
  4. 移除标签中的位置
  5. 将标记留给后一步( 电子签名锚)
  6. 多个段块
  7. 图像
  8. API 密码
  9. 有标签的 Google Docs
  10. 正在准备 Google Docs 导出( normalizeTemplate (中文(简体) )
  11. 两次 renderDocxPasses (中文(简体) )
  12. 完成已提交文件( postprocessDocx (中文(简体) )
  13. 页:1
  14. Google Docs API 兼容适配器( @dealops/doxy/gdocs (中文(简体) )

@ dealops/ doxy( 密码)

填充 .docx 有数据的模板。 替换 {{tags}},重复表格行和数组段落,插入图像,并获得一个 .docx 回来 没有本地的依赖性:它运行在节点,浏览器,和Cloudflare Workers.

npm install @dealops/doxy
import { renderDocx } from '@dealops/doxy';
import { readFileSync, writeFileSync } from 'node:fs';

const output = renderDocx(readFileSync('order-form.docx'), {
  customer: { name: 'Acme Corp' },
  date: new Date(),
  lines: [
    { sku: 'SEAT', qty: 10, price: '$100.00' },
    { sku: 'SUPPORT', qty: 1, price: '$500.00' },
  ],
  notes: ['Net 30', 'Auto-renews annually'],
  logo: { type: 'image', data: readFileSync('logo.png'), width: 120 },
});

writeFileSync('acme-order-form.docx', output);

模板语法

直接用Word或Google Docs来写标签. 标记保留它们所替换的文本的格式,一个由Word分解为多行的标记(因为拼写检查标记或格式修改)仍然有效.

标记含义
{{name}} 编号 : {{customer.name}} 编号 : {{Payment Terms}}插入一个值。 多点路径行走嵌入对象;名称可能包含空格.
{{#items}} … {{/items}}重复每个数组元素的封装内容 。 进去, {{.}} 是元素,其属性属于范围.
{{#flag}} … {{/flag}}具有非阵列值: 如果真伪( 对象成为范围) 一次渲染, 否则删除 .
{{^items}} … {{/items}}倒置部分:仅在值缺失时才渲染出, falsy, 或为空数组.
{{! comment }}从输出中删除 .
{{@index}} {{@number}} {{@first}} {{@last}} {{@length}}循环元数据(基于0的索引,基于1的数字,布尔斯,数组长度).

值:字符串作为-is插入(\n 成为换行符, \t 标签、数字和布林是字符串, Date 变成 YYYY-MM-DD, (中文(简体) ), null编号 :undefined 成为空文本。 过 formatValue 来改变这一切.

再说一遍

重复单位从打开和关闭标签的位置选择:

打开和关闭的标签是..重复单位
在同一段中只是它们之间的内容(内含)
在同一表格行的不同单元格中整个行
在表格的不同行中从打开的标签行到关闭标签行
不同段落从第一至最后一段

仅包含一节标签的段落或表格行被删除,因此本表格模板:

产品齐特
{{#lines}}{{sku}}{{qty}}{{/lines}}

使每行项目各有一行,这行的标记在自己的行上也一样:

产品齐特
{{#lines}}
{{sku}}{{qty}}
{{/lines}}

要强制单位,请在打开的标签上添加后缀: {{#items:p}} 重复整段(项目清单), {{#items:tr}} 桌排, {{#items:tc}} 牢房, {{#items:tbl}} 整个桌子.

来自数据的表格行

许多真实的模板(特别是Google Docs导出)已经包含有页眉行和单占位符行的表格,例如:

收费类型单位价格齐特小计
{{fees}}

传出 a rows 值和该行每数据行一次被填充,逐个单元格,保留每个单元格的格式。 占位符行本身被删除 :

fees: {
  type: 'rows',
  rows: [
    ['Platform Subscription', '$45,000.00', 1, '$45,000.00'],
    ['Credits', '$1.00', 40000, '$40,000.00'],
    [{ text: 'Total', bold: true }, null, null, { text: '$85,000.00', bold: true }],
  ],
  emptyTable: 'remove',   // optional: with zero rows, delete the whole table instead of leaving the header
}

单元格接受字符串、数字, { text, bold?, italic?, underline?, strike? }, (中文(简体) ), { runs: [...], align?: 'left' | 'center' | 'right', shading? },或 { paragraphs: [...], shading? } 对于一个单元格中的若干段落(例如,产品说明有 " - " 一行)。 排队也可以是 { cells: [...], shading: 'efefef' } 以填充该行的每个单元格(hex RGB, # 可选); 单元格本身 shading 胜出,或者替换模板单元格的背景。 使用 strike: true 为已删除的行项目运行. emptyTable 也可能是 'remove-with-heading',它又删除了表格上方的标题段落和下方的一个空白段落,因此未使用的"证书"部分完全消失.

A 类 rows 值可能包含 columnHeaders: string[] 对于不给出自己标题的模板:一个只作为占位符行的表格,它们作为粗体第一行(只有在有数据行时),在运行的文本中,一个标记将其打印为粗体分页行并排在分页行上。 一张有自己头排的桌子忽略了它们;没有行, emptyTable 决定.

移除标签中的位置

A 类 remove 值删除标签周围的结构。 用于可选摘要行和可选章节而不修改模板 :

startup_discount: { type: 'remove', unit: 'row' },            // drop this row of the pricing summary
credits_table:    { type: 'remove', unit: 'table-with-heading' },  // drop the table, its heading, and the blank line after it
footnote:         { type: 'remove', unit: 'paragraph' },

单位 : paragraph, (中文(简体) ), row (桌子也是最后一行的时候), table, (中文(简体) ), table-with-heading。 。 。 .

将标记留给后一步( 电子签名锚)

装入 onMissing: 'keep' 并且简单地从数据中省去这些密钥: {{sig_customer_0}} 在文档中保留 DocuSign 锁定匹配,同时其它所有都填充.

多个段块

A 类 paragraphs 值用真实段落(而不是换行符)来替换标记,每个段落复制,并在标记位置运行格式。 用于条款和术语:

terms: {
  type: 'paragraphs',
  paragraphs: [
    { runs: [{ text: 'A. Taxes.', bold: true }, ' Charges exclude taxes.'] },
    { runs: [{ text: 'B. Renewal.', bold: true }, ' Renews for successive 12-month terms.'] },
    'Plain paragraph',
  ],
}

如果标签与其他文本或其他标签共享其段落,则段落被分拆,所以 {{clause_a}}{{clause_b}}Fixed text 其中一款成为条款A的段落,然后成为条款B的段落,然后成为固定案文。 一个空的 paragraphs: [] 是一个已拒绝的条款:标记被清除,它留下的段落在文档允许的地方被丢弃;一个双行条款框(标签行于一个体格上),它留下的正体被取出,因此几个已拒绝的条款中的最后一部分随它一起取出。 带有打印条款的框,图像,嵌入的表或标签仍留在其标签行中.

图像

传递对象而不是字符串 :

{ type: 'image', data: Uint8Array | ArrayBuffer, width?: number, height?: number, format?: 'png' | 'jpeg' | 'gif' | 'bmp', alt?: string }

大小以像素为单位,为96 dpi. 给出一个维度,而另一个维度则遵循图像的外观比;给出无并使用内在大小.

API 密码

renderDocx(template: Uint8Array | ArrayBuffer, data: Record<string, unknown>, options?: RenderOptions): Uint8Array
getTemplateTags(template, options?): TagInfo[]   // [{ name, kind: 'value' | 'section' | 'inverted', parents, part }]
DocxTemplate.from(template).render(data, options?) // same thing, object style

RenderOptions数字 :

选项默认说明
delimiters{ start: '{{', end: '}}' }自定义标记分隔符 .
onMissing'empty''empty' 插入 nullText, (中文(简体) ), 'keep' 把标签放在原地, 'throw' 掷出 DoxyTemplateError。 。 。 .
nullText''文本为 null编号 :undefined。 。 。 .
removeEmptyParagraphsfalse丢出一个段落,其唯一内容是变为空的标记(没有条款或标题时没有空白行).
formatValue(value, tagName) => string | BlockValue | undefined; 返回时 undefined 以使用默认值.
parts文档、页眉、页脚、脚注、尾注优先选择 zip 条目名称来选择哪些部件被处理 .

模板出错( 未关闭的段落, 不匹配的相近标签) srow DoxyTemplateError 与部分名称.

有标签的 Google Docs

开车 .docx 导出带有标签的 Doc 以标签的名称打开 Title 段落中的一段,该段作为额外的首页打印. stripTabTitleSection(docx) 在文档以这种方式开始时删除该主区域,并返回字节。 工人将它应用于每个 Drive 导入.

正在准备 Google Docs 导出(normalizeTemplate(中文(简体) )

normalizeTemplate(docx) 返回时 { docx, changed, strippedTitleSection, droppedRuns, cellsWithMargins }: 上面的标签标题部分被剥去, 运行时不会使任何内容被丢弃( Drive 写道 ) w:rtl- 只插入每个空白段落, 文本填入这样的单元格会复制运行的段落标记的字体和大小, 而每个没有自己的表格单元格 w:tcMar 从表格样式链、默认表格样式和表格的 w:tblCellMar (谷歌声) .docx 导入时忽略样式级的粘贴,但保持每个单元格的边距。 在输出上再次运行它不会改变任何东西。 这些部件也出口: dropEmptyRuns(root), (中文(简体) ), inlineCellMargins(root, readTableStyles(stylesXml)), (中文(简体) ), stripTabTitleSectionXml(doc)。 。 。 .

两次renderDocxPasses(中文(简体) )

renderDocxPasses(template, data, options, maxPasses) 生成,然后用相同的数据再次生成结果,同时它仍然包含一个标签,其顶级密钥在其中 data (单位:千美元)hasKnownTag),最多为 maxPasses 时间。 类似 "Payment: {{payment_terms}}" 在第二个通行证上解析;标记数据不知道(电子签名主锚保存在 onMissing: 'keep')从不触发一个,而一个指自己停止于约束的值.

完成已提交文件(postprocessDocx(中文(简体) )

postprocessDocx(docx, spec) 适用,顺序如下:

  • hideTextPatterns 编号 : removeTextPatterns:正则表达式与每个段落的合并文本相匹配,在正文,表格单元格,页眉和页脚中. 匹配从不跨越图像、段落或片段。 隐藏文本变为白 (w:color FFFFFF,而不是 Word 的隐藏文本属性,所以它留在 PDF 文本层中;删除已删除的文本。 删除胜于两个重叠之处;零长匹配被忽略.
  • links: 每起事件的第一个确切发生时间 text 在正文中(包括表格单元格,横行)成为超链接 url (https或https),保留文本及其其他格式.

结果报告 hidden, (中文(简体) ), removed 财务报告和审定财务报表 linked。 。 。 .

页:1

  • 页眉、页脚、脚注和尾注与正文一起处理.
  • 文本框内的标记工作;跨越文本框边界的标记不工作.
  • 重复的图片获得新鲜 docPr ids,所以Word不会抱怨重复.
  • Google Docs: 输入文件中的标记, 然后 File → Download → Microsoft Word (.docx) 或通过 Drive API 导出。 此回放中的同伴工人会自动做到这一点.

麻省理工学院

Google Docs API 兼容适配器(@dealops/doxy/gdocs(中文(简体) )

如果您已经用 Google Docs API 生成文档(documents.get 寻找索引, documents.batchUpdate 与 replaceAllText, (中文(简体) ), insertTableRow, (中文(简体) ), insertText,可以指向一个 .docx 换句话说 适配器会曝光相同的资源形状,相同的索引规则(在 0–1 分节断开,段落以 \n,表格为表格花费了一个索引,每行1个,每单元格1个,结尾1个)和同一个请求对象,并将其应用于Word XML.

import { DocsCompatClient } from '@dealops/doxy/gdocs';

const docs = new DocsCompatClient();                 // stands in for google.docs({ version: 'v1', auth })
const documentId = docs.open(templateBytes);         // instead of a Drive file id

const { data: doc } = await docs.documents.get({ documentId });
// …walk doc.body.content exactly as before to find '{{recurring_table_0}}' and its table…

await docs.documents.batchUpdate({
  documentId,
  requestBody: {
    requests: [
      { replaceAllText: { containsText: { text: '{{customer_name}}', matchCase: true }, replaceText: 'Acme' } },
      { insertTableRow: { tableCellLocation: { tableStartLocation: { index: tableStart }, rowIndex: 1 }, insertBelow: true } },
      { insertText: { location: { index: cellIndex + 1 }, text: 'Platform' } },
      { updateTextStyle: { range: { startIndex, endIndex }, textStyle: { bold: true }, fields: 'bold' } },
    ],
  },
});

const output = docs.export(documentId);              // .docx bytes

下一级表格 : new DocxDocument(bytes).get() 编号 : .batchUpdate(requests) 编号 : .toBytes(),以及 applyDocsRequests(bytes, requests) 为了一个镜头. docs.documents.create() 从空白信件页开始.

支持的请求: replaceAllText, (中文(简体) ), insertText, (中文(简体) ), deleteContentRange, (中文(简体) ), updateTextStyle (粗体、斜体、下划线、切入、小Cap、字体Size、前景/背景颜色、字体家族、基线抵消、链接), updateParagraphStyle (名称样式、对齐、间隔、缩进、与-next、边框), insertTable, (中文(简体) ), insertTableRow, (中文(简体) ), insertTableColumn, (中文(简体) ), deleteTableRow, (中文(简体) ), deleteTableColumn, (中文(简体) ), updateTableCellStyle, (中文(简体) ), updateTableColumnProperties, (中文(简体) ), updateTableRowStyle, (中文(简体) ), pinTableHeaderRows, (中文(简体) ), createParagraphBullets 编号 : deleteParagraphBullets, (中文(简体) ), insertInlineImage 编号 : replaceImage (http(s)和 data: URIs; 通过 fetchImage 用于控制取回, insertPageBreak, (中文(简体) ), updateDocumentStyle。页眉和页脚可以通过 segmentId (单位:千美元)header1, (中文(简体) ), footer1,...;见 documentStyle.defaultHeaderId) (中文(简体) ).

其它任何东西mergeTableCells,命名范围、脚注、分节、评论、标签、建议、人员/日期/下拉芯片) UnsupportedRequestError 命名请求,所以迁移会大声地失败,而不是沉默地失败. 要记住的区别:文本样式只从直接运行格式中报告(不是通过命名样式解决), documents.get 返回无 namedStyles,以及 revisionId 是个柜台.

版本与发布记录

@dealops/doxy 共发布 7 个版本,首次发布于 2026/10/20;过去 12 个月发布了 7 个版本。

版本发布日期距今
0.1.5latest2026/10/2717 天前
0.1.42026/10/2123 天前
0.1.32026/10/2123 天前
0.1.22026/10/2024 天前
0.1.12026/10/2024 天前
0.1.02026/10/2024 天前

关于 @dealops/doxy 的常见问题

@dealops/doxy 是什么?

填充.docx模板:替换QQtags}},重复表格行和段落,插入图像. 包括编辑.docx文件的Google Docs API兼容适配器(documents.get / 批量Update). 运行于节点,浏览器,和Cloudflare Workers.

如何安装 @dealops/doxy?

在项目目录运行 npm install @dealops/doxy,或使用 pnpm add / yarn add / bun add。

@dealops/doxy 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@dealops/doxy 支持 ESM 还是 CommonJS?

同时提供 ES Module 与 CommonJS 入口,import 和 require() 都可以使用,打包工具会自动选择合适的版本。

@dealops/doxy 可以商用吗?

@dealops/doxy 使用 MIT 许可。宽松许可:允许商业使用、修改和再分发,通常只需在分发时保留原版权与许可声明。以上为概括,不构成法律意见,请以许可原文为准。

@dealops/doxy 的下载量有多少?

根据 npm 官方下载统计,@dealops/doxy 在 2026/10/12 – 2026/11/10 这 30 天内被下载 2,288 次,日均约 76 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@dealops/doxy 还在维护吗?

最新版本 0.1.5 发布于 2026/10/27(17 天前);至今共发布 7 个版本,其中过去 12 个月发布了 7 个;首次发布于 2026/10/20。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。