开发中由于服务端与客户端是两种不同的平台,而且服务端又是老系统,不具备很好的面向对象的性质,所以导致客户端与服务端只好通过一些制定好的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> #import "TreeNode.h" @interface XMLParser : NSObject { NSMutableArray *stack; } + (XMLParser *) sharedInstance; - (TreeNode *) parseXMLFromURL: (NSURL *) url; - (TreeNode *) parseXMLFromData: (NSData*) data; @end
shareInstance使用一个单例。
调用parseXMLFromURL方法,需要一个NSURL的参数,返回我们需要的树节点。
调用parseXMLFromData方法,需要一个NSData的参数,返回我们需要的树节点。
在此之前,先定义TreeNode类:
#import <CoreFoundation/CoreFoundation.h> @interface TreeNode : NSObject { TreeNode *parent; NSMutableArray *children; NSString *key; NSString *leafvalue; } @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; + (TreeNode *) treeNode; - (NSString *) dump; - (void) teardown; // Leaf Utils - (BOOL) isLeaf; - (BOOL) hasLeafValue; - (NSArray *) leaves; - (NSArray *) allLeaves; // Key Utils - (NSArray *) keys; - (NSArray *) allKeys; - (NSArray *) uniqKeys; - (NSArray *) uniqAllKeys; // Search Utils - (TreeNode *) objectForKey: (NSString *) aKey; - (NSString *) leafForKey: (NSString *) aKey; - (NSMutableArray *) objectsForKey: (NSString *) aKey; - (NSMutableArray *) leavesForKey: (NSString *) aKey; - (TreeNode *) objectForKeys: (NSArray *) keys; - (NSString *) leafForKeys: (NSArray *) keys; // Convert Utils - (NSMutableDictionary *) dictionaryForChildren; @end
TreeNode实现:
#import "TreeNode.h" // String stripper utility macro #define STRIP(X) [X stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]] @implementation TreeNode @synthesize parent; @synthesize children; @synthesize key; @synthesize leafvalue; #pragma mark Create and Initialize TreeNodes - (TreeNode *) init { if (self = [super init]) { self.key = nil; self.leafvalue = nil; self.parent = nil; self.children = nil; } return self; } + (TreeNode *) treeNode { return [[[self alloc] init] autorelease]; } #pragma mark TreeNode type routines - (BOOL) isLeaf { return (self.children.count == 0); } - (BOOL) hasLeafValue { return (self.leafvalue != nil); } #pragma mark TreeNode data recovery routines // Return an array of child keys. No recursion - (NSArray *) keys { NSMutableArray *results = [NSMutableArray array]; for (TreeNode *node in self.children) [results addObject:node.key]; return results; } // Return an array of child keys with depth-first recursion. - (NSArray *) allKeys { NSMutableArray *results = [NSMutableArray array]; for (TreeNode *node in self.children) { [results addObject:node.key]; [results addObjectsFromArray:node.allKeys]; } return results; } - (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 array; } // Return a sorted, uniq array of child keys. No recursion - (NSArray *) uniqKeys { return [self uniqArray:[self keys]]; } // Return a sorted, uniq array of child keys. With depth-first recursion - (NSArray *) uniqAllKeys { return [self uniqArray:[self allKeys]]; } // Return an array of child leaves. No recursion - (NSArray *) leaves { NSMutableArray *results = [NSMutableArray array]; for (TreeNode *node in self.children) if (node.leafvalue) [results addObject:node.leafvalue]; return results; } // Return an array of child leaves with depth-first recursion. - (NSArray *) allLeaves { NSMutableArray *results = [NSMutableArray array]; for (TreeNode *node in self.children) { if (node.leafvalue) [results addObject:node.leafvalue]; [results addObjectsFromArray:node.allLeaves]; } return results; } #pragma mark TreeNode search and retrieve routines // Return the first child that matches the key, searching recursively breadth first - (TreeNode *) objectForKey: (NSString *) aKey { TreeNode *result = nil; for (TreeNode *node in self.children) if ([node.key isEqualToString: aKey]) { result = node; break; } if (result) return result; for (TreeNode *node in self.children) { result = [node objectForKey:aKey]; if (result) break; } return result; } // Return the first leaf whose key is a match, searching recursively breadth first - (NSString *) leafForKey: (NSString *) aKey { TreeNode *node = [self objectForKey:aKey]; return node.leafvalue; } // Return all children that match the key, including recursive depth first search. - (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]]; } return result; } // Return all leaves that match the key, including recursive depth first search. - (NSMutableArray *) leavesForKey: (NSString *) aKey { NSMutableArray *result = [NSMutableArray array]; for (TreeNode *node in [self objectsForKey:aKey]) if (node.leafvalue) [result addObject:node.leafvalue]; return result; } // Follow a key path that matches each first found branch, returning object - (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]; } return nil; } // Follow a key path that matches each first found branch, returning leaf - (NSString *) leafForKeys: (NSArray *) keys { TreeNode *node = [self objectForKeys:keys]; return node.leafvalue; } #pragma mark output utilities // Print out the tree - (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]; } - (NSString *) dump { NSMutableString *outstring = [[NSMutableString alloc] init]; [self dumpAtIndent:0 into:outstring]; return [outstring autorelease]; } #pragma mark conversion utilities // When you're sure you're the parent of all leaves, transform to a dictionary - (NSMutableDictionary *) dictionaryForChildren { NSMutableDictionary *results = [NSMutableDictionary dictionary]; for (TreeNode *node in self.children) if (node.hasLeafValue) [results setObject:node.leafvalue forKey:node.key]; return results; } #pragma mark invocation forwarding // Invocation Forwarding lets node act like array - (id)forwardingTargetForSelector:(SEL)sel { if ([self.children respondsToSelector:sel]) return self.children; return nil; } // Extend selector compliance - (BOOL)respondsToSelector:(SEL)aSelector { if ( [super respondsToSelector:aSelector] ) return YES; if ([self.children respondsToSelector:aSelector]) return YES; return NO; } // Allow posing as NSArray class for children - (BOOL)isKindOfClass:(Class)aClass { if (aClass == [TreeNode class]) return YES; if ([super isKindOfClass:aClass]) return YES; if ([self.children isKindOfClass:aClass]) return YES; return NO; } #pragma mark cleanup - (void) teardown { for (TreeNode *node in [[self.children copy] autorelease]) [node teardown]; [self.parent.children removeObject:self]; self.parent = nil; } - (void) dealloc { self.parent = nil; self.children = nil; self.key = nil; self.leafvalue = nil; [super dealloc]; } @end
从上面的代码可以看出,定义了很多方便的方法来获取数据。
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类:
#import "XMLParser.h" @implementation XMLParser static XMLParser *sharedInstance = nil; // Use just one parser instance at any time +(XMLParser *) sharedInstance { if(!sharedInstance) { sharedInstance = [[self alloc] init]; } return sharedInstance; } // Parser returns the tree root. You may have to go down one node to the real results - (TreeNode *) parse: (NSXMLParser *) parser { stack = [NSMutableArray array]; TreeNode *root = [TreeNode treeNode]; root.parent = nil; root.leafvalue = nil; root.children = [NSMutableArray array]; [stack addObject:root]; [parser setDelegate:self]; [parser parse]; [parser release]; // pop down to real root TreeNode *realroot = [[root children] lastObject]; root.children = nil; root.parent = nil; root.leafvalue = nil; root.key = nil; realroot.parent = nil; return realroot; } - (TreeNode *)parseXMLFromURL: (NSURL *) url { TreeNode *results; NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; NSXMLParser *parser = [[NSXMLParser alloc] initWithContentsOfURL:url]; results = [self parse:parser]; [pool drain]; return results; } - (TreeNode *)parseXMLFromData: (NSData *) data { TreeNode *results; NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; NSXMLParser *parser = [[NSXMLParser alloc] initWithData:data]; results = [self parse:parser]; [pool drain]; return results; } // Descend to a new element - (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.leafvalue = nil; leaf.children = [NSMutableArray array]; [stack addObject:leaf]; } // Pop after finishing element - (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName { [stack removeLastObject]; } // Reached a leaf - (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string { if (![[stack lastObject] leafvalue]) { [[stack lastObject] setLeafvalue:[NSString stringWithString:string]]; return; } [[stack lastObject] setLeafvalue:[NSString stringWithFormat:@"%@%@", [[stack lastObject] leafvalue], string]]; } @end
使用这两个类:
下面看下我们如何使用这个类:
在iis中放下面这个xml:
<?xml version="1.0" encoding="UTF-8"?> <Login> <LoginResult>True</LoginResult> <LoginInfo>恭喜你登录成功</LoginInfo> <LastLogin>2011-05-09 12:20</LastLogin> <Right> <A>1</A> <B>1</B> <C>0</C> </Right> </Login>
使用下面代码获取web服务器上的xml,并将xml转换成树:
NSURL * url = [[NSURL alloc] initWithString:@"http://10.5.23.117:4444/Login.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 @synthesize root; // Each instance of this controller has a separate root, as // descending through the tree produces new roots. - (id) initWithRoot:(TreeNode *) newRoot { if (self = [super init]) { self.root = newRoot; NSString *s =[newRoot dump]; if (newRoot.key) self.title = newRoot.key; } return self; } - (id)initWithStyle:(UITableViewStyle)style { self = [super initWithStyle:style]; if (self) { // Custom initialization } return self; } // The number of rows equals the number of children for a node - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { return [self.root.children count]; } // Color code the cells that can be navigated through - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"generic"]; if (!cell) cell = [[[UITableViewCell alloc] initWithFrame:CGRectZero reuseIdentifier:@"generic"] autorelease]; TreeNode *child = [[self.root children] objectAtIndex:[indexPath row]]; // Set text if (child.hasLeafValue) cell.textLabel.text = [NSString stringWithFormat:@"%@:%@", child.key, child.leafvalue]; else cell.textLabel.text = child.key; // Set color if (child.isLeaf) cell.textLabel.textColor = [UIColor darkGrayColor]; else cell.textLabel.textColor = [UIColor blackColor]; return cell; } // On selection, either push a new controller or show the leaf value - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { TreeNode *child = [self.root.children objectAtIndex:[indexPath row]]; if (child.isLeaf) { return; } TreeBrowserController *tbc = [[[TreeBrowserController alloc] initWithRoot:child] autorelease]; [self.navigationController pushViewController:tbc animated:YES]; } // These controllers are ephemeral and need dealloc - (void) dealloc { self.root = nil; [super dealloc]; } @end
总结:这篇文章通过封装两个类库,可以从web上很高效获取xml,将xml转换成树形结构,可以很方便的对树进行操作。