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转换成树形结构,可以很方便的对树进行操作。

原文链接:加载失败,请重新获取