在iOS开发中,大多数情况下,从网络获取的数据通常分两种。
JSON格式或者XML格式。
JSON是一种轻量级的数据格式,一般用于数据交互
JSON数据类似OC中的字典,解析方式也有很多
ios5中apple增加了解析JSON的api:NSJSONSerialization
(性能最好)
下面是NSJSONSerialization常用的两个方法
// JSON数据 > OC对象
+ (id)JSONObjectWithData:(NSData *)data options:(NSJSONReadingOptions)opt error:(NSError **)error;
// OC对象 > JSON数据
+ (NSData *)dataWithJSONObject:(id)obj options:(NSJSONWritingOptions)opt error:(NSError **)error;
另外Github上关于解析JSON的框架也有很多,JSONKit、SBJson、TouchJSON、JSONModel等。
关于JSON解析不做赘述,本文主要讲解XML解析。
虽然现在大部分公司中给我们的数据都是JSON,但是不排除一些比较老的公司中还在使用XML格式的数据,去年刚入行的时候接到的第一个项目中数据就是XML格式的,数据量又大又复杂,其中一个模块返回的XML数据接近1M,当时真的是被虐成狗。
XML全称是Extensible Markup Language,译作“可扩展标记语言”
跟JSON一样,也是常用的一种用于交互的数据格式
一般也叫XML文档(XML Document)
一个常见的XML文档一般由元素(Element)
和属性(Attribute)
组成
一个元素包括了开始标签和结束标签
拥有元素内容:<city>上海</city>
没有元素内容:<city></city>
没有元素内容的简写:<city/>
一个元素可以嵌套若干个子元素(不能出现交叉嵌套)
<citys>
<city>
<name>上海</name>
<weather>大暴雨</weather>
<air>舒适</air>
</city>
</citys>
规范的XML文档最多只有1个根元素,其他元素都是根元素的子孙元素
XML中的所有空格和换行,都会当做具体内容处理
一个元素可以拥有多个属性,属性值必须用 双引号"" 或者 单引号'' 括住。
<city name="上海" weather="大暴雨" air="舒适" />
属性表示的信息也可以用子元素来表示,比如
<city>
<name>上海</name>
<weather>大暴雨</weather>
<air>舒适</air>
</city>
关于XML具体的语法请百度or谷歌。
DOM解析:一次性将整个XML文档加载进内存,比较适合解析小文件
SAX解析:从根元素开始,按顺序一个元素一个元素往下解析,比较适合解析大文件
iOS SDK 提供了两个xml框架。
NSXMLParser
:它是基于objective-c语言的sax解析框架,是ios sdk默认的xml解析框架,不支持dom模式。 第三方xml解析框架
Gdataxml
:它是基于dom模式的解析库,由google开发,可以读写xml文档,支持xpath查询。通过标题,应该知道本文讲解的是NSXMLParser解析与Gdataxml解析。
我从之前项目中截取一段数据作为例子分别使用两种方式解析
<CrReport xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="http://tempuri.org/">
<CrRptByHour>
<CrReportByHour>
<Hour>10</Hour>
<Caption>10-12</Caption>
<CrChannel>0.8</CrChannel>
<CrRegion>0.6</CrRegion>
<CrArea>0.7</CrArea>
</CrReportByHour>
<CrReportByHour>
<Hour>12</Hour>
<Caption>12-14</Caption>
<CrChannel>1.5</CrChannel>
<CrRegion>0.9</CrRegion>
<CrArea>1.0</CrArea>
</CrReportByHour>
</CrRptByHour>
<CrRptByDay>
<CrReportByDay>
<Date>2015-03-02T00:00:00+08:00</Date>
<WeekDay>1</WeekDay>
<CrChannel>0.0330</CrChannel>
<CrRegion>0.0290</CrRegion>
<CrArea>0.0290</CrArea>
</CrReportByDay>
<CrReportByDay>
<Date>2015-03-02T00:00:00+08:00</Date>
<WeekDay>2</WeekDay>
<CrChannel>0.0310</CrChannel>
<CrRegion>0.0280</CrRegion>
<CrArea>0.0300</CrArea>
</CrReportByDay>
</CrRptByDay>
<CrRptSubTotal>
<CrReportSubTotal>
<RegionType>0</RegionType>
<Caption>SA Channel</Caption>
<CrYTD>0.0310</CrYTD>
<CrQTD>0.0310</CrQTD>
<CrMTD>0.0290</CrMTD>
<CrWTD>0.0000</CrWTD>
<CrYersterday>0.0000</CrYersterday>
<CrToday>0</CrToday>
</CrReportSubTotal>
<CrReportSubTotal>
<RegionType>1</RegionType>
<Caption>SA North B</Caption>
<CrYTD>0.0280</CrYTD>
<CrQTD>0.0280</CrQTD>
<CrMTD>0.0280</CrMTD>
<CrWTD>0.0000</CrWTD>
<CrYersterday>0.0000</CrYersterday>
<CrToday>0</CrToday>
</CrReportSubTotal>
</CrRptSubTotal>
</CrReport>
要使用GDataXML,先要对项目进行一些配置.
1>导入libxml2动态库
targets–Build Phases–link Binary With Libraries
2>设置libxml2的头文件搜索路径(为了能找到libxml2库的所有头文件)
在Head Search Path中加入/usr/include/libxml2
3>设置链接参数(自动链接libxml2库)
在Other Linker Flags中加入-lxml2
CMD+B 编译通过没有报错说明环境配置成功。
GDataXML中常用的类
GDataXMLDocument: 代表整个XML文档
GDataXMLElement: 代表文档中的每个元素
使用attributeForName:方法可以获得属性值
上代码
//加载整个XML数据
self.document=[[GDataXMLDocument alloc] initWithData:data options:0 error:nil];
//获得文档的根元素 CrReport
GDataXMLElement* rootElement = self.document.rootElement;
//获得根节点下所有CrRptByHour
NSArray* CrRptByHour = [rootElement elementsForName:@"CrRptByHour"];
for (GDataXMLElement* subByHoru in CrRptByHour) {
//获得CrRptByHour节点下所有CrReportByHour 节点中的内容
NSArray* CrReportByHour = [subByHoru elementsForName:@"CrReportByHour"];
//遍历CrReportByHour 中所有内容
for (GDataXMLElement* subElement in CrReportByHour) {
//获取Hour、caption等节点中的内容,一般此处用模型来接受这些值
NSString* Hour = [[[subElement elementsForName:@"Hour"] objectAtIndex:0] stringValue];
NSString* caption = [[[subElement elementsForName:@"Caption"] objectAtIndex:0] stringValue];
NSString* CrChannel = [[[subElement elementsForName:@"CrChannel"] lastObject] stringValue];
NSString* CrRegion = [[[subElement elementsForName:@"CrRegion"] lastObject]stringValue];
NSString* CrArea = [[[subElement elementsForName:@"CrArea"] lastObject] stringValue];
NSLog(@"Hour=%@",Hour);
NSLog(@"caption=%@",caption);
NSLog(@"CrChannel=%@",CrChannel);
NSLog(@"CrRegion=%@",CrRegion);
NSLog(@"CrArea=%@",CrArea);
NSLog(@"=======================");
/* <CrReportByHour Hour=@"10" Caption=@"10-12" CrChannel=@"0.8" CrRegion=@"0.6" CrArea=@"0.7"/> 注意如果是这种类型的数据,用下面这种形式取值 [[subElement attributeForName:@"Hour"] stringValue]; //根据属性名称获取值 */
}
}
你可以下载代码试着解析CrRptByDay与CrRptSubTotal中的内容。
GdataXML的最吸引人的在于他支持xpath语法,关于xpath语法内容还还请各位看官另行搜索。我把之前项目中封装的一整套解析工具也放到代码中XML文件夹中,其中大量使用xpath,有兴趣的朋友可以下载来研究一下。
另外完整数据涉及之前公司的商业机密,所有没有放进去,大家凑合看吧。
NSXMLParser属于SAX解析,是从上往下依次解析每个元素,在解析到每个元素的时候会通知代理,所以使用NSXMLParser必须遵守他的协议。
使用非常简单
// 创建解析器
NSXMLParser *parser = [[NSXMLParser alloc]initWithData:data];
// 设置代理
parser.delegate = self;
// 开始解析
[parser parse];
NSXMLParser的delegate
/** * 解析到文档的开头时会调用 */
- (void)parserDidStartDocument:(NSXMLParser *)parser
{
// NSLog(@"parserDidStartDocument----");
}
/** * 解析到一个元素的开始就会调用 * * @param elementName 元素名称 * @param attributeDict 属性字典 */
- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName attributes:(NSDictionary *)attributeDict
{
一般情况,如果数据是这种格式,把内容放到属性中
<CrReportByHour Hour=@"10" Caption=@"10-12" CrChannel=@"0.8" CrRegion=@"0.6" CrArea=@"0.7"/>
系统会自动把以上内容转为字典存放到attributeDict这个字典中
可以用KVC通过字典给模型直接复制(字典中的key必须都能在模型中找到)
}
/** * 解析到一个元素的结束就会调用 * * @param elementName 元素名称 */
- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName
{
// NSLog(@"didEndElement----%@", elementName);
}
/** * 解析到文档的结尾时会调用(解析结束) */
- (void)parserDidEndDocument:(NSXMLParser *)parser
{
// NSLog(@"parserDidEndDocument----");
}
如果解析的是下面这段XML,直接在开始解析的方法中打印字典log输出
<CrReport xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="http://tempuri.org/">
<CrRptByHour>
<CrReportByHour Hour="10" Caption="10-12" CrChannel="0.8" CrRegion="0.6" CrArea="0.7" />
<CrReportByHour Hour="10" Caption="10-14" CrChannel="1.5" CrRegion="0.9" CrArea="1.0" />
</CrRptByHour>
</CrReport>
无奈当时服务器返回的数据不是这种格式,而且内容全都嵌入在元素中。
这里我们需要用到一个代理方法
// 当解析器找到开始标记和结束标记之间的字符时,调用这个方法解析当前节点的所有字符
-(void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string{
//string 就是每个元素中包含的内容,我们需要在这里拿到并记录自己需要的数据
self.currentString = string;
}
当初由于项目需要在不同的地方展示这份XML中三分数据,所有我当时的思路是在需要解析的地方创建解析工具类,传入父元素名,来获取下面子节点的所有内容.
根据自己传入的元素名来判断
在开始解析元素的方法中初始化可变字典,并且添加到全局的数组中
在上面的方法中用全局变量来记录string值
在结束元素解析的方法中用用当前元素作为key,记录的string为value写入字典中。
XMLParser* parser = [[XMLParser alloc] parseDataByData:xmlData];
//传入节点名称获取内容 CrReportByHour、CrReportByDay、CrReportSubTotal
NSMutableArray* array = [parser searchDataWithRootElement:@"CrReportByHour"];
NSLog(@"%@",array);
具体实现代码大家下载来自己看吧,那时候刚入行,代码写的比较渣。
代码下载: https://github.com/hongfenglt/XMLParse