iOS 网络编程:XML解析

1 XML文档结构

1.1 简介

XML 指可扩展标记语言(eXtensible Markup Language)。XML 被设计用来传输和存储数据。其非常像HTML的标记语言,但与之不同的是,XML是用来传输和存储数据;而HTML是用来显示数据;同时XML 标签没有被预定义,需要自行定义标签,而HTML的标签是有明确的语义的。

XML 文档必须包含根元素。该元素是所有其他元素的父元素。XML 文档中的元素形成了一棵文档树。这棵树从根部开始,并扩展到树的最底端。所有的元素都可以有子元素。

iOS 网络编程:XML解析

图 4

 1 <?xml version="1.0" encoding="ISO-8859-1"?>
 2 <bookstore>
 3     <book category="CHILDREN">
 4         <title>Harry Potter</title>
 5         <author>J K. Rowling</author>
 6         <year>2005</year>
 7         <price>29.99</price>
 8     </book>
 9     <book category="WEB">
10         <title>Learning XML</title>
11         <author>Erik T. Ray</author>
12         <year>2003</year>
13         <price>39.95</price>
14     </book>
15 </bookstore>

1.2 语法规则

XML 的语法规则很简单,且很有逻辑。这些规则很容易学习,也很容易使用。XML文档的基本架构可以分为下面几个部分:

1) 声明

如上述的<?xml version="1.0" encoding="ISO-8859-1"?>就是XML的声明,它定义了XML文件的版本和使用的字符集,这里为1.0版,使用字符集为ISO-8859-1。

2) 根元素

XML 文档必须有一个元素是所有其他元素的父元素。该元素称为根元素。

3) 子元素

XML 元素指的是从(且包括)开始标签直到(且包括)结束标签的部分。一个元素可以包含:

  • 其他元素
  • 文本
  • 属性
  • 或混合以上所有...

4) 属性

属性(Attribute)提供有关元素的额外信息,属性定义在开始标签之中,并且属性值必须被引号包围,不过单引号和双引号均可使用

<file type="gif">computer.gif</file>

5) 命名空间

在 XML 中,元素名称是由开发者定义的,当两个不同的文档使用相同的元素名时,就会发生命名冲突。为了避免这种元素名称冲突,所以引入了命名空间,命名空间是在元素的开始标签的 xmlns 属性中定义的。命名空间声明的语法如下。xmlns:前缀="URI"

 1 <root>
 2 
 3 <h:table xmlns:h="http://www.w3.org/TR/html4/">
 4 <h:tr>
 5 <h:td>Apples</h:td>
 6 <h:td>Bananas</h:td>
 7 </h:tr>
 8 </h:table>
 9 
10 <f:table xmlns:f="http://www.w3cschool.cc/furniture">
11 <f:name>African Coffee Table</f:name>
12 <f:width>80</f:width>
13 <f:length>120</f:length>
14 </f:table>
15 
16 </root>

在上面的实例中,<table> 标签的 xmlns 属性定义了 h: 和 f: 前缀的合格命名空间。当命名空间被定义在元素的开始标签中时,所有带有相同前缀的子元素都会与同一个命名空间相关联。

1.3 注意规则

1) 所有的 XML 元素都必须有一个关闭标签

      在XML 中,省略关闭标签是非法的。所有元素都必须有关闭标签,并且有两种:显示和非显示的

<p>This is a paragraph.</p>
<br />

2) XML 标签对大小写敏感

XML 标签对大小写敏感。标签 <Letter> 与标签 <letter> 是不同的。必须使用相同的大小写来编写开始标签和关闭标签。

3) 实体引用

在XML 中,一些字符拥有特殊的意义。如果您把字符 "<" 放在 XML 元素中,会发生错误,这是因为解析器会把它当作新元素的开始。为了避免这个错误,请用实体引用来代替 "<" 字符:

在XML中,有 5 个预定义的实体引用:

&lt;

<

less than

&gt;

>

greater than

&amp;

&

ampersand

&apos;

'

apostrophe

&quot;

"

quotation mark

4) XML 中的注释

在 XML 中编写注释的语法与 HTML 的语法很相似。

<!-- This is a comment -->

5) 在 XML 中,空格会被保留

HTML 会把多个连续的空格字符裁减(合并)为一个,而在 XML 中,文档中的空格不会被删减。

6) XML 以 LF 存储换行

在 Windows 应用程序中,换行通常以一对字符来存储:回车符(CR)和换行符(LF)。 在 Unix 和 Mac OSX 中,使用 LF 来存储新行。 在旧的 Mac 系统中,使用 CR 来存储新行。 XML 以 LF 存储换行。

2 XML文档解析

解析XML文档时,目前有两种流行的模式:SAX和DOM。

2.1 解析模式

1) SAM模式

     SAM时一种基于事件驱动的解析模式。解析XML文档时,程序从上到下读取XML文档,如果遇到开始标签、结束标签和属性等,就会触发相应的事件。但是这种解析XML文件的方式有一个弊端,那就是只能读取XML文档,不能写入XML文档,它的优点是解析速度快适合大文件

2) DOM模式

      DOM模式将XML文档映射为一棵树状结构进行分析,获取节点的内容以及相关属性,或是新增、删除和修改节点的内容。XML解析器在加载XML文件以后,DOM模式将XML文件的元素视为树状结构的节点,一次性读入到内存中。如果文档比较大,解析速度就会变慢,所以该模式适合小文件。但是在DOM模式中,有一点是SAX无法取代的,那就是DOM能够修改XML文档

2.2 解析框架

目前流行的解析框架有:

  • NSXMLParser框架:这是IOS自带解析框架,其使用的是SAM解析模式;
  • TBXML框架:这是第三方框架,使用DOM模式。

3 NSXMLParser框架

3.1 简介

NSXMLParser框架采用SAM模式进行解析,即其是基于事件驱动的方式进行解析。NSXMLParser框架的解析工作是交给了NSXMLParserDelegate实现类去完成。在委托中定义了很多回调方法,当进行SAX解析XML文件过程中,遇到开始标签、结束标签、文档开始、文档结束和字符串等XML语法时就会触发相应的回调方法。

其中主要的有5个回调方法,其它相关的回调方法可以参考Apple帮助文档:

  • -parserDidStartDocument:(NSXMLParser *)parser:在文档开始的时候触发
  • -parserDidEndDocument:(NSXMLParser *)parser:在文档结束时触发
  • -parser:didStartElement:namespaceURI:qualifiedName:attributes:遇到开始标签时触发,其中namespaceURI是命名空间,attributes是字典类型的属性集合
  • - parser:didEndElement:namespaceURI:遇到结束标签时触发
  • - parser:foundCharacters:遇到字符串时触发

3.2 第一个应用程序

1) XML文件:note.xml

 1 <?xml version="1.0" encoding="UTF-8"?>
 2 <Notes>
 3   <Note id="1">
 4     <CDate>2012-12-21</CDate>
 5     <Content>早上8点钟到公司</Content>
 6     <UserID>tony</UserID>
 7   </Note>
 8   <Note id="2">
 9     <CDate>2012-12-22</CDate>
10     <Content>发布iOSBook1</Content>
11     <UserID>tony</UserID>
12   </Note>
13   <Note id="3">
14     <CDate>2012-12-23</CDate>
15     <Content>发布iOSBook2</Content>
16     <UserID>tony</UserID>
17   </Note>
18   <Note id="4">
19     <CDate>2012-12-24</CDate>
20     <Content>发布iOSBook3</Content>
21     <UserID>tony</UserID>
22   </Note>
23   <Note id="5">
24     <CDate>2012-12-25</CDate>
25     <Content>发布2016奥运会应用iPhone版本</Content>
26     <UserID>tony</UserID>
27   </Note>
28   <Note id="6">
29     <CDate>2012-12-26</CDate>
30     <Content>发布2016奥运会应用iPad版本</Content>
31     <UserID>tony</UserID>
32   </Note>
33 </Notes>

2) 委托类头文件

1 @interface ViewController : UIViewController <NSXMLParserDelegate>

3 @property (strong,nonatomic) NSMutableArray *notes;  //解析出的数据内部是字典类型的数组
4 @property (strong,nonatomic) NSString *currentTagName;  //当前标签的名字

6 -(void)start;  //自定义方法:开始解析

8 @end

3) 委托类源文件

 1 @implementation ViewController
 2 - (void)viewDidLoad {
 3     [super viewDidLoad];
 4     [self start];
 5 }
 6 
 7 -(void)start
 8 {
 9     NSString* path = [[NSBundle mainBundle] pathForResource:@"Notes" ofType:@"xml"]; //获取XML文件路径
10     NSURL *url = [NSURL fileURLWithPath:path];
11     
12     NSXMLParser *parser = [[NSXMLParser alloc] initWithContentsOfURL:url]; //创建NSXMLParser 对象
13     parser.delegate = self; //设置委托对象
14     [parser parse];  //开始进行解析
15     NSLog(@"解析完成...");
16 }
17 
18 - (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName //遇到一个开始标签时候触发
19   namespaceURI:(NSString *)namespaceURI
20   qualifiedName:(NSString *)qualifiedName
21   attributes:(NSDictionary *)attributeDict
22 {
23     _currentTagName = elementName;
24     if ([_currentTagName isEqualToString:@"Note"]) {
25         NSString *_id = [attributeDict objectForKey:@"id"];
26         NSMutableDictionary *dict = [NSMutableDictionary new];
27         [dict setObject:_id forKey:@"id"];
28         [_notes addObject:dict];  //为新的标签创建一个对应的字典,并添加到数组中
29     }   
30 }
31 
32 - (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string  //遇到字符串时候触发
33 {
34     //替换回车符和空格
35     string =[string stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
36     if ([string isEqualToString:@""]) {
37         return;
38     }
39     //NSLog(string);
40     NSMutableDictionary *dict = [_notes lastObject];//获取数组中最后一个元素(即当前标签所对应的字典)
41     if ([_currentTagName isEqualToString:@"CDate"] && dict) {
42         [dict setObject:string forKey:@"CDate"]; //往字典中添加键值对。
43     }
44     if ([_currentTagName isEqualToString:@"Content"] && dict) {
45         [dict setObject:string forKey:@"Content"];
46     }
47     if ([_currentTagName isEqualToString:@"UserID"] && dict) {
48         [dict setObject:string forKey:@"UserID"];
49     }
50 }
51 
52 - (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName //遇到结束标签时候出发
53   namespaceURI:(NSString *)namespaceURI
54  qualifiedName:(NSString *)qName;
55 {
56     self.currentTagName = nil;
57 }
58 - (void)parserDidStartDocument:(NSXMLParser *)parser  //文档开始的时候触发
59 {
60     _notes = [NSMutableArray new];
61     NSLog(@"文档开始的时候触发");
62 }
63 - (void)parserDidEndDocument:(NSXMLParser *)parser //遇到文档结束时候触发
64 {
65     [[NSNotificationCenter defaultCenter] postNotificationName:@"reloadViewNotification" object:self.notes userInfo:nil];
66     self.notes = nil;
67 }
68 - (void)parser:(NSXMLParser *)parser parseErrorOccurred:(NSError *)parseError  //文档出错的时候触发
69 {
70     NSLog(@"%@",parseError);
71 }
72 @end

3.3 XML解析过程

使用NSXMLParser解析XML文件非常简单,只需实现NSXMLParserDelegate协议相应的方法即可,如下图所示:

1) 首先,实现NSXMLParserDelegate协议,并在该协议相应的回调方法中,实现具体解析内容;

2) 然后,获取XML文件的路径;

3) 接着,调用NSXMLParser类的构造函数initWithContentsOFURL来创建NSXMLParser对象;

4) 最后,给NSXMLParser对象指定委托对象,最后调用该对象的Parse方法启动解析;

iOS 网络编程:XML解析

4 TBXML解析

4.1 简介

TBXML是轻量级的DOM模式解析库,它只能读取XML文档,不能写XML文档,但是解析XML是最快的。

其中TBXML框架是一个开源项目,其相关的网络地址为:

从下载的TBXML.h文件中,可以 了解其内部的结构和提供的功能方法:

 1 // ================================================================================================
 2 //  Structures
 3 // ================================================================================================
 4 /** The TBXMLAttribute structure holds information about a single XML attribute. The structure holds the attribute name, value and next sibling attribute. This structure allows us to create a linked list of attributes belonging to a specific element.
 5  */
 6 typedef struct _TBXMLAttribute {
 7     char * name;
 8     char * value;
 9     struct _TBXMLAttribute * next;
10 } TBXMLAttribute;
11 
12 /** The TBXMLElement structure holds information about a single XML element. The structure holds the element name & text along with pointers to the first attribute, parent element, first child element and first sibling element. Using this structure, we can create a linked list of TBXMLElements to map out an entire XML file.*/
13 typedef struct _TBXMLElement {
14     char * name;
15     char * text;
16     
17     TBXMLAttribute * firstAttribute;
18     struct _TBXMLElement * parentElement;
19 
20     struct _TBXMLElement * firstChild;
21     struct _TBXMLElement * currentChild;
22     
23     struct _TBXMLElement * nextSibling;
24     struct _TBXMLElement * previousSibling;
25 } TBXMLElement;
26 
27 /** The TBXMLElementBuffer is a structure that holds a buffer of TBXMLElements. When the buffer of elements is used, an additional buffer is created and linked to the previous one. This allows for efficient memory allocation/deallocation elements.
28  */
29 typedef struct _TBXMLElementBuffer {
30     TBXMLElement * elements;
31     struct _TBXMLElementBuffer * next;
32     struct _TBXMLElementBuffer * previous;
33 } TBXMLElementBuffer;
34 
35 /** The TBXMLAttributeBuffer is a structure that holds a buffer of TBXMLAttributes. When the buffer of attributes is used, an additional buffer is created and linked to the previous one. This allows for efficient memeory allocation/deallocation of attributes.
36  */
37 typedef struct _TBXMLAttributeBuffer {
38     TBXMLAttribute * attributes;
39     struct _TBXMLAttributeBuffer * next;
40     struct _TBXMLAttributeBuffer * previous;
41 } TBXMLAttributeBuffer;

由于提供的方法不方便粘贴进入文档,所以具体方法可以参考TBXML.h文件。

4.2 第一个应用程序

4.2.1 环境配置

因为TBXML框架是第三方的开发库,所以在Xcode中使用需要手动添加相应的文件并相应配置一些内容。具体过程为:

1) 添加如下四个TBXML所依赖的Framwork和库

  • Foundation.framework
  • UIKit.framework
  • CoreGraphics.framework
  • Libz.tbd

iOS 网络编程:XML解析

图 6

iOS 网络编程:XML解析

图 7

2) 在Object-C语言中,需要在工程中添加预编译头问津啊PrefixHeader.pch,并在该文件中添加相应的两行代码。

iOS 网络编程:XML解析

图 8

iOS 网络编程:XML解析

图 9

3) 将在gidhut网站中下载的文件夹添加到工程中,如图 10所示是已经添加完成的项目目录。

iOS 网络编程:XML解析

图 10

4.2.2 示例代码

由于解析XML文件必须有具体的XML文件内容格式,所以本例还是以2.3.2小节的Note.xml文件进行解析。其中解析类的.h文件如下所示。

 1 #import <UIKit/UIKit.h>
 2 #import "TBXML.h" //必须引入的头文件
 3 
 4 @interface ViewController : UIViewController 
 5 
 6 //解析出的数据内部是字典类型
 7 @property (strong,nonatomic) NSMutableArray *notes;
 8 
 9 //开始解析方法
10 -(void)startTBXMLParser;
11 
12 @end

而具体的.m文件内容如下:

 1 -(void)startTBXMLParser
 2 {
 3     _notes = [NSMutableArray new];
 4     TBXML* tbxml = [[TBXML alloc] initWithXMLFile:@"Notes.xml" error:nil];//读取XML文件,并以树形结构创建对象。
 5     TBXMLElement * root = tbxml.rootXMLElement;  //获取XML树形的根节点
 6     
 7     // if root element is valid
 8     if (root) {
 9         TBXMLElement * noteElement = [TBXML childElementNamed:@"Note" parentElement:root];//获取第一个子元素
10         while ( noteElement != nil) {
11             NSMutableDictionary *dict = [NSMutableDictionary new];
12             TBXMLElement *CDateElement = [TBXML childElementNamed:@"CDate" parentElement:noteElement];
13             if ( CDateElement != nil) {
14                 NSString *CDate = [TBXML textForElement:CDateElement];  //获取子元素的内容。
15                 [dict setValue:CDate forKey:@"CDate"];
16             }
17             
18             TBXMLElement *ContentElement = [TBXML childElementNamed:@"Content" parentElement:noteElement];
19             if ( ContentElement != nil) {
20                 NSString *Content = [TBXML textForElement:ContentElement];
21                 [dict setValue:Content forKey:@"Content"];
22                 //NSLog(Content);
23             }
24             
25             TBXMLElement *UserIDElement = [TBXML childElementNamed:@"UserID" parentElement:noteElement];
26             if ( UserIDElement != nil) {
27                 NSString *UserID = [TBXML textForElement:UserIDElement];
28                 [dict setValue:UserID forKey:@"UserID"];
29             }
30             
31             //获得ID属性
32             NSString *_id = [TBXML valueOfAttributeNamed:@"id" forElement:noteElement error:nil];
33 [dict setValue:_id forKey:@"id"];
34 [_notes addObject:dict];
35             noteElement = [TBXML nextSiblingNamed:@"Note" searchFromElement:noteElement];
36         }
37     }
38     
39     NSLog(@"解析完成...");
40     [[NSNotificationCenter defaultCenter] postNotificationName:@"reloadViewNotification" object:self.notes userInfo:nil];
41     self.notes = nil;
42 }

4.3 XML解析过程

利用TBXML框架解析XML的过程是以树状的结构加载XML文件,树中的每个节点都是XML的一个元素,所以就可以遍历树中的每个节点(XML的元素),从而即可解析整棵树(整个XML文件)。

4.3.1 加载XML文件

XML文件在TBXML框架中以TBXML对象表示,并且该框架提供了多种方式加载XML文件。

  • 文件名方式: 
TBXML * tbxml = [[TBXML tbxmlWithXMLFile:@"books.xml"] retain];
  • 扩展名方式:
TBXML * tbxml = [[TBXML tbxmlWithXMLFile:@"books" fileExtension:@"xml"] retain];
  • NSData对象方式:
TBXML * tbxml = [[TBXML tbxmlWithXMLData:myXMLData] retain];
  • URL路径方式:
TBXML * tbxml = [[TBXML tbxmlWithURL:[NSURL URLWithString: @"http://www.w3schools.com/XML/note.xml"]] retain];
  • XML 字符方式:
TBXML * tbxml = [[TBXML tbxmlWithXMLString:@"<root><elem1 attribute1=\"elem1-attribute1\"/><elem2 attribute2=\"attribute2\"/></root>"] retain];

4.3.2 获取XML元素

在XML文件中所有的节点都是元素,最顶端的是根元素,其它节点都是根元素的子元素(直接或是间接)。

  • 获取根元素

每个XML文档都只有一个根元素,所以可以获取TBXML对象的rootXMLElement成员变量,如:

TBXMLElement * rootXMLElement = tbxml.rootXMLElement;
  • 获取子元素

    TBXML对象提供一个childElementNamed: parentElement:方法获取子元素。其中该方法通过指定相应的父元素对象和子元素标签名来查找子元素对象,其从文档中查找第一个符合条件的对象。

[TBXML childElementNamed:@"author" parentElement:root]

4.3.3 获取XML属性

在XML文件中每个元素都可能有元素,所以可以利用TBXML对象的valueOfAttributeNamed: forElement:方法来查询相应元素的属性。

NSString *name = [TBXML valueOfAttributeNamed:@"name" forElement:author];

4.3.4 获取XML元素的文本

在获取XML元素对象后,就可以通过TBXML对象的textForElement:方法获取指定元素的文本值。

NSString *description = [TBXML textForElement:descriptionElement];

4.3.5 遍历不知名的元素或属性

由于XML文档是一个树状的结构,每个元素都有指向其第一个子元素的指针firstChild,从而能够获取子元素。同时所有子元素都形成一条链表,所有可以通过元素的nextSibling指针,获取其兄弟元素。

对于XML元素中的属性,也可以通过同样的方式进行遍历。可以通过TBXMLElement对象的firstAttribute指针获取第一个元素,然后通过TBXMLAttribute属性对象的next指针获取下一个兄弟属性。

 1 - (void)loadUnknownXML 
 2 {
 3     tbxml = [[TBXML tbxmlWithXMLFile:@"books.xml"] retain];// Load and parse the books.xml file
 4 
 5     if (tbxml.rootXMLElement)
 6         [self traverseElement:tbxml.rootXMLElement];
 7 
 8     [tbxml release];// release resources
 9 }
10 
11 - (void) traverseElement:(TBXMLElement *)element 
12 {
13 do 
14 {
15         NSLog(@"%@",[TBXML elementName:element]);// Display the name of the element
16 
17         // Obtain first attribute from element
18         TBXMLAttribute * attribute = element->firstAttribute;
19     while (attribute)
20     {
21             // Display name and value of attribute to the log window
22             NSLog(@"%@->%@ = %@",   [TBXML elementName:element],
23                                 [TBXML attributeName:attribute],
24                                 [TBXML attributeValue:attribute]);
25 
26             attribute = attribute->next;// Obtain the next attribute
27         }
28 
29         // if the element has child elements, process them
30         if (element->firstChild) 
31                 [self traverseElement:element->firstChild];//递归查询子元素
32 
33     } while ((element = element->nextSibling));  // Obtain next sibling element
34 }

5 参考文献

[1] Runoob.com;

[2] Introduction to Event-Driven XML Programming Guide for Cocoa;(simply need to parse XML and extract information from an existing source of XML。NSXMLParser )

[3] Introduction to Tree-Based XML Programming Guide for Cocoa;

[4] TBXML网站

上一篇:【OpenGL游戏开发之二】OpenGL常用API


下一篇:wsdl透明解析