From: "Jesús Gabriel y Galán" Date: 2008-12-02T03:02:20+09:00 Subject: Re: Design suggestion for translations/mappings from xml On Thu, Nov 27, 2008 at 6:33 PM, Brian Lonsdorf wrote: > Thanks so much for your response! Glad it helped. > One enhancement that made me smile was a call to attr_accessor in the > map method :) Now *you* got me thinking a little step further. Finally I've had some spare time, and came up with this couple enhancements (plus implementing the XML stuff: parsing using nokogiri, generated the XML by hand): require 'nokogiri' module XMLMap module ClassMethods def set_xml_data property, xpath re = %r{\A(/(\w+))+(/@(\w+))?\Z} raise "Invalid xpath: #{xpath} for attribute #{property}. Only simple tags and attrs supported (#{re})" unless xpath =~ re (@mappings ||= {})[property] = xpath end def mapped_reader property, xpath set_xml_data property, xpath self.class_eval {attr_reader property.to_sym} end def mapped_writer property, xpath set_xml_data property, xpath self.class_eval {attr_writer property.to_sym} end def mapped_accessor property, xpath set_xml_data property, xpath self.class_eval {attr_accessor property.to_sym} end def from_xml xml o = self.new doc = Nokogiri.XML(xml) @mappings.each do |attr, xpath| item = doc.xpath xpath unless item.empty? o.instance_variable_set "@#{attr}".to_sym, item.inner_text end end o end def mappings @mappings end end def to_xml xml = Hash.new {|h,k| h[k] = Hash.new(&h.default_proc)} self.class.mappings.each do |attr, xpath| value = instance_variable_get "@#{attr}" continue unless value tag, attr = xpath.split("/@") tags = tag.split("/") h = tags[1..-2].inject(xml) {|h, tag| h[tag]} if attr h[tags[-1]][:attributes][attr] = value else h[tags[-1]][:value] = value end end output = "" xml.each do |node, data| generate_node node, data, output end output end def generate_node node,data,output value = data.delete(:value) attrs = data.delete(:attributes) output << "<#{node}" if attrs attrs.each do |attr, value| output << " #{attr}=\"#{value}\"" end end if value output << ">#{value}" elsif !data.empty? output << ">" data.each do |child_tag, child_data| generate_node child_tag, child_data, output end output << "" else output << "/>" end end def self.included child child.extend ClassMethods end end The XML stuff ended up a little messy, I'd appreciate any help or comment there. Usage: class A include XMLMap mapped_reader :first, "/root/first" mapped_reader :second, "/root/second" mapped_accessor :attr, "/root/first/@attr" end a = A.from_xml %q{the_first_valuethe_second_value} p a a.attr = "changed value" puts a.to_xml I haven't tested very thoroughly, so there might be a bug or two in there. Probably you will want to reimplement the to_xml method to use a proper XML generator. Any comment about the code or approach appreciated, for sure there's room for improvement :-) Regards, Jesus.