目前大部分的的app的请求数据的类型都为json数据,对于json数据我们借助MJExtension或者其它优秀的第三方解析类库进行解析,即可得到显示的数据内容。但有时候也会遇到用xml数据的情况,对于xml数据的解析,应该怎样操作呢?下面我写一个简单的demo来实现一下。
1、看一下要解析的数据
t123456
模拟试卷一
1
1
3
3
听对话回答问题1/听对话回答问题1
6
听对话回答问题2/听对话回答问题2
6
分析一下:info
标签内容数据,可以看作是一个字典(或类) 。sectionList
标签的内容,可以看作一个数组里包含着两个元素(字典或类)。分析清楚后就可以为解析xml数据时的提供一个方向了。
2、构建解析类
NSXMLParser
实现的是sax
方法解析xml文件。下面科普一下sax
和dom
的解析方法有什么优缺点?
dom
实现的原理是把整个xml文档一次性读出,放在一个树型结构里。在需要的时候,查找特定节点,然后对节点进行读或写。
优点
:实现简单,读写平衡;
缺点
:比较占内存,因为他要把整个xml文档都读入内存,文件越大,缺点就越明显。
sax
的实现方法和dom
不同。它只在xml文档中查找特定条件的内容,并且只提取需要的内容。
优点
:占用内存小,灵活。
缺点
:编写实现上比较复杂。
(1)、构建一个解析工具类
ParserManager.h
@interface ParserManager : NSObject
/**
初始化方法
@param data 解析的xml数据
@return ParserManager
*/
-(instancetype)initWithData:(NSData *)data;
/**
解析完成时调用
@param completion 完成时回调(demo中是把解析出来的数据返回了)
*/
-(void)finishedParser:(void(^)(NSArray *modeArray,NSDictionary *modeDic,NSError *error))completion;
@end
ParserManager.m
定义一个NSXMLParser类,以及一些必要的属性,属性的定义可以根据xml的数据结构来定义
@interface ParserManager ()
/** 定义一个解析类 */
@property(nonatomic,strong) NSXMLParser *parser;
/** 记录当前解析的节点 */
@property(nonatomic,strong) NSString *currentElementName;
/** 记录当前解析的类的属性 */
@property(nonatomic,strong) NSMutableDictionary *currentParserDic;
/** 记录当前解析的类的数组 */
@property(nonatomic,strong) NSMutableArray *currentParserArray;
/** 接收解析出来的dic数据 */
@property(nonatomic,strong) NSMutableDictionary *modeDic;
/** 接收解析出来的array数据 */
@property(nonatomic,strong) NSMutableArray *modeArray;
/** 解析完成的回调 */
@property(nonatomic,copy) void(^parserFinished)(NSArray *demoArray,NSDictionary *demoDic,NSError *error);
@end
初始化以及懒加载
-(instancetype)initWithData:(NSData *)data{
if (self = [super init]) {
self.parser = [[NSXMLParser alloc] initWithData:data];
self.parser.delegate = self;
//开始解析
[self.parser parse];
}
return self;
}
-(NSMutableDictionary *)currentParserDic{
if (!_currentParserDic) {
_currentParserDic = [NSMutableDictionary dictionary];
}
return _currentParserDic;
}
-(NSMutableArray *)currentParserArray{
if (!_currentParserArray) {
_currentParserArray = [NSMutableArray array];
}
return _currentParserArray;
}
(2)、执行NSXMLParserDelegate代理方法
接下来的事情就简单了,只需在NSXMLParserDelegate
代理方法中进行操作即可。
执行解析方法会调用时会调用:
-(void)parserDidStartDocument:(NSXMLParser *)parser{
// NSLog(@"开始解析");
}
读取到节点时会执行,按照demo的xml数据解析会首先读取到的节点是
[从上往下逐行逐字进行解析
]。此外获取节点中的属性值也在此方法中操作,比如要获取
节点的的属性paperpath
的值。(demo中在此方法中对
节点中清除了currentParserArray
和currentParserArray
内的数据,目的是保证解析数据的准确性)。
//开始解析到某个节点是调用
-(void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName attributes:(NSDictionary *)attributeDict{
self.currentElementName = elementName;
//当节点解析到info时开始准备一个字典接收解析出来的数据
if ([elementName isEqualToString:@"info"]) {
[self.currentParserArray removeAllObjects];
[self.currentParserDic removeAllObjects];
}
//同理,当节点解析到sectionList时开始准备一个数组接收解析出来的数据
if ([elementName isEqualToString:@"sectionList"]) {
[self.currentParserArray removeAllObjects];
[self.currentParserDic removeAllObjects];
}
//获取节点中的属性值
if ([elementName isEqualToString:@"paperName"]) {
NSLog(@"%@",attributeDict[@"paperpath"]);
}
}
获取节点数据
//取值
-(void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string{
//didEndElement方法执行后,即节点解析结束后还会调用此方法,解析出来的string的值为"\n ",并不是想要显示数据所以要做过滤操作
if (self.currentElementName && [self isValidationString:string]) {
[self.currentParserDic setValue:string forKey:self.currentElementName];
[self.currentParserArray addObject:self.currentParserDic];
}
}
节点解析结束。注意:self.currentElementName = nil;
(按照demo的xml数据解析会首先读取到的节点是,[
从上往下逐行逐字进行解析
])
//结束解析某个节点时调用
-(void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName{
//把数据取出
if ([elementName isEqualToString:@"info"]) {
self.modeDic = self.currentParserDic.copy;
}
if ([elementName isEqualToString:@"sectionList"]) {
self.modeArray = self.currentParserArray.copy;
}
self.currentElementName = nil;
}
结束整个xml文件解析时调用
//结束整个xml文件解析时调用
-(void)parserDidEndDocument:(NSXMLParser *)parser{
//放在主线程中处理是为了解决self.parserFinished未被初始化的问题
dispatch_async(dispatch_get_main_queue(), ^{
if (self.parserFinished) {
self.parserFinished(self.modeArray,self.modeDic,nil);
}
});
}
解析出错时调用的代理方法
-(void)parser:(NSXMLParser *)parser parseErrorOccurred:(NSError *)parseError{
dispatch_async(dispatch_get_main_queue(), ^{
if (self.parserFinished) {
self.parserFinished(nil,nil,parseError);
}
});
}
-(void)parser:(NSXMLParser *)parser validationErrorOccurred:(NSError *)validationError{
dispatch_async(dispatch_get_main_queue(), ^{
if (self.parserFinished) {
self.parserFinished(nil,nil,validationError);
}
});
}
(3)、外调方法以及私有方法的实现
-(void)finishedParser:(void (^)(NSArray *, NSDictionary *,NSError*))completion{
self.parserFinished = completion;
}
//过滤不必要的数据
-(BOOL)isValidationString:(NSString *)string{
if ([string hasPrefix:@"\n"]){
return NO;
}else{
return YES;
}
}
(4)、控制器调用并查看效果
- (void)viewDidLoad {
[super viewDidLoad];
NSString *filePath = [[NSBundle mainBundle] pathForResource:@"demo" ofType:@"xml"];
NSData *data = [NSData dataWithContentsOfFile:filePath];
ParserManager *parser = [[ParserManager alloc] initWithData:data];
__weak typeof(self) weakSelf = self;
[parser finishedParser:^(NSArray *modeArray, NSDictionary *modeDic, NSError *error) {
if (!error) {
weakSelf.contentText.text = [NSString stringWithFormat:@"%@\n%@",modeArray,modeDic];
}else{
NSLog(@"error:%@",error.localizedDescription);
}
}];
}
接下来就是字典转模型的步骤了,这应该都玩的挺6的了,就不多说了。
总结:1、要清楚xml数据的结构 2、要清楚NSXMLParser解析类代理方法的调用顺序。明白这两点就可以很快的把xml数据解析出来了。
都看到这里了,点个喜欢
❤️不过分吧