diff options
author | Xavier Noria <fxn@hashref.com> | 2010-05-01 02:23:47 -0700 |
---|---|---|
committer | Xavier Noria <fxn@hashref.com> | 2010-05-01 02:23:47 -0700 |
commit | efba1d4227514a6ce4880910a6531b0a6c3c75aa (patch) | |
tree | daff4155b8c19b915125ac0b0da458279358e743 /activesupport | |
parent | 81807e0fe2879e08563c91ee6809ab6d1d0bd081 (diff) | |
parent | 6c280f3398966ffba45069500ff43d632513fe44 (diff) | |
download | rails-efba1d4227514a6ce4880910a6531b0a6c3c75aa.tar.gz rails-efba1d4227514a6ce4880910a6531b0a6c3c75aa.tar.bz2 rails-efba1d4227514a6ce4880910a6531b0a6c3c75aa.zip |
Merge commit 'rails/master'
Conflicts:
railties/guides/source/index.html.erb
Diffstat (limited to 'activesupport')
-rw-r--r-- | activesupport/CHANGELOG | 2 | ||||
-rw-r--r-- | activesupport/activesupport.gemspec | 2 | ||||
-rw-r--r-- | activesupport/lib/active_support/cache/strategy/local_cache.rb | 14 | ||||
-rw-r--r-- | activesupport/lib/active_support/core_ext/array/conversions.rb | 42 | ||||
-rw-r--r-- | activesupport/lib/active_support/core_ext/hash/conversions.rb | 149 | ||||
-rw-r--r-- | activesupport/lib/active_support/inflector/transliterate.rb | 94 | ||||
-rw-r--r-- | activesupport/lib/active_support/multibyte/chars.rb | 45 | ||||
-rw-r--r-- | activesupport/lib/active_support/xml_mini.rb | 127 | ||||
-rw-r--r-- | activesupport/test/core_ext/array_ext_test.rb | 10 | ||||
-rw-r--r-- | activesupport/test/transliterate_test.rb | 51 |
10 files changed, 276 insertions, 260 deletions
diff --git a/activesupport/CHANGELOG b/activesupport/CHANGELOG index 7bfc377ff1..f24a1b1c6c 100644 --- a/activesupport/CHANGELOG +++ b/activesupport/CHANGELOG @@ -1,5 +1,7 @@ *Rails 3.0.0 [beta 4/release candidate] (unreleased)* +* Array#to_xml is more powerful and able to handle the same types as Hash#to_xml #4490 [Neeraj Singh] + * Harmonize the caching API and refactor the backends. #4452 [Brian Durand] All caches: * Add default options to initializer that will be sent to all read, write, fetch, exist?, increment, and decrement diff --git a/activesupport/activesupport.gemspec b/activesupport/activesupport.gemspec index ad1401bfa9..0fea84a6ef 100644 --- a/activesupport/activesupport.gemspec +++ b/activesupport/activesupport.gemspec @@ -19,7 +19,7 @@ Gem::Specification.new do |s| s.has_rdoc = true - s.add_dependency('i18n', '~> 0.3.6') + s.add_dependency('i18n', '~> 0.4.0.beta') s.add_dependency('tzinfo', '~> 0.3.16') s.add_dependency('builder', '~> 2.1.2') s.add_dependency('memcache-client', '>= 1.7.5') diff --git a/activesupport/lib/active_support/cache/strategy/local_cache.rb b/activesupport/lib/active_support/cache/strategy/local_cache.rb index 8942587ac8..efb5ad26ab 100644 --- a/activesupport/lib/active_support/cache/strategy/local_cache.rb +++ b/activesupport/lib/active_support/cache/strategy/local_cache.rb @@ -56,6 +56,13 @@ module ActiveSupport @middleware ||= begin klass = Class.new klass.class_eval(<<-EOS, __FILE__, __LINE__ + 1) + class << self + def name + "ActiveSupport::Cache::Strategy::LocalCache" + end + alias :to_s :name + end + def initialize(app) @app = app end @@ -67,11 +74,6 @@ module ActiveSupport Thread.current[:#{thread_local_key}] = nil end EOS - - def klass.to_s - "ActiveSupport::Cache::Strategy::LocalCache" - end - klass end end @@ -140,7 +142,7 @@ module ActiveSupport private def thread_local_key - @thread_local_key ||= "#{self.class.name.underscore}_local_cache_#{self.object_id}".gsub("/", "_").to_sym + @thread_local_key ||= "#{self.class.name.underscore}_local_cache_#{object_id}".gsub(/[\/-]/, '_').to_sym end def local_cache diff --git a/activesupport/lib/active_support/core_ext/array/conversions.rb b/activesupport/lib/active_support/core_ext/array/conversions.rb index 5d8e78e6e5..2b07f05d27 100644 --- a/activesupport/lib/active_support/core_ext/array/conversions.rb +++ b/activesupport/lib/active_support/core_ext/array/conversions.rb @@ -1,6 +1,7 @@ +require 'active_support/xml_mini' require 'active_support/core_ext/hash/keys' 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: @@ -127,34 +128,31 @@ class Array # </messages> # def to_xml(options = {}) - raise "Not all elements respond to to_xml" unless all? { |e| e.respond_to? :to_xml } require 'builder' unless defined?(Builder) options = options.dup - options[:root] ||= all? { |e| e.is_a?(first.class) && first.class.to_s != "Hash" } ? ActiveSupport::Inflector.pluralize(ActiveSupport::Inflector.underscore(first.class.name)).tr('/', '_') : "records" - options[:children] ||= options[:root].singularize - options[:indent] ||= 2 - options[:builder] ||= Builder::XmlMarkup.new(:indent => options[:indent]) - - root = options.delete(:root).to_s - children = options.delete(:children) - - if !options.has_key?(:dasherize) || options[:dasherize] - root = root.dasherize + 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 - options[:builder].instruct! unless options.delete(:skip_instruct) + builder = options[:builder] + builder.instruct! unless options.delete(:skip_instruct) - opts = options.merge({ :root => children }) + root = ActiveSupport::XmlMini.rename_key(options[:root].to_s, options) + children = options.delete(:children) || root.singularize - xml = options[:builder] - if empty? - xml.tag!(root, options[:skip_types] ? {} : {:type => "array"}) - else - xml.tag!(root, options[:skip_types] ? {} : {:type => "array"}) { - yield xml if block_given? - each { |e| e.to_xml(opts.merge({ :skip_instruct => true })) } - } + attributes = options[:skip_types] ? {} : {:type => "array"} + return builder.tag!(root, attributes) if empty? + + builder.__send__(:method_missing, root, attributes) do + each { |value| ActiveSupport::XmlMini.to_tag(children, value, options) } + yield builder if block_given? end end + end diff --git a/activesupport/lib/active_support/core_ext/hash/conversions.rb b/activesupport/lib/active_support/core_ext/hash/conversions.rb index c882434f78..14e5d2f8ac 100644 --- a/activesupport/lib/active_support/core_ext/hash/conversions.rb +++ b/activesupport/lib/active_support/core_ext/hash/conversions.rb @@ -1,3 +1,4 @@ +require 'active_support/xml_mini' require 'active_support/time' require 'active_support/core_ext/array/wrap' require 'active_support/core_ext/hash/reverse_merge' @@ -5,79 +6,6 @@ require 'active_support/core_ext/object/blank' require 'active_support/core_ext/string/inflections' class Hash - # This module exists to decorate 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 - - XML_TYPE_NAMES = { - "Symbol" => "symbol", - "Fixnum" => "integer", - "Bignum" => "integer", - "BigDecimal" => "decimal", - "Float" => "float", - "TrueClass" => "boolean", - "FalseClass" => "boolean", - "Date" => "date", - "DateTime" => "datetime", - "Time" => "datetime" - } 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 @@ -130,62 +58,19 @@ 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) + options[:indent] ||= 2 + options[:root] ||= "hash" + options[:builder] ||= Builder::XmlMarkup.new(:indent => options[:indent]) - options[:builder].__send__(:method_missing, root) do - 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})) - 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] + builder = options[:builder] + builder.instruct! unless options.delete(:skip_instruct) - 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 + root = ActiveSupport::XmlMini.rename_key(options[:root].to_s, options) - options[:builder].tag!(key, - XML_FORMATTING[type_name] ? XML_FORMATTING[type_name].call(value) : value, - attributes - ) - end - end - end - - yield options[:builder] if block_given? + builder.__send__(:method_missing, root) do + each { |key, value| ActiveSupport::XmlMini.to_tag(key, value, options) } + yield builder if block_given? 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 - dasherize ? key.dasherize : key end class << self @@ -213,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 @@ -244,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/inflector/transliterate.rb b/activesupport/lib/active_support/inflector/transliterate.rb index 9c99dcfb01..5ec87372d0 100644 --- a/activesupport/lib/active_support/inflector/transliterate.rb +++ b/activesupport/lib/active_support/inflector/transliterate.rb @@ -3,45 +3,62 @@ require 'active_support/core_ext/string/multibyte' module ActiveSupport module Inflector - extend self - # UTF-8 byte => ASCII approximate UTF-8 byte(s) - ASCII_APPROXIMATIONS = { - 198 => [65, 69], # Æ => AE - 208 => 68, # Ð => D - 216 => 79, # Ø => O - 222 => [84, 104], # Þ => Þ - 223 => [115, 115], # ß => ss - 230 => [97, 101], # æ => ae - 240 => 100, # ð => d - 248 => 111, # ø => o - 254 => [116, 104], # þ => th - 272 => 68, # Đ => D - 273 => 100, # đ => đ - 294 => 72, # Ħ => H - 295 => 104, # ħ => h - 305 => 105, # ı => i - 306 => [73, 74], # IJ =>IJ - 307 => [105, 106], # ij => ij - 312 => 107, # ĸ => k - 319 => 76, # Ŀ => L - 320 => 108, # ŀ => l - 321 => 76, # Ł => L - 322 => 108, # ł => l - 329 => 110, # ʼn => n - 330 => [78, 71], # Ŋ => NG - 331 => [110, 103], # ŋ => ng - 338 => [79, 69], # Œ => OE - 339 => [111, 101], # œ => oe - 358 => 84, # Ŧ => T - 359 => 116 # ŧ => t - } - - # Replaces accented characters with an ASCII approximation, or deletes it if none exsits. - def transliterate(string) - ActiveSupport::Multibyte::Chars.new(string).tidy_bytes.normalize(:d).unpack("U*").map do |char| - ASCII_APPROXIMATIONS[char] || (char if char < 128) - end.compact.flatten.pack("U*") + # Replaces non-ASCII characters with an ASCII approximation, or if none + # exists, a replacement character which defaults to "?". + # + # transliterate("Ærøskøbing") + # # => "AEroskobing" + # + # Default approximations are provided for Western/Latin characters, + # e.g, "ø", "ñ", "é", "ß", etc. + # + # This method is I18n aware, so you can set up custom approximations for a + # locale. This can be useful, for example, to transliterate German's "ü" + # and "ö" to "ue" and "oe", or to add support for transliterating Russian + # to ASCII. + # + # In order to make your custom transliterations available, you must set + # them as the <tt>i18n.transliterate.rule</tt> i18n key: + # + # # Store the transliterations in locales/de.yml + # i18n: + # transliterate: + # ü: "ue" + # ö: "oe" + # + # # Or set them using Ruby + # I18n.backend.store_translations(:de, :i18n => { + # :transliterate => { + # :rule => { + # "ü" => "ue", + # "ö" => "oe" + # } + # } + # }) + # + # The value for <tt>i18n.transliterate.rule</tt> can be a simple Hash that maps + # characters to ASCII approximations as shown above, or, for more complex + # requirements, a Proc: + # + # I18n.backend.store_translations(:de, :i18n => { + # :transliterate => { + # :rule => lambda {|string| MyTransliterator.transliterate(string)} + # } + # }) + # + # Now you can have different transliterations for each locale: + # + # I18n.locale = :en + # transliterate("Jürgen") + # # => "Jurgen" + # + # I18n.locale = :de + # transliterate("Jürgen") + # # => "Juergen" + def transliterate(string, replacement = "?") + I18n.transliterate(Multibyte::Chars.normalize( + Multibyte::Chars.tidy_bytes(string), :c), :replacement => replacement) end # Replaces special characters in a string so that it may be used as part of a 'pretty' URL. @@ -73,5 +90,6 @@ module ActiveSupport end parameterized_string.downcase end + end end diff --git a/activesupport/lib/active_support/multibyte/chars.rb b/activesupport/lib/active_support/multibyte/chars.rb index 4ade1158fd..cca30d1141 100644 --- a/activesupport/lib/active_support/multibyte/chars.rb +++ b/activesupport/lib/active_support/multibyte/chars.rb @@ -75,8 +75,6 @@ module ActiveSupport #:nodoc: UNICODE_TRAILERS_PAT = /(#{codepoints_to_pattern(UNICODE_LEADERS_AND_TRAILERS)})+\Z/u UNICODE_LEADERS_PAT = /\A(#{codepoints_to_pattern(UNICODE_LEADERS_AND_TRAILERS)})+/u - UTF8_PAT = ActiveSupport::Multibyte::VALID_CHARACTER['UTF-8'] - attr_reader :wrapped_string alias to_s wrapped_string alias to_str wrapped_string @@ -409,25 +407,11 @@ module ActiveSupport #:nodoc: # Returns the KC normalization of the string by default. NFKC is considered the best normalization form for # passing strings to databases and validations. # - # * <tt>str</tt> - The string to perform normalization on. # * <tt>form</tt> - The form you want to normalize in. Should be one of the following: # <tt>:c</tt>, <tt>:kc</tt>, <tt>:d</tt>, or <tt>:kd</tt>. Default is # ActiveSupport::Multibyte.default_normalization_form def normalize(form=ActiveSupport::Multibyte.default_normalization_form) - # See http://www.unicode.org/reports/tr15, Table 1 - codepoints = self.class.u_unpack(@wrapped_string) - chars(case form - when :d - self.class.reorder_characters(self.class.decompose_codepoints(:canonical, codepoints)) - when :c - self.class.compose_codepoints(self.class.reorder_characters(self.class.decompose_codepoints(:canonical, codepoints))) - when :kd - self.class.reorder_characters(self.class.decompose_codepoints(:compatability, codepoints)) - when :kc - self.class.compose_codepoints(self.class.reorder_characters(self.class.decompose_codepoints(:compatability, codepoints))) - else - raise ArgumentError, "#{form} is not a valid normalization variant", caller - end.pack('U*')) + chars(self.class.normalize(@wrapped_string, form)) end # Performs canonical decomposition on all the characters. @@ -659,7 +643,7 @@ module ActiveSupport #:nodoc: # Replaces all ISO-8859-1 or CP1252 characters by their UTF-8 equivalent resulting in a valid UTF-8 string. # - # Passing +true+ will forcibly tidy all bytes, assuming that the string's encoding is entirely CP-1252 or ISO-8859-1. + # Passing +true+ will forcibly tidy all bytes, assuming that the string's encoding is entirely CP1252 or ISO-8859-1. def tidy_bytes(string, force = false) if force return string.unpack("C*").map do |b| @@ -708,6 +692,31 @@ module ActiveSupport #:nodoc: end bytes.empty? ? "" : bytes.flatten.compact.pack("C*").unpack("U*").pack("U*") end + + # Returns the KC normalization of the string by default. NFKC is considered the best normalization form for + # passing strings to databases and validations. + # + # * <tt>string</tt> - The string to perform normalization on. + # * <tt>form</tt> - The form you want to normalize in. Should be one of the following: + # <tt>:c</tt>, <tt>:kc</tt>, <tt>:d</tt>, or <tt>:kd</tt>. Default is + # ActiveSupport::Multibyte.default_normalization_form + def normalize(string, form=ActiveSupport::Multibyte.default_normalization_form) + # See http://www.unicode.org/reports/tr15, Table 1 + codepoints = u_unpack(string) + case form + when :d + reorder_characters(decompose_codepoints(:canonical, codepoints)) + when :c + compose_codepoints(reorder_characters(decompose_codepoints(:canonical, codepoints))) + when :kd + reorder_characters(decompose_codepoints(:compatability, codepoints)) + when :kc + compose_codepoints(reorder_characters(decompose_codepoints(:compatability, codepoints))) + else + raise ArgumentError, "#{form} is not a valid normalization variant", caller + end.pack('U*') + end + end protected 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 <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?(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' diff --git a/activesupport/test/core_ext/array_ext_test.rb b/activesupport/test/core_ext/array_ext_test.rb index aecc644549..e7617466c2 100644 --- a/activesupport/test/core_ext/array_ext_test.rb +++ b/activesupport/test/core_ext/array_ext_test.rb @@ -211,7 +211,7 @@ class ArrayToXmlTests < Test::Unit::TestCase { :name => "Jason", :age => 31, :age_in_millis => BigDecimal.new('1.0') } ].to_xml(:skip_instruct => true, :indent => 0) - assert_equal '<records type="array"><record>', xml.first(30) + assert_equal '<objects type="array"><object>', xml.first(30) assert xml.include?(%(<age type="integer">26</age>)), xml assert xml.include?(%(<age-in-millis type="integer">820497600000</age-in-millis>)), xml assert xml.include?(%(<name>David</name>)), xml @@ -233,7 +233,7 @@ class ArrayToXmlTests < Test::Unit::TestCase { :name => "David", :street_address => "Paulina" }, { :name => "Jason", :street_address => "Evergreen" } ].to_xml(:skip_instruct => true, :skip_types => true, :indent => 0) - assert_equal "<records><record>", xml.first(17) + assert_equal "<objects><object>", xml.first(17) assert xml.include?(%(<street-address>Paulina</street-address>)) assert xml.include?(%(<name>David</name>)) assert xml.include?(%(<street-address>Evergreen</street-address>)) @@ -245,7 +245,7 @@ class ArrayToXmlTests < Test::Unit::TestCase { :name => "David", :street_address => "Paulina" }, { :name => "Jason", :street_address => "Evergreen" } ].to_xml(:skip_instruct => true, :skip_types => true, :indent => 0, :dasherize => false) - assert_equal "<records><record>", xml.first(17) + assert_equal "<objects><object>", xml.first(17) assert xml.include?(%(<street_address>Paulina</street_address>)) assert xml.include?(%(<street_address>Evergreen</street_address>)) end @@ -255,7 +255,7 @@ class ArrayToXmlTests < Test::Unit::TestCase { :name => "David", :street_address => "Paulina" }, { :name => "Jason", :street_address => "Evergreen" } ].to_xml(:skip_instruct => true, :skip_types => true, :indent => 0, :dasherize => true) - assert_equal "<records><record>", xml.first(17) + assert_equal "<objects><object>", xml.first(17) assert xml.include?(%(<street-address>Paulina</street-address>)) assert xml.include?(%(<street-address>Evergreen</street-address>)) end @@ -319,7 +319,7 @@ class ArrayExtractOptionsTests < Test::Unit::TestCase assert_equal({}, options) assert_equal [hash], array end - + def test_extract_options_extracts_extractable_subclass hash = ExtractableHashSubclass.new hash[:foo] = 1 diff --git a/activesupport/test/transliterate_test.rb b/activesupport/test/transliterate_test.rb index d689b6be73..b054855d08 100644 --- a/activesupport/test/transliterate_test.rb +++ b/activesupport/test/transliterate_test.rb @@ -4,36 +4,6 @@ require 'active_support/inflector/transliterate' class TransliterateTest < Test::Unit::TestCase - APPROXIMATIONS = { - "À"=>"A", "Á"=>"A", "Â"=>"A", "Ã"=>"A", "Ä"=>"A", "Å"=>"A", "Æ"=>"AE", - "Ç"=>"C", "È"=>"E", "É"=>"E", "Ê"=>"E", "Ë"=>"E", "Ì"=>"I", "Í"=>"I", - "Î"=>"I", "Ï"=>"I", "Ð"=>"D", "Ñ"=>"N", "Ò"=>"O", "Ó"=>"O", "Ô"=>"O", - "Õ"=>"O", "Ö"=>"O", "Ø"=>"O", "Ù"=>"U", "Ú"=>"U", "Û"=>"U", "Ü"=>"U", - "Ý"=>"Y", "Þ"=>"Th", "ß"=>"ss", "à"=>"a", "á"=>"a", "â"=>"a", "ã"=>"a", - "ä"=>"a", "å"=>"a", "æ"=>"ae", "ç"=>"c", "è"=>"e", "é"=>"e", "ê"=>"e", - "ë"=>"e", "ì"=>"i", "í"=>"i", "î"=>"i", "ï"=>"i", "ð"=>"d", "ñ"=>"n", - "ò"=>"o", "ó"=>"o", "ô"=>"o", "õ"=>"o", "ö"=>"o", "ø"=>"o", "ù"=>"u", - "ú"=>"u", "û"=>"u", "ü"=>"u", "ý"=>"y", "þ"=>"th", "ÿ"=>"y", "Ā"=>"A", - "ā"=>"a", "Ă"=>"A", "ă"=>"a", "Ą"=>"A", "ą"=>"a", "Ć"=>"C", "ć"=>"c", - "Ĉ"=>"C", "ĉ"=>"c", "Ċ"=>"C", "ċ"=>"c", "Č"=>"C", "č"=>"c", "Ď"=>"D", - "ď"=>"d", "Đ"=>"D", "đ"=>"d", "Ē"=>"E", "ē"=>"e", "Ĕ"=>"E", "ĕ"=>"e", - "Ė"=>"E", "ė"=>"e", "Ę"=>"E", "ę"=>"e", "Ě"=>"E", "ě"=>"e", "Ĝ"=>"G", - "ĝ"=>"g", "Ğ"=>"G", "ğ"=>"g", "Ġ"=>"G", "ġ"=>"g", "Ģ"=>"G", "ģ"=>"g", - "Ĥ"=>"H", "ĥ"=>"h", "Ħ"=>"H", "ħ"=>"h", "Ĩ"=>"I", "ĩ"=>"i", "Ī"=>"I", - "ī"=>"i", "Ĭ"=>"I", "ĭ"=>"i", "Į"=>"I", "į"=>"i", "İ"=>"I", "ı"=>"i", - "IJ"=>"IJ", "ij"=>"ij", "Ĵ"=>"J", "ĵ"=>"j", "Ķ"=>"K", "ķ"=>"k", "ĸ"=>"k", - "Ĺ"=>"L", "ĺ"=>"l", "Ļ"=>"L", "ļ"=>"l", "Ľ"=>"L", "ľ"=>"l", "Ŀ"=>"L", - "ŀ"=>"l", "Ł"=>"L", "ł"=>"l", "Ń"=>"N", "ń"=>"n", "Ņ"=>"N", "ņ"=>"n", - "Ň"=>"N", "ň"=>"n", "ʼn"=>"n", "Ŋ"=>"NG", "ŋ"=>"ng", "Ō"=>"O", "ō"=>"o", - "Ŏ"=>"O", "ŏ"=>"o", "Ő"=>"O", "ő"=>"o", "Œ"=>"OE", "œ"=>"oe", "Ŕ"=>"R", - "ŕ"=>"r", "Ŗ"=>"R", "ŗ"=>"r", "Ř"=>"R", "ř"=>"r", "Ś"=>"S", "ś"=>"s", - "Ŝ"=>"S", "ŝ"=>"s", "Ş"=>"S", "ş"=>"s", "Š"=>"S", "š"=>"s", "Ţ"=>"T", - "ţ"=>"t", "Ť"=>"T", "ť"=>"t", "Ŧ"=>"T", "ŧ"=>"t", "Ũ"=>"U", "ũ"=>"u", - "Ū"=>"U", "ū"=>"u", "Ŭ"=>"U", "ŭ"=>"u", "Ů"=>"U", "ů"=>"u", "Ű"=>"U", - "ű"=>"u", "Ų"=>"U", "ų"=>"u", "Ŵ"=>"W", "ŵ"=>"w", "Ŷ"=>"Y", "ŷ"=>"y", - "Ÿ"=>"Y", "Ź"=>"Z", "ź"=>"z", "Ż"=>"Z", "ż"=>"z", "Ž"=>"Z", "ž"=>"z" - } - def test_transliterate_should_not_change_ascii_chars (0..127).each do |byte| char = [byte].pack("U") @@ -41,10 +11,25 @@ class TransliterateTest < Test::Unit::TestCase end end - def test_should_convert_accented_chars_to_approximate_ascii_chars - APPROXIMATIONS.each do |given, expected| - assert_equal expected, ActiveSupport::Inflector.transliterate(given) + def test_transliterate_should_approximate_ascii + # create string with range of Unicode"s western characters with + # diacritics, excluding the division and multiplication signs which for + # some reason or other are floating in the middle of all the letters. + string = (0xC0..0x17E).to_a.reject {|c| [0xD7, 0xF7].include? c}.pack("U*") + string.each_char do |char| + assert_match %r{^[a-zA-Z']*$}, ActiveSupport::Inflector.transliterate(string) end end + def test_transliterate_should_work_with_custom_i18n_rules_and_uncomposed_utf8 + char = [117, 776].pack("U*") # "ü" as ASCII "u" plus COMBINING DIAERESIS + I18n.backend.store_translations(:de, :i18n => {:transliterate => {:rule => {"ü" => "ue"}}}) + I18n.locale = :de + assert_equal "ue", ActiveSupport::Inflector.transliterate(char) + end + + def test_transliterate_should_allow_a_custom_replacement_char + assert_equal "a*b", ActiveSupport::Inflector.transliterate("a索b", "*") + end + end |