2017-07-09 08:06:36 -04:00
|
|
|
# frozen_string_literal: true
|
2017-07-10 09:39:13 -04:00
|
|
|
|
2016-08-06 11:58:50 -04:00
|
|
|
require "time"
|
|
|
|
require "base64"
|
|
|
|
require "bigdecimal"
|
2018-12-21 04:12:10 -05:00
|
|
|
require "bigdecimal/util"
|
2017-10-21 09:11:29 -04:00
|
|
|
require "active_support/core_ext/module/delegation"
|
|
|
|
require "active_support/core_ext/string/inflections"
|
|
|
|
require "active_support/core_ext/date_time/calculations"
|
2009-04-22 20:41:28 -04:00
|
|
|
|
2009-01-29 11:24:16 -05:00
|
|
|
module ActiveSupport
|
2009-03-09 15:46:06 -04:00
|
|
|
# = XmlMini
|
2009-03-09 16:42:42 -04:00
|
|
|
#
|
|
|
|
# To use the much faster libxml parser:
|
|
|
|
# gem 'libxml-ruby', '=0.9.7'
|
|
|
|
# XmlMini.backend = 'LibXML'
|
2009-01-29 11:24:16 -05:00
|
|
|
module XmlMini
|
|
|
|
extend self
|
2008-11-26 02:37:10 -05:00
|
|
|
|
2010-07-23 14:11:14 -04:00
|
|
|
# This module decorates files deserialized using Hash.from_xml with
|
2010-04-29 06:27:25 -04:00
|
|
|
# the <tt>original_filename</tt> and <tt>content_type</tt> methods.
|
|
|
|
module FileLike #:nodoc:
|
|
|
|
attr_writer :original_filename, :content_type
|
|
|
|
|
|
|
|
def original_filename
|
2016-08-06 11:58:50 -04:00
|
|
|
@original_filename || "untitled"
|
2010-04-29 06:27:25 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
def content_type
|
2016-08-06 11:58:50 -04:00
|
|
|
@content_type || "application/octet-stream"
|
2010-04-29 06:27:25 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
DEFAULT_ENCODINGS = {
|
|
|
|
"binary" => "base64"
|
2010-10-05 22:21:07 -04:00
|
|
|
} unless defined?(DEFAULT_ENCODINGS)
|
2010-04-29 06:27:25 -04:00
|
|
|
|
2016-05-17 10:56:08 -04:00
|
|
|
unless defined?(TYPE_NAMES)
|
|
|
|
TYPE_NAMES = {
|
|
|
|
"Symbol" => "symbol",
|
|
|
|
"Integer" => "integer",
|
|
|
|
"BigDecimal" => "decimal",
|
|
|
|
"Float" => "float",
|
|
|
|
"TrueClass" => "boolean",
|
|
|
|
"FalseClass" => "boolean",
|
|
|
|
"Date" => "date",
|
|
|
|
"DateTime" => "dateTime",
|
|
|
|
"Time" => "dateTime",
|
|
|
|
"Array" => "array",
|
|
|
|
"Hash" => "hash"
|
|
|
|
}
|
|
|
|
end
|
2010-04-29 06:27:25 -04:00
|
|
|
|
|
|
|
FORMATTING = {
|
|
|
|
"symbol" => Proc.new { |symbol| symbol.to_s },
|
|
|
|
"date" => Proc.new { |date| date.to_s(:db) },
|
2012-05-23 03:59:13 -04:00
|
|
|
"dateTime" => Proc.new { |time| time.xmlschema },
|
2012-01-01 12:57:55 -05:00
|
|
|
"binary" => Proc.new { |binary| ::Base64.encode64(binary) },
|
2010-04-29 06:27:25 -04:00
|
|
|
"yaml" => Proc.new { |yaml| yaml.to_yaml }
|
|
|
|
} unless defined?(FORMATTING)
|
|
|
|
|
2011-05-22 01:47:32 -04:00
|
|
|
# TODO use regexp instead of Date.parse
|
2010-04-29 06:27:25 -04:00
|
|
|
unless defined?(PARSING)
|
|
|
|
PARSING = {
|
2013-11-12 22:31:02 -05:00
|
|
|
"symbol" => Proc.new { |symbol| symbol.to_s.to_sym },
|
2010-04-29 06:27:25 -04:00
|
|
|
"date" => Proc.new { |date| ::Date.parse(date) },
|
2011-05-22 07:46:23 -04:00
|
|
|
"datetime" => Proc.new { |time| Time.xmlschema(time).utc rescue ::DateTime.parse(time).utc },
|
2010-04-29 06:27:25 -04:00
|
|
|
"integer" => Proc.new { |integer| integer.to_i },
|
|
|
|
"float" => Proc.new { |float| float.to_f },
|
2016-12-12 09:41:59 -05:00
|
|
|
"decimal" => Proc.new do |number|
|
|
|
|
if String === number
|
2018-12-20 20:28:06 -05:00
|
|
|
number.to_d
|
2016-12-12 09:41:59 -05:00
|
|
|
else
|
|
|
|
BigDecimal(number)
|
|
|
|
end
|
|
|
|
end,
|
2013-11-12 22:31:02 -05:00
|
|
|
"boolean" => Proc.new { |boolean| %w(1 true).include?(boolean.to_s.strip) },
|
2010-04-29 06:27:25 -04:00
|
|
|
"string" => Proc.new { |string| string.to_s },
|
2018-02-21 16:34:39 -05:00
|
|
|
"yaml" => Proc.new { |yaml| YAML.load(yaml) rescue yaml },
|
2012-01-01 12:57:55 -05:00
|
|
|
"base64Binary" => Proc.new { |bin| ::Base64.decode64(bin) },
|
2010-04-29 06:27:25 -04:00
|
|
|
"binary" => Proc.new { |bin, entity| _parse_binary(bin, entity) },
|
|
|
|
"file" => Proc.new { |file, entity| _parse_file(file, entity) }
|
|
|
|
}
|
|
|
|
|
|
|
|
PARSING.update(
|
|
|
|
"double" => PARSING["float"],
|
|
|
|
"dateTime" => PARSING["datetime"]
|
|
|
|
)
|
|
|
|
end
|
|
|
|
|
2015-06-09 14:24:25 -04:00
|
|
|
attr_accessor :depth
|
|
|
|
self.depth = 100
|
|
|
|
|
2016-08-06 13:38:33 -04:00
|
|
|
delegate :parse, to: :backend
|
2009-03-09 20:27:39 -04:00
|
|
|
|
2012-11-14 00:58:33 -05:00
|
|
|
def backend
|
|
|
|
current_thread_backend || @backend
|
|
|
|
end
|
|
|
|
|
2009-03-09 15:46:06 -04:00
|
|
|
def backend=(name)
|
2012-11-14 00:58:33 -05:00
|
|
|
backend = name && cast_backend_name_to_module(name)
|
|
|
|
self.current_thread_backend = backend if current_thread_backend
|
|
|
|
@backend = backend
|
2009-03-10 15:08:42 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
def with_backend(name)
|
2012-11-14 00:58:33 -05:00
|
|
|
old_backend = current_thread_backend
|
|
|
|
self.current_thread_backend = name && cast_backend_name_to_module(name)
|
2009-03-10 15:08:42 -04:00
|
|
|
yield
|
|
|
|
ensure
|
2012-11-14 00:58:33 -05:00
|
|
|
self.current_thread_backend = old_backend
|
2009-03-09 15:39:20 -04:00
|
|
|
end
|
2010-04-29 06:27:25 -04:00
|
|
|
|
|
|
|
def to_tag(key, value, options)
|
|
|
|
type_name = options.delete(:type)
|
2016-08-06 13:38:33 -04:00
|
|
|
merged_options = options.merge(root: key, skip_instruct: true)
|
2010-04-29 06:27:25 -04:00
|
|
|
|
|
|
|
if value.is_a?(::Method) || value.is_a?(::Proc)
|
|
|
|
if value.arity == 1
|
|
|
|
value.call(merged_options)
|
|
|
|
else
|
|
|
|
value.call(merged_options, key.to_s.singularize)
|
|
|
|
end
|
|
|
|
elsif value.respond_to?(:to_xml)
|
|
|
|
value.to_xml(merged_options)
|
|
|
|
else
|
|
|
|
type_name ||= TYPE_NAMES[value.class.name]
|
|
|
|
type_name ||= value.class.name if value && !value.respond_to?(:to_str)
|
|
|
|
type_name = type_name.to_s if type_name
|
2012-05-23 03:59:13 -04:00
|
|
|
type_name = "dateTime" if type_name == "datetime"
|
2010-04-29 06:27:25 -04:00
|
|
|
|
|
|
|
key = rename_key(key.to_s, options)
|
|
|
|
|
2016-08-16 03:30:11 -04:00
|
|
|
attributes = options[:skip_types] || type_name.nil? ? {} : { type: type_name }
|
2010-04-29 06:27:25 -04:00
|
|
|
attributes[:nil] = true if value.nil?
|
|
|
|
|
|
|
|
encoding = options[:encoding] || DEFAULT_ENCODINGS[type_name]
|
|
|
|
attributes[:encoding] = encoding if encoding
|
|
|
|
|
|
|
|
formatted_value = FORMATTING[type_name] && !value.nil? ?
|
|
|
|
FORMATTING[type_name].call(value) : value
|
|
|
|
|
|
|
|
options[:builder].tag!(key, formatted_value, attributes)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def rename_key(key, options = {})
|
2010-11-03 14:02:42 -04:00
|
|
|
camelize = options[:camelize]
|
2010-04-29 06:27:25 -04:00
|
|
|
dasherize = !options.has_key?(:dasherize) || options[:dasherize]
|
2010-10-31 23:16:55 -04:00
|
|
|
if camelize
|
2010-11-03 14:02:42 -04:00
|
|
|
key = true == camelize ? key.camelize : key.camelize(camelize)
|
2010-10-31 23:16:55 -04:00
|
|
|
end
|
2010-07-17 17:45:19 -04:00
|
|
|
key = _dasherize(key) if dasherize
|
2010-04-29 06:27:25 -04:00
|
|
|
key
|
|
|
|
end
|
|
|
|
|
2016-12-17 03:13:50 -05:00
|
|
|
private
|
2010-04-29 06:27:25 -04:00
|
|
|
|
2016-08-06 13:55:02 -04:00
|
|
|
def _dasherize(key)
|
|
|
|
# $2 must be a non-greedy regex for this to work
|
2016-10-28 23:05:58 -04:00
|
|
|
left, middle, right = /\A(_*)(.*?)(_*)\Z/.match(key.strip)[1, 3]
|
2016-08-06 13:55:02 -04:00
|
|
|
"#{left}#{middle.tr('_ ', '--')}#{right}"
|
|
|
|
end
|
2010-07-17 17:45:19 -04:00
|
|
|
|
2016-09-14 04:57:52 -04:00
|
|
|
# TODO: Add support for other encodings
|
2016-12-17 03:13:50 -05:00
|
|
|
def _parse_binary(bin, entity)
|
2016-08-06 13:55:02 -04:00
|
|
|
case entity["encoding"]
|
|
|
|
when "base64"
|
|
|
|
::Base64.decode64(bin)
|
|
|
|
else
|
|
|
|
bin
|
|
|
|
end
|
2010-04-29 06:27:25 -04:00
|
|
|
end
|
|
|
|
|
2016-08-06 13:55:02 -04:00
|
|
|
def _parse_file(file, entity)
|
|
|
|
f = StringIO.new(::Base64.decode64(file))
|
|
|
|
f.extend(FileLike)
|
|
|
|
f.original_filename = entity["name"]
|
|
|
|
f.content_type = entity["content_type"]
|
|
|
|
f
|
|
|
|
end
|
2012-11-14 00:58:33 -05:00
|
|
|
|
|
|
|
def current_thread_backend
|
|
|
|
Thread.current[:xml_mini_backend]
|
|
|
|
end
|
|
|
|
|
|
|
|
def current_thread_backend=(name)
|
|
|
|
Thread.current[:xml_mini_backend] = name && cast_backend_name_to_module(name)
|
|
|
|
end
|
|
|
|
|
|
|
|
def cast_backend_name_to_module(name)
|
|
|
|
if name.is_a?(Module)
|
|
|
|
name
|
|
|
|
else
|
2017-10-21 09:11:29 -04:00
|
|
|
require "active_support/xml_mini/#{name.downcase}"
|
2012-11-14 00:58:33 -05:00
|
|
|
ActiveSupport.const_get("XmlMini_#{name}")
|
|
|
|
end
|
|
|
|
end
|
2009-03-09 15:46:06 -04:00
|
|
|
end
|
2008-11-26 02:37:10 -05:00
|
|
|
|
2016-08-06 11:58:50 -04:00
|
|
|
XmlMini.backend = "REXML"
|
2009-03-09 15:46:06 -04:00
|
|
|
end
|