2010年12月03日

libxml2でHTML Parse

環境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 | トラックバック
コメント
コメントする









名前、アドレスを登録しますか?