PDFを作成したい

※現在このドキュメントは古い状態になっています。最新版は、こちらのサイトで改訂版を公開しています。

概要

現在使用しているドキュメントやスプレッドシートをPDFに変換したいと思った時に、通常であればPDFプリンタやChromeの印刷機能などを利用して作成を行っていると思います。しかし、この場合、PDFファイルはデスクトップ等に出力され、そのPDFを流用したいと考えた場合、デスクトップからの添付やGoogle Driveにアップロードしてから利用するという手順を踏む必要性があります。ちょっとした手間なのですが、せっかく出力したものを、そのままの流れで流用するという要望は多いと思います。とりわけ、スクリプトを扱う上では、メールにそのまま添付して送付したいなどなど。

一度用意しておけば、他でも流用の効くテクニックだと思うのでここにメモをしておこうと思います。ちなみに、作成したスプレッドシートの様子は以下の通り。PDFアイコンにスクリプトを割り当てています。

特定のスプレッドシートなどをまるごとPDF化したい

ソースコード

function spreadsheet2pdfmail(){
   //自分のIDと名前を取得する
   var objSpreadsheet = SpreadsheetApp.getActiveSpreadsheet();
   var mysheetname = objSpreadsheet.getName();
   
   //PDFに変換する(今回はルートディレクトリに作成)
   var pdf = objSpreadsheet.getAs('application/pdf');
   DocsList.createFile(pdf).rename(mysheetname);
   
   //作成したPDFファイルをメールに添付して送る
   var mail = 'test@gmail.com'
   var subject = 'PDF送るよ'
   var body = 'まぁ、送るんで見てくれ'
   MailApp.sendEmail(mail, subject, body, {attachments:pdf});
}

ポイント

今回のスクリプトは以下のポイントがあります。

  • ファイル名を取得しておいて、そのままPDFのファイル名に使用するため、DocsList.createFileに続けて、.renameをもってファイル名を変更しています。
  • フォルダの指定をしていないので、ルートディレクトリにスプレッドシートのファイル名と同じ名前のPDFが作成されます。
  • ついでにその勢いで、sendEmailにてPDFを添付して送りつけています。
  • getAsの引数はMIMETYPEを指定します。
  • これに、時間トリガーなどをつければ、指定時刻に定期的にPDFを作成して、メールで送ってきてくれるようになりますので、便利。
  • ループで回したり、プロジェクトプロパティに値を格納しておいて、メール送信時に呼び出すなど一工夫があると、さらに利便性が上がります。
  • この方法はスプレッドシートやファイルまるごとがPDF化されるので、特定のシートだけPDF化するなどは出来ません。

特定のスプレッドシートの特定のシートだけPDF化したい

特定のスプレッドシートをPDFに変換するためにはgetAsを使うことで可能でした。しかし、この方法はそのスプレッドシート全てがPDF化されてしまうので、正直ちょっと不便です。実際によく使う方法は、特定のシートをPDF化することでしょう。しかし、Google Appsには印刷に関するメソッドが存在しないので、エクスポートを利用してPDF化することが可能です。この方法は、PDF化だけじゃなく現在の特定のシートを別形式でエクスポートも可能なので、色々と使い勝手が良いメソッドです。

故に、PDF以外にもExcel形式やODS形式、CSV形式にも出来ます。スクリプトトリガーなどと合わせることで、自動的にデータを集めて、自動的にPDF化して、解析したページだけメールに添付して送るなんて芸当もこれで可能になります。

ソースコード

function sheet2pdf2mail() {   
  //アクティブシートのIDとGIDを取得する
  var ss = SpreadsheetApp.getActiveSpreadsheet();
  var sheet = ss.getActiveSheet();
  var sheetID = sheet.getSheetId();
  var key = ss.getId();
  
  //作成するPDFのファイル名
  var name = "testman.pdf";
 
  //PDF生成するURLをfetchする
  var pdf = UrlFetchApp.fetch("https://docs.google.com/a/spreadsheets/d/"+key+"&gid="+sheetID+
                              "&format=pdf&portrait=false&size=A4&gridlines=false&fitw=true")
                              .getBlob().setName(name); 
 
  //作成したPDFファイルをメールに添付して送る
   var mail = '送信先メールアドレスをここに入れる'
   var subject = 'PDF送るよ'
   var body = 'まぁ、送るんで見てくれ'
   MailApp.sendEmail(mail, subject, body, {attachments:pdf});
  
}

URLオプション

URLに続けてオプションを付加すると、Chromeでの印刷時と同じオプションを付け加えることが出来る。重要度の高い物を赤字、次に必要な物を青字で説明してみた。

  • key : スプレッドシートのIDを指定する
  • gid : シートIDを指定する。シート毎に付いているもので、URLに入ってる。このオプション指定をしないと、全ページ印刷の対象となる。
  • fmcmd : フォーマット形式の指定。実は、format=pdfという旧式の指定方法も使える。今回はPDFなので12を指定する。しかし、なぜか旨く動かなかったので、format=pdfを今回は使った。
  1. 4 = XLS形式
  2. 5 = CSV形式
  3. 12 = PDF形式
  4. 13 = OpenDocument形式
  5. 23 = TSV形式
  6. 102 = HTML形式(zipで固めたものがダウンロードされる)
  • size : 出力するサイズ。A4と入れるとA4形式となる。番号での指定もできるようだ。
  • fzr : 各ページに行見出しを含めるかどうか。trueで含める、falseで含めない。
  • portrait : trueで縦出力、falseで横出力
  • fitw : ページをフィットさせるかどうか。trueでフィット、falseで原寸大。
  • gridlines : グリッドラインの有無。trueで有り、falseで無し。
  • printtitle : ドキュメントのタイトルの有無。trueで有り、falseで無し。
  • sheetnames : シート名の有無。trueで有り、falseで無し。
  • pagenum : ページ番号の指定。CENTERと入れると真ん中に表示される。
  • attachment : 不明。getFileというコマンドと連携してるとかしてないとか。
  • locale : 言語の指定。必要ないと思う。
  • range : シート単位ではなく、指定範囲内を印刷。名前付き範囲を指定したり、A1:D5の範囲指定ならば、A1%3AD5(%3Aでコロン)といったURLエンコードした範囲を与える
  • widget : rangeの指定時のみ使用可能。今見ている範囲内をtrueとすると印刷範囲とする変なオプション。使いドコロ不明。

ポイント

  • 今回は、A4用紙横印刷でグリッドライン無しのフィットページにてPDFを作成しています。
  • StackOverFlowなどのサイトでは、なにやらログイン処理するOAuthを使っていますが、今回はGoogle Docsで公開ファイルなのでそういうのは無しにしました。面倒だし。外部サービス(Google Apps含む)で生成されるファイルならば場合によってはログイン処理は必要ですので、OAuthの件は追加が必要でしょう。
  • UrlFetchApp.fetchにて生成されたPDFを拾ってます。なので、DriveAppやDocsListなどを併用すればGoogle Driveにそのまま保存することも出来ます。但し、ログインが必要なサイトの場合、OAuth処理をしておかないと、ログイン画面のHTMLが取れるだけなので、注意して下さい。
  • 実はこのコード、便利なのですがユーザ毎に最初の1回は、認証作業が必要です(下記の図のような画面)。しかし、この認証画面はスクリプトエディタ上で、対象コードを実行しないと出てこない仕様になっているので、非常にやっかいなものです。1回とはいえ、多くの人間が使うスクリプトシートで、スクリプトエディタに入ってアクセス許可を与えるというのは、ちょっと面倒です。これはOAuth1の認証画面でUrlFetchAppの持っている機能ですが、既にOAuth1自体、Googleから移行を促されているので、別の方法も考えました。
  • 尚、別の方法に関してはスクリプトエディタに入らずとも認証作業を行えるOAuth2.0を利用しています。その為、作成コードが少々ことなっています。OAuth2.0認証のページを参考にして下さい。
  • 尚、スクリプトトリガーなどで自動化している場合には設置者が一度この認証作業をしてしまえば問題ありません。自動化コードは設置者として稼働します。

図:OAuth関係!?のちょっと特殊な承認画面

自分がGoogle Apps内で使っているコード

//masterシートをPDF化してドライブに保存、mailで送信する関数
function sheet2pdf2mail() {
  //PDF化して送信する確認ダイアログ
  var ui = SpreadsheetApp.getUi();
  var re = ui.alert("PDF化して送信", "masterシートをPDF化してメール送信しますか?", ui.ButtonSet.YES_NO_CANCEL);
  switch(re){
    case ui.Button.YES:
      break;
    case ui.Button.NO:
      ui.alert("送信を中止しました。");
      return 0;
      break;
    case ui.Button.CANCEL:
      ui.alert("送信をキャンセルしました。");
      return 0;
     break;
  }
  //現在のシートのIDとGIDを取得する
  var ss = SpreadsheetApp.getActiveSpreadsheet();
  var sheet = ss.getSheetByName("master");
  var filterday = sheet.getRange("D1").getValue();
  var sheetID = sheet.getSheetId();
  var key = ss.getId();
  
  //作成するPDFのファイル名
  var name = "作成書類" + getDate(filterday) + ".pdf";
 
  //OAuthのセッティング
  var oauthConfig = UrlFetchApp.addOAuthService("spreadsheets");
  var scope = "https://docs.google.com/feeds"
 
  oauthConfig.setConsumerKey("anonymous");
  oauthConfig.setConsumerSecret("anonymous");
  oauthConfig.setRequestTokenUrl("https://www.google.com/accounts/OAuthGetRequestToken?scope="+scope);
  oauthConfig.setAuthorizationUrl("https://accounts.google.com/OAuthAuthorizeToken");    
  oauthConfig.setAccessTokenUrl("https://www.google.com/accounts/OAuthGetAccessToken");  
 
  var Params = {
    oAuthServiceName: 'spreadsheets',
    oAuthUseToken: 'always',
  };
  
  //PDF出力をするURLをFetchする
  var pdf = UrlFetchApp.fetch('https://docs.google.com/a/ドメイン名/spreadsheets/d/'+key+'/export?gid='+sheetID+
                              '&format=pdf&portrait=false&size=A4&gridlines=false&fitw=true',Params)
                              .getBlob().setName(name); 
  
  //作成したPDFをフォルダに格納する
  var folder = DocsList.getFolderById("ここにPDF作成先フォルダのIDを入れる");
  var file = folder.createFile(pdf); 
  var fileid = file.getId();
  var milkysky = "https://docs.google.com/a/ドメイン名/file/d/" + fileid; 
                              
                              
  //送信先を取得する
  var sendlist = SpreadsheetApp.openById("送信先リストであるスプレッドシートのIDを指定");  //mailsendlistを直接指定
  var todatabase = sendlist.getSheetByName("tolist").getRange("A2:H").getValues();
  var mail = "";
  for(var i = 0;i < todatabase.length;i++){
    if(todatabase[i][0] == "作成書類"){
      //項目名がヒットしたら、ccデータがあるやつを全て変数に含める。
      for(var j = 1;j < 7;j++){
        if(todatabase[i][j] == ""){
        }else{
          mail =  mail + todatabase[i][j] + ",";
        }
      }
    }else{
    }
  } 
  
  //CC送信先を取得する
  var ccdatabase = sendlist.getSheetByName("ccdatabase").getRange("A2:G").getValues();
  var cc = "";
  for(var i = 0;i < ccdatabase.length;i++){
    if(ccdatabase[i][0] == "○○事務所"){
      //施設名がヒットしたら、ccデータがあるやつを全て変数に含める。
      for(var j = 1;j < 6;j++){
        if(ccdatabase[i][j] == ""){
        }else{
          cc =  cc + ccdatabase[i][j] + ",";
        }
      }
    }else{
    }
  }
  
  //送信者をCCに加える
  cc = cc + GetUser();
  
  //body文章を作成する。
  //改行するには <br>や<p> を入れてください
・・・・省略・・・・
  
  var body =""; //本文
  
  body += header;
  body += bodytop;
  body += footer;
 
  //作成したPDFファイルをメールに添付して送る
  var subject = "作成書類" + getDate(filterday) + "分";
  var titlename = "テスト送信"; 
  MailApp.sendEmail({
    to: mail,
    subject: subject,
    htmlBody: body,
    cc: cc,
    name:titlename,
  });
  
}

書き込みが間に合わない時の処置

データの書き込みを行い、其の流れでPDFを生成する場合にいは、データの書き込みが間に合わずPDFが生成されてしまうケースが、しばしばあります。

このままでは、折角のPDFもデータが欠けた状態で作成されてしまうので、以下のような無限ループを利用して、「最終書き込みセル」を確認しながら、スクリプトをウェイトさせます。

  //最終rangeに書き込みがされていない場合、無限ループ
  var rangechk = SpreadsheetApp.openById(filesId).getRange("comments").getValue();
  while (rangechk == ""){
    rangechk = SpreadsheetApp.openById(filesId).getRange("comments").getValue();
}

最終書き込みをするであろう場所を今回は、名前付き範囲で指定しています。

その中身が空の状態の時は無限ループします。値が確認できたら、PDF生成ルーチンへ進むといった感じです。

参考リンク