@ 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。 。 。 . |
removeEmptyParagraphs | false | 丢出一个段落,其唯一内容是变为空的标记(没有条款或标题时没有空白行). |
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
- 页眉、页脚、脚注和尾注与正文一起处理.
- 文本框内的标记工作;跨越文本框边界的标记不工作.
- 重复的图片获得新鲜
docPrids,所以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 是个柜台.