Event-Driven XML Programming Guide--事件驱动解析XML
来源:互联网 发布:scp默认端口 编辑:程序博客网 时间:2024/05/04 11:34
转自http://supershll.blog.163.com/blog/static/37070436201251393157364/
Event-Driven XML Programming Guide
Cocoa提供了NSXMLParser类使用事件驱动解析XML文档和使用DTD声明的文档。他把其发现的东西报告给代理,让代理去处理数据。这个文档解释了如何使用NSXMLParser。1,有两种方法可以解析和处理XML,每种都有自己的API:
1)基于树的API:包括DOM。
2)事件驱动API:SAX是最熟知的例子。也叫作流解析(streaming parser)
NSXMLParser采用了event-driven。但是取代回调,NSXMLParser是发送消息给其代理,随着parser遇到不同的item--包括element,attribute,declaration,entity reference,等等--他本身不对这些item做任何事情。
假设我们有下面的这个XML文件:
<?xml version= "1.0" encoding="UTF8">
<article author="John Doe">
<para>This is a very short article.</para>
</article>
parser会报告下面的一系列事件给代理:
1)开始解析文档
2)找到 article 元素的开始标签
3)找到article元素的 author 属性,value为“John Doe"
4)找到para 元素的开始标签
5)找到characters "This is a very short article."
6) 找到 para元素的结束标签
7)找到article 元素的结束标签
8)结束解析文档
无论是基于树的还是基于事件驱动的解析都有优点和缺点。建立一个内存树需要可以想像得到的内存,尤其是大文档。
如果你只考虑性能而不修改,那么使用事件驱动是不错的选择。事件驱动解析不适合修改和扩展数据和写回文件。NSXMLParser还不支持验证文档结构是否遵循DTD或其他schema指定的规则。所有这些你需要一个DOM。当然,你也可以使用NSXMLParser构建你自己的树。
NSXMLParser验证XML或DTD是否结构良好。例如,他检查一个开始标签是否匹配有一个结束标签,或者一个属性是否有指定值。如果他遇到这些语法错误,他将停止解析并通知代理。
虽然parser只理解XML和DTD标记语言,但是它也能解析任何基于XML语言的Schema,例如RELAX NG和XML Schema。
2,使用NSXMLParser解析XML文档,你需要做:
1)定位XML:下面显示了从文件系统浏览器选择一个XML文件的代码:
Listing 1 Opening an XML file
- (void)openXMLFile {
NSArray *fileTypes = [NSArray arrayWithObject:@"xml"];
NSOpenPanel *oPanel = [NSOpenPanel openPanel];
NSString *startingDir = [[NSUserDefaults standardUserDefaults] objectForKey:@"StartingDirectory"];
if (!startingDir)
startingDir = NSHomeDirectory();
[oPanel setAllowsMultipleSelection:NO];
[oPanel beginSheetForDirectory:startingDir file:nil types:fileTypes
modalForWindow:[self window] modalDelegate:self
didEndSelector:@selector(openPanelDidEnd:returnCode:contextInfo:)
contextInfo:nil];
}
- (void)openPanelDidEnd:(NSOpenPanel *)sheet returnCode:(int)returnCode contextInfo:(void *)contextInfo {
NSString *pathToFile = nil;
if (returnCode == NSOKButton) {
pathToFile = [[[sheet filenames] objectAtIndex:0] copy];
}
if (pathToFile) {
NSString *startingDir = [pathToFile stringByDeletingLastPathComponent];
[[NSUserDefaults standardUserDefaults] setObject:startingDir forKey:@"StartingDirectory"];
[self parseXMLFile:pathToFile];
}
}
你可能从其他对象,如property-list(例如NSDictionary)或网络比特流接收到XML,在这些情况下,你需要首先转换XML为NSData。
2)创建并初始化NSXMLParser的实例,确保设备一个代理。如下所示:
Listing 2 Creating and initializing a NSXMLParser instance
- (void)parseXMLFile:(NSString *)pathToFile {
BOOL success;
NSURL *xmlURL = [NSURL fileURLWithPath:pathToFile];
if (addressParser) // addressParser is an NSXMLParser instance variable
[addressParser release];
addressParser = [[NSXMLParser alloc] initWithContentsOfURL:xmlURL];
[addressParser setDelegate:self];
[addressParser setShouldResolveExternalEntities:YES];
success = [addressParser parse]; // return value not used
// if not successful, delegate is informed of error
}
还可以使用NSData进行初始化:
addressParser = [[NSXMLParser alloc] initWithData:xmlData];
3)实现代理方法:(可以只实现那些你感兴趣的方法)
所有的解析操作从parserDidStartDocument:开始,以parserDidEndDocument:结束
4)处理任何解析错误:
如果parser遇到错误,它会停止解析并调用代理方法 parser:parseErrorOccurred:
在你解析XML时,内存管理变得非常重要。解析XML通常要求你创建很多对象;你应该不要让这些对象不再有用时累积到内存中。一种技术是在解析数据开始时创建一个自动释放池,结束时释放他。
3,处理XML元素和属性:
一般你解析XML文档,更多的是处理元素和元素相关的东西,如属性和其文本内容。发送的消息至少有3种:
1)parser:didStartElement:namespaceURI:qualifiedName:attributes:
2)parser:foundCharacters:
3)parser:didEndElement:namespaceURI:qualifiedName:
parser可能对一个element多次发送parser:foundCharacters:消息;然而,如果characters只由空白字符组成(空格,新行,tab,和类似字符),parser会发送
parser:foundIgnorableWhitespace:消息。
当你解析XML元素时,一个高级技巧是使用多个代理,每个代理知道如何处理特定类型的元素。
4,设计要素:
在面向对象的环境中,一个通常的策略是map them to objects。根元素和其他顶级元素经常等价于NSDictionary和NSArray。其他元素可能为一个或多个model对象。
然而,不是所有的元素都能很好的用对象表示。一些低级和特殊的”叶"元素更多的是其父节点逻辑上的属性。你应该让这些属性成为model对象的属性。
5,处理一个元素的例子:
XML文件内容如下:
Listing 1 Some of the sample XML
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE addresses SYSTEM "addresses.dtd">
<addresses owner="swilson">
<person>
<lastName>Doe</lastName>
<firstName>John</firstName>
<phone location="mobile">(201) 345-6789</phone>
<email>jdoe@foo.com</email>
<address>
<street>100 Main Street</street>
<city>Somewhere</city>
<state>New Jersey</state>
<zip>07670</zip>
</address>
</person>
<!-- more person elements go here -->
</addresses>
让我们看一下这些元素的前3个元素是如何被处理的。
Listing 2 Implementing parser:didStartElement:namespaceURI:qualifiedName:attribute:
- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName attributes:(NSDictionary *)attributeDict {
if ( [elementName isEqualToString:@"addresses"]) {
// addresses is an NSMutableArray instance variable
if (!addresses)
addresses = [[NSMutableArray alloc] init];
return;
}
if ( [elementName isEqualToString:@"person"] ) {
// currentPerson is an ABPerson instance variable
currentPerson = [[ABPerson alloc] init];
return;
}
if ( [elementName isEqualToString:@"lastName"] ) {
[self setCurrentProperty:kABLastNameProperty];
return;
}
// .... continued for remaining elements ....
}
重要的动作是有一个方法来追踪当前元素。一个原因是parser:foundCharacters:可能被一个元素多次调用。在这个方法中,代理应该append相应的element的字符。如下所示:
- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string {
if (!currentStringValue) {
// currentStringValue is an NSMutableString instance variable
currentStringValue = [[NSMutableString alloc] initWithCapacity:50];
}
[currentStringValue appendString:string];
}
如果parser遇到一些空白字符组成的内容,他发送 parser:foundIgnorableWhitespace:来给代理机会保留任何空白字符。
最终,parser遇到元素的结尾,他发送 parser:didEndElement:namespaceURI:qualifiedName: 消息给代理。如下所示:
Listing 4 Implementing parser:didEndElement:namespaceURI:qualifiedName:
- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName {
// ignore root and empty elements
if (( [elementName isEqualToString:@"addresses"]) ||
( [elementName isEqualToString:@"address"] )) return;
if ( [elementName isEqualToString:@"person"] ) {
// addresses and currentPerson are instance variables
[addresses addObject:currentPerson];
[currentPerson release];
return;
}
NSString *prop = [self currentProperty];
// ... here ABMultiValue objects are dealt with ...
if (( [prop isEqualToString:kABLastNameProperty] ) ||
( [prop isEqualToString:kABFirstNameProperty] )) {
[currentPerson setValue:(id)currentStringValue forProperty:prop];
}
// currentStringValue is an instance variable
[currentStringValue release];
currentStringValue = nil;
}
6,处理属性:
仍然是在parser:didStartElement:namespaceURI:qualifiedName:attributes: 这个函数中,attributes:参数是一个NSDictionary,查询其相应的key来获得属性值。
如下所示:
Listing 5 Handling an attribute of an element
- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName attributes:(NSDictionary *)attributeDict {
if ( [elementName isEqualToString:@"addresses"]) {
// addresses is an NSMutableArray instance variable
if (!addresses)
addresses = [[NSMutableArray alloc] init];
NSString *thisOwner = [attributeDict objectForKey:@"owner"];
if (thisOwner)
[self setOwner:thisOwner forAddresses:addresses];
return;
// ... continued ...
}}
7,处理解析错误
当parser遇到语法错误或其他错误,它停止解析并发送
parser:parseErrorOccurred:消息给代理。在实现方法中,应该显示给用户错误原因。解析错误是致命的(不可恢复的),所以通知用户是你实际上能做的所有事情。通过这个信息,用户可能修正XML文件。
下面显示了如何实现parser:parseErrorOccurred:函数:
Listing 1 Handling parsing errors
- (void)parser:(NSXMLParser *)parser parseErrorOccurred:(NSError *)parseError {
NSWindow *modWin = [self windowForSheet];
if (!modWin) modWin = [NSApp mainWindow];
NSAlert *parserAlert = [[NSAlert alloc] init];
[parserAlert setMessageText:@"Parsing Error!"];
[parserAlert setInformativeText:[NSString stringWithFormat:@"Error %i,
Description: %@, Line: %i, Column: %i", [parseError code],
[[parser parserError] localizedDescription], [parser lineNumber],
[parser columnNumber]]];
[parserAlert addButtonWithTitle:@"OK"];
[parserAlert beginSheetModalForWindow:modWin modalDelegate:self
didEndSelector:@selector(alertDidEnd:returnCode:contextInfo:)
contextInfo:nil];
[parserAlert release];
}
- (void)alertDidEnd:(NSAlert *)alert returnCode:(int)returnCode contextInfo:(void *)contextInfo { }
这段代码的关键是NSAlert的提示文本的建立。这个文本包括error code(一个NSXMLParserErrorenum常量),一个本地描述,一个line number和column。在例子中,代理从两个源头获取信息:从parser本身和NSError对象。从parser对象也可以获得NSError对象。
然而,NSError的默认的本地描述是基本的,你可能想提供你自己的描述。有时解析错误可能需要一个特定于应用程序的解释。要实现这个目的,你可以使用NSXMLParserError常量来决定如何显示错误信息。
8,使用多代理:
对于一些大型XML文档,只使用一个代理可能不是最好的选择。
作为一个例子,一个应用用它遇到的元素创建一个DOM-style tree。从根元素开始,一个元素创建一个子元素并且传递控制给他--通过将其设置为代理。子元素再创建其子元素(继续),每次都重置代理。如果一个元素没有子节点,或者它是一个复合元素,它accumulates(累积,积攒)为自己累积文本内容。最终,当解析器遇到元素的结束标签,元素设置代理给其父元素。下面显示了相关的实现代码:
Listing 1 Resetting the delegate for the next element
- (void)parser:(NSXMLParser *)parser
didStartElement:(NSString *)elementName
namespaceURI:(NSString *)namespaceURI
qualifiedName:(NSString *)qualifiedName
attributes:(NSDictionary *)attributeDict {
// Element is a custom class for object representing element nodes
// Creation of element sets child as delegate (see below)
[self addChild:[Element elementWithName:elementName
attributes:attributeDict parent:self children:nil parser:parser]];
}
- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string {
[self appendString:string];
}
- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName {
Element *parent = [self parent];
[parser setDelegate:parent]; // RESET DELEGATE TO PARENT
}
+ (id)elementWithName:(NSString *)elementName attributes:(NSDictionary *)attributes parent:(Element *)parent children:(NSArray *)children parser:(NSXMLParser *)parser {
return [[[[self class] alloc] initWithName:elementName
attributes:attributes parent:parent children:children
parser:parser] autorelease];
}
- (id)initWithName:(NSString *)elementName attributes:(NSDictionary *)attributes parent:(id)parent children:(NSArray *)children parser:(NSXMLParser *)parser {
self = [super init];
if (self) {
[self setName:elementName];
if (attributes) {
[self addAttributes:attributes];
}
[self setParent:parent];
if (children) {
[self addChildren:children];
}
[parser setDelegate:self]; // CHILD SET AS DELEGATE
}
return self;
}
另外一个管理多代理的技巧是在一个集合中如NSDictionary对象中,维持若干代理对象,每个都有其特定的role(规则)。这些对象应该知道在给定的上下文中 谁是他们的父或子元素,so能够在其当前元素解析完后,为下一个元素设置代理(使用合适的dictionary key)。
9,创建XML Tree 结构:
一般如果你希望添加或修改XML文档的内容,你必须创建一个静态树结构--能完全表示元素和文档其他的结构。如果你打算验证DTD(或其他语言的schema,那种预描述文档结构的)的XML文档结构,树表现形式也是关键。
当血多开发者想构建一个DOM-style树表示XML文档时,他们使用一个tree-based解析器,而不是流解析器如NSXMLParser。(然而,Tree-based解析引擎通常建立在流解析上)。尽管如此,这并不意味着你不能使用NSXMLParser创建树结构。虽然这篇文档并不深入探讨使用NSXMLParser创建XML树结构,但是他仍然是一个你可以选择的普遍方法。
注意:DOM(for Document Object Model)是W3C指定的一个模型标准来描述XML和HTML文档。它也定义了访问和操作这些对象的接口,这些对象代表了文档的元素和其相关的属性。下面讨论的程序并不只针对DOM,虽然他们很相似。
你可以表示任何XML文档为一个hierarchical(层次)tree --他的节点是元素显示了父子的关系。每个元素可以有1或多个子元素,并且除了根元素,每个元素都有且只有一个父元素。Tree通过一个root element(唯一没有父元素)定位,叶节点表示只包含有文本的元素,虽然他们也可以是混合元素或空元素。
例子,考虑下面的文档:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE addresses SYSTEM "addresses.dtd">
<addresses>
<person idnum="0123">
<lastName>Doe</lastName>
<firstName>John</firstName>
<phone location="mobile">(201) 345-6789</phone>
<email>jdoe@foo.com</email>
<address>
<street>100 Main Street</street>
<city>Somewhere</city>
<state>New Jersey</state>
<zip>07670</zip>
</address>
</person>
</addresses>
addresses 是根元素,person是第一个子元素,lastName,firstName,phone,email,address都是其子元素。street,city...又是address的子元素。
有几种办法可以通过使用NSXMLParser来构建XML文档的一个tree表现形式。这篇文章采用面向对象的方法--动态转换代理负责处理文档的元素,是一个递归。代码的结果是双链接列表的对象和对象数组;抽象的结果是一个文档的树表示形式。
构建树的步骤如下:
1) 创建一个类--其实例表示XML文档的一个元素。这个类应该定义元素的名字,和他的父和子的关系;他还应该封装相关的元素属性。作为一个速记符号,我们称这个类为MyElement。
2)From a top-level object in the application,加载一个XML文档,创建一个NSXMLParser实例,指定顶级对象为代理,并且开始解析文档。
3)解析器遇到文档的根元素,并发送 parser:didStartElement:namespaceURI:qualifiedName:attributes: 给代理。代理创建一个MyElement对象表示根元素并设置其父为nil。对象的创建和初始化的方法也设置其为NSXMLParser实例的新代理。
4)parser遇到下一个元素--根元素的第一个子元素--并且再次发送parser:didStartElement:namespaceURI:qualifiedName:attributes:消息给代理。现在的代理是刚刚创建的表示根元素的MyElement对象。它创建另外一个MyElement对象表示新元素(设置新对象为代理,并设置自身为其父)并且添加新对象到其子列表中。
5)新大吃收到下一个 parser:didStartElement:namespaceURI:qualifiedName:attributes: 消息,表示其第一个子元素,并且他创建它并添加它到它的子列表中。
6)这个递归持续下去,直到遇到叶元素--只包含文本,混合内容或空元素。 If there is mixed content the descent is not truly over since parser:didStartElement:namespaceURI:qualifiedName:attributes: is sent to the delegate even after it receives parser:foundCharacters: for the current element.(不会翻译,看不懂)。。处理取决于元素的类型:
a)如果它是一个空元素,处理跳过到下一个步骤(end-element tag)
b)如果当前元素节点只有文本,代理响应 parser:foundCharacters:消息--通过累积文本
c)如果是混合内容,代理首先处理文本--自从它收到通知它元素开始和元素结束标签的消息之后。一种处理这种情况的方法就是wrap the text in special text-element对象,并且插入这些(使用正确的顺序)到元素的子列表中。
7)最终,parser发送 parser:didEndElement:namespaceURI:qualifiedName: 给代理,通知其代理元素完成。代理设置新代理为其父并返回。
8)如果父有更多的子元素,解析器发送下一个 parser:didStartElement:namespaceURI:qualifiedName:attributes: 消息,父MyElement对象创建MyElement实例表示其下一个子,并添加新创建的对象到其子列表。如果父没有更多的子(就是他收到parser:didEndElement:namespaceURI:qualifiedName:消息)它将设置新代理给其父,并返回。
9)程序继续--直到整个XML文档被处理并且所有的树的分支都被构建。
节点树应该能够打印为XML代码。你的应用应该也实现一个算法--请求对象打印自身到正确的文档序列中。
10,验证技巧和技术:
11,使用NSXMLParser处理DTD声明:NSXMLParser报告DTD声明给其代理。如果schema语言是DTD,NSXMLParser帮助你获得你需要的数据,或者验证或者用于其他目的,例如强制修正等等,
NSXMLParser定义了6个代理方法--当其遇到DTD声明 in a internal or external source.这些方法的格式为:
parser:foundTypeDeclarationWithName:...
第三个参数和其他后续的参数取决于声明的类型。
parser:foundElementDeclarationWithName:model:
例如: <!ELEMENT dictionary (documentation?, suite+)>
... (这里不翻译了,用到再补充)
12,XML术语:
- Event-Driven XML Programming Guide--事件驱动解析XML
- Java学习笔记(15)Event-Driven Programming and Animations 事件驱动编程与动画
- 事件-驱动模拟(Event-driven simulation)
- Event-driven architecture(事件驱动架构)
- Event-driven programming
- Event-driven programming library
- Event-Driven Programming
- Event-driven programming for Android
- Event Driven Programming In C
- event driven gui programming model
- 接口连接架构(Interface Connection Architecture)和事件驱动架构(Event Driven Architecture)
- 以消息为基础,以事件驱动之(message based, event driven)
- 事件驱动的模拟(event-driven-simulation)在排队中的运用
- MFC基于消息,使用事件驱动(Message Based,Event Driven)机制
- sax和Dom解析xml文档 文档驱动和事件驱动
- 表驱动编程方法 table-driven programming
- 表驱动编程方法 table-driven programming
- [代码笔记] python 之 xml解析_sax:基于事件驱动的解析方式
- 什么是java序列化及序列化对象定义
- 快速在 Oracle Linux 5 上 安装Oracle Database11g
- 51单片机普通IO口模拟IIC总线的程序实
- 编码MetaDataRegisterAction
- 51单片机GPIO口模拟串口通信
- Event-Driven XML Programming Guide--事件驱动解析XML
- STC89C52单片机内部EEPROM驱动
- 详谈 Oracle 中的网络配置文件
- Java中的Interface
- Oracle中是数据导入、导出
- HTML灰色不可编辑文本框
- 正式发布本人设计的51单片机开发板JL8051
- 韩顺平_php从入门到精通_视频教程_第6讲_浮动窗口_表单及表单控件①_学习笔记_源代码图解_PPT文档整理
- perl解释器的代码和用perl写出来的代码一样难看