V8 runtime を使用した状態で google.script.run を使って HTML form オブジェクトを Google Apps Script 側へ送るためのパーサー

# 2022年2月1日更新
HTML formから取得したform objectをGoogle Apps Script側へ送った後、スプレッドシートへ値を追加するためのGASライブラリを作成しました。HtmlFormObjectParserForGoogleAppsScript_jsと組み合わせて使用します。

[https://github.com/tanaikech/HtmlFormApp](https://github.com/tanaikech/HtmlFormApp)

# 2021年12月13日更新
Martin Hawksey氏のApps Script Pulseで紹介されていました[こちらの記事](https://pulse.appsscript.info/p/2021/12/fixed-google-apps-script-web-app-html-form-file-input-fields-not-in-blob-compatible-format/)で、Web Appsでは最近、下記のサンプルスクリプト`google.script.run.upload(this.parentNode)`でファイルBlobが正常にGAS側へ送られるようになったとのことです。[Ref](https://issuetracker.google.com/issues/155109626?pli=1)

少し調べてみますと、今の段階ではWeb Appsのみでこのバグが修正されたようで、ダイアログやサイドバーではまだこのバグは解消されていないことがわかりました。[Ref](https://tanaikech.github.io/2021/12/09/fixed-google-apps-script-web-app-html-form-file-input-fields-not-in-blob-compatible-format/)

Web AppsではV8 runtimeを使用していたも下記"1. サンプルスクリプト"は使用可能ですが、例えばGoogle SpreadsheetなどのGoogle Docs上のダイアログやサイドバーでは使用できないことがわかりました。


# 概要

この投稿では Google Apps Script エディタで V8 runtime を使用した状態で google.script.run を使って HTML form オブジェクトを Google Apps Script 側へ送るためのパーサーを紹介させていただきます。

Google Apps Script は、2020 年 7 月に V8 runtime の使用が可能になりました。これにより、一部の仕様が変更されたことによる問題が発生しています。ここで紹介させていただく HTML form オブジェクトの google.script.run でのパースについても同様の問題があります。Stackoverflow でもこの問題に関する複数の質問が投稿されており、重要性の高さを理解しました。この問題は、Google 側の今後のアップデートで解消される可能性もありますが、この投稿では現時点での回避策として、この問題解決のための方法を紹介させていただきます。

# 問題の再現

初めに、サンプルとして、スタンドアロンタイプの Google Apps Script プロジェクトを作成し、次のような HTML と Google Apps Script をコピーペーストしてください。

## 1. サンプルスクリプト

### HTML side: `index.html`

```html
<form>
  <input type="file" name="file" />
  <input
    type="button"
    value="ok"
    onclick="google.script.run.upload(this.parentNode)"
  />
</form>
```

### Google Apps Script side: `Code.gs`

```javascript
function doGet() {
  return HtmlService.createHtmlOutputFromFile("index.html");
}

function upload(e) {
  DriveApp.createFile(e.file);
}
```

## 2. Web Apps のデプロイ

HTML へアクセスするために[Web Apps をデプロイ](https://developers.google.com/apps-script/guides/web#deploy_a_script_as_a_web_app)してください。

## 3. 動作テスト

次に示す 2 つの条件でスクリプトをテストします。

### 条件 1

スクリプトエディタ上で**[v8 runtime が有効になっている状況](https://developers.google.com/apps-script/guides/v8-runtime#enabling_the_v8_runtime)**で Web Apps をデプロイし、ブラウザを使って Web Apps へアクセスし、ブラウザでコンソールを開き、サンプルとして画像ファイルを選択してからボタンを押してください。これにより、Google Drive のルートフォルダにアップロードした画像ファイルが作成されます。アップロードされたファイルを開こうとすると、残念ながらアップロードされた画像ファイルは破損しているために開くことができません。この問題は文字コードの問題によるものと推測しています。

この場合、utf-8 のテキストファイルのアップロードの場合はデータの破損はなさそうです。

### 条件 2

スクリプトエディタ上で**[v8 runtime を無効にした(Rhino runtime)状況](https://developers.google.com/apps-script/guides/v8-runtime#enabling_the_rhino_runtime)**で条件 1 と同様に実行すると、アップロードされた画像は無事に開くことができます。

上記の結果から、簡単な回避策としては v8 runtime の無使用で問題ないと思われます。しかしながら、v8 runtime を使用するメリットはいろいろあります（[参考 1](https://gist.github.com/tanaikech/3331e1e631d619abef8f32c4be14ba3a), [参考 2](https://gist.github.com/tanaikech/7198bc9019202f4080de8fd2e1b278fb)）ので、v8 runtime を有効にした状況下で上記のファイルアップロードが可能になればより便利かと考えました。

そこで、次に紹介させていただくライブラリを作成しました。

# HTML form オブジェクト用パーサー

リポジトリは[こちら](https://github.com/tanaikech/HtmlFormObjectParserForGoogleAppsScript_js)です。

サンプルスクリプトとして、上記のスクリプトを修正すると、下記のように書くことができます。

`<script src="https://cdn.jsdelivr.net/gh/tanaikech/HtmlFormObjectParserForGoogleAppsScript_js@master/htmlFormObjectParserForGoogleAppsScript_js.min.js"></script>`は、HTML フォームオブジェクトをパースするための Javascript ライブラリです。

**この場合、v8 runtime は有効にしてください。**（この JS ライブラリを使用するパターンでは、v8 runtime を有効、無効の両方で動作します。）

## サンプルスクリプト

### HTML side: `index.html`

```html
<script src="https://cdn.jsdelivr.net/gh/tanaikech/HtmlFormObjectParserForGoogleAppsScript_js@master/htmlFormObjectParserForGoogleAppsScript_js.min.js"></script>
<form>
  <input type="file" name="file" />
  <input
    type="button"
    value="ok"
    onclick="ParseFormObjectForGAS(this.parentNode).then(obj => google.script.run.upload(obj))"
  />
</form>
```

### Google Apps Script side: `Code.gs`

```javascript
function doGet() {
  return HtmlService.createHtmlOutputFromFile("index.html");
}

function upload(e) {
  DriveApp.createFile(
    Utilities.newBlob(
      e.file[0].files[0].bytes,
      e.file[0].files[0].mimeType,
      e.file[0].files[0].filename
    )
  );
}
```

上記のようにスクリプトを修正した後、Web Apps を再デプロイしてからブラウザでアクセスしてください。サンプルとして画像ファイルを選択してボタンをクリックしてください。アップロードされた画像ファイルは無事開くことができます。

この JS ライブラリでは、取得したファイルをバイト配列に変換して Google Apps Script 側へ送ります。これによりデータの破損なく送ることができます。

この JS ライブラリは、ファイル入力だけでなく、フォームで送信するほとんどの input tags に対応しています。他のサンプル HTML は[リポジトリ](https://github.com/tanaikech/HtmlFormObjectParserForGoogleAppsScript_js)をご覧ください。

# 制限事項

- 今の場合、ファイル化するために Google Apps Script の Blob を使用しますので、データの最大サイズは 50 MB です。これに注意してください。50 MB 以上のファイルを Google Drive へ送る場合は、resumable upload を使用してください。このためのライブラリも用意しており、[こちら](https://github.com/tanaikech/AsynchronousResumableUploadForGoogleDrive)でごサンプルスクリプトと共に確認頂けます。