※本記事はプロモーションを含みます
目次
Googleスプレッドシートに溜まった顧客リストや請求情報をもとに、個別の契約書や送付状、請求書を1件ずつ手作業でGoogleドキュメントに転記する作業は、件数が増えるほど転記ミスや抜け漏れが発生しやすくなります。なので、この作業をスクリプトで自動化する場合は、雛形となるドキュメント内にプレースホルダーとなる目印の文字列を埋め込んでおき、プログラム側からその文字列を検索して動的な値へ一気に書き換える手法が堅牢です。
本記事では、Google Apps Script(GAS)を使ってGoogleドキュメントのテンプレート内に用意したプレースホルダー文字列を特定の値へ置換し、確定保存して文書を自動生成するまでの流れを整理します。まずは全体の流れを確認しましょう。
テンプレートを手作業で複製して打ち替えていた頃と、置換スクリプトで吐き出す運用の違い
Googleスプレッドシート管理の台帳から帳票を作成するとき、以前は台帳を開きながら雛形となるGoogleドキュメントを「コピーを作成」で複製し、氏名や会社名、金額などの項目を画面上で選択して手動で打ち替える運用が一般的でした。ただし、このやり方では差し替え漏れが頻繁に起きます。というのは、前回の取引先名が本文の途中に残ったまま送付してしまったり、金額の桁数を間違えて入力してしまったりするからです。なので、1件あたり数分かかる作業も、月間で数十件、数百件と積み重なると何時間もの工数を奪うことになります。
一方、GASを使ってGoogleドキュメントのテンプレート文字列を一括置換する運用へ移行すると、作業の性質が根本から変わります。というのは、雛形にあらかじめ二重中括弧などで囲んだ共通の目印({{名前}}、{{会社名}}など)を配置しておき、スプレッドシートの各行から取得した配列データを流し込むだけで、一連の置換処理が数秒で完了するからです。人間が介在するポイントはスプレッドシート上の元データを整える部分だけになるので、誤字脱字や消し忘れといったヒューマンエラーを仕組み側で完全に遮断できるようになります。
ただし、手動運用のもうひとつの問題として、作成されたドキュメントのファイル名や保管先フォルダが担当者ごとにバラバラになりがちだった点が挙げられます。なので、ツールによる自動生成を取り入れると、置換処理と同時に保存先フォルダの指定や、日付・顧客名を含めた統一フォーマットでのファイル名設定までを一貫して自動化できます。結果として、契約書や請求書を自動生成する運用フロー全体が標準化され、誰が実行しても同じ品質の文書が出力される状態を作ることができます。まずは自社の運用ルールを確認しましょう。
プレースホルダーの設計ルールと置換処理を走らせる基本スクリプト
Googleドキュメント側のテンプレートを作成する際、まず重要になるのがプレースホルダーの命名設計です。ただし、本文中に「名前」という単語がそのまま存在する場合、単に「名前」を置換対象にしてしまうと、「お客様のお名前を記載してください」といった説明文まで巻き込んで書き換わってしまう事故が起こります。なので、本文中で自然言語として出現しない記号で囲むルールを統一することが不可欠です。実際の現場では、二重波括弧を使った「{{会社名}}」や「{{金額}}」、あるいは角括弧を用いた「[[契約日]]」といった記号の組み合わせが広く採用されています。
GASからGoogleドキュメント内のプレースホルダーを置換する際は、DocumentAppサービスを使用します。対象ドキュメントをID指定で開き、本文を表現するBodyオブジェクトを取得した上で、replaceTextメソッドを呼び出します。なので、最後に編集内容をディスクへ確定させるためにsaveAndCloseを実行します。
function replaceDocumentTemplate() { ... }
このコードを実行する前に考慮すべきポイントは、元のテンプレートファイルを直接開いて置換してはいけないという点です。というのは、上記の最小コードは「すでに存在するドキュメントの文字を書き換える」動作を行うため、雛形そのものに対して実行すると、一度置換されたプレースホルダーは二度と元に戻らないからです。なので、次回以降の実行で「{{名前}}」という文字列が存在しなくなり、二重実行ができなくなります。
なので、実際の業務ではDriveAppを使って元テンプレートを別名で複製し、その新しく作られたコピー先ファイルのIDに対してDocumentApp.openByIdを実行するのが鉄則です。雛形は常にプレースホルダーが残ったクリーンな状態を保ち、複製された新規ファイル側だけを加工することで、何度でも安定して差し替え処理を繰り返すことが可能になります。まずは手元のテストファイルで動作を確認しましょう。
スプレッドシートの行データから大量の個別ファイルを複製・差し替え出力するフロー
単発の置換ができるようになったら、次はGoogleスプレッドシートのシート上にある一覧データをループ処理して、行ごとに個別のGoogleドキュメントを生成していく処理へと拡張します。ただし、この段階でつまずきやすいのが、DriveAppとDocumentAppを行き来する際のリソース負荷と実行時間です。
スプレッドシートからgetDataRange().getValues()で二次元配列を取得し、ヘッダー行を除いた各レコードを反復処理します。1行処理するごとに、指定した親フォルダ内にテンプレートファイルをmakeCopyで複製し、複製されたファイルのIDを使ってDocumentAppで中身を展開します。なので、{{会社名}}、{{担当者名}}、{{請求金額}}、{{発行日}}といった該当列の値を順次replaceTextで流し込んでいきます。
ここで気をつけるべきは、スプレッドシート側でセルに数値や日付が入っている場合、GAS上ではそれらがJavaScriptのNumberオブジェクトやDateオブジェクトとして読み込まれる点です。というのは、replaceTextメソッドの第2引数には文字列(string)を渡す必要があるからです。もしDateオブジェクトや数値をそのまま渡してしまうと、型不一致によるエラーが発生したり、日付が「Wed Oct 25 2023 00:00:00 GMT+0900 (JST)」のような意図しないフォーマットで差し込まれたりします。なので、数値を渡すならロケールを考慮したフォーマット関数を通すかString()で囲み、日付ならUtilities.formatDateを使って「yyyy年MM月dd日」のように明示的に整形してからreplaceTextへ渡す設計を確認しましょう。
ただし、数十件から数百件のファイルを一括生成する場合、GASの1回あたりの最大実行時間(通常アカウントで6分)という壁に突き当たります。というのは、DriveAppによるファイルの複製とDocumentAppによるドキュメントのオープン・保存処理は、GASの内部APIの中でも比較的オーバーヘッドが大きい重たい処理だからです。1件の処理に1〜2秒かかるので、100件を超えるような大規模処理を行う場合は、処理済みの行にフラグを立てて中断・再開できる仕組みを作るか、一定件数ごとにバッチを分割する設計を用意しましょう。
replaceTextで予期せぬ欠損や書式崩れを起こさないための注意点
DocumentAppのBodyクラスに用意されているreplaceTextメソッドは非常に強力ですが、内部的には正規表現(Regular Expression)によるパターンマッチングを行っています。なので、この仕様を正しく理解していないと、意図しない置換漏れや置換ミスに悩まされることになります。
第1引数に渡す検索対象の文字列は、単なる文字ではなく正規表現パターンとして解釈されます。ただし、プレースホルダーに特殊記号を含めている場合、エスケープの処理が必要です。たとえば「{{名前}}」という表記を使っている場合、正規表現において中括弧「{」や「}」はリピート回数を表す記号として機能することがあります。なので、環境や正規表現エンジンの解釈によっては、エスケープなしの「{{名前}}」が構文エラーを引き起こしたり、正しく文字列として認識されなかったりするケースがあります。確実性を期すためには、記号の前に記号を付けて打ち消すか、正規表現の特殊文字と干渉しにくい設計を確認しましょう。
さらに、フォントスタイルや文字装飾の崩れにも警戒が必要です。ただし、Googleドキュメントのテンプレート上で、プレースホルダーの一部だけ文字サイズが異なっていたり、貼り付けの拍子に「{{」と「名前」と「}}」で異なる文字色やリンク属性が付与されていたりすると、ドキュメントの内部構造上、文字列が複数の要素(TextRun)に分割されて保持されることがあります。見た目上はひと続きの「{{名前}}」に見えていても、内部的には別々の要素として扱われているので、replaceTextが文字列をひとつの塊として検出できず、置換が無視されて素通りしてしまう現象が発生します。
この現象を回避するためには、テンプレートを作成する段階で、プレースホルダー全体を特殊な装飾のない文字として貼り付け直すか、書式を消去した状態で最初から打ち直すことが大切です。というのは、置換後の文字列は、プレースホルダーの先頭文字に適用されていた書式(フォント、フォントサイズ、太字、色など)を引き継ぐ性質があるからです。なので、テンプレート側で「{{」の部分に適用されているスタイルが、差し替え後の文字列全体に影響することを意識してテンプレートを装飾しましょう。
もうひとつの注意点は、ヘッダー、フッター、表(テーブル)の中にあるプレースホルダーの扱いです。ただし、doc.getBody()で取得できるBodyオブジェクトは、ドキュメントの本文エリアのみを対象としています。なので、ヘッダー領域やフッター領域にプレースホルダーを配置している場合、body.replaceTextを実行してもヘッダーやフッターの中身は一切置換されません。ヘッダーやフッターも置換したい場合は、doc.getHeader()やdoc.getFooter()を個別に呼び出し、取得した範囲に対して同様にreplaceTextを実行しましょう。テーブルセル内の文字列についてはBodyの配下に含まれているのでbody.replaceTextで置換可能ですが、改行を含む文字列を差し込んだ際に表の高さが意図せず広がってしまうレイアウト崩れには注意しましょう。
編集内容が保存されないトラブルを防ぐsaveAndCloseとPDF化の連携
GASでドキュメントの差し替え処理を組んだ初心者が最も遭遇しやすい不具合が、「スクリプトはエラーなく終了したのに、生成されたファイルを開くと置換前のままになっている」という現象や、「直後にPDF化してメール送信したら、プレースホルダーが残ったままのPDFが送られてしまった」という事故です。
このトラブルの根本原因は、DocumentAppが内部的に行っている一時記憶領域(キャッシュ)と、ディスクへの書き込みタイミングのズレにあります。というのは、body.replaceTextを実行した段階では、変更内容は一時記憶領域に保持されているだけであり、Googleドライブ上の実体ファイルには即座に反映されていないからです。スクリプトが終了する際に自動的に保存されることもありますが、タイミングによっては保存が遅延したり、後続の処理が古い状態のファイルを参照してしまったりします。
なので、ここで必須となるのが明示的なdoc.saveAndClose()の呼び出しです。saveAndClose()を実行することで、一時記憶領域にあるすべての変更内容が完全にGoogleドキュメントのストレージに書き込まれ、ドキュメントへの接続が閉じられます。というのは、これにより確実に変更が確定した状態が保証されるからです。
特に、置換処理を行った直後にそのドキュメントをPDFとして変換し、メール添付や共有リンクとして配信する一連の流れでは、saveAndClose()を呼ぶタイミングが非常に重要です。ただし、saveAndClose()を呼ばずにDriveApp.getFileById(docId).getAs(“application/pdf”)を実行した場合、Googleドライブ側は最後にと保存された状態(=置換前のテンプレート状態)からPDFを作成してしまいます。なので、顧客名が「{{名前}}」のまま印刷された請求書PDFが生成されてしまうという重大な問題に直結します。

