2012年10月23日火曜日

ICS以降のXmlPullParserで起きる不都合とか

androidのXml#newPullParserで取得できるパーサはバージョンによって異なる。

具体的には、
  • Android3.2まではExpatPullParser
  • Android4.0以降はKXmlParser
---

もともとandroid SDKにはこの2種類のパーサが用意されていた。

Xml#newPullParserで取得できるExpatPullParserの挙動にはバグが多いため、XmlPullParserFactory#newPullParser()で取得できるKXmlParserを推奨しているサイトもあった

しかし、ほとんどの書籍やサイトでは両者を区別せず、呼び出し方が簡単としてExpatPullParserを推奨されていた。もちろん自分も今まで全く知りませんでした。(解析器周りは複雑すぎてよく分からないことが多い…)

ICSになって、バグの多いExpatPullParserは削除され、Xml#newpullParserでもKXmlParserのインスタンスが取得できるようになった。結果として、バグったパーサ上で正常に動いていたコードが、逆に動かなくなる…という現象が発生した。


その原因と対策方法はDevelopers Blogでちゃんと解説されていたんだけど、スルーしてたせいで完全にハマってしまった。

---

それとは別に、ICS以降のExpatPullParserでのみ起きる不都合が存在する。


それはxmlにBOM(Byte-Order-Mark)が付与されている場合に発生する。BOMとはエンコード方式を特定するための特殊なバイト列のことで、一部のテキストエディタではUTF-8で保存した場合に先頭にBOMを付与する。


上記はWindows標準メモ帳にてUTF-8で保存したファイルを、バイナリエディタで開いたもの。先頭にEF BB BFのバイト列が埋め込まれていることが分かる。しかし、メモ帳側からはそのことは分からない

ExpatPullParserはこのBOMを解釈できないのか、TEXTノードとみなすのか、文頭でnextTag()を呼ぶと例外が発生してしまう。3.2以前のパーサではBOMが存在しても無視していたので、この点では退化している。

まあandroidが悪いというよりも、後方互換性からBOMを放置するJava悪いという気がする。対処するには、xmlファイルを格納したStringの0文字目を識別する。String#charAt(0)が0xFEFFなら、String#subString(1)で除外。

---

2つの不都合の見分け方は、以下の通り。
  1. 例外の発生箇所がnextText()の直後であり、UnexpectedToken...というエラーメッセージでposition:START_TAGと書かれていた場合、パーサの変化が原因である。
  2. 例外の発生箇所がxml読み込み開始直後であり、UnexpectedToken...というエラーメッセージでposition:TEXTと書かれていた場合、BOM問題が原因である。

0 件のコメント:

コメントを投稿