※本記事はプロモーションを含みます
目次
スプレッドシートにまとめた月次売上や日報のデータを、会議用の議事録や提出用レポートとしてGoogleドキュメントに清書したい場面はよくあります。なので、毎回手作業でドキュメントを立ち上げて表や数値を貼り付けていると、転記ミスが発生しやすく作業時間も無駄に消費されます。ただし、GASを使ってGoogleドキュメントの新規作成から本文の書き込みまでを一連の処理としてスクリプト化しておけば、集計が終わった瞬間に整形されたレポートが自動生成される仕組みを作ることができます。
ただし、いざGASでGoogleドキュメントの新規作成や書き込みを実装しようとすると、作成自体は数行で書けるものの地味なつまづきに直面しがちです。というのは、「ファイルがマイドライブのルートに勝手に作られて散乱する」「改行や見出しのスタイル設定が意図した見た目にならない」「連続して書き込んでいる途中で参照エラーが出る」といった問題が起きるからです。なので、ここでは、単にドキュメントを作るだけでなく、実務で破綻しないための本文操作や配置制御まで含めた具体的な実装手順を整理します。
DocumentApp.createとDriveAppの生成挙動の混同
GASでGoogleドキュメントを新規作成する際、最初に候補に挙がるのがDocumentApp.create()とDriveApp.createFile()の二つです。ただし、ここで多くの人が「どちらを使ってもファイルができるのだから同じだろう」と考えてしまい、後続の本文編集処理で予期せぬエラーに遭遇します。というのは、この二つのメソッドは、裏側で扱っているオブジェクトの型と生成時の振る舞いが根本的に異なるからです。
DocumentApp.create()を実行すると、指定したファイル名を持つGoogleドキュメントが生成されます。なので、戻り値としてDocumentクラスのインスタンスが返されます。このインスタンスからは、getBody()を通じてドキュメントの本文コンテナへ即座にアクセスできます。なので、段落の追加やスタイルの適用といったテキスト操作を直接呼び出せます。ただし、このメソッドで作成されたファイルは、実行者のGoogleドライブの最上位(マイドライブ直下)に無条件で配置されるという仕様を持っています。というのは、指定したフォルダの中に直接作成する引数が用意されていないからです。
それに対してDriveApp.createFile()は、Googleドライブ上のファイル全般を生成・管理するためのAPIです。ただし、このメソッドはMIMEタイプやバイナリデータを指定してファイルを作る設計になっています。なので、新規のGoogleドキュメントを空の状態で作成してその内容をリッチテキストとして直接編集していく用途には適していません。DriveAppで作られたファイルオブジェクトはFileクラスです。なので、DocumentAppのgetBody()やappendParagraph()といった文書構造を操作するメソッドを直接呼び出すことはできません。というのは、ドキュメントの内部構造を触るためには、一度FileのIDを取得してからDocumentApp.openById()で開き直す必要があるからです。
したがって、Googleドキュメントを新規作成して本文を書き込む業務フローでは、まずDocumentApp.create()でドキュメント本体と本文インスタンスを立ち上げてテキストの組み立てを行います。なので、フォルダ移動が必要な場合にのみDriveAppを使ってファイルの親フォルダを付け替える、という役割分担を明確に意識する必要があります。ただし、この境界を曖昧にしたまま書き始めると、型の不一致エラーや、意図しないフォルダ階層へのファイル散乱といったトラブルを招くことになります。
新規ドキュメントの生成と段落テキストを追記する処理
DocumentAppを使った新規ドキュメント作成と本文書き込みの基本となる処理構成を以下に示します。なので、スプレッドシートから呼び出すことを想定し、新規作成したドキュメントの本文領域を取得して、見出しと本文段落を順番に流し込み、最後に生成されたファイルのアクセスURLをログに出力する一連の流れとなります。
function createReportDocument() { // 1. 新規ドキュメントの生成 const docTitle = "新規議事録_" + Utilities.formatDate(new Date(), "Asia/Tokyo", "yyyyMMdd"); const doc = DocumentApp.create(docTitle); // 2. 本文コンテナ(Body)の取得 const body = doc.getBody(); // 3. 見出し段落の追加とスタイリング const heading = body.appendParagraph("本日のアジェンダと決定事項"); heading.setHeading(DocumentApp.ParagraphHeading.HEADING1); // 4. 通常テキストの追記 body.appendParagraph("先週の進捗確認および今期の重点施策について協議を行いました。"); body.appendParagraph("決定事項:次期スプリントの機能開発スコープを確定。"); // 5. 変更内容の確定とURL出力 doc.saveAndClose(); Logger.log("Doc URL: " + doc.getUrl()); }
この処理を実行するタイミングは、スプレッドシート側でデータの取りまとめが完了し、報告用ドキュメントを新しく切り出したい段階です。なので、たとえば月次締め処理のトリガーや、日報送信ボタンを押したタイミングなどが該当します。ただし、作成されたドキュメントのURLをLogger.logで出力しておけば、スプレッドシートの管理台帳セルにURLを書き戻したり、Slackやチャットツールに即座に通知したりする連携が容易になります。
この処理を書く際にずれやすいのが、新規作成されたドキュメントの初期状態に対する認識です。というのは、DocumentApp.create()で生成された直後のドキュメントには、完全な白紙ではなく、空の段落(Paragraph)がすでに1行分だけ初期状態で含まれているからです。なので、何も考慮せずにbody.appendParagraph()を連続して呼び出すと、ドキュメントの先頭に不自然な1行の空行が残ってしまう現象が起こります。ただし、見た目上は最初の見出しの上に余計な空白が空いているように見え、デザインを厳密に揃えたい帳票作成業務では違和感の原因になります。
この先頭空行を回避するには、最初のテキストを追記する際にbody.appendParagraph()を使うべきではありません。なので、すでにある先頭要素であるbody.getParagraphs()[0]を取得してその要素に対してsetText()を呼び出すか、あるいは一通りappendParagraph()で書き込んだ後にbody.getChild(0).removeFromParent()を呼んで先頭の空要素を削除するアプローチをとる必要があります。ただし、簡単な議事録であれば気にならないことも多いです。なので、社外提出用の企画書や定型レポートを自動生成する場合は、この1行の隙間がレイアウト崩れとして指摘されやすいため事前の考慮が欠かせません。
見出しスタイルの適用とテキスト属性指定の差異
Googleドキュメントの本文に文章を流し込む際、単なるベタテキストの連続では読みづらくなります。なので、見出し(Heading)や文字装飾を適切に設定する必要があります。ただし、GASではappendParagraph()で返されるParagraphオブジェクトに対して、DocumentApp.ParagraphHeadingの定数を指定することでスタイリングを行います。
ここでつまずきやすいのが、段落全体に対するスタイル指定と、段落内の一部の文字に対するスタイル指定の扱いの違いです。たとえば、ある行を見出し1に設定したい場合、段落に対してsetHeading(DocumentApp.ParagraphHeading.HEADING1)を実行します。というのは、これは段落単位の属性であるため、その行全体がGoogleドキュメントの標準スタイルで定義された見出し1のフォントサイズや行間に切り替わるからです。なので、見出しの種類にはHEADING1からHEADING6まで、さらにはTITLEやSUBTITLEといった定数が用意されており、構造化された文書を組み立てる際にはこれらを階層に応じて使い分けます。
一方、太字や斜体、文字色といった装飾は、段落全体に適用することもできれば、テキストの一部分だけに適用することもできます。なので、段落全体を太字にしたい場合はparagraph.setBold(true)で済みます。ただし、スプレッドシートから持ってきた特定のキーワードや数値だけを強調したい場合は、段落からTextオブジェクトを取り出して範囲(オフセット)を指定して装飾する必要があります。というのは、具体的には、paragraph.editAsText()を呼び出し、setBold(startOffset, endOffsetInclusive, true)のように文字の開始位置と終了位置を数値で渡すからです。
この文字装飾のオフセット計算を雑に行うと、日本語のマルチバイト文字の長さや改行コードのカウントのズレによって、意図しない箇所の文字が太字になったり、例外エラーでスクリプトが停止したりします。なので、実務でレポート文章を自動組み立てする場合は、文章を連結した後に正規表現などでキーワードの位置を割り出す手法は避けるべきです。ただし、テンプレートとなる文章をあらかじめブロックごとに分割して追記し、ブロック単位でスタイルを設定していく方がバグを大幅に減らすことができます。
マイドライブ直下への生成放置を避けるフォルダ退避
DocumentApp.create()でGoogleドキュメントを新規作成すると、前述の通り必ずマイドライブの最上位にファイルが作られます。なので、個人でテスト実行している段階では大きな問題になりません。ただし、実務で毎日・毎月動くバッチ処理として運用し始めると、マイドライブ直下に「新規議事録_2026xxxx」といったファイルが何十個、何百個と放置される大惨事になります。というのは、チームの共有ドライブで運用している場合、マイドライブに作られたファイルは他のメンバーから見えず、共有漏れの原因にもなるからです。
この問題を回避するためには、ドキュメントを作成した直後にDriveAppを利用して、目的の管理フォルダへファイルを移動させる処理を必ずセットで組み込む必要があります。ただし、GASのDriveAppにおけるファイル移動は、Googleドライブの内部構造(ショートカットと親フォルダの概念)に依存しているため、少し独特の手順を踏む必要があります。
従来のドライブAPIでは、ファイルをターゲットフォルダにaddFolder()してから、元のマイドライブ(getRootFolder())からremoveFolder()するという二段階の操作が一般的でした。ただし、近年のGoogle Workspace環境ではファイルと親フォルダの仕様が整理され、moveTo()メソッドを使用するのが標準的な実装となっています。なので、具体的には、以下のようにDocumentAppで作成したファイルのIDを使ってDriveApp.getFileById()を呼び出し、移動先のFolderオブジェクトに対してmoveTo()を実行します。
const docId = doc.getId();
const file = DriveApp.getFileById(docId);
const targetFolder = DriveApp.getFolderById(“フォルダID”);
file.moveTo(targetFolder);
この処理を挟むことで、ドキュメントの新規作成処理を行いながらも、成果物は部署共通の指定フォルダやプロジェクト別のアーカイブフォルダの中に最初から存在しているかのように整理されます。なので、共有ドライブ内で自動生成ファイルを完結させたい場合も同様で、共有ドライブ内のフォルダIDを指定してmoveTo()を呼ぶことで、実行者のプライベートなストレージを消費することなく安全にチーム内にファイルを公開できます。ただし、このファイル移動処理を忘れると、後からの手動整理に追われることになるため、新規作成スクリプトを書く際はセットで組み込んでおくことが鉄則です。

