はじめに
最近、立て続けにCSVアップローダを絡めた案件依頼がありまして、その度にGAS側のバグのため、古い実行環境で運用する必要があります、というような説明をしていました。もちろん、代替案としてドライブ経由のアップロードについても説明し、選択していただくようにしていました。ただ、この対応は思考することを放棄しているようで、なんとなくスッキリしませんでした。そこで、なにか対応策はないものかと、調べてみました。
V8ランタイムとRhino
執筆時点でのGAS開発環境のデフォルトはV8ランタイムとなっています。それ以前はMozillaのRhino(ライノ)というエンジンで動いていたようですが、この移行により、モダンなJavascript(一部を除きES2019まで)の記法や関数が使えるようになりました。それと同時に、処理速度も向上しているようです。
しかしながら、エンジン移行に伴う影響も多々報告されています(多くは過去資産が動かなくなったというものですが)。これらについてはofficeの杜さんの記事が非常に参考になると思います。1
その影響の一つに、本記事のメインテーマ、アップローダが機能しない、というものがあります。具体的には、HtmlServiceにて生成したフォーム経由でアップロードを実行した場合、ファイル送信処理のままになる、バイナリデータが壊れる、というものです。テキストファイルなら大丈夫という報告も散見されますが、少なくとも私のコードではGAS側で受け取ることはできませんでした。
V8ランタイム環境で運用できることが最善
暫定的にV8ランタイムからRhino環境に切り替えられるようになっていますが、いずれ廃止されるであろうことを考慮すると、いつまでもRhino環境で運用していただくわけにはいきません。V8ランタイム環境で運用できることが最善、ということで、バイト配列(型付き配列)2 に変換してからGASへ送信するという回避策がありましたので実装、検証してみました。
目的
V8ランタイム環境下にて動作するCSVアップローダを実装します。ただし、今後対応される可能性も高いため、あくまでも暫定対応という位置づけです。
ロジック
CSVを展開するシートをアクティブにした状態で、カスタムメニューによる実行を前提としています。
先述した記事を参考にし、フォーム部分のHTMLを記述します。
<form id='myForm'>
<input name="myFile" type="file">
<button onclick="fUpload(this.parentNode)">UPLOAD</button>
</form>
<div id='message'></div>
<script>
// UPLOAD処理
const fUpload = fObject => {
const raw = fObject.myFile.files[0]
const fr = new FileReader()
fr.onload = e => {
const obj = {
mimeType: raw.type,
bytes: [...new Int8Array(e.target.result)] // 受け取ったデータをバイト配列へ
}
google.script.run
.withSuccessHandler(onSuccess)
.withFailureHandler(onFailure)
.stUploaderV8(obj)
}
fr.readAsArrayBuffer(raw)
document.querySelector('#myForm').style.display = 'none'
document.querySelector('#message').innerHTML = 'UPLOAD...'
}
// 成功時処理
function onSuccess() {
google.script.host.close()
}
// 失敗時処理
function onFailure(e) {
document.querySelector('#message').innerHTML = `ERROR! ${e.message}`
}
</script>
アップロードしたCSVデータをクライアントサイドでバイト配列へ変換し、GASに渡します。[...new Int8Array(e.target.result)]
のように、クライアントサイド側ならRhino環境でもスプレッド構文などが使えます。
下記、受け取り側のGASコードです。
function stUploaderV8(fObject) {
const csvData = Utilities.newBlob(fObject.bytes, fObject.mimeType).getDataAsString() // バイト配列として受け取ったデータをCSVとして取得
const parsed = Utilities.parseCsv(csvData)// 二次元配列へ
// CSVをアクティブシートへセット
const Sh = SpreadsheetApp.getActive().getActiveSheet()
/* 以降の処理省略 */
}
バイト配列をnewBlob()
経由でCSV変換し、perseCsv()
で二次元配列に変換します。二次元配列へ展開できたら後はシートへセットするだけですので、以降の処理については割愛します。
最後に
V8ランタイムではHtmlService経由のフォームオブジェクトをうまく解析できないようで、事前に解析しやすいようにした上でGASへ渡す、ということなのでしょうね。
表現として合っているかどうかは不明ですが、薬で言えば、糖衣錠やカプセルタイプのものではなく、粉状やドリンクタイプ、点滴等に代えて対処しなければならないということでしょうね。
余談
他者様が構築したプロジェクトのリファクタリングや修正を行う機会もあり、そのときに遭遇したのが、Rhino環境下でアップローダが機能しなくなったケース。NDAのため詳しくお伝えできませんが、GAS側のCSV→二次元配列処理中に止まっていました。
[参考]
- Google Apps ScriptのV8 Runtime対応を検証してみた officeの杜
- Javaer101: Google Apps Scriptをv8ファイルアップロードに移動すると、サイドバーから機能しなくなりました
- JavaScript 型付き配列 - JavaScript | MDN