class Hash class << self def from_xml(xml) # TODO: Refactor this into something much cleaner that doesn't rely on XmlSimple undasherize_keys(typecast_xml_value(XmlSimple.xml_in(xml, 'forcearray' => false, 'forcecontent' => true, 'keeproot' => true, 'contentkey' => '__content__') )) end private def typecast_xml_value(value) case value.class.to_s when "Hash" if value.has_key?("__content__") content = translate_xml_entities(value["__content__"]) case value["type"] when "integer" then content.to_i when "boolean" then content.strip == "true" when "datetime" then ::Time.parse(content).utc when "date" then ::Date.parse(content) else content end else (value.blank? || value['type'] || value['nil'] == 'true') ? nil : value.inject({}) do |h,(k,v)| h[k] = typecast_xml_value(v) h end end when "Array" value.map! { |i| typecast_xml_value(i) } case value.length when 0 then nil when 1 then value.first else value end when "String" value else raise "can't typecast #{value.inspect}" end end def translate_xml_entities(value) value.gsub(/</, "<"). gsub(/>/, ">"). gsub(/"/, '"'). gsub(/'/, "'"). gsub(/&/, "&") end def undasherize_keys(params) case params.class.to_s when "Hash" params.inject({}) do |h,(k,v)| h[k.to_s.tr("-", "_")] = undasherize_keys(v) h end when "Array" params.map { |v| undasherize_keys(v) } else params end end end def with_indifferent_access MerbHash.new(self) end def to_params result = '' stack = [] each do |key, value| Hash === value ? stack << [key, value] : result << "#{key}=#{value}&" end stack.each do |parent, hash| hash.each do |key, value| if Hash === value stack << ["#{parent}[#{key}]", value] else result << "#{parent}[#{key}]=#{value}&" end end end result.chop end # lets through the keys in the argument # >> {:one => 1, :two => 2, :three => 3}.pass(:one) # => {:one=>1} def pass(*keys) self.reject { |k,v| ! keys.include?(k) } end alias only pass # blocks the keys in the arguments # >> {:one => 1, :two => 2, :three => 3}.block(:one) # => {:two=>2, :three=>3} def block(*keys) self.reject { |k,v| keys.include?(k) } end alias except block end # like HashWithIndifferentAccess from ActiveSupport. class MerbHash < Hash def initialize(constructor = {}) if constructor.is_a?(Hash) super() update(constructor) else super(constructor) end end def default(key) self[key.to_s] if key.is_a?(Symbol) end alias_method :regular_writer, :[]= unless method_defined?(:regular_writer) alias_method :regular_update, :update unless method_defined?(:regular_update) alias_method :u, :regular_update def []=(key, value) regular_writer(convert_key(key), convert_value(value)) end def update(other_hash) other_hash.each_pair { |key, value| regular_writer(convert_key(key), convert_value(value)) } self end alias_method :merge!, :update def key?(key) super(convert_key(key)) end alias_method :include?, :key? alias_method :has_key?, :key? alias_method :member?, :key? def fetch(key, *extras) super(convert_key(key), *extras) end def values_at(*indices) indices.collect {|key| self[convert_key(key)]} end def dup MerbHash.new(self) end def merge(hash) self.dup.update(hash) end def delete(key) super(convert_key(key)) end # allow merbhash.key to work the same as merbhash[key] def method_missing(m,*a) m.to_s =~ /=$/?self[$`]=a[0]:a==[]?self[m]:raise(NoMethodError,"#{m}") end protected def convert_key(key) key.kind_of?(Symbol) ? key.to_s : key end def convert_value(value) value.is_a?(Hash) ? value.with_indifferent_access : value end end