iOS开发之将XML转换成树
开发中由于服务端与客户端是两种不同的平台,而且服务端又是老系统,不具备很好的面向对象的性质,所以导致客户端与服务端只好通过一些制定好的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转换成树形结构,可以很方便的对树进行操作。
(全文完)
以下为广告部分
您部署的HTTPS网站安全吗?
如果您想看下您的网站HTTPS部署的是否安全,花1分钟时间来 myssl.com 检测以下吧。让您的HTTPS网站变得更安全!
快速了解HTTPS网站安全情况。
安全评级(A+、A、A-...)、行业合规检测、证书信息查看、证书链信息以及补完、服务器套件信息、证书兼容性检测等。
安装部署SSL证书变得更方便。
SSL证书内容查看、SSL证书格式转换、CSR在线生成、SSL私钥加解密、CAA检测等。
让服务器远离SSL证书漏洞侵扰
TLS ROBOT漏洞检测、心血漏洞检测、FREAK Attack漏洞检测、SSL Poodle漏洞检测、CCS注入漏洞检测。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· .NET周刊【3月第1期 2025-03-02】
· 分享 3 个 .NET 开源的文件压缩处理库,助力快速实现文件压缩解压功能!
· [AI/GPT/综述] AI Agent的设计模式综述
2010-07-26 利用.NET Framework4.0的源代码调试你的应用程序