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];
参考資料: