SAX,全称Simple API for XML。
SAX的原理
大多数SAX实现都会产生以下类型的事件:
在文档的开始和结束时触发文档处理事件。 在文档内每一XML元素接受解析的前后触发元素事件。 任何元数据通常都由单独的事件交付。 在处理文档的DTD或Schema时产生DTD或Schema事件。 产生错误事件用来通知主机 应用程序 解析错误。
DOM
DOM= Document Object Model,文档对象模型,DOM可以以一种独立于平台和语言的方式访问和修改一个文档的内容和结构。
DOM实际上是以面向对象方式描述的文档模型。DOM定义了表示和修改文档所需的对象、这些对象的行为和属性以及这些对象之间的关系。可以把DOM认为是页面上数据和结构的一个树形表示,不过页面当然可能并不是以这种树的方式具体实现。
根据W3C DOM规范,DOM是HTML与XML的应用编程接口(API),DOM将整个页面映射为一个由层次节点组成的文件。有1级、2级、3级共3个级别。
节点
根据 DOM,HTML 文档中的每个成分都是一个节点。 DOM 是这样规定的: 整个文档是一个文档节点 每个 HTML 标签是一个元素节点 包含在 HTML 元素中的文本是文本节点 每一个 HTML 属性是一个属性节点 注释属于注释节点Node 层次
节点彼此都有等级关系。 HTML 文档中的所有节点组成了一个文档树(或节点树)。HTML 文档中的每个元素、属性、文本等都代表着树中的一个节点。树起始于文档节点,并由此继续伸出枝条,直到处于这棵树最低级别的所有文本节点
一般XML的解析器有SAX解析和DOM解析两种方式、相比之下SAX比较小巧精干,耗费的内存小。这是因为其设计思想与DOM完全不一样,一边得到数据一边解析,由回调的方式通知得到的数据,没有了DOM树的概念。
开发XML解析程序的时候,需要注意到XML文件一般都比较大,如果使用DOM的话,消费的内存量肯定很多。所以在iPhone中上面这两种解析器 只能使用SAX的解析方式。DOM方式只能在模拟器上使用(比如NSXMLDocument类),放到实际设备上就不管用了。(不过,在下面的章节中我将 介绍一种使用DOM的第三方方法,对于小的XML文档还是值得一用的。)
libxml2 vs NSXMLParser
一般是使用libxml2的SAX解析器呢,还是使用NSXMLParser能,我们通过下面的SDK中附属的例子XMLPerformance来做个测试。
相同的XML文档由网络下载,然后解析,比较的结果如下 :
下载用时 | 解析用时 | 合计 | |
---|---|---|---|
NSXMLParser | 1.419s | 5.525s | 7.134s |
libxml2 | 2.520s | 2.247s | 2.646s |
可以看到,libxml2比NSXMLParser快得多。这与它们处理的方式有些关系,NSXMLParser中调用SAX API的时候,参数是作为字符串传递的,需要先转换为NSString或者是NSDictionary对象,并且它不像libxml2那样是一边下载一边 解析,需要整个文件下载完了才开始解析。所以说建议一般使用libxml2。
libxml2的例子
项目中添加libxml
首先需要将libxml添加到你的工程项目中。
我们知道,当向项目中添加外部库的时候,如果是程序框架的,比如UIKit.framework,Foundation.framework等放在 Sysytem/Library/Frameworks 目录下。SDK放在 /Developer/Platforms/iPhoneOS.platform/Developer/SDKs 目录下。
但是由于libxml是UNIX的库、位于SDK文件夹的usr/lib下。头文件位于 usr/include 下。
在 libxml 目录下将 libxml2.2.dylib(或更高版本)添加到项目中。将头文件路径 usr/include/libxml2 也添加到包含路径中。
以下是libxml2.2.dylib的路径 /Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS${VER}sdk/usr/lib/libxml2.2.dylib 以下是头文件的路径 /Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS${VER}.sdk/usr/include/libxml2
libxml中的SAX解析器
用过SAX解析器的朋友都知道,SAX就是事先登录一些处理函数,当XML解析到属性或要素的时候,回调登录的处理函数。
以下是一个例子,DownloadOperation实现网络文件的下载,同时交给libxml2的SAX解析器处理:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
| // DownloadOperation.h
#import <Foundation/Foundation.h>
#import <libxml/tree.h>
@interface DownloadOperation : NSOperation
{
NSURLRequest* _request;
NSURLConnection* _connection;
xmlParserCtxtPtr _parserContext;
BOOL _isExecuting, _isFinished;
BOOL _isChannel, _isItem;
NSMutableDictionary* _channel;
NSMutableDictionary* _currentItem;
NSMutableString* _currentCharacters;
}
// Property
@property (readonly) NSDictionary* channel;
// Initialize
- (id)initWithRequest:(NSURLRequest*)request;
@end
|
首先、#import了libxml/tree.h头文件。然后声明了一个 xmlParserCtxtPtr 变量作为解析器的实例。
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
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
| // DownloadOperation.m
#import "DownloadOperation.h"
@interface DownloadOperation (private)
- (void)startElementLocalName:(const xmlChar*)localname
prefix:(const xmlChar*)prefix
URI:(const xmlChar*)URI
nb_namespaces:(int)nb_namespaces
namespaces:(const xmlChar**)namespaces
nb_attributes:(int)nb_attributes
nb_defaulted:(int)nb_defaulted
attributes:(const xmlChar**)attributes;
- (void)endElementLocalName:(const xmlChar*)localname
prefix:(const xmlChar*)prefix URI:(const xmlChar*)URI;
- (void)charactersFound:(const xmlChar*)ch
len:(int)len;
@end
static void startElementHandler(
void* ctx,
const xmlChar* localname,
const xmlChar* prefix,
const xmlChar* URI,
int nb_namespaces,
const xmlChar** namespaces,
int nb_attributes,
int nb_defaulted,
const xmlChar** attributes)
{
[(DownloadOperation*)ctx
startElementLocalName:localname
prefix:prefix URI:URI
nb_namespaces:nb_namespaces
namespaces:namespaces
nb_attributes:nb_attributes
nb_defaulted:nb_defaulted
attributes:attributes];
}
static void endElementHandler(
void* ctx,
const xmlChar* localname,
const xmlChar* prefix,
const xmlChar* URI)
{
[(DownloadOperation*)ctx
endElementLocalName:localname
prefix:prefix
URI:URI];
}
static void charactersFoundHandler(
void* ctx,
const xmlChar* ch,
int len)
{
[(DownloadOperation*)ctx
charactersFound:ch len:len];
}
static xmlSAXHandler _saxHandlerStruct = {
NULL, /* internalSubset */
NULL, /* isStandalone */
NULL, /* hasInternalSubset */
NULL, /* hasExternalSubset */
NULL, /* resolveEntity */
NULL, /* getEntity */
NULL, /* entityDecl */
NULL, /* notationDecl */
NULL, /* attributeDecl */
NULL, /* elementDecl */
NULL, /* unparsedEntityDecl */
NULL, /* setDocumentLocator */
NULL, /* startDocument */
NULL, /* endDocument */
NULL, /* startElement*/
NULL, /* endElement */
NULL, /* reference */
charactersFoundHandler, /* characters */
NULL, /* ignorableWhitespace */
NULL, /* processingInstruction */
NULL, /* comment */
NULL, /* warning */
NULL, /* error */
NULL, /* fatalError //: unused error() get all the errors */
NULL, /* getParameterEntity */
NULL, /* cdataBlock */
NULL, /* externalSubset */
XML_SAX2_MAGIC, /* initialized */
NULL, /* private */
startElementHandler, /* startElementNs */
endElementHandler, /* endElementNs */
NULL, /* serror */
};
@implementation DownloadOperation
// Property
@synthesize channel = _channel;
//--------------------------------------------------------------//
#pragma mark -- Initialize --
//--------------------------------------------------------------//
+ (BOOL)automaticallyNotifiesObserversForKey:(NSString*)key
{
if ([key isEqualToString:@"isExecuting"] ||
[key isEqualToString:@"isFinished"])
{
return YES;
}
return [super automaticallyNotifiesObserversForKey:key];
}
- (id)initWithRequest:(NSURLRequest*)request
{
if (![super init]) {
return nil;
}
// 实例初始化
_request = [request retain];
_isExecuting = NO;
_isFinished = NO;
_channel = [[NSMutableDictionary dictionary] retain];
[_channel setObject:[NSMutableArray array] forKey:@"items"];
_currentItem = nil;
return self;
}
- (void)dealloc
{
// 内存释放
[_request release], _request = nil;
[_connection cancel];
[_connection release], _connection = nil;
[_channel release], _channel = nil;
[_currentCharacters release], _currentCharacters = nil;
[super dealloc];
}
//--------------------------------------------------------------//
#pragma mark -- Operating --
//--------------------------------------------------------------//
- (BOOL)isConcurrent
{
return YES;
}
- (BOOL)isExecuting
{
return _isExecuting;
}
- (BOOL)isFinished
{
return _isFinished;
}
- (void)start
{
// 开始下载
if (![self isCancelled]) {
// 创建XML解析器
_parserContext = xmlCreatePushParserCtxt(&_saxHandlerStruct, self, NULL, 0, NULL);
// 设定标志
[self setValue:[NSNumber numberWithBool:YES] forKey:@"isExecuting"];
_isChannel = NO;
_isItem = NO;
// 创建连接
[NSURLConnection connectionWithRequest:_request delegate:self];
}
}
- (void)cancel
{
// 释放XML解析器
if (_parserContext) {
xmlFreeParserCtxt(_parserContext), _parserContext = NULL;
}
[_connection cancel], _connection = nil;
[self setValue:[NSNumber numberWithBool:NO] forKey:@"isExecuting"];
[self setValue:[NSNumber numberWithBool:YES] forKey:@"isFinished"];
[super cancel];
}
//--------------------------------------------------------------//
#pragma mark -- NSURLConnection delegate --
//--------------------------------------------------------------//
- (void)connection:(NSURLConnection*)connection
didReceiveData:(NSData*)data
{
// 添加解析数据
xmlParseChunk(_parserContext, (const char*)[data bytes], [data length], 0);
}
- (void)connectionDidFinishLoading:(NSURLConnection*)connection
{
// 添加解析数据(结束)
xmlParseChunk(_parserContext, NULL, 0, 1);
// 释放XML解析器
if (_parserContext) {
xmlFreeParserCtxt(_parserContext), _parserContext = NULL;
}
// 设定标志
_connection = nil;
[self setValue:[NSNumber numberWithBool:NO] forKey:@"isExecuting"];
[self setValue:[NSNumber numberWithBool:YES] forKey:@"isFinished"];
}
- (void)connection:(NSURLConnection*)connection
didFailWithError:(NSError*)error
{
// 释放XML解析器
if (_parserContext) {
xmlFreeParserCtxt(_parserContext), _parserContext = NULL;
}
// 设定标志
_connection = nil;
[self setValue:[NSNumber numberWithBool:NO] forKey:@"isExecuting"];
[self setValue:[NSNumber numberWithBool:YES] forKey:@"isFinished"];
}
//--------------------------------------------------------------//
#pragma mark -- libxml handler --
//--------------------------------------------------------------//
- (void)startElementLocalName:(const xmlChar*)localname
prefix:(const xmlChar*)prefix
URI:(const xmlChar*)URI
nb_namespaces:(int)nb_namespaces
namespaces:(const xmlChar**)namespaces
nb_attributes:(int)nb_attributes
nb_defaulted:(int)nb_defaulted
attributes:(const xmlChar**)attributes
{
// channel
if (strncmp((char*)localname, "channel", sizeof("channel")) == 0) {
_isChannel = YES;
return;
}
// item
if (strncmp((char*)localname, "item", sizeof("item")) == 0) {
_isItem = YES;
_currentItem = [NSMutableDictionary dictionary];
[[_channel objectForKey:@"items"] addObject:_currentItem];
return;
}
// title, link, description
if (strncmp((char*)localname, "title", sizeof("title")) == 0 ||
strncmp((char*)localname, "link", sizeof("link")) == 0 ||
strncmp((char*)localname, "description", sizeof("description")) == 0)
{
// 创建字符串
[_currentCharacters release], _currentCharacters = nil;
_currentCharacters = [[NSMutableString string] retain];
}
}
- (void)endElementLocalName:(const xmlChar*)localname
prefix:(const xmlChar*)prefix URI:(const xmlChar*)URI
{
// channel
if (strncmp((char*)localname, "channel", sizeof("channel")) == 0) {
_isChannel = NO;
return;
}
// item
if (strncmp((char*)localname, "item", sizeof("item")) == 0) {
_isItem = NO;
_currentItem = nil;
return;
}
// title, link, description
if (strncmp((char*)localname, "title", sizeof("title")) == 0 ||
strncmp((char*)localname, "link", sizeof("link")) == 0 ||
strncmp((char*)localname, "description", sizeof("description")) == 0)
{
NSString* key;
key = [NSString stringWithCString:(char*)localname encoding:NSUTF8StringEncoding];
NSMutableDictionary* dict = nil;
if (_isItem) {
dict = _currentItem;
}
else if (_isChannel) {
dict = _channel;
}
[dict setObject:_currentCharacters forKey:key];
[_currentCharacters release], _currentCharacters = nil;
}
}
- (void)charactersFound:(const xmlChar*)ch
len:(int)len
{
// 添加解析到的字符串
if (_currentCharacters) {
NSString* string;
string = [[NSString alloc] initWithBytes:ch length:len encoding:NSUTF8StringEncoding];
[_currentCharacters appendString:string];
[string release];
}
}
@end
|
连接开始的时候(start函数)使用 xmlCreatePushParserCtxt 创建解析器实例,这里注意第二个参数,将DownloadOperation 的实例传到解析器内,这个正是回调函数中的第一个参数 — 作为回调函数的句柄调用类成员函数(当然,不使用实例方法,将回调函数设置成类方法也是可行的。但是当你使用到DownloadOperation中的成 员等会有些不便,所以从OO的角度出发,还是传递回调函数的对象实例为佳)。
开始下载的时候,因为数据是顺序得到的,所以一边下载,一边用 xmlParseChunk 传递给解析器。
libxml的SAX句柄函数在xmlSAXHandler结构中定义。这个构造体内有30多个句柄定义,一般我们只需要登录其中几个就够了。比如 例子中的 startElementNsSAX2Func、endElementNsSAX2Func、charactersSAXFunc 等,他们的定义如下:
最后,因为用SAX解析,需要知道当前解析的位置,所以标记参数需要合理的使用。
使用DOM解析
上面我们已经介绍了,iPhone 中的XML解析器都是SAX的,如果仅仅对于比较小的XML文档,或者说想得到DOM树结构的XML文档来说,使用DOM解析还是有一定价值的(比如针对简单的SOAP,REST文档解析等)。
Google Data APIs
这里介绍一种使用第三方类库的方法,具体见这里。其实说是第三方类库,其实还是使用了libxml2,所以前期库文件和头文件的设置与上面libxml2是一致的。并将 -lxml2 加到link的设置中。
使用的时候,先从这里下载并解冻 Google Data APIs Objective-C Client Library,然后将下面解开的文件拷贝到项目中去。
1
2
3
4
| GDataXMLNode.h
GDataXMLNode.m
GDataDefines.h
GDataTargetNamespace.h
|
解析的例子如下:
1
2
3
4
5
6
7
8
9
10
11
12
| <users>
<user id="0">
<name>azalea</name>
<email>azalea@azalea.net</email>
<address country="Japan">Hokkaido</address>
</user>
<user id="1">
<name>Baka.K.El.Doglla</name>
<email>unknown@unknown.net</email>
<address country="Doglla">Doglla</address>
</user>
</users>
|
下面就是使用方法了,DOM的API使用起来还是感觉便利些:
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 | #import "GDataXMLNode.h"
- (void)applicationDidFinishLaunching:(UIApplication *)application {
// load xml file as text
NSString* path = [[NSBundle mainBundle] pathForResource:@"sample" ofType:@"xml"];
NSString* fileText = [NSString stringWithContentsOfFile:path encoding:NSUTF8StringEncoding error:nil];
NSLog(@"path:%d , fileText:%d",[path retainCount],[fileText retainCount]);
// parse text as xml
NSError* error;
GDataXMLDocument* document = [[GDataXMLDocument alloc] initWithXMLString:fileText options:0
error:&error];
GDataXMLElement *rootNode = [document rootElement];
// get user names by xpath
int count = 0;
NSArray* userList = [rootNode nodesForXPath:@"//users/user/name" error:&error];
for(GDataXMLNode* node in userList) {
NSLog([node stringValue]);
}
// Configure and show the window
[window addSubview:[navigationController view]];
[window makeKeyAndVisible];
[document release];
}
|
TouchXML
TouchXML与上面的Google Data的XML解析器类似,也是基于libxml2的一款第三方DOM解析器。设置是一样的。
下面开一个例子(从网上摘抄的):
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
| #pragma mark -
#pragma mark NewCateBooksViewController
//将xml字符串中的某些元素解析到一个数组中
+ (NSMutableArray*) parseBooks: (NSString*)xmlString /* NewCateBooksViewController */
{
if(0 == [xmlString length])
return nil;
NSMutableArray* items = [[[NSMutableArray alloc] init] autorelease];
CXMLDocument *doc = [[CXMLDocument alloc] initWithXMLString: xmlString options: 0 error: nil];
NSArray *resultNodes = nil;
resultNodes = [doc nodesForXPath:@"//booklist" error:nil];//根结点
if([resultNodes count])
{
CXMLElement *rootElement = [resultNodes lastObject];
if(rootElement)
{
NSArray* _bookElements = [rootElement elementsForName:@"book"];
for(CXMLElement* _bookElement in _bookElements)
{
RecomendBookListBean * bean = [[RecomendBookListBean alloc] init];
// if([_cateElements count])
// bean.b_cate_name = [[_cateElements lastObject] stringValue];
// bean.b_id = [_bookElement elementsForName:@"id"];
NSArray * idElements = [_bookElement elementsForName:@"id"];
if([idElements_ count])
bean.book_id=[[idElements_ lastObject]stringValue];
NSArray* nameElements = [_bookElement elementsForName:@"name"];
if([nameElements count])
bean.book_name = [[nameElements lastObject] stringValue];
NSArray* authorElements = [_bookElement elementsForName:@"author"];
if([authorElements count])
bean.book_author= [[authorElements lastObject] stringValue];
NSArray* urlElements = [_bookElement elementsForName:@"bookcoverurl"];
if([urlElements count])
bean.book_pic_url= [[urlElements lastObject] stringValue];
NSArray* descripElements = [_bookElement elementsForName:@"description"];
if([descripElements count])
bean.book_description = [[descripElements lastObject] stringValue];
NSArray* sizeElements = [_bookElement elementsForName:@"size"];
if([sizeElements count])
bean.book_size = [[sizeElements lastObject] stringValue];
[items addObject: bean];
[bean release];
}
}
}
[doc release];
return items;
}
#pragma mark -
-(void)updateArray:(NSString *)Url
{
NSError *error;
NSURLResponse *response;
NSData *dataReply;
NSString *stringReply;
NSMutableURLRequest *request =[NSMutableURLRequest requestWithURL:[NSURL URLWithString:Url]];
[request setHTTPMethod:@"GET"];
dataReply = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error];
if(dataReply==nil&&error!=nil)
{
return;
}
else
{
stringReply = [[NSString alloc]initWithData:dataReply encoding:NSUTF8StringEncoding];
[bookArray addObjectsFromArray:[xmlTry parseBooks:stringReply]]; //调用解析函数
[stringReply release];//remember to release
}
}
|
KissXML
KissXML据说速度比 TouchXML 快些,暂时还没有试过,用兴趣的朋友可以试试。例子如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
| DDXMLDocument *doc = [[[DDXMLDocument alloc] initWithData:data options:0 error:&error] autorelease];
DDXMLElement *root = [doc rootElement];
[root addNamespace:[DDXMLNode namespaceWithName:@"idx" stringValue:@"urn:atom-extension:indexing"]];
[root addNamespace:[DDXMLNode namespaceWithName:@"gr" stringValue:@"http://www.google.com/schemas/reader/atom/"]];
[root addNamespace:[DDXMLNode namespaceWithName:@"media" stringValue:@"http://search.yahoo.com/mrss/"]];
[root addNamespace:[DDXMLNode namespaceWithName:@"foo" stringValue:@"http://www.w3.org/2005/Atom"]];
NSArray *titles = [root nodesForXPath:@"//foo:feed/foo:title" error:&error];
NSArray *feedTitles = [root nodesForXPath:@"//foo:source/foo:title" error:&error];
NSArray *feedIds = [root nodesForXPath:@"//foo:source/foo:id" error:&error];
NSArray *entryTitles = [root nodesForXPath:@"//foo:entry/foo:title" error:&error];
NSArray *contents = [root nodesForXPath:@"//foo:entry/foo:content" error:&error];
NSArray *publisheds = [root nodesForXPath:@"//foo:entry/foo:published" error:&error];
for (NSUInteger i = 0; i < [feedTitles count]; i++) {
NSMutableDictionary *result = [[[NSMutableDictionary alloc] init] autorelease];
[result setObject:[[feedIds objectAtIndex:i] stringValue] forKey:@"feed_id"];
[result setObject:[[feedTitles objectAtIndex:i] stringValue] forKey:@"feed_title"];
[result setObject:[[entryTitles objectAtIndex:i] stringValue] forKey:@"entry_title"];
[result setObject:[[contents objectAtIndex:i] stringValue] forKey:@"content"];
[result setObject:[[publisheds objectAtIndex:i] stringValue] forKey:@"published"];
[self.objects addObject:result];
}
|
说到XML不得不提WEB应用中最常见的几种通讯规范:SOAP,XML-RPC,WSDL等,他们都是基于XML协定的。在下一节中我将介绍几种处理web应用的程序库。