作者:朱祁林
出处:http://zhuqil.cnblogs.com
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
开发中由于服务端与客户端是两种不同的平台,而且服务端又是老系统,不具备很好的面向对象的性质,所以导致客户端与服务端只好通过一些制定好的xml进行通信。
在iOS中对XML的解析不像donet这么方便。当然也存在一些很方便的开源类库去调用,但是有些开源的类库显得很笨重。这篇文章我将封装一个简单操作XML转换成树的类方便自己操作:首先通过NSXMLParser从服务端获取XML,它可以一边下载,一边解析,然后转换成树形结构,最后我们可以从树形结构中去取值。
使用NSXMLParser解析XML:
NSXMLParser中主要有三个委托方法来解析XML:
1、parser:didStartElement: 当解析器对象遇到xml的开始标记时,调用这个方法。
2、parser:didEndElement:当解析器对象遇到xml的结束标记时,调用这个方法。
3、parser:foundCharacters:当解析器找到开始标记和结束标记之间的字符时,调用这个方法。
了解了NSXMLParser机制。然后我们来封装解析XML的类:XMLParser。
#import <CoreFoundation/CoreFoundation.h> |
@interface XMLParser : NSObject |
+ (XMLParser *) sharedInstance; |
- (TreeNode *) parseXMLFromURL: ( NSURL *) url; |
- (TreeNode *) parseXMLFromData: ( NSData *) data; |
shareInstance使用一个单例。
调用parseXMLFromURL方法,需要一个NSURL的参数,返回我们需要的树节点。
调用parseXMLFromData方法,需要一个NSData的参数,返回我们需要的树节点。
在此之前,先定义TreeNode类:
#import <CoreFoundation/CoreFoundation.h> |
@interface TreeNode : NSObject |
NSMutableArray *children; |
@property ( nonatomic , retain) TreeNode *parent; |
@property ( nonatomic , retain) NSMutableArray *children; |
@property ( nonatomic , retain) NSString *key; |
@property ( nonatomic , retain) NSString *leafvalue; |
@property ( nonatomic , readonly ) BOOL isLeaf; |
@property ( nonatomic , readonly ) BOOL hasLeafValue; |
@property ( nonatomic , readonly ) NSArray *keys; |
@property ( nonatomic , readonly ) NSArray *allKeys; |
@property ( nonatomic , readonly ) NSArray *uniqKeys; |
@property ( nonatomic , readonly ) NSArray *uniqAllKeys; |
@property ( nonatomic , readonly ) NSArray *leaves; |
@property ( nonatomic , readonly ) NSArray *allLeaves; |
@property ( nonatomic , readonly ) NSString *dump; |
- ( NSArray *) uniqAllKeys; |
- (TreeNode *) objectForKey: ( NSString *) aKey; |
- ( NSString *) leafForKey: ( NSString *) aKey; |
- ( NSMutableArray *) objectsForKey: ( NSString *) aKey; |
- ( NSMutableArray *) leavesForKey: ( NSString *) aKey; |
- (TreeNode *) objectForKeys: ( NSArray *) keys; |
- ( NSString *) leafForKeys: ( NSArray *) keys; |
- ( NSMutableDictionary *) dictionaryForChildren; |
TreeNode 实现:
#define STRIP(X) [X stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]] |
#pragma mark Create and Initialize TreeNodes |
return [[[ self alloc] init] autorelease]; |
#pragma mark TreeNode type routines |
return ( self .children.count == 0); |
return ( self .leafvalue != nil ); |
#pragma mark TreeNode data recovery routines |
NSMutableArray *results = [ NSMutableArray array]; |
for (TreeNode *node in self .children) [results addObject:node.key]; |
NSMutableArray *results = [ NSMutableArray array]; |
for (TreeNode *node in self .children) |
[results addObject:node.key]; |
[results addObjectsFromArray:node.allKeys]; |
- ( NSArray *) uniqArray: ( NSArray *) anArray |
NSMutableArray *array = [ NSMutableArray array]; |
for ( id object in [anArray sortedArrayUsingSelector: @selector (caseInsensitiveCompare:)]) |
if (![[array lastObject] isEqualToString:object]) [array addObject:object]; |
return [ self uniqArray:[ self keys]]; |
- ( NSArray *) uniqAllKeys |
return [ self uniqArray:[ self allKeys]]; |
NSMutableArray *results = [ NSMutableArray array]; |
for (TreeNode *node in self .children) if (node.leafvalue) [results addObject:node.leafvalue]; |
NSMutableArray *results = [ NSMutableArray array]; |
for (TreeNode *node in self .children) |
if (node.leafvalue) [results addObject:node.leafvalue]; |
[results addObjectsFromArray:node.allLeaves]; |
#pragma mark TreeNode search and retrieve routines |
- (TreeNode *) objectForKey: ( NSString *) aKey |
for (TreeNode *node in self .children) |
if ([node.key isEqualToString: aKey]) |
if (result) return result; |
for (TreeNode *node in self .children) |
result = [node objectForKey:aKey]; |
- ( NSString *) leafForKey: ( NSString *) aKey |
TreeNode *node = [ self objectForKey:aKey]; |
- ( NSMutableArray *) objectsForKey: ( NSString *) aKey |
NSMutableArray *result = [ NSMutableArray array]; |
for (TreeNode *node in self .children) |
if ([node.key isEqualToString: aKey]) [result addObject:node]; |
[result addObjectsFromArray:[node objectsForKey:aKey]]; |
- ( NSMutableArray *) leavesForKey: ( NSString *) aKey |
NSMutableArray *result = [ NSMutableArray array]; |
for (TreeNode *node in [ self objectsForKey:aKey]) |
[result addObject:node.leafvalue]; |
- (TreeNode *) objectForKeys: ( NSArray *) keys |
if ([keys count] == 0) return self ; |
NSMutableArray *nextArray = [ NSMutableArray arrayWithArray:keys]; |
[nextArray removeObjectAtIndex:0]; |
for (TreeNode *node in self .children) |
if ([node.key isEqualToString:[keys objectAtIndex:0]]) |
return [node objectForKeys:nextArray]; |
- ( NSString *) leafForKeys: ( NSArray *) keys |
TreeNode *node = [ self objectForKeys:keys]; |
#pragma mark output utilities |
- ( void ) dumpAtIndent: ( int ) indent into:( NSMutableString *) outstring |
for ( int i = 0; i < indent; i++) [outstring appendString:@ "--" ]; |
[outstring appendFormat:@ "[%2d] Key: %@ " , indent, key]; |
if ( self .leafvalue) [outstring appendFormat:@ "(%@)" , STRIP( self .leafvalue)]; |
[outstring appendString:@ "\n" ]; |
for (TreeNode *node in self .children) [node dumpAtIndent:indent + 1 into: outstring]; |
NSMutableString *outstring = [[ NSMutableString alloc] init]; |
[ self dumpAtIndent:0 into:outstring]; |
return [outstring autorelease]; |
#pragma mark conversion utilities |
- ( NSMutableDictionary *) dictionaryForChildren |
NSMutableDictionary *results = [ NSMutableDictionary dictionary]; |
for (TreeNode *node in self .children) |
if (node.hasLeafValue) [results setObject:node.leafvalue forKey:node.key]; |
#pragma mark invocation forwarding |
- ( id )forwardingTargetForSelector:( SEL )sel |
if ([ self .children respondsToSelector:sel]) return self .children; |
- ( BOOL )respondsToSelector:( SEL )aSelector |
if ( [ super respondsToSelector:aSelector] ) return YES ; |
if ([ self .children respondsToSelector:aSelector]) return YES ; |
- ( BOOL )isKindOfClass:(Class)aClass |
if (aClass == [TreeNode class ]) return YES ; |
if ([ super isKindOfClass:aClass]) return YES ; |
if ([ self .children isKindOfClass:aClass]) return YES ; |
for (TreeNode *node in [[ self .children copy ] autorelease]) [node teardown]; |
[ self .parent.children removeObject: self ]; |
从上面的代码可以看出,定义了很多方便的方法来获取数据。
1、teardown:清除所有节点
2、isLeaf:判断是否是叶子节点
3、hasLeafValue:判断节点是否有值
4、- (NSArray *) leaves:返回节点的所有一级子节点值
5、- (NSArray *) allLeaves:返回节点的所有子节点的值
6、keys; 返回节点所有一级子节点名称。
7、 allKeys; 返回节点所有子节点名称。
8、 uniqKeys;返回节点一级子节点名称,不重复。
9、uniqAllKeys;返回节点子节点名称,不重复。
10、- (TreeNode *) objectForKey:根据节点名称查询节点
11、- (NSString *) leafForKey: (NSString *) aKey:根据节点名称查询出节点的值
12、- (NSMutableArray *) objectsForKey: (NSString *) aKey;根据节点名称查询出所以满足条件的节点
13、- (NSMutableArray *) leavesForKey: (NSString *) aKey;根据节点名称查询出所以满足条件的节点的值
14、- (TreeNode *) objectForKeys: (NSArray *) keys;:根据节点名称路径查询出第一个满足条件的节点。
15、- (NSString *) leafForKeys: (NSArray *) keys 根据节点名称路径查询出第一个满足条件的节点的值。
16、- (NSMutableDictionary *) dictionaryForChildren:将树转换成dictionary
树定义好了,下面实现XMLParser类:
@implementation XMLParser |
static XMLParser *sharedInstance = nil ; |
+(XMLParser *) sharedInstance |
sharedInstance = [[ self alloc] init]; |
- (TreeNode *) parse: ( NSXMLParser *) parser |
stack = [ NSMutableArray array]; |
TreeNode *root = [TreeNode treeNode]; |
root.children = [ NSMutableArray array]; |
[parser setDelegate: self ]; |
TreeNode *realroot = [[root children] lastObject]; |
- (TreeNode *)parseXMLFromURL: ( NSURL *) url |
NSAutoreleasePool * pool = [[ NSAutoreleasePool alloc] init]; |
NSXMLParser *parser = [[ NSXMLParser alloc] initWithContentsOfURL:url]; |
results = [ self parse:parser]; |
- (TreeNode *)parseXMLFromData: ( NSData *) data |
NSAutoreleasePool * pool = [[ NSAutoreleasePool alloc] init]; |
NSXMLParser *parser = [[ NSXMLParser alloc] initWithData:data]; |
results = [ self parse:parser]; |
- ( void )parser:( NSXMLParser *)parser didStartElement:( NSString *)elementName namespaceURI:( NSString *)namespaceURI qualifiedName:( NSString *)qName attributes:( NSDictionary *)attributeDict |
if (qName) elementName = qName; |
TreeNode *leaf = [TreeNode treeNode]; |
leaf.parent = [stack lastObject]; |
[( NSMutableArray *)[[stack lastObject] children] addObject:leaf]; |
leaf.key = [ NSString stringWithString:elementName]; |
leaf.children = [ NSMutableArray array]; |
- ( void )parser:( NSXMLParser *)parser didEndElement:( NSString *)elementName namespaceURI:( NSString *)namespaceURI qualifiedName:( NSString *)qName |
[stack removeLastObject]; |
- ( void )parser:( NSXMLParser *)parser foundCharacters:( NSString *)string |
if (![[stack lastObject] leafvalue]) |
[[stack lastObject] setLeafvalue:[ NSString stringWithString:string]]; |
[[stack lastObject] setLeafvalue:[ NSString stringWithFormat:@ "%@%@" , [[stack lastObject] leafvalue], string]]; |
使用这两个类:
下面看下我们如何使用这个类:
在iis中放下面这个xml:
<? xml version = "1.0" encoding = "UTF-8" ?> |
< LoginResult >True</ LoginResult > |
< LoginInfo >恭喜你登录成功</ LoginInfo > |
< LastLogin >2011-05-09 12:20</ LastLogin > |
使用下面代码获取web服务器上的xml,并将xml转换成树:
TreeNode *node = [parser parseXMLFromURL:url]; |
获取xml中的登录结果:
NSString * result = [node leafForKey:@ "LoginResult" ]; |
类似xpath去取值:
NSArray *path =[[ NSArray alloc]initWithObjects:@ "Right" ,@ "A" , nil ]; |
NSString * result = [node leafForKeys:path]; |
将xml显示在tableview上:
@implementation TreeBrowserController |
- ( id ) initWithRoot:(TreeNode *) newRoot |
NSString *s =[newRoot dump]; |
if (newRoot.key) self .title = newRoot.key; |
- ( id )initWithStyle:(UITableViewStyle)style |
self = [ super initWithStyle:style]; |
- ( NSInteger )tableView:(UITableView *)tableView |
numberOfRowsInSection:( NSInteger )section |
return [ self .root.children count]; |
- (UITableViewCell *)tableView:(UITableView *)tableView |
cellForRowAtIndexPath:( NSIndexPath *)indexPath |
UITableViewCell *cell = [tableView |
dequeueReusableCellWithIdentifier:@ "generic" ]; |
if (!cell) cell = [[[UITableViewCell alloc] |
initWithFrame:CGRectZero reuseIdentifier:@ "generic" ] |
TreeNode *child = [[ self .root children] |
objectAtIndex:[indexPath row]]; |
cell.textLabel.text = [ NSString stringWithFormat:@ "%@:%@" , |
child.key, child.leafvalue]; |
cell.textLabel.text = child.key; |
cell.textLabel.textColor = [UIColor darkGrayColor]; |
cell.textLabel.textColor = [UIColor blackColor]; |
- ( void )tableView:(UITableView *)tableView |
didSelectRowAtIndexPath:( NSIndexPath *)indexPath |
[ self .root.children objectAtIndex:[indexPath row]]; |
TreeBrowserController *tbc = [[[TreeBrowserController alloc] |
initWithRoot:child] autorelease]; |
[ self .navigationController pushViewController:tbc animated: YES ]; |
效果:
![](https://pic002.cnblogs.com/images/2011/38542/2011072620161489.png)
![](https://pic002.cnblogs.com/images/2011/38542/2011072620162534.png)
总结:这篇文章通过封装两个类库,可以从web上很高效获取xml,将xml转换成树形结构,可以很方便的对树进行操作。
前面一篇文章,很好将xml转换成树,并进行操作,但是忽略了对xml节点上属性的操作,现在让我来修改代码,将属性添加进来。
1、在treenode中加一个类型为NSDictionary的attributeDict用于存放属性。代码如下:
NSDictionary * attributeDict;
2、在中可以在parser:didStartElement:方法中取到属性列表,在其中添加添加下面代码。
leaf.attributeDict = [[ NSDictionary alloc] initWithDictionary:attributeDict]; |
3、修改样例xml。
<? xml version = "1.0" encoding = "UTF-8" ?> |
< LoginResult id = "1" >True</ LoginResult > |
< LoginInfo >OK</ LoginInfo > |
< LastLogin >2011-05-09 12:20</ LastLogin > |
4、取属性id的值。
TreeNode * resultTreeNode = [node objectForKey:@ "LoginResult" ]; |
NSString *result = [resultTreeNode.attributeDict objectForKey:@ "id" ]; |