From 2e9af3638d950ef840e1287f99e323887ec6a4c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Thu, 29 Apr 2010 12:27:25 +0200 Subject: Move several configuration values from Hash to ActiveSupport::XmlMini, which both Hash and Array depends on. Also, refactored ActiveModel serializers to just use ActiveSupport::XmlMini.to_tag. As consequence, if a serialized attribute is an array or a hash, it's not encoded as yaml, but as a hash or array. --- activesupport/lib/active_support/core_ext/array.rb | 1 - .../active_support/core_ext/array/conversions.rb | 33 +++--- activesupport/lib/active_support/core_ext/hash.rb | 1 - .../active_support/core_ext/hash/conversions.rb | 116 +++---------------- .../core_ext/hash/conversions_xml_value.rb | 51 --------- activesupport/lib/active_support/xml_mini.rb | 127 ++++++++++++++++++++- 6 files changed, 160 insertions(+), 169 deletions(-) delete mode 100644 activesupport/lib/active_support/core_ext/hash/conversions_xml_value.rb (limited to 'activesupport/lib') diff --git a/activesupport/lib/active_support/core_ext/array.rb b/activesupport/lib/active_support/core_ext/array.rb index d20b725701..4688468a8f 100644 --- a/activesupport/lib/active_support/core_ext/array.rb +++ b/activesupport/lib/active_support/core_ext/array.rb @@ -5,4 +5,3 @@ require 'active_support/core_ext/array/conversions' require 'active_support/core_ext/array/extract_options' require 'active_support/core_ext/array/grouping' require 'active_support/core_ext/array/random_access' -require 'active_support/core_ext/hash/conversions_xml_value' diff --git a/activesupport/lib/active_support/core_ext/array/conversions.rb b/activesupport/lib/active_support/core_ext/array/conversions.rb index b9ef8c0ee1..2b07f05d27 100644 --- a/activesupport/lib/active_support/core_ext/array/conversions.rb +++ b/activesupport/lib/active_support/core_ext/array/conversions.rb @@ -1,7 +1,7 @@ +require 'active_support/xml_mini' require 'active_support/core_ext/hash/keys' -require 'active_support/core_ext/hash/conversions_xml_value' require 'active_support/core_ext/hash/reverse_merge' -require 'active_support/inflector' +require 'active_support/core_ext/string/inflections' class Array # Converts the array to a comma-separated sentence where the last element is joined by the connector word. Options: @@ -52,8 +52,6 @@ class Array alias_method :to_default_s, :to_s alias_method :to_s, :to_formatted_s - include Hash::XmlValue - # Returns a string that represents this array in XML by sending +to_xml+ # to each element. Active Record collections delegate their representation # in XML to this method. @@ -133,22 +131,27 @@ class Array require 'builder' unless defined?(Builder) options = options.dup - options[:indent] ||= 2 - options.reverse_merge!({ :builder => Builder::XmlMarkup.new(:indent => options[:indent]) }) - - options[:root] ||= all? { |e| e.is_a?(first.class) && first.class.to_s != "Hash" } ? ActiveSupport::Inflector.pluralize(ActiveSupport::Inflector.underscore(first.class.name)).tr('/', '_') : "objects" + options[:indent] ||= 2 + options[:builder] ||= Builder::XmlMarkup.new(:indent => options[:indent]) + options[:root] ||= if first.class.to_s != "Hash" && all? { |e| e.is_a?(first.class) } + underscored = ActiveSupport::Inflector.underscore(first.class.name) + ActiveSupport::Inflector.pluralize(underscored).tr('/', '_') + else + "objects" + end + builder = options[:builder] + builder.instruct! unless options.delete(:skip_instruct) - options[:builder].instruct! unless options.delete(:skip_instruct) - root = rename_key(options[:root].to_s, options) + root = ActiveSupport::XmlMini.rename_key(options[:root].to_s, options) + children = options.delete(:children) || root.singularize - options[:children] ||= options[:root].singularize attributes = options[:skip_types] ? {} : {:type => "array"} - return options[:builder].tag!(root, attributes) if empty? + return builder.tag!(root, attributes) if empty? - options[:builder].__send__(:method_missing, root, attributes) do - each { |value| xml_value(options[:children], value, options) } - yield options[:builder] if block_given? + builder.__send__(:method_missing, root, attributes) do + each { |value| ActiveSupport::XmlMini.to_tag(children, value, options) } + yield builder if block_given? end end diff --git a/activesupport/lib/active_support/core_ext/hash.rb b/activesupport/lib/active_support/core_ext/hash.rb index 2e7bdce360..501483498d 100644 --- a/activesupport/lib/active_support/core_ext/hash.rb +++ b/activesupport/lib/active_support/core_ext/hash.rb @@ -1,5 +1,4 @@ require 'active_support/core_ext/hash/conversions' -require 'active_support/core_ext/hash/conversions_xml_value' require 'active_support/core_ext/hash/deep_merge' require 'active_support/core_ext/hash/diff' require 'active_support/core_ext/hash/except' diff --git a/activesupport/lib/active_support/core_ext/hash/conversions.rb b/activesupport/lib/active_support/core_ext/hash/conversions.rb index 1b2f69f573..14e5d2f8ac 100644 --- a/activesupport/lib/active_support/core_ext/hash/conversions.rb +++ b/activesupport/lib/active_support/core_ext/hash/conversions.rb @@ -1,88 +1,11 @@ +require 'active_support/xml_mini' require 'active_support/time' require 'active_support/core_ext/array/wrap' require 'active_support/core_ext/hash/reverse_merge' require 'active_support/core_ext/object/blank' require 'active_support/core_ext/string/inflections' -require 'active_support/core_ext/hash/conversions_xml_value' class Hash - # This module exists to decorate files deserialized using Hash.from_xml with - # the original_filename and content_type 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 - - include XmlValue - - XML_TYPE_NAMES = { - "Symbol" => "symbol", - "Fixnum" => "integer", - "Bignum" => "integer", - "BigDecimal" => "decimal", - "Float" => "float", - "TrueClass" => "boolean", - "FalseClass" => "boolean", - "Date" => "date", - "DateTime" => "datetime", - "Time" => "datetime", - "Array" => "array", - "Hash" => "hash" - } unless defined?(XML_TYPE_NAMES) - - XML_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| ActiveSupport::Base64.encode64(binary) }, - "yaml" => Proc.new { |yaml| yaml.to_yaml } - } unless defined?(XML_FORMATTING) - - # TODO: use Time.xmlschema instead of Time.parse; - # use regexp instead of Date.parse - unless defined?(XML_PARSING) - XML_PARSING = { - "symbol" => Proc.new { |symbol| symbol.to_sym }, - "date" => Proc.new { |date| ::Date.parse(date) }, - "datetime" => Proc.new { |time| ::Time.parse(time).utc rescue ::DateTime.parse(time).utc }, - "integer" => Proc.new { |integer| integer.to_i }, - "float" => Proc.new { |float| float.to_f }, - "decimal" => Proc.new { |number| BigDecimal(number) }, - "boolean" => Proc.new { |boolean| %w(1 true).include?(boolean.strip) }, - "string" => Proc.new { |string| string.to_s }, - "yaml" => Proc.new { |yaml| YAML::load(yaml) rescue yaml }, - "base64Binary" => Proc.new { |bin| ActiveSupport::Base64.decode64(bin) }, - "binary" => Proc.new do |bin, entity| - case entity['encoding'] - when 'base64' - ActiveSupport::Base64.decode64(bin) - # TODO: Add support for other encodings - else - bin - end - end, - "file" => Proc.new do |file, entity| - f = StringIO.new(ActiveSupport::Base64.decode64(file)) - f.extend(FileLike) - f.original_filename = entity['name'] - f.content_type = entity['content_type'] - f - end - } - - XML_PARSING.update( - "double" => XML_PARSING["float"], - "dateTime" => XML_PARSING["datetime"] - ) - end - # Returns a string containing an XML representation of its receiver: # # {"foo" => 1, "bar" => 2}.to_xml @@ -135,17 +58,18 @@ class Hash require 'builder' unless defined?(Builder) options = options.dup - options[:indent] ||= 2 - options.reverse_merge!({ :builder => Builder::XmlMarkup.new(:indent => options[:indent]), - :root => "hash" }) - options[:builder].instruct! unless options.delete(:skip_instruct) - root = rename_key(options[:root].to_s, options) - # common upto this point - options[:builder].__send__(:method_missing, root) do - each do |key, value| - xml_value(key, value, options) - end - yield options[:builder] if block_given? + options[:indent] ||= 2 + options[:root] ||= "hash" + options[:builder] ||= Builder::XmlMarkup.new(:indent => options[:indent]) + + builder = options[:builder] + builder.instruct! unless options.delete(:skip_instruct) + + root = ActiveSupport::XmlMini.rename_key(options[:root].to_s, options) + + builder.__send__(:method_missing, root) do + each { |key, value| ActiveSupport::XmlMini.to_tag(key, value, options) } + yield builder if block_given? end end @@ -174,12 +98,8 @@ class Hash end elsif value.has_key?("__content__") content = value["__content__"] - if parser = XML_PARSING[value["type"]] - if parser.arity == 2 - XML_PARSING[value["type"]].call(content, value) - else - XML_PARSING[value["type"]].call(content) - end + if parser = ActiveSupport::XmlMini::PARSING[value["type"]] + parser.arity == 1 ? parser.call(content) : parser.call(content, value) else content end @@ -205,11 +125,7 @@ class Hash 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 + value.length > 1 ? value : value.first when 'String' value else diff --git a/activesupport/lib/active_support/core_ext/hash/conversions_xml_value.rb b/activesupport/lib/active_support/core_ext/hash/conversions_xml_value.rb deleted file mode 100644 index fac8f90122..0000000000 --- a/activesupport/lib/active_support/core_ext/hash/conversions_xml_value.rb +++ /dev/null @@ -1,51 +0,0 @@ -class Hash - module XmlValue - def xml_value(key, value, options) - 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})) - 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 - else - 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 = rename_key(key.to_s, options) - - 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 - end - #yield options[:builder] if block_given? - end - - def rename_key(key, options = {}) - camelize = options.has_key?(:camelize) && options[:camelize] - dasherize = !options.has_key?(:dasherize) || options[:dasherize] - key = key.camelize if camelize - dasherize ? key.dasherize : key - end - end -end - diff --git a/activesupport/lib/active_support/xml_mini.rb b/activesupport/lib/active_support/xml_mini.rb index f22fbcc0e1..7594d7b68b 100644 --- a/activesupport/lib/active_support/xml_mini.rb +++ b/activesupport/lib/active_support/xml_mini.rb @@ -9,6 +9,71 @@ module ActiveSupport module XmlMini extend self + # This module exists to decorate files deserialized using Hash.from_xml with + # the original_filename and content_type 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?(TYPE_NAMES) + + TYPE_NAMES = { + "Symbol" => "symbol", + "Fixnum" => "integer", + "Bignum" => "integer", + "BigDecimal" => "decimal", + "Float" => "float", + "TrueClass" => "boolean", + "FalseClass" => "boolean", + "Date" => "date", + "DateTime" => "datetime", + "Time" => "datetime", + "Array" => "array", + "Hash" => "hash" + } unless defined?(TYPE_NAMES) + + 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| ActiveSupport::Base64.encode64(binary) }, + "yaml" => Proc.new { |yaml| yaml.to_yaml } + } unless defined?(FORMATTING) + + # TODO: use Time.xmlschema instead of Time.parse; + # use regexp instead of Date.parse + unless defined?(PARSING) + PARSING = { + "symbol" => Proc.new { |symbol| symbol.to_sym }, + "date" => Proc.new { |date| ::Date.parse(date) }, + "datetime" => Proc.new { |time| ::Time.parse(time).utc rescue ::DateTime.parse(time).utc }, + "integer" => Proc.new { |integer| integer.to_i }, + "float" => Proc.new { |float| float.to_f }, + "decimal" => Proc.new { |number| BigDecimal(number) }, + "boolean" => Proc.new { |boolean| %w(1 true).include?(boolean.strip) }, + "string" => Proc.new { |string| string.to_s }, + "yaml" => Proc.new { |yaml| YAML::load(yaml) rescue yaml }, + "base64Binary" => Proc.new { |bin| ActiveSupport::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_reader :backend delegate :parse, :to => :backend @@ -16,7 +81,7 @@ module ActiveSupport if name.is_a?(Module) @backend = name else - require "active_support/xml_mini/#{name.to_s.downcase}.rb" + require "active_support/xml_mini/#{name.to_s.downcase}" @backend = ActiveSupport.const_get("XmlMini_#{name}") end end @@ -27,6 +92,66 @@ module ActiveSupport ensure self.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 + + 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.has_key?(:camelize) && options[:camelize] + dasherize = !options.has_key?(:dasherize) || options[:dasherize] + key = key.camelize if camelize + key = key.dasherize if dasherize + key + end + + protected + + # TODO: Add support for other encodings + def _parse_binary(bin, entity) #:nodoc: + case entity['encoding'] + when 'base64' + ActiveSupport::Base64.decode64(bin) + else + bin + end + end + + def _parse_file(file, entity) + f = StringIO.new(ActiveSupport::Base64.decode64(file)) + f.extend(FileLike) + f.original_filename = entity['name'] + f.content_type = entity['content_type'] + f + end end XmlMini.backend = 'REXML' -- cgit v1.2.3