我们知道xml是一种数据交换格式,在各种语言中的使用都非常广泛。
在学习java的过程中我已经对xml解析有了一定的了解,在java中对xml文档进行解析也是一件比较麻烦复杂的事情,当时对我也造成了不下的阻力。
在这几天学习ios网络编程的时候,我又遇到了xml解析的问题。
在ios开发中,iOS SDK提供了NSXML和libxml2两种xml框架,另外还有许多诸如TBXML、touchXML等的第三方框架可以使用。
一、使用NSXML框架
在学习中我主要使用了NSXML和TBXML两种框架进行xml解析,其中NSXML是使用了SAX的解析模式,这是一种基于事件驱动的解析模式,简单来说就是从上到下扫描整个xml文档,每当遇到开始标签、结束标签以及属性时,就会触发相应的事件。
基于代码复用性的考虑,我们编写一个专门的xml解析类用来使用NSXML框架进行xml解析:
#import <Foundation/Foundation.h>
@interface NotesXMLParser : NSObject<NSXMLParserDelegate>
@property (strong,nonatomic) NSMutableArray* notes;
@property (strong,nonatomic) NSString* currentTagName;
- (void)start;
@end
<pre name="code" class="objc">#import "NotesXMLParser.h"
@implementation NotesXMLParser
@synthesize notes = _notes;
@synthesize currentTagName = _currentTagName;
- (void)start{
//设置xml文件路径
NSString* path = [[NSBundle mainBundle] pathForResource:@"Notes" ofType:@"xml"];
NSURL* url = [NSURL fileURLWithPath:path];
//开始解析xml
NSXMLParser* parser = [[NSXMLParser alloc] initWithContentsOfURL:url];
parser.delegate = self;
[parser parse];
}
//在开始解析xml文档时调用,可用来初始化变量
- (void)parserDidStartDocument:(NSXMLParser *)parser{
_notes = [NSMutableArray new];
}
//解析出错时调用
- (void)parser:(NSXMLParser *)parser parseErrorOccurred:(NSError *)parseError{
NSLog(@"%@",parseError);
}
//遇到开始标签时调用
- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName attributes:(NSDictionary *)attributeDict{
//记录当前解析的标签名
_currentTagName = elementName;
//若是Note标签,则要新建一个字典类型加入到notes中
if ([_currentTagName isEqualToString:@"Note"]) {
NSString* _id = [attributeDict objectForKey:@"id"];
NSMutableDictionary* dict = [NSMutableDictionary new];
[dict setObject:_id forKey:@"id"];
[_notes addObject:dict];
}
}
//遇到字符串时调用
- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string{
//替换空格和回车
string = [string stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
//遇到空字符串则跳过
if ([string isEqualToString:@""]) {
return;
}
//取出当前解析中的字典类型
NSMutableDictionary* dict = [_notes lastObject];
//当字符创非空,添加到字典类型中
if ([_currentTagName isEqualToString:@"CDate"] && dict) {
[dict setObject:string forKey:@"CDate"];
}
if ([_currentTagName isEqualToString:@"Content"] && dict) {
[dict setObject:string forKey:@"Content"];
}
if ([_currentTagName isEqualToString:@"UserID"] && dict) {
[dict setObject:string forKey:@"UserID"];
}
}
//遇到结束标签
- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName{
_currentTagName = nil;
}
//xml文档解析完毕
- (void)parserDidEndDocument:(NSXMLParser *)parser{
//通过通知机制将解析的结果传送回调用处
[[NSNotificationCenter defaultCenter] postNotificationName:@"reloadViewNotification" object:self.notes userInfo:nil];
self.notes = nil;
}
@end
然后,在需要进行xml解析的地方实例化该xml解析类并进行xml解析,在本例中,我们是在试图控制器中的ViewDidLoad方法中进行xml解析的:
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
//注册通知接受者
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(reloadView:) name:@"reloadViewNotification" object:nil];
//实例化xml解析类并进行解析
NotesXMLParser* parser = [NotesXMLParser new];
[parser start];
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
//接收到通知后调用的方法
- (void)reloadView:(NSNotification*)notification{
self.listData = [notification object];
[self.tableView reloadData];
}
到此就完成了使用NSXML进行xml解析的全过程,可以发现使用NSXML进行解析会比较复杂,我们需要手动写解析的全过程。
二、使用TBXML框架
TBXML进行xml解析是基于DOM模式的解析,DOM模式会将xml文件的元素当做一个树状结构,一次性读入内存中。由于TBXML是第三方框架,所以不能直接使用,使用的过程如下。
1.首先到https://github.com/71squared/TBXML 下载TBXML框架,解压后将TBXML-Headers和TBXML-Code文件夹添加到工程中:
2.在工程中添加TBXML所依赖的Framework和库,如下图:
3.添加预编译头文件,点击File-->New-->File...菜单项,选择iOS-->Other-->PCH File 文件,创建预编译头文件PrefixHeader.pch,并编写如下:
#ifndef TBXML_PrefixHeader_pch
#define TBXML_PrefixHeader_pch
#import <Foundation/Foundation.h>
#define ARC_ENABLED
// Include any system framework and library headers here that should be included in all compilation units.
// You will also need to set the Prefix Header build setting of one or more of your targets to reference this file.
#endif
4.配置预编译头文件到工程,打开工程,选择TARGETS-->工程名-->Build Settings-->Apple LLVM 6.1-Language-->Prefix Header 输入PrefixHeader.pch,如下图:
5.到这里,就已经将TBXML框架加入到工程之中了,接下来,只需要修改xml解析类中的start方法,就可以了,并不需要修改其他的代码:
- (void)start{
//初始化notes变量
_notes = [NSMutableArray new];
//设置xml文件路径
TBXML* tbxml = [[TBXML alloc] initWithXMLFile:@"Notes.xml" error:nil];
//设置根节点
TBXMLElement* root = tbxml.rootXMLElement;
if (root) {
//在根节点的子节点中寻找Note标签的节点
TBXMLElement* noteElement = [TBXML childElementNamed:@"Note" parentElement:root];
while (noteElement != nil) {
//解析各属性
NSMutableDictionary* dict = [NSMutableDictionary new];
//添加id属性并添加到notes中
[dict setObject:[TBXML valueOfAttributeNamed:@"id" forElement:noteElement error:nil] forKey:@"id"];
[_notes addObject:dict];
//添加Note节点中的各个子节点到字典类型中
TBXMLElement* CDateElement = [TBXML childElementNamed:@"CDate" parentElement:noteElement];
if (CDateElement != nil) {
[dict setObject:[TBXML textForElement:CDateElement] forKey:@"CDate"];
}
TBXMLElement* ContentElement = [TBXML childElementNamed:@"Content" parentElement:noteElement];
if (ContentElement != nil) {
[dict setObject:[TBXML textForElement:ContentElement] forKey:@"Content"];
}
TBXMLElement* UserIDElement = [TBXML childElementNamed:@"UserID" parentElement:noteElement];
if (UserIDElement != nil) {
[dict setObject:[TBXML textForElement:UserIDElement] forKey:@"UserID"];
}
//寻找与note节点出于同一级的下一个Note节点
noteElement = [TBXML nextSiblingNamed:@"Note" searchFromElement:noteElement];
}
NSLog(@"完成TBXML解析");
//通过通知机制传输解析结果
[[NSNotificationCenter defaultCenter] postNotificationName:@"reloadViewNotification" object:self.notes userInfo:nil];
self.notes = nil;
}
}
可以看出,使用了tbxml框架来进行解析,不仅代码量减少了很多,解析的过程也更加地清晰,使用起来更加简单。