【GAS】テンプレートを元にレポート一覧を作成

Google Apps Scriptを使用し、Googleスプレッドシートに格納されたデータをもとに、Googleドキュメントのテンプレートを使用して新しいドキュメントを作成し、最終的にはPDFファイルに出力するものです。GoogleスプレッドシートとGoogleドキュメントを組み合わせて、データに基づくレポートを効率的に作成するためのものです。

一つのデータ毎にドキュメントを作成するのではなく、一つのドキュメントに改ページを加えながら追記していくので、ユーザーのアカウント情報をまとめた用紙を一括作成したり、アンケート結果や成果物をレポートとして一括作成するといった活用が見込まれます。あるいは、フォームと連携したスプレッドシートを用いることで、さらに活用の幅が広がると思います。

「[(配布用)テンプレからレポート一括作成](http://bit.ly/3JUQY4y)」と「[(配布用)レポートテンプレ](http://bit.ly/3n9nhDM)」をコピーして、使用して下さい。なお、スプレッドシートに書かれている情報は、「[個人情報テストデータジェネレーター](https://testdata.userlocal.jp/)」で生成した動作確認用のダーミーデータです。

# 主な機能
- Googleスプレッドシートのメニューバーに「レポート作成」という新しいメニューを追加
- 「レポート作成」メニューから「作成」という項目を選択すると、createReportFromTemplate関数が実行されます。
- createReportFromTemplate関数が実行されると、スプレッドシートからデータを取得し、指定されたGoogleドキュメントテンプレートを使用して新しいドキュメントを生成します。テンプレート内のプレースホルダーは、スプレッドシートのデータに置き換えられます。
- 新しく作成されたドキュメントは、スプレッドシートと同じフォルダに保存されます。
- 最後に新しいドキュメントをPDF形式で出力し、スプレッドシートと同じフォルダに保存します。
このツールは、Google スプレッドシートとGoogle ドキュメントを組み合わせて、データに基づくレポートを効率的に作成するためのものです。

# 注意事項
- テンプレートIDの設定: スクリプト内のTEMPLATE_DOCUMENT_IDを、実際のGoogle ドキュメントテンプレートのIDに置き換える必要があります。これを行わないと、スクリプトは正しく動作しません。
- アクセス許可: スクリプトがGoogle スプレッドシートやGoogle ドキュメントにアクセスするためには、適切なアクセス許可が必要です。初回実行時に、スクリプトがリクエストするアクセス許可を承認してください。
- プレースホルダーの形式: テンプレート内のプレースホルダーは、{}で囲まれた形式である必要があります。例えば、スプレッドシートのヘッダーが「氏名」であれば、テンプレート内のプレースホルダーは{氏名}となります。
- 他の要素タイプへの対応: 現在のスクリプトでは、主要な要素タイプ（段落、リスト項目、テーブル、ページブレーク）に対応していますが、他の要素タイプを含むテンプレートを使用する場合、スクリプト内で適切な処理を追加する必要があります。
- スクリプトの実行時間: Google Apps Scriptには実行時間の制限があります。処理するデータが非常に大量であったり、複雑な処理が必要な場合、スクリプトの実行時間が制限を超える可能性があります。
- エラーハンドリング: 現在のスクリプトでは、エラーハンドリングが最小限になっています。実際に使用する場合、適切なエラーハンドリングを追加して、スクリプトの安定性と堅牢性を向上させることが望ましいです。

# お願い
テンプレートに画像を含めたものの開発を最終目標とし様々なコードを試しましたが、先行事例も見当たらず断念しました。アドバイスいただけると幸いです。

# スクリプト
```javascript:コード.gs
function onOpen() {
  let ui = SpreadsheetApp.getUi();
  let menu = ui.createMenu('レポート作成');
  menu.addItem('作成', 'createReportFromTemplate');
  menu.addToUi();
}

function createReportFromTemplate() {
  // スプレッドシート、テンプレートドキュメントのIDを指定
  let spreadsheetId = SpreadsheetApp.getActiveSpreadsheet().getId();
  let templateId = 'TEMPLATE_DOCUMENT_ID';
  
  // スプレッドシートからデータを取得
  let sheet = SpreadsheetApp.openById(spreadsheetId).getActiveSheet();
  let data = sheet.getDataRange().getValues();

  // ヘッダー行を削除
  data.shift();

  // テンプレートドキュメントを開く
  let templateDoc = DocumentApp.openById(templateId);
  let templateBody = templateDoc.getActiveSection();
  
  // スプレッドシートと同じフォルダに新しいドキュメントを作成
  let spreadsheetFile = DriveApp.getFileById(spreadsheetId);
  let parentFolder = spreadsheetFile.getParents().next();
  let newDoc = DocumentApp.create('New Document');
  let newDocumentId = newDoc.getId();
  parentFolder.addFile(DriveApp.getFileById(newDocumentId));

  // 新しいドキュメントを開く
  newDoc = DocumentApp.openById(newDocumentId);
  let newDocBody = newDoc.getActiveSection();

  // データをテンプレートに当てはめて新しいドキュメントに追記
  data.forEach(function(row) {
    let tempBody = templateBody.copy();

    // テンプレート内のプレースホルダーを置き換える
    for (let i = 0; i < row.length; i++) {
      let placeholder = '{' + sheet.getRange(1, i+1).getValue() + '}';
      tempBody.replaceText(placeholder, row[i]);
    }

  for (let i = 0; i < tempBody.getNumChildren(); i++) {
    let childElement = tempBody.getChild(i).copy();
    let childElementType = childElement.getType();
    let newDocBody = newDoc.getBody();

    // 各要素タイプに基づいて要素を追加
    switch (childElementType) {
      case DocumentApp.ElementType.PARAGRAPH:
        newDocBody.appendParagraph(childElement.asParagraph());
        break;
      case DocumentApp.ElementType.LIST_ITEM:
        newDocBody.appendListItem(childElement.asListItem());
        break;
      case DocumentApp.ElementType.TABLE:
        newDocBody.appendTable(childElement.asTable());
        break;
      case DocumentApp.ElementType.PAGE_BREAK:
        newDocBody.appendPageBreak(childElement.asPageBreak());
        break;
      // 他の要素タイプがある場合には、ここに追加
      default:
      break;
    }
  }

    // ページブレークを追加
    newDocBody.appendPageBreak();
  });

  // 変更を保存
  newDoc.saveAndClose();

  // 新しいドキュメントをPDFファイルとして出力
  exportToPdf(newDocumentId, parentFolder);
}

function exportToPdf(documentId, parentFolder) {
  // PDFファイル名を指定
  let pdfName = 'output.pdf';

  // ドキュメントをPDF形式で取得
  let pdfBlob = DriveApp.getFileById(documentId).getAs('application/pdf');

  // PDFファイル名を設定
  pdfBlob.setName(pdfName);

  // PDFファイルをスプレッドシートと同じフォルダに保存
  parentFolder.createFile(pdfBlob);
}
```
# 謝辞
「一つのドキュメントファイルにテンプレートを元に成形されたデータを追記していく」スクリプトの書き方で苦労していましたが、chatGPT（GPT-4）にその解決方法を教えてもらいました。ネット上に多くの知識を蓄積してくださった世界中の皆様、それを集約してくれるchatGPTさん、ありがとうございます。これからも困った時にはサポートをお願いします。