Changeset 4453
- Timestamp:
- 06/16/06 10:07:13 (4 years ago)
- Files:
-
- trunk/actionpack/lib/action_controller/cgi_ext/cgi_methods.rb (modified) (6 diffs)
- trunk/actionpack/test/controller/webservice_test.rb (modified) (1 diff)
- trunk/activesupport/CHANGELOG (modified) (1 diff)
- trunk/activesupport/lib/active_support/core_ext/hash/conversions.rb (modified) (3 diffs)
- trunk/activesupport/lib/active_support/core_ext/string/inflections.rb (modified) (2 diffs)
- trunk/activesupport/lib/active_support/vendor/xml_simple.rb (moved) (moved from trunk/actionpack/lib/action_controller/vendor/xml_simple.rb)
- trunk/activesupport/test/core_ext/hash_ext_test.rb (modified) (1 diff)
Legend:
- Unmodified
- Added
- Removed
- Modified
- Copied
- Moved
trunk/actionpack/lib/action_controller/cgi_ext/cgi_methods.rb
r4410 r4453 1 1 require 'cgi' 2 require 'action_controller/vendor/xml_simple'3 2 require 'action_controller/vendor/xml_node' 4 3 … … 6 5 # a CGI extension class or testing in isolation. 7 6 class CGIMethods #:nodoc: 8 public7 class << self 9 8 # Returns a hash with the pairs from the query string. The implicit hash construction that is done in 10 9 # parse_request_params is not done here. 11 def CGIMethods.parse_query_parameters(query_string)10 def parse_query_parameters(query_string) 12 11 parsed_params = {} 13 12 … … 42 41 # "Somewhere cool!" are translated into a full hash hierarchy, like 43 42 # { "customer" => { "address" => { "street" => "Somewhere cool!" } } } 44 def CGIMethods.parse_request_parameters(params)43 def parse_request_parameters(params) 45 44 parsed_params = {} 46 45 … … 60 59 end 61 60 62 def self.parse_formatted_request_parameters(mime_type, raw_post_data)63 params =case strategy = ActionController::Base.param_parsers[mime_type]61 def parse_formatted_request_parameters(mime_type, raw_post_data) 62 case strategy = ActionController::Base.param_parsers[mime_type] 64 63 when Proc 65 64 strategy.call(raw_post_data) 66 65 when :xml_simple 67 raw_post_data.blank? ? nil : 68 typecast_xml_value(XmlSimple.xml_in(raw_post_data, 69 'forcearray' => false, 70 'forcecontent' => true, 71 'keeproot' => true, 72 'contentkey' => '__content__')) 66 raw_post_data.blank? ? {} : Hash.create_from_xml(raw_post_data) 73 67 when :yaml 74 68 YAML.load(raw_post_data) … … 77 71 { node.node_name => node } 78 72 end 79 80 dasherize_keys(params || {})81 73 rescue Object => e 82 74 { "exception" => "#{e.message} (#{e.class})", "backtrace" => e.backtrace, … … 84 76 end 85 77 86 def self.typecast_xml_value(value)87 case value88 when Hash89 if value.has_key?("__content__")90 content = translate_xml_entities(value["__content__"])91 case value["type"]92 when "integer" then content.to_i93 when "boolean" then content == "true"94 when "datetime" then Time.parse(content)95 when "date" then Date.parse(content)96 else content97 end78 private 79 # Splits the given key into several pieces. Example keys are 'name', 'person[name]', 80 # 'person[name][first]', and 'people[]'. In each instance, an Array instance is returned. 81 # 'person[name][first]' produces ['person', 'name', 'first']; 'people[]' produces ['people', ''] 82 def split_key(key) 83 if /^([^\[]+)((?:\[[^\]]*\])+)$/ =~ key 84 keys = [$1] 85 86 keys.concat($2[1..-2].split('][')) 87 keys << '' if key[-2..-1] == '[]' # Have to add it since split will drop empty strings 88 89 keys 98 90 else 99 value.empty? ? nil : value.inject({}) do |h,(k,v)| 100 h[k] = typecast_xml_value(v) 101 h 102 end 91 [key] 103 92 end 104 when Array105 value.map! { |i| typecast_xml_value(i) }106 case value.length107 when 0 then nil108 when 1 then value.first109 else value110 end111 else112 raise "can't typecast #{value.inspect}"113 93 end 114 end 94 95 def get_typed_value(value) 96 # test most frequent case first 97 if value.is_a?(String) 98 value 99 elsif value.respond_to?(:content_type) && ! value.content_type.blank? 100 # Uploaded file 101 unless value.respond_to?(:full_original_filename) 102 class << value 103 alias_method :full_original_filename, :original_filename 115 104 116 private 117 118 def self.translate_xml_entities(value) 119 value.gsub(/</, "<"). 120 gsub(/>/, ">"). 121 gsub(/"/, '"'). 122 gsub(/'/, "'"). 123 gsub(/&/, "&") 124 end 125 126 def self.dasherize_keys(params) 127 case params.class.to_s 128 when "Hash" 129 params.inject({}) do |h,(k,v)| 130 h[k.to_s.tr("-", "_")] = dasherize_keys(v) 131 h 132 end 133 when "Array" 134 params.map { |v| dasherize_keys(v) } 135 else 136 params 137 end 138 end 139 140 # Splits the given key into several pieces. Example keys are 'name', 'person[name]', 141 # 'person[name][first]', and 'people[]'. In each instance, an Array instance is returned. 142 # 'person[name][first]' produces ['person', 'name', 'first']; 'people[]' produces ['people', ''] 143 def CGIMethods.split_key(key) 144 if /^([^\[]+)((?:\[[^\]]*\])+)$/ =~ key 145 keys = [$1] 146 147 keys.concat($2[1..-2].split('][')) 148 keys << '' if key[-2..-1] == '[]' # Have to add it since split will drop empty strings 149 150 keys 151 else 152 [key] 153 end 154 end 155 156 def CGIMethods.get_typed_value(value) 157 # test most frequent case first 158 if value.is_a?(String) 159 value 160 elsif value.respond_to?(:content_type) && ! value.content_type.blank? 161 # Uploaded file 162 unless value.respond_to?(:full_original_filename) 163 class << value 164 alias_method :full_original_filename, :original_filename 165 166 # Take the basename of the upload's original filename. 167 # This handles the full Windows paths given by Internet Explorer 168 # (and perhaps other broken user agents) without affecting 169 # those which give the lone filename. 170 # The Windows regexp is adapted from Perl's File::Basename. 171 def original_filename 172 if md = /^(?:.*[:\\\/])?(.*)/m.match(full_original_filename) 173 md.captures.first 174 else 175 File.basename full_original_filename 105 # Take the basename of the upload's original filename. 106 # This handles the full Windows paths given by Internet Explorer 107 # (and perhaps other broken user agents) without affecting 108 # those which give the lone filename. 109 # The Windows regexp is adapted from Perl's File::Basename. 110 def original_filename 111 if md = /^(?:.*[:\\\/])?(.*)/m.match(full_original_filename) 112 md.captures.first 113 else 114 File.basename full_original_filename 115 end 176 116 end 177 117 end 178 118 end 119 120 # Return the same value after overriding original_filename. 121 value 122 123 elsif value.respond_to?(:read) 124 # Value as part of a multipart request 125 result = value.read 126 value.rewind 127 result 128 elsif value.class == Array 129 value.collect { |v| get_typed_value(v) } 130 else 131 # other value (neither string nor a multipart request) 132 value.to_s 179 133 end 134 end 135 136 PARAMS_HASH_RE = /^([^\[]+)(\[.*\])?(.)?.*$/ 137 def get_levels(key) 138 all, main, bracketed, trailing = PARAMS_HASH_RE.match(key).to_a 139 if main.nil? 140 [] 141 elsif trailing 142 [key] 143 elsif bracketed 144 [main] + bracketed.slice(1...-1).split('][') 145 else 146 [main] 147 end 148 end 180 149 181 # Return the same value after overriding original_filename. 182 value 183 184 elsif value.respond_to?(:read) 185 # Value as part of a multipart request 186 result = value.read 187 value.rewind 188 result 189 elsif value.class == Array 190 value.collect { |v| CGIMethods.get_typed_value(v) } 191 else 192 # other value (neither string nor a multipart request) 193 value.to_s 150 def build_deep_hash(value, hash, levels) 151 if levels.length == 0 152 value 153 elsif hash.nil? 154 { levels.first => build_deep_hash(value, nil, levels[1..-1]) } 155 else 156 hash.update({ levels.first => build_deep_hash(value, hash[levels.first], levels[1..-1]) }) 157 end 194 158 end 195 end 196 197 PARAMS_HASH_RE = /^([^\[]+)(\[.*\])?(.)?.*$/ 198 def CGIMethods.get_levels(key) 199 all, main, bracketed, trailing = PARAMS_HASH_RE.match(key).to_a 200 if main.nil? 201 [] 202 elsif trailing 203 [key] 204 elsif bracketed 205 [main] + bracketed.slice(1...-1).split('][') 206 else 207 [main] 208 end 209 end 210 211 def CGIMethods.build_deep_hash(value, hash, levels) 212 if levels.length == 0 213 value 214 elsif hash.nil? 215 { levels.first => CGIMethods.build_deep_hash(value, nil, levels[1..-1]) } 216 else 217 hash.update({ levels.first => CGIMethods.build_deep_hash(value, hash[levels.first], levels[1..-1]) }) 218 end 219 end 159 end 220 160 end trunk/actionpack/test/controller/webservice_test.rb
r3936 r4453 147 147 XML 148 148 assert_equal %(<foo "bar's" & friends>), @controller.params[:data] 149 end150 151 def test_dasherized_keys_as_yaml152 ActionController::Base.param_parsers[Mime::YAML] = :yaml153 process('POST', 'application/x-yaml', "---\nfirst-key:\n sub-key: ...\n", true)154 assert_equal 'action, controller, first_key(sub_key), full', @controller.response.body155 assert_equal "...", @controller.params[:first_key][:sub_key]156 149 end 157 150 trunk/activesupport/CHANGELOG
r4452 r4453 1 1 *SVN* 2 3 * Added Hash.create_from_xml(string) which will create a hash from a XML string and even typecast if possible [DHH]. Example: 4 5 Hash.create_from_xml <<-EOT 6 <note> 7 <title>This is a note</title> 8 <created-at type="date">2004-10-10</created-at> 9 </note> 10 EOT 11 12 ...would return: 13 14 { :note => { :title => "This is a note", :created_at => Date.new(2004, 10, 10) } } 2 15 3 16 * 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] trunk/activesupport/lib/active_support/core_ext/hash/conversions.rb
r4432 r4453 1 1 require 'date' 2 require 'xml_simple' 2 3 3 4 module ActiveSupport #:nodoc: … … 20 21 "binary" => Proc.new { |binary| Base64.encode64(binary) } 21 22 } 23 24 def self.included(klass) 25 klass.extend(ClassMethods) 26 end 22 27 23 28 def to_xml(options = {}) … … 71 76 72 77 end 78 79 module ClassMethods 80 def create_from_xml(xml) 81 # TODO: Refactor this into something much cleaner that doesn't rely on XmlSimple 82 undasherize_keys(typecast_xml_value(XmlSimple.xml_in(xml, 83 'forcearray' => false, 84 'forcecontent' => true, 85 'keeproot' => true, 86 'contentkey' => '__content__') 87 )) 88 end 89 90 private 91 def typecast_xml_value(value) 92 case value.class.to_s 93 when "Hash" 94 if value.has_key?("__content__") 95 content = translate_xml_entities(value["__content__"]) 96 case value["type"] 97 when "integer" then content.to_i 98 when "boolean" then content == "true" 99 when "datetime" then ::Time.parse(content).utc 100 when "date" then ::Date.parse(content) 101 else content 102 end 103 else 104 value.empty? ? nil : value.inject({}) do |h,(k,v)| 105 h[k] = typecast_xml_value(v) 106 h 107 end 108 end 109 when "Array" 110 value.map! { |i| typecast_xml_value(i) } 111 case value.length 112 when 0 then nil 113 when 1 then value.first 114 else value 115 end 116 else 117 raise "can't typecast #{value.inspect}" 118 end 119 end 120 121 def translate_xml_entities(value) 122 value.gsub(/</, "<"). 123 gsub(/>/, ">"). 124 gsub(/"/, '"'). 125 gsub(/'/, "'"). 126 gsub(/&/, "&") 127 end 128 129 def undasherize_keys(params) 130 case params.class.to_s 131 when "Hash" 132 params.inject({}) do |h,(k,v)| 133 h[k.to_s.tr("-", "_")] = undasherize_keys(v) 134 h 135 end 136 when "Array" 137 params.map { |v| undasherize_keys(v) } 138 else 139 params 140 end 141 end 142 end 73 143 end 74 144 end trunk/activesupport/lib/active_support/core_ext/string/inflections.rb
r4428 r4453 1 1 require File.dirname(__FILE__) + '/../../inflector' unless defined? Inflector 2 2 3 module ActiveSupport #:nodoc: 3 4 module CoreExtensions #:nodoc: … … 7 8 # "ScaleScore".tableize => "scale_scores" 8 9 module Inflections 9 10 10 # Returns the plural form of the word in the string. 11 11 # trunk/activesupport/test/core_ext/hash_ext_test.rb
r4413 r4453 281 281 end 282 282 283 def test_single_record_from_xml 284 topic_xml = <<-EOT 285 <topic> 286 <title>The First Topic</title> 287 <author-name>David</author-name> 288 <id type="integer">1</id> 289 <approved type="boolean">false</approved> 290 <replies-count type="integer">0</replies-count> 291 <written-on type="date">2003-07-16</written-on> 292 <viewed-at type="datetime">2003-07-16T09:28:00+0000</viewed-at> 293 <content>Have a nice day</content> 294 <author-email-address>david@loudthinking.com</author-email-address> 295 <parent-id></parent-id> 296 </topic> 297 EOT 298 299 expected_topic_hash = { 300 :title => "The First Topic", 301 :author_name => "David", 302 :id => 1, 303 :approved => false, 304 :replies_count => 0, 305 :written_on => Date.new(2003, 7, 16), 306 :viewed_at => Time.utc(2003, 7, 16, 9, 28), 307 :content => "Have a nice day", 308 :author_email_address => "david@loudthinking.com", 309 :parent_id => nil 310 }.stringify_keys 311 312 assert_equal expected_topic_hash, Hash.create_from_xml(topic_xml)["topic"] 313 end 314 315 def test_multiple_records_from_xml 316 topics_xml = <<-EOT 317 <topics> 318 <topic> 319 <title>The First Topic</title> 320 <author-name>David</author-name> 321 <id type="integer">1</id> 322 <approved type="boolean">false</approved> 323 <replies-count type="integer">0</replies-count> 324 <written-on type="date">2003-07-16</written-on> 325 <viewed-at type="datetime">2003-07-16T09:28:00+0000</viewed-at> 326 <content>Have a nice day</content> 327 <author-email-address>david@loudthinking.com</author-email-address> 328 <parent-id></parent-id> 329 </topic> 330 <topic> 331 <title>The Second Topic</title> 332 <author-name>Jason</author-name> 333 <id type="integer">1</id> 334 <approved type="boolean">false</approved> 335 <replies-count type="integer">0</replies-count> 336 <written-on type="date">2003-07-16</written-on> 337 <viewed-at type="datetime">2003-07-16T09:28:00+0000</viewed-at> 338 <content>Have a nice day</content> 339 <author-email-address>david@loudthinking.com</author-email-address> 340 <parent-id></parent-id> 341 </topic> 342 </topics> 343 EOT 344 345 expected_topic_hash = { 346 :title => "The First Topic", 347 :author_name => "David", 348 :id => 1, 349 :approved => false, 350 :replies_count => 0, 351 :written_on => Date.new(2003, 7, 16), 352 :viewed_at => Time.utc(2003, 7, 16, 9, 28), 353 :content => "Have a nice day", 354 :author_email_address => "david@loudthinking.com", 355 :parent_id => nil 356 }.stringify_keys 357 358 assert_equal expected_topic_hash, Hash.create_from_xml(topics_xml)["topics"]["topic"].first 359 end 283 360 end