对于 XML 处理,至少可以采取两种态度。一种是采用可以从许多编程语言调用的标准 API。第二种是修改 XML 处理库以适应正用于开发 XML 应用程序的编程语言的特定功能。 在本专栏的前几篇文章中,David 研究了使用他自己的 Python
xml_pickle
和
xml_objectify
库以及 Haskell
HaXml
库的第二种方法的多个版本。相当新、但发展很快的 Ruby 编程语言的常用库也采用第二种方法。 这里,David 介绍了 Ruby Electric XML(
REXML
),这种库采用 Ruby 的长处,并围绕它们构建 XML 处理。
REXML
具有类似于 SAX 的流样式和 DOM 的树样式的 API,但没有直接将它本身限制于这两种 API。
首先,让我介绍一下 Ruby 语言。这里,我不能完全保证能使不熟悉 Ruby 的读 者快速掌握这种语言 ― 既然如此,我建议参阅 参考资料中 的文章。 但我自己作为一名学习 Ruby 语言的程序员,我可以让您了解它为什么很有趣。 Ruby 是一种脚本语言,曾经被描述为“更好的 Perl”。此外,每一种更新的脚本语言可能也都被这样描述过,包括 Python。 当从完全 Smalltalk 式的 OOP 态度来看,对于 Ruby,描述变得更为确切,不是说 Perl 出了什么错(这是没有对某种语言的攻击),而是说 Ruby 保留了 Perl 的许多简明性及其许多捷径。 而且(至少对我而言),Ruby 达到了简明性, 而且还避免了在某些 Perl 代码中发现的“可执行行噪声”质量问题。同时, 与 Python 版本相比,许多 Ruby 结构“感觉”更直接(即使它们并没有真正节省太多总体长度)。
REXML
是由 Sean Russell 编写的库。它不是 Ruby 的唯一 XML 库,但它是很受欢迎的一个,并且是用纯 Ruby 编写( NQXML
也是用 Ruby 编写的, 但 XMLParser
封装了用 C 编写的 Jade 库)。 在他的 REXML 概述中,Russell 评论道:
我 有这样的问题:我不喜欢令人困惑的 API。有几种用于 Java 实现的 XML 解析器 API。其中大多数都遵循 DOM 或 SAX,并且在基本原理上与不断出现的众多 Java API 非常相似。也就是说,它们看 上去象是由从未使用过他们自己的 API 的理论家设计出来的。 通常,现有的 XML API 都很令人讨厌。他们采用一种被明确设计成非常简单、一流且功能强大的标记语言, 然后用讨厌的、过多的和大型 API 对它进行封装。甚至是为了进行最基本的 XML 树操作,我总是不得不参考 API 文档; 没有任何东西是凭直觉的,而且几乎每个操作都很复杂。
虽然我并不认为它有多么令人心烦,但我同意 Russell 的观点:XML API 对于大多数使用它们的人来说无疑带来了过多的工作量。
我猜想处理 XML 文档的所有程序员中有 80% 真正需要的只是一种提取数据并 将它们作为结构化的数据容易地进行操作的方法。DOM 使这一事情变得困难,而 SAX 使 它变得更困难。在前几篇文章中,我提倡我自己的 Python xml_objectify
模块的清晰性和简明性。让我使用文件 address.xml
(它描述一个地址簿)来快速重复一个示例。
如何使用 xml_objectify 来引用嵌套数据
>>> from xml_objectify import XML_Objectify |
我们需要略微知道一点数据的格式……但不必很多(有关本文中使用的样本文档, 请参阅 参考资料)。我们需要知道文档的根元素是地址 簿(但它的名称不必是 <addressbook></addressbook>
)。而且我们需要知道该文档可以列出多人(但是,如果只有一个人, 他可以作为 addressbook.person
或 addressbook.person[0]
来引用,那样也不会发生错误)。 从概念上讲,还需要知道人有地址,地址有城市。 知道这些就够用了!
相反,DOM ― 它将其本身标榜为 OOP 化的 XML ― 却要我们完成所有的操作步骤。第一个难题涉及根元素; 要完成这个任务至少有五种不同的方法可以想到:
使用 DOM 来命名 XML 文档根元素
>>> from xml.dom import minidom |
您还必须对究竟什么是方法、什么是属性做一些猜测(或在手边放一本手册)。 假设我们知道需要根元素,则 ._get_documentElement()
方法可能 是最好的选择。现在,如果我们想要向下找到第二个人的城市,就象 xml_objectify
示例中那样,应该怎么做呢?
如何使用 DOM 来引用嵌套数据
>>> addressbook = dom._get_documentElement() |
这种样式相当冗长,但或许是最接近的 DOM 等价样式。 您可以直接使用 .childNodes
属性数组来保存一些字符, 但这种样式是脆弱的,例如,如果 <addressbook></addressbook>
有子元素, 而 <person></person>
没有的话。您还必须知道一些本质细节, city
是元素属性,而不是子标记内容(任何一种方法都可能对正在讨论的基本数据有意义)。
![]() ![]() |
REXML
的目的是 正好够用。在最大程度上,它能很好地完成任务。 实际上, REXML
支持两种不同样式的 XML 处理 ― “树”和“流”。 第一种样式是 DOM 所尝试要做的更简单的版本;第二种样式是 SAX 所尝试要做的更简单的版本。 让我们先研究树样式。假设我们要提取上一个示例中的同一个地址簿文档。 下面的示例来自我所创建的经修改的 eval.rb
; 标准 eval.rb
(链接到 Ruby 教程)可以根据对复杂对象的表达式求值显示非常长的计算结果 ― 我的 eval.rb 在没有错误发生的情况下不作出反应:
如何使用 REXML 来引用嵌套数据
ruby> require "rexml/document" |
这个表达式很普通。 .to_a()
方法创建文档中所有 <person></person>
元素的数组,在其它命名中它可能是有用的。 元素有点象 DOM 节点,但它其实更接近于 XML 本身(而且使用起来也更简单)。 .to_a()
的参数是 XPath,在这种情况下,可以标识文档中任何地方的所有 <person></person>
元素。如果我们只需要第一层上的元素,可以使用:
创建匹配元素的数组
ruby> persons = addrbook.elements.to_a("/addressbook/person") |
我们甚至可以更直接地将 XPath 用作 .elements
属性的重载索引。例如:
使用 REXML 来引用嵌套数据的另一种方法
ruby> puts addrbook.elements["//person[2]/address"].attributes["city"] |
请注意,XPath 使用基于 1 的索引,不象 Ruby 和 Python 数组使用基于 0 的索引。换句话说, 它仍是我们正在检查其所在城市的同一个人。通过查看 REXML
请注意,XPath 使用基于 1 的索引,不象 Ruby 和 Python 数组使用基于 0 的索引。换句话说, 它仍是我们正在检查其所在城市的同一个人。通过查看
用 REXML 显示元素的 XML 源代码
ruby> puts addrbook.elements["//person[2]/address"] ruby> puts addrbook.elements["//person[2]/contact-info"] <contact-info> <email address="robb@iro.ibm.com"> <home-phone number="03-3987873"> </home-phone></email></contact-info> |
此外,XPath 不必只与一个元素匹配。我们已在定义 persons
数组时看见过,但另一个示例强调了这一点:
将多个元素与 XPath 匹配
ruby> puts addrbook.elements.to_a("//person/address[@state='CA']") |
与此相反, .elements
属性的索引只产生 第一个匹配的元素:
当 XPath 只匹配第一次出现时
ruby> puts addrbook.elements["//person/address[@state='CA']"] ruby> puts addrbook.elements.to_a("//person/address[@state='CA']")[0] |
也可以通过 REXML
中的 XPath
类使用 XPath 地址, 它具有诸如 .first()
、 .each()
和 .match()
这样的方法。
REXML
元素的一个独特的惯用方法是 .each
迭代器。虽然 Ruby 有一个可对集合进行操作的循环结构 for
, 但 Ruby 程序员通常更喜欢使用迭代器方法来将控制传递给代码块。下面的两种结构是等价的, 但第二种结构有更为自然的 Ruby 感觉:
通过在 REXML 中匹配 XPath 进行迭代
ruby> for addr in addrbook.elements.to_a("//address[@state='CA']") |
![]() ![]() |
出于“正好够用”的目的, REXML
的树方式可能是 Ruby 语言最简单的方法。 但 REXML
还提供了一种流方式,它象是 SAX 的更轻量级的变体。 正如使用 SAX 一样, REXML
没有向应用程序程序员提供来自 XML 文档的缺省数据结构。 相反,“listener”或“handler”类负责提供响应文档流中各种事件的一组方法。 以下是常用集合:开始标记、结束标记、遇到的元素文本等等。
虽然流方式远远没有象以树方式工作那样容易,但通常它的速度要快很多。 REXML
教程声称流方式的速度要快 1500倍。 虽然我没有尝试过对它进行基准测试,但我猜想这是一种有限的情况(我的小示例在树方式中也是瞬间完成的)。 总之,如果速度要紧,那么速度上的差异很可能是显著的。
让我们研究一个非常简单的示例,它所做的事情与上面的“列出加州城市”示例相同。 对它进行扩展以用于复杂的文档处理相对比较简单:
REXML 中 XML 文档的流处理
ruby> require "rexml/document" |
流处理示例中要注意的一件事情是,标记属性被作为一组数组传递, 它要处理的工作比起散列要稍微多一点(但可能在库中创建会更快)。
![]() ![]() |
这篇文章研究了另外一种比起 DOM、SAX 和 XSLT 麻烦的 API 更轻量级的替代方法。 连同前几篇文章中研究的 xml_objectify
、PYX 和 HaXml
选项一起,Ruby 程序员还得到了一种处理 XML 的快速方法,而不必经历陡峭的学习曲线。
- 您可以参阅本文在 developerWorks 全球站点上的 英文原文.
- Maya Stodte 为 IBM developerWorks 编写了 Ruby 简介,但已经过了两年,Ruby 在这段时间内已有一定的发展。
- Joshua Drake 也编写了一篇描述一些 基本 Ruby 结构的较新文章。
- 幸运的是,Ruby 在其网站上有一个极好的 教程。 语言参考和其它文档也值得一读。
- 我还阅读了 O'Reilly 出版的题为 Ruby in a Nutshell的 书籍, 由 Ruby 创建人 Yukihiro Matsumoto 所著。作为一名学习 Ruby 的程序员,我承认: 这本书很适合我,但可能不太适合更有经验的 Ruby 用户。 尽管如此,我仍认为这本书“没有被十分完整翻译”(原著是用日文写的)。 虽然作为参考这本书组织得很好,但有许多描述仍让我无法确定语言中那些偶尔的微妙之处。
- REXML 主页包含一个非常好的教程, 它并不完整,但它可以很好地帮助您尽快熟悉。
- 请访问有关 Ruby 和 XML 的新闻和讨论的 网站。
- 可以从 http://gnosis.cx/download/address.xml找到本文中使用的地址簿示例。
- 最后,请了解一下 IBM WebSphere Studio Application Developer, 这是一个易于使用的集成开发环境,可用于构建、测试和部署 J2EE 应用程序,包括从 DTD 和模式生成 XML 文档。