2016 07 03

HTML5 File API : read

身の回りからIE8が一掃されていることに気づいたのが先月。 せっかくなので改めてHTML5で新たに追加された機能を見直していて、これまでスルーしていたFileAPIを使ってみたくなった。 で、暇な時間に色々試して大体掴めてきたので、先々の作業で雛形として使えるようにポイントだけ抜き出しておく。

動作確認したのは以下の組み合わせ。

IE 11 Edge Chrome FireFox Safari
Windows 7
Windows 10
OSX Yosemite

ブラウザは現時点の最新版だが、OSは微妙に古い。

read

まずはテキストファイルの読み込み。

ファイルを選択して、そのファイルを読んで、中身をテキストエリアに表示するサンプル。

read_sample.html

<!DOCTYPE html> <html> <head> <meta charset="UTF-8" /> <title>File-API Read Sample</title> <script> function doLoad() { try { var f = document.getElementById( "srcFile" ).files[ 0 ]; var r = new FileReader(); r.onload = function ( evnt ) { document.getElementById( "dspArea" ).value = evnt.target.result; }; r.onerror = function ( evnt ) { var code = evnt.target.error.code || "-"; var name = evnt.target.error.name || "-"; var message = evnt.target.error.message || "-"; alert( "ERROR : " + code + " : " + name + " : " + message ); }; r.readAsText( f ); } catch ( e ) { alert( e ); } } </script> </head> <body> <div id="controls"> <input type="file" id="srcFile" /> <input type="button" value="load" onclick="doLoad()" /> </div> <div id="contents"> <textarea id="dspArea" rows="10" cols="30"></textarea> </div> </body> </html>

大まかな流れは以下の通り。

  1. FileReader オブジェクトを生成する。
  2. 生成した FIleReader オブジェクトに、必要なイベント処理を設定する。
  3. ファイルオブジェクトを引数にテキストファイル用の読み込みメソッドを実行する。

FileReader を使うと、ファイルの読み込みは非同期処理で実行される。 同期処理としたい場合は FileReaderSync オブジェクトを使うのだが、非同期の方が何かと便利なので、とりあえず非同期で。

イベントには開始/途中一定周期毎/成功/失敗/終了とあるのだが、とりあえず成功と失敗を押さえておけばいいだろう。

必要なイベントに対する処理を準備してから、読込実行メソッド readAsText を呼ぶ。 ここで引数として渡すファイルオブジェクトは、HTMLのファイル選択要素から取得する。

この readAsText には、第2引数としてエンコーディングを指定することもできる。 当然だが、指定すればそのエンコーディングのファイルとして読み込み、これをユニコードに変換して内部展開する。 省略した場合は UTF-8 として処理する。

開始位置とサイズを指定して、ファイルの一部だけを読むこともできる。 また、テキストファイルだけじゃなくてバイナリデータを扱えるメソッドも用意されている。 が、それらの使い所が思いつかないので、ここではスルー。

各イベント処理の function は、引数にイベントオブジェクトが渡される。 イベントオブジェクトの taregt に FileReader オブジェクトが設定されている。 FileReader オブジェクトの result に読み込んだファイルの内容が設定される。 上の雛形をそのまま使うなら、引数から FileReader オブジェクトを取得しなくても、r.result でいける。

読込成功時は、この result の内容を処理することになるだろう。

読込失敗時は、ブラウザによって挙動が違うので注意が必要。

今回、エラー発生時の動作確認のために、以下の手順を実行した。

  1. ファイル選択。
  2. OS上で、選択したファイルの名前を変更。
  3. もう存在しなくなったファイルの読込実行。

読もうとしたファイルの名前が変わっているために、ファイルが見つからないというエラーが発生すると期待したのだが。

IE 11, Edge

いきなり残念な結果だが、この手順ではエラーが発生しない。 よってエラー処理は未確認。 しかし何故?

処理を追いかけてみると、onerror ではなくて onload の方が実行されている。 その結果、IE 11 では何も起こらない。 Edge の方は、表示欄に何か表示していた場合はそれが消える。 result には長さ0の文字列が設定されていたので、たぶんこれを書いているのだろう。

onerror の方に制御がこないので当然だが、FileReader オブジェクトの error には何も設定されていない。

Chrome

ファイルが無いことを検知して、onerrorの方に制御がくる。 で、FileReader オブジェクトの error に FileError オブジェクトが設定される。 ここからエラーコードとエラー名、詳細メッセージを取得できる。

FireFox

指定したファイルを読む前に名前を変更すると、onerror ではなく try - catch の方で例外がキャッチされる。 一度読み込みが成功した後にファイル名を変えて再読込すると、onerror の方に制御がくる。 何故? 何かキャッシュしてたりするのだろうか。

onerrorの方に制御がくる場合は、FileReader オブジェクトの error に FileError オブジェクトが設定されるのは Chrome と同じ。 だがこちらにはエラーコードが存在しない。 詳細メッセージは属性としては存在するが、値は空文字列。 エラー名だけが取得できる。

何が違うのかと思ったら、オブジェクトの体系が違うらしい。

Chrome の場合、厳密に言うと、error には FileError をプロトタイプとするオブジェクトが設定されている。 FireFox の場合、DOMError が設定されている。 何故そんなことになっているのかは判らないが。

Safari

ほぼ Chrome と同じ挙動。

違うのは、設定されるエラー情報がコードだけなこと。 エラー名も詳細メッセージも、そもそも属性が存在しない。

属性としては存在しないのだが、エラーコードを取得するためのキーが FileError のプロトタイプ中に定義されていて、これがエラー名として使えそう。 key がエラー名で value がエラーコードという構成なのでコードから名前を取得するにはちょっと面倒だけど、コード以外に何か情報を表示したい場合はこんな感じ。

function getErrorName( error ) { if ( error.name ) { return error.name; } var errDef = Object.getPrototypeOf( error ); for ( var name in errDef ) { if ( errDef[ name ] === error.code ) { return name; } } return ""; }

こうすると、例えばエラーコードが1だったときは、"NOT_FOUND_ERR" が取得できる。

連想配列で一意性が保証されるのはkeyだけなので、これは論理的には問題があるコードなのだが、実運用上エラー名を一意にしないってのは考えにくいし、実際 Safari では一意になっているし、とりあえずは大丈夫だろう。

長くなったので、続きは明日。