Ruby中XML格式数据处理库REXML的使用方法指南
以树方式使用REXML
REXML的目的是正好够用。在最大程度上,它能很好地完成任务。实际上,REXML支持两种不同样式的XML处理―“树”和“流”。第一种样式是DOM所尝试要做的更简单的版本;第二种样式是SAX所尝试要做的更简单的版本。让我们先研究树样式。假设我们要提取上一个示例中的同一个地址簿文档。下面的示例来自我所创建的经修改的eval.rb;标准eval.rb(链接到Ruby教程)可以根据对复杂对象的表达式求值显示非常长的计算结果―我的eval.rb在没有错误发生的情况下不作出反应:
如何使用REXML来引用嵌套数据
ruby>require"rexml/document" ruby>includeREXML ruby>addrbook=(Document.newFile.new"address.xml").root ruby>persons=addrbook.elements.to_a("//person") ruby>putspersons[1].elements["address"].attributes["city"] NewYork
这个表达式很普通。.to_a()方法创建文档中所有<person>元素的数组,在其它命名中它可能是有用的。元素有点象DOM节点,但它其实更接近于XML本身(而且使用起来也更简单)。.to_a()的参数是XPath,在这种情况下,可以标识文档中任何地方的所有<person>元素。如果我们只需要第一层上的元素,可以使用:
创建匹配元素的数组
ruby>persons=addrbook.elements.to_a("/addressbook/person")
我们甚至可以更直接地将XPath用作.elements属性的重载索引。例如:
使用REXML来引用嵌套数据的另一种方法
ruby>putsaddrbook.elements["//person[2]/address"].attributes["city"] NewYork
请注意,XPath使用基于1的索引,不象Ruby和Python数组使用基于0的索引。换句话说,它仍是我们正在检查其所在城市的同一个人。通过查看REXML请注意,XPath使用基于1的索引,不象Ruby和Python数组使用基于0的索引。换句话说,它仍是我们正在检查其所在城市的同一个人。通过查看
用REXML显示元素的XML源代码
ruby>putsaddrbook.elements["//person[2]/address"] <addresscity='NewYork'street='118St.'number='344'state='NY'/> ruby>putsaddrbook.elements["//person[2]/contact-info"] <contact-info> <emailaddress='robb@iro.ibm.com'/> <home-phonenumber='03-3987873'/> </contact-info>
此外,XPath不必只与一个元素匹配。我们已在定义persons数组时看见过,但另一个示例强调了这一点:
将多个元素与XPath匹配
ruby>putsaddrbook.elements.to_a("//person/address[@state='CA']") <addresscity='Sacramento'street='SpruceRd.'number='99'state='CA'/> <addresscity='LosAngeles'street='PineRd.'number='1234'state='CA'/>
与此相反,.elements属性的索引只产生第一个匹配的元素:
当XPath只匹配第一次出现时
ruby>putsaddrbook.elements.to_a("//person/address[@state='CA']") <addresscity='Sacramento'street='SpruceRd.'number='99'state='CA'/> <addresscity='LosAngeles'street='PineRd.'number='1234'state='CA'/>
也可以通过REXML中的XPath类使用XPath地址,它具有诸如.first()、.each()和.match()这样的方法。
REXML元素的一个独特的惯用方法是.each迭代器。虽然Ruby有一个可对集合进行操作的循环结构for,但Ruby程序员通常更喜欢使用迭代器方法来将控制传递给代码块。下面的两种结构是等价的,但第二种结构有更为自然的Ruby感觉:
通过在REXML中匹配XPath进行迭代
ruby>foraddrinaddrbook.elements.to_a("//address[@state='CA']") |putsaddr.attributes["city"] |end Sacramento LosAngeles ruby>addrbook.elements.each("//address[@state='CA']"){ ||addr|putsaddr.attributes["city"] |} Sacramento LosAngeles
以流方式使用REXML
出于“正好够用”的目的,REXML的树方式可能是Ruby语言最简单的方法。但REXML还提供了一种流方式,它象是SAX的更轻量级的变体。正如使用SAX一样,REXML没有向应用程序程序员提供来自XML文档的缺省数据结构。相反,“listener”或“handler”类负责提供响应文档流中各种事件的一组方法。以下是常用集合:开始标记、结束标记、遇到的元素文本等等。
虽然流方式远远没有象以树方式工作那样容易,但通常它的速度要快很多。REXML教程声称流方式的速度要快1500倍。虽然我没有尝试过对它进行基准测试,但我猜想这是一种有限的情况(我的小示例在树方式中也是瞬间完成的)。总之,如果速度要紧,那么速度上的差异很可能是显著的。
让我们研究一个非常简单的示例,它所做的事情与上面的“列出加州城市”示例相同。对它进行扩展以用于复杂的文档处理相对比较简单:
REXML中XML文档的流处理
ruby>require"rexml/document" ruby>require"rexml/streamlistener" ruby>includeREXML ruby>classHandler |includeStreamListener |deftag_startname,attrs |ifname=="address"andattrs.assoc("state")[1]=="CA" |putsattrs.assoc("city")[1] |end |end |end ruby>Document.parse_stream((File.new"address.xml"),Handler.new) Sacramento LosAngeles
流处理示例中要注意的一件事情是,标记属性被作为一组数组传递,它要处理的工作比起散列要稍微多一点(但可能在库中创建会更快)。
编码问题
REXML所有文本节点中都是以UTF-8编码的,所有调用的代码都要注意这一点,在程序中,传递给REXML的字符串必须是经过UTF-8编码的。
REXML不可能总是正确猜测出你的文本的编码方式,所以它总是假定为UTF-8编码。同时,如果你试图添加其他编码方式的文本,REXML不会发出警告。添加者必须保证自己添加的是UTF-8的文本。如果添加标准的ASCII7位编码,是没有关系的。如果使用ISO8859-1文本,必须在添加之前转换为UTF-8编码。可以使用text.unpack("C").pack("U")。变更编码进行输出,只有Document.write()和Document.to_s()支持。如果需要输出特定编码的节点,必须用Output把输出对象包装起来。
e=Element.new"<a/>" e.text="f\xfcr"#ISO-8859-1'??' o='' e.write(Output.new(o,"ISO-8859-1"))
可以向Output传递任何支持的编码。