GASを使ってスプレッドシートの指定シートをPDFに出力する方法

# やりたいこと
以前、GASを使ってBacklogの課題を取得しました([GASを使ってBacklogの課題を取得する方法 - Qiita](https://qiita.com/chovin/items/b2ed76d32201db9f77b2))。
取得したBacklog課題の情報をスプレッドシートで加工して使っています。

今度はそのスプレッドシートの指定のシートだけをPDFファイルに出力します。
出力先はスプレッドシートと同じGoogleドライブのフォルダにします。

:::note warn
ご利用は自己責任でお願いいたします。
:::

# 1. PDF出力用のURLを作成する
`Spreadsheet.getAs('application/pdf')`を使うとスプレッドシートをPDFへ出力できます

> **getAs(contentType)**
> このオブジェクト内のデータを、指定されたコンテンツ タイプに変換した blob として返します。
> [Class Spreadsheet &nbsp;|&nbsp; Apps Script &nbsp;|&nbsp; Google for Developers](https://developers.google.com/apps-script/reference/spreadsheet/spreadsheet?hl=ja#getascontenttype)

ただ、スプレッドシート内のすべてのシートが出力されてしまい、特定シートのみを出力することができません。

そのため、今回のようにシートを指定したい場合はPDF出力用のURLを作成してそのURLからデータを取得することになります。

URLの基本フォーマットは
`https://docs.google.com/spreadsheets/d/{スプレッドシートのID}/export?{出力するためのGETパラメータ}`
になります。

PDF出力のためには「出力するためのGETパラメータ」に`exportFormat=pdf&format=pdf`を指定します。
特定シートを指定するためには「出力するためのGETパラメータ」に`gid={シートID}`を指定します。
他にも印刷と同じように「用紙のサイズ」「用紙の向き」などが指定できます。

GETパラメータは数が多いと文字列結合するのが大変なので、
URLSearchParamsクラスを使いたいところですがGASではサポートしていないので
オブジェクトに入れてからencodeURIComponentメソッドを挟んで一気に結合します。

> **encodeURIComponent()**
> encodeURIComponent() 関数は、 URI (Uniform Resource Identifier) 構成要素を特定の文字を UTF-8 文字エンコーディングで表された 1 個から 4 個のエスケープシーケンスに置き換えることでエンコードします (サロゲートペアで構成される文字のみ 4 個のエスケープシーケンスになります)。
> [encodeURIComponent() - JavaScript | MDN](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/encodeURIComponent)


```js
/**
 * PDF出力用のURLを作成する.
 * @param {Spreadsheet} 出力対象のスプレッドシート.
 */
function createUrlForPdf(spreadsheet) {
  const params = {
    'exportFormat': 'pdf',
    'format': 'pdf',
    'gid': spreadsheet.getSheetByName('PDF出力したいシート名').getSheetId(), // シート名を指定して出力対象シートのIDを指定
    'size': 'A4', // 用紙サイズ:A4
    'portrait': false, // 用紙向き:縦
    'fitw': true, // 幅を用紙に合わせる
    'horizontal_alignment': 'CENTER', // 水平方向:中央
    'gridlines': false, // グリッドライン:非表示
  }
  const query = Object.keys(params).map(function(key) {
    return encodeURIComponent(key) + '=' + encodeURIComponent(params[key]);
  }).join('&');
  return `https://docs.google.com/spreadsheets/d/${spreadsheet.getId()}/export?${query}`;
}
```

# 2. URLでBlobデータを取得する
まずはBearer認証するためにユーザーのOAuth2.0アクセストークンを取得します。

> **getOAuthToken()**
> 有効なユーザーの OAuth 2.0 アクセス トークンを取得します。
> [Class ScriptApp &nbsp;|&nbsp; Apps Script &nbsp;|&nbsp; Google for Developers](https://developers.google.com/apps-script/reference/script/script-app?hl=ja#getOAuthToken())

取得したトークンとURLを使ってBlobデータを取得します。
Bearer認証しないとでっかいHTMLみたいなファイルが出力されてしまうので忘れないように指定します。

```js
/** @type {string} ユーザーのOAuth2.0アクセストークン */
const token = ScriptApp.getOAuthToken();
// URLからblobデータを取得する
const blob = UrlFetchApp.fetch(url, {headers: {'Authorization': 'Bearer ' + token}}).getBlob();
```

# 3. PDF出力先のフォルダIDを取得する
今回は、スプレッドシートと同じ場所へ出力したいので、スプレッドシートが格納されているフォルダのIDを取得します。
スプレッドシートのIDから親フォルダを取得して、そこからIDを取得します。
参考 : [【GAS】GoogleドライブのフォルダIDの取得方法](https://tetsuooo.net/gas/748/)

> **getParents()**
> File の直接の親であるフォルダのコレクションを取得します。
> [Class File &nbsp;|&nbsp; Apps Script &nbsp;|&nbsp; Google for Developers](https://developers.google.com/apps-script/reference/drive/file?hl=ja#getparents)

Spreadsheetクラスから簡単に取得できそうに思ったのですが、ちょっと簡単な方法がわかりませんでした。

```js
/**
 * スプレッドシートが格納されているフォルダのIDを取得する.
 * @param {Spreadsheet} 対象のスプレッドシート.
 */
function getFolderIdBySpreadsheet(spreadsheet) {
  const parents = DriveApp.getFileById(spreadsheet.getId()).getParents();
  const folder = parents.next();
  return folder.getId();
}
```

# 4. PDFファイルをフォルダに出力する
最後に取得したBlobデータをフォルダへ出力します。
ファイル名を指定しないと「export.pdf」というファイル名になってしまうので任意のファイル名を指定しました。

```js
// ファイル名を設定してフォルダにPDFファイルを出力する
DriveApp.getFolderById(folderId).createFile(blob.setName(fileName));
```

# コード全体
<details><summary>最後にコード全体を記載します。長いので気になった方のみご参照ください。「▶」をクリックすると表示します。</summary><div>

```js
/**
 * PDFに出力する
 */
function exportToPDF() {
  const spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
  try {
    /** @type {string} PDF出力用URL. */
    const url = createUrlForPdf(spreadsheet);
    /** @type {string} ユーザーのOAuth2.0アクセストークン */
    const token = ScriptApp.getOAuthToken();
    // URLからblobデータを取得する
    const blob = UrlFetchApp.fetch(url, {headers: {'Authorization': 'Bearer ' + token}}).getBlob();
    // PDFをスプレッドシートと同じフォルダに出力するためにフォルダIDを取得する.
    const folderId = getFolderIdBySpreadsheet(spreadsheet)
    // PDFのファイル名を作成する.
    const fileName = createPdfFileName();
    // ファイル名を設定してフォルダにPDFファイルを出力する
    DriveApp.getFolderById(folderId).createFile(blob.setName(fileName));
    // 出力完了メッセージを表示
    Browser.msgBox(`このスプレッドシートと同じフォルダに「${fileName}」を出力しました。`);
  } catch(e) {
    Browser.msgBox(`PDF出力に失敗しました\\n${e.message}`);
  }
}

/**
 * PDF出力用のURLを作成する.
 * @param {Spreadsheet} 出力対象のスプレッドシート.
 */
function createUrlForPdf(spreadsheet) {
  const params = {
    'exportFormat': 'pdf',
    'format': 'pdf',
    'gid': spreadsheet.getSheetByName('PDF出力したいシート名').getSheetId(), // シート名を指定して出力対象シートのIDを指定
    'size': 'A4', // 用紙サイズ:A4
    'portrait': false, // 用紙向き:縦
    'fitw': true, // 幅を用紙に合わせる
    'horizontal_alignment': 'CENTER', // 水平方向:中央
    'gridlines': false, // グリッドライン:非表示
  }
  const query = Object.keys(params).map(function(key) {
    return encodeURIComponent(key) + '=' + encodeURIComponent(params[key]);
  }).join('&');
  return `https://docs.google.com/spreadsheets/d/${spreadsheet.getId()}/export?${query}`;
}

/** PDFファイル用のファイル名を作成する. */
function createPdfFileName() {
  const timestamp = Utilities.formatDate(new Date(), "JST", "yyyyMMdd_HHmmss");
  return "output_" + timestamp + ".pdf";
}

/**
 * スプレッドシートが格納されているフォルダのIDを取得する.
 * @param {Spreadsheet} 対象のスプレッドシート.
 */
function getFolderIdBySpreadsheet(spreadsheet) {
  const parents = DriveApp.getFileById(spreadsheet.getId()).getParents();
  const folder = parents.next();
  return folder.getId();
}
```
</div></details>