Ticket #95: hash_to_xml_rexml.diff
| File hash_to_xml_rexml.diff, 5.8 kB (added by chr..@octopod.info, 1 year ago) |
|---|
-
lib/merb/core_ext/merb_hash.rb
old new 64 64 # { "bicep" => 60 } 65 65 66 66 def from_xml( xml ) 67 undasherize_keys( Hpricot::XML( xml ).root.to_hash )67 ToHashParser.from_xml(xml) 68 68 end 69 70 private71 72 def undasherize_keys(params)73 case params.class.to_s74 when "Hash"75 params.inject({}) do |h,(k,v)|76 h[k.to_s.tr("-", "_")] = undasherize_keys(v)77 h78 end79 when "Array"80 params.map { |v| undasherize_keys(v) }81 else82 params83 end84 end85 69 end 86 70 87 71 def to_params … … 146 130 147 131 end 148 132 133 require 'rexml/parsers/streamparser' 134 require 'rexml/parsers/baseparser' 135 require 'rexml/light/node' 149 136 150 class Hpricot::Elem 151 # Converts this Hpricto::Elem to a hash. 152 # If the element contains mixed content. i.e. tags and text, the innerHTML will 153 # be used. 137 # This is a slighly modified version of the XMLUtilityNode from 138 # http://merb.devjavu.com/projects/merb/ticket/95 (has.sox@gmail.com) 139 # It's mainly just adding vowels, as I ht cd wth n vwls :) 140 # This represents the hard part of the work, all I did was change the underlying 141 # parser 142 class REXMLUtilityNode # :nodoc: 143 attr_accessor :name, :attributes, :children 144 145 def initialize(name, attributes = {}) 146 @name = name.tr("-", "_") 147 @attributes = undasherize_keys(attributes) 148 @children = [] 149 @text = false 150 end 151 152 def add_node(node) 153 @text = true if node.is_a? String 154 @children << node 155 end 156 154 157 def to_hash 155 result = {} 156 # Get any attributes of the tag out and into the hash 157 if !attributes.nil? 158 result[name].merge!( attributes ) unless result[name].nil? 159 result[name] = attributes if result[name].nil? 160 end 161 162 163 if children.detect{ |a| a.text? && a.to_s.strip.length != 0 } 164 # There is text nodes in here. 165 # Just return the innerHTML of the node, typecasted if required 166 content = translate_xml_entities( inner_html.strip ) 167 result[name] = case attributes["type"] 168 169 when "integer" then content.to_i 170 when "boolean" then content.strip == "true" 171 when "datetime" then ::Time.parse(content).utc 172 when "date" then ::Date.parse(content) 173 else content 174 end 175 176 else 177 # Get the children in on the action 158 if @text 159 return { name => typecast_value( translate_xml_entities( inner_html ) ) } 160 else 161 #change repeating groups into an array 162 # group by the first key of each element of the array to find repeating groups 163 groups = @children.group_by{ |c| c.name } 178 164 179 child_array = children.map do |child| 180 child_hash = {} 181 if !child.text? 182 child_hash = child.to_hash 183 end 184 # If there was no hash found for this child then make it nil so the compact will take it out 185 child_hash.empty? ? nil : child_hash 186 # We're grouping by the first key to see if this element has any duplicate child tag names 187 end.compact.group_by{ |child| child.keys.first } 188 189 # If any hashes in the child_array have a duplicate tag name collect these into an array 190 child_array.keys.each do |a| 191 if child_array[a].size == 1 192 # There is only one of these elements 193 # Grab it amd put it into the result 194 result[name].merge!( child_array[a].first ) 165 hash = {} 166 groups.each do |key, values| 167 if values.size == 1 168 hash.merge!( values.first ) 195 169 else 196 # There are more than one of these elements 197 # They need to be put into an array 198 result[name].merge!( { a => child_array[a].map{ |child| child[child.keys.first] }} ) 170 hash.merge!( key => values.map{ |element| element.to_hash[key] } ) 199 171 end 200 172 end 201 173 174 # merge the arrays, including attributes 175 hash.merge!( attributes ) unless attributes.empty? 176 return { name => hash } 202 177 end 203 result204 178 end 205 206 207 private 179 180 def to_s 181 self.to_html 182 end 183 184 185 def typecast_value(value) 186 return value unless attributes["type"] 187 188 case attributes["type"] 189 when "integer" then value.to_i 190 when "boolean" then value.strip == "true" 191 when "datetime" then ::Time.parse(value).utc 192 when "date" then ::Date.parse(value) 193 else value 194 end 195 end 196 208 197 def translate_xml_entities(value) 209 198 value.gsub(/</, "<"). 210 199 gsub(/>/, ">"). … … 212 201 gsub(/'/, "'"). 213 202 gsub(/&/, "&") 214 203 end 204 205 def undasherize_keys(params) 206 params.keys.each do |key, vvalue| 207 params[key.tr("-", "_")] = params.delete(key) 208 end 209 params 210 end 211 212 def inner_html 213 @children.join 214 end 215 216 def to_html 217 "<#{name}#{attributes_to_xml}>#{inner_html}</#{name}>" 218 end 219 220 def attributes_to_xml 221 attributes.keys.map do |key| 222 "#{key}='#{attributes[key]}'" 223 end.join 224 end 215 225 end 226 227 class ToHashParser # :nodoc: 228 def self.from_xml(xml) 229 stack = [] 230 parser = REXML::Parsers::BaseParser.new(xml) 231 232 while true 233 event = parser.pull 234 case event[0] 235 when :end_document 236 break 237 when :end_doctype, :start_doctype 238 # do nothing 239 when :start_element 240 stack.push REXMLUtilityNode.new(event[1], event[2]) 241 when :end_element 242 if stack.size > 1 243 temp = stack.pop 244 stack.last.add_node(temp) 245 end 246 when :text 247 stack.last.add_node(event[1]) unless event[1].strip.length == 0 248 end 249 end 250 stack.pop.to_hash 251 end 252 end
