什么是XML?
全称是Extensible Markup Language,可译为可扩展性标记语言。
跟JSON一样,也是一种用于交互的数据格式。
一般也叫做XML文档。
XML举例
<videos>
<video name="小黄人第01部" length="30" />
<video name="小黄人第02部" length="19" />
<video name="小黄人第03部" length="33" />
</videos>
XML语法
一个常见的XML文档一般由以下部分组成
文档声明
元素(Element)
属性(Attribute)
在XML文档的最前面,必须编写一个文档声明,用来声明XML文档的类型。
最简单的声明
<?xml version=“1.0”>
用encoding属性说明文档的编码
<?xml version=“1.0” encoding = “UTF-8”>
XML语法—元素(ELement)
一个元素包括了开始标签和结束标签
拥有元素的内容:<video>小黄人</video>
没有元素的内容:<video></video>
没有内容的简写:<video/>
一个元素可以嵌套若干个子元素(不能出现交叉嵌套)
<videos>
<video>
<name>小黄人</name>
<length>30</length>
</video>
</videos>
交叉嵌套举例如下:
<videos>
<video>
<name>小黄人</name>
<length>30</length>
</videos>
</video>
规范的XML文档最多只有一个根元素,其他元素都是根元素的子孙元素。
XML语法——元素注意:
XML中所有空格和换行,(空格和换行都会当做字符处理)都会当做具体内容处理。
下面两个元素的内容是不一样的。
第一个
<video>小黄人</video>
第二个
<video>
小黄人
</video>
XML语法——属性(Attribute)
一个元素可以拥有很多属性。
<video name=“小黄人第01部”length=“30”>
video元素拥有name和length两个属性。
属性值必须用双引号或单引号括住。
实际上,属性表示的信息也可以用子元素来表示,比如:
<video>
<name>小黄人第01部</name>
<length>30</length>
</video>
注意:一个元素相当于一个Model,一个元素属性相当于Model中的变量。每一个子元素相当于一个Model成员对象。
XML解析:
要想从XML中提取有用的信息,必须学会解析XML
提取name元素里面的内容
<name>小黄人第01部</name>
提取video元素中name和length属性的值
<video name=“小黄人第01部”length=“30”>
XML解析主要有两种
DOM:一次性将整个XML文档加载进内存,比较适合解析小文件。
SAX:从根元素开始,按顺序一个一个元素往下解析(类似于递归),比较适合解析大文件。
苹果自带的XML解析方法以及步骤如下:
第一步:创建解析器。
首先在获得网络数据后,实例化一个XML的解析器进行实现XML的解析。如下所示:
NSXMLParser *parse = [[NSXMLParser alloc] initWithData: data];
第二步:设置代理。并将当前控制器遵守NSXMLParserDelegate协议
parse.delegate = self;
第三步:开始解析。[parse parse];
第四步:实现NSXMLParserDelegate里的五个必不可少的代理方法,如下所示:
(1)打开文档开始准备解析。代理方法如下所示:
- (void)parserDidStartDocument:(NSXMLParser *)parser
(2)开始结点。代理方法如下所示:
- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName attributes:(NSDictionary *)attributeDict
(3)发现结点内容。代理方法如下所示:
- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string
(4)结束结点。代理方法如下所示:
- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName
(5)结束文档。代理方法如下所示:
- (void)parserDidEndDocument:(NSXMLParser *)parser
注意:因为是递归执行,所以除了第一个打开文档和最后一个结束文档外,其他的都是循环执行的。
解析步骤如下:
步骤一、新建一个全局的可变容器,用来存放XML文档转化后的Model。
步骤二、新建一个全局的可变字符串变量,用来存放拼接后的字符串。
步骤三、根据解析内容新建对应的Model模型,并在当前控制器中新建对应的全局Model属性。
步骤四、在代理方法中对数据进行处理。处理大概包括以下几个过程:
4.1、在打开文档开始解析的代理方法(1)中初始化全局容器,即清空全局容器的内容。(为了防止多次解析时重复添加数据)。
4.2、在开始结点的代理方法中对各个结点元素的名称进行判断,并创建对应的Model对象以及设置Model对应的attributeDict字典内的属性。另外还需要把全局的可变字符串置为空(因为开始解析新的结点)。
4.3、在发现节点内容的代理方法(3)中, 需要进行全局可变字符串的拼接操作。
4.4、在结束结点的代理方法(4)中需要对节点名称进行判断分别执行不同的操作(添加对应Model到全局可变容器或者利用KVC对Model的子元素进行赋值)。
4.5、在结束文档的代理方法中可以在主线程更新UI。
思路如下图:
新建工程,步骤如下:
3
4
5
6
7
8
新建模型类如下:
Video.h
#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
Video.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
viewController.m
//
// ViewController.m
// 07-xml解析
//
// Created by apple on 15/1/19.
// Copyright (c) 2015年 apple. All rights reserved.
//
#import "ViewController.h"
#import "Video.h"
@interface ViewController ()<NSXMLParserDelegate>
/**整个xml的数据容器*/
@property(nonatomic,strong)NSMutableArray *videos;
/**第3步拼接字符串*/
@property(nonatomic,strong)NSMutableString *elementString;
/**当前的模型*/
@property(nonatomic,strong) Video *currentVideo;
/**表格的所有数据*/
@property (nonatomic, strong) NSMutableArray *dataList;
@end
@implementation ViewController
- (void)setDataList:(NSMutableArray *)dataList
{
_dataList = dataList;
// 只要重新给表格的数据容器赋值,就刷新
[self.tableView reloadData];
// 隐藏刷新控件
[self.refreshControl endRefreshing];
}
- (NSMutableArray *)videos
{
if (_videos == nil) {
_videos = [NSMutableArray array];
}
return _videos;
}
- (NSMutableString *)elementString
{
if (_elementString == nil) {
_elementString = [NSMutableString string];
}
return _elementString;
}
- (void)viewDidLoad {
[super viewDidLoad];
[self loadData];
}
#pragma mark - 加载数据
- (IBAction)loadData
{
// 1. url
NSURL *url = [NSURL URLWithString:@"http://127.0.0.1/videos.xml"];
// 2. 请求
NSURLRequest *request = [NSURLRequest requestWithURL:url cachePolicy:1 timeoutInterval:2.0f];
// 3. 连接
[NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) {
// xml的解析
// 1. 实例化一个xml的解析器--通过代理来实现xml的解析
NSXMLParser *parse = [[NSXMLParser alloc] initWithData:data];
// 2. 设置代理
parse.delegate = self;
// 3. 解析器开始解析
[parse parse];
}];
}
#pragma mark - 代理方法
//1.打开文档, 准备开始解析
- (void)parserDidStartDocument:(NSXMLParser *)parser
{
NSLog(@"1. 打开文档, 准备开始解析");
// 初始化数组容器, 清空容器,便于多次加载数据
[self.videos removeAllObjects];
}
//2.开始节点
- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName attributes:(NSDictionary *)attributeDict
{
NSLog(@"2.开始节点%@---%@", elementName, attributeDict);
// 如果开始节点的名称 是video,就创建一个对象
if ([elementName isEqualToString:@"video"]) {
self.currentVideo = [[Video alloc] init];
// 设置videoID
self.currentVideo.videoId = attributeDict[@"videoId"];
}
// 清空字符串的内容,因为马上要进入第3 个方法,要开始拼接当前的节点的内容
[self.elementString setString:@""];
}
//3. 发现节点里面内容
- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string
{
NSLog(@"3. 发现节点里面内容--->%@", string);
// 开始拼接
[self.elementString appendString:string];
}
//4. 结束节点
- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName
{
NSLog(@"4. 结束节点 %@", elementName);
if ([elementName isEqualToString:@"video"]) {
// 如果结束的节点是video, 需要把这个对象添加到数组
[self.videos addObject:self.currentVideo];
}else if (![elementName isEqualToString:@"videos"]){
[self.currentVideo setValue:self.elementString forKeyPath:elementName];
}
// else if ([elementName isEqualToString:@"name"]) {// 如果结束节点,就可以把第二步创建的对象,设置name/length属性
// // 设置name属性
// // 1. 直接赋值
self.currentVideo.name = self.elementString;
// // 2. KVC赋值
[self.currentVideo setValue:self.elementString forKeyPath:@"name"];
// // 3. 改造
// [self.currentVideo setValue:self.elementString forKeyPath:elementName];
// }else if ([elementName isEqualToString:@"length"]){
// // 设置length属性
self.currentVideo.length = @([self.elementString integerValue]);
//
// [self.currentVideo setValue:self.elementString forKeyPath:elementName];
// }else if ([elementName isEqualToString:@"videoURL"]){
// // 设置videoURL属性
self.currentVideo.videoURL = self.elementString;
// [self.currentVideo setValue:self.elementString forKeyPath:elementName];
// }else if ([elementName isEqualToString:@"desc"]){
// // 设置desc属性
self.currentVideo.desc = self.elementString;
// [self.currentVideo setValue:self.elementString forKeyPath:elementName];
// }else if ([elementName isEqualToString:@"teacher"]){
// // 设置teacher属性
self.currentVideo.teacher = self.elementString;
// [self.currentVideo setValue:self.elementString forKeyPath:elementName];
// }
// cocoa 的大招 KVC
}
//5 结束文档
- (void)parserDidEndDocument:(NSXMLParser *)parser
{
NSLog(@"5 结束文档");
// xml真正解析结束, 可以更新UI, 在主线程
dispatch_async(dispatch_get_main_queue(), ^{
self.dataList = self.videos;
});
}
#pragma tabel数据源方法
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return self.videos.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"cell"];
Video *video = self.dataList[indexPath.row];
cell.textLabel.text = video.name;
return cell;
}
@end
本地服务器显示如下:
后台运行结果如下:
手机显示如下: