Changeset 1196
- Timestamp:
- 01/08/08 02:35:45 (11 months ago)
- Files:
-
- trunk/lib/merb/core_ext/hash.rb (modified) (6 diffs)
- trunk/spec/merb/core_ext (added)
- trunk/spec/merb/core_ext/class_spec.rb (added)
- trunk/spec/merb/core_ext/enumerable_spec.rb (added)
- trunk/spec/merb/core_ext/hash_spec.rb (added)
- trunk/spec/merb/core_ext/inflector_spec.rb (added)
- trunk/spec/merb/core_ext/kernel_spec.rb (added)
- trunk/spec/merb/core_ext/numeric_spec.rb (added)
- trunk/spec/merb/core_ext/object_spec.rb (added)
- trunk/spec/merb/core_ext/string_spec.rb (added)
- trunk/spec/merb/core_ext/symbol_spec.rb (added)
- trunk/spec/merb/core_ext_spec.rb (deleted)
Legend:
- Unmodified
- Added
- Removed
- Modified
- Copied
- Moved
trunk/lib/merb/core_ext/hash.rb
r1184 r1196 1 # require 'hpricot'1 # require 'hpricot' 2 2 3 3 class Hash 4 5 4 class << self 6 # Converts valid XML into a Ruby Hash structure. 7 # <tt>xml</tt>:: A string representation of valid XML 8 # 9 # == Typecasting 10 # Typecasting is performed on elements that have a "<tt>type</tt>" attribute of 11 # <tt>integer</tt>:: 12 # <tt>boolean</tt>:: anything other than "true" evaluates to false 13 # <tt>datetime</tt>:: Returns a Time object. See +Time+ documentation for valid Time strings 14 # <tt>date</tt>:: Returns a Date object. See +Date+ documentation for valid Date strings 15 # 16 # Keys are automatically converted to +snake_case+ 17 # 18 # == Caveats 19 # * Mixed content tags are assumed to be text and any xml tags are kept as a String 20 # * Any attributes other than type on a node containing a text node will be discarded 21 # 22 # == Examples 23 # 24 # === Standard 25 # <user gender='m'> 26 # <age type='integer'>35</age> 27 # <name>Home Simpson</name> 28 # <dob type='date'>1988-01-01</dob> 29 # <joined-at type='datetime'>2000-04-28 23:01</joined-at> 30 # <is-cool type='boolean'>true</is-cool> 31 # </user> 32 # 33 # evaluates to 34 # 35 # { "user" => 36 # { "gender" => "m", 37 # "age" => 35, 38 # "name" => "Home Simpson", 39 # "dob" => DateObject( 1998-01-01 ), 40 # "joined_at" => TimeObject( 2000-04-28 23:01), 41 # "is_cool" => true 42 # } 43 # } 44 # 45 # === Mixed Content 46 # <story> 47 # A Quick <em>brown</em> Fox 48 # </story> 49 # 50 # evaluates to 51 # { "story" => "A Quick <em>brown</em> Fox" } 52 # 53 # === Attributes other than type on a node containing text 54 # <story is-good='fasle'> 55 # A Quick <em>brown</em> Fox 56 # </story> 57 # 58 # evaluates to 59 # { "story" => "A Quick <em>brown</em> Fox" } 60 # 61 # <bicep unit='inches' type='integer'>60</bicep> 62 # 63 # evaluates with a typecast to an integer. But ignores the unit attribute 64 # { "bicep" => 60 } 65 66 def from_xml( xml ) 67 ToHashParser.from_xml(xml) 68 end 5 # Converts valid XML into a Ruby Hash structure. 6 # <tt>xml</tt>:: A string representation of valid XML 7 # 8 # == Typecasting 9 # Typecasting is performed on elements that have a "<tt>type</tt>" attribute of 10 # <tt>integer</tt>:: 11 # <tt>boolean</tt>:: anything other than "true" evaluates to false 12 # <tt>datetime</tt>:: Returns a Time object. See +Time+ documentation for valid Time strings 13 # <tt>date</tt>:: Returns a Date object. See +Date+ documentation for valid Date strings 14 # 15 # Keys are automatically converted to +snake_case+ 16 # 17 # == Caveats 18 # * Mixed content tags are assumed to be text and any xml tags are kept as a String 19 # * Any attributes other than type on a node containing a text node will be discarded 20 # 21 # == Examples 22 # 23 # === Standard 24 # <user gender='m'> 25 # <age type='integer'>35</age> 26 # <name>Home Simpson</name> 27 # <dob type='date'>1988-01-01</dob> 28 # <joined-at type='datetime'>2000-04-28 23:01</joined-at> 29 # <is-cool type='boolean'>true</is-cool> 30 # </user> 31 # 32 # evaluates to 33 # 34 # { "user" => 35 # { "gender" => "m", 36 # "age" => 35, 37 # "name" => "Home Simpson", 38 # "dob" => DateObject( 1998-01-01 ), 39 # "joined_at" => TimeObject( 2000-04-28 23:01), 40 # "is_cool" => true 41 # } 42 # } 43 # 44 # === Mixed Content 45 # <story> 46 # A Quick <em>brown</em> Fox 47 # </story> 48 # 49 # evaluates to 50 # { "story" => "A Quick <em>brown</em> Fox" } 51 # 52 # === Attributes other than type on a node containing text 53 # <story is-good='fasle'> 54 # A Quick <em>brown</em> Fox 55 # </story> 56 # 57 # evaluates to 58 # { "story" => "A Quick <em>brown</em> Fox" } 59 # 60 # <bicep unit='inches' type='integer'>60</bicep> 61 # 62 # evaluates with a typecast to an integer. But ignores the unit attribute 63 # { "bicep" => 60 } 64 def from_xml( xml ) 65 ToHashParser.from_xml(xml) 66 end 69 67 end 70 68 71 69 # convert this hash into a Mash for string or symbol key access 72 70 def to_mash 73 hash = Mash.new(self) 74 hash.default = self.default 75 hash 76 end 77 78 # convert this hash to a query string param 79 # {:name => "Bob", :address => {:street => '111 Ruby Ave.', :city => 'Ruby Central', :phones => ['111-111-1111', '222-222-2222']}}.to_params 71 hash = Mash.new(self) 72 hash.default = default 73 hash 74 end 75 76 # Convert this hash to a query string: 77 # 78 # { :name => "Bob", 79 # :address => { 80 # :street => '111 Ruby Ave.', 81 # :city => 'Ruby Central', 82 # :phones => ['111-111-1111', '222-222-2222'] 83 # } 84 # }.to_params 80 85 # #=> "name=Bob&address[city]=Ruby Central&address[phones]=111-111-1111222-222-2222&address[street]=111 Ruby Ave." 86 # 81 87 def to_params 82 result= ''88 params = '' 83 89 stack = [] 84 85 each do |key, value| 86 Hash === value ? stack << [key, value] : result << "#{key}=#{value}&" 87 end 88 90 91 each do |k, v| 92 if v.is_a?(Hash) 93 stack << [k,v] 94 else 95 params << "#{k}=#{v}&" 96 end 97 end 98 89 99 stack.each do |parent, hash| 90 hash.each do |k ey, value|91 if Hash === value92 stack << ["#{parent}[#{k ey}]", value]100 hash.each do |k, v| 101 if v.is_a?(Hash) 102 stack << ["#{parent}[#{k}]", v] 93 103 else 94 result << "#{parent}[#{key}]=#{value}&"104 params << "#{parent}[#{k}]=#{v}&" 95 105 end 96 106 end 97 107 end 98 result.chop 99 end 100 101 # lets through the keys in the argument 102 # {:one => 1, :two => 2, :three => 3}.pass(:one) 103 # #=> {:one=>1} 108 109 params.chop! # trailing & 110 params 111 end 112 113 # Returns a new Hash with only the selected keys: 114 # 115 # { :one => 1, :two => 2, :three => 3 }.only(:one) 116 # #=> { :one => 1 } 104 117 def only(*allowed) 105 self.reject { |k,v| ! allowed.include?(k) } 106 end 107 108 # blocks the keys in the arguments 109 # {:one => 1, :two => 2, :three => 3}.block(:one) 110 # #=> {:two=>2, :three=>3} 118 reject { |k,v| !allowed.include?(k) } 119 end 120 121 # Returns a new hash without the selected keys: 122 # 123 # { :one => 1, :two => 2, :three => 3 }.except(:one) 124 # #=> { :two => 2, :three => 3 } 125 # 111 126 def except(*rejected) 112 self.reject { |k,v| rejected.include?(k) }113 end 114 127 reject { |k,v| rejected.include?(k) } 128 end 129 115 130 # Converts the hash into xml attributes 131 # 116 132 # { :one => "ONE", "two"=>"TWO" }.to_xml_attributes 117 133 # #=> 'one="ONE" two="TWO"' 134 # 118 135 def to_xml_attributes 119 136 map do |k,v| 120 137 %{#{k.to_s.camelize.sub(/^(.{1,1})/) { |m| m.downcase }}="#{v}"} 121 end.join( " ")138 end.join(' ') 122 139 end 123 140 124 141 alias_method :to_html_attributes, :to_xml_attributes 125 142 126 143 # Adds the given class symbol or string to the hash in the 127 144 # :class key. This will add a html class if there are already any existing 128 145 # or create the key and add this as the first class 129 # 130 # Example 146 # 131 147 # @hash[:class] #=> nil 132 148 # @hash.add_html_class!(:selected) #=> @hash[:class] == "selected" 133 # 149 # 134 150 # @hash.add_html_class!("class1 class2") #=> @hash[:class] == "selected class1 class2" 151 # 135 152 def add_html_class!(html_class) 136 153 if self[:class] … … 140 157 end 141 158 end 142 143 # Destructively convert all keys to symbols recursively. 159 160 # Destructively convert all keys which respond_to?(:to_sym) to symbols. 161 # Works recursively if given nested hashes. 162 # 144 163 # { 'one' => 1, 'two' => 2 }.symbolize_keys! 145 164 # #=> { :one => 1, :two => 2 } 165 # 146 166 def symbolize_keys! 147 167 each do |k,v| 148 sym = k.respond_to?(:to_sym) ? k.to_sym : k149 self[sym] = Hash === v ? v.symbolize_keys! : v150 delete(k) unless k == sym168 sym = k.respond_to?(:to_sym) ? k.to_sym : k 169 self[sym] = Hash === v ? v.symbolize_keys! : v 170 delete(k) unless k == sym 151 171 end 152 172 self 153 173 end 154 155 # Destructively convert each key to an uppercase string non-recursively. 156 # {:name => "Bob", "age" => 12, "nick" => "Bobinator"}.environmentize_keys! 157 # #=> {"NAME"=>"Bob", "NICK"=>"Bobinator", "AGE"=>12} 174 175 # Destructively and non-recursively convert each key to an uppercase string. 176 # 177 # { :name => "Bob", "age" => 12, "nick" => "Bobinator" }.environmentize_keys! 178 # #=> { "NAME" => "Bob", "NICK" => "Bobinator", "AGE" => 12 } 179 # 158 180 def environmentize_keys! 159 181 keys.each do |key| … … 162 184 self 163 185 end 164 165 def method_missing(m,*a) #:nodoc: 166 m.to_s =~ /=$/ ? self[$`]=a[0] : a==[] ? self[m] : raise(NoMethodError,"#{m}") 167 end 168 169 def respond_to?(method, include_private=false) 186 187 def method_missing(method, *args) #:nodoc: 188 if method.to_s =~ /=$/ 189 self[$`] = args[0] 190 elsif args.empty? 191 self[method] 192 else 193 raise NoMethodError, method.to_s 194 end 195 end 196 197 def respond_to?(method, include_private = false) 170 198 return true if keys.include?(method) 171 199 super(method, include_private) 172 200 end 173 174 end 201 end 175 202 176 203 require 'rexml/parsers/streamparser' … … 205 232 # group by the first key of each element of the array to find repeating groups 206 233 groups = @children.group_by{ |c| c.name } 207 208 hash = {} 234 235 hash = {} 209 236 groups.each do |key, values| 210 237 if values.size == 1 211 hash.merge! ( values.first )238 hash.merge! values.first 212 239 else 213 hash.merge! ( key => values.map{ |element| element.to_hash[key] } )240 hash.merge! key => values.map { |element| element.to_hash[key] } 214 241 end 215 242 end 216 243 217 244 # merge the arrays, including attributes 218 hash.merge!( attributes ) unless attributes.empty? 219 return { name => hash } 220 end 221 end 222 223 def to_s 224 self.to_html 225 end 226 227 245 hash.merge! attributes unless attributes.empty? 246 247 { name => hash } 248 end 249 end 250 228 251 def typecast_value(value) 229 return value unless attributes["type"] 252 return value unless attributes["type"] 230 253 231 254 case attributes["type"] … … 237 260 end 238 261 end 239 262 240 263 def translate_xml_entities(value) 241 264 value.gsub(/</, "<"). … … 245 268 gsub(/&/, "&") 246 269 end 247 248 def undasherize_keys(params)249 params.keys.each do |key, vvalue|250 params[key.tr("-", "_")] = params.delete(key)251 end252 params253 end 254 270 271 def undasherize_keys(params) 272 params.keys.each do |key, vvalue| 273 params[key.tr("-", "_")] = params.delete(key) 274 end 275 params 276 end 277 255 278 def inner_html 256 279 @children.join 257 280 end 258 259 def to_html 281 282 def to_html 260 283 "<#{name}#{attributes.to_xml_attributes}>#{inner_html}</#{name}>" 284 end 285 286 def to_s 287 to_html 261 288 end 262 289 end 263 290 264 291 class ToHashParser # :nodoc: 265 def self.from_xml(xml)266 stack = []267 parser = REXML::Parsers::BaseParser.new(xml)268 269 while true270 event = parser.pull271 case event[0]272 when :end_document273 break274 when :end_doctype, :start_doctype275 # do nothing276 when :start_element277 stack.push REXMLUtilityNode.new(event[1], event[2])278 when :end_element279 if stack.size > 1280 temp = stack.pop281 stack.last.add_node(temp)282 end283 when :text, :cdata284 stack.last.add_node(event[1]) unless event[1].strip.length == 0285 end286 end287 stack.pop.to_hash288 end 292 def self.from_xml(xml) 293 stack = [] 294 parser = REXML::Parsers::BaseParser.new(xml) 295 296 while true 297 event = parser.pull 298 case event[0] 299 when :end_document 300 break 301 when :end_doctype, :start_doctype 302 # do nothing 303 when :start_element 304 stack.push REXMLUtilityNode.new(event[1], event[2]) 305 when :end_element 306 if stack.size > 1 307 temp = stack.pop 308 stack.last.add_node(temp) 309 end 310 when :text, :cdata 311 stack.last.add_node(event[1]) unless event[1].strip.length == 0 312 end 313 end 314 stack.pop.to_hash 315 end 289 316 end
