2012/02/23

libxml2での文字列の扱いは、要注意。

iOSアプリ上で、htmlを字句解析する必要が生じた。

NSXMLParserはHTMLのような、アバウトな記述のMLを解析させると確実にエラーを吐いて、途中で解析を投げ出してしまう。
エラー無視のパラメータとかはどうやらないらしい。

というわけで、必然的にCライブラリのlibxml2に白羽の矢がたった。
コレをSAXインタフェースで使用する。
その方法は、google先生にお伺いを立てると、いろいろ教えていただけるので、記載はしない。

だが、SAXインタフェース上で、
cdataBlock検出時(たぶんその他のcharacters/commentでも同様)に渡される文字列にヒトクセあったので備忘録として記載する。


static void cdataHandler(void * ctx, const xmlChar * value, int len) 
{
}

コレが、cdataBlock検出時に呼び出される関数。
こいつの第二引数valueにUTF-8の文字列が格納されている。
そして、第三引数lenには、valueの有効文字列長が格納されている。

クセその1
scriptなどの大規模な文字列の場合、文字列が一括で渡されない。
具体的には、1000Byte単位で上記関数が呼び出される。
1000Byteを超える場合は、複数回関数が呼び出される。

当然、受け側としては受け取った文字列を連結して保持する事になる。
そこで、もう一つのクセが待ち構えているのだ。

クセその2
この文字列の終端はnullターミネートされていない(!)
このため、通常の手段としてNSMutableStringに蓄積しようとしても、NSMutableDataに溜め込もうとしても、strcatで連結しようとしてもゴミが残留するのだ。
このゴミが、最終的にNSStringに変換しようとするときに高確立でエラーとなって、NSStringが生成されないという現象が発生する。

ではどうするか?

ここで信用できる情報は何か?
文字列データは、nullターミネートされていないが、それ以外の部分はおおむね正しい。
そして、文字列長情報も問題ない。
信用できないのは、文字列長を超えた部分に存在しているゴミデータである。

ならば。
文字列の連結の度、有効文字列長の末尾に、自力でnullターミネートを書き込んでやればよろしい。
具体的には、

charLen_ = charLen_ + len;
if ( chars_ == nil ) {
 chars_ = xmlStrdup(value);
} else {
 chars_ = xmlStrcat(chars_, value);
}
chars_[charLen_] = 0x00;


xmlStrxxx関数はxmlChar型の操作関数だ。
これでNSStringに変換出来ないという症状は押さえ込めた。

ちなみに、1000byte未満の文字列の場合、正しくnullターミネートされているらしく、
NSString変換に失敗する事は無いようである。


0 件のコメント:

コメントを投稿