XPCOMでUTF-8文字列から入力ストリームを作る

  • 最近、XULの勉強をしている。
  • Gaucheの豊富なポート操作に慣れているおかげで、XPCOMのストリームの理解がそれほど苦にならない。
  • GaucheはポートにバイナリIOとキャラクタIOを混ぜて使えたり、ファイルを重複して開いてもポートへの操作を共有してくれたりと、微に入り細を穿っている。
    • それに反してXPCOMでは、
    • 例えば、バイナリIOのnsISeekableStreamのseekと、キャラクタIOのnsIConverterInputStreamのreadStringは混ぜられない(たぶん)。(追記:2009/4/27 詳しく言うと、seekでUTF-8文字列の"キリの悪いところ"を指すと、readStringに謎の文字がでる。Gaucheは謎の文字を"不完全文字列"としてちゃんと区別してくれるので助かる。)
    • nsIFileInputStreamはファイルを重複して開こうとすると前のストリームを勝手に閉じてしまう。
  • JavascriptSchemeに設計のヒントを得ているというし、やはりSchemeを勉強しておいてよかった。
  • "プログラミングに必要な知恵はすべてGaucheユーザリファレンスで学んだ"

さて、XPCOMで、Gaucheのopen-input-string、つまりUTF-8文字列から入力ストリームを作ってみよう。2通りの方法を得た。

  • 1. nsIScriptableUnicodeConverterでUTF-8文字列から入力ストリームを作り、それをnsIConverterInputStreamでUTF-8入力ストリームにする。
const Cc = Components.classes;
const Ci = Components.interfaces;

function openInputString(aString) {
  var converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"]
                    .createInstance(Ci.nsIScriptableUnicodeConverter);
  converter.charset = "UTF-8";
  var s = converter.convertToInputStream(aString);

  var stream = Cc["@mozilla.org/intl/converter-input-stream;1"]
                    .createInstance(Ci.nsIConverterInputStream);
  stream.init(s, "UTF-8", 1024, Ci.nsIConverterInputStream.DEFAULT_REPLACEMENT_CHARACTER);
  return stream;
}
  • 2. nsIStorageStreamという入出力ストリームに、UTF-8Pascal文字列(先頭4byteに、文字列の長さが書いてある)をnsIBinaryOutputStreamで出力してから入力ストリームをとって、それをnsIConverterInputStreamでUTF-8入力ストリームにする。
function openInputString2(aString) {
  var storage = Cc["@mozilla.org/storagestream;1"]
                  .createInstance(Ci.nsIStorageStream);

  // I have no idea what to pick for the first parameter (segments)
  storage.init(4, 0xffffffff, null);
  var out = storage.getOutputStream(0);

  var binout = Cc["@mozilla.org/binaryoutputstream;1"]
                 .createInstance(Ci.nsIBinaryOutputStream);
  binout.setOutputStream(out);
  binout.writeUtf8Z(aString);
  binout.close();

  var trunc = 4;
  var s = storage.newInputStream(trunc);

  var stream = Cc["@mozilla.org/intl/converter-input-stream;1"]
                 .createInstance(Ci.nsIConverterInputStream);
  stream.init(s, "UTF-8", 1024, Ci.nsIConverterInputStream.DEFAULT_REPLACEMENT_CHARACTER);
  return stream;
}

使用例。コンソールに"あいうえお"と出る。

var stream = openInputString("あいうえお");
var str = {};
var numChars = stream.readString(1024, str);
if (numChars != 0 /* EOF */)
  Application.console.log(str.value);

どっとはらい

ついでに、方法2でwriteUtf8Zが書き込んだPascal文字列の先頭4byteを見るコード

function pascal(aString) {
  var storage = Cc["@mozilla.org/storagestream;1"]
                  .createInstance(Ci.nsIStorageStream);

  // I have no idea what to pick for the first parameter (segments)
  storage.init(4, 0xffffffff, null);
  var out = storage.getOutputStream(0);

  var binout = Cc["@mozilla.org/binaryoutputstream;1"]
                 .createInstance(Ci.nsIBinaryOutputStream);
  binout.setOutputStream(out);
  binout.writeUtf8Z(aString);
  binout.close();

  var s = storage.newInputStream(0);

  var binin = Cc["@mozilla.org/binaryinputstream;1"]
                .createInstance(Ci.nsIBinaryInputStream);
  binin.setInputStream(s);
  var bytes = binin.readByteArray(4);
  Application.console.log(bytes);
}

"あいうえお"だとUTF-8で15byteなのでコンソールに[0,0,0,15]と出る。