Changeset 350

Show
Ignore:
Timestamp:
07/28/07 14:47:33 (1 year ago)
Author:
e.@brainspl.at
Message:

Commiting the REXML Hash.from_xml code. Nice work to everyone involved, i love's me some benchmarks. Closes #95

Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • trunk/lib/merb/core_ext/merb_hash.rb

    r340 r350  
    6565       
    6666      def from_xml( xml ) 
    67         undasherize_keys(  Hpricot::XML( xml ).root.to_hash  )    
    68       end 
    69  
    70     private 
    71      
    72       def undasherize_keys(params) 
    73         case params.class.to_s 
    74         when "Hash" 
    75           params.inject({}) do |h,(k,v)| 
    76             h[k.to_s.tr("-", "_")] = undasherize_keys(v) 
    77             h 
    78           end 
    79         when "Array" 
    80           params.map { |v| undasherize_keys(v) } 
    81         else 
    82           params 
    83         end 
    84      end 
     67        ToHashParser.from_xml(xml)   
     68      end 
    8569  end 
    8670   
     
    147131end   
    148132 
    149  
    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. 
     133require 'rexml/parsers/streamparser' 
     134require 'rexml/parsers/baseparser' 
     135require 'rexml/light/node' 
     136 
     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 
     142class 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   
    154157  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 
     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 } 
     164 
     165      hash = {}   
     166      groups.each do |key, values| 
     167        if values.size == 1 
     168          hash.merge!( values.first )  
     169        else 
     170          hash.merge!( key => values.map{ |element| element.to_hash[key] } ) 
     171        end 
     172      end 
     173 
     174      # merge the arrays, including attributes 
     175      hash.merge!( attributes ) unless attributes.empty? 
     176      return { name => hash } 
     177    end 
     178  end 
     179 
     180  def to_s  
     181    self.to_html 
     182  end 
     183 
     184 
     185  def typecast_value(value) 
     186    return value unless attributes["type"]  
    161187     
    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 
    178  
    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 ) 
    195         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] }} ) 
    199         end 
    200       end 
    201        
    202     end 
    203     result 
    204   end 
    205    
    206    
    207   private 
     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 
    208197  def translate_xml_entities(value) 
    209198    value.gsub(/&lt;/,   "<"). 
     
    213202          gsub(/&amp;/,  "&") 
    214203  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   
    215225end 
     226 
     227class 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      
     252end