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のこの仕様はあまり認知されていないみたいです。
- rubyでは
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では?・・・・・後で余力があればやります。