JavaScriptからローカルファイルを作成する方法まとめ

久しぶりに JavaScript と戯れたいなぁと思ったので JavaScript からローカルファイルを作成する方法をまとめてみました。

私の知る限り、Internet Explorer(ActiveX)、Firefox(XPCOM)、Google Chrome(File API: Writer)の3ブラウザがローカルファイルの作成に対応しています。
※File API: Writer は意味合いが違う気がしますが・・・

– 2012-04-12 追記 –
当時の正確なバージョンはわかりませんが、時期的に IE8、Firefox 5、Google Chrome 12 で動作確認していると思われます
———————

細かいことは後で説明するとしてローカルファイルを作成するコードは例えば次のようになります。
※IE、Firefox の場合は /tmp(Unix), C:\tmp(Windows)以下にファイルが作成されることが前提です

<!DOCTYPE HTML PUBLIC '-//W3C//DTD HTML 4.01 Transitional//EN' 'http://www.w3.org/TR/html4/loose.dtd'>
<html>
  <head>
    <meta http-equiv="content-type" content="text/html; charset=UTF-8">
    <script type="text/javascript">
    function writeToLocal(filename, content) {
      var ua = navigator.userAgent.toLowerCase();

      try {
        if (ua.indexOf('firefox') != -1) {  // Firefox
          filename = (ua.indexOf('windows') != -1 ? 'C:\\tmp\\' : '/tmp/') + filename;
          // ローカルファイルにアクセスする権限を取得
          // fileスキームじゃない場合は about:config で
          // signed.applets.codebase_principal_support を true にする必要あり;
          netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect');
          // ファイルコンポーネントの取得+ローカルファイル操作用のインターフェイスの取得;
          var file = Components.classes['@mozilla.org/file/local;1'].createInstance(Components.interfaces.nsILocalFile);
          file.initWithPath(filename);

          var fileStream = Components
            .classes['@mozilla.org/network/file-output-stream;1']
            .createInstance(Components.interfaces.nsIFileOutputStream);
          // ファイルが存在しない場合は664の権限で新規作成して書き込み権限で開く
          // cf. https://developer.mozilla.org/en/NsIFileOutputStream
          //     http://www.oxymoronical.com/experiments/apidocs/interface/nsIFileOutputStream;
          fileStream.init(
            file,
            0x02 | 0x08,  // 0x01: 読み取り専用, 0x02: 書き込み, 0x03: 読み書き, 0x08: 新規作成, 0x10: 追記
            0664,         // mode
            0             // 第4引数は現在サポートしていないとか
          );

          // cf. http://www.oxymoronical.com/experiments/apidocs/interface/nsIConverterOutputStream
          var converterStream = Components
            .classes['@mozilla.org/intl/converter-output-stream;1']
            .createInstance(Components.interfaces.nsIConverterOutputStream);
          converterStream.init(
            fileStream,
            'UTF-8',
            content.length,
            Components.interfaces.nsIConverterInputStream.DEFAULT_REPLACEMENT_CHARACTER
          );
          converterStream.writeString(content);

          converterStream.close();
          fileStream.close();
          alert('書き込みが完了しました!');
        } else if (ua.indexOf('chrome') != -1) {  // Google Chrome
           // 起動オプションに --unlimited-quota-for-files --allow-file-access-from-files をつける必要あり
          function errorCallback(e) {
            alert('Error: ' + e.code);
          }

          function fsCallback(fs) {
            fs.root.getFile(filename, {create: true}, function(fileEntry) {
              fileEntry.createWriter(function(fileWriter) {
                fileWriter.onwriteend = function(e) {
                  alert('書き込みが完了しました!');
                };

                fileWriter.onerror = function(e) {
                  alert('Failed: ' + e);
                };

                var bb = new WebKitBlobBuilder();
                bb.append(content);
                var output = bb.getBlob('text/plain');
                fileWriter.write(output);
              }, errorCallback);
            }, errorCallback);
          }
          // 現時点ではたぶん第1引数はPERSISTENTもTEMPORARYディレクトリ名が異なるだけだし、
          // 第2引数は極端な話0でもOK
          webkitRequestFileSystem(PERSISTENT, 1024, fsCallback, errorCallback);
        } else if (ua.indexOf('msie')) {  // MS IE
          filename = 'C:\\tmp\\' + filename;
          // インターネットオプションで「スクリプトを実行しても安全だとマークされていない
          // ActiveX コントロールの初期化とスクリプトの実行(セキュリティで保護されていない)」
          // を有効にする必要あり
          var fso = new ActiveXObject('Scripting.FileSystemObject');

          // ファイルを新規作成して書き込みモードで開く (文字コードはUTF-16)
          // cf. http://msdn.microsoft.com/ja-jp/library/cc428044.aspx
          //     http://msdn.microsoft.com/ja-jp/library/cc428042.aspx
          var file = fso.OpenTextFile(
            filename,
            2,     // 1: 読み取り専用, 2: 書き込み, 8: 追記
            true,  // ファイルが存在しなければ新規作成するかどうか
            -1     // -2: OSのデフォルト文字コード, -1: UTF-16, 0: ASCII
          );
          file.Write(content);

          file.Close();
          alert('書き込みが完了しました!');

          /*
           * ADODB.Stream を使う場合(レジストリをいじっても何故か書き込めない・・・)
           */
          // var adodbStream = new ActiveXObject('ADODB.Stream');
          // adodbStream.type = 2;  // テキストファイル(バイナリは1)
          // adodbStream.charset = 'UTF-8';
          // adodbStream.open(filename);
          // adodbStream.writeText(content);
          // adodbStream.saveToFile(filename, 2);  // 上書き保存(1だと新規作成のみが対象)
          // adodbStream.close();
        } else {
          alert('エラー: ローカルファイルへの書き込み方がわかりません・・・');
        }
      } catch (e) {
        alert('Error: ' + e);
      }
    }
    writeToLocal('test.txt', 'これはJavaScriptが作成したファイルだよ!\n');
    </script>
  </head>
</html>

かなり詳しくコメント書いてるんであまり説明の必要もないと思いますが、極簡単な説明と注意点だけ書きます。
File API も含めてどれもセキュリティ的によろしくないと思うので、テストで試してみるにしても終わったら元のオプションに戻すことをお勧めします。

IE

ActiveX を利用しています。ActiveX はご存知ブラウザだけでは実現不可能な機能を可能にする”便利”な機能です。
セキュリティオプションで「スクリプトを実行しても安全だとマークされていない ActiveX コントロールの初期化とスクリプトの実行(セキュリティで保護されていない)」を有効にする必要があります。
※fileスキームの場合は必要なし(かも)

UTF-8 で 出力しようとすると ADODB.Stream を使ったり Utf8Lib.Utf8Enc を使えば可能みたいなんですが、レジストリやセキュリティオプションをいじっても「Error: オートメーションサーバーはオブジェクトを作成できません。」というエラーが出てしまいました。1

Firefox

XPCOM (Cross Platform Component Object Model) を利用しています。Wikipediaによれば

XPCOM(Cross Platform Component Object Model)は、Mozillaプロジェクトにおいて開発されているクロスプラットフォームなコンポーネント技術である。

とのことです。Firefox のアドオンで多様な機能を提供できるのは XPCOM を使っているから(のはず)です。
about:config で signed.applets.codebase_principal_support を true にする必要があります。
※fileスキームの場合は必要なし

Chrome

File API: Writer を利用しています。File API を利用することでドラッグ&ドロップしたファイルをページに表示したりサーバにアップしたりできることはよく知られていると思いますが、ローカルに書き込みもできるように話が進んでいるようです。
File API: Writer を現時点で実装しているのは主要ブラウザではおそらく Chrome だけです。
Chrome で File API: Writer の機能を利用してローカルに書き込むには起動オプションに –unlimited-quota-for-files –allow-file-access-from-files をつける必要があります。
※近々デフォルトで有効になるのではないかと
– 2012-04-12 追記 –
Google Chrome 18 では File API: Writer の機能を使うのに起動オプション不要でした。
———————

Windows 7 の場合

コマンドプロンプトから次のコマンドを実行します(USERNAMEは自分のユーザ名を使用してください)

> cd C:\User\USERNAME\AppData\Local\Google\Chrome\Application
> chrome.exe --unlimited-quota-for-files --allow-file-access-from-files

出力されるファイルは
C:\User\USERNAME\AppData\Local\Google\Chrome\User Data\Default\FileSystem\Persistent
以下に作成されます。

Mac OS X の場合

端末から次のコマンドを実行します。

$ open -a "Google Chrome" --args --unlimited-quota-for-files --allow-file-access-from-files

出力されるファイルは
/Users/USERNAME/Library/Application Support/Google/Chrome/Default/FileSystem/Persistent
以下に作成されます。
– 2012-04-12 追記 –
Google Chrome 18 の時点では /Users/USERNAME/Library/Application Support/Google/Chrome/Default/File System 以下に作成されるようになっていました
———————

以上のようにデータをクライアント側に保存しておけばオフライン作業ができたり、簡単なアプリケーションであれば JavaScript のみで完結できたりと夢が広がりますね!

File API に興味がある方はこんなエントリーもありますよ!
Filefoxでファイルの非同期アップロード with 複数パラメータ - あらびき日記

参考

ActiveX 関係

XPCOM 関係

File API: Writer 関係

  1. regedit で \HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Internet Explorer\ActiveX Compatibility{00000566-0000-0010-8000-00AA006D2EA4} の Compatibility の値を0にすると対処できるようですが無理でした