iOS - XML 数据解析

前言

	@interface NSXMLParser : NSObject
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 版本(),甚至包含字符编码,比如针对 Unicode 的 <?xml version="1.0" encoding="utf-8"?>。因为这个声明必须出现在文件的开头,所以如果打算将多个小的 XML 文件合并为一个大 XML 文件,则可以忽略这个可选信息。

1.3 创建根元素

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

  • 清单 1. 根元素

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

1.4 命名元素

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

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

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

  • 清单 2. 更多元素

    	<?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 元素

    	<?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 文件

    	<?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

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

1.8 使用实体

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

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

  • 清单 6. ENTITY

    	<!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 数据

      	// 解析本地数据时 xmlData 为读取的本地数据,解析网络数据时 xmlData 为网络下载的数据
      NSXMLParser *xmlParser = [[NSXMLParser alloc] initWithData:xmlData]; // 设置代理,需遵守协议 <NSXMLParserDelegate>
      xmlParser.delegate = self; // 开始解析,一旦开始解析,后续的工作都是由代理方法来实现的
      [xmlParser parse];
    • 协议方法

      	// 打开文档,开始 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 数据

      	// 解析本地数据时 xmlData 为读取的本地数据,解析网络数据时 xmlData 为网络下载的数据
      let xmlParser = NSXMLParser(data: xmlData!) // 设置代理,需遵守协议 <NSXMLParserDelegate>
      xmlParser.delegate = self // 开始解析,一旦开始解析,后续的工作都是由代理方法来实现的
      xmlParser.parse()
    • 协议方法

      	// 打开文档,开始 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

      	@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

      	// 开发调试输出
      - (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

      	/*
      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

      	// 数据源数组
      @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 网址:https://github.com/graetzer/GDataXML-HTML

  • GDataXML-HTML 使用 ARC

  • Objective-C

    	// 添加第三方库文件
    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

    	// 添加第三方库文件
    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

    	// 实例化 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

    	// 实例化 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

    	// 实例化 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

    	// 实例化 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

      	@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

      	// 开发调试输出
      - (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

      	/*
      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

      	// 数据源数组
      @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 仍有不可动摇的地位。

上一篇:mysql添加外键


下一篇:[翻译] TGLStackedViewController