XML简介
XML
出身名门,W3C
制定,微软和IBM曾经共同大力推荐过的数据格式.XML
指可扩展标记语言(Extensible Markup Language).- 被设计用来传输和存储数据.在以前是常用的数据格式.
- 对比 :
HTML
超文本标记语言,是设计用来显示网页的.并不是保存数据和传输的方式.
XML格式
XML
由标记组成,xml的标记是可扩展的,没有预定义.<tag>
这就是标记.有开始标记和结束标记.例如<tag>
表示开始标记,</tag>
表示结束标记.是成对出现的.-
注意 : 一个
XML
文件有且只有一个节点(标签).
XML演练
- 新建一个empty空文件,取名demo.xml
<!-- 描述几个学生 -->
<Class>
<!-- 用name和age标签描述学生 -->
<student>
<name>卡特琳娜</name>
<age>21</age>
</student>
<student>
<name>牛头</name>
<age>22</age>
</student>
<!-- 标签中定义属性也可以描述学生 -->
<student name="卡尔萨斯" age="23">
</studet>
<!-- 标签中定义属性描述学生的简写 -->
<student name="瑞兹" age="24" />
</Class>
XML文件解析方式
- 如果想获取到
XML
文件中保存的数据,需要将XML
文件中的数据解析出来. SAX
方式解析和DOM
方式解析
SAX方式解析XML
- 苹果官方提供的原生的解析
XML
文件的方式 - 在iOS上解析
XML
文件的方式. - 速度快,内存占用小.
- 是只读的,只能读取
XML
文件数据不能做修改操作.
DOM方式解析XML
- 是在
MAC
使用的解析方式. - 注意 : 内存消耗极大,不适用于手机.
- 可以读写
XML
文件. - iPhone无法直接使用
DOM
方式解析XML
. - 如果要在iPhone上使用
DOM
方式解析XML
文件,可以使用第三方框架.
XML解析之SAX解析
SAX方式解析XML步骤
- 创建
XML
文件解析器. - 遵守解析器的代理协议.
- 启动解析器,开始解析.
XML
文件的解析是在代理方法中完成的.需要实现以下代理方法.
1.开始解析`XML`文件.
2.找开始节点(标签).
3.找节点之间的内容.
4.找结束节点.
5.结束解析`XML`文件.
6.监听`XML`文件是否解析出错.
SAX方式解析XML实现
- 1.发送网络请求,获取二进制的
XML
数据.
/// 下载二进制的XML数据
- (void)downloadXML
{
// 获取URL
NSURL *url = [NSURL URLWithString:@"http://localhost/videos.xml"];
// 创建请求
NSURLRequest *request = [NSURLRequest requestWithURL:url];
// 发送异步请求,请求自建服务器中的demo.json数据
[NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse * _Nullable response, NSData * _Nullable data, NSError * _Nullable connectionError) {
// 判断链接是否错误
if (connectionError) {
NSLog(@"链接错误 %@",connectionError);
return;
}
// 检查响应体是否有错
NSHTTPURLResponse *HTTPURLResponse = (NSHTTPURLResponse *)response;
if (HTTPURLResponse.statusCode == 200 || HTTPURLResponse.statusCode == 304) {
NSLog(@"二进制的XML数据 %@",data);
} else {
NSLog(@"服务器内部错误");
return;
}
}];
}
- 2.拿到二进制的
XML
数据之后,创建XML
解析器,设置代理,启动解析器.
// 创建XML解析器
NSXMLParser *XMLParser = [[NSXMLParser alloc] initWithData:data];
// 设置解析器代理协议
XMLParser.delegate = self;
// 开始解析
[XMLParser parse];
- 3.实现代理方法
// 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);
}
// 3.找节点之间的内容
- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string
{
// string : 节点之间的内容.
NSLog(@"3.找节点之间的内容 %@",string);
}
// 4.找结束节点
- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName
{
NSLog(@"4.找结束节点 %@",elementName);
}
// 5.结束解析文档
- (void)parserDidEndDocument:(NSXMLParser *)parser
{
NSLog(@"5.结束解析文档");
}
// 6.监听解析是否出错
- (void)parser:(NSXMLParser *)parser parseErrorOccurred:(NSError *)parseError
{
NSLog(@"6.监听解析是否出错 %@",parseError);
}
结论
- 通过代理方法中的NSLog,可以了解到解析
XML
文件中数据的过程. - 以上步骤,2--->3--->4,会不断循环,一直到所有数据解析完成.
节点之间的内容
并不是一次就能找全的,有时候需要循环多次才能找全.- 如果解析数据量非常大的
XML
文件是很耗时的.当在哪个线程设置了代理协议,就在哪个线程中解析.
XML解析之转模型
准备模型类
- .h文件中的声明
@interface VideoModel : NSObject
/// 视频编号
@property (nonatomic,copy) NSString *videoId;
/// 视频名称
@property (nonatomic,copy) NSString *name;
/// 视频长度
@property (nonatomic,copy) NSString *length;
/// 视频链接
@property (nonatomic,copy) NSString *videoURL;
/// 视频图标
@property (nonatomic,copy) NSString *imageURL;
/// 视频标题
@property (nonatomic,copy) NSString *desc;
/// 视频作者
@property (nonatomic,copy) NSString *teacher;
/// 字典转模型 : 解析XML的时候,没有字典,所以这个不需要
//+ (instancetype)videoWithDict:(NSDictionary *)dict;
@end
/*
<video videoId="1">
<name>01.C语言-语法预览</name>
<length>320</length>
<videoURL>/itcast/videos/01.C语言-语法预览.mp4</videoURL>
<imageURL>/itcast/images/head1.png</imageURL>
<desc>C语言-语法预览</desc>
<teacher>李雷</teacher>
</video>
*/
- .m文件中的实现
@implementation VideoModel
/// 无用处
+ (instancetype)videoWithDict:(NSDictionary *)dict
{
// 创建模型对象
VideoModel *v = [[VideoModel alloc] init];
// KVC 字典转模型
[v setValuesForKeysWithDictionary:dict];
// 返回模型对象
return v;
}
/// 打印模型的详细内容
- (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
解析XML文件
将
XML
文件中的数据读取出来,转换成可以直接使用的模型数据.
XML
数据转模型数据的准备工作
@interface ViewController () <NSXMLParserDelegate>
/// video对应的模型对象
@property (nonatomic,strong) VideoModel *currentVideo;
/// 拼接节点之间内容的字符串
@property (nonatomic,copy) NSMutableString *stringM;
/// 模型数组
@property (nonatomic,strong) NSMutableArray *videoM;
@end
@implementation ViewController
- (NSMutableString *)stringM
{
if (_stringM == nil) {
_stringM = [NSMutableString string];
}
return _stringM;
}
- (NSMutableArray *)videoM
{
if (_videoM == nil) {
_videoM = [NSMutableArray array];
}
return _videoM;
}
实现代理方法
- 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节点(标签),就创建一个对应的模型对象
if ([elementName isEqualToString:@"video"]) {
// 创建video标签对应的模型对象
self.currentVideo = [[VideoModel alloc] init];
// 拿到标签的属性,可以给模型的videoId属性赋值
self.currentVideo.videoId = attributeDict[@"videoId"];
// 将模型对象添加到模型数组中
[self.videoM addObject:self.currentVideo];
}
}
- 3.找节点之间的内容
- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string
{
// string : 节点之间的内容.(内容不会一次性找全)
NSLog(@"3.找节点之间的内容 %@",string);
// 拼接节点之间的字符串
[self.stringM appendString:string];
}
-
4.找结束节点
-
注意 : 在KVC给模型的属性赋值的过程中,一定要排除
videos
和video
两个节点.因为这两个节点不需要转模型,且在模型类中没有对应的属性值.不排除掉,KVC在赋值过程中程序会崩溃.
- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName
{
NSLog(@"4.找结束节点 %@",elementName);
// 找到了结束节点,说明节点之间的内容已经获取到了,就可以给模型的属性赋值了
// 如果要在这个方法里面获取到模型,那么模型需要定义成全局的
// 一定要排除`videos`和`video`两个节点,这两个节点不需要转模型
if (![elementName isEqualToString:@"videos"]&&![elementName isEqualToString:@"video"]) {
[self.currentVideo setValue:self.stringM forKey:elementName];
}
}
- 5.结束解析文档
- (void)parserDidEndDocument:(NSXMLParser *)parser
{
NSLog(@"5.结束解析文档");
// 打印全部模型
NSLog(@"%@",self.videoM);
}
- 6.监听解析是否出错
- (void)parser:(NSXMLParser *)parser parseErrorOccurred:(NSError *)parseError
{
NSLog(@"6.监听解析是否出错 %@",parseError);
}
结论
-
不能按照
字典转模型
的逻辑来处理XML转模型
.解析XML
数据的过程中没有字典. -
每个
video标签
就是描述一个视频的.每个模型对象
也是用来描述一个视频的.所以每找到一个video标签
就要创建一个对应的模型对象.
模型对象属性赋值会在多个代理方法中进行赋值.所以需要定义成全局的模型对象
. -
节点之间的内容
有时候不会一次性就找全,所以需要多次拼接字符串
,而且要在找结束节点
代理方法中使用,所以也要定义成全局的可变字符串
. -
为了能够打印出所有的模型对象,需要将模型对象添加到数组中.
清空供拼接的可变字符串
// 4.找结束节点
- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName
{
NSLog(@"4.找结束节点 %@",elementName);
// 找到了结束节点,说明节点之间的内容已经获取到了,就可以给模型的属性赋值了
// 如果要在这个方法里面获取到模型,那么模型需要定义成全局的
// 一定要排除`videos`和`video`两个节点,这两个节点不需要转模型
if (![elementName isEqualToString:@"videos"]&&![elementName isEqualToString:@"video"]) {
[self.currentVideo setValue:self.stringM forKey:elementName];
}
// 给模型属性赋值完成之后,需要清空字符串.
[self.stringM setString:@""];
}
XML解析之DOM解析
- 仅做了解.
DOM简介
DOM
: Document Object ModelDOM
: 文档对象模型 (文档树
模型)- 一次性把
XML
文件全部加载到内存,内存消耗大,适合读写比较小的XML
文件. - 之前的
SAX
方式只能读取XML
文件,但是DOM
方式可以修改XML
文件,包括添加删除修改节点. - iOS系统默认不支持DOM解析,在iOS系统下解析DOM的话需要第三方框架.
- 第三方框架 : GData/KissXML(XMPP中使用此框架)
GData中类的关系分析
集成GData框架
- 导入
GDataXMLNode
第三方文件. - 解决GData的报错.
GData解析XML的步骤
1.获取XML文档.
2.获取XML文档的根标签.
3.遍历根标签,获取根标签的子标签(video标签),创建video标签对应的模型对象.
4.遍历video标签,获取video标签的子标签,给模型对象的属性赋值.
5.遍历video标签,获取video标签的属性,给模型对象的videoId属性赋值.
GData解析XML的实现
- 模型类没有变化,还是SAX方式解析的那个模型类.
- 从网络中获取到XML文件的二进制数据.
- 代码实现 :
/// DOM方式解析XML
- (void)DOM:(NSData *)data
{
// 1.获取XML文档
GDataXMLDocument *XMLDocument = [[GDataXMLDocument alloc] initWithData:data error:NULL];
// 2.获取XML文档的根标签
GDataXMLElement *rootElement = XMLDocument.rootElement;
// 创建模型数组,将模型对象添加到模型数组中
NSMutableArray *videoM = [NSMutableArray array];
// 3.遍历根标签,获取根标签的子标签(video标签),创建video标签对应的模型对象
for (GDataXMLElement *videoElement in rootElement.children) {
// 创建video标签对应的模型对象
VideoModel *video = [[VideoModel alloc] init];
// 将模型对象添加到模型数组中
[videoM addObject:video];
// 4.遍历video标签,获取video标签的子标签,给模型对象的属性赋值
for (GDataXMLElement *subElement in videoElement.children) {
// KVC给模型的属性赋值
[video setValue:subElement.stringValue forKey:subElement.name];
}
// 5.遍历video标签,获取video标签的属性,给模型对象的videoId属性赋值
NSLog(@"%@",[videoElement.attributes class]);
for (GDataXMLNode *attr in videoElement.attributes) {
// KVC给模型的videoId属性赋值
[video setValue:attr.stringValue forKey:attr.name];
}
}
NSLog(@"%@",videoM);
}