Ruby代码可用于快速而肮脏的XML序列化吗?

时间:2020-03-05 18:59:51  来源:igfitidea点击:

给定一个中等复杂的XML结构(数十个元素,数百个属性),而没有XSD,并且希望创建一个对象模型,那么一种避免编写样板from_xml()和to_xml()方法的优雅方法是什么?

例如,给定:

<Foo bar="1"><Bat baz="blah"/></Foo>

我如何避免编写无穷无尽的序列:

class Foo
  attr_reader :bar, :bat

  def from_xml(el)
     @bar = el.attributes['bar']
     @bat = Bat.new()
     @bat.from_xml(XPath.first(el, "./bat")
  end
 etc...

我不介意显式地创建对象结构。我确定可以通过一些高级编程来解决序列化问题。

我不是在尝试为每个类保存一两行(通过将from_xml行为移至初始化程序或者类方法等)。我正在寻找可以重复我的思维过程的"元"解决方案:

"我知道每个元素都将成为一个类名。我知道每个XML属性都将成为一个字段名。我知道要分配的代码只是@#{attribute_name} = el。[#{attribute_name} ],然后递归到子元素。然后在to_xml上进行反向操作。"

我同意" builder"类加XmlSimple似乎是正确路径的建议。 XML->哈希->? ->对象模型(和利润!)

更新2008-09-18 AM:@ Roman,@ fatgeekuk和@ScottKoon的出色建议似乎已经使问题解决了。我下载了HPricot源,以查看它如何解决该问题。关键方法显然是instance_variable_set和class_eval。 irb的工作非常令人鼓舞,现在正在朝着实施方向发展。

解决方案

回答

我们能否定义一个缺少的方法,让我们可以执行以下操作:

@bar = el.bar?那将摆脱一些样板。如果始终要以这种方式定义Bat,则可以将XPath推送到initialize方法中,

class Bar
  def initialize(el)
    self.from_xml(XPath.first(el, "./bat"))
  end
end

Hpricot或者REXML可能也有帮助。

回答

我们可以使用Builder而不是创建to_xml方法,并且可以使用XMLSimple将xml文件拉入哈希中,而不是使用from _xml方法。不幸的是,我不确定我们会从这些技术中获得真正的收益。

回答

我们可以尝试使用hpricot解析XML并使用输出来构建普通的旧Ruby对象吗? [DISCLAIMER]我还没有尝试过。

回答

我会继承attr_accessor来为我们构建to_xml和from_xml。

这样的事情(请注意,这不是全部功能,只是轮廓)

class XmlFoo
  def self.attr_accessor attributes = {}
    # need to add code here to maintain a list of the fields for the subclass, to be used in to_xml and from_xml
    attributes.each do |name, value|
      super name
    end
  end

  def to_xml options={}
    # need to use the hash of elements, and determine how to handle them by whether they are .kind_of?(XmlFoo)
  end

  def from_xml el
  end
end

然后,我们可以像...一样使用它。

class Second < XmlFoo
  attr_accessor :first_attr => String, :second_attr => Float
end

class First < XmlFoo
  attr_accessor :normal_attribute => String, :sub_element => Second
end

希望这能给出一个总体思路。

回答

我建议开始使用XmlSimple。在输入文件上运行XmlSimple#xml_in之后,我们将获得一个哈希。然后,我们可以递归到其中(obj.instance_variables)并将所有内部哈希(element.is_a?(Hash))变成同名的对象,例如:

obj.instance_variables.find {|v| obj.send(v.gsub(/^@/,'').to_sym).is_a?(Hash)}.each do |h|
  klass= eval(h.sub(/^@(.)/) { .upcase })

也许可以找到一种更清洁的方法来执行此操作。
然后,如果我们想从这个新对象中生成xml,则可能需要更改XmlSimple#xml_out以接受另一个选项,该选项将对象与通常用作参数接收的哈希值区分开来,然后,我们将必须编写XmlSimple#value_to_xml方法的版本,因此它将调用accessor方法,而不是尝试访问哈希结构。另一种选择是,通过返回所需的实例变量,使所有类都支持[]运算符。