環境 | iOS(iPhone) SDK 4.0 |
iOSでHTML(not XML)を解析(Parse)するコードを書く機会がありましたので、トラブル情報や解決策などをまとめておきます。
ライブラリはNSXMLDocumentとNSXMLParserとlibxml2あたりを考えてみましたが、 NSXMLDocumentはiOSには無いみたいですし、NSXMLParserは壊れたHTMLを最後までパースできないようでしたので、 消去法でlibxml2を使うことにしました。
「iPhoneアプリ開発、その(143) libxml2を使ってみる」を参考にプロジェクトにlibxml2を追加し、
Googleを活用しつつコードを書いていったところ、問題が起きました。
エラーを無視して解析を続けるオプションで解析しても、途中で解析が止まるのです。
以下のサンプルコードで問題を確認することができます。
libxml2のバージョンは2.7.3でした。
static void startElementHandler(void *ctx, const xmlChar *name, const xmlChar **atts) { NSLog(@"startElement name=%s", name); } static void endElementHandler(void *ctx, const xmlChar *name) { NSLog(@"endElement name=%s", name); } - (void)test { NSData *data = [@"<html><body><![if !(lt IE 7.0)]><![endif]></body></html>" dataUsingEncoding:NSUTF8StringEncoding]; xmlParserCtxtPtr parserContext; xmlSAXHandler saxHandlerStruct = {0}; parserContext = xmlCreatePushParserCtxt(&saxHandlerStruct, NULL, NULL, 0, NULL); saxHandlerStruct.startElement = &startElementHandler; saxHandlerStruct.endElement = &endElementHandler; int options = xmlCtxtUseOptions(parserContext, XML_PARSE_RECOVER | XML_PARSE_NOERROR | XML_PARSE_NOWARNING); int result = xmlParseChunk(parserContext, (const char *)[data bytes], [data length], 1); // ここでresult == 1 (XML_ERR_INTERNAL_ERROR) になる // startElementHandler, endElementHandlerは呼び出されない。 xmlFreeParserCtxt(parserContext); }
以下のようにdataを設定すると、問題はおきません。
NSData *data = [@"<html><body><!--[if !(lt IE 7.0)]--><!--[endif]--></body></html>" dataUsingEncoding:NSUTF8StringEncoding];
HTMLデータの方に問題があるということになるのでしょうが、XML_ERR_INTERNAL_ERRORが起きる理由は良く分かりません。
私のユースケースではHTMLデータを修正できないので、別の方法を探してみました。
結果、libxml2にあるHTML 4.0 non-verifying parserを使うことで解析できることを確認しました。
サンプルコードを以下に示します。
- (void)test { NSData *data = [@"<html><body><![if !(lt IE 7.0)]><![endif]></body></html>" dataUsingEncoding:NSUTF8StringEncoding]; htmlParserCtxtPtr parserContext; htmlSAXHandler saxHandlerStruct = {0}; saxHandlerStruct.startElement = &startElementHandler; saxHandlerStruct.endElement = &endElementHandler; parserContext = htmlCreatePushParserCtxt(&saxHandlerStruct, NULL, NULL, 0, NULL, XML_CHAR_ENCODING_UTF8); int options = htmlCtxtUseOptions(parserContext, HTML_PARSE_RECOVER | HTML_PARSE_NOERROR | HTML_PARSE_NOWARNING); int result = htmlParseChunk(parserContext, (const char *)[data bytes], [data length], 1); // ここでresult == 68 (XML_ERR_NAME_REQUIRED)になる。 // startElementHandler, endElementHandlerは正常に(それぞれhtmlとbodyで2回ずつ)呼び出される。 htmlFreeParserCtxt(parserContext); }
htmlParseChunkが返すエラーをどのようにハンドリングすれば良いのか良く分かりませんが、 解析が止まるわけではないので、エラーを無視すれば特に問題は起きないようです。 この方向でしばらくコードを書いてみることにします。
しかしC言語業界のHTML Parserの定番って何になるんでしょうね。 XML Parserは実装も情報もたくさんあるのですが、HTML Parserは実装も情報も少なく、調べるのに苦労しました。
投稿者 MASATO : 2010年12月03日 22:58 | トラックバック