この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
前回の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になりますので、良かったら勝手に使ってください!