用于快速和脏的XML序列化的Ruby代码?

时间:2008-09-17 20:01:54

标签: xml ruby serialization xml-serialization metaprogramming

鉴于一个中等复杂的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。“


我同意建议“构建器”类加上XmlSimple似乎是正确的路径。 XML - &gt;哈希 - &gt; ? - &GT;对象模型(和利润!)


更新2008-09-18 AM:来自@ Roman,@ fatgeekuk和@ScottKoon的优秀建议似乎打破了问题。我下载了HPricot源码,看看它是如何解决问题的;关键方法显然是instance_variable_set和class_eval。 irb的工作非常令人鼓舞,我现在正朝着实施方向迈进......非常兴奋

5 个答案:

答案 0 :(得分:1)

您可以使用Builder而不是创建to_xml方法,并且可以使用XMLSimple将xml文件拉入Hash而不是使用from _xml方法。不幸的是,我不确定你使用这些技术会获得多少收获。

答案 1 :(得分:1)

我建议使用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(/^@(.)/) { $1.upcase })

也许可以找到一种更清洁的方法来做到这一点。 之后,如果你想从这个新对象创建一个xml,你可能需要更改XmlSimple#xml_out以接受另一个选项,它将你的对象与它用来作为参数接收的通常哈希区分开,然后你'我必须编写你的XmlSimple#value_to_xml方法的版本,所以它将调用访问器方法而不是尝试访问哈希结构。另一个选择是让所有类通过返回所需的实例变量来支持[]运算符。

答案 2 :(得分:0)

您是否可以定义一个允许您执行的方法:

@bar = el.bar?这将摆脱一些样板。如果总是以这种方式定义Bat,则可以将XPath推送到初始化方法

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

Hpricot或REXML也可能有所帮助。

答案 3 :(得分:0)

你能尝试parsing the XML with hpricot并使用输出构建一个普通的旧Ruby对象吗? [免责声明]我没试过这个。

答案 4 :(得分:0)

我会将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

希望这能给出一个大致的想法。