将文本合并到文档中

本指南介绍了如何使用 Google 文档 API 将一个或多个外部数据源中的信息合并到现有模板文档中。

模板是一种包含固定文本和动态内容占位符的文档。例如,合同模板可能包含固定文本,其中包含收件人姓名和地址的占位符。然后,应用会将用户特定数据合并到模板中,以创建最终文档。

这种方法之所以有用,原因有以下几点:

  • 设计人员可以使用 Google 文档微调文档的设计。这比在应用中调整参数来设置渲染的布局要简单得多。

  • 将内容与展示分离是一项众所周知的设计原则,可带来诸多好处。

图表:显示了如何将来源中的数据合并到模板中以创建文档。
图 1. 将数据合并到模板中以创建文档。

文档合并的运作方式

以下示例展示了如何使用 Docs API 将数据合并到文档中:

  1. 使用占位内容创建文档,以便您进行设计和格式设置。系统会保留您要替换的任何文本格式。

  2. 对于您要插入的每个元素,请将占位内容替换为标记。请务必使用不太可能正常出现的字符串。例如,{{account-holder-name}} 可能是一个不错的标记。

  3. 在代码中,使用 Google Drive API 复制文档。

  4. 在代码中,使用 Docs API 的 batchUpdate 方法并提供文档名称,同时添加 ReplaceAllTextRequest

文档 ID 用于引用文档,可以从网址中获取:

https://docs.google.com/document/d/DOCUMENT_ID/edit

管理模板

对于应用定义和拥有的模板文档,请使用代表应用的专用账号创建模板。服务账号是不错的选择,可避免因 Google Workspace 政策限制共享而导致的问题。

从模板创建文档实例时,请始终使用最终用户凭据。这样一来,用户就可以完全掌控生成的文档,并避免与 Google 云端硬盘中每个用户的限制相关的扩缩问题。

如需使用服务账号创建模板,请使用应用凭据执行以下步骤:

  1. 使用 documents.create 在 Docs API 中创建文档。
  2. 更新权限,以允许文档收件人使用 Drive API 中的 permissions.create 读取文档。
  3. 更新权限,以允许模板作者使用 Drive API 中的 permissions.create 向其写入内容。
  4. 根据需要修改模板。

如需创建文档的实例,请使用用户凭据执行以下步骤:

  1. 使用 Drive API 中的 files.copy 创建模板的副本。
  2. 使用 Docs API 中的 documents.batchUpdate 替换值。

示例:将数据合并到模板中

以下代码示例展示了如何将模板的所有标签页中的两个字段替换为实际值,以生成最终文档:

图片显示了带有标记占位符的文档模板以及合并后的文档。
图 2. 将标记占位符替换为值。

如需执行此合并,请使用以下代码:

Java

String customerName = "Alice";
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy/MM/dd");
String date = formatter.format(LocalDate.now());

// Make a copy of the template document using the Drive API.
String copyTitle = "Merged Document";
File copyMetadata = new File().setName(copyTitle);
File documentCopyFile =
        driveService.files().copy(DOCUMENT_ID, copyMetadata).execute();
String documentCopyId = documentCopyFile.getId();

List requests = new ArrayList<>();
// One option for replacing all text is to specify all tab IDs.
requests.add(new Request()
        .setReplaceAllText(new ReplaceAllTextRequest()
                .setContainsText(new SubstringMatchCriteria()
                        .setText("{{customer-name}}")
                        .setMatchCase(true))
                .setReplaceText(customerName)
                .setTabsCriteria(new TabsCriteria()
                        .addTabIds(TAB_ID_1)
                        .addTabIds(TAB_ID_2)
                        .addTabIds(TAB_ID_3))));
// Another option is to omit TabsCriteria if you are replacing across all tabs.
requests.add(new Request()
        .setReplaceAllText(new ReplaceAllTextRequest()
                .setContainsText(new SubstringMatchCriteria()
                        .setText("{{date}}")
                        .setMatchCase(true))
                .setReplaceText(date)));

BatchUpdateDocumentRequest body = new BatchUpdateDocumentRequest();
service.documents().batchUpdate(documentCopyId, body.setRequests(requests)).execute();

Node.js

  let customerName = 'Alice';
  let date = yyyymmdd()
  let requests = [
    // One option for replacing all text is to specify all tab IDs.
    {
      replaceAllText: {
        containsText: {
          text: '{{customer-name}}',
          matchCase: true,
        },
        replaceText: customerName,
        tabsCriteria: {
          tabIds: [TAB_ID_1, TAB_ID_2, TAB_ID_3],
        },
      },
    },
    // Another option is to omit TabsCriteria if you are replacing across all tabs.
    {
      replaceAllText: {
        containsText: {
          text: '{{date}}',
          matchCase: true,
        },
        replaceText: date,
      },
    },
  ];

  // Make a copy of the template document using the Drive API.
  let copyTitle = 'Merged Document';
  driveService.files.copy({
    fileId: '1yBx6HSnu_gbV2sk1nChJOFo_g3AizBhr-PpkyKAwcTg',
    resource: {
      name: copyTitle,
    },
  }, (err, driveResponse) => {
    if (err) return console.log('The Drive API returned an error: ' + err);
    let documentCopyId = driveResponse.data.id;

    google.options({auth: auth});
    google
        .discoverAPI(
            'https://docs.googleapis.com/$discovery/rest?version=v1&key={YOUR_API_KEY}')
        .then(function(docs) {
          docs.documents.batchUpdate(
              {
                documentId: documentCopyId,
                resource: {
                  requests,
                },
              },
              (err, {data}) => {
                if (err) return console.log('The API returned an error: ' + err);
                console.log(data);
              });
        });
  });

Python

customer_name = 'Alice'
date = datetime.datetime.now().strftime("%y/%m/%d")

# Make a copy of the template document using the Drive API.
copy_title = 'Merged Document'
body = {
    'name': copy_title
}
drive_response = drive_service.files().copy(
    fileId=DOCUMENT_ID, body=body).execute()
document_copy_id = drive_response.get('id')

requests = [
        # One option for replacing all text is to specify all tab IDs.
        {
        'replaceAllText': {
            'containsText': {
                'text': '{{customer-name}}',
                'matchCase':  'true'
            },
            'replaceText': customer_name,
            'tabsCriteria': {
                'tabIds': [TAB_ID_1, TAB_ID_2, TAB_ID_3],
            },
        }},
        # Another option is to omit TabsCriteria if you are replacing across all tabs.
        {
        'replaceAllText': {
            'containsText': {
                'text': '{{date}}',
                'matchCase':  'true'
            },
            'replaceText': str(date),
        }
    }
]

result = service.documents().batchUpdate(
    documentId=document_copy_id, body={'requests': requests}).execute()

处理动态列表和表格

标准文档合并使用 ReplaceAllTextRequest 来替换单个一次性占位符(例如 {{customer-name}}{{date}})。不过,如果您的数据包含动态列表项(例如账单中的行、已订购的产品列表或动态表格),则无法使用标准文本替换,因为在模板设计期间,系统无法确定项的数量。

如需处理动态列表内容,请使用以下策略之一。

选项 1:将行附加到模板表格

如果您的模板文档已包含格式化表格(例如,包含标题行和单个占位行),您可以动态克隆并填充列表中的每个项目对应的行:

  1. 读取模板结构:使用 documents.get 方法找到表格并确定模板行的索引
  2. 插入新行:对于数据列表中的每个项(第一个项除外,该项可以重复使用现有的模板行),调用 InsertTableRowRequest 以在模板行下方插入新行。
  3. 填充单元格数据:通过替换模板行中的占位符来填充单元格。对于新创建的行,请使用 InsertTextRequest 将相应文本插入每个单元格的坐标位置。

如需查看有关如何插入表格行的示例,请参阅处理表格

方法 2:将标记替换为生成的表格

如果您想以编程方式从头构建表格,请执行以下操作:

  1. 放置占位符标记:在模板文档中使用单个标记(例如 {{invoice-table}})来标记列表应放置的位置。
  2. 找到占位符:使用搜索操作找到代码的起始索引
  3. 删除占位符:使用 DeleteContentRangeRequest 移除 {{invoice-table}} 文本。
  4. 插入表格:在该起始索引处发送 InsertTableRequest,并根据数据源指定行数和列数。
  5. 写入值:按顺序填充每个表格单元格。

如需查看以编程方式插入表的示例,请参阅使用表格