iOS网络数据解析之XML解析详解(GDataXMLNode&原生NSXMLParser)

在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,当时真的是被虐成狗。

<h3>XML概述</h3>

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>
        <weather>大暴雨</weather>
          <air>舒适</air>
    </city>

关于XML具体的语法请百度or谷歌。
<h3>XML解析方式</h3>

DOM解析:一次性将整个XML文档加载进内存,比较适合解析小文件
SAX解析:从根元素开始,按顺序一个元素一个元素往下解析,比较适合解析大文件

iOS SDK 提供了两个xml框架。

  1. NSXMLParser:它是基于objective-c语言的sax解析框架,是ios sdk默认的xml解析框架,不支持dom模式。
  2. libxml2: 它是基于c语言的xml解析器,被苹果整合在ios sdk中,支持sax和dom模式。

第三方xml解析框架

  1. tbxml:它是轻量级的dom模式解析库,不支持xml文档验证和xpath,只能读取xml文档,不能写xml文档。
  2. touchxml:它是基于dom模式的解析库,与 tbxml类似,只能读取xml文档,不能写xml文档。
  3. kissxml:它是基于dom模式的解析库,基于touchxml,主要的不同是可以写入xml文档。
  4. Gdataxml:它是基于dom模式的解析库,由google开发,可以读写xml文档,支持xpath查询。

<h3>具体解析实例</h3>
通过标题,应该知道本文讲解的是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>

<h4>GDataXML</h4>
要使用GDataXML,先要对项目进行一些配置.
1>导入libxml2动态库
targets--Build Phases--link Binary With Libraries


image

image

2>设置libxml2的头文件搜索路径(为了能找到libxml2库的所有头文件)
在Head Search Path中加入/usr/include/libxml2


image

3>设置链接参数(自动链接libxml2库)
在Other Linker Flags中加入-lxml2


image

4>由于GDataXML是非ARC的,因此得设置编译参数


image

CMD+B 编译通过没有报错说明环境配置成功。

GDataXML中常用的类
GDataXMLDocument: 代表整个XML文档
GDataXMLElement: 代表文档中的每个元素
使用attributeForName:方法可以获得属性值

上代码(下面这段怎么编辑都有点问题,所以截图了)

Snip20150826_32.png

输出内容如下


image

你可以下载代码试着解析CrRptByDay与CrRptSubTotal中的内容。

GdataXML的最吸引人的在于他支持xpath语法,关于xpath语法内容还还请各位看官另行搜索。我把之前项目中封装的一整套解析工具也放到代码中XML文件夹中,其中大量使用xpath,有兴趣的朋友可以下载来研究一下。

另外完整数据涉及之前公司的商业机密,所有没有放进去,大家凑合看吧。


<h4>NSXMLParser</h4>

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>

image

看到熟悉的字典大家应该懂了。

无奈当时服务器返回的数据不是这种格式,而且内容全都嵌入在元素中。
这里我们需要用到一个代理方法

// 当解析器找到开始标记和结束标记之间的字符时,调用这个方法解析当前节点的所有字符 
-(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);

array的输出内容是


image

具体实现代码大家下载来自己看吧,那时候刚入行,代码写的很渣。
代码下载:https://github.com/hongfenglt/XMLParse

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,968评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,601评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,220评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,416评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,425评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,144评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,432评论 3 401
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,088评论 0 261
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,586评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,028评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,137评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,783评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,343评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,333评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,559评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,595评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,901评论 2 345

推荐阅读更多精彩内容