2007-02-09 09:04:31 +01:00
|
|
|
require 'date'
|
|
|
|
require 'xml_simple'
|
2007-10-15 19:16:54 +02:00
|
|
|
require 'cgi'
|
|
|
|
|
|
|
|
# Extensions needed for Hash#to_query
|
|
|
|
class Object
|
|
|
|
def to_param #:nodoc:
|
|
|
|
to_s
|
|
|
|
end
|
|
|
|
|
|
|
|
def to_query(key) #:nodoc:
|
|
|
|
"#{CGI.escape(key.to_s)}=#{CGI.escape(to_param.to_s)}"
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
class Array
|
|
|
|
def to_query(key) #:nodoc:
|
|
|
|
collect { |value| value.to_query("#{key}[]") }.sort * '&'
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
# Locked down XmlSimple#xml_in_string
|
|
|
|
class XmlSimple
|
|
|
|
# Same as xml_in but doesn't try to smartly shoot itself in the foot.
|
|
|
|
def xml_in_string(string, options = nil)
|
|
|
|
handle_options('in', options)
|
|
|
|
|
|
|
|
@doc = parse(string)
|
|
|
|
result = collapse(@doc.root)
|
|
|
|
|
|
|
|
if @options['keeproot']
|
|
|
|
merge({}, @doc.root.name, result)
|
|
|
|
else
|
|
|
|
result
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def self.xml_in_string(string, options = nil)
|
|
|
|
new.xml_in_string(string, options)
|
|
|
|
end
|
|
|
|
end
|
2007-02-09 09:04:31 +01:00
|
|
|
|
2007-01-22 14:43:50 +01:00
|
|
|
module ActiveSupport #:nodoc:
|
|
|
|
module CoreExtensions #:nodoc:
|
|
|
|
module Hash #:nodoc:
|
|
|
|
module Conversions
|
|
|
|
XML_TYPE_NAMES = {
|
|
|
|
"Fixnum" => "integer",
|
2007-02-09 09:04:31 +01:00
|
|
|
"Bignum" => "integer",
|
|
|
|
"BigDecimal" => "numeric",
|
|
|
|
"Float" => "float",
|
2007-01-22 14:43:50 +01:00
|
|
|
"Date" => "date",
|
2007-02-09 09:04:31 +01:00
|
|
|
"DateTime" => "datetime",
|
2007-01-22 14:43:50 +01:00
|
|
|
"Time" => "datetime",
|
|
|
|
"TrueClass" => "boolean",
|
|
|
|
"FalseClass" => "boolean"
|
2007-02-09 09:04:31 +01:00
|
|
|
} unless defined? XML_TYPE_NAMES
|
|
|
|
|
2007-01-22 14:43:50 +01:00
|
|
|
XML_FORMATTING = {
|
|
|
|
"date" => Proc.new { |date| date.to_s(:db) },
|
2007-02-09 09:04:31 +01:00
|
|
|
"datetime" => Proc.new { |time| time.xmlschema },
|
|
|
|
"binary" => Proc.new { |binary| Base64.encode64(binary) }
|
|
|
|
} unless defined? XML_FORMATTING
|
|
|
|
|
|
|
|
def self.included(klass)
|
|
|
|
klass.extend(ClassMethods)
|
|
|
|
end
|
|
|
|
|
2007-10-15 19:16:54 +02:00
|
|
|
def to_query(namespace = nil)
|
|
|
|
collect do |key, value|
|
|
|
|
value.to_query(namespace ? "#{namespace}[#{key}]" : key)
|
|
|
|
end.sort * '&'
|
|
|
|
end
|
|
|
|
|
2007-01-22 14:43:50 +01:00
|
|
|
def to_xml(options = {})
|
|
|
|
options[:indent] ||= 2
|
2007-02-09 09:04:31 +01:00
|
|
|
options.reverse_merge!({ :builder => Builder::XmlMarkup.new(:indent => options[:indent]),
|
|
|
|
:root => "hash" })
|
2007-01-22 14:43:50 +01:00
|
|
|
options[:builder].instruct! unless options.delete(:skip_instruct)
|
2007-02-09 09:04:31 +01:00
|
|
|
dasherize = !options.has_key?(:dasherize) || options[:dasherize]
|
|
|
|
root = dasherize ? options[:root].to_s.dasherize : options[:root].to_s
|
2007-01-22 14:43:50 +01:00
|
|
|
|
2007-02-09 09:04:31 +01:00
|
|
|
options[:builder].__send__(:method_missing, root) do
|
2007-01-22 14:43:50 +01:00
|
|
|
each do |key, value|
|
|
|
|
case value
|
|
|
|
when ::Hash
|
|
|
|
value.to_xml(options.merge({ :root => key, :skip_instruct => true }))
|
|
|
|
when ::Array
|
|
|
|
value.to_xml(options.merge({ :root => key, :children => key.to_s.singularize, :skip_instruct => true}))
|
2007-02-09 09:04:31 +01:00
|
|
|
when ::Method, ::Proc
|
|
|
|
# If the Method or Proc takes two arguments, then
|
|
|
|
# pass the suggested child element name. This is
|
|
|
|
# used if the Method or Proc will be operating over
|
|
|
|
# multiple records and needs to create an containing
|
|
|
|
# element that will contain the objects being
|
|
|
|
# serialized.
|
|
|
|
if 1 == value.arity
|
|
|
|
value.call(options.merge({ :root => key, :skip_instruct => true }))
|
|
|
|
else
|
|
|
|
value.call(options.merge({ :root => key, :skip_instruct => true }), key.to_s.singularize)
|
|
|
|
end
|
2007-01-22 14:43:50 +01:00
|
|
|
else
|
2007-02-09 09:04:31 +01:00
|
|
|
if value.respond_to?(:to_xml)
|
|
|
|
value.to_xml(options.merge({ :root => key, :skip_instruct => true }))
|
|
|
|
else
|
|
|
|
type_name = XML_TYPE_NAMES[value.class.name]
|
|
|
|
|
|
|
|
key = dasherize ? key.to_s.dasherize : key.to_s
|
2007-01-22 14:43:50 +01:00
|
|
|
|
2007-02-09 09:04:31 +01:00
|
|
|
attributes = options[:skip_types] || value.nil? || type_name.nil? ? { } : { :type => type_name }
|
|
|
|
if value.nil?
|
|
|
|
attributes[:nil] = true
|
|
|
|
end
|
|
|
|
|
|
|
|
options[:builder].tag!(key,
|
|
|
|
XML_FORMATTING[type_name] ? XML_FORMATTING[type_name].call(value) : value,
|
|
|
|
attributes
|
|
|
|
)
|
|
|
|
end
|
2007-01-22 14:43:50 +01:00
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
2007-02-09 09:04:31 +01:00
|
|
|
|
|
|
|
end
|
|
|
|
|
|
|
|
module ClassMethods
|
|
|
|
def from_xml(xml)
|
|
|
|
# TODO: Refactor this into something much cleaner that doesn't rely on XmlSimple
|
2007-10-15 19:16:54 +02:00
|
|
|
undasherize_keys(typecast_xml_value(XmlSimple.xml_in_string(xml,
|
2007-02-09 09:04:31 +01:00
|
|
|
'forcearray' => false,
|
|
|
|
'forcecontent' => true,
|
|
|
|
'keeproot' => true,
|
|
|
|
'contentkey' => '__content__')
|
2007-10-15 19:16:54 +02:00
|
|
|
))
|
2007-02-09 09:04:31 +01:00
|
|
|
end
|
2007-10-15 19:16:54 +02:00
|
|
|
|
2007-02-09 09:04:31 +01:00
|
|
|
def create_from_xml(xml)
|
|
|
|
ActiveSupport::Deprecation.warn("Hash.create_from_xml has been renamed to Hash.from_xml", caller)
|
|
|
|
from_xml(xml)
|
|
|
|
end
|
|
|
|
|
|
|
|
private
|
|
|
|
def typecast_xml_value(value)
|
|
|
|
case value.class.to_s
|
|
|
|
when "Hash"
|
|
|
|
if value.has_key?("__content__")
|
|
|
|
content = translate_xml_entities(value["__content__"])
|
|
|
|
case value["type"]
|
|
|
|
when "integer" then content.to_i
|
|
|
|
when "boolean" then content.strip == "true"
|
|
|
|
when "datetime" then ::Time.parse(content).utc
|
|
|
|
when "date" then ::Date.parse(content)
|
|
|
|
else content
|
|
|
|
end
|
|
|
|
else
|
|
|
|
(value.blank? || value['type'] || value['nil'] == 'true') ? nil : value.inject({}) do |h,(k,v)|
|
|
|
|
h[k] = typecast_xml_value(v)
|
|
|
|
h
|
|
|
|
end
|
|
|
|
end
|
|
|
|
when "Array"
|
|
|
|
value.map! { |i| typecast_xml_value(i) }
|
|
|
|
case value.length
|
|
|
|
when 0 then nil
|
|
|
|
when 1 then value.first
|
|
|
|
else value
|
|
|
|
end
|
|
|
|
when "String"
|
|
|
|
value
|
|
|
|
else
|
|
|
|
raise "can't typecast #{value.inspect}"
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def translate_xml_entities(value)
|
|
|
|
value.gsub(/</, "<").
|
|
|
|
gsub(/>/, ">").
|
|
|
|
gsub(/"/, '"').
|
|
|
|
gsub(/'/, "'").
|
|
|
|
gsub(/&/, "&")
|
|
|
|
end
|
|
|
|
|
|
|
|
def undasherize_keys(params)
|
|
|
|
case params.class.to_s
|
|
|
|
when "Hash"
|
|
|
|
params.inject({}) do |h,(k,v)|
|
|
|
|
h[k.to_s.tr("-", "_")] = undasherize_keys(v)
|
|
|
|
h
|
|
|
|
end
|
|
|
|
when "Array"
|
|
|
|
params.map { |v| undasherize_keys(v) }
|
|
|
|
else
|
|
|
|
params
|
|
|
|
end
|
|
|
|
end
|
2007-01-22 14:43:50 +01:00
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|