iOS开发-XML解析基础笔记

当你想将某些网络媒体数据载入你的app时,就避免不了和商家提供的数据格式打交道,主要途径就是通过数据交换格式展示给想开发的用户。现在主要的两种数据交换格式为XML,JSON。在iOS开发中,提倡使用JSON格式,但是不乏一些商家并没有提供JSON格式的数据。本文简单讲解以下XML解析的基础,供新手参考,也是共同学习,因为在网上大部分写的都不够详细。之后会写一些关于JSON格式的,感觉JSON比XML的解析还是更加简单易懂易用的。

在iOS中,解析XML有许多框架可以选用,自带NSXMLParser, 一个开源框架libxml2,和其他第三方框架,这里主要说下自带的NSXMLParser的解析,其他第三方框架之后会更新。

NSXMLParser遵守的是SAX解析方式,相对于另外一种DOM的解析方式,SAX是以后逐渐的主流,因为其解析过程就好像是NSURLSession取代NSURLConnection一样,主要还是为了提高用户体验,SAX可以一边解析一边更新数据,而DOM则需要解析完整个文件才能进行处理。

NSXMLParser的实现思想也是完全按照一边解析一边提取数据的思想来做,这点如果是用JSON格式习惯的童鞋们,Google的GDataXML更适合你,其与JSON提取数据的过程有点类似。

这里只说一下如何得到XML里的数据,关于建立,更改XML文件的操作,之后也许会提到。

先贴代码,实现XML解析的model文件

 1 #import "XMLProcess.h"
 2 @interface XMLProcess()<NSXMLParserDelegate>
 3 @property (nonatomic)NSString *webElementNode, *contentStr;
 4 @property (nonatomic)BOOL flag;
 5 
 6 @end
 7 
 8 @implementation XMLProcess
 9 
10 #pragma mark - from web URL
11 
12 //- (NSXMLParser *)parser
13 //{
14 //    NSURL *url = [NSURL URLWithString:@"https://itunes.apple.com/us/rss/topsongs/limit=10/xml"];
15 //    NSXMLParser *parser = [[NSXMLParser alloc]initWithContentsOfURL:url];
16 //    parser.delegate = self;
17 //    return parser;
18 //}
19 
20 #pragma mark - From Local files
21 
22 - (NSXMLParser *)parser
23 {
24     NSString *filePath = [[NSBundle mainBundle]pathForResource:@"top10songs" ofType:@"xml"];
25     NSData *data = [NSData dataWithContentsOfFile:filePath];
26     NSXMLParser *parser = [[NSXMLParser alloc]initWithData:data];
27     parser.delegate = self;
28     return parser;
29 }
30 
31 - (void)parserDidStartDocument:(NSXMLParser *)parser
32 {
33     self.xmlResult = [[NSMutableArray alloc]init];
34 }
35 
36 - (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName attributes:(NSDictionary<NSString *,NSString *> *)attributeDict
37 {
38     if ([elementName isEqualToString:@"entry"]) {
39         self.flag = YES;
40     }
41     self.webElementNode = [NSString stringWithString:elementName];
42     self.contentStr = [[NSString alloc]init];
43 }
44 
45 - (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string
46 {
47     if (self.flag == YES) {
48         if ([self.webElementNode isEqualToString:@"title"]) {
49             self.contentStr = [NSString stringWithString:string];
50         }
51     }
52 }
53 
54 - (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName
55 {
56     if ([elementName isEqualToString:@"title"]) {
57         [self.xmlResult addObject:self.contentStr];
58     }
59     
60 }
61 
62 @end

清晰明了,我将NSXMLParser作了一个重写,得到XML文件有两种方式,一种是网络上的XML,一种是本地的XML文件。

网上几乎没有说网络XML文件的,其实也简单,注释掉的line 12-18就是网络XML文件的,line22-29是本地xml文件的,之前需要将XML文件引入你的project,选上copy item if needed。

解析的过程主要是delegate方法,这里以iTunes RSS Top 10 songs的XML为例 https://itunes.apple.com/us/rss/topsongs/limit=10/xml,有两个方法是选用的

1 - (void)parserDidStartDocument:(NSXMLParser *)parser
2 {
3     self.xmlResult = [[NSMutableArray alloc]init];
4 }
5 
6 - (void)parserDidEndDocument:(NSXMLParser *)parser
7 {
8     
9 }

这两个方法类似于viewWillAppear和viewDidDisappear这两个方法,一定要用的时候比较少,一般用于锦上添花,显得代码更有逻辑性。比如在DidStart中初始化结果数组,以减少不必要的代码执行。

重点说下line 36 - 58这三个delegate方法,典型的SAX思维,一边解析一边操作数据

说明:iOS中model里自定义的方法需要在controller中能执行的方法中调用才能生效,但是delegate方法放在model中是完全没问题的

1 - (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName attributes:(NSDictionary<NSString *,NSString *> *)attributeDict
2 {
3     if ([elementName isEqualToString:@"entry"]) {
4         self.flag = YES;
5     }
6     self.webElementNode = [NSString stringWithString:elementName];
7     self.contentStr = [[NSString alloc]init];
8 }

这个方法是开始遍历节点,由于在iTunes源中,我们需要的数据在众多的<entry></entry>标签中,我选用的是title,而在开始出也有一个<title>iTunes Store: Top Songs</title>,而避免这个不必要的title,则需要加一个BOOL来判断执行,当开始遍历entry标签时,flag=YES。如果你的XML文件中没有你认为多余的标签时,请删除关于flag的代码,由于网上没有写过关于这种情况的判断,所以我写了一下,在你有这种多余某几个标签,而又没有attribute标示时,就像我做示例的这个XML,如果你用的NSXMLParser这种不像NSJSONSerializtion那种处理JSON简单明了的方法,则需要这样判断一下。

argument elementName是标签名称,由于需要将指定标签名称用于下一个delegate方法,所以赋给一个接口变量,self.contentStr代表标签内内容,argument attributeDict是属性的集合,当需要进行特定节点的处理or特定顺序排序时使用,进入下一个

1 - (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string
2 {
3     if (self.flag == YES) {
4         if ([self.webElementNode isEqualToString:@"title"]) {
5             self.contentStr = [NSString stringWithString:string];
6         }
7     }
8 }

这个方法用于找到了特定标签时的处理,注意是开始时,此处当flag==yes也就是属于entry之后的标签时,将title的内容赋予contentStr,NSXMLParser不会在没有属性标示时判断节点的从属级关系,只是从上到下一个一个走。

1 - (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName
2 {
3     if ([elementName isEqualToString:@"title"]) {
4         [self.xmlResult addObject:self.contentStr];
5     }
6     
7 }

这个方法是全局的关键,对最后的数据进行操作要在这个方法里进行,意思是当走到结束标签时做的处理,为什么不在上一个delegate方法做最后处理呢?上一个方法的名字看起来更像是需要做最后处理的方法啊。如果做下调试以及想到标签的从属关系,不难想到。

上一个方法是开始的那个时刻做的方法,如果在其中做处理,由于没有关闭标签的限制(因为最后一个方法才是提到关闭标签的方法),会一直输出数据,由于不能进入其他同等级or上级的标签内,所以走到比其高等级标签时,就会输出空行,某些本身标签内的数据也会以一行一个character的形式输出。

所以最后一个方法意思是解析到关闭标签了,这时self.contentStr就表示整个标签内的数据,网上大部分说的将内容string做appendingString处理有时并不是必要的。至此将其加入结果数据,解析完成。以下是viewController的代码

 1 #import "ViewController.h"
 2 #import "XMLProcess.h"
 3 
 4 @interface ViewController ()<UITableViewDataSource, UITableViewDelegate, NSXMLParserDelegate>
 5 @property (nonatomic) UITableView *tableView;
 6 
 7 @property (nonatomic)NSString *webElementNode;
 8 @property (nonatomic)NSString *contentStr;
 9 @property (nonatomic)NSMutableArray *xmlResult;
10 
11 @property (nonatomic)XMLProcess *xmlProcess;
12 @end
13 
14 @implementation ViewController
15 
16 - (void)viewDidLoad {
17     [super viewDidLoad];
18     self.tableView = [[UITableView alloc]initWithFrame:self.view.bounds];
19     [self.tableView setDataSource:self];
20     [self.tableView setDelegate:self];
21     [self.view addSubview:self.tableView];
22     
23     self.xmlProcess = [[XMLProcess alloc]init];
24     
25     [self.xmlProcess.parser parse];
26 }
27 
28 - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
29 {
30     return 1;
31 }
32 
33 - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
34 {
35     return self.xmlProcess.xmlResult.count;
36 }
37 
38 - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
39 {
40     UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"cell"];
41     if (!cell) {
42         cell = [[UITableViewCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"cell"];
43     }
44     cell.textLabel.text = [self.xmlProcess.xmlResult objectAtIndex:indexPath.row];
45     return cell;
46 }
47 
48 - (void)didReceiveMemoryWarning {
49     [super didReceiveMemoryWarning];
50 }
51 
52 @end

注意在25行,当执行[self.parse parse];方法时才开始解析XML文件从而在制定时刻调用指定的delegate方法,调用顺序是123123123,不同于很多构造方法比如上边的构造tableView的方法调用顺序就是111222333.执行结果如图

希望对纯新手有启发

欢迎访问我的个人主页http://www.jiachengzhi.com

 

posted @ 2015-10-27 11:54  ChrisUpForever  Views(677)  Comments(0Edit  收藏  举报