文字列と UTF-8 バイト列の相互変換 ― 2006年10月23日 23時11分
やっていることは「高度な JavaScript 技集」の「UTF-8 <-> UTF16 変換」と同じ。
function toUTF8Octets(string) {
return unescape(encodeURIComponent(string));
}
function fromUTF8Octets(octets) {
return decodeURIComponent(escape(octets));
}
encodeURIComponent
は encodeURI
でもいい (むしろそのほうが処理する文字種が減って速くなりそう) が、decodeURIComponent
は decodeURI
にすると一部の文字 ("?"、"#" など) がデコードされなくなる。
使いどころ
Base64 エンコードする関数 (「高度な JavaScript 技集」の base64encode
や Firefox など Gecko 系ブラウザで実装されている btoa
) では渡された文字列をバイト列として扱う (btoa
では \u0100 以上の文字を含む文字列を渡すとエラーになる)。なので、あらかじめ文字列をバイト列に変換しておく必要がある。
btoa(toUTF8Octets("Base64 エンコード"));
// QmFzZTY0IOOCqOODs+OCs+ODvOODiQ==
なお、data URI を扱う際に Base64 は必須ではない。英数字以外の文字が多く含まれている場合 (画像など) は Base64 エンコードしたほうが長さの点から有利だが、そうでなければ内容を URL エンコードするだけで十分だ。
location.href = "data:text/plain;charset=utf-8," + encodeURI("日本語も OK");
余談だが、C 言語の atoi
が頭にあると、つい atob
が文字列を Base64 エンコードするものと思ってしまうが (b = Base64 という連想)、実際はその逆だ。btoa
は Binary to ASCII ということなのだろうか。
XUL アプリケーションでの使いどころ
通常、テキストファイルに書き込むときは nsIOutputStream::write
を使うが、これは文字列をバイト列として扱うので、日本語文字などをそのまま渡すと文字化けする。テキストファイルを読み込むときも、nsIScriptableInputStream::read
で返されるのはバイト列を表現する文字列である。
そのため、ファイル入出力の際に日本語文字などを扱うためには、Gecko 1.8 / Firefox 1.5 から導入された nsIConverterInputStream
/ nsIConverterOutputStream
、または nsIScriptableUnicodeConverter
を使うのだが、いちいちほかの XPCOM コンポーネントを利用するまでもない・文字コードは UTF-8 決め打ちでいいというときは以下のようにできる。
// file に content を UTF-8 で書き込む
function writeTo(/* nsIFile */ file, /* String */ content) {
var stream = Components.classes['@mozilla.org/network/file-output-stream;1']
.createInstance(Components.interfaces.nsIFileOutputStream);
stream.init(file, 0x02 | 0x08 | 0x20, 420, -1);
content = toUTF8Octets(content);
stream.write(content, content.length);
stream.close();
}
また、prefs.js から設定を読み書きする際も、nsIPrefBranch::setComplexValue
/ getComplexValue
と nsISupportsString
の組み合わせを使わずとも、日本語文字などを扱える。
var pref = Components.classes["@mozilla.org/preferences;1"]
.getService(Components.interfaces.nsIPrefBranch);
// 設定の保存
pref.setCharPref(name, toUTF8Octets(value));
// 設定の読み込み
var value = fromUTF8Octets(pref.getCharPref(name));
さて、このほかにも文字列・バイト列を扱う XPCOM コンポーネントは数多く存在するが、それぞれの場面で文字列とバイト列のどちらを使えばいいかは、XULPlanet の XPCOM Reference または使用するインターフェースの IDL ファイルから確認できる。引数、返り値、または属性の型が AString
、wstring
、PRUnichar*
なら文字列、ACString
、string
、char*
ならバイト列だ。
スクリプト中の文字列リテラルに関しては、HTML / XUL の script 要素から読み込まれた場合は文字列として扱われるが、JavaScript で作成された XPCOM コンポーネントや mozIJSSubScriptLoader
から読み込まれたスクリプトではバイト列として扱われる。どちらの場合でも同じく扱いたいときは以下のようにすることもできる。(そもそもそのような文字列は locale に分離すべきだが。)
// スクリプトの文字コードが UTF-8 であることが前提
if ("あ".length == 1)
function L(string) { return string; }
else
function L(string) { return decodeURIComponent(escape(string)); }
var s = L("日本語文字");
s.length; // 5
それにしても、nsIScriptableUnicodeConverter::ConvertFromUnicode
/ ConvertToUnicode
メソッドの名前が大文字から始まっているのは気持ち悪い。なぜ誰も指摘しなかったんだ。
参考
注意点
escape
/ unescape
は、ECMAScript では非規範的な仕様とされ、JavaScript では非推奨なので、気になる人は以下のようにするといいかもしれない。
function toUTF8Octets(string) {
return encodeURI(string).replace(/%(..)/g, function (s, code) {
return String.fromCharCode("0x" + code);
});
}
function fromUTF8Octets(octets) {
return decodeURIComponent(octets.replace(/[%\x80-\xFF]/g, function (c) {
return "%" + c.charCodeAt(0).toString(16);
}));
}
コメント
_ Alice0775 ― 2006年12月09日 05時59分
_ 仕様書作成係 ― 2008年12月23日 15時14分
大変参考になりました。
これからも、良い情報の発信をしていだければと思います。
ありがとうございました。
※メールアドレスとURLの入力は必須ではありません。 入力されたメールアドレスは記事に反映されず、ブログの管理者のみが参照できます。
※投稿には管理者が設定した質問に答える必要があります。
トラックバック
このエントリのトラックバックURL: http://nanto.asablo.jp/blog/2006/10/23/572458/tb