| 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. |
|---|
| | 133 | require 'rexml/parsers/streamparser' |
|---|
| | 134 | require 'rexml/parsers/baseparser' |
|---|
| | 135 | require '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 |
|---|
| | 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 | |
|---|
| 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"] |
|---|
| 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 | |
|---|