当前位置: 首页 > article >正文

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 节点。其实,上面的那些语法都是简写形式的语法,如果将它们完整写出来,将是这样的形式:

  1. /child::package/child::metadata/child::id
  2. /child::package/child::metadata/child::node()[1]
  3. /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框架:

  1. NSXMLParser:它是基于objective-c语言的sax解析框架,是ios sdk默认的xml解析框架,不支持dom模式。
  2. libxml2: 它是基于c语言的xml解析器,被苹果整合在ios sdk中,支持sax和dom模式。

第三方xml解析框架

  1. tbxml:它是轻量级的dom模式解析库,不支持xml文档验证和xpath,只能读取xml文档,不能写xml文档。
  2. touchxml:它是基于dom模式的解析库,与 tbxml类似,只能读取xml文档,不能写xml文档。
  3. kissxml:它是基于dom模式的解析库,基于touchxml,主要的不同是可以写入xml文档。
  4. 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:

奇迹读书地址


http://www.kler.cn/a/237.html

相关文章:

  • NodeLocal DNS 全攻略:从原理到应用实践
  • 三极管工作状态分析
  • open61499符合新型工业控制测试要求吗
  • unity学习13:gameobject的组件component以及tag, layer 归类
  • Centos7使用yum工具出现 Could not resolve host: mirrorlist.centos.org
  • Qt 下位机串口模拟器
  • Linux系统搭建FTP服务器
  • Springboot是什么
  • 2022-2-23作业
  • 蓝桥杯刷题第六天
  • Android 录屏 实现
  • 【6G 新技术】6G数据面介绍
  • 最新!Windows 11 更新将整合 AI 技术
  • [闪存2.1] NAND FLASH特性串烧 | 不了解闪存特性,你能用好闪存产品吗?
  • ffmpeg将图片合成为视频常用参数介绍
  • Spring —— 初学 Spring, 理解控制反转
  • 一条sql执行很慢可能的原因,如何优化
  • 计算机网络概述
  • 使用Maven实现第一个Servlet程序
  • Python打包成exe,文件太大问题解决办法(比保姆级还保姆级)
  • 【无标题】
  • 【CSS】SVG实战入门,svg画曲线,svg简单动画上手入门
  • 【C++笔试强训】第三十一天
  • 改进YOLO系列 | CVPR2023最新Backbone | FasterNet 远超 ShuffleNet、MobileNet、MobileViT 等模型
  • MySQL数据库的基础语法总结(1)
  • 【2022-09-14】米哈游秋招笔试三道编程题