ios-XML文档解析之SAX解析

  • 首先SAX解析xml
    *xml文档的格式特点是节点,大体思路是把每个最小的子节点作为对象的属性,每个最小子节点的'父'节点作为对象,将节点转化为对象,输出.

  • 每个节点都是成对存在的,有开始有结束.有始有终

  • 搭建本地服务器,并创建异步请求访问本地服务器中的数据video.xml

#import "ViewController.h"
#import "Video.h"
@interface ViewController () <NSXMLParserDelegate>
//vidios用来保存video对象的数组
@property (nonatomic, strong) NSMutableArray *videos;
//当前创建的video对象
@property (nonatomic, strong) Video *currentVideo;
//存储当前节点的内容
@property (nonatomic, copy) NSMutableString *mString;
@end

  • currentVideo每次寻找到节点名字为video的时候创建一个video对象,并添加到数组videos中.
  • mString是开始与结束节点之间的内容用mString来存储.在找到结束节点之后把mString通过setvalue...forkey...的方式(kvc)赋值给video对应的属性.
  • 通过SAX(NSXMLParser)方式解析xml文件要遵守协议NSXMLParserDelegate,设置代理.=self

@implementation ViewController
//懒加载--->初始化videos
- (NSMutableArray *)videos {
    if (_videos == nil) {
        _videos = [NSMutableArray arrayWithCapacity:10];
    }
    return _videos;
}
//初始化mString
- (NSMutableString *)mString {
    if (_mString == nil) {
        _mString = [NSMutableString string];
    }
    return _mString;
}



- (void)viewDidLoad {
    [super viewDidLoad];
        
    [self loadXML];

}

//异步请求xml
- (void)loadXML {
    //异步请求服务器的xml文件
    NSURL *url = [NSURL URLWithString:@"http://127.0.0.1/videos.xml"];
    NSURLRequest *request = [NSURLRequest requestWithURL:url];
    [NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse * _Nullable response, NSData * _Nullable data, NSError * _Nullable connectionError) {
        if (connectionError) {
            NSLog(@"连接错误 %@",connectionError);
            return;
        }
        NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
        if (httpResponse.statusCode == 200 || httpResponse.statusCode == 304) {
            //解析数据
            NSXMLParser *parser = [[NSXMLParser alloc] initWithData:data];
            //设置代理
            parser.delegate = self;
            //开始执行代理的方法,代理的方法中开始解析的
            [parser parse];
        }else{
            NSLog(@"服务器内部错误");
        }
    }];
}

  • 创建video分类
#import <Foundation/Foundation.h>

@interface Video : NSObject

@property (nonatomic, copy) NSNumber *videoId;
@property (nonatomic, copy) NSString *name;
@property (nonatomic, copy) NSNumber *length;
@property (nonatomic, copy) NSString *videoURL;
@property (nonatomic, copy) NSString *imageURL;
@property (nonatomic, copy) NSString *desc;
@property (nonatomic, copy) NSString *teacher;

@property (nonatomic, readonly) NSString *time;

- (instancetype)initWithDict:(NSDictionary *)dict;
+ (instancetype)videoWithDict:(NSDictionary *)dict;

@end


  • .m文件
#import "Video.h"

@implementation Video

- (instancetype)initWithDict:(NSDictionary *)dict {
    self = [super init];
    if (self) {
        [self setValuesForKeysWithDictionary:dict];
    }
    return self;
}

+ (instancetype)videoWithDict:(NSDictionary *)dict {
    return [[self alloc] initWithDict:dict];
}

- (NSString *)time {
    int len = self.length.intValue;
    
    return [NSString stringWithFormat:@"%02d:%02d:%02d", len / 3600, (len % 3600) / 60, (len % 60)];
}

- (NSString *)description {
    return [NSString stringWithFormat:@"<%@ : %p> { videoId : %@, name : %@, length : %@, videoURL : %@, imageURL : %@, desc : %@, teacher : %@}", [self class], self, self.videoId, self.name, self.length, self.videoURL, self.imageURL, self.desc, self.teacher];
}

@end


  • 重写- (NSString *)description 为了在解析结束后打印出来看结果是否正确

  • 开始写代理方法


//1 开始解析文档
- (void)parserDidStartDocument:(NSXMLParser *)parser {
    NSLog(@"1 开始解析文档  %@",[NSThread currentThread]);
}
//2 找开始节点
- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName attributes:(NSDictionary<NSString *,NSString *> *)attributeDict {
    //elementName 节点的名称
    //attributeDict  标签的属性
    NSLog(@"2 找开始节点  %@--%@",elementName,attributeDict);
    
    //如果是video标签,创建video对象
    if ([elementName isEqualToString:@"video"]) {
        self.currentVideo = [[Video alloc] init];
        self.currentVideo.videoId = @([attributeDict[@"videoId"] intValue]);
        
        //添加到数组中
        [self.videos addObject:self.currentVideo];
        
    }
    
    
}

  • 开始查找节点,如果节点名称==video则创建对象self.currentVideo = [[Video alloc] init];并且加入可变数组videos中.

//3 找节点之间的内容
- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string {
    //
    NSLog(@"3 找节点之间的内容 %@",string);
    //拼接字符串
    [self.mString appendString:string];
    
}

  • 将开始于结束节点之间的内容用可变字符串mString保存起来.当找到结束节点名字后,利用结束节点名通过setvalue...forkey...方式把mString的内容赋值给对应的video(或者说self.currentVideo)的属性.

//4 找结束节点
- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName {
    //elementName 节点名称
    NSLog(@"4 找结束节点 %@",elementName);
    
    //判断标签是否是对应的属性
    if (![elementName isEqualToString:@"video"] && ![elementName isEqualToString:@"videos"]) {
       
        //kvc 赋值的过程就是地址指向的过程,不会做类型转换
        [self.currentVideo setValue:self.mString forKey:elementName];

    }
    
    //清空可变字符串
    [self.mString setString:@""];
}

  • 每次用完mString都要清空,不然前面的节点内容都会拼接到正在解析的节点的内容上.

//5 结束解析文档
- (void)parserDidEndDocument:(NSXMLParser *)parser {
    NSLog(@"5 结束解析文档");
    NSLog(@"%@",self.videos);
}
//6 解析出错
- (void)parser:(NSXMLParser *)parser parseErrorOccurred:(NSError *)parseError {
    NSLog(@"出错");
}


  • 文档解析完成.思路--->找最小子节点--->把最小子节点的父节点作为一个对象,最小子节点作为属性.通过读取到节点的名称,用kvc方式对对象的属性进行赋值.

  • 文档输出由于汉字编码是Unicode方式需要对其转换.

  • 下面演示不管是数组还是字典只要整体输出都会有编码问题

  • 解决方法:重写数组与字典的- (NSString *)descriptionWithLocale:(id)locale,写完不用导入头文件.

  • 创建NSObject 分类Log

  • 用block循环逐个输出解决汉字问题

小结:XML和json解析的区别
XML:
* 优点:1>所有的语言中格式统一,数据共享比较方便
* 缺点: 1>XML格式文件庞大,格式复杂,解析起来比较麻烦!压缩比例小。
JOSN:
* 优点:
* 1>数据结构简单,易于读写,易于解析,压缩比例大。
* 2>支持多种语言,java,ios,javascript,php等语言。
* 3>前端和后台维护简单方便。
* 缺点:1>JSON出现的时间比XML晚。移动端开发推荐使用json!!!

原文地址:https://www.cnblogs.com/adampei-bobo/p/5301911.html