iOS开发之解析XML文件

作者: 与佳期 | 来源:发表于2015-10-20 21:21 被阅读5195次

读写XML文档,目前流行的两种模式:SAX和DOM。
1.SAX是一种基于事件驱动的解析模式。解析XML的时候,程序从上到下读取XML文档,如果遇到开始标签、结束标签、属性等,就会触发相应的事件。

  • 优点:解析速度快,iOS重点推荐使用SAX模式解析
  • 缺点:只能读取XML文档,不能写入XML文档

2.DOM模式是将XML文档作为一颗树状结构进行分析,提供获取节点的内容,以及相关属性,或是新增、删除和修改节点的内容。XML解析器在加载XML文件以后,DOM将XML文件的元素视为一个树状结构的节点,一次性读入到内存中。

  • 优点:能够修改XML文档
  • 缺点:如果文档比较大,解析速度就会变慢

NSXML是iOS SDK自带的,也是苹果默认的解析框架,采用SAX模式解析,它是SAX解析模式的代表。本文主要介绍采用NSXML解析XML文件。

采用NSXML解析XML文件

NSXML框架中的核心是NSXMLParser和它的代理NSXMLParserDelegate,NSXMLParserDelegate中的常用方法有:

// 在文档开始的时候触发
- (void)parserDidStartDocument:(NSXMLParser *)parser;

// 在文档出错的时候触发,该方法一般在调试阶段使用,实际发布时意义不大
- (void)parser:(NSXMLParser *)parser parseErrorOccurred:(NSError *)parseError;

// 遇到一个开始标签时触发,其中namespaceURI部分是命名空间,qualifiedName是限定名,attributes是字典类型的属性集合
- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName attributes:(NSDictionary *)attributeDict;

// 遇到字符串时触发
- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string;

// 遇到结束标签时触发
- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName;

// 遇到文档结束时触发
- (void)parserDidEndDocument:(NSXMLParser *)parser;

代码演示解析XML文件

Notes.xml文件如下:

<?xml version="1.0" encoding="UTF-8"?>
<Notes>
  <Note id="1">
  <CDate>2015-10-20</CDate>
    <Content>早上7点钟起床到实验室</Content>
    <UserID>xiaolou</UserID>
  </Note>
  <Note id="2">
    <CDate>2015-10-21</CDate>
    <Content>学习iOS开发</Content>
    <UserID>xiaolou</UserID>
  </Note>
  <Note id="3">
    <CDate>2015-10-22</CDate>
    <Content>学习iOS开发之解析XML文件</Content>
    <UserID>xiaolou</UserID>
  </Note>
  <Note id="4">
    <CDate>2015-10-23</CDate>
    <Content>采用NSXML解析XML文件</Content>
    <UserID>xiaolou</UserID>
  </Note>
  <Note id="5">
    <CDate>2015-10-24</CDate>
    <Content>解析速度快,iOS重点推荐使用SAX模式解析</Content>
    <UserID>xiaolou</UserID>
  </Note>
  <Note id="6">
    <CDate>2015-10-25</CDate>
    <Content>该投简历了,希望能找个好工作</Content>
    <UserID>xiaolou</UserID>
  </Note>
</Notes>

新建一个类NotesXMLParser,继承自NSObject,遵守委托协议NSXMLParserDelegate,并拥有相关属性与方法。

NotesXMLParser.h文件:
#import <Foundation/Foundation.h>
@interface NotesXMLParser : NSObject <NSXMLParserDelegate>
// 解析出的数据内部是字典类型
@property (strong, nonatomic) NSMutableArray *notes;
// 当前标签的名字
@property (strong, nonatomic) NSString *currentTagName;

// 开始解析
- (void)start;
@end

注:定义currentTagName属性的目的是:在触发开始标签方法和结束标签方法 期间临时存储正在解析的元素名,在方法(parser:foundCharacters:)触发时,能够知道目前解析器处于哪个元素之中

NotesXMLParser.m文件中实现start方法:
- (void)start {
    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];
    NSLog(@"解析完成。。。");
}

注:NSXMLParser是解析类,它有3个构造方法
1)- (instancetype)initWithContentsOfURL:(NSURL *)url;
2)- (instancetype)initWithData:(NSData *)data;
3) - (instancetype)initWithStream:(NSInputStream *)stream;

实现NSXMLParserDelegate代理方法:

// 文档开始的时候触发
- (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 {
    // elementName是正在解析的元素的名字
    _currentTagName = elementName;
    // 如果元素名字为Note,取出它的属性id
    if ([_currentTagName isEqualToString:@"Note"]) {
        // 属性在attributeDict参数中传递过来,它是一个字典类型,其中的键的名字就是属性的名字,值是属性的值
        NSString *_id = [attributeDict objectForKey:@"id"];
        NSMutableDictionary *dict = [NSMutableDictionary new];
        [dict setObject:_id forKey:@"id"];
        [_notes addObject:dict];
    }
}

// 遇到字符串时候触发,该方法是解析元素文本内容主要场所
- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string {
    // 剔除回车和空格
    // stringByTrimmingCharactersInSet:方法是剔除字符方法
    // [NSCharacterSet whitespaceAndNewlineCharacterSet]指定字符集为换行符和回车符
    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 {
    // 清理刚才解析的元素的名字,以便于记录接下来解析的元素的名字
    self.currentTagName = nil;
}

// 遇到文档结束时触发
- (void)parserDidEndDocument:(NSXMLParser *)parser {
    // 使用通知机制将数据通过广播通知投送回表示层
    [[NSNotificationCenter defaultCenter] postNotificationName:@"reloadViewNotification" object:self.notes userInfo:nil];
    // 解析完成,清理成员变量
    self.notes = nil;
}
ViewController.m文件:
#import "ViewController.h"
#import "NotesXMLParser.h"
@interface ViewController () <UITableViewDataSource> {
    UITableView *_tableView;
}
//保存数据列表
@property (nonatomic,strong) NSMutableArray* listData;
@end

@implementation ViewController
- (void)viewDidLoad {
    [super viewDidLoad];
    _tableView = [[UITableView alloc] initWithFrame:self.view.bounds style:UITableViewStylePlain];
    _tableView.dataSource = self;
    [self.view addSubview:_tableView];

    // 注册一个通知,这样ViewController才能在解析完成后接收到投送回来的通知
    // 一旦投送成功就会触发reloadView:方法,在该方法中取出数据,并重新加载表示图
    [[NSNotificationCenter defaultCenter] addObserver:self
                                         selector:@selector(reloadView:)
                                         name:@"reloadViewNotification"
                                         object:nil];
    NotesXMLParser *parser = [NotesXMLParser new];
    // 开始解析
    [parser start];
}

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
    return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    return self.listData.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    static NSString *ID = @"cell";
    UITableViewCell * cell = [tableView dequeueReusableCellWithIdentifier:ID];
    if (!cell) {
        cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:ID];
    }
    NSMutableDictionary*  dict = self.listData[indexPath.row];
    cell.textLabel.text = [dict objectForKey:@"Content"];
    cell.detailTextLabel.text = [dict objectForKey:@"CDate"];
    return cell;
}

#pragma mark - 处理通知
- (void)reloadView:(NSNotification *)notification {
    // 在该方法中取出数据,并重新加载表示图
    NSMutableArray *resList = [notification object];
    self.listData = resList;
    [_tableView reloadData];
}
@end

后记

小白出手,请多指教。
如言有误,还望斧正!
另:本篇文章内容主要学习自关东升老师的《iOS网络编程与云端应用最佳时间》一书中的内容。

相关文章

网友评论

  • 心至靜行至遠:这也就意味着预先知道所有字段名字,一个一个判断?也就是数据模型不一样就需要重写代理方法?
  • iOS_July:入门级的小白,学习中,谢谢分享
  • 910333e0f2fe:亲 如果我的XML是写在远程服务器上,我想在TableView上加一个下拉刷新,那么我在下拉刷新事件中是不是调用[parser start];方法就可以了? :pray:
    与佳期:@JoyT 大神不敢当,我也是小白😬
    910333e0f2fe:@与佳期 受教了!谢谢大神。
    与佳期:@JoyT 理论上是可以的。只不过在现实开发中1:现在用的基本都是json格式 2:应当先做网络请求将数据解析成模型再去用。
  • 9af2fe88efa2:请问,首尾标签中间的部分怎么读取呢?比如
    <Content>解析速度快,iOS重点推荐使用SAX模式解析</Content>
    要读取中间的文字怎么读取呢?NSXMLParser好像只能解析标签的属性啊
    lfb_CD:不是好像,NSXMLParser就是解析不了中间那种文本
    与佳期:@Make__ 实现NSXMLParserDelegate代理方法里有解析代码啊:
    if ([_currentTagName isEqualToString:@"Content"] && dict) {
    [dict setObject:string forKey:@"Content"];
    }
    此时的字符串string就是中间的文字

本文标题:iOS开发之解析XML文件

本文链接:https://www.haomeiwen.com/subject/roazcttx.html