iPhoneでXMLを解析する

CocoaではNSXMLDocumentを使うと簡単にXMLデータを処理することができるが、iPhoneにはなぜか搭載されていないためNSXMLParserを使い自分でXML解析を行う必要がある。NSXMLParserはイベントドリブンのパーサーなのでそのままでは多少使い勝手が悪い。

NSXMLParserの挙動の確認

NSXMLParserの挙動を確認するために、下記の用なクラスを用意した。
MyXMLDocument.h

//
//  MyXMLDocument.h
//  xml_parser
//
#import
@interface MyXMLDocument : NSObject {
	NSXMLParser *parser;
}
- (id) initWithXMLString:(NSString*)string;
@end

MyXMLDocument.mm

//
//  MyXMLDocument.mm
//  xml_parser
//
#import "MyXMLDocument.h"
@implementation MyXMLDocument
- (id) initWithXMLString:(NSString*)string{
	if (self = [super init]) {
		//XMLをパースします
		parser = [[NSXMLParser alloc] initWithData:[string dataUsingEncoding:NSUTF8StringEncoding]];
		[parser setDelegate:self];
		[parser parse];
	}
	return self;
}
- (void) dealloc{
	NSLog(@"dealloc");
	[parser release];
	[super dealloc];
}
#pragma mark -
#pragma mark NSXMLParserのデリゲート
- (void)parserDidStartDocument:(NSXMLParser *)parser {
	NSLog(@"解析を開始しました");
}
- (void)parser:(NSXMLParser *)parser parseErrorOccurred:(NSError *)parseError {
	NSLog(@"エラーが発生しました");
}
- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName attributes:(NSDictionary *)attributeDict{
	NSLog(@"要素[%@]が見つかりました", elementName);
}
- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName{
	NSLog(@"要素[%@]が終わりました", elementName);
}
- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string{
	NSLog(@"文字列[%@]が見つかりました", string);
}
- (void)parserDidEndDocument:(NSXMLParser *)parser {
	NSLog(@"解析が完了しました");
}
@end

テスト用コード

	MyXMLDocument *doc = [[MyXMLDocument alloc] initWithXMLString:@"1230"];
	[doc release];

実行結果

2009-12-03 12:03:05.137 xml_parser[658:207] 解析を開始しました
2009-12-03 12:03:05.139 xml_parser[658:207] 要素[user]が見つかりました
2009-12-03 12:03:05.140 xml_parser[658:207] 要素[age]が見つかりました
2009-12-03 12:03:05.141 xml_parser[658:207] 文字列[12]が見つかりました
2009-12-03 12:03:05.142 xml_parser[658:207] 要素[age]が終わりました
2009-12-03 12:03:05.142 xml_parser[658:207] 要素[weight]が見つかりました
2009-12-03 12:03:05.143 xml_parser[658:207] 文字列[30]が見つかりました
2009-12-03 12:03:05.143 xml_parser[658:207] 要素[weight]が終わりました
2009-12-03 12:03:05.144 xml_parser[658:207] 要素[user]が終わりました
2009-12-03 12:03:05.145 xml_parser[658:207] 解析が完了しました

NSXMLParserでは、要素の解析が進むとその度にデリゲート先のメソッドが呼ばれるようになっている。

  • parser:didStartElement:… 新しい要素が見つかった時に呼ばれる
  • parser:didEndElement:… その要素が閉じられた時に呼ばれる
  • parser:foundCharacters… 要素の中で文字列が見つかった時に呼ばれる

ツリーの作成

以上の挙動を元に、解析結果を簡単なツリーにするクラスを実装した。アトリビュートについては扱っていない。
MyXMLDocumentNode.h

//
//  MyXMLDocumentNode.h
//  xml_parser
//
#import
@interface MyXMLDocumentNode : NSObject {
	MyXMLDocumentNode *parent;
	NSMutableArray *children;
	NSString *nodeName;
	NSString *value;
}
@property (nonatomic, retain) NSString *nodeName, *value;
@property (nonatomic, assign) MyXMLDocumentNode *parent;
@property (nonatomic, retain) NSMutableArray *children;
- (void) addChild:(MyXMLDocumentNode*)child;
- (MyXMLDocumentNode*) findChildByName:(NSString*)name;
- (NSArray*) findChildrenByName:(NSString*)name;
@end

MyXMLDocumentNode.mm

//
//  MyXMLDocumentNode.mm
//  xml_parser
//
#import "MyXMLDocumentNode.h"
@implementation MyXMLDocumentNode
@synthesize parent, children, nodeName, value;
- (id) init{
	if (self = [super init]) {
		parent = nil;
		children = [[NSMutableArray alloc] initWithCapacity:1];
	}
	return self;
}
//子ノードを追加します
- (void) addChild:(MyXMLDocumentNode*)child{
	child.parent = self;	//親を自分に設定します
	[children addObject:child];	//子一覧に追加します
}
//ノード名から子ノードを検索します
//複数のノードがある場合は最初のノードが返されます
- (MyXMLDocumentNode*) findChildByName:(NSString*)name{
	for (MyXMLDocumentNode* child in children){
		if ([child.nodeName isEqualToString:name]){
			return child;
		}
	}
	//見つからなければnilを返します
	return nil;
}
//ノード名を持つ子ノードをNSArrayで返します
- (NSArray*) findChildrenByName:(NSString*)name{
	NSMutableArray* found = [[[NSMutableArray alloc] initWithCapacity:1] autorelease];
	for (MyXMLDocumentNode* child in children){
		if ([child.nodeName isEqualToString:name]){
			[found addObject:child];
		}
	}
	return found;
}
- (void)dealloc{
	//子ノードを解放します
	for (MyXMLDocumentNode *child in children) {
		[child release];
	}
	[children release];
	//自身を解放します
	[nodeName release];
	[value release];
	[super dealloc];
}
@end

MyXMLDocument.h

//
//  MyXMLDocument.h
//  xml_parser
//
#import
#import "MyXMLDocumentNode.h"
@interface MyXMLDocument : NSObject {
	NSXMLParser *parser;
	MyXMLDocumentNode *rootNode;
	MyXMLDocumentNode *currentNode;
}
@property (nonatomic, assign) MyXMLDocumentNode *rootNode;
- (id) initWithXMLString:(NSString*)string;
@end

MyXMLDocument.mm

//
//  MyXMLDocument.mm
//  xml_parser
//
#import "MyXMLDocument.h"
@implementation MyXMLDocument
@synthesize rootNode;
- (id) initWithXMLString:(NSString*)string{
	if (self = [super init]) {
		//XMLをパースします
		parser = [[NSXMLParser alloc] initWithData:[string dataUsingEncoding:NSUTF8StringEncoding]];
		currentNode = nil;
		rootNode = nil;
		[parser setDelegate:self];
		[parser parse];
	}
	return self;
}
- (void) dealloc{
	NSLog(@"dealloc");
	[parser release];
	[rootNode release];
	[super dealloc];
}
#pragma mark -
#pragma mark NSXMLParserのデリゲート
- (void)parserDidStartDocument:(NSXMLParser *)parser {
	NSLog(@"解析を開始しました");
}
- (void)parser:(NSXMLParser *)parser parseErrorOccurred:(NSError *)parseError {
	NSLog(@"エラーが発生しました");
}
- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName attributes:(NSDictionary *)attributeDict{
	//新しいノードを作成します
	MyXMLDocumentNode *newNode = [[MyXMLDocumentNode alloc] init];
	newNode.nodeName = elementName;
	if (rootNode == nil) {	//最初に登場したノードをルートノードとします
		rootNode = newNode;
		currentNode = rootNode;
	} else {	//そうでない場合、新しいノードを現在のノードに追加します
		[currentNode addChild:newNode];
		currentNode = newNode;
	}
}
- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName{
	//一つ上の階層に移動します
	currentNode = currentNode.parent;
}
- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string{
	//現在のノードの値を設定します
	currentNode.value = string;
}
- (void)parserDidEndDocument:(NSXMLParser *)parser {
	NSLog(@"解析が完了しました");
	NSLog(@"ルートノードの名前は%@, 子供の数は%d", rootNode.nodeName, [rootNode.children count]);
}
@end

使い方

	MyXMLDocument *doc = [[MyXMLDocument alloc] initWithXMLString:@"12Taro13Daijiro"];
	NSLog(@"ルートノードに%@という名前の子ノードが%d件", @"user", [[doc.rootNode findChildrenByName:@"user"] count]);
	NSLog(@"一人目のuserのnameは%@", [[doc.rootNode findChildByName:@"user"] findChildByName:@"name"].value);
	[doc release];

参考資料:

  1. Mac Dev Center: NSXMLParser Class Reference
  2. iPhone SDK Tutorial: Build a Simple RSS reader for the iPhone
Pocket

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です