高橋征義です。

先に要旨を書いておきます。

・XMLParserでUTF-16LE&CR+LFの文書をファイルオブジェクトの形で
  parseすると落ちる(原因はexpat?)。
・その前に、XMLParserの「getsメソッドがあるオブジェクトの場合、
  行ごとに読み込んでparseする」という仕様は、素朴に行なうと
  UTF-16 Little-Endian の場合に(16bitの)コードユニットを分断して
  しまうことになるが、それは構わないのか構うのか?  構うなら
  どうするべきか?

とりわけ後者の方の意見を聞いてみたいです。

                           □

では、本文です。

xmlparserを使ってUTF-16の文書をparseしようとすると、
Segmentation faultになってRubyごと落ちることがある
ようです(Ruby 1.6.5 + XMLParser 0.6.1 + expat 1.95.2)。

これは、

  require 'xmlparser'
  
  class FXMLParser < XMLParser; end

  ## UTF-16なXML文書をtmp.xmlにいったん書き込む
  File.open("tmp.xml","w"){|f|
    f.write("\377\376<\000a\000>\000\r\000\n\000"+
  	  "<\000/\000a\000>\000\r\000\n\000")
  }
  
  ## ファイルオブジェクトにしてparseする
  File.open("tmp.xml"){|f|
    x = FXMLParser.new()
    x.parse(f)
  }

というように、Fileオブジェクトを渡してparseさせようとした際に、
ファイルの中身がUTF-16(LE)で改行がCR+LFだった場合に発生するよう
です。

UTF-16(LE)の文書の場合、改行は「\r \000 \n \000」といったように
なるわけですが、この最後の1回前、「(..略..) > \000 \r \000 \n」
という部分まで読みこみ(まだ最後の「\000」は読み込んでいない)、
ここまでをparseするべくXML_Parseを呼ぶと、そこで失敗するようです。
# というわけで、どうやらexpatのせいのような気が (--; 

                           □

とはいえ、現在のXMLParserの「getsを使って改行(\n)まで読み込む」
という仕様がちょっと美しくないかも、という気もします。なぜなら、
UTF-16(LE)の場合、1コードユニットの半分だけを読み込む、という
ことになってしまうためです。

ちなみにPerlのExpat.xsとPythonのpyexpat.cでは、ファイルから
読み込む場合は XML_ParseBufferを使い、固定バイト数だけreadで
読み込んでます。当然ながらバッファのバイト数は2の倍数なわけで、
UTF-16が途中でぶち切れるようなことはなさそうです。

もっとも、Rubyの場合、直接ファイルを読み込むのではなく、
あくまで「getsメソッドを持っているオブジェクトは、ファイルで
あるかどうかかかわらず、getsメソッドを使って読み込みながら
parseする」ということをやっているわけで、PerlやPythonと
同じようにはできないですし、するべきでもないように思います。

それでも、getsではなくreadを使うことにして、行ごとではなく
固定バイト数ごとにparseする、というのはアリかもしれません。
……とはいえ、これもいまいちの解決法ではありますが。たまたま
expatが対応している文字コードではうまくいく、というだけの
話ですし。


というわけで、どうしたもんでしょうね?
# やっぱそもそもexpatをなんとかするべきなんだから、Rubyは
# 今のままでよい?  それとも?

高橋征義 (TAKAHASHI Masayoshi)       Email:maki / inac.co.jp