ウェブブラウザ上のJavaScriptでExcelファイルをゴニョゴニョしたい

2022年6月13日 修正。追記を削除。FileReaderを使用したコードを削除。代わりに Blob.arrayBuffer()メソッドを使用したコートに変更しました。


2021年10月21日 追記 FileReaderを使わず、Blob.arrayBufer()を使用したコードを追記しました。


特に需要はないと思いますが、極たま~~~にウェブでエクセルファイルを扱うことがあります。
なんつっても、世の中の文書フォーマットは、マイクロソフトのエクセル(Excel)がデファクトスタンダードです💦
これはもうどうにもなりません。CSVで、つっても、エクセルファイルをよこしやがります。

まぁ、やっぱり業務システムにはエクセルは欠かせません、というか、これなしには、日本は動きません。日本の企業とか国、地方公共団体は、エクセルで動いているんです。これはもう動かしようのない事実であり当分の間は変わりませんし、変化の兆しも見えません。

しょうがない。

と、開発者の方が思ったかどうかわかりませんが、ブラウザのJavaScriptでエクセルファイルをパースして編集して、出力してくれる ライブラリがあるんですね。サーバーサイドのNode.jsでも使えます、というかそっちがメインのライブラリなのかも。

一般的にMS-Officeをインストールしたパソコンにはおまけ?として、EXCELのオートメーションが使えるようになってます。あくまでOfficeを買えば!の話ですが。
Windows Scripting HostなどからVBScriptやJScriptを使ってカンタンにエクセルファイルをアーダコーダできます。

・・・が、今回はこういう噺ではありません。
一般的なブラウザ上で、エクセルファイルをアーダコーダできるライブラリがあったんです。需要がなかったので今まで知らなかった!

SheetJS Spreadsheets simplified

で、ググったりしていろいろ調べると、意外にカンタンに使えちゃいますね。ただし、javascriptの blobとかFileReaderとかArrayBufferとかUint8Arrayとか・・・そういうちょっとややこしめの知識が必要です。

昔と違ってブラウザでローカルファイルとか普通に扱えます。ですが、その時は必ず、上記のFile/Blob/FileReader/ArrayBuffer/TypedArrayとかが絡んできます。
FileとBlobの関係、ArrayBufferとTypedArray(Uint8Arrayとか)の関係、さらにはFileReaderとFile/Blobの関係。このあたりは鬼門です。なんでこんなめんどくさいんだよ!っていつも思います。

MDNとかのリファレンスを読むのが手っ取り早いのですが・・・とりあえず、MDNのサイトでは、こういう場合は、こうする、という「お約束」の手順が書かれているので、まずそれを丸覚えするのがいいと思います。公文式です(笑) 理屈は後から学べばいいんです。ただ、ググってブログ記事を参考にするのは結局は理解するのが遅くなってしまうので、リファレンスとサンプルを読んで、書いて、試してみたほうがいいと思ってます。

※ たぶんIEでは動かないと思う。試してないけど。IEは既にMSも認めたオワコンなんで、どーでもいい。

下のデモ(テストコード)

さて、HTMLファイルをサクッと書きます。(Ryzenの価格表コピペしました。)

JSライブラリは適当にCDNから引っ張ってきましょう。僕は jQuery好き好き人間なので、jQueryを使用します。すみません。
INPUT[type=file]タグでローカルファイルの口としましょう。ドラッグ&ドロップを仕込んでもいいのですが、コードをカンタンにするため、普通のファイル選択にしました。ここでローカルファイルを選んでjavascriptコードに放り込みます。

で、次に実際の処理を書いていきます。まず、基本。INPUT[type=file]のonchangeイベントハンドラを起点にしています。(1311行目付近)

ローカルファイルを読み込んでゴニョゴニョするときは、必ずFileReaderのインスタンスを作って、onloadイベントで処理を行います。(18行目付近)
下記例では、FileReader.readAsArrayBuffer() していますが、単純に Data URIが必要であれば FileReader.readAsDataUrl() を使用します。
ローカルの画像ファイルを読み込んで表示するときは、readAsDataUrlメソッドを使いますよねぇ。
ちなみにreadAsArrayBufferメソッドを使うと、onloadイベントハンドラ内で ev.target.result によってArrayBufferオブジェクトを得ることができますが、

Internet Explorerが今月中(2022年6月現在)に抹殺される。そして、FileReaderを使うやり方は、もう時代遅れなので、Blob.arrayBuffer()メソッドとasync/await構文を使ったものにしましょう。

直接このArrayBufferオブジェクトにアクセスすることができません。必ず、TypedArray・・・たとえば、Uint8Arrayなどのオブジェクトのインスタンスからアクセスします。めんどくさいですねぇ。

FileReader.onload内で、ev.target.result を そのまま Uint8Arrayコンストラクタに渡して、ArrayBufferへアクセスするための Uint8Arrayオブジェクトを作り(21行目付近)、それを XLSX.readメソッドに渡します。ただそれだけで、エクセルファイルをパースしてくれます。あとは、XLSX.utils オブジェクトのメソッドをコールしていけば、だいたいのことはできると思います。
CSVデータが欲しければ、XLSX.utils.sheet_to_csv, JSONデータが欲しければ XLSX.utils.sheet_to_json、シートを増やしたければ、XLSX.utils.append_sheet、他にも javascript配列をシートに、DOM TABLE要素をシートに・・・とか対応するメソッドがあるようです。この辺のドキュメントはgithubをたよりに試行錯誤するしかないのかな。。。

エクセルファイルにシートを追加して、そのエクセルファイルをダウンロードする、これだけのことをクライアント内(ブラウザ内)で完結することができてしまいます。
ローカル内でサーバーを立てる必要もありません。上記二つのHTMLとJSを任意のフォルダに適当な名前で保存して、file://スキームで試すことも可能です。

(1) HTMLファイル ローカルで開いたところ。

(2) エクセルを選択すると、ダウンロードリンクが追加されます。

(3) 元のエクセルはいつもダミーで使わせてもらってる疑似会社情報のエクセルファイル
( http://hogehoge.tk/personal/ )

(4) HTMLのテーブルデータをエクセルシートにぶっこんでくれます。
ただしセル書式は全部吹っ飛びます。

今回は、エクセルシートを追加してみましたけど、セルの属性とかは全部ぶっとんでしまいました💦
が、単にJSONやCSVが取れればいい、というのが大半の需要かと思いますので問題はないでしょう。

僕は エクセルファイルをサーバー側で変換するのではなく、クライアントで一旦CSVに変換してから、サーバーにアップロードして、処理を行う用途に使用しました。サーバーでエクセルファイルを処理すると重くなるんで・・・。
エクセルを読むだけなら、カンタンにできるので、本当にありがたいライブラリです。

ただ一点、ちゃんとしたリファレンスドキュメントがありません。。。これはPro版を買えってことなのかなぁ。。。Community版はApache License 2.0なんで、「おまえら、ソース読んで、自分でなんとかしろよ」ってことなんでしょうね。