XML 数据解析

前言

  • Objective-C

    1
    @interface NSXMLParser : NSObject
  • Swift

    1
    public class NSXMLParser : NSObject

1、XML 数据

  • XML(Extensible Markup Language)是可扩展标记语言的缩写,其中的标记(markup)是关键部分。可以创建内容,然后使用限定标记标记它,从而使每个单词、短语或块成为可识别、可分类的信息。创建的文件或文档实例由元素(标记)和内容构成。当从打印输出读取或以电子形式处理文档时,元素能够帮助更好地理解文档。元素的描述性越强,文档各部分越容易识别。自从出现标记至今,带有标记的内容就有一个优势,即在计算机系统缺失时,仍然可以通过标记理解打印出来数据。标记语言从早期的私有公司和政府制定形式逐渐演变成标准通用标记语言(Standard Generalized Markup Language,SGML)、超文本标记语言(Hypertext Markup Language,HTML),并且最终演变成 XML。SGML 比较复杂,HTML(实际上仅是一组元素集)在识别信息方面不够强大。XML 则是一种易于使用和易于扩展的标记语言。

1.1 构建 XML

  • XML 文件由内容和标记组成。通过以标记包围内容的方式将大部分内容包含在元素中。例如,假设需要创建一本烹饪书。需要用 XML 编写名为 Ice Cream Sundae 的食谱。为了标记食谱名,需要将这个文本包含到元素中,即分别在文本的首末两端添加开始和结束标记。可以将元素命名为 recipename。要标记元素的开始标记,像这样将元素名放到尖括号中(<>):<recipename>。然后输入文本 Ice Cream Sundae。在文本的后面输入结束标记,即将元素名放在尖括号内,然后在元素名前面加上一个终止斜杠(/),比如:</recipename>。这些标记构成一个元素 <recipename> Ice Cream Sundae </recipename>,可以在元素的内部添加内容或其他元素。

  • 可以为某个文档或文档集创建元素名。可以创建规则让元素根据特定需求组合起来。元素名可以是比较具有针对性的,也可以是比较通用的。还可以创建决定添加何种元素的规则。这些规则可以是严格的,也可以是松散的。一定要为文档创建元素,以识别认为重要的部分。

1.2 创建文件声明

  • XML 文档的第一行可以是一个 XML 声明。这是文件的可选部分,它将文件识别为 XML 文件,有助于工具和人类识别 XML(不会误认为是 SGML 或其他标记)。可以将这个声明简单地写成 <?xml?>,或包含 XML 版本(<?xml version=”1.0”?>),甚至包含字符编码,比如针对 Unicode 的 <?xml version="1.0" encoding="utf-8"?>。因为这个声明必须出现在文件的开头,所以如果打算将多个小的 XML 文件合并为一个大 XML 文件,则可以忽略这个可选信息。

1.3 创建根元素

  • 根元素的开始和结束标记用于包围 XML 文档的内容。一个文件只能有一个根元素,并且需要使用 “包装器” 包含它。清单 1 显示了经过删节的示例,其中的根元素名为 <recipe>。在构建文档时,内容和其他标记必须放在 <recipe></recipe> 之间。

  • 清单 1. 根元素

    1
    2
    3
    <?xml version="1.0" encoding="UTF-8"?>
    <recipe>
    </recipe>

1.4 命名元素

  • 标记的大小写保持一致创建 XML 时,要确保开始和结束标记的大小写是一致的。如果大小写不一致,在使用或查看 XML 时将出现错误。例如,如果大小写不一致,Internet Explorer 将不能显示文件的内容,但它会显示开始和结束标记不一致的消息。

  • 到目前为止,都使用 <recipe> 作为根元素。在 XML 中,先要为元素选择名称,然后再根据这些名称定义相应的 DTD 或 schema。创建名称时可以使用英文字母、数字和特殊字符,比如下划线(_)。下面给出命名时需要注意的地方:

    • 元素名中不能出现空格。
    • 名称只能以英文字母开始,不能是数字或符号。(在第一个字母之后就可以使用字母、数字或规定的符号,或它们的混合)。
    • 对大小写没有限制,但前后要保持一致,以免造成混乱。
  • 继续以前面的示例为例,如果添加了名为 <recipename> 的元素,它将有一个开始标记 <recipename> 和相应的结束标记 </recipename>

  • 清单 2. 更多元素

    1
    2
    3
    4
    5
    <?xml version="1.0" encoding="UTF-8"?>
    <recipe>
    <recipename>Ice Cream Sundae</recipename>
    <preptime>5 minutes</preptime>
    </recipe>
  • XML 文档可以使用内部不包含任何内容的空标记,这些标记可以表示为单个标记,而不是一组开始和结束标记。以类似于 HTML 的文件为例,里面的 <img src="mylogo.gif"> 是一个独立的元素。它不包含任何子元素或文本,因此它是一个空元素,可以将它表示为 <img src="mylogo.gif" />(以一个空格和熟悉的终止斜杠结束)。

1.5 嵌套元素

  • 嵌套即把某个元素放到其他元素的内部。这些新的元素称为子元素,包含它们的元素称为父元素。<recipe> 根元素中嵌套有几个其他元素,如清单 3 所示。这些嵌套的子元素包括 <recipename>、<ingredlist><preptime><ingredlist> 元素内部包含多个子元素 <listitem>。XML 文档可以使用多层嵌套。

  • 一个常见的语法错误是父元素和子元素的错误嵌套。任何子元素都要完全包含在其父元素的开始和结束标记内部。每个同胞(Sibling)元素必须在下一个同胞元素开始之前结束。清单 3 的代码显示了正确的嵌套。这些标记的开始和结束没有与其他标记混合在一起。

  • 清单 3. 正确嵌套的 XML 元素

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    <?xml version="1.0" encoding="UTF-8"?>
    <recipe>
    <recipename>Ice Cream Sundae</recipename>
    <ingredlist>
    <listitem>
    <quantity>3</quantity>
    <itemdescription>chocolate syrup or chocolate fudge</itemdescription>
    </listitem>
    <listitem>
    <quantity>1</quantity>
    <itemdescription>nuts</itemdescription>
    </listitem>
    <listitem>
    <quantity>1</quantity>
    <itemdescription>cherry</itemdescription>
    </listitem>
    </ingredlist>
    <preptime>5 minutes</preptime>
    </recipe>

1.6 添加属性

  • 有时候要为元素添加属性。属性由一个名称–值对构成,值包含在双引号中,比如:type=”dessert”。属性是在使用元素时存储额外信息的一种方式。在同一个文档中,可以根据需要对每个元素的不同实例采用不同的属性值。

  • 可以在元素的开始标记内部输入一个或多个属性,比如:<recipe type="dessert">。如果要添加多个属性,各个属性之间使用空格分开,比如:<recipename cuisine="american" servings="1">。清单 4 显示了当前的 XML 文件。

  • 清单 4. 带有元素和属性的 XML 文件

    1
    2
    3
    4
    5
    <?xml version="1.0" encoding="UTF-8"?>
    <recipe type="dessert">
    <recipename cuisine="american" servings="1">Ice Cream Sundae</recipename>
    <preptime>5 minutes</preptime>
    </recipe>
  • 可以根据需要使用任意数量的属性。要考虑需要添加到文档的细节。如果要对文档分类,属性尤其有用,比如按照菜谱的 type 进行分类。属性名可以包含在元素名中使用的字符,规则也是类似的,即字符之间不能带有空格,名称只能以字母开始。

1.7 构造良好并且有效的 XML

  • 如果根据结构规则创建 XML,就很容易实现构造良好的 XML。构造良好的 XML 即遵循所有 XML 规则创建的 XML:正确的元素命名,嵌套,属性命名等等。

  • 要实现构造良好的 XML 取决于如何处理 XML。但考虑一下前面提到的示例,它要求根据菜谱类型进行分类。您需要确保每个 <recipe> 元素都包含 type 属性,以对菜谱进行分类。能够正确验证并确保存在属性值是非常重要的(避免出现双关语)。

  • 验证就是根据元素规则检查文档的结构,以及如何为每个父元素定义子元素。这些规则是在 文档类型定义(Document Type Definition,DTD)或模式(schema )中定义的。验证要求您创建自己的 DTD 或 schema ,然后在 XML 文件中引用 DTD 或 schema 文件。

  • 为了实现验证,必须在 XML 文档的顶部附近包含文档类型(DOCTYPE)。这行代码将引用用于验证文档的 DTD 或 schema(元素和规则列表)。例如,DOCTYPE 可能类似于 清单 5。

  • 清单 5. DOCTYPE

    1
    <!DOCTYPE MyDocs SYSTEM "filename.dtd">
  • 这个例子假设元素列表文件的名称是 filename.dtd,并且位于您的计算机上(如果指向公共文件位置,则 SYSTEM 和 PUBLIC 是相对的)。

1.8 使用实体

  • 实体可以是文本短语或特殊字符。它们可以指向内部或外部。必须正确地声明和表示实体,以避免错误和确保正确显示。

  • 不能直接在内容中输入特殊字符。如果要在文本中使用符号,必须使用它的字符代码将它设置为实体。您可以将短语(比如公司名)设置为实体,然后就可以在内容中使用该实体。为了设置实体,必须先为它创建一个名称,然后将它输入到内容中,以 and 符号(&)开始,并以分号(;)结束 — 例如,&coname;。然后在 DOCTYPE 的方括号([])内部输入代码,如 清单 6 所示。这个代码识别表示实体的文本。

  • 清单 6. ENTITY

    1
    <!DOCTYPE MyDocs SYSTEM "filename.dtd" [<!ENTITY coname "Rabid Turtle Industries"]>
  • 使用实体可以避免反复输入相同的短语和信息。在很多情况下它还使得调整文本更加容易(变更公司名时),只需对实体定义进行简单调整。

1.9 避免错误

  • 在学习创建 XML 文件时,在 XML 编辑器中打开它,以检查它的结构是否良好,并且确保您遵循 XML 规则。例如,如果您使用 Windows® Internet Explorer®,就可以在浏览器中打开 XML。如果它能够显示 XML 元素、属性和内容,则表明 XML 是构造良好的。相反,如果显示错误,则很可能是出现语法错误,您需要小心检查文档,看看是不是丢失标记和标点符号或输入错误。

  • 如在嵌套元素小节中提到的一样,包含其他元素的元素就是被包含元素的父元素。在下面的示例中,<recipe> 是根元素,并且包含文件的完整内容。父元素 <recipe> 包含的子元素有 <recipename>、<ingredlist>、<directions> 等等。在这种结构中,<recipename>、<ingredlist><directions> 成了同胞元素。此外,还要正确嵌套同胞 元素。

1.10 iOS XML 解析方式

  • 1、DOM 解析:

    • 是在 MAC 中使用的解析方式。DOM 方式的 XML 解析内存消耗非常大,iOS 默认不支持 DOM 方式解析。
    • DOM 方式解析一次性将 XML 文档以树型结构读入内存,可以读,可以改,内存消耗极大,横向的节点越多,内存消耗越大。
    • 使用 DOM 解析适合于小的 XML 解析,并且能够动态维护,不适用于手机,主要用在 PC 端或者服务器端。
    • 在 MAC 中提供一个 NSXML 的类,可以实现 DOM 方式的解析。
    • 有一些第三方框架是能够实现 DOM 方式的解析,使用 DOM 解析,适合非常小的 XML 数据结构。
  • 2、SAX 解析:

    • 是苹果提供的解析方式。
    • 是只读的方式,从上向下的方式解析,解析速度快,适合大的 XML 文件解析。
    • NSXMLParser 通过代理实现解析。解析的时候相对比较繁琐,有 5 个代理方法,每个代理方法都要写一定代码。
  • 3、第三方框架 解析:

    • GDataXML-HTML 框架:

      • 是 iOS 中 DOM 方式解析 XML 的第三方框架。在 iOS 开发中,如果要使用 DOM 方式解析,最好只处理小的 XML。

      • GDataXMLDocument 对应一个完整的 XML 文档,实例化之后,会把整个 XML 以树型结构读入内存。

      • 解析可读、可写。 addChild 添加节点, removeChild 删除节点。

      • 所有的 DOM 解析,里面都叫 name 和 stringValue 属性。

    • KissXML 框架:

      • XMPP 中用到 DOM 方式解析 XML 的第三方框架。

2、系统方式 XML 数据解析

2.1 系统方式数据解析

  • SAX 解析步骤:

    • 1、开始文档 - 准备工作
    • 2、开始 “节点”
    • 3、发现节点内部的内容,每一个节点,可能需要多次才能找完
    • 4、结束 “节点”
    • 5、结束文档 - 解析结束

    • 以上步骤,2,3,4,会不断循环,一直到所有解析完成。

  • 使用 NSXMLParser 对 XML 数据解析时,由上至下依次遍历每一个节点,遍历一个节点执行一次协议方法。

  • 遵守协议 NSXMLParserDelegate

  • Objective-C

    • 解析 xml 数据

      1
      2
      3
      4
      5
      6
      7
      8
      // 解析本地数据时 xmlData 为读取的本地数据,解析网络数据时 xmlData 为网络下载的数据
      NSXMLParser *xmlParser = [[NSXMLParser alloc] initWithData:xmlData];

      // 设置代理,需遵守协议 <NSXMLParserDelegate>
      xmlParser.delegate = self;

      // 开始解析,一旦开始解析,后续的工作都是由代理方法来实现的
      [xmlParser parse];
    • 协议方法

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      // 打开文档,开始 XML 数据解析
      - (void)parserDidStartDocument:(NSXMLParser *)parser {

      }

      // 开始遍历节点,包含根节点,"Element" 元素 节点
      - (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI
      qualifiedName:(NSString *)qName attributes:(NSDictionary *)attributeDict {

      /*
      依次遍历节点名称和属性值

      elementName: 节点名称
      attributeDict:节点属性值
      */
      }

      // 发现节点的内容
      - (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string {

      /*
      依次遍历节点内容

      string:节点内容
      */
      }

      // 结束节点遍历
      - (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI
      qualifiedName:(NSString *)qName {

      /*
      依次结束节点遍历

      elementName: 节点名称
      */
      }

      // 结束文档,结束 XML 数据解析
      - (void)parserDidEndDocument:(NSXMLParser *)parser {

      }
  • Swift

    • 解析 xml 数据

      1
      2
      3
      4
      5
      6
      7
      8
      // 解析本地数据时 xmlData 为读取的本地数据,解析网络数据时 xmlData 为网络下载的数据
      let xmlParser = NSXMLParser(data: xmlData!)

      // 设置代理,需遵守协议 <NSXMLParserDelegate>
      xmlParser.delegate = self

      // 开始解析,一旦开始解析,后续的工作都是由代理方法来实现的
      xmlParser.parse()
    • 协议方法

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      // 打开文档,开始 XML 数据解析
      func parserDidStartDocument(parser: NSXMLParser) {

      }

      // 开始遍历节点,包含根节点,"Element" 元素 节点
      func parser(parser: NSXMLParser, didStartElement elementName: String, namespaceURI: String?, qualifiedName
      qName: String?, attributes attributeDict: [String: String]) {

      /*
      依次遍历节点名称和属性值

      elementName: 节点名称
      attributeDict:节点属性值
      */
      }

      // 发现节点的内容
      func parser(parser: NSXMLParser, foundCharacters string: String) {

      /*
      依次遍历节点内容

      string:节点内容
      */
      }

      // 结束节点遍历
      func parser(parser: NSXMLParser, didEndElement elementName: String, namespaceURI: String?, qualifiedName qName: String?) {

      /*
      依次结束节点遍历

      elementName: 节点名称
      */
      }

      // 结束文档,结束 XML 数据解析
      func parserDidEndDocument(parser: NSXMLParser) {

      }

2.2 系统方式解析转数据模型

  • Objective-C

    • VideoModel.h

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      @interface VideoModel : NSObject

      // 为了避免服务端返回的数值型数据是 null,可以把数值型的数据设置成 NSNumber
      @property (nonatomic, copy) NSNumber *videoId;

      // copy 属性,在设置数值的时候,如果有一方是可变的,会默认做一次 copy 操作,会建立新的副本
      @property (nonatomic, copy) NSString *name;

      // 在模型中对象全都是用 copy 属性会比较安全
      @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;

      @end
    • VideoModel.m

      1
      2
      3
      4
      5
      6
      7
      8
      // 开发调试输出
      - (NSString *)description {

      return [NSString stringWithFormat:@"<%@ : %p> {\n\t\tvideoId: %@,\n\t\tname: %@,\n\t\tlength: %@,\n\t\tvideoURL: %@,\n\t\t
      imageURL: %@,\n\t\tdesc: %@,\n\t\tteacher: %@\n\t}", [self class], self,
      self.videoId, self.name, self.length, self.videoURL, self.imageURL, self.desc,
      self.teacher];
      }
    • NSArray+LocaleLog.m

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      /*
      Xcode 没有针对国际化语言做特殊处理,直接 Log 数组,只打印 UTF8 的编码,不能显示中文。重写这个方法,就能够解决输出问题,这个方法是专门为了本地话
      提供的一个调试方法,只要重写,不需要导入头文件,程序中所有的 NSLog 数组的方法,都会被替代。
      */

      - (NSString *)descriptionWithLocale:(id)locale {

      NSMutableString *strM = [NSMutableString stringWithString:@"(\n"];

      [self enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {

      [strM appendFormat:@"\t%@", obj];
      if (idx != self.count - 1) [strM appendFormat:@",\n"];
      }];

      [strM appendString:@"\n)"];

      return strM;
      }
    • ViewController.m

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      51
      52
      53
      54
      55
      56
      57
      58
      59
      60
      61
      62
      63
      64
      65
      66
      67
      68
      69
      70
      71
      72
      73
      74
      75
      76
      77
      78
      79
      80
      81
      82
      83
      84
      85
      86
      87
      88
      89
      // 数据源数组
      @property (nonatomic, strong) NSMutableArray *videosSourceArray;

      // 数据模型
      @property (nonatomic, strong) VideoModel *videoModel;

      // 数据模型的值
      @property (nonatomic, strong) NSMutableString *videoModelElementValueString;

      - (NSMutableArray *)videosSourceArray {
      if (_videosSourceArray == nil) {
      _videosSourceArray = [[NSMutableArray alloc] init];
      }
      return _videosSourceArray;
      }

      - (NSMutableString *)videoModelElementValueString {
      if (_videoModelElementValueString == nil) {
      _videoModelElementValueString = [[NSMutableString alloc] init];
      }
      return _videoModelElementValueString;
      }

      // 解析 xml 数据

      // 解析本地数据时 xmlData 为本地数据,解析网络数据时 xmlData 为网络下载数据
      NSXMLParser *xmlParser = [[NSXMLParser alloc] initWithData:xmlData];

      // 设置代理,需遵守协议 <NSXMLParserDelegate>
      xmlParser.delegate = self;

      // 开始解析,一旦开始解析,后续的工作都是由代理方法来实现的
      [xmlParser parse];

      // 协议方法

      // 打开文档,开始 XML 数据解析
      - (void)parserDidStartDocument:(NSXMLParser *)parser {

      // 为了保证能够多次解析,清空数据源数组
      [self.videosSourceArray removeAllObjects];
      }

      // 开始遍历节点
      - (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI
      qualifiedName:(NSString *)qName attributes:(NSDictionary *)attributeDict {

      // 判断是否是 video,如果是新建一个 videoModel 模型对象,并设置 videoId 的属性
      if ([elementName isEqualToString:@"video"]) {

      // 设置 videoId 属性
      self.videoModel = [[VideoModel alloc] init];
      self.videoModel.videoId = @([attributeDict[@"videoId"] intValue]);
      }

      // 清空上次 videoModel 内容的值
      [self.videoModelElementValueString setString:@""];
      }

      // 发现节点的内容
      - (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string {

      // 拼接 videoModel 内容的值
      [self.videoModelElementValueString appendString:string];
      }

      // 结束节点遍历
      - (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI
      qualifiedName:(NSString *)qName {

      // 如果是 video,将模型添加到数据源数组
      if ([elementName isEqualToString:@"video"]) {

      [self.videosSourceArray addObject:self.videoModel];

      } else if (![elementName isEqualToString:@"videos"]) {

      // 如果不是最后一个节点(根节点)

      // KVC 设置数值,KVC 是一种间接设置数值的技术,被称为 cocoa 的大招
      [self.videoModel setValue:self.videoModelElementValueString forKey:elementName];
      }
      }

      // 结束文档,结束 XML 数据解析
      - (void)parserDidEndDocument:(NSXMLParser *)parser {

      NSLog(@"5. 结束文档 %@", self.videosSourceArray);
      }

3、GDataXML-HTML 方式 XML 数据解析

3.1 添加 GDataXML-HTML

  • GitHub 网址

  • GDataXML-HTML 使用 ARC

  • Objective-C

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    // 添加第三方库文件
    GDataXML-HTML

    // 在 TARGETS -> Builed Settings -> Search Paths -> Header Search Paths 中添加
    // (libxml includes require that the target Header Search Paths contain)
    /usr/include/libxml2

    // 添加系统库文件 或者在 TARGETS -> Builed Settings -> Linking -> Other Linker Flags 中添加(and Other Linker Flags contain
    libxml2.2.tbd -lxml2

    // 包含头文件
    #import "GDataXMLNode.h"
  • Swift

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    // 添加第三方库文件
    GDataXML-HTML

    // 在 TARGETS -> Builed Settings -> Search Paths -> Header Search Paths 中添加
    // (libxml includes require that the target Header Search Paths contain)
    /usr/include/libxml2

    // 添加系统库文件 或者在 TARGETS -> Builed Settings -> Linking -> Other Linker Flags 中添加(and Other Linker Flags contain
    libxml2.2.tbd -lxml2

    // 创建名为 “项目名-Bridging-Header.h” 的桥接头文件,如:
    SwiftXML-Bridging-Header.h

    // 在 TARGETS -> Build Setting -> Swift Compiler - Code generation -> Objective-C Bridging Header 中添加
    // “项目名/项目名-Bridging-Header.h” 路径,如:
    SwiftXML/SwiftXML-Bridging-Header.h

    // 在创建的桥接头文件中包含头文件
    #import "GDataXMLNode.h"

3.2 GData path 数据解析

  • path 方法只能从外向内一层一层的解析 xml 数据。

  • Objective-C

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    // 实例化 GDataXMLDocument 对象
    /*
    GDataXMLDocument 是 GData 里专门用来解析 xml 数据的类
    */
    GDataXMLDocument *xmlDocument = [[GDataXMLDocument alloc] initWithData:xmlData error:NULL];

    // 解析根节点
    /*
    最外层的节点
    */
    GDataXMLElement *rootElement = [xmlDocument rootElement];

    // 解析子节点

    // 获取子节点的个数
    NSUInteger childElementCount = rootElement.childCount;

    // 获取当前节点的所有子节点
    NSArray<GDataXMLElement *> *childrenElementsArray1 = rootElement.children;
    GDataXMLElement *childrenElement1 = childrenElementsArray1[1];

    // 获取当前节点指定名字的所有子节点,必须是当前节点的直接子节点
    NSArray<GDataXMLElement *> *childrenElementsArray2 = [rootElement elementsForName:@"video"];
    GDataXMLElement *childrenElement2 = childrenElementsArray2[1];

    // 解析节点属性
    /*
    属性也是一种特殊的节点,为 GDataXMLNode 类型
    */
    NSArray<GDataXMLNode *> *elementAttributeArray = [childrenElement1 attributes];
    GDataXMLNode *attribute = elementAttributeArray[0];

    // 获取节点值

    // 将当前节点转换成字符串格式
    NSString *elementString = childrenElement1.XMLString;

    // 获取当前节点的名字
    NSString *elementName = childrenElement1.name;

    // 获取当前节点及子节点的内容值
    NSString *elementValue = childrenElement1.stringValue;

    // 将当前属性转换成字符串格式
    NSString *attributeString = attribute.XMLString;

    // 获取当前属性的名字
    NSString *attributeName = attribute.name;

    // 获取当前属性的内容值
    NSString *attributeValue = attribute.stringValue;
  • Swift

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    // 实例化 GDataXMLDocument 对象
    /*
    GDataXMLDocument 是 GData 里专门用来解析 xml 数据的类
    */
    let xmlDocument = try? GDataXMLDocument(data: xmlData)

    // 解析根节点
    /*
    最外层的节点
    */
    let rootElement: GDataXMLElement = xmlDocument!.rootElement()

    // 解析子节点

    // 获取子节点的个数
    let childElementCount: UInt = rootElement.childCount()

    // 获取当前节点的所有子节点
    let childrenElementsArray1: [AnyObject] = rootElement.children()
    let childrenElement1: GDataXMLElement = childrenElementsArray1[1] as! GDataXMLElement

    // 获取当前节点指定名字的所有子节点,必须是当前节点的直接子节点
    let childrenElementsArray2: [AnyObject] = rootElement.elementsForName("video")
    let childrenElement2: GDataXMLElement = childrenElementsArray2[1] as! GDataXMLElement

    // 解析节点属性
    /*
    属性也是一种特殊的节点,为 GDataXMLNode 类型
    */
    let elementAttributeArray: [AnyObject] = childrenElement1.attributes()
    let attribute: GDataXMLNode = elementAttributeArray[0] as! GDataXMLNode

    // 获取节点值

    // 将当前节点转换成字符串格式
    let elementString: String = childrenElement1.XMLString()

    // 获取当前节点的名字
    let elementName: String = childrenElement1.name()

    // 获取当前节点及子节点的内容值
    let elementValue: String = childrenElement1.stringValue()

    // 将当前属性转换成字符串格式
    let attributeString: String = attribute.XMLString()

    // 获取当前属性的名字
    let attributeName: String = attribute.name()

    // 获取当前属性的内容值
    let attributeValue: String = attribute.stringValue()

3.3 GData xpath 数据解析

  • xpath 方法可以指定路径解析 xml 数据。

  • Objective-C

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    // 实例化 GDataXMLDocument 对象
    /*
    GDataXMLDocument 是 GData 里专门用来解析 xml 数据的类
    */
    GDataXMLDocument *xmlDocument = [[GDataXMLDocument alloc] initWithData:xmlData error:NULL];

    // 1、绝对路径

    // 指定绝对路径值,由根路径开始的完整路径
    NSString *absoluteXpath = @"/videos/video/desc";

    // 获取指定路径下的所有子节点
    NSArray *childrenElementsArray1 = [xmlDocument nodesForXPath:absoluteXpath error:NULL];

    for (GDataXMLElement *childrenElement in childrenElementsArray1) {
    NSLog(@"childrenElement = %@", childrenElement.stringValue);
    }

    // 2、相对路径

    // 指定相对路径值,双斜杠加最后一级路径
    NSString *relativelyXpath = @"//desc";

    // 获取指定路径下的所有子节点
    NSArray *childrenElementsArray2 = [xmlDocument nodesForXPath:relativelyXpath error:NULL];

    for (GDataXMLElement *childrenElement in childrenElementsArray2) {
    NSLog(@"childrenElement = %@", childrenElement.stringValue);
    }
  • Swift

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    // 实例化 GDataXMLDocument 对象
    /*
    GDataXMLDocument 是 GData 里专门用来解析 xml 数据的类
    */
    let xmlDocument = try? GDataXMLDocument(data: xmlData)

    // 1、绝对路径

    // 指定绝对路径值,由根路径开始的完整路径
    let absoluteXpath = "/videos/video/desc"

    // 获取指定路径下的所有子节点
    let childrenElementsArray1 = try? xmlDocument?.nodesForXPath(absoluteXpath)

    for childrenElement in childrenElementsArray1!! {
    print((childrenElement as! GDataXMLElement).stringValue())
    }

    // 2、相对路径

    // 指定相对路径值,双斜杠加最后一级路径
    let relativelyXpath = "//desc"

    // 获取指定路径下的所有子节点
    let childrenElementsArray2 = try? xmlDocument?.nodesForXPath(relativelyXpath)

    for childrenElement in childrenElementsArray2!! {
    print((childrenElement as! GDataXMLElement).stringValue())
    }

3.4 GData 解析转数据模型

  • Objective-C

    • VideoModel.h

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      @interface VideoModel : NSObject

      // 为了避免服务端返回的数值型数据是 null,可以把数值型的数据设置成 NSNumber
      @property (nonatomic, copy) NSNumber *videoId;

      // copy 属性,在设置数值的时候,如果有一方是可变的,会默认做一次 copy 操作,会建立新的副本
      @property (nonatomic, copy) NSString *name;

      // 在模型中对象全都是用 copy 属性会比较安全
      @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;

      @end
    • VideoModel.m

      1
      2
      3
      4
      5
      6
      7
      8
      9
      // 开发调试输出
      - (NSString *)description {

      return [NSString stringWithFormat:@"<%@ : %p> {\n\t\tvideoId: %@,\n\t\tname: %@,\n\t\tlength: %@,\n\t\
      tvideoURL: %@,\n\t\timageURL: %@,\n\t\tdesc: %@,\n\t\
      tteacher: %@\n\t}", [self class], self, self.videoId,
      self.name, self.length, self.videoURL, self.imageURL,
      self.desc, self.teacher];
      }
    • NSArray+LocaleLog.m

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      /*
      Xcode 没有针对国际化语言做特殊处理,直接 Log 数组,只打印 UTF8 的编码,不能显示中文。重写这个方法,就能够解决输出问题,
      这个方法是专门为了本地话提供的一个调试方法,只要重写,不需要导入头文件,程序中所有的 NSLog 数组的方法,都会被替代。
      */

      - (NSString *)descriptionWithLocale:(id)locale {

      NSMutableString *strM = [NSMutableString stringWithString:@"(\n"];

      [self enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {

      [strM appendFormat:@"\t%@", obj];
      if (idx != self.count - 1) [strM appendFormat:@",\n"];
      }];

      [strM appendString:@"\n)"];

      return strM;
      }
    • ViewController.m

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      // 数据源数组
      @property (nonatomic, strong) NSMutableArray *videosSourceArray;

      // 数据模型
      @property (nonatomic, strong) VideoModel *videoModel;

      - (NSMutableArray *)videosSourceArray {
      if (_videosSourceArray == nil) {
      _videosSourceArray = [[NSMutableArray alloc] init];
      }
      return _videosSourceArray;
      }

      // 解析 xml 数据

      // 从本地文件中读取数据
      NSData *xmlData = [NSData dataWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"XmlDataFile" ofType:@"xml"]];

      // GDataXMLDocument 是 GData 里专门用来解析 xml 数据的类
      GDataXMLDocument *xmlDocument = [[GDataXMLDocument alloc] initWithData:xmlData error:NULL];

      // 解析根节点
      for (GDataXMLElement *childElement in xmlDocument.rootElement.children) {

      self.videoModel = [[VideoModel alloc] init];
      [self.videosSourceArray addObject:self.videoModel];

      // 解析子节点属性值
      for (GDataXMLNode *attribute in childElement.attributes) {

      [self.videoModel setValue:attribute.stringValue forKey:attribute.name];
      }

      // 解析子节点的子节点
      for (GDataXMLElement *propertyElement in childElement.children) {

      [self.videoModel setValue:propertyElement.stringValue forKey:propertyElement.name];
      }
      }

      NSLog(@"videosSourceArray: %@", self.videosSourceArray);

4、XML 和 JSON 两种数据交换格式的比较

  • 目前,在 web 开发领域,主要的数据交换格式有 XML 和 JSON,对于 XML 相信每一个 web developer 都不会感到陌生; 相比之下,JSON 可能对于一些新步入开发领域的新手会感到有些陌生,也可能你之前已经听说过,但对于 XML 和 JSON 的不同之处可能会不怎么了解。对于在 Ajax 开发中,是选择 XML 还是 JSON,一直存在着争议,个人还是比较倾向于 JSON 的,虽然 JSON 才处于起步阶段,但我相信 JSON 最终会取代 XML 成为 Ajax 的首选,到时 Ajax 可能要更名为 Ajaj(Asynchronous JavaScript and JSON)了;

  • 1、数据交换格式比较之关于 XML 和 JSON:

    • XML:extensible markup language,一种类似于 HTML 的语言,他没有预先定义的标签,使用 DTD(document type definition) 文档类型定义来组织数据;格式统一,跨平台和语言,早已成为业界公认的标准。具体的可以问 Google 或百度。相比之 JSON 这种轻量级的数据交换格式,XML 可以称为重量级的了。

    • JSON : JavaScript Object Notation 是一种轻量级的数据交换格式。易于人阅读和编写。同时也易于机器解析和生成。它基于 JavaScript Programming Language , Standard ECMA-262 3rd Edition – December 1999 的一个子集。JSON 采用完全独立于语言的文本格式,但是也使用了类似于 C 语言家族的习惯(包括 C, C++, C#, Java, JavaScript, Perl, Python 等)。这些特性使 JSON 成为理想的数据交换语言。

  • 2、数据交换格式比较之关于轻量级和重量级:

    • 轻量级和重量级是相对来说的,那么 XML 相对于 JSON 的重量级体现在哪呢?我想应该体现在解析上,XML 目前设计了两种解析方式:DOM 和 SAX;

    • DOM是把一个数据交换格式 XML 看成一个 DOM 对象,需要把 XML 文件整个读入内存,这一点上 JSON 和 XML 的原理是一样的,但是 XML 要考虑父节点和子节点,这一点上 JSON 的解析难度要小很多,因为 JSON 构建于两种结构:key/value,键值对的集合;值的有序集合,可理解为数组;

    • SAX 不需要整个读入文档就可以对解析出的内容进行处理,是一种逐步解析的方法。程序也可以随时终止解析。这样,一个大的文档就可以逐步的、一点一点的展现出来,所以 SAX 适合于大规模的解析。这一点,JSON 目前是做不到得。

    • 所以,JSON 和 XML 的轻/重量级的区别在于:JSON 只提供整体解析方案,而这种方法只在解析较少的数据时才能起到良好的效果;而 XML 提供了对大规模数据的逐步解析方案,这种方案很适合于对大量数据的处理。

  • 3、数据交换格式比较之关于数据格式编码及解析的难度:

    • 在编码上,虽然 XML 和 JSON 都有各自的编码工具,但是 JSON 的编码要比 XML 简单,即使不借助工具,也可以写出 JSON 代码,但要写出好的 XML 代码就有点困难;与 XML 一样,JSON 也是基于文本的,且它们都使用 Unicode 编码,且其与数据交换格式 XML 一样具有可读性。

    • 主观上来看,JSON 更为清晰且冗余更少些。JSON 网站提供了对 JSON 语法的严格描述,只是描述较简短。从总体来看,XML 比较适合于标记文档,而 JSON 却更适于进行数据交换处理。

    • 在解析上,在普通的 web 应用领域,开发者经常为 XML 的解析伤脑筋,无论是服务器端生成或处理 XML,还是客户端用 JavaScript 解析 XML,都常常导致复杂的代码,极低的开发效率。

    • 实际上,对于大多数 web 应用来说,他们根本不需要复杂的 XML 来传输数据,XML 宣称的扩展性在此就很少具有优势;许多 Ajax 应用甚至直接返回 HTML 片段来构建动态 web 页面。和返回 XML 并解析它相比,返回 HTML 片段大大降低了系统的复杂性,但同时缺少了一定的灵活性。同 XML 或 HTML 片段相比,数据交换格式 JSON 提供了更好的简单性和灵活性。在 web serivice 应用中,至少就目前来说 XML 仍有不可动摇的地位。

文章目录
  1. 1. 前言
  2. 2. 1、XML 数据
    1. 2.1. 1.1 构建 XML
    2. 2.2. 1.2 创建文件声明
    3. 2.3. 1.3 创建根元素
    4. 2.4. 1.4 命名元素
    5. 2.5. 1.5 嵌套元素
    6. 2.6. 1.6 添加属性
    7. 2.7. 1.7 构造良好并且有效的 XML
    8. 2.8. 1.8 使用实体
    9. 2.9. 1.9 避免错误
    10. 2.10. 1.10 iOS XML 解析方式
  3. 3. 2、系统方式 XML 数据解析
    1. 3.1. 2.1 系统方式数据解析
    2. 3.2. 2.2 系统方式解析转数据模型
  4. 4. 3、GDataXML-HTML 方式 XML 数据解析
    1. 4.1. 3.1 添加 GDataXML-HTML
    2. 4.2. 3.2 GData path 数据解析
    3. 4.3. 3.3 GData xpath 数据解析
    4. 4.4. 3.4 GData 解析转数据模型
  5. 5. 4、XML 和 JSON 两种数据交换格式的比较
隐藏目录