module ROXML class RequiredElementMissing < Exception # :nodoc: end # # Internal base class that represents an XML - Class binding. # class XMLRef # :nodoc: delegate :required?, :array?, :blocks, :accessor, :variable_name, :default, :to => :opts def initialize(opts, instance) @opts = opts @instance = instance end def to_xml val = @instance.__send__(accessor) opts.to_xml.respond_to?(:call) ? opts.to_xml.call(val) : val end def update_xml(xml, value) returning wrap(xml) do |xml| write_xml(xml, value) end end def name opts.name_explicit? ? opts.name : conventionize(opts.name) end def xpath_name if !opts.name_explicit? && namespace = @instance.class.roxml_namespace "#{namespace}:#{name}" else name end end def default @default ||= @opts.default || (@opts.array? ? Array.new : nil) @default.duplicable? ? @default.dup : @default end def value_in(xml) value = fetch_value(xml) freeze(apply_blocks(value)) end private attr_reader :opts def conventionize(what) if !what.blank? && @instance.try(:class).try(:roxml_naming_convention).respond_to?(:call) require 'uri' URI.unescape(@instance.class.roxml_naming_convention.call(URI.escape(what, /\/|::/))) else what end end def wrapper conventionize(opts.wrapper) end def apply_blocks(val) begin blocks.apply_to(val) rescue Exception => ex raise ex, "#{accessor}: #{ex.message}" end end def freeze(val) if opts.freeze? val.each(&:freeze) if val.is_a?(Enumerable) val.freeze else val end end def xpath wrapper ? "#{wrapper}/#{xpath_name}" : xpath_name.to_s end def auto_xpath "#{conventionize(opts.name.pluralize)}/#{xpath_name}" if array? end def wrap(xml) return xml if !wrapper || xml.name == wrapper if child = xml.children.find {|c| c.name == wrapper } return child end xml.child_add(XML::Node.new_element(wrapper)) end def nodes_in(xml) vals = xml.search(xpath) if (opts.hash? || opts.array?) && vals.empty? && !wrapper && auto_xpath vals = xml.search(auto_xpath) @auto_vals = !vals.empty? end if vals.empty? raise RequiredElementMissing, "#{name} from #{xml} for #{accessor}" if required? default else yield(vals) end end end # Interal class representing an XML attribute binding # # In context: # # XMLTextRef # class XMLAttributeRef < XMLRef # :nodoc: private # Updates the attribute in the given XML block to # the value provided. def write_xml(xml, value) xml.attributes[name] = value.to_s.to_utf end def fetch_value(xml) nodes_in(xml) do |nodes| nodes.first.value end end def xpath_name "@#{name}" end end # Interal class representing XML content text binding # # In context: # # XMLTextRef # class XMLTextRef < XMLRef # :nodoc: delegate :cdata?, :content?, :name?, :to => :opts private # Updates the text in the given _xml_ block to # the _value_ provided. def write_xml(xml, value) if content? add(xml, value) elsif name? xml.name = value elsif array? value.each do |v| add(xml.child_add(XML::Node.new_element(name)), v) end else add(xml.child_add(XML::Node.new_element(name)), value) end end def fetch_value(xml) if content? || name? value = if content? xml.content.to_s.strip elsif name? xml.name end if value.empty? raise RequiredElementMissing, "#{name} from #{xml} for #{accessor}" if required? default else value end else nodes_in(xml) do |nodes| if array? nodes.collect do |e| e.content.strip.to_latin end else nodes.first.content end end end end def add(dest, value) if cdata? dest.child_add(XML::Node.new_cdata(value.to_s.to_utf)) else dest.content = value.to_s.to_utf end end end class XMLHashRef < XMLTextRef # :nodoc: delegate :hash, :to => :opts def initialize(opts, inst) super(opts, inst) @key = opts.hash.key.to_ref(inst) @value = opts.hash.value.to_ref(inst) end private # Updates the composed XML object in the given XML block to # the value provided. def write_xml(xml, value) value.each_pair do |k, v| node = xml.child_add(XML::Node.new_element(hash.wrapper)) @key.update_xml(node, k) @value.update_xml(node, v) end end def fetch_value(xml) nodes_in(xml) do |nodes| nodes.collect do |e| [@key.value_in(e), @value.value_in(e)] end end end def apply_blocks(vals) unless blocks.empty? vals.collect! do |kvp| super(kvp) end end vals.to_hash if vals end def freeze(vals) if opts.freeze? vals.each_pair{|k, v| k.freeze; v.freeze } vals.freeze else vals end end end class XMLObjectRef < XMLTextRef # :nodoc: delegate :type, :to => :opts private # Updates the composed XML object in the given XML block to # the value provided. def write_xml(xml, value) if array? value.each do |v| xml.child_add(v.to_xml(name)) end elsif value.is_a?(ROXML) xml.child_add(value.to_xml(name)) else node = XML::Node.new_element(name) node.content = value.to_xml xml.child_add(node) end end def fetch_value(xml) nodes_in(xml) do |nodes| unless array? instantiate(nodes.first) else nodes.collect do |e| instantiate(e) end end end end def instantiate(elem) if type.respond_to? :from_xml type.from_xml(elem) else type.new(elem) end end end end