鉴于一个中等复杂的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的工作非常令人鼓舞,我现在正朝着实施方向迈进......非常兴奋
答案 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
希望这能给出一个大致的想法。