GASで10件以上PDFファイルをダウンロードさせる

GAS

CSVをダウンロードするやつは前に書いていますが、今回はPDFです。
GoogleドキュメントをPDFにしてローカルにダウンロードするのですが、いろいろぶつかったことがあったので残します。

モノとしては、Googleドキュメントのフォーマットの内容を任意の文字に置換してからPDFに変換、ダウンロードする機能があります。
なお、スプレッドシートのコンテナバインドスクリプトを想定しています。
サイドバーから実行します。
スプレッドシートの構成や内容は割愛します。

Chromeでファイルを10件以上ダウンロードさせたい場合のご参考にしてください。

スポンサーリンク

まずはコード

HTML

<!DOCTYPE html>
<html>
  <head>
    <base target="_top">
    <!-- jQueryのライブラリ類を読み込む -->
    <script src="//ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js"></script>
    <link rel="stylesheet" href="//ajax.googleapis.com/ajax/libs/jqueryui/1.10.2/themes/smoothness/jquery-ui.css" />
    <script src="//ajax.googleapis.com/ajax/libs/jqueryui/1.10.2/jquery-ui.min.js"></script>
    <link rel="stylesheet" href="https://ssl.gstatic.com/docs/script/css/add-ons1.css">
    <style>
      /* 入力補完用のCSS設定 */
      .ui-autocomplete {
        max-height: 200px;
        overflow-y: auto;
        overflow-x: hidden;
        padding-right: 20px;
      }
     /* ラベル用のCSS設定 */
      label {
        float: left;
        margin-right: 0.5em;
        color: black;
        font-size: 15px;
      }
    </style>
  </head>
  <body>
    <div class="sidebar">
      <div class="block form-group">
        <h1>ドキュメント作成</h1>
        <p>チェックしたドキュメントをダウンロード

          <button class="blue"  onclick="downloadPDF()" id="DownloadPdfbuttom">PDFダウンロード</button>
        </p>
      </div>
  </body>
  <script>
    function downloadPDF(){
      alert("ダウンロードを開始します。アラートが出る場合は許可してください。");
      google.script.run
        .withSuccessHandler(successCreatePDF)
        .withFailureHandler(failureCreatePDF)
        .recieveFromHtml();
    }
    function failureCreatePDF(error){
      alert(error);
    }
    async function successCreatePDF(files){
      let count = 0;
      for(let i = 0; i < files.length; i++){
        if (++count >= 10) {
            await pause(1000);
            count = 0;
        }
        let a = document.createElement("a");
        document.body.appendChild(a);
        a.download = files[i].filename;
        a.href = files[i].data;
        a.click();
      }
    }
  function pause(msec) {
    return new Promise(
        (resolve, reject) => {
            setTimeout(resolve, msec || 1000);
        }
    );
  }
  function getToday(){
    let today = new Date();
    today.setDate(today.getDate());
    const yyyy = today.getFullYear();
    const mm = ("0"+(today.getMonth()+1)).slice(-2);
    const dd = ("0"+today.getDate()).slice(-2);
    const result = yyyy+'-'+mm+'-'+dd;
    return result;
  }
  </script>
</html>

ポイントはasync functionです。この記事で残しておきたいのはこれだけです。
Chromeの仕様で一括ダウンロードを許可していても一度に10件までしかダウンロードさせてくれませんでした。
10ファイル目以降のダウンロードはasync, promiseで1秒待ってあげると制限をぬけられるようです。(はまってから2日かかった)

ちなみにPDFのダウンロードはGASから受け取ったblobのPDF配列のリンクを作成、a.click()でローカルにダウンロードさせています。


GS

function recieveFromHtml(){
  const ui = SpreadsheetApp.getUi();
  const ans = ui.alert('チェックしたドキュメントのPDFを作成します。\n作成まで時間がかかる場合があります。\nダウンロードが完了するまでページを閉じないでください。', ui.ButtonSet.YES_NO);
  if(ans == "NO"){
    ui.alert('キャンセルしました', ui.ButtonSet.OK);
    return;
  }
  const result_file_obj = downloadPapers();
  SpreadsheetApp.getActiveSpreadsheet().toast('作成完了', 'PDFはGoogleドライブにも作成されます。', 10);
  return result_file_obj;
}

ポイントというほどのところはないです。


GS

function downloadPapers(){
  const ss = SpreadsheetApp.getActiveSpreadsheet();
  const dest_folder = checkDirectory("PDF置き場");
  const today = Utilities.formatDate(new Date(), "JST", "yyyy-MM-dd"); 
  const titles = target.titles;
  const contents = target.contents;
  const info = target.info;
  //ドキュメント作成、ダウンロード
  let result_files = [];
  const total_files = (titles.length * info.length);
  let num = 1;
  for(let i = 0; i<titles.length; i++){
    let title = titles[i];
    let content = contents[i];
    for(let o = 0; o < info.length; o++){
      //let paper = DocumentApp.openByUrl(templete_urls[i]);
      let paper = DocumentApp.openByUrl(templete_urls[0]);
      let document_id = paper.getId();
      let mv_document = DriveApp.getFileById(document_id);
      let file_name = today+ "_" +title;

      let dest_document = mv_document.makeCopy(file_name);
      dest_document.moveTo(dest_folder);

      //印刷用ドキュメントの内容を置換
      let document_obj = DocumentApp.openById(dest_document.getId());
      target_body = document_obj.getBody();
      const before_words = ["<今日>","<タイトル>","<本文>"];//after_wordsの順番と対応すること
      const after_words = [today, title, content,];//before_wordsの順番と対応すること
      for(let j = 0; j < before_words.length; j++){
        target_body.replaceText(before_words[j], after_words[j]);
      }
      document_obj.saveAndClose();
      //documentをPDF化
      let blob = document_obj.getAs('application/pdf');
      blob.setName(file_name + ".pdf");
      let result_file = dest_folder.createFile(blob);
      let result_obj = {
        "data": `data:${result_file.getMimeType()};base64,${Utilities.base64Encode(result_file.getBlob().getBytes())}`,
        "filename": result_file.getName()
      }
      result_files.push(result_obj);
      ss.toast('進行状況: '+ num +'/'+ total_files+' ファイル', '作成中', 5);
      num++;
    }
  }
  return result_files;
}

function getTemplete(){
  const ss = SpreadsheetApp.getActiveSpreadsheet();
  const sheet = ss.getSheetByName("フォーマット");
  const templete_urls = sheet.getRange("A2:A").getValues().flat().filter(function(x){return !(x === null || x === undefined || x === "");});
  return templete_urls;
}

function checkDirectory(folder_name){
  const ss = SpreadsheetApp.getActiveSpreadsheet();
  const ssId = ss.getId();
  const targetDirName = folder_name;
  const parentFolder = DriveApp.getFileById(ssId).getParents();
  const base_folder_key = parentFolder.next().getId();
  const pFolder = DriveApp.getFolderById(base_folder_key);
  const folder_childs = pFolder.getFolders();
  //カレントフォルダに「targetDirNane」があるか確認、なければ作成する。
  let flg = 0;
  let cFolder = "";
  let folder_key = "";
  if ( folder_childs.hasNext() ){
    while ( folder_childs.hasNext() ){
      cFolder = folder_childs.next();
      if(cFolder.getName() == targetDirName){
        flg = 1;
        folder_key = cFolder.getId();
      }
    }
  }
  if(flg == 0){
    const target_folder = pFolder.createFolder(targetDirName);
    folder_key = target_folder.getId();
  }

  const folder = DriveApp.getFolderById(folder_key);
  return folder;
}

ポイントはスプシが置いてあるカレントにアウトプット用のフォルダを作成する処理checkDirectory()と、ドキュメントの中身を指定して特定の文字に置換していること、ドキュメントをPDFのblobとして取得してHTMLにreturnしていることです。


GS

function onOpen() {
  SpreadsheetApp.getUi()
      .createMenu('ダイアログを開く')
      .addItem('ダイアログ', 'showSidebar')
      .addToUi();
}
//インストーラブルトリガーとしてスプシ起動時に実行するようにする
function showSidebar() {
  const htmlOutput = HtmlService.createHtmlOutputFromFile('sidebar').setTitle('ダイアログ').setSandboxMode(HtmlService.SandboxMode.IFRAME);
  SpreadsheetApp.getUi().showSidebar(htmlOutput);
}

HTMLはスプシのサイドバーとして起動するようにしています。
スプシのサイドバーからPDFダウンロードボタンを押すと指定したフォルダにあるフォーマット(Googleドキュメント)の中身を任意の内容に置換し指定のGoogleドライブに作成&ローカルにダウンロードという流れです。

このままでは動きませんので

↑のコードまんまだと動きません。

スプシにフォーマットのURLだとか作成するPDFの内容だとか置換する文字列だとか色々構成が必要になります。
そのまま出すのはちょっと色々あるし情報マスクして公開するのは結構大変なのでエッセンスだけ残します。

文字列の置換はこんな感じ
for(let j = 0; j < before_words.length; j++){
target_body.replaceText(before_words[j], after_words[j]);
}

Chromeで10件以上ファイルをダウンロードさせたいならこんな感じです
async function successCreatePDF(files){
let count = 0;
for(let i = 0; i < files.length; i++){
if (++count >= 10) {
await pause(1000);
count = 0;
}
let a = document.createElement(“a”);
document.body.appendChild(a);
a.download = files[i].filename;
a.href = files[i].data;
a.click();
}
}
function pause(msec) {
return new Promise(
(resolve, reject) => {
setTimeout(resolve, msec || 1000);
}
);
}

Chromeでダウンロードが10件までに制限されているってわかるまで大変だった・・・

コメント

タイトルとURLをコピーしました