Permalink
Browse files

Added Hash.create_from_xml(string) which will create a hash from a XM…

…L string and even typecast if possible [DHH]

git-svn-id: http://svn-commit.rubyonrails.org/rails/trunk@4453 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
  • Loading branch information...
1 parent 7c326a3 commit 36dc94a6a1e742848c5a80975b8bf5d216f54022 @dhh dhh committed Jun 16, 2006
@@ -1,14 +1,13 @@
require 'cgi'
-require 'action_controller/vendor/xml_simple'
require 'action_controller/vendor/xml_node'
# Static methods for parsing the query and request parameters that can be used in
# a CGI extension class or testing in isolation.
class CGIMethods #:nodoc:
- public
+ class << self
# Returns a hash with the pairs from the query string. The implicit hash construction that is done in
# parse_request_params is not done here.
- def CGIMethods.parse_query_parameters(query_string)
+ def parse_query_parameters(query_string)
parsed_params = {}
query_string.split(/[&;]/).each { |p|
@@ -41,7 +40,7 @@ def CGIMethods.parse_query_parameters(query_string)
# Returns the request (POST/GET) parameters in a parsed form where pairs such as "customer[address][street]" /
# "Somewhere cool!" are translated into a full hash hierarchy, like
# { "customer" => { "address" => { "street" => "Somewhere cool!" } } }
- def CGIMethods.parse_request_parameters(params)
+ def parse_request_parameters(params)
parsed_params = {}
for key, value in params
@@ -59,162 +58,103 @@ def CGIMethods.parse_request_parameters(params)
parsed_params
end
- def self.parse_formatted_request_parameters(mime_type, raw_post_data)
- params = case strategy = ActionController::Base.param_parsers[mime_type]
+ def parse_formatted_request_parameters(mime_type, raw_post_data)
+ case strategy = ActionController::Base.param_parsers[mime_type]
when Proc
strategy.call(raw_post_data)
when :xml_simple
- raw_post_data.blank? ? nil :
- typecast_xml_value(XmlSimple.xml_in(raw_post_data,
- 'forcearray' => false,
- 'forcecontent' => true,
- 'keeproot' => true,
- 'contentkey' => '__content__'))
+ raw_post_data.blank? ? {} : Hash.create_from_xml(raw_post_data)
when :yaml
YAML.load(raw_post_data)
when :xml_node
node = XmlNode.from_xml(raw_post_data)
{ node.node_name => node }
end
-
- dasherize_keys(params || {})
rescue Object => e
{ "exception" => "#{e.message} (#{e.class})", "backtrace" => e.backtrace,
"raw_post_data" => raw_post_data, "format" => mime_type }
end
- def self.typecast_xml_value(value)
- case value
- 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 == "true"
- when "datetime" then Time.parse(content)
- when "date" then Date.parse(content)
- else content
- end
- else
- value.empty? ? 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
- else
- raise "can't typecast #{value.inspect}"
- end
- end
-
- private
-
- def self.translate_xml_entities(value)
- value.gsub(/&lt;/, "<").
- gsub(/&gt;/, ">").
- gsub(/&quot;/, '"').
- gsub(/&apos;/, "'").
- gsub(/&amp;/, "&")
- end
-
- def self.dasherize_keys(params)
- case params.class.to_s
- when "Hash"
- params.inject({}) do |h,(k,v)|
- h[k.to_s.tr("-", "_")] = dasherize_keys(v)
- h
- end
- when "Array"
- params.map { |v| dasherize_keys(v) }
- else
- params
- end
- end
-
- # Splits the given key into several pieces. Example keys are 'name', 'person[name]',
- # 'person[name][first]', and 'people[]'. In each instance, an Array instance is returned.
- # 'person[name][first]' produces ['person', 'name', 'first']; 'people[]' produces ['people', '']
- def CGIMethods.split_key(key)
- if /^([^\[]+)((?:\[[^\]]*\])+)$/ =~ key
- keys = [$1]
+ private
+ # Splits the given key into several pieces. Example keys are 'name', 'person[name]',
+ # 'person[name][first]', and 'people[]'. In each instance, an Array instance is returned.
+ # 'person[name][first]' produces ['person', 'name', 'first']; 'people[]' produces ['people', '']
+ def split_key(key)
+ if /^([^\[]+)((?:\[[^\]]*\])+)$/ =~ key
+ keys = [$1]
- keys.concat($2[1..-2].split(']['))
- keys << '' if key[-2..-1] == '[]' # Have to add it since split will drop empty strings
+ keys.concat($2[1..-2].split(']['))
+ keys << '' if key[-2..-1] == '[]' # Have to add it since split will drop empty strings
- keys
- else
- [key]
+ keys
+ else
+ [key]
+ end
end
- end
- def CGIMethods.get_typed_value(value)
- # test most frequent case first
- if value.is_a?(String)
- value
- elsif value.respond_to?(:content_type) && ! value.content_type.blank?
- # Uploaded file
- unless value.respond_to?(:full_original_filename)
- class << value
- alias_method :full_original_filename, :original_filename
-
- # Take the basename of the upload's original filename.
- # This handles the full Windows paths given by Internet Explorer
- # (and perhaps other broken user agents) without affecting
- # those which give the lone filename.
- # The Windows regexp is adapted from Perl's File::Basename.
- def original_filename
- if md = /^(?:.*[:\\\/])?(.*)/m.match(full_original_filename)
- md.captures.first
- else
- File.basename full_original_filename
+ def get_typed_value(value)
+ # test most frequent case first
+ if value.is_a?(String)
+ value
+ elsif value.respond_to?(:content_type) && ! value.content_type.blank?
+ # Uploaded file
+ unless value.respond_to?(:full_original_filename)
+ class << value
+ alias_method :full_original_filename, :original_filename
+
+ # Take the basename of the upload's original filename.
+ # This handles the full Windows paths given by Internet Explorer
+ # (and perhaps other broken user agents) without affecting
+ # those which give the lone filename.
+ # The Windows regexp is adapted from Perl's File::Basename.
+ def original_filename
+ if md = /^(?:.*[:\\\/])?(.*)/m.match(full_original_filename)
+ md.captures.first
+ else
+ File.basename full_original_filename
+ end
end
end
end
- end
- # Return the same value after overriding original_filename.
- value
+ # Return the same value after overriding original_filename.
+ value
- elsif value.respond_to?(:read)
- # Value as part of a multipart request
- result = value.read
- value.rewind
- result
- elsif value.class == Array
- value.collect { |v| CGIMethods.get_typed_value(v) }
- else
- # other value (neither string nor a multipart request)
- value.to_s
+ elsif value.respond_to?(:read)
+ # Value as part of a multipart request
+ result = value.read
+ value.rewind
+ result
+ elsif value.class == Array
+ value.collect { |v| get_typed_value(v) }
+ else
+ # other value (neither string nor a multipart request)
+ value.to_s
+ end
end
- end
- PARAMS_HASH_RE = /^([^\[]+)(\[.*\])?(.)?.*$/
- def CGIMethods.get_levels(key)
- all, main, bracketed, trailing = PARAMS_HASH_RE.match(key).to_a
- if main.nil?
- []
- elsif trailing
- [key]
- elsif bracketed
- [main] + bracketed.slice(1...-1).split('][')
- else
- [main]
+ PARAMS_HASH_RE = /^([^\[]+)(\[.*\])?(.)?.*$/
+ def get_levels(key)
+ all, main, bracketed, trailing = PARAMS_HASH_RE.match(key).to_a
+ if main.nil?
+ []
+ elsif trailing
+ [key]
+ elsif bracketed
+ [main] + bracketed.slice(1...-1).split('][')
+ else
+ [main]
+ end
end
- end
- def CGIMethods.build_deep_hash(value, hash, levels)
- if levels.length == 0
- value
- elsif hash.nil?
- { levels.first => CGIMethods.build_deep_hash(value, nil, levels[1..-1]) }
- else
- hash.update({ levels.first => CGIMethods.build_deep_hash(value, hash[levels.first], levels[1..-1]) })
+ def build_deep_hash(value, hash, levels)
+ if levels.length == 0
+ value
+ elsif hash.nil?
+ { levels.first => build_deep_hash(value, nil, levels[1..-1]) }
+ else
+ hash.update({ levels.first => build_deep_hash(value, hash[levels.first], levels[1..-1]) })
+ end
end
- end
+ end
end
@@ -148,13 +148,6 @@ def test_entities_unescaped_as_xml_simple
assert_equal %(<foo "bar's" & friends>), @controller.params[:data]
end
- def test_dasherized_keys_as_yaml
- ActionController::Base.param_parsers[Mime::YAML] = :yaml
- process('POST', 'application/x-yaml', "---\nfirst-key:\n sub-key: ...\n", true)
- assert_equal 'action, controller, first_key(sub_key), full', @controller.response.body
- assert_equal "...", @controller.params[:first_key][:sub_key]
- end
-
def test_typecast_as_yaml
ActionController::Base.param_parsers[Mime::YAML] = :yaml
process('POST', 'application/x-yaml', <<-YAML)
View
@@ -1,5 +1,18 @@
*SVN*
+* Added Hash.create_from_xml(string) which will create a hash from a XML string and even typecast if possible [DHH]. Example:
+
+ Hash.create_from_xml <<-EOT
+ <note>
+ <title>This is a note</title>
+ <created-at type="date">2004-10-10</created-at>
+ </note>
+ EOT
+
+ ...would return:
+
+ { :note => { :title => "This is a note", :created_at => Date.new(2004, 10, 10) } }
+
* Added Jim Weirich's excellent FlexMock class to vendor (Copyright 2003, 2004 by Jim Weirich (jim@weriichhouse.org)) -- it's not automatically required, though, so require 'flexmock' is still necessary [DHH]
* Fixed that Module#alias_method_chain should work with both foo? foo! and foo at the same time #4954 [anna@wota.jp]
@@ -1,4 +1,5 @@
require 'date'
+require 'xml_simple'
module ActiveSupport #:nodoc:
module CoreExtensions #:nodoc:
@@ -20,6 +21,10 @@ module Conversions
"binary" => Proc.new { |binary| Base64.encode64(binary) }
}
+ def self.included(klass)
+ klass.extend(ClassMethods)
+ end
+
def to_xml(options = {})
options[:indent] ||= 2
options.reverse_merge!({ :builder => Builder::XmlMarkup.new(:indent => options[:indent]),
@@ -70,6 +75,71 @@ def to_xml(options = {})
end
end
+
+ module ClassMethods
+ def create_from_xml(xml)
+ # TODO: Refactor this into something much cleaner that doesn't rely on XmlSimple
+ undasherize_keys(typecast_xml_value(XmlSimple.xml_in(xml,
+ 'forcearray' => false,
+ 'forcecontent' => true,
+ 'keeproot' => true,
+ 'contentkey' => '__content__')
+ ))
+ 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 == "true"
+ when "datetime" then ::Time.parse(content).utc
+ when "date" then ::Date.parse(content)
+ else content
+ end
+ else
+ value.empty? ? 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
+ else
+ raise "can't typecast #{value.inspect}"
+ end
+ end
+
+ def translate_xml_entities(value)
+ value.gsub(/&lt;/, "<").
+ gsub(/&gt;/, ">").
+ gsub(/&quot;/, '"').
+ gsub(/&apos;/, "'").
+ gsub(/&amp;/, "&")
+ 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
+ end
end
end
end
@@ -1,12 +1,12 @@
require File.dirname(__FILE__) + '/../../inflector' unless defined? Inflector
+
module ActiveSupport #:nodoc:
module CoreExtensions #:nodoc:
module String #:nodoc:
# String inflections define new methods on the String class to transform names for different purposes.
# For instance, you can figure out the name of a database from the name of a class.
# "ScaleScore".tableize => "scale_scores"
module Inflections
-
# Returns the plural form of the word in the string.
#
# Examples
Oops, something went wrong.

0 comments on commit 36dc94a

Please sign in to comment.