iOSでXMLをパースする #2 KissXML編
前回のTBXMLに引き続き、今回はKissXMLを使ってみたいと思います。
KissXML
KissXMLはTouchXMLというライブラリをベースに作られたDOMタイプのパーサです。TBXMLとの大きな違いはXPathをサポートしていることと読書可であることです。ライセンスは修正BSDライセンス(New BSD License)です。今回も前回同様パースしたXMLをNSDictionaryに変換するロジックで試します。では早速使ってみましょう。
KissXMLを使ってみよう!
ここからは以下の環境を前提に説明します。尚、作成するサンプルは弊社開発ブログのRSS(https://dev.classmethod.jp/feed/)を解析してNSDictionaryに格納するというものです。ちなみにデータ取得にはAFNetworkingを使用します。
Mac OS X 10.8 Moutain lion
Xcode 4.6.2
iOS SDK 6.1
サンプルプロジェクトのダウンロード
今回紹介するiOSアプリのソースコードをGitHubにあげてあるのでダウンロードしてください。
hirai-yuki/KissXMLSample
サンプルアプリを実行すると、デバッグエリアにNSDictionaryに変換されたXMLデータが表示されるかと思います。
必要なフレームワーク・ライブラリの設定
CocoaPodsを利用する場合
KissXMLはCcocoaPodsからもインストール可能です。CocoaPodsを利用する場合は、Podfileに以下のように記述します。
pod 'KissXML'
KissXMLを直接インポートする場合
KissXMLはGitHubにあるrobbiehanson/KissXMLからダウンロードできます。
KissXMLのインポート
ダウンロードした以下のファイルをプロジェクトにインポートします。
- KissXML/
- Additions/DDXMLElementAdditions
- Categories/NSString+DDXML
- DDXML.h
- DDXMLDocument
- DDXMLElement
- DDXMLNode
- Private/DDXMLPrivate.h
ソースからインポートするのはDDXML.hだけです。
libxml2.dylibを追加
KissXMLではlibxml2.dylibを使用するのでプロジェクトで使用するように設定してください。
プロジェクトの設定
プロジェクトナビゲータよりプロジェクトを選択し「Build Settings」を開き、以下の設定を変更しましょう。
項目 | 設定値 |
---|---|
Other Linker Flags | -lxml2 |
Header Search Paths | /usr/include/libxml2 |
KissXMLの使い方
XMLの解析
KissXMLもTBXMLと同様インスタンスの生成と同時に解析をしてくれる模様。使用できるイニシャライザは以下の通り。
- - (id)initWithXMLString:(NSString *)string options:(NSUInteger)mask error:(NSError **)error;
- - (id)initWithData:(NSData *)data options:(NSUInteger)mask error:(NSError **)error;
使用例:ViewController.m
・・・ - (void)viewDidLoad { ・・・ [operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) { NSDate *startDate = [NSDate date]; NSError *error = nil; DDXMLDocument *doc = [[DDXMLDocument alloc] initWithData:responseObject options:0 error:&error]; if (!error) { NSDictionary *xml = [doc.rootElement convertDictionary]; NSTimeInterval interval = [[NSDate date] timeIntervalSinceDate:startDate]; NSLog(@"実行時間 : %lf (sec)\n%@", interval, xml); } else { NSLog(@"%@ %@", [error localizedDescription], [error userInfo]); } } failure:^(AFHTTPRequestOperation *operation, NSError *error) { NSLog(@"%@ %@", [error localizedDescription], [error userInfo]); }]; NSOperationQueue *queue = [[NSOperationQueue alloc] init]; [queue addOperation:operation]; } @end
属性・要素へのアクセス
TBXMLとは違い、属性・要素へのアクセスはインスタンスを介して行います。KissXMLでは要素にアクセスするために以下のクラスが用意されています。
- DDXMLDocument
- DDXMLElement
- DDXMLNode
DDXMLDocumentとDDXMLElementはDDXMLNodeのサブクラスとして定義されているため、これにより柔軟な実装ができます。また、KissXMLではXPathをサポートしているため、以下のように簡単に要素にアクセスできます。
// 以下のXMLを読み込む // <?xml version="1.0" encoding="utf-8"?> // <items> // <item> // <title>hoge</title> // </item> // </items> DDXMLDocument *doc = [[DDXMLDocument alloc] initWithData:responseObject options:0 error:&error]; // /items/item/titleにアクセス NSArray *nodes = [doc nodesForXPath:@"/items/item/title" error:nil]; DDXMLNode *node = nodes[0]; // /items/item/titleの値を出力 NSLog(@"/items/item/title: %@", node.stringValue); // <- hogeが出力される [/c] <h3>読み込んだXMLの変更</h3> <p> 読み込んだXMLを変更するのも簡単です。以下のように直接値を代入するだけです。 </p> // 以下のXMLを読み込む // <?xml version="1.0" encoding="utf-8"?> // <items> // <item> // <title>hoge</title> // </item> // </items> DDXMLDocument *doc = [[DDXMLDocument alloc] initWithData:responseObject options:0 error:&error]; // /items/item/titleにアクセス NSArray *nodes = [doc nodesForXPath:@"/items/item/title" error:nil]; DDXMLNode *node = nodes[0]; // /items/item/titleの値を変更 node.stringValue = @"hogehoge"; // 以下のXMLが出力される // <?xml version="1.0" encoding="utf-8"?> // <items> // <item> // <title>hogehoge</title> // </item> // </items> NSLog(@"%@", doc.XMLString);
要素の追加は、DDXMLElementの- (void)addChild:(DDXMLNode *)childメソッドで行います。
// 以下のXMLを読み込む // <?xml version="1.0" encoding="utf-8"?> // <items> // <item> // <title>hoge</title> // </item> // </items> DDXMLDocument *doc = [[DDXMLDocument alloc] initWithData:responseObject options:0 error:&error]; // /items/testを追加 DDXMLElement *testElement = [DDXMLElement elementWithName:@"test"]; testElement.stringValue = @"テスト"; [doc.rootElement addChild:testElement]; // 以下のXMLが出力される // <?xml version="1.0" encoding="utf-8"?> // <items> // <item> // <title>hogehoge</title> // </item> // <test>テスト</test> // </items> NSLog(@"%@", doc.XMLString);
DDXMLElement+Dictionary
KissXMLで解析した結果をNSDictionaryに格納するカテゴリは以下の通りです。
#import "DDXMLElement+Dictionary.h" NSString * const kTextNodeKey = @"text"; @implementation DDXMLElement (Dictionary) - (NSDictionary *)convertDictionary { NSMutableDictionary *elementDict = [NSMutableDictionary dictionary]; // elementDictに属性をセット for (DDXMLNode *attribute in self.attributes) { elementDict[attribute.name] = attribute.stringValue; } // elementDictにネームスペースをセット for (DDXMLNode *namespace in self.namespaces) { elementDict[namespace.name] = namespace.stringValue; } if (self.childCount > 0) { // 子要素がある場合は子要素に対し再起的に+ dictionaryWithElement:メソッドを実行する。 for (DDXMLNode *childNode in self.children) { if (childNode.kind == DDXMLElementKind) { DDXMLElement *childElement = (DDXMLElement *)childNode; NSString *childElementName = childElement.name; NSDictionary *childElementDict = [childElement convertDictionary]; if (elementDict[childElementName] == nil) { // elementDictにchildElementNameで指定された要素が存在しない場合、elementDictに要素を追加する [elementDict addEntriesFromDictionary:childElementDict]; } else if ([elementDict[childElementName] isKindOfClass:[NSArray class]]) { // childElementNameで指定された要素が既存在しかつ配列の場合、その配列に子要素を追加する NSMutableArray *items = [NSMutableArray arrayWithArray:elementDict[childElementName]]; [items addObject:childElementDict[childElementName]]; elementDict[childElementName] = [NSArray arrayWithArray:items]; } else { // childElementNameで指定された要素が既存在しかつ配列でない場合、新しく配列を生成して子要素を追加する NSMutableArray *items = [NSMutableArray array]; [items addObject:elementDict[childElementName]]; [items addObject:childElementDict[childElementName]]; elementDict[childElementName] = [NSArray arrayWithArray:items]; } } else if (childNode.stringValue != nil && childNode.stringValue.length > 0) { // テキストがあればセットする if (elementDict.count > 0) { elementDict[kTextNodeKey] = childNode.stringValue; } else { elementDict[self.name] = childNode.stringValue; } } } } NSDictionary *resultDict = nil; if (elementDict.count > 0) { if (elementDict[self.name]) { resultDict = [NSDictionary dictionaryWithDictionary:elementDict]; } else { resultDict = [NSDictionary dictionaryWithObject:elementDict forKey:self.name]; } } return resultDict; } @end
まとめ
個人的な感想ですが、要素に対応したクラスが用意されていることやXPathをサポートしていることからTBXMLよりいい感じです。次回はGDataXMLを使って解説します!
ちなみにKissXMLをNSDictionaryに変換する部分はDDXMLElement+Dictionary.hとDDXMLElement+Dictionary.mになりますので、良かったら勝手に使ってください!