On Thu, Nov 27, 2008 at 6:33 PM, Brian L. [email protected]
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}</#{node}>”
elsif !data.empty?
output << “>”
data.each do |child_tag, child_data|
generate_node child_tag, child_data, output
end
output << “</#{node}>”
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.