美文网首页
ios - NSXMLParser XML解析

ios - NSXMLParser XML解析

作者: lizhi_boy | 来源:发表于2019-03-28 19:37 被阅读0次

    目前大部分的的app的请求数据的类型都为json数据,对于json数据我们借助MJExtension或者其它优秀的第三方解析类库进行解析,即可得到显示的数据内容。但有时候也会遇到用xml数据的情况,对于xml数据的解析,应该怎样操作呢?下面我写一个简单的demo来实现一下。

    1、看一下要解析的数据

    <paper>
        <info>
            <paperId>t123456</paperId>
            <paperName paperpath = '试卷路径'>模拟试卷一</paperName>
            <partNumber>1</partNumber>
            <sectionNumber>1</sectionNumber>
            <questionNumber>3</questionNumber>
            <totalScore>3</totalScore>
        </info>
        <sectionList>
            <sectionItem>
                <sectionFolder>听对话回答问题1/听对话回答问题1</sectionFolder>
                <sectionTotalScore>6</sectionTotalScore>
            </sectionItem>
            <sectionItem>
                <sectionFolder>听对话回答问题2/听对话回答问题2</sectionFolder>
                <sectionTotalScore>6</sectionTotalScore>
            </sectionItem>
            </sectionList>
    </paper>
    

    分析一下:info标签内容数据,可以看作是一个字典(或类) 。sectionList标签的内容,可以看作一个数组里包含着两个元素(字典或类)。分析清楚后就可以为解析xml数据时的提供一个方向了。

    2、构建解析类

    NSXMLParser 实现的是sax方法解析xml文件。下面科普一下saxdom的解析方法有什么优缺点?
    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 ()<NSXMLParserDelegate>
    /** 定义一个解析类 */
    @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数据解析会首先读取到的节点是<paper>[从上往下逐行逐字进行解析]。此外获取节点中的属性值也在此方法中操作,比如要获取<paperName>节点的的属性paperpath的值。(demo中在此方法中对<info>节点中清除了currentParserArraycurrentParserArray内的数据,目的是保证解析数据的准确性)。

    //开始解析到某个节点是调用
    -(void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName attributes:(NSDictionary<NSString *,NSString *> *)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数据解析会首先读取到的节点是</paperId>,[从上往下逐行逐字进行解析])

    //结束解析某个节点时调用

    -(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);
            }
        }];
    }
    
    demo打印信息.png

    接下来就是字典转模型的步骤了,这应该都玩的挺6的了,就不多说了。

    总结:1、要清楚xml数据的结构 2、要清楚NSXMLParser解析类代理方法的调用顺序。明白这两点就可以很快的把xml数据解析出来了。


    都看到这里了,点个喜欢❤️不过分吧

    相关文章

      网友评论

          本文标题:ios - NSXMLParser XML解析

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