mirror of
https://github.com/rails/rails.git
synced 2022-11-09 12:12:34 -05:00
010e246756
Some methods were added to public API in
5b14129d8d
and they should be not part of
the public API.
207 lines
6.3 KiB
Ruby
207 lines
6.3 KiB
Ruby
require "time"
|
|
require "base64"
|
|
require "bigdecimal"
|
|
require "active_support/core_ext/module/delegation"
|
|
require "active_support/core_ext/string/inflections"
|
|
require "active_support/core_ext/date_time/calculations"
|
|
|
|
module ActiveSupport
|
|
# = XmlMini
|
|
#
|
|
# To use the much faster libxml parser:
|
|
# gem 'libxml-ruby', '=0.9.7'
|
|
# XmlMini.backend = 'LibXML'
|
|
module XmlMini
|
|
extend self
|
|
|
|
# This module decorates files deserialized using Hash.from_xml with
|
|
# the <tt>original_filename</tt> and <tt>content_type</tt> methods.
|
|
module FileLike #:nodoc:
|
|
attr_writer :original_filename, :content_type
|
|
|
|
def original_filename
|
|
@original_filename || "untitled"
|
|
end
|
|
|
|
def content_type
|
|
@content_type || "application/octet-stream"
|
|
end
|
|
end
|
|
|
|
DEFAULT_ENCODINGS = {
|
|
"binary" => "base64"
|
|
} unless defined?(DEFAULT_ENCODINGS)
|
|
|
|
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"
|
|
}
|
|
|
|
# No need to map these on Ruby 2.4+
|
|
TYPE_NAMES["Fixnum"] = "integer" unless 0.class == Integer
|
|
TYPE_NAMES["Bignum"] = "integer" unless 0.class == Integer
|
|
end
|
|
|
|
FORMATTING = {
|
|
"symbol" => Proc.new { |symbol| symbol.to_s },
|
|
"date" => Proc.new { |date| date.to_s(:db) },
|
|
"dateTime" => Proc.new { |time| time.xmlschema },
|
|
"binary" => Proc.new { |binary| ::Base64.encode64(binary) },
|
|
"yaml" => Proc.new { |yaml| yaml.to_yaml }
|
|
} unless defined?(FORMATTING)
|
|
|
|
# TODO use regexp instead of Date.parse
|
|
unless defined?(PARSING)
|
|
PARSING = {
|
|
"symbol" => Proc.new { |symbol| symbol.to_s.to_sym },
|
|
"date" => Proc.new { |date| ::Date.parse(date) },
|
|
"datetime" => Proc.new { |time| Time.xmlschema(time).utc rescue ::DateTime.parse(time).utc },
|
|
"integer" => Proc.new { |integer| integer.to_i },
|
|
"float" => Proc.new { |float| float.to_f },
|
|
"decimal" => Proc.new do |number|
|
|
if String === number
|
|
begin
|
|
BigDecimal(number)
|
|
rescue ArgumentError
|
|
BigDecimal("0")
|
|
end
|
|
else
|
|
BigDecimal(number)
|
|
end
|
|
end,
|
|
"boolean" => Proc.new { |boolean| %w(1 true).include?(boolean.to_s.strip) },
|
|
"string" => Proc.new { |string| string.to_s },
|
|
"yaml" => Proc.new { |yaml| YAML::load(yaml) rescue yaml },
|
|
"base64Binary" => Proc.new { |bin| ::Base64.decode64(bin) },
|
|
"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
|
|
|
|
attr_accessor :depth
|
|
self.depth = 100
|
|
|
|
delegate :parse, to: :backend
|
|
|
|
def backend
|
|
current_thread_backend || @backend
|
|
end
|
|
|
|
def backend=(name)
|
|
backend = name && cast_backend_name_to_module(name)
|
|
self.current_thread_backend = backend if current_thread_backend
|
|
@backend = backend
|
|
end
|
|
|
|
def with_backend(name)
|
|
old_backend = current_thread_backend
|
|
self.current_thread_backend = name && cast_backend_name_to_module(name)
|
|
yield
|
|
ensure
|
|
self.current_thread_backend = old_backend
|
|
end
|
|
|
|
def to_tag(key, value, options)
|
|
type_name = options.delete(:type)
|
|
merged_options = options.merge(root: key, skip_instruct: true)
|
|
|
|
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
|
|
type_name = "dateTime" if type_name == "datetime"
|
|
|
|
key = rename_key(key.to_s, options)
|
|
|
|
attributes = options[:skip_types] || type_name.nil? ? {} : { type: type_name }
|
|
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 = {})
|
|
camelize = options[:camelize]
|
|
dasherize = !options.has_key?(:dasherize) || options[:dasherize]
|
|
if camelize
|
|
key = true == camelize ? key.camelize : key.camelize(camelize)
|
|
end
|
|
key = _dasherize(key) if dasherize
|
|
key
|
|
end
|
|
|
|
private
|
|
|
|
def _dasherize(key)
|
|
# $2 must be a non-greedy regex for this to work
|
|
left, middle, right = /\A(_*)(.*?)(_*)\Z/.match(key.strip)[1, 3]
|
|
"#{left}#{middle.tr('_ ', '--')}#{right}"
|
|
end
|
|
|
|
# TODO: Add support for other encodings
|
|
def _parse_binary(bin, entity)
|
|
case entity["encoding"]
|
|
when "base64"
|
|
::Base64.decode64(bin)
|
|
else
|
|
bin
|
|
end
|
|
end
|
|
|
|
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
|
|
|
|
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
|
|
require "active_support/xml_mini/#{name.downcase}"
|
|
ActiveSupport.const_get("XmlMini_#{name}")
|
|
end
|
|
end
|
|
end
|
|
|
|
XmlMini.backend = "REXML"
|
|
end
|