module ROXML
unless const_defined? 'XML_PARSER'
begin
require 'libxml'
XML_PARSER = 'libxml'
rescue LoadError
XML_PARSER = 'rexml'
end
end
require File.join(File.dirname(__FILE__), 'xml', XML_PARSER)
#
# Internal base class that represents an XML - Class binding.
#
class XMLRef # ::nodoc::
attr_reader :accessor, :name, :array, :default, :block, :wrapper
def initialize(accessor, args, &block)
@accessor = accessor
@array = args.array?
@name = args.name
@default = args.default
@block = block
@wrapper = args.wrapper
end
# Reads data from the XML element and populates the instance
# accordingly.
def populate(xml, instance)
data = value(xml)
instance.instance_variable_set("@#{accessor}", data) if data
instance
end
def name?
false
end
private
def xpath
wrapper ? "#{wrapper}#{xpath_separator}#{name}" : name.to_s
end
def wrap(xml)
(wrapper && xml.name != wrapper) ? xml.child_add(XML::Node.new_element(wrapper)) : xml
end
end
# Interal class representing an XML attribute binding
#
# In context:
#
# XMLTextRef
#
class XMLAttributeRef < XMLRef # ::nodoc::
# Updates the attribute in the given XML block to
# the value provided.
def update_xml(xml, value)
xml.attributes[name] = value.to_utf
xml
end
def value(xml)
parent = wrap(xml)
val = xml.attributes[name] || default
block ? block.call(val) : val
end
private
def xpath_separator
'@'
end
end
# Interal class representing XML content text binding
#
# In context:
#
# XMLTextRef
#
class XMLTextRef < XMLRef # ::nodoc::
attr_reader :cdata, :content
def initialize(accessor, args, &block)
super(accessor, args, &block)
@content = args.content?
@cdata = args.cdata?
end
# Updates the text in the given _xml_ block to
# the _value_ provided.
def update_xml(xml, value)
parent = wrap(xml)
if content
add(parent, value)
elsif name?
parent.name = value
elsif array
value.each do |v|
add(parent.child_add(XML::Node.new_element(name)), v)
end
else
add(parent.child_add(XML::Node.new_element(name)), value)
end
xml
end
def value(xml)
val = if content
xml.content.strip
elsif name?
xml.name
elsif array
arr = xml.search(xpath).collect do |e|
e.content.strip.to_latin if e.content
end
arr unless arr.empty?
else
child = xml.search(name).first
child.content if child
end
val = default unless val && !val.blank?
block ? block.call(val) : val
end
def name?
name == '*'
end
private
def xpath_separator
'/'
end
def add(dest, value)
if cdata
dest.child_add(XML::Node.new_cdata(value.to_utf))
else
dest.content = value.to_utf
end
end
end
class XMLHashRef < XMLTextRef # ::nodoc::
attr_reader :hash
def initialize(accessor, args, &block)
super(accessor, args, &block)
@hash = args.hash
if @hash.key.name? || @hash.value.name?
@name = '*'
end
end
# Updates the composed XML object in the given XML block to
# the value provided.
def update_xml(xml, value)
parent = wrap(xml)
value.each_pair do |k, v|
node = add_node(parent)
hash.key.update_xml(node, k)
hash.value.update_xml(node, v)
end
xml
end
def value(xml)
vals = xml.search(xpath).collect do |e|
[@hash.key.value(e), @hash.value.value(e)]
end
if block
vals.collect! do |(key, val)|
block.call(key, val)
end
end
vals.to_h
end
private
def add_node(xml)
xml.child_add(XML::Node.new_element(hash.wrapper))
end
end
class XMLObjectRef < XMLTextRef # ::nodoc::
attr_reader :klass
def initialize(accessor, args, &block)
super(accessor, args, &block)
@klass = args.type
end
# Updates the composed XML object in the given XML block to
# the value provided.
def update_xml(xml, value)
parent = wrap(xml)
unless array
parent.child_add(value.to_xml(name))
else
value.each do |v|
parent.child_add(v.to_xml(name))
end
end
xml
end
def value(xml)
val = unless array
if child = xml.search(xpath).first
instantiate(child)
end
else
arr = xml.search(xpath).collect do |e|
instantiate(e)
end
arr unless arr.empty?
end || default
block ? block.call(val) : val
end
private
def instantiate(elem)
if klass.respond_to? :parse
klass.parse(elem)
else
klass.new(elem)
end
end
end
#
# Returns an XML::Node representing this object.
#
def to_xml(name = nil)
returning XML::Node.new_element(name || tag_name) do |root|
tag_refs.each do |ref|
if v = __send__(ref.accessor)
ref.update_xml(root, v)
end
end
end
end
end