SAXパーサ作成時のバグ

以前作成したJavaのSAX方式(ストリーム読込)のXMLパーサのトラブルメモ。
長いXMLを読むだけであればメモリパフォーマンスを考慮してSAXパーサを使用しますが、ちょっと意識していないとバグ埋め込んでしまうかも知れません。

  • サンプルコード
public class ParseTest extends DefaultHandler {
  public void startElement(String uri,
                           String localName,
                           String qName,
                           Attributes attributes) {
    //要素開始タグ時の処理
  }
  public void characters(char[] ch,
                         int offset,
                         int length) {
    String value = new String(ch, offset, length);
    //valueをBeanに設定
  }
  public void endElement(String uri,
                         String localName,
                         String qName) {
    //要素終了タグ時の処理
  }
}

SAXParserFactory.newInstance().newSAXParser().parse(
    new File("test.xml"), new ParseTest());

上記はよく見かけるSAXパーサのサンプルですが、長いXMLの場合に稀にvalueが予期せず分割されバグとなることがあります。

ContentHandler#charactersのリファレンスを見ると下記のことが書かれています。

文字データの通知を受け取ります。 
パーサは、このメソッドを呼び出して、各文字データチャンクを報告します。SAX パーサは、連続する文字データを単一のチャンクとして、またはいくつかのチャンクに分割して返します。

検証したところ、readbuffer(1024byte)を超えるストリーム(XML)の場合に、そのバッファの切れ目で一度charctersがコールされ、また続きのストリームから再度charctersがコールされるようで結果valueが分割されてしまう模様です。
上記のことから正しくはcharcters()ではvalueをスタックし、endElement時に結合させるような仕掛けを作る必要があります。

今更、一からSAXパーサを作る機会は滅多にないかもしれませんが、SAXのこの仕様はあまり認知されていないみたいです。


require 'rexml/parsers/streamparser'
require 'rexml/parsers/baseparser'
require 'rexml/streamlistener'

class MyListener
include REXML::StreamListener
  def text(text)
    p text
  end
end

source = File.read "test.xml"
listener = MyListener.new
REXML::Parsers::StreamParser.new(source, listener).parse

rexml/parsers/streamparserで同じように検証してみましたがTEXT要素読込で切れることはありませんでした。



javascriptでは?、PHPでは?、.netでは?、Flexでは?・・・・・後で余力があればやります。