個別のタグでIDを埋め込むかどうかは置いといて、前回の出力結果
からIDを取り出すには
を使うのが吉。
こいつはその名のとおり文字列を走査するクラスで、文字列の何文字目を見るか(注視点)を指定でき、その注視点から文字列を整数表現として解釈したり浮動小数点表現として解釈させたりできるんですな。
![$テン*シー*シー-1](https://stat.ameba.jp/user_images/20100623/15/xcc/a6/1a/j/o0266005710605277830.jpg?caw=800)
NSStringでも自分自身の文字列を、整数や浮動小数点として変換するintValueメソッドやdoubleValueメソッドてのがあるけど、NSScannerの場合は注視点を変更できる点で柔軟性が高い。
今回の捜査対象文字列を考えると以下のように"="に続く文字列を整数として解釈したいわけですよ。
![$テン*シー*シー-2](https://stat.ameba.jp/user_images/20100623/15/xcc/66/41/j/o0257005710605279402.jpg?caw=800)
まずは上記走査対象の文字列をattributeDictから
で取り出してNSScannerのscannerWithString:クラスメソッドに指定して作成。
これで準備は完了したので、今度はNSScannerのscanUpToString:intoString:メソッドを使ってIDの直前まで指定文字列が見つかるまで注目点を移動させる
これで、注目点はcontribution_id=の先頭まで移動。
![$テン*シー*シー-3](https://stat.ameba.jp/user_images/20100623/15/xcc/fa/a0/j/o0253005610605283231.jpg?caw=800)
今度はscanString:intoString:メソッドでその部分をスキップ
これでさっきの求めたいIDの手前まで注目点が移動したので
![$テン*シー*シー-2](https://stat.ameba.jp/user_images/20100623/15/xcc/66/41/j/o0257005710605279402.jpg?caw=800)
最後にscanInt:メソッドで整数を取り出しておしまい。
ま、こんな感じで投稿IDは取り出せます。
で、konohana_DBクラスは投稿ID以外に
を返さなきゃならないので、そこらへんも順次取り出してみましょう。
まず、サーバーから返されるXHTML文は以下のように、class="row"のdivタグの範囲が一つの項目になるわけです。
![$テン*シー*シー-4](https://stat.ameba.jp/user_images/20100623/16/xcc/83/83/j/o0438029410605287857.jpg?caw=800)
なので、前回は適当にthumbFlag変数使ってdivタグとclass属性見てりゃ判別つくよってのを確認したんですが、今回はまともに対応してみます。
というのも、divタグは以下のように何重にも入れ子状態で出現するので
![$テン*シー*シー-5](https://stat.ameba.jp/user_images/20100623/16/xcc/66/87/j/o0498015410605292310.jpg?caw=800)
本来ならclass="row"のdiv領域、class="thumb"のdiv領域、class="name"のdiv領域と状態を把握して動作した方がいいわけですよ。thumbFlagなんかで適当にやらずに。
なので、modeというint型変数を用意して、こいつにclass="row"のdiv領域、class="thumb"のdiv領域、class="name"のdiv領域という状態を記憶させます。
ただ~、その場合タグが閉じた時に、前のタグを復活させるのがチトやっかいなんですわ。
例えばdivタグ一回だけなら現在の状態をcurt、その前の状態をlast変数に保存するって方法もありなんですが…
![$テン*シー*シー-6](https://stat.ameba.jp/user_images/20100623/16/xcc/ac/10/j/o0300014510605295773.jpg?caw=800)
入れ子状態だと以下のように状態を復帰しきれないんですな。
![$テン*シー*シー-7](https://stat.ameba.jp/user_images/20100623/16/xcc/af/b5/j/o0407017210605297328.jpg?caw=800)
その解決方法としてスタックって考え方を使います。
スタックってのはスタッキングチェアのスタックで、積み重ねて最後に置いたものを、最初に取り出すって保存領域の考え方です。
![$テン*シー*シー-8](https://stat.ameba.jp/user_images/20100623/16/xcc/1a/e1/j/o0367009910605298703.jpg?caw=800)
これを使って、didStartElementでdivタグに出会うと、それまでのモードをプッシュして、その後、新しいモードを設定、didEndElementでdivタグに出会ったら最後にプッシュしたモードをポップする。
![$テン*シー*シー-9](https://stat.ameba.jp/user_images/20100623/16/xcc/8f/51/j/o0407018810605300910.jpg?caw=800)
これでつじつまが合うわけですな。
で、このスタック機能を実現するのにはNSMutableArrayを使います。NSStackとか適当に探してみたけど無かったのよ。
でプッシュして
でポップの代わりとするわけです。
なんですが…
NSMutableArray(もちろんNSArrayも)はaddObject:メソッド内部で指定されたインスタンスに対しretain呼ぶので、NSObjectを継承したクラスじゃないint型は指定できません。
このため
なるクラスが用意されとります。
こいつはint型やdouble型を内部に持つNSObject継承クラス。
単に数字を記憶するのに、結構まわりくどい事になるんだけどしょうがない。ただNSNumberはNSArrayに限らずNSDictionaryやキー値コーディングなんかでバリバリ使われるクラスなんで覚えておいて損はないかと思われ。
最終的な出力はこんな感じ。IDは[]でくくった中が取り出した整数値。
ま、こんな要領で提案、支持の方も解析していきます。
ただしhtmlParserViewControllerにNSXMLParser delegate(って何?な人は前回を読みましょう)を組み込むのはやめて、投稿用、提案用、支持用の3つの解析用クラスを定義する予定。
で、取り出したデータ群を受け取るにはNSMutableDictionaryを使います。
そこらへんは以下次回!
------------
サンプルプロジェクト:htmlParser03.zip
ID = ./suggestlist.php?contribution_id=13
からIDを取り出すには
NSScanner
を使うのが吉。
OS 3.2からは正規表現ライブラリのlibicucore.dylibなんかも正式サポートみたいっす。ま、そこまでは必要ないってことで
こいつはその名のとおり文字列を走査するクラスで、文字列の何文字目を見るか(注視点)を指定でき、その注視点から文字列を整数表現として解釈したり浮動小数点表現として解釈させたりできるんですな。
![$テン*シー*シー-1](https://stat.ameba.jp/user_images/20100623/15/xcc/a6/1a/j/o0266005710605277830.jpg?caw=800)
NSStringでも自分自身の文字列を、整数や浮動小数点として変換するintValueメソッドやdoubleValueメソッドてのがあるけど、NSScannerの場合は注視点を変更できる点で柔軟性が高い。
今回の捜査対象文字列を考えると以下のように"="に続く文字列を整数として解釈したいわけですよ。
![$テン*シー*シー-2](https://stat.ameba.jp/user_images/20100623/15/xcc/66/41/j/o0257005710605279402.jpg?caw=800)
まずは上記走査対象の文字列をattributeDictから
[attributeDict valueForKey:@"href"]
で取り出してNSScannerのscannerWithString:クラスメソッドに指定して作成。
NSScanner* scanner = [NSScanner scannerWithString:[attributeDict valueForKey:@"href"]];
これで準備は完了したので、今度はNSScannerのscanUpToString:intoString:メソッドを使ってIDの直前まで指定文字列が見つかるまで注目点を移動させる
[scanner scanUpToString:@"contribution_id=" intoString:nil];
これで、注目点はcontribution_id=の先頭まで移動。
![$テン*シー*シー-3](https://stat.ameba.jp/user_images/20100623/15/xcc/fa/a0/j/o0253005610605283231.jpg?caw=800)
今度はscanString:intoString:メソッドでその部分をスキップ
[scanner scanString:@"contribution_id=" intoString:nil];
これでさっきの求めたいIDの手前まで注目点が移動したので
![$テン*シー*シー-2](https://stat.ameba.jp/user_images/20100623/15/xcc/66/41/j/o0257005710605279402.jpg?caw=800)
最後にscanInt:メソッドで整数を取り出しておしまい。
int ID;
[scanner scanInt:&ID];
[scanner scanInt:&ID];
ま、こんな感じで投稿IDは取り出せます。
で、konohana_DBクラスは投稿ID以外に
提案名
登校時のコメント
画像
登校時のコメント
画像
を返さなきゃならないので、そこらへんも順次取り出してみましょう。
まず、サーバーから返されるXHTML文は以下のように、class="row"のdivタグの範囲が一つの項目になるわけです。
![$テン*シー*シー-4](https://stat.ameba.jp/user_images/20100623/16/xcc/83/83/j/o0438029410605287857.jpg?caw=800)
なので、前回は適当にthumbFlag変数使ってdivタグとclass属性見てりゃ判別つくよってのを確認したんですが、今回はまともに対応してみます。
というのも、divタグは以下のように何重にも入れ子状態で出現するので
![$テン*シー*シー-5](https://stat.ameba.jp/user_images/20100623/16/xcc/66/87/j/o0498015410605292310.jpg?caw=800)
本来ならclass="row"のdiv領域、class="thumb"のdiv領域、class="name"のdiv領域と状態を把握して動作した方がいいわけですよ。thumbFlagなんかで適当にやらずに。
なので、modeというint型変数を用意して、こいつにclass="row"のdiv領域、class="thumb"のdiv領域、class="name"のdiv領域という状態を記憶させます。
enum {
mode_None,
mode_Row, // class="row"のdiv領域
mode_Thumb, // class="thumb"のdiv領域
mode_Name // class="name"のdiv領域
};
mode_None,
mode_Row, // class="row"のdiv領域
mode_Thumb, // class="thumb"のdiv領域
mode_Name // class="name"のdiv領域
};
ただ~、その場合タグが閉じた時に、前のタグを復活させるのがチトやっかいなんですわ。
例えばdivタグ一回だけなら現在の状態をcurt、その前の状態をlast変数に保存するって方法もありなんですが…
![$テン*シー*シー-6](https://stat.ameba.jp/user_images/20100623/16/xcc/ac/10/j/o0300014510605295773.jpg?caw=800)
入れ子状態だと以下のように状態を復帰しきれないんですな。
![$テン*シー*シー-7](https://stat.ameba.jp/user_images/20100623/16/xcc/af/b5/j/o0407017210605297328.jpg?caw=800)
その解決方法としてスタックって考え方を使います。
スタックってのはスタッキングチェアのスタックで、積み重ねて最後に置いたものを、最初に取り出すって保存領域の考え方です。
LIFO(Last In, First Out)とかFILO(First In, Last Out)とか言われてるやつで、これの逆のFIFO(First In, First Out)ちゅ~のもあります。一般にキューと呼ばれててCocoaでもNSOperationQueueなんてのがあります。
とりあえず、スタックとキューは覚えてて損は無し。
とりあえず、スタックとキューは覚えてて損は無し。
![$テン*シー*シー-8](https://stat.ameba.jp/user_images/20100623/16/xcc/1a/e1/j/o0367009910605298703.jpg?caw=800)
これを使って、didStartElementでdivタグに出会うと、それまでのモードをプッシュして、その後、新しいモードを設定、didEndElementでdivタグに出会ったら最後にプッシュしたモードをポップする。
![$テン*シー*シー-9](https://stat.ameba.jp/user_images/20100623/16/xcc/8f/51/j/o0407018810605300910.jpg?caw=800)
これでつじつまが合うわけですな。
で、このスタック機能を実現するのにはNSMutableArrayを使います。NSStackとか適当に探してみたけど無かったのよ。
addObject
でプッシュして
objectAtIndex:渡すインディックスはcountで返される配列の数 - 1
removeLastObject
removeLastObject
でポップの代わりとするわけです。
なんですが…
NSMutableArray(もちろんNSArrayも)はaddObject:メソッド内部で指定されたインスタンスに対しretain呼ぶので、NSObjectを継承したクラスじゃないint型は指定できません。
ここでは使わないけど、複製を作れるようにしたい場合、copyメソッドも使われるので、NSCopyingプロトコルも継承してないと駄目なのよ。
このため
NSNumber
なるクラスが用意されとります。
こいつはint型やdouble型を内部に持つNSObject継承クラス。
単に数字を記憶するのに、結構まわりくどい事になるんだけどしょうがない。ただNSNumberはNSArrayに限らずNSDictionaryやキー値コーディングなんかでバリバリ使われるクラスなんで覚えておいて損はないかと思われ。
- (void)parser:(NSXMLParser *)parser
didStartElement:(NSString *)elementName
namespaceURI:(NSString *)namespaceURI
qualifiedName:(NSString *)qName
attributes:(NSDictionary *)attributeDict {
NSString* className = [attributeDict valueForKey:@"class"];
if ([elementName isEqualToString:@"div"]) {
// divタグに出会ったので以前のmodeをプッシュ
[modeStack addObject:[NSNumber numberWithInt:mode]];
// divタグに合わせmodeを設定
if ([className isEqualToString:@"row"]) {
mode = mode_Row;
}
if ([className isEqualToString:@"name"]) {
mode = mode_Name;
}
if ([className isEqualToString:@"thumb"]) {
mode = mode_Thumb;
}
}
・
・
// 各modeにあわせた処理
・
・
}
- (void)parser:(NSXMLParser *)parser
didEndElement:(NSString *)elementName
namespaceURI:(NSString *)namespaceURI
qualifiedName:(NSString *)qName {
・
・
// 各modeにあわせた処理
・
・
if ([elementName isEqualToString:@"div"]) {
// divタグが閉じるので、その直前のmodeを復帰させる。
NSNumber* num = [modeStack objectAtIndex:[modeStack count] - 1];
mode = [num intValue];
[modeStack removeLastObject];
}
・
・
}
didStartElement:(NSString *)elementName
namespaceURI:(NSString *)namespaceURI
qualifiedName:(NSString *)qName
attributes:(NSDictionary *)attributeDict {
NSString* className = [attributeDict valueForKey:@"class"];
if ([elementName isEqualToString:@"div"]) {
// divタグに出会ったので以前のmodeをプッシュ
[modeStack addObject:[NSNumber numberWithInt:mode]];
// divタグに合わせmodeを設定
if ([className isEqualToString:@"row"]) {
mode = mode_Row;
}
if ([className isEqualToString:@"name"]) {
mode = mode_Name;
}
if ([className isEqualToString:@"thumb"]) {
mode = mode_Thumb;
}
}
・
・
// 各modeにあわせた処理
・
・
}
- (void)parser:(NSXMLParser *)parser
didEndElement:(NSString *)elementName
namespaceURI:(NSString *)namespaceURI
qualifiedName:(NSString *)qName {
・
・
// 各modeにあわせた処理
・
・
if ([elementName isEqualToString:@"div"]) {
// divタグが閉じるので、その直前のmodeを復帰させる。
NSNumber* num = [modeStack objectAtIndex:[modeStack count] - 1];
mode = [num intValue];
[modeStack removeLastObject];
}
・
・
}
最終的な出力はこんな感じ。IDは[]でくくった中が取り出した整数値。
image URL = ./img_xcc_test/20100516084849.JPG
ID = ./suggestlist.php?contribution_id=13 [13]
name = f
comment =
・
・
image URL = ./img_xcc_test/20100516084815.JPG
ID = ./suggestlist.php?contribution_id=9 [9]
name = f1
comment = ゆり?
image URL = ./img_xcc_test/20100516084800.JPG
ID = ./suggestlist.php?contribution_id=8 [8]
name = たんぽぽ
comment = 黄色い花
ID = ./suggestlist.php?contribution_id=13 [13]
name = f
comment =
・
・
image URL = ./img_xcc_test/20100516084815.JPG
ID = ./suggestlist.php?contribution_id=9 [9]
name = f1
comment = ゆり?
image URL = ./img_xcc_test/20100516084800.JPG
ID = ./suggestlist.php?contribution_id=8 [8]
name = たんぽぽ
comment = 黄色い花
ま、こんな要領で提案、支持の方も解析していきます。
ただしhtmlParserViewControllerにNSXMLParser delegate(って何?な人は前回を読みましょう)を組み込むのはやめて、投稿用、提案用、支持用の3つの解析用クラスを定義する予定。
で、取り出したデータ群を受け取るにはNSMutableDictionaryを使います。
そこらへんは以下次回!
------------
サンプルプロジェクト:htmlParser03.zip