ruby on rails

您很可能听说过 Ruby on Rails。甚至有可能使用过它;或者是刚刚开始使用它进行编程。相信大家已经看出本文的主题就是 Rails,不论读者属于哪种情况,都将从本文中受益。Ruby 与 XML 是一对梦幻组合 —— 欲知详情请继续阅读。
开始之前

本教程面向那些想要了解关于建立 Rails 应用程序框架及使用 Ruby 和 Rails 处理 XML 的基础知识的开发群体。初级和中级程序员或只稍微了解一点 Rails 知识的开发人员可能受益最大。本文将花费少量的时间对 Rails 进行大致的介绍,如果有必要地话还将讨论一下 Ruby 语法,但是这些主题在其他地方有更详细的介绍。有关更多信息,请参见 参考资料。

本文的主旨

现在我们将构建一个 Rail 应用程序,并讨论一些有关 Rails 的工作原理、组织构造和使用方法的基本知识,然后将继续介绍 Rails 结合 XML 的使用方法。在 Ruby 中生成和解析 XML 有很多种方法,我们将介绍其中的一小部分,包括 REXML(Ruby Electric XML)、Builder 和 Hpricot(从技术角度来说,Hpricot 是一种 HTML 解析器 —— 但运行速度快,同时也可以处理 XML)。

简介

在过去的 5 到 7 年时间里,XML 成为了应用程序间通信的事实标准(或者至少是事实标准之一)。无论是运行在同一台计算机上的一个应用程序,还是以太网中公开的的一些 Web 服务,抑或是支付网关,运输网关或者其他随机数据源,都需要了解 XML(不管您喜欢与否)。这样说来,撰写这篇教程似乎有些讽刺意味 —— 介绍 Rails 框架上的 XML 处理。不过 Rails 也日渐成名,Loud Thinking 一书中说过:“学习如何使用开源 Web 框架 Rails 轻松愉悦地创建实际应用程序,其所需编写的代码比大多数框架花费在 XML 处理上的还要少。”。这篇教程中所有真正的 XML 处理都不依赖 Rails。所有用到的 XML API 都是直接用 Ruby 编写,并能在任意 Ruby 应用程序中使用。我们将在 Rails 上下文环境中开发 XML API,因为这样可以上传文件以进行处理,并能方便地与编写的代码进行交互。

Ruby

Ruby 是一种动态类型的脚本语言(如果您还不知道),它是由 Yukihiro "Matz" Matsumoto 创建的。他从 1993 年就开始开发这种语言,直到 1995 年才公开发布。Ruby 拥有各种优秀的特性,比如说闭包、回调和 duck typing。可以在它的上面构建一些很灵活的框架,这就导致了 Rails 的出现。







Rails

Rails 是由 David Heinemeier Hansson 构建的一种框架,于 2004 年公开发布。Rails 使用模型、视图、控制器( MVC )架构来构建 Web 应用程序,并依据约定优于配置来强调编程。Rails 拥有许多令人惊叹的优异特性,比如说完全自动化的对象关系映射和持久性存储。在这篇教程中,将演示使用一些基本的 Rails 特性(即,简单的控制器特性和视图特性)进行 XML 解析。







Ruby 和 XML

Ruby 拥有许多 API 可用于解析和生成 XML,REXML 是标准的 API,它是 Ruby 核心的一部分,而 Builder 和 Hpricot 之类的框架则可用作 gem。从本质上说,Gem 是第三方的 API,或者说是符合某一特定格式的库。它们可以方便地下载和安装(从概念上讲,类似于 JAR 文件中的第三方 Java API)。







REXML

REXML 是用于解析和创建 XML 的标准 Ruby 库。它附于 Ruby 核心语言发行版中,因此随时可以获得。







Builder

Builder 是一种第三方的 gem,在生成 XML 内容方面 Builder 越来越受人们欢迎。它的语法非常整洁且易于使用和阅读,使用 Ruby 的 method_missing 回调方法可以生成 XML。







Hpricot

Hpricot 是一种快速的 HTML 解析器(不过也可以使用它解析 XML!),使用 “why the lucky stiff” 进行编写,后者也很流行。







#{random_xml_util}

Ruby 还拥有一些其他的 API 可用于解析 XML,但是这些内容并不在文本的讨论范围之内。如果想要了解相关信息,请访问 RubyForge 并搜索所需的内容。

现在,开始介绍重要的内容。在这一节中,我们将用到一些绑定脚本,这些脚本可用来生成各种优秀的 Rails 部件。在开始介绍 XML 处理之前,我将先讨论一些 Rails 的基本知识。

生成 Rails 应用程序存根

我是通过终端在 Mac OS X 中完成本教程的各种任务的,因此如果您使用的是 Windows 操作系统,那么也可以轻松地使用命令行进行操作。所有命令的作用应该完全相同。

首先,我们需要创建 Rails 应用程序,为此可以执行清单 1 中的命令。


清单 1. 用于生成 Rails 应用程序存根的命令
rails xml_tutorial -f


这条命令将运行 Rails 脚本并告诉它我们要新建一个名为 xml_tutorial 的 Rails 应用程序。命令中还使用了 -f 标志,用于将 Rails 版本固定为操作系统中安装的当前版本。这将会把 Rails 框架的当前版本复制到 vendor/plugins 目录下,有效地避免运行应用程序时出现的一些麻烦,如果您安装了一个不同版本的 Rails 的话。

运行脚本,生成应用程序结构。产生的部分输出如清单 2 所示。


清单 2. Rails 应用程序创建脚本产生的部分输出
monkey:~/Work/Rails_XML_Tutorial daniel$ rails xml_tutorial -f create create app/controllers create app/helpers create app/models create app/views/layouts......rm -rf vendor/railsmkdir -p vendor/railscd vendor/railsmv activesupport-1.4.1 activesupportmv activerecord-1.15.2 activerecordmv actionpack-1.13.2 actionpackmv actionmailer-1.3.2 actionmailermv actionwebservice-1.2.2 actionwebservicecd -frozemonkey:~/Work/Rails_XML_Tutorial daniel$


现在,我们来看看默认创建的应用程序结构,从而开始了解 Rails “约定优于配置”。在 Rails 应用程序中,我们根据文件的内容把文件存放在特定的位置。控制器、帮助文档、模型、视图 —— 所有这些文件都有各自的存放目录,因此 Rails 就可以知道文件的内容,以及如何使用这些文件。前面已经提到,本文不会深入地介绍 Rails 目录的布局,不过您可以通过查阅 参考资料 获得大量有用的信息。





回页首



创建控制器

如您如料,Rails 附带了脚本用于为某些类型的对象生成存根。可用 Rails 生成的对象存根包括控制器、模型、邮件程序、Web 服务等等。我们只要创建一个控制器就可以了,那么现在就开始吧。首先,我们需要切换到 Rails 应用程序所在的目录。(如清单 3 所示)。


清单 3. 创建 Rails 控制器
monkey:~/Work/Rails_XML_Tutorial daniel$ cd xml_tutorial/
monkey:~/Work/Rails_XML_Tutorial/xml_tutorial daniel$ ruby script/generate controller main
exists app/controllers/
exists app/helpers/
create app/views/main
exists test/functional/
create app/controllers/main_controller.rb
create test/functional/main_controller_test.rb
create app/helpers/main_helper.rb
monkey:~/Work/Rails_XML_Tutorial/xml_tutorial daniel$



清单 3 调用 Ruby 解释程序,让它运行 script 目录下的一个名为 generate 的文件,并将参数 controller main 传递给该文件。Rails 生成了一个名为 ‘main’ 的控制器,这时就可以使用这个控制器进行操作了。按照 “约定优于配置” 的原则,控制器中定义的每个公有方法都被称为一个动作,并且通常拥有一个关联的视图。可以根据 URI 约定对所有这些方法进行访问。

比方说,现在有一个名为 “main” 的控制器。如果我们在控制器中定义了一个名为 “index” 的公有方法,则可以在 http://localhost:3000/main/index 分别访问该控制器和动作。

Rails 提供了许多极好的方法,可以延伸此类应用,但是本教程所要讨论的内容并不仅限于这些。还有许多更有趣的东西值得讨论 —— 比如说 XML。





创建布局和视图

这里我们不需要任何精美华丽的视图,因此简单准备一下尽快使应用程序运行起来。有关示例文件,请查看代码下载部分。

布局和视图之间的区别在于:布局是全面的页面布局。它反复地用于应用程序中的一些常用元素(比如:导航、页眉、页脚等等)。视图是在布局中使用。

同样,按照约定在布局目录中创建一个名为 “main.rhtml” 的文件。我们以控制器的名称给它命名,这样 Rails 就能自动分辨此控制器使用的布局。然后我们将创建一个索引视图,它拥有一个基本的表单上传字段,因此可以使用它上传和解析 XML 文档。(为节省时间,可以从代码下载中复制 index.rhtml 文件)





最终效果

重要目录和文件的布局的最终效果如图 1 所示。


图 1. 重要文件和部分目录的结构


我们已经有了 xml_tutorial 目录,它是 Rails 应用程序的根目录。然后我们也有了 main_controller.rb 文件,它是使用 Rails 内置生成脚本生成的。我们创建了一个名为 “main.rhtml” 的布局文件和一个名为 “index.rhtml” 的视图。请注意,视图位于 main 目录下。用来告知 Rails 该目录下的所有视图都属于 main 控制器。这些都遵守了 “约定优于配置” 的原则。这个原则看起来还不错。这时,当您在浏览器中打开 http://localhost:3000/main/index 时,Rails 知道您需要以下内容:

访问 main_controller.rb 文件
执行 index 方法
在最终显示页面中使用 layouts/main.rhtml 布局。
在返回给用户的布局文件中包含 main/index.rhtml 的内容。





启动服务器

一旦您创建好视图,就可以启动服务器了(这取决于您安装的软件,可能需要启动 WEBrick、Lighttpd 或 Mongrel,这些服务器都不在本文的讨论范围之内)。要启动服务器,请返回到命令行(如清单 4 所示)。


清单 4. 启动 Rails 应用程序
monkey:~/Work/Rails_XML_Tutorial/xml_tutorial daniel$ ruby script/server
=> Booting lighttpd (use 'script/server webrick' to force WEBrick)
=> Rails application started on http://0.0.0.0:3000
=> Call with -d to detach
=> Ctrl-C to shutdown server (see config/lighttpd.conf for options)



如您所见,运行 ruby script/server 命令后服务器就开始运行了。图 2 显示了用户界面。


图 2. 用户界面


这篇教程其余部分还介绍了哪些内容呢?那就继续学习吧!


创建新 XML 文档

创建 XML 文档有几种不同的方法,从使用 API 到手工编写代码都可以。这一节将演示如何使用 REXML 和 Builder 来生成 XML 文档。我们将使用两种 API 生成相同的文档,让大家了解这两种 API 的语法之间有什么区别。

示例 XML 文档

为演示生成和解析功能,我们将使用一个示例文档,它列出了一些食物。清单 5 显示了将要创建和使用的 XML 文档的基本结构。


清单 5. 本教程中使用的示例 XML
Fish Head Curry Little India ... Pork Enchilada Iguana Cafe


生成的实际 XML 文档将包含大约十二个 Dish 元素,它们来自新家坡我最喜欢的一些地方。这个列表含有几种令人反感的菜肴,这样便可以把某些菜肴评为比较差的级别。

我们将把一些硬编码数据存储在控制器中,并根据这些数据生成这个文档。数据将作为 Hash 的 Array 元素存储在控制器(main_controller.rb)中,因此在使用 REXML 和 Builder 生成 XML 时可以轻松地访问数据。考虑到读者可能还不太明白,清单 6 中包含了一小段代码用于展示硬编码数据是如何存储在控制器中的。


清单 6. 用于生成示例 XML 的 Ruby 数据结构(Array 和 Hash)
class MainController < ApplicationControllerDISHES = [ { :rating =>2, :category => "singaporean", :dish_name => "Fish Head Curry", :where_to_buy => "Little India" }, { :rating =>8, :category => "western", :dish_name => "Cowboy Burger", :where_to_buy => "Brewerkz" }, # ... Other hashes here...]...


接下来,我们将介绍如何使用 REXML 将这些数据转换成 XML。





REXML

本文前面已经提到,REXML 是一种标准的 XML API,它由核心 Ruby 发行版中附带。因此不用担心它的获得。使用 REXML 非常简单,尽管对于编程创建大型的 XML 程序,很多人更喜欢使用 Builder,因为使用它编写的代码比较简短。main_controller.rb 中给出了本节的清单中所有的示例代码。清单 7 显示了如何使用 REXML 创建新 XML 文档。


清单 7. 使用 REXML 创建新 XML 文档
... { :rating =>8, :category => "mexican", :dish_name => "Pork Enchilada", :where_to_buy => "Iguana Cafe" } ] private def generate_rexml doc = REXML::Document.new end


这不算太难,是吧?添加一个根元素如何?清单 8 显示了其过程。


清单 8. 在 REXML 文档中创建根节点
... def generate_rexml doc = REXML::Document.new root = doc.add_element( "Food" ) endend


既然已经有了示例 XML 文档的基本结构,下一步就该迭代硬编码数据结构,生成其余的元素。


清单 9. 使用 REXML 生成示例 XML 文档的内容
...root = doc.add_element( "Food" )DISHES.each{ |element_data| dish_element = root.add_element( "Dish" ) dish_element.add_attribute( "rating", element_data[:rating] ) dish_element.add_attribute( "category", element_data[:category] ) dish_name_element = dish_element.add_element( "DishName" ) dish_name_element.add_text( element_data[:dish_name] ) where_to_buy_element = dish_element.add_element( "WhereToBuy" ) where_to_buy_element.add_text( element_data[:where_to_buy] )}...


希望您熟悉迭代 Array 的 Ruby 语法。如 清单 9 所示,代码遍历 Array 元素中的每个 Hash,然后为每一个 Hash 创建一个 XML Dish 元素。为 Dish 元素指定了两个属性(“rating” 和 “category”)和两个子元素(“DishName” 和 “WhereToBuy”)。为了使代码整洁且易于阅读,使用的变量名和语法显得有些冗长。既然已经成功创建了 XML 文档,我们就需要把它写到一个对象中去,以便将其发回客户机。REXML::Document 类中有一点很不错,那就是 write 方法会把其内容写到所有响应 << string 的对象中去。真是好运连连,因为 Ruby 的 String 类响应 << string,所以只需将 XML 文档写入 String 类中即可(如清单 10 所示)。


清单 10. 把 REXML 文档的内容写入 String 类
... where_to_buy_element.add_text( element_data[:where_to_buy] ) } doc.write( out_string = "", 2 ) return out_string endend


清注意在 Ruby 中,我们可以动态地声明变量,清单 10 中就是如此。我们已经声明了变量 out_string 并为它指定了一个初始值,作为发送至 doc.write 的参数的一部分。(第二个参数表示缩排)许多其他语言,比如说 Java,要求该任务分两步完成,其代码如清单 11 所示。


清单 11. 把 REXML 文档的内容写入 String 类的另一种方法
out_string = ""doc.write( out_string, 2 )


在 Ruby 中也可以采用这种方式,不过最好是删除代码中额外的行。很快,为实际查看 XML 文档,可查看劫持 Rails 响应的代码,并将生成到客户机浏览器中作为文件下载的 XML 文档写入类。不过首先还是看看如何使用 Builder 而不是 REXML 生成同样的 XML 文档。





Builder

使用 Builder 编写生成 XML 标记的代码极其容易。以致于在阅读了本节其余内容后,您可能甚至会想请几天假来仔细考虑 Builder 的神奇所在。

清单 12 显示了如何创建文档。


清单 12. 使用 Builder 创建新的 XML 文档
def generate_builder doc = Builder::XmlMarkup.new( :target => out_string = "", :indent => 2 ) end


请注意,此处 Builder 使用 target 作为构造函数的参数之一。通过 REXML 在使用 API 生成一些 XML 文档后选择 target 输出对象。Builder 将预先请求一个对象,用于写入生成的 XML 文档。请注意,此处进行的内联变量声明与 REXML 示例中的一样。

现在来见识一下 Ruby 的 method_missing 回调方法的威力以及 API 如何利用该方法。清单 13 显示了如何创建 Food 元素。


清单 13. 使用 Builder 在示例 XML 文档中创建根元素
def generate_builder doc = Builder::XmlMarkup.new( :target => out_string = "", :indent => 2 ) doc.Food end


此时,您有可能会说 ——“等一等,XML API 中没有 Food 方法 —— 这太可笑了!”—— 当然被您说中了。Ruby 会说 “是吗?我不知道对象中有一个 Food 方法,我将把这个调用分派给 method_missing,并让 API 来进行处理,如果可以的话,否则,我将产生一个异常。”因此实际上 XML 文档中的内容如清单 14 所示。


清单 14. 由 Builder 文档前一方法调用产生的 XML 文档



现在,看看如何创建嵌套元素、属性和文本节点,如清单 15 所示。


清单 15. 使用 Builer 生成示例 XML 文档
def generate_builder doc = Builder::XmlMarkup.new( :target => out_string = "", :indent => 2 ) doc.Food { DISHES.each{ |element_data| doc.Dish( "rating" => element_data[:rating], "category" => element_data[:category] ){ doc.DishName( element_data[:dish_name] ) doc.WhereToBuy( element_data[:where_to_buy] ) } } } return out_string end


这就完成了!利用这一小段漂亮的代码通过 Builder 即可生成示例 XML 文档。

您会发现,嵌套代码块即创建嵌套的 XML 元素。如果您想为 XML 元素指定属性,可以传递一个 Hash 元素作为参数,这样就可以创建各种属性。比方说,在上面的代码中,清单 16 中的调用将产生一个 XML 元素,如 清单 17 所示。


清单 16. 如何使用 Builder 创建带属性的 XML 元素
doc.Dish( "rating" => element_data[:rating], "category" => element_data[:category] )


清单 17 显示了生成的 XML 元素。


清单 17. 由前面列出的方法调用生成的 XML 元素



示例代码中有一种情况没有考虑到,那就是可能需要创建一个含有文本数据和属性的元素。设想创建一个类似清单 18 所示的 XML 元素。


清单 18. 同时含有属性和文本的 XML 元素示例
Cowboy Burger


为此可在 Builder 中先传递一个 String 参数,然后再传递一个 Hash 参数,如清单 19 所示。


清单 19. 如何使用 Builder 创建带属性和文本的 XML 元素
doc.Dish( "Cowboy Burger", "rating" => 8, "category" => "western" )


如您所见,Builder 中的语法非常易于使用且直观明了。除了我之前提到的以外,表 1 引用了 Builder 的 document 对象的一些特殊的方法。


表 1. Builder document 对象中的一些特殊方法
方法名 动作
cdata! 在 XML 标记中插入 CDATA 代码段
comment! 在标记中插入 XML 注释
declare! 在标记中插入 XML 声明
instruct! 在标记中插入处理指令
target! 返回文档 target 对象(向其中写入 XML 的对象)

有关 Builder 的更多信息,请参阅 RubyForge 上的 Rdoc。下面我们看看如何把生成的 XML 返回给浏览器。

下载 XML 文档

一般来说,Rails 控制器中的动作方法的作用是向发出请求的客户机呈现视图。通常,视图就是类似于 RHTML 或 RJS 之类的文件。但是如果需要直接获取响应,并且发送完全不同的内容,该怎么做呢。接下来我们就此作出讨论。

劫持 Rails 响应

有时我们需要把自己的内容呈现给客户机,比如呈现此示例 XML 中生成的一个文件。有时可能是一个 PDF 文档、逗号定界值或者一些其他格式的文件。Rails 只需一小段代码便可以实现这个功能。考察 main_controller.rb 中的方法,它负责把生成的 XML 文件作为下载文件 sample.xml 发回客户机的浏览器(如清单 20 所示)。


清单 20. 劫持 Rails 响应,向客户机发送自定义数据
def hijack_response( out_data ) send_data( out_data, :type => "text/xml", :filename => "sample.xml" )end


真的就是这么简单。在本例中,out_data 是包含生成的 XML 文件的字符串实例,“text/xml” 是在响应中设置的 mime 类型,“sample.xm” 是显示给客户机的文件名称,作为客户机试图下载的文件的名称。 send_data 是 ActionController::Streaming 模块的一个保护类型的实例方法,后者在 ActionController::Base 中作为 mixin 存在,反过来从 ActionController::Base 继承作为类层次结构的一部分(如清单 21 所示)。


清单 21. Rails 控制器类层次结构
ActionController::Base (Part of the Rails framework)| -- ApplicationController (application.rb - generated for you) | -- MainController (main_controller.rb - your controller)


模块和 mixin 不在本文的讨论范围之内,不过可以参阅 参考资料 获得更多有关它们的信息。既然已经有了一些 XML 文档,那就用它们来做点工作吧。


在 Rails 中上传文件(本例使用 XML 文件)

试想一下,这篇教程给您带来的激动吧。您可能按部就班的操作,通过单击一些链接生成示例 XML 文档。您激动万分地向老板展示生成的 XML。可是老板的反应却是失望至极。他对 Fish Head Curry 和 Pig Organ Soup 糟糕的食物评级感到很不解。老板要求您立刻为他构建一个 Web 应用程序,通过该应用程序可以上传 XML 文档并对他所喜欢的食物指定更好的等级。您很幸运,本教程其余部分就是要讨论这个问题。看看如何使用 Rails 上传文件以供处理。

视图代码

考虑到完整性因素,我们简要地看一下处理文件上传表单的视图中的一小段代码(如清单 22 所示)。


清单 22. 用于上传 XML 文档的 RHTML 表单
<% form_tag( {:action => 'upload'}, {:multipart => true} ) do -%>

<%= file_field_tag "xml_file" %>
<%= submit_tag "Parse with REXML" %> <%= submit_tag "Parse with Hpricot" %>

<% end -%>


代码使用了标准的 form_tag 和 helper 方法(它是 Rails 框架的一部分),并结合使用了 file_field_tag(用于生成 HTML 文件输入标记)和 submit_tag(用于生成 HTML 提交按钮)。还将使用 submit_tag 调用的值来确定使用控制器中的哪个处理程序来解析上传的 XML 文档。代码不是很漂亮,但很好完成了例子所需的功能。请注意,没有使用模型支持的 helper 标记,因为这篇教程中并没有构建模型。





控制器代码

要获得 Rails 控制器中上传文件的内容,只需使用一行代码(如清单 23 所示)。


清单 23. 在 Rails 控制器的 params 对象中检索上传文件
def upload uploaded_file = params[:xml_file] end


这就完成了!如果用户在这个文件框中上传了文件,则产生的变量将是一个 StringIO 或 File 实例。如果用户并未指定文件,那么上传的结果将是一个空 String。uploaded_file 对象的类是什么并不重要,只需关心它是否响应了 read 方法。可以有条件地把文件的内容声明和指定为一个变量,前提是 uploaded_file 实例响应了 read 方法(如清单 24 所示)。


清单 24. 把上传文件的内容读入 String 变量
def upload uploaded_file = params[:xml_file] data = uploaded_file.read if uploaded_file.respond_to? :read end


完成这些之后,指定解析上传文件的方法。在本例中,可以传给 REXML,也可以传给 Hpricot,这由用户在浏览器中单击的按钮决定(如清单 25 所示)。


清单 25. 根据用户输入指定相应的解析方法
def upload uploaded_file = params[:xml_file] data = uploaded_file.read if uploaded_file.respond_to? :read if request.post? and data case params[:commit] when "Parse with REXML" : parse_with_rexml( data ) when "Parse with Hpricot" : parse_with_hpricot( data ) else parse_recursive( data ) end else redirect_to :action => 'index' end end


清单 25 中惟一值得注意的代码段就是新代码的第一行。第一个 if 语句只检查请求是否是 POST 类型(与之相反的是 GET),并确保确实有上传的数据(确保用户确实选取了文件用于上传)。这就完成了!下面我们看看要真正解析和操作这个上传的 XML 文件,需要编写什么样的代码。

解析和操作 XML 文件

既然老板还在为 Fish Head Curry 的分数过低而闷闷不乐,想必您也等不及要继续开发解析和操作 XML 文件的功能。不过同时,从前面的经验我们知道这些 XML API 拥有易于操作的界面。可以着手下一步开发,马上就为那些菜肴评一个高点的分数,让老板高兴起来。

这里的目标是解析上传的 XML 文档,根据需要进行搜索和迭代,找到 Fish Head Curry 和 Pig Organ Soup。,并把它们的等级升级为 6。您希望这样可以让老板面露喜色,不出意外的话,老板会注意到代码的妙处,而不是分数较低的菜肴。

使用 REXML 进行解析

在本节中,我们将使用 REXML 解析示例文档并正确地更新相关元素。使用 REXML,遍历某个 XML 文档是非常简单的,因为所有的元素和属性都可以通过数组样式或 XPath 查询或简单的迭代来进行访问。我在 参考资料 中列出了一些其他有关 REXML 的优秀文章。但现在先动手操作。首先,使用 XPath 查询搜索文档中所有的 DishName 元素(如清单 26 所示)。


清单 26. 使用 REXML 的 XPath 类搜索 DishName 元素
def parse_with_rexml( xml_data ) doc = REXML::Document.new( xml_data ) REXML::XPath.each( doc, "//DishName" ){ |dish_name_element| #... your code here ... #... do work with elements ... } doc.write( out_string = "", 2 ) hijack_response( out_string ) end


这一段非常简单的代码将把文档中的所有 DishName 元素找出来,然后对它们进行迭代。您可以根据需要,在代码块中加入自定义的代码,对元素进行操作。文本 //DishName 是一条 XPath 查询语句,它的基本意思是 “获取此文档中所有的 DishName 元素”。

在此特例中,我们要检查元素的文本值,看它是否为 “Fish Head Curry” 或 “Pig Organ Soup”。


清单 27. 检索给定 XML 元素的文本值
if dish_name_element.text == "Fish Head Curry" or dish_name_element.text == "Pig Organ Soup" #... your code here ... #... do work with elements ...end


调用一个 REXML 元素的 text 方法将返回其第一个子文本元素(如果存在的话)的 String 值,不存在则返回零。

一旦获得了符合要求的 DishName 元素,下一步就需要找出它的父元素。不要忘了文档的结构(如清单 28 所示)。


清单 28. 示例 XML 文档中的部分代码段
... Fish Head Curry Little India ...


可以看出 Dish 元素是父元素,调用 parent 方法便可以访问它(如清单 29 所示)。


清单 29. 使用 REXML 检索某个 XML 元素的 parent 元素
parent = dish_name_element.parent


有了父元素之后,就可以访问 rating 属性,并给它指定一个新值(如清单 30 所示)。


清单 30. 使用 REXML 给 XML 元素中的属性指定新值
parent.attributes["rating"] = 6


完成了!已经可以让您的老板高兴了!可以在清单 31 中看一看完整的代码块。


清单 31. 使用 REXML 导航、解析和修改 XML 元素的代码块
XPath.each( doc, "//DishName" ){ |dish_name_element| if dish_name_element.text == "Fish Head Curry" or dish_name_element.text == "Pig Organ Soup" parent = dish_name_element.parent parent.attributes["rating"] = 6 end}


这个时候,您感觉不错,并且在经历了评级失败后希望给老板一个惊喜。于是决定再编写一些用于变更等级的 XML 操作代码片段,这次只使用一行代码。现在您感觉就像是一个 Ruby 的英雄,因此这不值一提(考虑到可读性,可以将代码分成三行)。如清单 32 所示。


清单 32. 解析和操作示例 XML 文档的另一种方法,只使用一行代码
doc.root.each_element( "//DishName" ){ |e| e.parent.attributes["rating"] = 6 unless ["Fish Head Curry", "Pig Organ Soup"].index( e.text ).nil? }


这个方法并没有明确的使用一条 XPath 语句,而是使用的 each_element 方法,该方法是 REXML 的 Element 类的一部分。如果要用一句话来表示这段的意思,从左至右可以这样理解(重要的词语粗体显示):

“找出每个 DishName 元素,如果它的文本是 ‘Pig Organ Soup’ 或 ‘Fish Head Curry’,则把 parent 元素的 rating 属性的值指定为 6”。

接下来我们将介绍使用 Hpricot!





使用 Hpricot 进行解析

从技术角度来说,Hpricot 是一种 HTML 解析器,而不是 XML 解析器,不过由于 HTML 和 XML 使用的语法基本上没什么区别,因此 Hpricot 可以很好地解析发送给它的任何 XML 文档。许多 Ruby 使用者都说 Hpricot 的运行速度(它的扫描器是用 C 语言编写的)比 REXML 快,而且语法也更好,因此他们都选择使用 Hpricot。

使用 Hpricot 解析 XML 时有一点需要注意,如果处理的数据区分大小写,那么有可能产生一些问题。Hpricot 0.5 版(最新发布的)甚至在明确地解析 XML 文档时,也会把所有元素名称都转换成小写。Hpricot Trac (#53) 计划要解决这个问题,不过还没有实现。亲自使用 Hpricot 后您就会对此有所体验了。解析文档和指定新等级的完整代码段非常类似于 REXML(如清单 33 所示)。


清单 33. 使用 Hpricot 解析、导航和操作 XML 文档
def parse_with_hpricot( xml_data ) doc = Hpricot.XML( xml_data ) (doc/:dishname).each{ |dish_name_element| if dish_name_element.inner_html == "Fish Head Curry" or dish_name_element.inner_html == "Pig Organ Soup" parent = dish_name_element.parent parent.attributes["rating"] = "6" end } end


(不要忘了在 main_controller.rb 文件的顶部声明包含 rubygems 和 hpricot!)

清单 33 中的重点是一个调用,如清单 34 所示。


清单 34. Hpricot 的除号方法
(doc/:dishname)


在 Ruby 开发中,一切事物都是对象。Ruby 中没有原语,这也是为什么可以使用除号(/)作为方法名的原因之一吧。Hpricot 中的除号只是 search 方法的别名,因此 清单 34 中的代码等价于清单 35 所显示的代码。


清单 35. Hpricot 的 search 方法(别名为 /)
doc.search(:dishname)


在 Ruby 中,方法调用所使用是圆括号是可有可无的,最好是记住这点。您可以使用也可以不使用,只要您认为好读就行。您会发现除了这一点区别之外,此处操作 XML 文档的代码几乎与前面的是一模一样的。

使用 Hpricot 的 CSS 和 XPath 选择器,可以完成各种印象深刻的任务。如果想要了解更多高级的用法,我建议您阅读 Hpricot Web 站点上的所有例子。





使用 REXML 递归解析 XML

有时我们需要解析某个 XML 文档,但是事先可能并不知道所要找的结构或者内容。在这种情况下,我们不一定非要搜索特定的元素,或者可能只想构建一些存储在内存中的数据结构。使用 REXML 来完成这一功能简单得不可想像(如清单 36 所示)。


清单 36. 递归迭代整个 XML 文档
def parse_recursive( xml_data ) doc = REXML::Document.new( xml_data ) root = doc.root root.each_recursive{ |element| logger.info "Element: #{element}"
}
redirect_to :action => 'index'
end

end



(要查看输出内容,请参阅 log/development.log 文件。结果位于其他一些消息中)

从技术角度来说,真正遍历整个文档并记录每一元素的代码只有一行。但是被分成了三行,因为这样看上去更像那么一回事,可是呢 —— 使用一行代码即可(如清单 37 所示)。


清单 37. 只用一行代码实现递归迭代整个 XML 文档的功能
root.each_recursive{ |element| logger.info "Element: #{element}" }



REXML API 还包括许多其他方便的方法。比方说,如果只想迭代给定元素的直接子元素,而不是遍历整个文档,那么可以使用清单 38 中的代码。


清单 38. 遍历给定 XML 元素的直接子元素
doc = REXML::Document.new( xml_data )root = doc.rootroot.each_element{ |child| logger.info "Child Element: #{child}" }


这些就是本文所介绍的有关 XML、Ruby 和 Rails 的所有内容。

总结

在这篇教程中,我们生成了一个 Rails 应用程序存根,并创建了一个用于处理请求的控制器,使用这些请求可以生成和操作示例 XML 文档。并且介绍了如何使用 REXML 和 Builder 生成 XML 内容,以及如何使用 REXML 和 Hpricot 导航和操作 XML 内容。除此之外,还简要地讨论了如何在 Rails 中处理文件上传,以及如何劫持 Rails 响应对象,以便向客户机提供一些数据(比如说生成的 XML 数据),而不是通常所提供的 Rails 视图。

你可能感兴趣的:(rails,Rails,Ruby,XML,应用服务器,浏览器)