で、今回はHTML文の解析にNSXMLParser使ってみます。
その(143)ではlibxml2を組み込んで、Cのコールバック関数内で自分のクラスのメソッド呼ぶdelegateパターンを自前で用意して対応したんだけど、NSXMLParserの場合は当然最初からdelegateパターンが用意されとります。
delegateパターンわからん人はモーダルビューを表示する(2)あたりを読んでくれい。
用意するメソッドは以下のとおり
ま、呼び出しのタイミング自体(新しいタグの開始、タグの終了、その間の文字列、異常事態)はその(144)の自作delegate版とほぼ同じなわけですが、引数がNSObjectやNSDictionaryとかになっててObjective-Cとして使い勝手がよくなります。
そのぶん、よぶんな作業で時間を食うわけですが…
それは、まあ個人の好き好きでいいんじゃないかと思われ。
あくまでlibxml2でライトな生活を好むもよし、NSXMLParserでお気楽生活を楽しむもよし、NSXMLDocument使ってiPhoneのメモリをヒイヒイ、ラメェ~、それ以上読み込むと壊れちゃうよ~とか言わせてみたりするもよしちゅ~ところですわ。
libxml2使う場合と違ってlibxml2.dylibを組み込まなくても使えるところもいいですな。
使い方も一番簡単なやり方だと上のdelegateメソッド用意して
でおしまい。
非常に簡単。
ただし、ユーザーの使い勝手を考えると、大きなデータの場合はやっぱり非同期でデータを取り出してからNSXMLParserを使うのが正解。
その場合はNSURLConnectionのconnectionWithRequestなんかを使って非同期で取り出したNSData* dataを
て感じで渡してやればいい。
NSOparation内でinitWithContentsOfURL側を使う手も有りか?
ま、そこらへんは置いといて、とりあえずHTML文の解析。
まずはdidStartElement:メソッドでattributeDictに何が入ってんだろと
ってやってみたコンソール出力結果がこれ。コンソールの出し方わからん人はコンソールを表示するね。
例えば、送られてくるHTML文は
というようになってるんですが、これが一つ一つタグごとに分解されてdidStartElement:メソッドが呼び出されるわけで、上の<html>タグなら
というように綺麗に分解されて入ってくる。
なのでxmlnsには何が設定されてるのかなと思ったら
とするだけで、urlStringに"http://www.w3.org/1999/xhtml"という文字列が入るわけです。
で、今回のこの花は?サーバーはCSSを導入(その(201) CSSでいくぜ)して部品ごとにdivタグがclass属性と一緒に指定されてるわけで、こいつに注目すれば割と単純に花の写真のURLや問い合わせ文を判別できるつーことになる。
div class=thumbタグの中で現れるimgタグは写真へのURLをもってるとかa class=contentsタグは提案IDを含むURLだとか判断できるわけですな。(thumbFlagはインスタンス変数として定義)て感じでタグを分別していくと
コンソールには
って感じの出力が得られるわけです。
IDはNSScannerなんかを使って、数字だけ取り出す作業がいりますな。
てか、花の写真のimgタグにはclass="photo"とか指定すれば、class=thumbを見張る必要さえ無い。いっそIDも
<div class="row">
あたりを拡張して
<div class="row" contribution_id ="提案ID">
とかにしますか。
そこらへん、次回。
------------
サンプルプロジェクト:htmlParser02.zip
その(143)ではlibxml2を組み込んで、Cのコールバック関数内で自分のクラスのメソッド呼ぶdelegateパターンを自前で用意して対応したんだけど、NSXMLParserの場合は当然最初からdelegateパターンが用意されとります。
delegateパターンわからん人はモーダルビューを表示する(2)あたりを読んでくれい。
用意するメソッドは以下のとおり
- (void)parser:(NSXMLParser *)parser
didStartElement:(NSString *)elementName
namespaceURI:(NSString *)namespaceURI
qualifiedName:(NSString *)qName
attributes:(NSDictionary *)attributeDict {
}
- (void)parser:(NSXMLParser *)parser
didEndElement:(NSString *)elementName
namespaceURI:(NSString *)namespaceURI
qualifiedName:(NSString *)qName {
}
- (void)parser:(NSXMLParser *)parser
foundCharacters:(NSString *)string {
}
- (void)parser:(NSXMLParser *)parser
parseErrorOccurred:(NSError *)parseError {
}
didStartElement:(NSString *)elementName
namespaceURI:(NSString *)namespaceURI
qualifiedName:(NSString *)qName
attributes:(NSDictionary *)attributeDict {
}
- (void)parser:(NSXMLParser *)parser
didEndElement:(NSString *)elementName
namespaceURI:(NSString *)namespaceURI
qualifiedName:(NSString *)qName {
}
- (void)parser:(NSXMLParser *)parser
foundCharacters:(NSString *)string {
}
- (void)parser:(NSXMLParser *)parser
parseErrorOccurred:(NSError *)parseError {
}
ま、呼び出しのタイミング自体(新しいタグの開始、タグの終了、その間の文字列、異常事態)はその(144)の自作delegate版とほぼ同じなわけですが、引数がNSObjectやNSDictionaryとかになっててObjective-Cとして使い勝手がよくなります。
そのぶん、よぶんな作業で時間を食うわけですが…
それは、まあ個人の好き好きでいいんじゃないかと思われ。
あくまでlibxml2でライトな生活を好むもよし、NSXMLParserでお気楽生活を楽しむもよし、NSXMLDocument使ってiPhoneのメモリをヒイヒイ、ラメェ~、それ以上読み込むと壊れちゃうよ~とか言わせてみたりするもよしちゅ~ところですわ。
libxml2使う場合と違ってlibxml2.dylibを組み込まなくても使えるところもいいですな。
使い方も一番簡単なやり方だと上のdelegateメソッド用意して
NSXMLParser* parser = [[NSXMLParser alloc] initWithContentsOfURL:
[NSURL URLWithString:@"http://localhost/konohana/mainlist.php"]];
parser.delegate = self;
[parser parse];
[NSURL URLWithString:@"http://localhost/konohana/mainlist.php"]];
parser.delegate = self;
[parser parse];
でおしまい。
非常に簡単。
ただし、ユーザーの使い勝手を考えると、大きなデータの場合はやっぱり非同期でデータを取り出してからNSXMLParserを使うのが正解。
その場合はNSURLConnectionのconnectionWithRequestなんかを使って非同期で取り出したNSData* dataを
NSXMLParser* parser = [[NSXMLParser alloc] initWithData:data];
て感じで渡してやればいい。
NSOparation内でinitWithContentsOfURL側を使う手も有りか?
ま、そこらへんは置いといて、とりあえずHTML文の解析。
まずはdidStartElement:メソッドでattributeDictに何が入ってんだろと
printf("%s\n", [elementName UTF8String]);
NSLog(@"%@", attributeDict);
NSLog(@"%@", attributeDict);
ってやってみたコンソール出力結果がこれ。コンソールの出し方わからん人はコンソールを表示するね。
NSLogに与える最初のパラメータはprintf同様に%sや%dの指定ができる、その中で特殊なのが
%@
で、この場合、インスタンスの説明を表示する指定となる。
正確に言うとインスタンスのdescriptionメソッドを呼び出し、返されたNSString文字列を表示している。descriptionはNSObjectに用意されているメソッドでAppStoreにアップする(2)で説明したようにクラスごとに自由にカスタマイズされている。attributeDictはNSDictionaryなのでキーと対応する内容の表示となるわけですな。
%@
で、この場合、インスタンスの説明を表示する指定となる。
正確に言うとインスタンスのdescriptionメソッドを呼び出し、返されたNSString文字列を表示している。descriptionはNSObjectに用意されているメソッドでAppStoreにアップする(2)で説明したようにクラスごとに自由にカスタマイズされている。attributeDictはNSDictionaryなのでキーと対応する内容の表示となるわけですな。
html
2010-06-09 22:15:48.685 htmlParser[3415:207] {
lang = ja;
"xml:lang" = ja;
xmlns = "http://www.w3.org/1999/xhtml";
}
head
2010-06-09 22:15:48.686 htmlParser[3415:207] {
}
meta
2010-06-09 22:15:48.686 htmlParser[3415:207] {
content = "text/html; charset=utf-8";
"http-equiv" = "content-type";
}
link
2010-06-09 22:15:48.686 htmlParser[3415:207] {
href = "style.css";
rel = stylesheet;
type = "text/css";
}
title
2010-06-09 22:15:48.687 htmlParser[3415:207] {
}
・
・
2010-06-09 22:15:48.685 htmlParser[3415:207] {
lang = ja;
"xml:lang" = ja;
xmlns = "http://www.w3.org/1999/xhtml";
}
head
2010-06-09 22:15:48.686 htmlParser[3415:207] {
}
meta
2010-06-09 22:15:48.686 htmlParser[3415:207] {
content = "text/html; charset=utf-8";
"http-equiv" = "content-type";
}
link
2010-06-09 22:15:48.686 htmlParser[3415:207] {
href = "style.css";
rel = stylesheet;
type = "text/css";
}
title
2010-06-09 22:15:48.687 htmlParser[3415:207] {
}
・
・
例えば、送られてくるHTML文は
<!DOCTYPE html PUBLIC "-//W3C//DTD
XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" lang="ja" xml:lang="ja">
<head>
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
・
・
XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" lang="ja" xml:lang="ja">
<head>
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
・
・
というようになってるんですが、これが一つ一つタグごとに分解されてdidStartElement:メソッドが呼び出されるわけで、上の<html>タグなら
html
2010-06-09 22:15:48.685 htmlParser[3415:207] {
lang = ja;
"xml:lang" = ja;
xmlns = "http://www.w3.org/1999/xhtml";
}
2010-06-09 22:15:48.685 htmlParser[3415:207] {
lang = ja;
"xml:lang" = ja;
xmlns = "http://www.w3.org/1999/xhtml";
}
というように綺麗に分解されて入ってくる。
なのでxmlnsには何が設定されてるのかなと思ったら
NSString* urlString = [attributeDict valueForKey:@"xmlns"];
とするだけで、urlStringに"http://www.w3.org/1999/xhtml"という文字列が入るわけです。
で、今回のこの花は?サーバーはCSSを導入(その(201) CSSでいくぜ)して部品ごとにdivタグがclass属性と一緒に指定されてるわけで、こいつに注目すれば割と単純に花の写真のURLや問い合わせ文を判別できるつーことになる。
div class=thumbタグの中で現れるimgタグは写真へのURLをもってるとかa class=contentsタグは提案IDを含むURLだとか判断できるわけですな。(thumbFlagはインスタンス変数として定義)て感じでタグを分別していくと
- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName
namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName
attributes:(NSDictionary *)attributeDict {
NSString* className = [attributeDict valueForKey:@"class"];
if ([className isEqualToString:@"thumb"]) {
thumbFlag = YES;
}
if ([className isEqualToString:@"contents"]) {
printf("ID = %s\n", [[attributeDict valueForKey:@"href"] UTF8String]);
}
if (thumbFlag && [elementName isEqualToString:@"img"]) {
printf("image URL = %s\n", [[attributeDict valueForKey:@"src"] UTF8String]);
thumbFlag = NO;
}
[currentParsedCharacterData release];
currentParsedCharacterData = nil;
}
- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string {
if (!currentParsedCharacterData) {
currentParsedCharacterData = [[NSMutableString alloc]init];
}
[currentParsedCharacterData appendString:string];
}
namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName
attributes:(NSDictionary *)attributeDict {
NSString* className = [attributeDict valueForKey:@"class"];
if ([className isEqualToString:@"thumb"]) {
thumbFlag = YES;
}
if ([className isEqualToString:@"contents"]) {
printf("ID = %s\n", [[attributeDict valueForKey:@"href"] UTF8String]);
}
if (thumbFlag && [elementName isEqualToString:@"img"]) {
printf("image URL = %s\n", [[attributeDict valueForKey:@"src"] UTF8String]);
thumbFlag = NO;
}
[currentParsedCharacterData release];
currentParsedCharacterData = nil;
}
- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string {
if (!currentParsedCharacterData) {
currentParsedCharacterData = [[NSMutableString alloc]init];
}
[currentParsedCharacterData appendString:string];
}
コンソールには
image URL = ./img_xcc_test/20100516084849.JPG
ID = ./suggestlist.php?contribution_id=13
image URL = ./img_xcc_test/20100516084844.JPG
ID = ./suggestlist.php?contribution_id=12
image URL = ./img_xcc_test/20100516084839.JPG
ID = ./suggestlist.php?contribution_id=11
image URL = ./img_xcc_test/20100516084830.JPG
ID = ./suggestlist.php?contribution_id=10
image URL = ./img_xcc_test/20100516084815.JPG
ID = ./suggestlist.php?contribution_id=9
image URL = ./img_xcc_test/20100516084800.JPG
ID = ./suggestlist.php?contribution_id=8
ID = ./suggestlist.php?contribution_id=13
image URL = ./img_xcc_test/20100516084844.JPG
ID = ./suggestlist.php?contribution_id=12
image URL = ./img_xcc_test/20100516084839.JPG
ID = ./suggestlist.php?contribution_id=11
image URL = ./img_xcc_test/20100516084830.JPG
ID = ./suggestlist.php?contribution_id=10
image URL = ./img_xcc_test/20100516084815.JPG
ID = ./suggestlist.php?contribution_id=9
image URL = ./img_xcc_test/20100516084800.JPG
ID = ./suggestlist.php?contribution_id=8
って感じの出力が得られるわけです。
IDはNSScannerなんかを使って、数字だけ取り出す作業がいりますな。
てか、花の写真のimgタグにはclass="photo"とか指定すれば、class=thumbを見張る必要さえ無い。いっそIDも
<div class="row">
あたりを拡張して
<div class="row" contribution_id ="提案ID">
とかにしますか。
そこらへん、次回。
------------
サンプルプロジェクト:htmlParser02.zip