|
SAX,功能强大的API广告 SAX,功能强大的API
这篇对 Benoit Marchal 所著的 XML by Example 第二版的预览给出了对 SAX 的翔实介绍,SAX
是用于处理 XML 的基于事件的 API,它已经成为事实上的标准。本篇预览讲述了何时使用 SAX 替换 DOM,概述了常用的 SAX 接口,并在基于 Java
的应用程序中提供了带有许多代码样本的详细示例。 您将了解到,SAX: 是基于事件的 API。 为什么出现另一个API? 图 1 显示了典型 XML 程序的两个组件: 语法分析器,代表应用程序解码 XML 文件的软件组件。语法分析器有效地使开发者避开复杂的 XML
语法。 图 1. XML 程序的体系结构
本章集中讨论图 1 中的虚线 - 语法分析器和应用程序之间的接口或 API(应用程序编程接口)。 基于对象和基于事件的接口 在拙作的另一章中详细讨论了由 W3C 开发并发布的 DOM,它是基于对象的语法分析器的标准 API。这个关于 DOM 的简要概述只为您提供背景知识,以便您更好地全面理解 SAX。 作为基于对象的接口,DOM 通过在内存中显示地构建对象树来与应用程序通信。对象树是 XML 文件中元素树的精确映射。 DOM 易于学习和使用,因为它与基本 XML 文档紧密匹配。它对于我称为以 XML 为中心的应用程序(例如,浏览器和编辑器)也是很理想的。以 XML 为中心的应用程序为了操纵 XML 文档而操纵 XML 文档。 然而,对于大多数应用程序,处理 XML 文档只是其众多任务中的一种。例如,记帐软件包可能导入 XML 发票,但这不是其主要活动。计算帐户余额、跟踪支出以及使付款与发票匹配才是主要活动。记帐软件包可能已经具有一个数据结构(最有可能是数据库)。DOM 模型不太适合记帐应用程序,因为在那种情况下,应用程序必须在内存中维护数据的两份副本(一个是 DOM 树,另一个是应用程序自己的结构)。至少,在内存维护两次数据会使效率下降。对于桌面应用程序来说,这可能不是主要问题,但是它可能导致服务器瘫痪。 对于不以 XML 为中心的应用程序,SAX 是明智的选择。实际上,SAX 并不在内存中显式地构建文档树。它使应用程序能用最有效率的方法存储数据。 图 2 说明了应用程序如何在 XML 树及其自身数据结构之间进行映射。 图 2. 将 XML 结构映射成应用程序结构
事件通知应用程序发生了某件事并需要应用程序作出反应。在浏览器中,通常为响应用户操作而生成事件:当用户单击按钮时,按钮产生一个 ONCLICK 事件。 在 XML 语法分析器中,事件与用户操作无关,而与正在读取的 XML 文档中的元素有关。有对于以下方面的事件: 元素开始和结束标记 图 3 显示语法分析器在读取文档时如何生成事件。 图 3. 语法分析器生成事件
清单 1. pricelist.xml 图 4. 价目表的结构
读取清单 1 时,语法分析器首先读取 XML 声明并生成文档开始事件。当它遇到第一个开始标记 <xbe:price-list> 时,语法分析器生成它的第二个事件来通知应用程序已经遇到了 price-list 元素。 接下来,语法分析器看到 product 元素的开始标记(为简单起见,在本文其余部分,我将忽略名称空格和缩进空格)并生成它的第三个事件。 在开始标记后,语法分析器看到 product 元素的内容:XML Training,它产生另一个事件。 下一个事件指出 product 元素的结束标记。语法分析器已经完成了对 product 元素的语法分析。到目前为止,它已经激发了 5 个事件:product 元素的 3 个事件,一个文档开始事件和一个 price-list 开始标记事件。 语法分析器现在移动到第一个 price-quote 元素。它为每个 price-quote 元素生成两个事件:一个开始标记事件和一个结束标记事件。 是的,即使将结束标记简化为开始标记中的 / 字符,语法分析器仍然生成一个结束事件。 有 4 个 price-quote 元素,所以语法分析器在分析它们时生成 8 个事件。最后,语法分析器遇到 price-list 的结束标记并生成它的最后两个事件:结束 price-list 和文档结束。 如图 5 所示,这些事件共同向应用程序描述了文档树。开始标记事件意味着“转到树的下一层”,而结束标记元素意味着“转到树的上一层”。 图 5. 语法分析器如何隐含地构建树
为什么使用基于事件的接口? 经验法则是在需要更多控制时使用 SAX;要增加方便性时,则使用 DOM。例如,DOM 在脚本语言中很流行。 采用 SAX 的主要原因是效率。SAX 比 DOM 做的事要少,但提供了对语法分析器的更多控制。当然,如果语法分析器的工作减少,则意味着您(开发者)有更多的工作要做。 而且,正如我们已讨论的,SAX 比 DOM 消耗的资源要少,这只是因为它不需要构建文档树。 在 XML 早期,DOM 得益于 W3C 批准的官方 API 这一身份。逐渐地,开发者选择了功能性而放弃了方便性,并转向了 SAX。 SAX 的主要限制是它无法向后浏览文档。实际上,激发一个事件后,语法分析器就将其忘记。如您将看到的,应用程序必须显式地缓冲其感兴趣的事件。 当然,无论它实现 SAX 还是 DOM API,语法分析器都做许多工作:它读取文档,强制实施 XML 语法并解析实体 - 先只列举这几个。验证语法分析器还强制实施文档模式。 使用语法分析器有很多原因,并且您应该掌握 API、SAX 和 DOM。它使您能灵活地根据手上的任务来选择最好的 API。幸好,现代语法分析器同时支持两种 API。 注:自然的接口 注:SAX 构建的树 SAX 最初是为 Java 而定义,但是它也可以用于 Python、Perl、C++ 和 COM(Windows 对象)。以后一定还有更多的语言绑定。而且,通过 COM,SAX 语法分析器还可以用于所有 Windows 编程语言,包括 Visual Basic 和 Delphi。 与 DOM 不同,SAX 没有经过官方标准机构的认可,但是它被广泛使用并被视为事实上的标准。(现在,SAX 由 David Megginson 编辑,但是他已经宣布将要退休。) 如您所见,在浏览器中,DOM 是首选的 API。因此,本章中的示例是用 Java 编写的。(如果您觉得需要一个 Java 速成课程,请转至拙作的附录 A 或者 developerWorks Java 区的“教学”部分。) 一些支持 SAX 的语法分析器包括 Xerces,Apache parser(以前的 IBM 语法分析器)、MSXML(Microsoft 语法分析器)和 XDK(Oracle 语法分析器)。这些语法分析器是最灵活的,因为它们还支持 DOM。 有几个语法分析器仅提供 SAX,例如 James Clark 的 XP、苐fred 和 Vivid Creations 的 ActiveSAX(请参阅参考资料)。 SAX 入门 编译示例 从作者网站的 XBE2 页面下载本摘录的清单。下载内容包括 Xerces。如果清单有问题,请访问作者网站以获取更新。 在名为 Cheapest.java 的文件中保存清单 2。转至 DOS 提示符,更改到保存 Cheapest.java 的目录,然后在 DOS 提示符处发出下列命令来编译: mkdir classes 编译将在 classes 目录中安装 Java 程序。这些命令假设您已经在 lib 目录中安装了 Xerces,并且在 src 目录中安装了清单 2。如果在另一个目录下安装语法分析器,则可能必须修改 classpath(第二条命令)。 要对价目表运行应用程序,请发出下面的命令: java com.psol.xbe2.Cheapest data\pricelist.xml
The cheapest offer is from XMLi ($699.00)
事件处理器的逐步讨论 声明事件处理器的最简单方案是继承 SAX 提供的 DefaultHandler: public class Cheapest
在清单 2 中,事件处理器仅对 price-quote 感兴趣,所以仅对它测试。该处理器对其它元素的事件不作任何处理。 if(uri.equals(NAMESPACE_URI) &&
name.equals("price-quote")) 当事件处理器发现 price-quote 元素时,它从属性列表中抽取供应商名称和价格。有了这些信息,查找最便宜的产品就是一个简单的比较处理了。 String attribute = 请注意,事件处理器接收元素名称、名称空间和属性列表作为来自语法分析器的参数。 现在,让我们将注意力转向 main() 方法。它创建一个事件处理器对象和一个语法分析器对象: Cheapest cheapest = new Cheapest(); XMLReader 和 XMLReaderFactory 由 SAX 定义。XMLReader 是一种 SAX 语法分析器。factory 是用于创建 XMLReaders 的帮助器类。 main() 设置一个语法分析器功能以请求名称空间处理,并且使用语法分析器注册事件处理器。最后,main() 使用至 XML 文件的 URI 调用 parse() 方法: parser.setFeature("http://xml.org/sax/features/namespaces",true); 看似无关的 parse() 方法触发对 XML 文档的语法分析,这导致了调用事件处理器。我们的 startElement() 方法正是在执行这个方法期间被调用的。在调用 parse() 背后发生了很多事情。 最后但很重要的一点,main() 打印出结果: Object[] objects = new Object[] 等一下!Cheapest.vendor 和 Cheapest.min 何时获取它们的值?我们不在 main() 中显式地设置它们!确实如此;这是事件处理器的工作。最后由 parse() 调用事件处理器。这就是事件处理的美妙之处。 注意 请记住,除非已经安装了“Java 开发工具箱”,否则不能编译这些示例。 最后,可能有一个错误类似于: src\Cheapest.java:7: Package org.xml.sax 或 Can't find class com/psol/xbe2/Cheapest 这极有可能出自以下原因: 类路径(第二个命令,classes;lib\xerces.jar))不正确。 常用的 SAX
接口和类 SAX 将其事件分为几个接口: ContentHandler
定义与文档本身关联的事件(例如,开始和结束标记)。大多数应用程序都注册这些事件。 为简化工作,SAX 在 DefaultHandler 类中提供了这些接口的缺省实现。在大多数情况下,为应用程序扩展 DefaultHandler 并覆盖相关的方法要比直接实现一个接口更容易。 XMLReader parser.parse(args[0]); XMLReader 的主要方法是: parse() 对 XML 文档进行语法分析。parse() 有两个版本;一个接受文件名或 URL,另一个接受
InputSource 对象(请参阅“InputSource”一节)。 XMLReaderFactory 对于 Xerces,类是 org.apache.xerces.parsers.SAXParser。应该使用 XMLReaderFactory,因为它易于切换至另一种 SAX 语法分析器。实际上,只需要更改一行然后重新编译。 XMLReader parser =
XMLReaderFactory.createXMLReader(
InputSource 在大多数情况下,文档是从 URL 装入的。但是,有特殊需求的应用程序可以覆盖 InputSource。例如,这可以用来从数据库中装入文档。 ContentHandler 如您所见,清单 2 实现在 ContentHandler 中定义的事件 startElement()。它用语法分析器注册 ContentHandler: Cheapest cheapest = new Cheapest(); ContentHandler 声明下列事件: startDocument()/endDocument()
通知应用程序文档的开始或结束。 属性 String attribute = attributes.getValue("","price"); Attributes 定义下列方法: getValue(i)/getValue(qName) /getValue(uri,localName) 返回第 i
个属性值或给定名称的属性值。 定位器 Locator 定义下列方法: getColumnNumber() 返回当前事件结束时所在的那一列。在 endElement()
事件中,它将返回结束标记所在的最后一列。 getSystemId() 返回当前文档事件的系统标识。 DTDHandler notationDecl() 通知应用程序已经声明了一个标记。 EntityResolver 因为 SAX 语法分析器已经可以解析大多数 URL,所以很少应用程序实现 EntityResolver。例外情况是目录文件(在另一章中讨论),它将公共标识解析成系统标识。如果在应用程序中需要目录文件,请下载 Norman Walsh 的目录软件包(请参阅参考资料)。 ErrorHandler 安装了定制错误处理器后,语法分析器不再抛出异常。抛出异常是事件处理器的责任。 接口定义了与错误的三个级别或严重性对应的三个方法: warning() 警示那些不是由 XML 规范定义的错误。例如,当没有 XML
声明时,某些语法分析器发出警告。它不是错误(因为声明是可选的),但是它可能值得注意。 SAXException 错误可以是语法分析错误也可以是事件处理器中的错误。要报告来自事件处理器的其它异常,可以将异常封装在 SAXException 中。 示例:假设在处理 startElement 事件时,事件处理器捕获了一个 IndexOutOfBoundsException。事件处理器可以将 IndexOutOfBoundsException 封装在 SAXException 中: public void startElement(String
uri, SAXException 一直向上传递到 parse() 方法,它在那里被捕获并进行解释。 try 维护状态 示例清单 3 更复杂,因为信息分散到了几个元素中。特别是,根据不同的交付延迟,供应商有不同的价格。如果用户愿意等待,他(或她)可能得到更好的价格。图 6 演示了文档结构。 清单 3. xtpricelist.xml 图 6. 价目表结构
清单 4 是一个新建的 Java 应用程序,它查找价目表中的最优价格。当查找最优价格时,它考虑到了客户对交付日期的需求。实际上,清单 3 中最便宜的供应商(XMLi)也是最慢的。另一方面,Emailaholic 很贵,但是它可以在两天内交付。 您可以如前面介绍的 Cheapest 应用程序那样编译并运行该应用程序。结果取决于对交付日期的需求。您将注意到这个程序采用两个参数:文件名和客户愿意等待的最长延迟。 java com.psol.xbe2.BestDeal data/xtpricelist.xml 60 返回: The best deal is proposed by XMLi. A(n) XML Training
delivered[ccc] 而: java com.psol.xbe2.BestDeal data/xtpricelist.xml 3 返回: The best deal is proposed by Emailaholic. A(n) XML
Training[ccc] 分层体系结构 应用程序是围绕两个类组织的:SAX2BestDeal 和 BestDeal。SAX2BestDeal 管理 SAX 语法分析器之间的接口。它用一致的方法来管理状态并将事件分组。 BestDeal 具有执行价格比较的逻辑。它还以结构形式保持为应用程序而不是为 XML 优化的信息。图 7 演示了该应用程序的体系结构。图 8 显示了 UML 类图。 图 7. 应用程序的体系结构
例如,在 characters() 事件中,SAX2BestDeal 需要知道文本是名称、价格还是可以忽略的空格。而且,有两个 name 元素:price-list 的 name 和 vendor 的 name。 状态 很明显,应用程序将首先遇到 price-list 标记。因此,第一个状态应该是位于 price-list 内。从那里开始,应用程序到达一个 name。因此,第二个状态是位于 price-list 的 name 内。 下一个元素必须是 vendor,因此第三个状态是位于 price-list 的 vendor 内。第四个状态是位于 price-list 的 vendor 的 name 内,因为 name 跟在 vendor 后。 name 后面是一个 price-quote 元素,相应的状态是位于 price-list 的 vendor 的 price 内。随后,语法分析器遇到已经有状态存在的 price-quote 或 vendor。 在带有状态和转换的图上(例如图 9 所示)会更容易使这个概念可视化。请注意根据您在处理 price-list/name 还是 price-list/vendor/name,有两个不同的状态与两个不同的名称元素相关联。 图 9. 状态转换图
final protected int START =
0, 转换 转换 1 状态变量的值根据事件而相应更改。在本示例中,elementStart() 更新状态: ifswitch(state) SAX2BestDeal 有几个实例变量来存储当前 name 和 price-quote 的内容。实际上,它维护树的一个小子集。请注意,与 DOM 不同,它从不拥有整个树,因为当应用程序使用过 name 和 price-quote 之后,它会废弃它们。 这是很有效的内存策略。事实上,您可以处理几十亿字节的文件,因为在任何时候,内存中只有一个小子集。 转换 2 语法分析器对文档中的每个字符(包括缩进)调用 characters()。只有记入 name 和 price-quote 中的文本才有意义,因此事件处理器使用状态。 switch(state) 转换 3 endElement() 的事件处理器更新状态,并调用 BestDeal 来处理当前元素:switch(state) { 学过的课程 应用程序逻辑(在 BestDeal 中)与事件处理器保持分离。事实上,在很多情况下,都独立于 XML 来编写应用程序逻辑。 分层方法在应用程序逻辑和语法分析之间建立一个明显的分界。 示例也清晰地说明了 SAX 比 DOM 更高效,但是它需要程序员完成更多工作。特别是,程序员必须显式地管理状态和状态之间的转换。(在 DOM 中,状态在树的递归遍历过程中是隐含的。) 灵活性 为灵活性而构建 <xbe:vendor> 但是将忽略联系信息。通常,简单地忽略未知元素是个好主意 - HTML 浏览器就总这样做。 强制实施结构 case VENDOR: 如果清单带有 contact 元素,它将报告: org.xml.sax.SAXException: Expecting <xbe:name> or <xbe:price-quote> 但是,如果实际上应用程序真正依赖于文档的结构,那么最好编写一个模式并使用验证语法分析器。 下一步做什么? 从作者网站的 XBE2 页面下载本摘录的清单。 在本书的发布者页面上查找有关 XML by Example 第二版的更多信息,该页面包括到书店网站的链接,您可以在那里订购这本书(将于 9 月出版)。 通过在官方的 SAX 主页上浏览 FAQ、历史和软件支持来更深入了解 SAX,它是由 XML-dev 列表上的团体开发的。从 SourceForge 上的 SAX 项目页面下载 SAX。 支持 SAX 的语法分析器:
如果在应用程序中需要目录文件,请下载 Norman Walsh 的目录软件包。 在 developerWorks XML 专区中查看最近发布的有关 SAX 的技巧,包括 Turning on validation in SAX-based parsers 和 Using SAX EntityResolver。 请仔细查看 developerWorks XML 专区作者专栏的 Working XML 中构建的更多 XML 应用程序示例。 请在 WAS 高级版 3.5 在线帮助的 technical background info on XML 中查看合并了 XML 语法分析器和 XSLT 引擎的 IBM WebSphere Application Server 如何支持 XML 。 关于 SAX 和 DOM 的幻灯片介绍,请从 Kelvin Lawrence(IBM XML 技术方面的
CTO)的发言人页面演讲稿“A
Detailed Introduction to Parsing and Processing XML documents using Java(TM)
technology”中打开幻灯片的 PDF。
如果您希望与本文章的作者或其所在机构,进一步交流,请联系:畅享网 姜小姐 jill.jiang@amt.com.cn | 021-51096826-112 | 在线联系 |
CIO职场,强者生存?在2008年,我们将继续看到CIO向商业运营方向发展。与此同时,我们也会看到商业管理人员将与技术管理人员一起竞争CIO岗位。 IT领导者的就职机会虽有不少,但其难度将会大幅提高。2…… |
|
|