テキストをドキュメントに結合する

このガイドでは、Google ドキュメント API を使用して、1 つ以上の外部データソースの情報を既存のテンプレート ドキュメントに統合する方法について説明します。

テンプレートは、固定テキストと動的コンテンツのプレースホルダを含むドキュメントの一種です。たとえば、契約テンプレートには、受取人の名前と住所のプレースホルダを含む固定テキストが含まれている場合があります。アプリは、ユーザー固有のデータをテンプレートに統合して、完成したドキュメントを作成します。

このアプローチが有用な理由はいくつかあります。

  • デザイナーは、Google ドキュメントを使用してドキュメントのデザインを微調整できます。これは、アプリでパラメータを調整してレンダリングされたレイアウトを設定するよりも簡単です。

  • コンテンツとプレゼンテーションを分離することは、多くのメリットがあるよく知られた設計原則です。

ソースのデータがテンプレートにマージされてドキュメントが作成される仕組みを示す図。
図 1. データをテンプレートに統合してドキュメントを作成します。

ドキュメントの統合の仕組み

Docs API を使用してドキュメントにデータを統合する方法の例を次に示します。

  1. プレースホルダ コンテンツを使用してドキュメントを作成し、デザインと形式を調整します。置き換えるテキストの書式設定は保持されます。

  2. 挿入する要素ごとに、プレースホルダのコンテンツをタグに置き換えます。通常は発生しない文字列を使用してください。たとえば、{{account-holder-name}} は適切なタグです。

  3. コードで Google Drive API を使用して、ドキュメントのコピーを作成します。

  4. コードで、ドキュメント名とともに Docs API の batchUpdate メソッドを使用し、ReplaceAllTextRequest を含めます。

ドキュメント ID はドキュメントを参照し、URL から取得できます。

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

テンプレートを管理

アプリが定義して所有するテンプレート ドキュメントについては、アプリを表す専用のアカウントを使用してテンプレートを作成します。サービス アカウントは、共有を制限する Google Workspace ポリシーによる複雑さを回避できるため、適切な選択肢です。

テンプレートからドキュメントのインスタンスを作成する場合は、常にエンドユーザーの認証情報を使用します。これにより、ユーザーは生成されたドキュメントを完全に制御でき、Google ドライブのユーザーごとの上限に関連するスケーリングの問題を防ぐことができます。

サービス アカウントを使用してテンプレートを作成するには、アプリの認証情報を使用して次の手順を行います。

  1. Docs API で documents.create を使用してドキュメントを作成します。
  2. Drive API の permissions.create を使用して、ドキュメントの受信者がドキュメントを読めるように権限を更新します。
  3. Drive API の permissions.create を使用して、テンプレート作成者が書き込めるように権限を更新します。
  4. 必要に応じてテンプレートを編集します。

ドキュメントのインスタンスを作成するには、ユーザー認証情報を使用して次の手順を行います。

  1. Drive API の files.copy を使用して、テンプレートのコピーを作成します。
  2. Docs API で documents.batchUpdate を使用して値を置き換えます。

例: データをテンプレートに差し込む

次のコードサンプルは、テンプレートのすべてのタブで 2 つのフィールドを実際の値に置き換えて、完成したドキュメントを生成する方法を示しています。

タグのプレースホルダを含むドキュメント テンプレートと、その結果として作成されたドキュメントを示す画像。
図 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 を使用して、個々の 1 回限りのプレースホルダ({{customer-name}}{{date}} など)を置き換えます。ただし、データにアイテムの動的リスト(請求書の明細行、注文した商品のリスト、動的テーブルなど)が含まれている場合、テンプレートの設計時にアイテムの数が不明であるため、標準のテキスト置換を使用できません。

動的リストのコンテンツを処理するには、次のいずれかの方法を使用します。

オプション 1: テンプレート テーブルに行を追加する

テンプレート ドキュメントにすでに書式設定された表(ヘッダー行と 1 つのプレースホルダ行など)が含まれている場合は、リスト内の各項目の行を動的に複製して入力できます。

  1. テンプレート構造を読み取る: documents.get メソッドを使用してテーブルを見つけ、テンプレート行のインデックスを特定します。
  2. 新しい行を挿入する: データリストの各項目(既存のテンプレート行を再利用できる最初の項目を除く)に対して、InsertTableRowRequest を呼び出して、テンプレート行の下に新しい行を挿入します。
  3. セルデータを入力する: テンプレート行のプレースホルダを置き換えて、セルに入力します。新しく作成された行については、InsertTextRequest を使用して、各セルの座標位置にそれぞれのテキストを挿入します。

テーブルの行を挿入する方法の例については、テーブルを操作するをご覧ください。

オプション 2: タグを生成されたテーブルに置き換える

テーブルをプログラムでゼロから構築する場合は、次の手順を行います。

  1. プレースホルダ タグを配置する: テンプレート ドキュメントで 1 つのタグ({{invoice-table}} など)を使用して、リストを配置する場所をマークします。
  2. プレースホルダを見つける: 検索オペレーションを使用して、タグの開始インデックスを見つけます。
  3. プレースホルダを削除する: DeleteContentRangeRequest を使用して {{invoice-table}} テキストを削除します。
  4. テーブルを挿入する: その開始インデックスで InsertTableRequest を送信し、データソースに基づいて行数と列数を指定します。
  5. 書き込み値: 各テーブル セルに順番に値を入力します。

プログラムでテーブルを挿入する例については、テーブルの操作をご覧ください。