module ROXML
unless const_defined? 'XML_PARSER'
begin
require 'libxml'
XML_PARSER = 'libxml' # :nodoc:
rescue LoadError
XML_PARSER = 'rexml' # :nodoc:
end
end
require File.join(File.dirname(__FILE__), 'xml', XML_PARSER)
class RequiredElementMissing < Exception # :nodoc:
end
#
# Internal base class that represents an XML - Class binding.
#
class XMLRef # :nodoc:
delegate :name, :required?, :array?, :wrapper, :blocks, :accessor, :variable_name, :to => :opts
alias_method :xpath_name, :name
def initialize(opts)
@opts = opts
end
# Reads data from the XML element and populates the instance
# accordingly.
def populate(xml, instance)
data = value(xml)
instance.instance_variable_set("@#{variable_name}", data)
instance
end
def name?
false
end
def update_xml(xml, value)
returning wrap(xml) do |xml|
write_xml(xml, value)
end
end
def default
@default ||= @opts.default || (@opts.array? ? Array.new : nil)
@default.duplicable? ? @default.dup : @default
end
def value(xml)
value = fetch_value(xml)
if value.blank?
raise RequiredElementMissing if required?
value = default
end
apply_blocks(value)
end
private
attr_reader :opts
def apply_blocks(val)
blocks.each {|block| val = block[val] }
val
end
def xpath
wrapper ? "#{wrapper}/#{xpath_name}" : xpath_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:
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)
attr = xml.search(xpath).first
attr && attr.value
end
def xpath_name
"@#{name}"
end
end
# Interal class representing XML content text binding
#
# In context:
#
# XMLTextRef
#
class XMLTextRef < XMLRef # :nodoc:
delegate :cdata?, :content?, :to => :opts
def name?
name == '*'
end
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?
xml.content.strip
elsif name?
xml.name
elsif array?
xml.search(xpath).collect do |e|
e.content.strip.to_latin if e.content
end
else
child = xml.search(xpath).first
child.content if child
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 default
result = super
result.nil? ? {} : result
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))
hash.key.update_xml(node, k)
hash.value.update_xml(node, v)
end
end
def fetch_value(xml)
xml.search(xpath).collect do |e|
[hash.key.value(e), hash.value.value(e)]
end
end
def apply_blocks(vals)
unless blocks.empty?
vals.collect! do |kvp|
super(kvp)
end
end
vals.to_hash if vals
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)
unless array?
xml.child_add(value.to_xml(name))
else
value.each do |v|
xml.child_add(v.to_xml(name))
end
end
end
def fetch_value(xml)
unless array?
if child = xml.search(xpath).first
instantiate(child)
end
else
xml.search(xpath).collect do |e|
instantiate(e)
end
end
end
def instantiate(elem)
if type.respond_to? :from_xml
type.from_xml(elem)
else
type.new(elem)
end
end
end
end