首先,让我介绍一下 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 = XML_Objectify('address.xml').make_instance()
>>> print addressbook.person[1].address.city
New York |
我们需要略微知道一点数据的格式……但不必很多(有关本文中使用的样本文档, 请参阅
参考资料)。我们需要知道文档的根元素是地址 簿(但它的名称不必是
<addressbook> )。而且我们需要知道该文档可以列出多人(但是,如果只有一个人, 他可以作为
addressbook.person 或
addressbook.person[0] 来引用,那样也不会发生错误)。 从概念上讲,还需要知道人有地址,地址有城市。
知道这些就够用了!
相反,DOM ― 它将其本身标榜为 OOP 化的 XML ― 却要我们完成所有的操作步骤。第一个难题涉及根元素; 要完成这个任务至少有五种不同的方法可以想到:
使用 DOM 来命名 XML 文档根元素
>>> from xml.dom import minidom
>>> dom = minidom.parse('address.xml')
>>> dom.firstChild
<DOM Element: addressbook at 1811436>
>>> dom._get_documentElement()
<DOM Element: addressbook at 1811436>
>>> dom._get_firstChild()
<DOM Element: addressbook at 1811436>
>>> dom.getElementsByTagName('addressbook')[0]
<DOM Element: addressbook at 1811436>
>>> dom.childNodes[0]
<DOM Element: addressbook at 1811436>
|
您还必须对究竟什么是方法、什么是属性做一些猜测(或在手边放一本手册)。 假设我们知道需要根元素,则
._get_documentElement() 方法可能 是最好的选择。现在,如果我们想要向下找到第二个人的城市,就象
xml_objectify 示例中那样,应该怎么做呢?
如何使用 DOM 来引用嵌套数据
>>> addressbook = dom._get_documentElement()
>>> print addressbook.getElementsByTagName('person')[1].\
.. getElementsByTagName('address')[0].getAttribute('city')
New York |
这种样式相当冗长,但或许是最接近的 DOM 等价样式。 您可以直接使用
.childNodes 属性数组来保存一些字符, 但这种样式是脆弱的,例如,如果
<addressbook> 有子元素, 而
<person> 没有的话。您还必须知道一些本质细节,
city 是元素属性,而不是子标记内容(任何一种方法都可能对正在讨论的基本数据有意义)。
REXML 的目的是
正好够用。在最大程度上,它能很好地完成任务。 实际上,
REXML 支持两种不同样式的 XML 处理 ― “树”和“流”。 第一种样式是 DOM 所尝试要做的更简单的版本;第二种样式是 SAX 所尝试要做的更简单的版本。 让我们先研究树样式。假设我们要提取上一个示例中的同一个地址簿文档。 下面的示例来自我所创建的经修改的
eval.rb ; 标准
eval.rb (链接到 Ruby 教程)可以根据对复杂对象的表达式求值显示非常长的计算结果 ― 我的 eval.rb 在没有错误发生的情况下不作出反应:
如何使用 REXML 来引用嵌套数据
ruby> require "rexml/document"
ruby> include REXML
ruby> addrbook = (Document.new File.new "address.xml").root
ruby> persons = addrbook.elements.to_a("//person")
ruby> puts persons[1].elements["address"].attributes["city"]
New York
|
这个表达式很普通。
.to_a() 方法创建文档中所有
<person> 元素的数组,在其它命名中它可能是有用的。 元素有点象 DOM 节点,但它其实更接近于 XML 本身(而且使用起来也更简单)。
.to_a() 的参数是 XPath,在这种情况下,可以标识文档中任何地方的所有
<person> 元素。如果我们只需要第一层上的元素,可以使用:
创建匹配元素的数组
ruby> persons = addrbook.elements.to_a("/addressbook/person") |
我们甚至可以更直接地将 XPath 用作
.elements 属性的重载索引。例如:
使用 REXML 来引用嵌套数据的另一种方法
ruby> puts addrbook.elements["//person[2]/address"].attributes["city"] New York |
请注意,XPath 使用基于 1 的索引,不象 Ruby 和 Python 数组使用基于 0 的索引。换句话说, 它仍是我们正在检查其所在城市的同一个人。通过查看
REXML 请注意,XPath 使用基于 1 的索引,不象 Ruby 和 Python 数组使用基于 0 的索引。换句话说, 它仍是我们正在检查其所在城市的同一个人。通过查看
用 REXML 显示元素的 XML 源代码
ruby> puts addrbook.elements["//person[2]/address"] <address city='New York' street='118 St.' number='344' state='NY'/> ruby> puts addrbook.elements["//person[2]/contact-info"] <contact-info> <email address='robb@iro.ibm.com'/> <home-phone number='03-3987873'/> </contact-info> |
此外,XPath 不必只与一个元素匹配。我们已在定义
persons 数组时看见过,但另一个示例强调了这一点:
将多个元素与 XPath 匹配
ruby> puts addrbook.elements.to_a("//person/address[@state='CA']")
<address city='Sacramento' street='Spruce Rd.' number='99' state='CA'/>
<address city='Los Angeles' street='Pine Rd.' number='1234' state='CA'/>
|
与此相反,
.elements 属性的索引只产生
第一个匹配的元素:
当 XPath 只匹配第一次出现时
ruby> puts addrbook.elements["//person/address[@state='CA']"]
<address city='Sacramento' street='Spruce Rd.' number='99' state='CA'/>
ruby> puts addrbook.elements.to_a("//person/address[@state='CA']")[0]
<address city='Sacramento' street='Spruce Rd.' number='99' state='CA'/> |
也可以通过
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']")
| puts addr.attributes["city"]
| end
Sacramento
Los Angeles
ruby> addrbook.elements.each("//address[@state='CA']") {
| |addr| puts addr.attributes["city"]
| }
Sacramento
Los Angeles |
出于“正好够用”的目的,
REXML 的树方式可能是 Ruby 语言最简单的方法。 但
REXML 还提供了一种流方式,它象是 SAX 的更轻量级的变体。 正如使用 SAX 一样,
REXML 没有向应用程序程序员提供来自 XML 文档的缺省数据结构。 相反,“listener”或“handler”类负责提供响应文档流中各种事件的一组方法。 以下是常用集合:开始标记、结束标记、遇到的元素文本等等。
虽然流方式远远没有象以树方式工作那样容易,但通常它的速度要快很多。
REXML 教程声称流方式的速度要快
1500倍。 虽然我没有尝试过对它进行基准测试,但我猜想这是一种有限的情况(我的小示例在树方式中也是瞬间完成的)。 总之,如果速度要紧,那么速度上的差异很可能是显著的。
让我们研究一个非常简单的示例,它所做的事情与上面的“列出加州城市”示例相同。 对它进行扩展以用于复杂的文档处理相对比较简单:
REXML 中 XML 文档的流处理
ruby> require "rexml/document"
ruby> require "rexml/streamlistener"
ruby> include REXML
ruby> class Handler
| include StreamListener
| def tag_start name, attrs
| if name=="address" and attrs.assoc("state")[1]=="CA"
| puts attrs.assoc("city")[1]
| end
| end
| end
ruby> Document.parse_stream((File.new "address.xml"), Handler.new)
Sacramento
Los Angeles
|
流处理示例中要注意的一件事情是,标记属性被作为一组数组传递, 它要处理的工作比起散列要稍微多一点(但可能在库中创建会更快)。
这篇文章研究了另外一种比起 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 文档。

David Mertz 希望一切都变得美好。可以通过 mertz@gnosis.cx 与 David 联系;在 http://gnosis.cx/publish/上详细介绍了他的生活。 欢迎对过去的、这一篇和以后的专栏文章提出意见和建议。