Ruby code for fast and dirty XML serialization?

Given the moderately complex XML structure (dozens of elements, hundreds of attributes) without an XSD and the desire to create an object model, what an elegant way to avoid writing template methods from_xml () and to_xml ()?

For example, given:

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

How to avoid recording infinite sequences:

 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... 

I am not against creating an object structure explicitly; this is serialization, which I'm just sure you can take care of some higher-level programs ...


I am not trying to save a line or two for each class (moving from_xml behavior to an initialization method or class, etc.). I am looking for a "meta" solution that duplicates my mental process:

"I know that each element will become the name of the class. I know that every XML attribute will be the name of the field. I know that the code for the assignment is just @ # {attribute_name} = el. [# {Attribute_name}] and then recursion into subelements. And the opposite is to to_xml. "


I agree with the assumption that the builder plus XmlSimple class seems correct. XML → Hash →? → Object model (and profit!)


Update 2008-09-18: Great suggestions from @Roman, @fatgeekuk and @ScottKoon seem to have broken the problem. I downloaded the HPricot source to find out how it solved the problem; the key methods are explicitly instance_variable_set and class_eval. irb works very encouragingly, now I'm moving on to implementation .... Very excited

+7
ruby xml serialization metaprogramming xml-serialization
source share
5 answers

You can use Builder instead of creating your to_xml method, and you can use XMLSimple to pull your xml file into Hash instead of using the from_xml method. Unfortunately, I'm not sure that you really get all this from using these methods.

+1
source share

I suggest using XmlSimple for starters. After running XmlSimple # xml_in on the input file, you will have a hash. Then you can rewrite (obj.instance_variables) into it and flip all internal hashes (element.is_a? (Hash)) to objects with the same name, for example:

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

Perhaps a cleaner way can be found for this. Subsequently, if you want to create xml from this new object, you probably need to modify XmlSimple # xml_out to accept another parameter that distinguishes your object from the regular hash that it used to receive as an argument, you will have to write your version of the method XmlSimple # value_to_xml, so it will call the accessor method instead of trying to access the hash structure. Another option is that all your classes support the [] operator, returning the required instance variable.

+1
source share

Failed to determine the missing method, which allows:

@bar = el.bar? This will save some patterns. If Bat will always be determined this way, you can click XPath on the initialize method,

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

Hpricot or REXML can help too.

0
source share

Could you try XML parsing with hpricot and using the output to create a simple old Ruby object? [DISCLAIMER] I have not tried this.

0
source share

I would subclass attr_accessor to create your to_xml and from_xml for you.

Something like this (note, this is not fully functional, only the outline)

 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 

you could use it like ....

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

Hope this gives a general idea.

0
source share

All Articles