iOS XML数据解析
前言
最近公司的活比较少,空闲时间十分多,遍寻思着写一款APP。在这个过程中便有使用到XML数据,于是研究了一下。
目前写的这个是本地阅读软件,后续会把在写这个APP用到的技术以及遇到的问题都整理出来,通过博客的形式分享给大家。
奇迹读书地址
XML
XML全称是Extensible Markup Language,译作“可扩展标记语言”;跟JSON一样,也是常用的一种用于交互的数据格式
一般也叫XML文档(XML Document)。
- 一个常见的XML文档一般由元素(Element)和属性(Attribute)组成:
- 一个元素包括开始标签和结束标签
- 规范的XML文档只有一个根元素,其他元素都是根元素的子孙元素
- XML中的空格、换行都会当做具体的内容处理
- 一个元素具有多个属性,属性值必须用当引号或双引号包住
- 属性表示的信息也可以用子元素来表示
XPath
XPath 是 XML 路径语言(XML Path Language),用来确定XML文档中某部分位置的语言。无论是什么语言什么框架,几乎都可以使用 XPath 来高效查询 XML 文件。
XPath路径
/package/metadata/id:这样的路径描述语法将可以找到 package 节点下的 metadata 节点下的 id 节点。
/package/metadata/*[1]:使用 * 可以找到任意名称,于是这样的路径描述语法将可以找到 metadata 下第一个节点,名称是任意的。尤其要注意的是,XPath 的路径语法第一个节点从 1 开始,而不是 0。
/package//dependency:// 表示只要是前面节点的内部即可,无论中间经过了多少层。如果把 // 写到了最前面,例如 //dependency,那么表示寻找任意位置的 dependency 节点。其实,上面的那些语法都是简写形式的语法,如果将它们完整写出来,将是这样的形式:
- /child::package/child::metadata/child::id
- /child::package/child::metadata/child::node()[1]
- /child::package/descendant-or-self::dependency
这里的 child、descendant-or-self 是轴描述语法,除了这两个,还有这些:
- child:子节点 可以省略不写
- attribute:属性 可以用 @ 来缩写
- descendant:子孙节点
- descendant-or-self:自身引用及子孙节点,可以用 // 来缩写
- parent:父节点 可以用 … 来缩写
- ancestor:祖先节点
- ancestor-or-self:自身引用及祖先节点
- following:在此节点后的所有完整节点,即不包含其祖先节点
- preceding:在此节点前的所有完整节点,即不包含其子孙节点
- following-sibling:下一个同级节点
- preceding-sibling:上一个同级节点
- self:自己 可以用 . 来缩写
- namespace:命名空间
对于 attribute 的使用,例如 //repository/@type 查找任意位置的 repository 节点的 type 属性。
XPath节点类型
在前面的路径中,已经使用了 node() 来寻找元素节点,除 node() 表达式之外,还有:
- comment():注释,也就是
- text():文字
- processing-instruction():XML 处理指令,也就是 <? 处理指令 ?>
- node():节点
XPath节点内容
使用中括号来描述节点的内容。例如 //repository[@type=‘git’] 用来查找任意位置的 repository 节点,并且它有一个 type 属性值为 git。
中括号是可以写多个的,例如:
//dependency[contains(@exclude, ‘Build’)][…/group/@targetFramework=‘.NETStandard2.0’]/@id
这将查找所有满足这些条件 dependency 节点的 id 属性:
- exclude 属性中包含 Build 字符串
- 其父节点为 group 且 targetFramework 属性为 .NETStandard2.0
XPath运算符
/、//、… 这是前面描述的路径运算符
| :用于取两个节点查找结果的并集
例如 //licenseUrl | //projectUrl | //iconUrl 取任意位置的 licenseUrl、projectUrl 和 iconUrl 节点。
and、or:对两个条件取“与”或者“或”
not() 函数:对条件取“非”
+、-、*、div 以及 mod:加减乘除以及取余数
=、!=、<、>、<=、>=:比较相等或大小
XML解析
DOM解析:一次性将整个XML文档加载进内存,比较适合解析小文件
SAX解析:Simple API for XML .基于事件驱动的解析方式,逐行解析数据(采用协议回调机制)。从根元素开始,按顺序一个元素一个元素往下解析,比较适合解析大文件。
iOS SDK 提供了两个xml框架:
- NSXMLParser:它是基于objective-c语言的sax解析框架,是ios sdk默认的xml解析框架,不支持dom模式。
- libxml2: 它是基于c语言的xml解析器,被苹果整合在ios sdk中,支持sax和dom模式。
第三方xml解析框架
- tbxml:它是轻量级的dom模式解析库,不支持xml文档验证和xpath,只能读取xml文档,不能写xml文档。
- touchxml:它是基于dom模式的解析库,与 tbxml类似,只能读取xml文档,不能写xml文档。
- kissxml:它是基于dom模式的解析库,基于touchxml,主要的不同是可以写入xml文档。
- Gdataxml:它是基于dom模式的解析库,由google开发,可以读写xml文档,支持xpath查询。
NSXMLParser SAX解析
SAX 解析XML,是基于事件通知的模式,一边读取XML文档一边处理,不必等整个文档加载完之后才采取操作,SAX解析器会检测整个XML树形结构,你的代码
会控制它在哪里停止,使用哪些数据之类的事情。就是说,SAX可控制性强,占用内存小,适用于提取部分数据。当在读取解析过程中遇到需要处理的对象,会发
出通知对其进行处理,如果XML格式在某一处出现错误,前面的数据会被提取出来,错误后面数据的就显示不出来。
NSXMLParser 类是iOS自带的XML解析类,采用SAX方式解析数据,解析过程由NSXMLParserDelegate协议方法回调
解析过程:开始标签->取值->结束标签->取值
以如下表示书籍目录的XML数据为例:
<?xml version="1.0" encoding="UTF-8"?>
<ncx version="2005-1" xmlns="http://www.daisy.org/z3986/2005/ncx/">
<head>
<meta name="dtb:uid" content="urn:uuid:e77f671c-5efd-4437-b973-47db008ac960" />
<meta name="dtb:depth" content="1" />
<meta name="dtb:totalPageCount" content="0" />
<meta name="dtb:maxPageNumber" content="0" />
<meta name="cover" content="cover_jpg" />
</head>
<docTitle>
<text>Swift 进阶</text>
</docTitle>
<navMap>
<navPoint id="navPoint-0">
<navLabel>
<text>Swift 进阶</text>
</navLabel>
<content src="title_page.xhtml" />
</navPoint>
<navPoint id="navPoint-1">
<navLabel>
<text>介绍</text>
</navLabel>
<content src="ch001.xhtml#介绍" />
<navPoint id="navPoint-2">
<navLabel>
<text>本书所面向的读者</text>
</navLabel>
<content src="ch001.xhtml#本书所面向的读者" />
</navPoint>
<navPoint id="navPoint-3">
<navLabel>
<text>主题</text>
</navLabel>
<content src="ch001.xhtml#主题" />
</navPoint>
<navPoint id="navPoint-4">
<navLabel>
<text>术语</text>
</navLabel>
<content src="ch001.xhtml#术语" />
</navPoint>
<navPoint id="navPoint-5">
<navLabel>
<text>Swift 风格指南</text>
</navLabel>
<content src="ch001.xhtml#swift-风格指南" />
</navPoint>
</navPoint>
</navMap>
</ncx>
分析数据结构:
- 表示章节的navPoint元素是一个嵌套数据元素,因为存在子章节
- 表示文本的text元素是一个公共的元素名称,多种数据类型(docTitle/navLabel)中都有使用
解析实现:
1、构建属性用存储解析结果,处理嵌套数据类型,区别解析元素
/// 解析的结果数据
@property (strong, nonatomic) NSMutableDictionary *result;
/// 嵌套的元素数组
@property (strong, nonatomic) NSMutableArray *nestedElement;
/// 是否解析docTitle元素
@property (assign, nonatomic) BOOL docTitle;
/// 解析的元素名称
@property (copy, nonatomic) NSString *elementName;
2、创建解析器,开始解析数据
NSString *path = [[NSBundle mainBundle] pathForResource:@"toc" ofType:@"ncx"];
NSXMLParser *parser = [[NSXMLParser alloc] initWithContentsOfURL:[NSURL fileURLWithPath:path]];
parser.delegate = self;
[parser parse];
3、实现代理方法,完成数据解析
#pragma mark - <NSXMLParserDelegate> 文档处理方法
// 当解析器开始解析文档时发送。
- (void)parserDidStartDocument:(NSXMLParser *)parser {
RRLog(@"%s", __FUNCTION__);
}
// 在解析器完成解析时发送。如果遇到这种情况,则说明解析成功。
- (void)parserDidEndDocument:(NSXMLParser *)parser {
RRLog(@"%s", __FUNCTION__);
}
// 当解析器发现元素开始标记时发送。
- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(nullable NSString *)namespaceURI qualifiedName:(nullable NSString *)qName attributes:(NSDictionary<NSString *, NSString *> *)attributeDict {
RRLog(@"%s", __FUNCTION__);
RRLog(@"%@%@%@%@", elementName, namespaceURI, qName, attributeDict);
if ([elementName isEqualToString:@"ncx"]) {
self.result = [NSMutableDictionary dictionaryWithDictionary:attributeDict];
}
if ([elementName isEqualToString:@"head"]) {
NSMutableArray *head = [NSMutableArray arrayWithCapacity:1];
self.result[@"head"] = head;
}
if ([elementName isEqualToString:@"meta"]) {
[self.result[@"head"] addObject:[NSMutableDictionary dictionaryWithDictionary:attributeDict]];
}
if ([elementName isEqualToString:@"docTitle"]) {
self.result[@"docTitle"] = [NSMutableDictionary dictionaryWithDictionary:attributeDict];
self.docTitle = YES;
}
if ([elementName isEqualToString:@"navMap"]) {
self.result[@"navMap"] = [NSMutableArray arrayWithCapacity:1];
}
if ([elementName isEqualToString:@"navPoint"]) {
NSDictionary *navPoint = [NSMutableDictionary dictionaryWithDictionary:attributeDict];
if (self.nestedElement && self.nestedElement.count > 0) {
NSMutableDictionary *superNavPoint = [self.nestedElement lastObject];
if (superNavPoint[@"navMap"]) {
[superNavPoint[@"navMap"] addObject:navPoint];
} else {
superNavPoint[@"navMap"] = [NSMutableArray arrayWithObject:navPoint];
}
[self.nestedElement addObject:navPoint];
} else {
self.nestedElement = [NSMutableArray arrayWithObject:navPoint];
[self.result[@"navMap"] addObject:navPoint];
}
}
if ([elementName isEqualToString:@"navLabel"]) {
NSMutableDictionary *navPoint = [self.nestedElement lastObject];
navPoint[@"navLabel"] = [NSMutableDictionary dictionaryWithDictionary:attributeDict];
}
if ([elementName isEqualToString:@"content"]) {
NSMutableDictionary *navPoint = [self.nestedElement lastObject];
navPoint[@"content"] = [NSMutableDictionary dictionaryWithDictionary:attributeDict];
}
self.elementName = elementName;
}
// 当遇到结束标记时发送。
- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(nullable NSString *)namespaceURI qualifiedName:(nullable NSString *)qName {
RRLog(@"%s", __FUNCTION__);
RRLog(@"%@%@%@", elementName, namespaceURI, qName);
if ([elementName isEqualToString:@"ncx"]) {
NSString *value = [[NSString alloc] initWithData:[NSJSONSerialization dataWithJSONObject:self.result options:NSJSONWritingPrettyPrinted error:nil] encoding:NSUTF8StringEncoding];
RRLog(@"%@", value);
}
if ([elementName isEqualToString:@"docTitle"]) {
self.docTitle = NO;
}
if ([elementName isEqualToString:@"navPoint"]) {
[self.nestedElement removeLastObject];
}
self.elementName = nil;
}
// 这将返回到目前为止遇到的字符的字符串。你不一定能得到最长的字符。解析器保留将这些字符传递给代理的权利,因为一行中可能会调用-parser:foundCharacters:
- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string {
RRLog(@"%s", __FUNCTION__);
RRLog(@"%@", string);
if ([self.elementName isEqualToString:@"text"]) {
NSMutableDictionary *parserValue = nil;
if (self.docTitle) {
parserValue = self.result[@"docTitle"];
} else {
parserValue = [self.nestedElement lastObject][@"navLabel"];
}
NSString *value = parserValue[@"text"];
if (!value) value = @"";
parserValue[@"text"] = [value stringByAppendingString:string];
}
}
在这里,通过数组来表示嵌套的数据结构(当然也可以使用其他方式),不过这样做比较容易实现多重嵌套的效果
一定要在解析到结束标签的时候将elementName置空,防止其他数据被解析到目标数据结构中
docTitle用来记录解析到的text标签是不是docTitle元素的子元素
最终解析结果如下:
{
"head" : [
{
"name" : "dtb:uid",
"content" : "urn:uuid:e77f671c-5efd-4437-b973-47db008ac960"
},
{
"name" : "dtb:depth",
"content" : "1"
},
{
"name" : "dtb:totalPageCount",
"content" : "0"
},
{
"name" : "dtb:maxPageNumber",
"content" : "0"
},
{
"name" : "cover",
"content" : "cover_jpg"
}
],
"docTitle" : {
"text" : "Swift 进阶"
},
"navMap" : [
{
"id" : "navPoint-0",
"content" : {
"src" : "title_page.xhtml"
},
"navLabel" : {
"text" : "Swift 进阶"
}
},
{
"id" : "navPoint-1",
"content" : {
"src" : "ch001.xhtml#介绍"
},
"navLabel" : {
"text" : "介绍"
},
"navMap" : [
{
"id" : "navPoint-2",
"content" : {
"src" : "ch001.xhtml#本书所面向的读者"
},
"navLabel" : {
"text" : "本书所面向的读者"
}
},
{
"id" : "navPoint-3",
"content" : {
"src" : "ch001.xhtml#主题"
},
"navLabel" : {
"text" : "主题"
}
},
{
"id" : "navPoint-4",
"content" : {
"src" : "ch001.xhtml#术语"
},
"navLabel" : {
"text" : "术语"
}
},
{
"id" : "navPoint-5",
"content" : {
"src" : "ch001.xhtml#swift-风格指南"
},
"navLabel" : {
"text" : "Swift 风格指南"
}
}
]
}
],
"xmlns" : "http:\/\/www.daisy.org\/z3986\/2005\/ncx\/",
"version" : "2005-1"
}
GDataXMLNode DOM解析
DOM:Document Object Model(文档对象模型)。解析时需要将XML文件整体读入,并且将XML结构化成树状,使用时再通过树状结构读取相关数据,查找特定节点,然后对节点进行读或写。他的主要优势是实现简单,读写平衡;缺点是比较占内存,因为他要把整个xml文档都读入内存,文件越大,这种缺点就越明显。当文件内容出现错误时,在输入框内会标记出错误的位置。
iOS中包含一个C语言的动态链接库libxml2.dylib,解析速度比NSXMLParser快。
GDataXMLNode是Google提供的开元XML解析类,对libxml2.dylib进行了Objective-C的封装,因此在使用GDataXML之前,需要先导入libxml2。GDataXML中常用的类
- GDataXMLDocument: 代表整个XML文档
- GDataXMLElement: 代表文档中的每个元素
使用attributeForName:方法可以获得属性值
/// XML数据DOM解析
- (void)domParseXML {
NSString *path = [[NSBundle mainBundle] pathForResource:@"toc" ofType:@"ncx"];
NSString *xml = [NSString stringWithContentsOfFile:path encoding:NSUTF8StringEncoding error:nil];
// 加载XML文档
GDataXMLDocument *document = [[GDataXMLDocument alloc] initWithXMLString:xml error:nil];
// 获取根元素
GDataXMLElement *root = [document rootElement];
// 可变字典保存解析结果
NSMutableDictionary *result = [NSMutableDictionary dictionaryWithCapacity:1];
// 遍历根元素属性列表
for (GDataXMLNode *node in root.attributes) {
// 保存属性到结果字典
[result setValue:node.stringValue forKey:node.name];
}
// 获取head元素
GDataXMLElement *headElement = [[root elementsForName:@"head"] firstObject];
// 可变数组保存解析的head元素
NSMutableArray *head = [NSMutableArray arrayWithCapacity:1];
result[@"head"] = head;
// 遍历head元素的meta子元素列表
for (GDataXMLElement *metaElement in [headElement elementsForName:@"meta"]) {
// 可变字典保存解析的meta元素
NSMutableDictionary *meta = [NSMutableDictionary dictionaryWithCapacity:1];
[head addObject:meta];
// 遍历meta元素属性列表
for (GDataXMLNode *metaNode in metaElement.attributes) {
// 保存属性到结果字典
[meta setValue:metaNode.stringValue forKey:metaNode.name];
}
}
// 获取docTitle元素
GDataXMLElement *docTitleElement = [[root elementsForName:@"docTitle"] firstObject];
// 可变字典保存解析的docTitle元素
NSMutableDictionary *docTitle = [NSMutableDictionary dictionaryWithCapacity:1];
result[@"docTitle"] = docTitle;
// 遍历docTitle子元素列表
for (GDataXMLElement *element in docTitleElement.children) {
[docTitle setValue:element.stringValue forKey:element.name];
}
// 获取navMap元素
GDataXMLElement *navMapElement = [[root elementsForName:@"navMap"] firstObject];
// 可变数组保存解析的navMap元素
NSMutableArray *navMap = [NSMutableArray arrayWithCapacity:1];
[result setValue:navMap forKey:navMapElement.name];
// 遍历navMap元素的navPoint子元素列表
for (GDataXMLElement *element in [navMapElement elementsForName:@"navPoint"]) {
[navMap addObject:[self parseNavPoint:element]];
}
NSString *value = [[NSString alloc] initWithData:[NSJSONSerialization dataWithJSONObject:result options:NSJSONWritingPrettyPrinted error:nil] encoding:NSUTF8StringEncoding];
RRLog(@"%@", value);
}
/// 解析navPoint元素
/// - Parameter element: navPoint元素
- (NSDictionary *)parseNavPoint:(GDataXMLElement *)element {
// 可变字典保存解析结果
NSMutableDictionary *result = [NSMutableDictionary dictionaryWithCapacity:1];
// 解析id属性
result[@"id"] = [element attributeForName:@"id"].stringValue;
// 获取navLabel元素
GDataXMLElement *navLabelElement = [[element elementsForName:@"navLabel"] firstObject];
// 可变数组保存解析的navLabel元素
NSMutableDictionary *navLabel = [NSMutableDictionary dictionaryWithCapacity:1];
[result setValue:navLabel forKey:navLabelElement.name];
// 遍历navLabel子元素列表
for (GDataXMLElement *textElement in navLabelElement.children) {
[navLabel setValue:textElement.stringValue forKey:textElement.name];
}
GDataXMLElement *contentElement = [[element elementsForName:@"content"] firstObject];
if (contentElement) {
// 可变字典保存解析的content元素
NSMutableDictionary *content = [NSMutableDictionary dictionaryWithCapacity:1];
[result setValue:content forKey:contentElement.name];
// 遍历content元素属性列表
for (GDataXMLNode *node in contentElement.attributes) {
[content setValue:node.stringValue forKey:node.name];
}
}
// 获取navPoint元素
NSArray *navPointElements = [element elementsForName:@"navPoint"];
if (navPointElements.count > 0) {
// 可变数组保存解析的navPoint元素
NSMutableArray *navPoint = [NSMutableArray arrayWithCapacity:1];
[result setValue:navPoint forKey:@"navPoint"];
// 遍历navPoint的navPoint子元素列表
for (GDataXMLElement *children in navPointElements) {
// 递归解析navPoint元素
[navPoint addObject:[self parseNavPoint:children]];
}
}
return result;
}
这里需要注意的是navPoint元素的解析,navPoint元素是一种嵌套的原数,所以用了递归的方式来解析,最终的解析结果如下:
{
"head" : [
{
"name" : "dtb:uid",
"content" : "urn:uuid:e77f671c-5efd-4437-b973-47db008ac960"
},
{
"name" : "dtb:depth",
"content" : "1"
},
{
"name" : "dtb:totalPageCount",
"content" : "0"
},
{
"name" : "dtb:maxPageNumber",
"content" : "0"
},
{
"name" : "cover",
"content" : "cover_jpg"
}
],
"docTitle" : {
"text" : "Swift 进阶"
},
"navMap" : [
{
"id" : "navPoint-0",
"content" : {
"src" : "title_page.xhtml"
},
"navLabel" : {
"text" : "Swift 进阶"
}
},
{
"id" : "navPoint-1",
"content" : {
"src" : "ch001.xhtml#介绍"
},
"navLabel" : {
"text" : "介绍"
},
"navPoint" : [
{
"id" : "navPoint-2",
"content" : {
"src" : "ch001.xhtml#本书所面向的读者"
},
"navLabel" : {
"text" : "本书所面向的读者"
}
},
{
"id" : "navPoint-3",
"content" : {
"src" : "ch001.xhtml#主题"
},
"navLabel" : {
"text" : "主题"
}
},
{
"id" : "navPoint-4",
"content" : {
"src" : "ch001.xhtml#术语"
},
"navLabel" : {
"text" : "术语"
}
},
{
"id" : "navPoint-5",
"content" : {
"src" : "ch001.xhtml#swift-风格指南"
},
"navLabel" : {
"text" : "Swift 风格指南"
}
}
]
}
],
"version" : "2005-1"
}
XPath 解析XML
从上面解析的数据中发现并没有解析到根元素的xmlns属性,这是为什么呢?
原因就是xmlns不是一个普通的属性,这是关键词命名空间。
XML命名空间介绍
XPath 解析不带命名空间的XML
先把数据中的命名空间(xmlns=“http://www.daisy.org/z3986/2005/ncx/”)删除。
然后使用第二章介绍的XPath语法解析,解析结果同上就不贴了:
/// 使用XPath解析XML文档
- (void)parseXMLByXPath {
NSString *path = [[NSBundle mainBundle] pathForResource:@"toc" ofType:@"ncx"];
NSString *xml = [NSString stringWithContentsOfFile:path encoding:NSUTF8StringEncoding error:nil];
// 加载XML文档
GDataXMLDocument *document = [[GDataXMLDocument alloc] initWithXMLString:xml error:nil];
// 可变字典保存解析结果
NSMutableDictionary *result = [NSMutableDictionary dictionaryWithCapacity:1];
// 获取/ncx/docTitle/路径下的text元素
GDataXMLNode *node = [document firstNodeForXPath:@"/ncx/@version" error:nil];
result[node.name] = node.stringValue;
// 获取/ncx/head/路径下的meta元素
NSArray *metas = [document nodesForXPath:@"/ncx/head/meta" error:nil];
NSMutableArray *head = [NSMutableArray arrayWithCapacity:1];
result[@"head"] = head;
for (GDataXMLElement *element in metas) {
NSMutableDictionary *meta = [NSMutableDictionary dictionaryWithCapacity:1];
[head addObject:meta];
for (GDataXMLNode *node in element.attributes) {
meta[node.name] = node.stringValue;
}
}
NSMutableDictionary *docTitle = [NSMutableDictionary dictionaryWithCapacity:1];
result[@"docTitle"] = docTitle;
// 获取/ncx/docTitle/路径下的text元素
node = [document firstNodeForXPath:@"//docTitle/text" error:nil];
docTitle[node.name] = node.stringValue;
// 获取/ncx/head/路径下的meta元素
NSArray *navPoints = [document nodesForXPath:@"/ncx/navMap/navPoint" error:nil];
NSMutableArray *navMap = [NSMutableArray arrayWithCapacity:1];
result[@"navMap"] = navMap;
for (GDataXMLElement *element in navPoints) {
[navMap addObject:[self parseNavPointByXPath:element]];
}
NSString *value = [[NSString alloc] initWithData:[NSJSONSerialization dataWithJSONObject:result options:NSJSONWritingPrettyPrinted error:nil] encoding:NSUTF8StringEncoding];
RRLog(@"%@", value);
}
/// 使用XPath解析navPoint元素
/// - Parameter element: navPoint元素
- (NSDictionary *)parseNavPointByXPath:(GDataXMLElement *)element {
NSMutableDictionary *result = [NSMutableDictionary dictionaryWithCapacity:1];
NSMutableDictionary *navLabel = [NSMutableDictionary dictionaryWithCapacity:1];
result[@"navLabel"] = navLabel;
// 获取./navLabel/路径下的text元素
GDataXMLNode *node = [element firstNodeForXPath:@"./navLabel/text" error:nil];
navLabel[node.name] = node.stringValue;
NSMutableDictionary *content = [NSMutableDictionary dictionaryWithCapacity:1];
result[@"content"] = content;
// 获取./content/路径下的src属性元素
node = [element firstNodeForXPath:@"./content/@src" error:nil];
content[node.name] = node.stringValue;
NSMutableArray *navPoint = [NSMutableArray arrayWithCapacity:1];
result[@"navPoint"] = navPoint;
// 获取./路径下的所有navPoint元素
for (GDataXMLElement *pointElement in [element nodesForXPath:@"./navPoint" error:nil]) {
[navPoint addObject:[self parseNavPointByXPath:pointElement]];
}
return result;
}
XPath 解析带有命名空间的XML
先把数据中的命名空间(xmlns=“http://www.daisy.org/z3986/2005/ncx/”)还原回去。
对于带有命名空间的XML,使用上面的XPath是解析不出来的,必须通过如下步骤解析:
- 使用GDataXMLDocument的
nodesForXPath: namespaces: error:
这个方法代替之前的nodesForXPath: error:
方法 - 在每个路径节点上加上命名空间的名称
- 传入完整的命名空间字典作为参数
// 解析命名空间
NSMutableDictionary *namespace = [NSMutableDictionary dictionaryWithCapacity:1];
for (GDataXMLNode *node in root.namespaces) {
NSString *name = node.name;
if (name && name.length > 0) {
} else {
name = @"xmlns";
}
namespace[name] = node.stringValue;
}
RRLog(@"namespace: %@", namespace);
// 获取/ncx/docTitle/text路径下元素(每个节点都要加上命名空间)
GDataXMLNode *node = [document firstNodeForXPath:@"/xmlns:ncx/xmlns:docTitle/xmlns:text" namespaces:namespace error:nil];
RRLog(@"%@: %@", node.name, node.stringValue);
打印数据如下:
namespace: {
xmlns = "http://www.daisy.org/z3986/2005/ncx/";
}
text: Swift 进阶
后记
最后再分享一遍这个APP:
奇迹读书地址