2012年4月24日

JavaScriptで文字コード変換ライブラリ作ってみた

文字コード変換ライブラリを JavaScript で作りました。
文字列ではなく配列 or TypedArray で処理します。

追記

↓動作サンプルを作りました
文字コード変換 動作サンプル

Unicode の変換が可能になりました。
文字コード配列から URLエンコード/デコード が可能になりました。
あと説明とサンプルも少し載せました。。(説明不足でごめんなさい)

こないだの 「JavaScriptだけでzipファイルの解凍 - Unzipper.js」が
SJIS ファイルとかだと表示で文字化けするので、ついつい。。

動作確認は、zip ファイル解凍のデモページでわかると思います。
zip の中に SJIS や EUC-JP のファイル (ファイル名) がある場合でも
UTF-8 表示で化けなければ問題なしです。


↑のデモページを開いて、デスクトップなどから zip ファイルをドロップすると
解凍して結果のテキストを表示します。

※ JavaScript だけで動いていて、どっかのサーバなどに送信したり保存したりしてません


今回はぜんぶ同期で書いちゃったのですが、
バイナリファイルとかが対象だとちょっと重たくなりがち。。
気が向いたら非同期も可能にしてみたいです。

あと、Unzipper.js と同じレポジトリにしちゃったけど
これもできれば別にしたいところ。
追記→ 作りました

使い方:
// <script src="encoding.js"></script>
//
// Encoding というオブジェクトがグローバルに定義されます
// 配列に対して変換または判別します

// 文字コード変換
var utf8Array = new Uint8Array(...) or [...] or Array(...);
var sjisArray = Encoding.convert(utf8Array, 'SJIS', 'UTF8');

// 文字コード自動判別で変換
var sjisArray = Encoding.convert(utf8Array, 'SJIS');
// or  
var sjisArray = Encoding.convert(utf8Array, 'SJIS', 'AUTO');


// 文字コード判別 (戻り値は下の「共通の文字コード値」のいずれか)
var encoding = Encoding.detect(utf8Array);
if (encoding === 'UTF8') {
    alert('UTF8です');
}

// 特定の文字コードかどうか判別
var isSJIS = Encoding.detect(sjisArray, 'SJIS');
if (isSJIS) {
    alert('SJISです');
}

// convert, detect 共通の文字コード値:
//  - 'UTF32'   (detect only)
//  - 'UTF16'   (detect only)
//  - 'BINARY'  (detect only)
//  - 'ASCII'   (detect only)
//  - 'JIS'
//  - 'UTF8'
//  - 'EUCJP'
//  - 'SJIS'
//  - 'UNICODE' (JavaScript Unicode String/Array)
//
// ※ (detect only) は Encoding.detect() でのみ有効 (変換はできない)
// ※ 'UNICODE' は JavaScript の Unicode コード値 (0xFF 以上の数値になりえる)
//

サンプル:
// EUCJPの文字コード配列 (中身は 'こんにちは、ほげ☆ぴよ')
var eucjpArray = [
    164, 179, 164, 243, 164, 203, 164, 193, 164, 207, 161,
    162, 164, 219, 164, 178, 161, 249, 164, 212, 164, 232
];
// UTF-8に変換
var utf8Array = Encoding.convert(eucjpArray, 'UTF8', 'EUCJP');
console.log( utf8Array );
// output: [
//   227, 129, 147, 227, 130, 147, 227, 129, 171,
//   227, 129, 161, 227, 129, 175, 227, 128, 129,
//   227, 129, 187, 227, 129, 146, 226, 152, 134,
//   227, 129, 180, 227, 130, 136
// ]
//   => 'こんにちは、ほげ☆ぴよ'

// ---------------------------------------------
// 文字コード自動判別で変換
//

// SJISの文字コード配列 (中身は 'こんにちは、ほげ☆ぴよ')
var sjisArray = [
    130, 177, 130, 241, 130, 201, 130, 191, 130, 205, 129,
     65, 130, 217, 130, 176, 129, 153, 130, 210, 130, 230
];
// Unicodeに変換
var unicodeArray = Encoding.convert(sjisArray, 'UNICODE', 'AUTO');
// 文字列にして表示
// codeToStringは、文字コード配列を文字列に変換(連結)して返す関数
console.log( Encoding.codeToString(unicodeArray) );
//
// output: 'こんにちは、ほげ☆ぴよ'
//

// ---------------------------------------------
// 文字コードの配列をURLエンコード/デコード
var sjisArray = [
  130, 177, 130, 241, 130, 201, 130, 191, 130, 205, 129,
   65, 130, 217, 130, 176, 129, 153, 130, 210, 130, 230
];
var encoded = Encoding.urlEncode(sjisArray);
console.log(encoded);
// output:
//   '%82%B1%82%F1%82%C9%82%BF%82%CD%81A%82%D9%82%B0%81%99%82%D2%82%E6'

var decoded = Encoding.urlDecode(encoded);
console.log(decoded);
// output: [
//   130, 177, 130, 241, 130, 201, 130, 191, 130, 205, 129,
//    65, 130, 217, 130, 176, 129, 153, 130, 210, 130, 230
// ]

ダウンロード



レポジトリ



その他、なにか問題等あれば
コメントやメールまたは @polygon_planet まで送っていただけるとうれしいです。



18 件のコメント:

  1. 便利そうですが、使い方がいまいちわかりません。
    サンプルなど示していただくとありがたいのですが・・・

    返信削除
    返信
    1. サンプル作りました
      情報不足ごめんなさいでした・・

      削除
  2. 素晴らしい!!!
    誰が何と言おうと私はpolygon planetさんを神認定させていただきます!
    このライブラリのおかげで、AjaxでUTF-8以外のデータを扱いたいのにできなかった人々の多くが救いの道へと導かれるに違いありません。
    現に私は救われました!

    …とはしゃいでおきながらこんなことを申し上げるのもナンなのですが、IE(標準・互換モードともに)では動作しないようです。
    Firefox,Chromeでは問題なく動いて文字コードの認識もできているのですが、IEではいかなる場合もBINARYとして認識してしまいます(ホントにBINARY形式なのかもしれませんが)。

    BINARY形式で読み込まれるのはファイルを読み込んだ場合で、textでも文字コードが何であろうが同じです。
    一方、CGIで生成したデータを渡した場合はいかなる場合もIEはUTF-8になります。
    この場合も、Firefox,Chromeではちゃんと文字コードを認識してくれました。
    あ、JISコードのファイルだけはちゃんと認識できていました。

    その他、SJISに変換されたはずのデータがASCHIIとして認識されていたりもするようなのですが、これについては結果に問題がない(SJISのHTMLで実装しても文字化けは起きていない(Firefox,Chromeの場合))のであまり気にしていません。

    ちなみに…さらに申し上げにくいのですが、神様がお作りになられたサンプルページは、IEでは動かなかったです…(ローカルに移植すればIEでも動きました。動作結果はやはり上記同様でした)。

    この現象、自分の知識では結局どうすることもできず、またツールのそもそもの開発趣旨から外れた使用法であったり(そもそもzip解凍の際に使うツールで、BINARYはexe等を読み込んだ場合しか想定されていないでしょうから)IEをどうにかしないことにはどうにもならないことかもしれないので、対応をお願いしますと申し上げるのもいかがなものかという気がしてはいますが、それでももし対応可能であれば是非とも神のお力におすがりしたく、そうでなければこの結果を「使用上の注意」として添えて頂ければと存じます。

    長文、失礼いたしました<(_ _)>

    返信削除
    返信
    1. たくさんのお言葉ありがとうございます

      いまのバージョンは、ブラウザ関係なく
      JavaScript (ECMA-262 3rd Edition) の実装なら動作すると思います

      テストページでは、jQuery.ajax (XMLHttpRequest) からの結果の時点で
      古いIEと、Firefox, GoogleChromeなどのブラウザで異なっているのが確認できました

      古いIEでは、
      mimeType : 'text/plain; charset=x-user-defined'
      が効いてないように思えます

      これはライブラリとは別の処理なのでむずかしいのですが
      convert() などに直接配列を指定するとブラウザ関係なく動くと思います

      また、解決策として XHR の結果を 'arraybuffer' で取得するとうまくいくかもしれません
      ただ、古いIEでは実装されていません

      結局のところ解決できなくて申し訳ないです・・

      削除
    2. 早速のお返事とアドバイス、ありがとうございます。

      さて、アドバイスして頂いた通りにしてみましたところ、Javascriptにバイナリデータをどうこうさせるのはやはり毒ということなのか、arraybufferで取得すると、処理の途中でデバッガそのものまで挙動が変になる(Firebug:応答しなくなる・IEのやつ:クラッシュする)有様でした。
      さらに、arraybufferではなくtextで取得してみましたら、Ajaxを使った場合と同様でした。
      また、Ajaxにおいては、convert等へ取得したデータをそのまま投入しても上手くいきませんでした(!dataではないのにDETECTではFalseとなります)。

      が、しかし。
      もともとAjaxでSSI的なことができないものかという試みでありまして、仮にXHRでやると上手くいくにしても次にはクロスドメイン制約の問題に直面することになるわけですし、それを回避せんとしてCGIを用いるのであれば、前回の書き込みの後に解決しておりまして(CGIで受けたデータを、IEではそのまま表示しFirefox・ChromeならConvertするとOK)、外部データを使わないからCGIも不要という場合にはもっと簡単なやり方もあるぢゃないかと今さらながらに気が付きました…

      てなわけで、いろいろ試したり調べたりすればするほど、お力にすがらんとしたことがいかに的外れであったかを思い知って恥じ入るばかりです。
      と同時に、実装元・スクリプト・データそれぞれがUTF-8でなければAjaxは使えない伝説の打破を可能たらしめた本ライブラリの偉大さもまた思い知った次第であります。

      …と、またまた長い書き込みになってしまいましたが、その後の報告かたがた、素晴らしいツールを開発して下さったことに、改めて感謝いたします。本当にありがとうございました!m(_ _)m

      削除
  3. encoding.jsでUTF8の文字列をEUCJPに変換しurlエンコードをしたところ、ECUJP変換後の配列に負の値が入っていたため、正しくurlエンコードができない状態のようです。
    UTF8ToECUJPの関数内で 0x80 で引いてる行がありますが、最後に 0xFF でマスクするとすべて正の値になりurlエンコードも正しく動作しました。

    文字コードに詳しくないのでこれが正しい対処かわかりませんが、修正して頂けたら幸いです

    返信削除
    返信
    1. ご報告ありがとうございます

      さっそく修正しました。
      https://github.com/polygonplanet/Unzipper.js/commit/64c33d4e7b7b8cb3f2456a13b8c67fe9f35ce8ac

      Module 対応も一緒に push してしまって、ごっちゃになっちゃいました…

      文字コード変換、ほとんどすべてのメソッドで 0xFF 範囲外になる可能性がありました
      重要なバグ報告、感謝です, ありがとうござます

      削除
  4. 初歩的な質問ですいません。

    var str ="こんにちは";

    これが SJIS だとして、UTF-8 に変換するにはどのようにすれば良いでしょうか。

    返信削除
    返信
    1. このコメントは投稿者によって削除されました。

      削除
  5. JavaScriptでは内部文字コードがSJISになることはなく、すべてUnicode(UTF-16)です。

    var str ="こんにちは";
    var utf16arr = str.split('').map(function(c) { return c.charCodeAt(0); });
    var utf8arr = Encoding.convert(utf16arr, 'UTF8', 'UNICODE');

    こんな感じでutf-8配列にはできます。

    返信削除
    返信
    1. お返事ありがとうございます。

      var str = $("#myText").text();
      var utf16arr = str.split('').map(function(c) { return c.charCodeAt(0); });
      var utf8arr = Encoding.convert(utf16arr, 'UTF8', 'UNICODE');
      var str_utf8 = Encoding.codeToString(utf8arr);

      こちらであっているでしょうか。

      sjisのページの文字列を拾ってきて、iframeでutf8のページを埋め込む時にURLの一部として利用しているのですがまだ化けているようでした。
      何が足りないのでしょうか。

      削除
    2. iframeText = decodeURIComponent(Encoding.urlEncode(utf16arr));

      みたいな感じでどうでしょう。
      異なる文字コード間で文字列を扱う場合、一旦Stringにしてしまうと化けてしまいます。
      そのへんのこともあり、encoding.js では配列で扱っています。

      iframeなら、Encodingを使わずに onload などで文字列を渡せば表示できそうな気もしますが…

      削除
  6. 初めまして。
    実はSIFT-JISで書かれたCSVファイルを読み込んで、HTMLにinnerHTML文で書き出そうと思っているのですが、どうしても文字化けがなおりません。
    読み込んだファイルを1文字ずつ配列に入れなおしていますが、どうやら文字化けした1文字を配列にするために、ASCII文字として認識されているようです。実際、エンコード判別をencoding.jsで行ったところASCIIとの判定が返ってきました。
    こうした場合、どのように組めば思った結果が得られるでしょうか。

    SHIFT-JISのファイルを読み込んで、正しくHTMLに表示するという要望は多いと思いますが、設置説明を読んでもよくわかりません。
    お手数ですが、お教えいただけませんか。

    返信削除
    返信
    1. CSVに限らず、ファイル読み込みの時点で文字列として読み込んでしまうと化けてしまいます。
      配列で読み込む方法は、XMLHttpRequest と 'arraybuffer' を使う方法があります。
      このページ上部のデモ( http://polygonplanet.github.com/Unzipper.js/examples/encoding-test.html ) では、70行目あたりの request関数内、
      mimeType : 'text/plain; charset=x-user-defined'
      を指定して配列としてファイル読み込みしています。
      一旦文字列にしてしまうとJavaScript内部で文字化けする可能性があるので、配列で扱います。

      削除
  7. 変換元の文字コードを指定してみてください。
    var unicodes = Encoding.convert(bytes, "UNICODE", "変換元の文字コード"); // 'UTF8' など

    デモページの不具合、気づいてませんでした・・・修正しました。
    ご報告ありがとうございます!

    返信削除
  8. polygon planet さん

    引数に変換元の文字コードを指定したところ、正しく表示できるようになりました。
    Auto で Convert できるのが理想ですが、やはり難しいのでしょうか……。

    ともあれ、大変助かります。
    ありがとうございました。

    返信削除
  9. はじめまして、ありがたく使わせて頂いています。

    FileReader#readAsArrayBuffer() で読み込んだオブジェクトに対して使用していますが、
    望んだ結果が得られない箇所があります。
    私の使い方が誤っていると思うのですが、別のライブラリを組み合わせると望んだ結果を得られるため、現在その方法で使用しています。
    encoding.jsだけで望んだ結果を得られる方法はないでしょうか?

    [やりたいこと]
    ファイルを読み込んで、ファイルの中身をUTF8の文字列で取り出したい

    // FileReader#onload イベントリスナーの実装

    function(event) {
    var b = event.target.result;
    var u8 = new Uint8Array(b);
    var du8 = Encoding.detect(u8); // (A)
    var cu8 = Encoding.convert(u8, "UFT8", du8);
    var su8 = Encoding.codeToSting(cu8); // (B)

    // 他ライブラリ使用 https://github.com/inexorabletash/text-encoding
    var s = TextDecoder("UTF-8").decode(new Uint8Array(cu8)); // (C)
    }

    SJISのファイルを読み込むと、
    (A)ではSJISと判定され、
    (B)で得られる文字列は化けています。
    しかし、(C)で得られる文字列は期待どおりの文字列でした。

    返信削除
    返信
    1. コメントありがとうございます

      もしかしてですが、
      var cu8 = Encoding.convert(u8, "UFT8", du8); // ← UTF8 の typo

      ではないでしょうか
      このコードの場合、内部で UNICODE 扱いになっているようです

      削除