跳转到主要内容

单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件.

当您初次登录到 developerWorks 时,将会为您创建一份概要信息。您在 developerWorks 概要信息中选择公开的信息将公开显示给其他人,但您可以随时修改这些信息的显示状态。您的姓名(除非选择隐藏)和昵称将和您在 developerWorks 发布的内容一同显示。

所有提交的信息确保安全。

  • 关闭 [x]

当您初次登录到 developerWorks 时,将会为您创建一份概要信息,您需要指定一个昵称。您的昵称将和您在 developerWorks 发布的内容显示在一起。

昵称长度在 3 至 31 个字符之间。 您的昵称在 developerWorks 社区中必须是唯一的,并且出于隐私保护的原因,不能是您的电子邮件地址。

单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件.

所有提交的信息确保安全。

  • 关闭 [x]

XML 问题 #18: REXML 库

Ruby 编程语言中的 XML 处理

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

简介: 

专栏图标 对于 XML 处理,至少可以采取两种态度。一种是采用可以从许多编程语言调用的标准 API。第二种是修改 XML 处理库以适应正用于开发 XML 应用程序的编程语言的特定功能。 在本专栏的前几篇文章中,David 研究了使用他自己的 Python xml_picklexml_objectify 库以及 Haskell HaXml 库的第二种方法的多个版本。相当新、但发展很快的 Ruby 编程语言的常用库也采用第二种方法。 这里,David 介绍了 Ruby Electric XML( REXML ),这种库采用 Ruby 的长处,并围绕它们构建 XML 处理。 REXML 具有类似于 SAX 的流样式和 DOM 的树样式的 API,但没有直接将它本身限制于这两种 API。

发布日期: 2002 年 3 月 01 日
级别: 初级
访问情况 : 2536 次浏览
评论: 


首先,让我介绍一下 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.personaddressbook.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 的目的是 正好够用。在最大程度上,它能很好地完成任务。 实际上, 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

出于“正好够用”的目的, 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

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

关于报告滥用的帮助

报告滥用

谢谢! 此内容已经标识给管理员注意。


关于报告滥用的帮助

报告滥用

报告滥用提交失败。 请稍后重试。


developerWorks:登录


需要一个 IBM ID?
忘记 IBM ID?


忘记密码?
更改您的密码

单击提交则表示您同意developerWorks 的条款和条件。 使用条款

 


当您初次登录到 developerWorks 时,将会为您创建一份概要信息。您在 developerWorks 概要信息中选择公开的信息将公开显示给其他人,但您可以随时修改这些信息的显示状态。您的姓名(除非选择隐藏)和昵称将和您在 developerWorks 发布的内容一同显示。

请选择您的昵称:

当您初次登录到 developerWorks 时,将会为您创建一份概要信息,您需要指定一个昵称。您的昵称将和您在 developerWorks 发布的内容显示在一起。

昵称长度在 3 至 31 个字符之间。 您的昵称在 developerWorks 社区中必须是唯一的,并且出于隐私保护的原因,不能是您的电子邮件地址。

(长度在 3 至 31 个字符之间)


单击提交则表示您同意developerWorks 的条款和条件。 使用条款.

 


为本文评分

评论

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=10
Zone=XML
ArticleID=54816
ArticleTitle=XML 问题 #18: REXML 库
publish-date=03012002
author1-email=mertz@gnosis.cx
author1-email-cc=mertz@gnosis.cx

标签

Help
使用 搜索 文本框在 My developerWorks 中查找包含该标签的所有内容。

使用 滑动条 调节标签的数量。

热门标签 显示了特定专区最受欢迎的标签(例如 Java technology,Linux,WebSphere)。

我的标签 显示了特定专区您标记的标签(例如 Java technology,Linux,WebSphere)。

使用搜索文本框在 My developerWorks 中查找包含该标签的所有内容。热门标签 显示了特定专区最受欢迎的标签(例如 Java technology,Linux,WebSphere)。我的标签 显示了特定专区您标记的标签(例如 Java technology,Linux,WebSphere)。