XMLを解析したい

iPhoneで利用できるXMLパーサにはいくつかありますが今回はiPhoneSDKに標準添付されているNSXMLParserを利用します。

ちなみに、NSXMLParser以外のXMLパーサには以下のようなものがあります。NSXMLParserはSAXベースのパーサなのに対し、TouchXMLやKissXMLはDOMベースのパーサとなります。SAXベースのXML解析はDOMベースと比較すると簡単とは言い難いので、よほど大きなXMLデータをあつかったりしない限りはこれらDOMベースのパーサを利用するのがいいかも。

NSMLParserでXMLを解析する手順は以下の通りです。

XMLデータを用意する

まずはXMLデータを用意します。NSXMLParserはNSDataかNSURLでXMLデータを指定することができます。今回はとあるユーザのTwitter上のつぶやきをNSURL形式で指定します。

NSString *user = @"htkymtks";
NSString *s = [NSString stringWithFormat:@"http://api.twitter.com/1/statuses/user_timeline/%@.xml", user];
NSURL *url = [NSURL URLWithString:s];

NSXMLParserのインスタンスを生成する

次にさきほど作成したNSURLを指定してNSXMLParserのインスタンスを生成します。NSURLの代わりにNSDataを指定する場合にはNSXMLParser#initWithData:メソッドを利用します。

NSXMLParser *parser = [[NSXMLParser alloc] initWithContentsOfURL:url];

デリゲートをセットする

NSXMLParserはXMLデータを読み込んでいく中で下記のイベントが発生する度に対応するイベントハンドラが呼び出されることでXMLの解析を行われます。これらイベントハンドラを実装したNSXMLParserDelegateオブジェクトをNSXMLParser#setDelegateでパーサへデリゲートをセットしてやります。

  • 解析開始時 (parserDidStartDocument:メソッド)
  • 開始タグを読み込んだとき (parser:didStartElement:namespaceURI:qualifiedName:attributes:メソッド)
  • 閉じタグを読み込んだとき (parser:didEndElement:namespaceURI:qualifiedName:メソッド)
  • タグ以外のテキストを読み込んだとき (parser:foundCharacters:メソッド)
  • 解析終了時 (parserDidEndDocument:メソッド)
[parser setDelegate:self];

パースの実行

XMLデータを指定し、デリゲートをセットしたあとは、NSXMLParser#parseを呼び出してパースを実行します。

[parser parse];

ではNSXMLParserを利用したプログラムを見てみましょう。Twitterから指定したユーザの最新のつぶやきを取得するものです。

...
@interface XmlAppDelegate : NSObject <UIApplicationDelegate, NSXMLParserDelegate> {
    UIWindow *window;
	
    NSMutableArray *statuses;
    BOOL inNameElement;
    BOOL inTextElement;
    NSMutableString *name;
    NSMutableString *text;
}
...
- (void)loadTwitterStatus;
@end
...
// twitterから指定ユーザのつぶやきを取得する
- (void)loadTwitterStatus {
	NSString *user = @"htkymtks";
	NSString *s = [NSString stringWithFormat:@"http://api.twitter.com/1/statuses/user_timeline/%@.xml", user];
	NSURL *url = [NSURL URLWithString:s];
	NSXMLParser *parser = [[NSXMLParser alloc] initWithContentsOfURL:url];
	[parser setDelegate:self];
	[parser parse];
	[parser release];
}

// XMLのパース開始
- (void)parserDidStartDocument:(NSXMLParser *)parser {
	// 初期化処理
	statuses = [NSMutableArray array];
	inNameElement = NO;
	inTextElement = NO;
}

// XMLのパース終了
- (void)parserDidEndDocument:(NSXMLParser *)parser {
	// 取得したつぶやきを出力する
	for (NSString *status in statuses) {
		NSLog(@"%@", status);
	}
}

// 要素の開始タグを読み込み
- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qualifiedName attributes:(NSDictionary *)attributeDict {
	if ([elementName isEqualToString:@"name"]) {
		// nameタグの中に入った!
		inNameElement = YES;
		name = [NSMutableString string];
	} else if ([elementName isEqualToString:@"text"]) {
		// textタグの中に入った!
		inTextElement = YES;
		text = [NSMutableString string];
	}
}

// 要素の閉じタグを読み込み
- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName {
	if ([elementName isEqualToString:@"status"]) {
		// statusタグが終わるタイミングで、statusesにつぶやきを追加する
		NSString *s = [NSString stringWithFormat:@"%@: %@", name, text];
		[statuses addObject:s];
	} else if ([elementName isEqualToString:@"name"]) {
		// nameタグから出た!
		inNameElement = NO;
	} else if ([elementName isEqualToString:@"text"]) {
		// textタグから出た!
		inTextElement = NO;
	}
}

// テキストデータ読み込み
- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string {
	// テキストデータは複数回に分けて呼び出されることがあるので注意
	if (inNameElement) {
		[name appendString:string];
	} else if (inTextElement) {
		[text appendString:string];
	}
}
...

このプログラムを実行すると以下のような出力が得られます。

2010-06-27 12:27:28.549 Xml[19513:207] はたけやまたかし: はじめてプーさんのハニーハントに乗ったときは「こいつはヤバイ」って思った。
2010-06-27 12:27:28.551 Xml[19513:207] はたけやまたかし: @m_pixy ハニーをハント!
2010-06-27 12:27:28.551 Xml[19513:207] はたけやまたかし: ランカちゃんバージョンのワバマイスターを聞いて心を落ち着ける
2010-06-27 12:27:28.551 Xml[19513:207] はたけやまたかし: マクドナルド店内でおばちゃんが飴ちゃん配って歩いている
2010-06-27 12:27:28.553 Xml[19513:207] はたけやまたかし: なぜここのマクドナルドはこんなにも殺伐としているのか。なぜわが最寄り駅にはスターバックスやタリーズといったおしゃれ若者系コーヒー店がないのか。責任者はどこか!
2010-06-27 12:27:28.553 Xml[19513:207] はたけやまたかし: マクドナルドサザンクロス店は今日も殺伐としています
2010-06-27 12:27:28.554 Xml[19513:207] はたけやまたかし: 風呂あがりの牛乳gkgk
2010-06-27 12:27:28.554 Xml[19513:207] はたけやまたかし: 銭湯の湯あがり牛乳は作られたファンタジーかもしれないがそれが美味しいことは歴然たる事実。
...