前面在项目过程中,与服务器端通信一直使用的JSON格式,在手机端JSON格式确实比较好用,苹果官方库还有许多三方库可以将JSON格式转化为数组或是字典直接使用,非常方便。这次有个功能服务端改成发XML数据了,原因是服务端那边用C#做的,可以从数据库中读取几行数据之后直接转成XML格式,而且不容易出错。于是这次研究了一下怎么解析XML。主要是使用系统的NSXMLParser类,这个类是按SAX的方式解析XML,对内存的压力会小一些,以下是解析过程。
首先看一下服务端发送过来的XML数据
<NewDataSet>
<Table>
<S_Id>7324</S_Id>
<E_Id>0</E_Id>
<ST_Id>3</ST_Id>
<TNumber>0</TNumber>
<Title>daaKmtu7FRRpctIzRLY023KfBcR61Phw9miyu7GQjNvP761ZeLntcpFuv8VhF3+OCgdQYsmQ8jeuHf5lMBtX00bAvvV96MDw550D+DFCJn3ESrwT1r9qPDxT0C66F6bV</Title>
<Content>tw/SEyqMBMlt/A61V4g/n4JUIYhZf9aTDdkjsYlLRqH20bF4ZZaDAbEap/JqlCKhwKP4XVr+2+YqXJV+y7gCtg==</Content>
<Result><img alt="" src="../SubjectImg/20120504033641487.gif" /></Result>
<Point>2</Point>
<Analysis>daaKmtu7FRRpctIzRLY023KfBcR61Phw9miyu7GQjNvP761ZeLntcpFuv8VhF3+OCgdQYsmQ8jeuHf5lMBtX00bAvvV96MDw550D+DFCJn3ESrwT1r9qPDxT0C66F6bV</Analysis>
<SS_Number>sdasd</SS_Number>
<IsSubject>1</IsSubject>
<HardType>1</HardType>
<ET_Id>1032</ET_Id>
<EO_Id>32</EO_Id>
<SC_Id>0</SC_Id>
<M_Id>3</M_Id>
<KeyWord><img alt="" sr</KeyWord>
<OptionCount>0</OptionCount>
<ImgSrc>UpLoadPic/20120504033641487.gif-----UpLoadPic/20120504033641487.gif-----UpLoadPic/20120504033641487.gif-----UpLoadPic/20120504033641487.gif</ImgSrc>
</Table>
<Table>
<S_Id>第二次</S_Id>
<E_Id>0</E_Id>
<ST_Id>3</ST_Id>
<TNumber>0</TNumber>
<Title>daaKmtu7FRRpctIzRLY023KfBcR61Phw9miyu7GQjNvP761ZeLntcpFuv8VhF3+OCgdQYsmQ8jeuHf5lMBtX00bAvvV96MDw550D+DFCJn3ESrwT1r9qPDxT0C66F6bV</Title>
<Content>tw/SEyqMBMlt/A61V4g/n4JUIYhZf9aTDdkjsYlLRqH20bF4ZZaDAbEap/JqlCKhwKP4XVr+2+YqXJV+y7gCtg==</Content>
<Result><img alt="" src="../SubjectImg/20120504033641487.gif" /></Result>
<Point>2</Point>
<Analysis>daaKmtu7FRRpctIzRLY023KfBcR61Phw9miyu7GQjNvP761ZeLntcpFuv8VhF3+OCgdQYsmQ8jeuHf5lMBtX00bAvvV96MDw550D+DFCJn3ESrwT1r9qPDxT0C66F6bV</Analysis>
<SS_Number>sdasd</SS_Number>
<IsSubject>1</IsSubject>
<HardType>1</HardType>
<ET_Id>1032</ET_Id>
<EO_Id>32</EO_Id>
<SC_Id>0</SC_Id>
<M_Id>3</M_Id>
<KeyWord><img alt="" sr</KeyWord>
<OptionCount>0</OptionCount>
<ImgSrc>UpLoadPic/20120504033641487.gif-----UpLoadPic/20120504033641487.gif-----UpLoadPic/20120504033641487.gif-----UpLoadPic/20120504033641487.gif</ImgSrc>
</Table>
</NewDataSet>
基本结构已经清晰了,顶级标签是<NewDataSet> 下来是<Table>,再下来相当于是一行数据的不同字段,解析的初步思路是:遇到<NewDataSet>标签时,生成一个数组,向下解析,遇到<Table>标签时生成一个字典,将该字典加入到前面生成的那个数组中,再向下解析时,遇到的标签以及标签里面的内容就当做键值对插入到字典中,整个XML数据解析完之后,数据就全部放在我第一次生成的那个数组里面了。
开始编码:
新建一个类:MKXMLAnalysis,
MKXMLAnalysis.h文件如下:
//
// MKXMLAnalysis.h
// testUIActionSheet
//
// Created by dengyuguo on 13-9-16.
// Copyright (c) 2013年 weicai. All rights reserved.
// 使用MSXMLParser将xml解析为数组
#import <Foundation/Foundation.h>
@class MKXMLAnalysis;
@protocol MKXMLAnalysisDelegate <NSObject>
//解析完成,array:解析xml获取的数组
-(void)analysisDidFinishAnalysis:(MKXMLAnalysis *)analysis analysisArray:(NSArray *)array;
@end
@interface MKXMLAnalysis : NSObject<NSXMLParserDelegate>
{
id<MKXMLAnalysisDelegate> delegate;
}
@property(nonatomic,assign)id<MKXMLAnalysisDelegate> delegate;
-(id)initWithXMLString:(NSString *)xmlString;
//开始解析
-(void)startAnalysis;
@end
这个类的目的很简单,用上面那种协议的XML数据生成这么一个对象,它将XML数据解析成一个数组,然后将这个数组,通过委托的形式返回给调用者。
再来看MKXMLAnalysis.m文件中几个重要的代码
@interface MKXMLAnalysis()
{
NSXMLParser * xmlParser;
NSMutableArray * resultArray;
int currentObjectIndex;
BOOL writeFlag;
//解析xml时获取到的节点,对应于对象的属性
NSString * currentKey;
}
@end
首先要在上面声明几个私有属性:xmlParser是解析XML数据的主角,resultArray是用来出来解析出来的数据的,currentObjectIndex,用来指定当前解析到了第几个<table>标签(没听明白没关系,实际过程中用不上这个属性),
writeFlag这个布尔变量很重要,在解析的过程中,它标示当解析碰到标签与标签之间的东西时,是否需要获取内容,并将内容写入字典中,详细情况看下面的代码。
-(id)initWithXMLString:(NSString *)xmlString
{
if (self=[super init]) {
NSData * xmlData=[xmlString dataUsingEncoding:NSUTF8StringEncoding];
xmlParser=[[NSXMLParser alloc]initWithData:xmlData];
[xmlParser setDelegate:self];
currentObjectIndex=-1;
writeFlag=NO;
}
return self;
}
在初始化方法里面将传入的xml字符串转化为data,因为NSXMLParser解析这个比较方便,然后初始化xmlParser,并且设置xmlParser的委托为self,writeFlag初始化状态显然应该是为NO。
//开始解析
-(void)startAnalysis
{
if([xmlParser parse])
{
}else{
NSLog(@"解析xml出错了");
}
}
开始解析
- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName attributes:(NSDictionary *)attributeDict
{
NSLog(@"找到头:<%@>",elementName);
if ([elementName isEqualToString:@"NewDataSet"]) {
writeFlag=NO;
}else if ([elementName isEqualToString:@"Table"]){
writeFlag=NO;
//创建一个存放对象数据的字典
NSMutableDictionary * objectDic=[[NSMutableDictionary alloc]init];
[resultArray addObject:objectDic];
}else{
writeFlag=YES;
if (currentKey!=nil) {
[currentKey release];
}
currentKey=elementName;
[currentKey retain];
}
}
第一个重要的回调函数
这个回调方法表示找到了标签的头,比如<NewDataSet>,或者是<Table>,或者是一些字段标签。我们来看一下代码:参数elementName,即是找到的标签的名字,如果找到的标签是NewDataSet时,标记writeFlag为NO,也就是说程序在找到标签之间的东西时,不用去获取内容,如果找到的标签是Table,writeFlag同样为NO,但这时,我们要生产一个字典了,因为Table标签下面的就是我们需要的数据,如果把他想象成一个表的,table下面就是一行数据了,我们需要有一个字典来放那一行数据。其他情况下碰到的开始标签就是各个字段了,这时我们设置writeFlag为YES,当前需要标签名需要记录下来以写入字典作为key,currentKey获取elementName的引用。这里遇到开始标签该干什么我们已经处理好了。
- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string
{
NSLog(@"找到内容:%@",string);
if (writeFlag==YES) {
//找到需要保存的数据
NSMutableDictionary * objectDic=(NSMutableDictionary *)[resultArray lastObject];
[objectDic setObject:string forKey:currentKey];
}
}
这是第二个重要的委托方法,一开始我以为只有找到了开始标签与结束标签之间的内容,xmlParser才会调用该方法,后来发现不是,实际情况xmlParser每找到一个标签,下一步都会执行这个回调,如果没有内容,string的值就空的。仔细想想,其实自己写算法的话也很难得写成前一种形式,要判断你找到的内容是介于开始标签和结束标签之间无意是件麻烦的事,聪明的做法是,每找到一个标签,都将标签后面的字符取到,直到碰到下一个标签为止,然后将获取到的字符串作为参数执行这个回调。当然,这是xmlParser的事,我们不用操心了,除非自己去实现一个XmlParser
这个方法里面就比较简单了,我们只需要判断writeFlag,如果为YES,表示找到了我们想要的数据,并且应该将它写到字典中
- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName
{
writeFlag=NO;
}
第三个重要的方法,这个方法就更简单了,结束标签到下一个标签的开始标签之间是没有内容的,显然writeFlag应该是NO
- (void)parserDidEndDocument:(NSXMLParser *)parser
{
[delegate analysisDidFinishAnalysis:self analysisArray:resultArray];
}
最后一个回调,解析完成之后,我们通过委托的方式,将解析获取到的数组发给客户端。
通过这个XML解析的详细操作,也算是认识到了,为什么JSON可以不用管它的具体机构就能直接转化为数组或字典,在JSON中“{}”大括号之间可以看做是一个对象,“[]”中括号可以看做是一个数组,这样我们还是按parser的方式来解析它时,只要碰到“{”大括号开始,我们就生成一个字典,然后再取解析里面的键值对存放到字典中,只要碰到"["中括号开始,我们就生成一个数组,存放一个一个对象。这样解析完成之后,就生成了数组或是对象,不需要我们事先知道结构,而XML显然就做不到这点,你碰到一个开始标签的时候,你是无法知道下面是应该生成一个数组还是应该生成一个字典的。
下面是MKXMLAnalysis.m的完整代码
//
// MKXMLAnalysis.m
// testUIActionSheet
//
// Created by weicai on 13-9-16.
// Copyright (c) 2013年 weicai. All rights reserved.
//
#import "MKXMLAnalysis.h"
@interface MKXMLAnalysis()
{
NSXMLParser * xmlParser;
NSMutableArray * resultArray;
int currentObjectIndex;
BOOL writeFlag;
//解析xml时获取到的节点,对应于对象的属性
NSString * currentKey;
}
@end
@implementation MKXMLAnalysis
@synthesize delegate;
-(id)initWithXMLString:(NSString *)xmlString
{
if (self=[super init]) {
NSData * xmlData=[xmlString dataUsingEncoding:NSUTF8StringEncoding];
xmlParser=[[NSXMLParser alloc]initWithData:xmlData];
[xmlParser setDelegate:self];
currentObjectIndex=-1;
writeFlag=NO;
}
return self;
}
//开始解析
-(void)startAnalysis
{
if([xmlParser parse])
{
}else{
NSLog(@"解析xml出错了");
}
}
- (void)parserDidStartDocument:(NSXMLParser *)parser
{
NSLog(@"开始解析");
resultArray=[[NSMutableArray alloc]init];
}
- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName attributes:(NSDictionary *)attributeDict
{
NSLog(@"找到头:<%@>",elementName);
if ([elementName isEqualToString:@"NewDataSet"]) {
writeFlag=NO;
}else if ([elementName isEqualToString:@"Table"]){
writeFlag=NO;
//创建一个存放对象数据的字典
NSMutableDictionary * objectDic=[[NSMutableDictionary alloc]init];
[resultArray addObject:objectDic];
}else{
writeFlag=YES;
if (currentKey!=nil) {
[currentKey release];
}
currentKey=elementName;
[currentKey retain];
}
}
- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string
{
NSLog(@"找到内容:%@",string);
if (writeFlag==YES) {
//找到需要保存的数据
NSMutableDictionary * objectDic=(NSMutableDictionary *)[resultArray lastObject];
[objectDic setObject:string forKey:currentKey];
}
}
- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName
{
writeFlag=NO;
}
- (void)parserDidEndDocument:(NSXMLParser *)parser
{
[delegate analysisDidFinishAnalysis:self analysisArray:resultArray];
}
-(void)dealloc
{
[resultArray release];
[xmlParser release];
[super dealloc];
}
@end
这个类不具有通用性,只能适用于上面那个结构的XML格式的解析,所以解析XML有点很重要,就是一定要跟服务端沟通好,固定一个结构,整个项目过程中的通信都按那个结构,这样就比较方便了。