StAXのパーサの不具合?
PilikaではStAXをXMLのパーサに採用しているわけですが、実装時からどうもおかしな挙動をしていました。
XMLStreamReader#getLocationは「プロセッサの現在の位置」を取得するメソッドなのですが、実際にとれる位置は現在の位置ではなくて次の要素が取得できる位置になっているのです。これは「そういう仕様なのか?」と解釈して、実装をその仕様にあわせて行っていました。ところが、今回JRuby on Railsで動かしたところ、どうもパーサが異なる挙動をしてレンダリングが動作しません。
テストも通っているのでおかしな話なのですが、調べていくと、どうもJRuby on Railsの環境のみで発生するようです。JRuby上で実行するだけならば、た例えjarを取り込んだとしてもJavaのみで実行した場合と同じ挙動をします。次に具体的なサンプルを示します。
まず、Javaのコードです。mainメソッドなどに貼り付ければ実行できると思います。
コードはXMLStreamReader を使い、イベント毎に開始タグ、終了タグ、テキストをgetLocation().getCharacterOffset()の位置と共に表示しているだけです。
javax.xml.stream.XMLInputFactory factory = javax.xml.stream.XMLInputFactory.newInstance(); factory.setProperty(javax.xml.stream.XMLInputFactory.SUPPORT_DTD, false); factory.setProperty(javax.xml.stream.XMLInputFactory.IS_COALESCING, true); factory.setProperty(javax.xml.stream.XMLInputFactory.IS_REPLACING_ENTITY_REFERENCES, false); XMLStreamReader reader = factory.createXMLStreamReader(new java.io.StringReader("<html><body>Hello ${world}.</body></html>")); while (reader.hasNext()) { int c = reader.next(); int offset = reader.getLocation().getCharacterOffset(); switch (c) { case javax.xml.stream.XMLStreamReader.START_ELEMENT: System.out.println(offset + ":START_ELEMENT:" + reader.getName()); break; case javax.xml.stream.XMLStreamReader.END_ELEMENT: System.out.println(offset + ":END_ELEMENT:" + reader.getName()); break; case javax.xml.stream.XMLStreamReader.CHARACTERS: System.out.println(offset + ":CHARACTERS:" + reader.getText()); break; default: break; } }
実行結果はこうなります。
6:START_ELEMENT:html 12:START_ELEMENT:body 29:CHARACTERS:Hello ${world}. 34:END_ELEMENT:body 41:END_ELEMENT:html
次にJRuby on Railsで動かすサンプルです。ほとんど変わりませんが、文法はRubyという不思議なコードです。このコードは、適当なコントローラでも作って、そこで実行してみてください。環境はNetbeans6.5のデフォルトである、JRuby1.4です。
require 'java' factory = javax.xml.stream.XMLInputFactory.newInstance(); factory.setProperty(javax.xml.stream.XMLInputFactory::SUPPORT_DTD, false); factory.setProperty(javax.xml.stream.XMLInputFactory::IS_COALESCING, true); factory.setProperty(javax.xml.stream.XMLInputFactory::IS_REPLACING_ENTITY_REFERENCES, false); reader = factory.createXMLStreamReader(java.io.StringReader.new("<html><body>Hello ${world}.</body></html>")); while reader.hasNext() c = reader.next() offset = reader.getLocation().getCharacterOffset() case c when javax.xml.stream.XMLStreamReader::START_ELEMENT puts offset.to_s + ':START_ELEMENT:' + reader.getName().to_s when javax.xml.stream.XMLStreamReader::END_ELEMENT puts offset.to_s + ':END_ELEMENT:' + reader.getName().to_s when javax.xml.stream.XMLStreamReader::CHARACTERS puts offset.to_s + ':CHARACTERS:' + reader.getText() end end
実行結果です。
0:START_ELEMENT:html 6:START_ELEMENT:body 12:CHARACTERS:Hello ${world}. 27:END_ELEMENT:body 34:END_ELEMENT:html
パースしているXMLは同じなのですが、比較してみると完全にずれてます。
イベント | Java | JRuby |
---|---|---|
START_ELEMENT:html | 6 | 0 |
START_ELEMENT:body | 12 | 6 |
CHARACTERS:Hello ${world}. | 29 | 12 |
END_ELEMENT:body | 34 | 27 |
END_ELEMENT:html | 41 | 34 |
ドキュメントを読む限りでは、JRuby側の方が正しいですね。というか、Java版のロケーションは完全に1つ先を出力しています*1。
というわけで、JRuby on Rails でPilikaを動かすのに難航しております。解決方法や情報がありましたら、教えてください。
*1:それにはまった。現状は強引に直前の位置を使っている