lib/apricoteatsgorilla.rb in smacks-apricoteatsgorilla-0.2.4 vs lib/apricoteatsgorilla.rb in smacks-apricoteatsgorilla-0.2.5

- old
+ new

@@ -8,111 +8,123 @@ # # Its initial purpose was to convert SOAP response messages to Hashes, # but it quickly evolved into a more general translation tool. class ApricotEatsGorilla - # Converts XML into a Hash. Starts parsing at root node by default. - # Call with XPath expession (Hpricot search) as second parameter to define - # a custom root node. The root node itself will not be included in the Hash. - # - # E.g. - # xml = "<dude><likes>beer</likes><hates>appletini</hates></dude>" - # ApricotEatsGorilla.xml_to_hash(xml) - # # => { "hates" => "appletini", "likes" => "beer" } - def self.xml_to_hash(xml, root_node = nil) - xml = clean_xml(xml) - doc = Hpricot.XML(xml) - root = root_node ? doc.at(root_node) : doc.root - xml_node_to_hash(root) - end + class << self + attr_accessor :sort_keys - # Converts a Hash to XML. - # - # E.g. - # hash = { "dude" => { "likes" => "beer", "hates" => "appletini" } } - # ApricotEatsGorilla.hash_to_xml(hash) - # # => "<dude><hates>appletini</hates><likes>beer</likes></dude>" - def self.hash_to_xml(hash) - nested_data_to_xml(hash.keys.first, hash.values.first) - end + # Converts XML into a Hash. Starts parsing at root node by default. + # Call with XPath expession (Hpricot search) as second parameter to define + # a custom root node. The root node itself will not be included in the Hash. + # + # E.g. + # xml = "<dude><likes>beer</likes><hates>appletini</hates></dude>" + # ApricotEatsGorilla.xml_to_hash(xml) + # # => { "hates" => "appletini", "likes" => "beer" } + def xml_to_hash(xml, root_node = nil) + xml = clean_xml(xml) + doc = Hpricot.XML(xml) + root = root_node ? doc.at(root_node) : doc.root + xml_node_to_hash(root) + end -private + # Converts a Hash to XML. + # + # E.g. + # hash = { "dude" => { "likes" => "beer", "hates" => "appletini" } } + # ApricotEatsGorilla.hash_to_xml(hash) + # # => "<dude><hates>appletini</hates><likes>beer</likes></dude>" + def hash_to_xml(hash) + nested_data_to_xml(hash.keys.first, hash.values.first) + end - # Converts XML into a Hash. - def self.xml_node_to_hash(node) - this_node = {} + private - node.each_child do |child| - if child.children.nil? - key, value = child.name, nil - elsif child.children.size == 1 && child.children.first.text? - key, value = child.name, string_to_bool?(child.children.first.raw_string) - else - key, value = child.name, xml_node_to_hash(child) - end + # Converts XML into a Hash. + def xml_node_to_hash(node) + this_node = {} - current = this_node[key] - case current - when Array: - this_node[key] << value - when nil - this_node[key] = value + node.each_child do |child| + if child.children.nil? || child.children.empty? + key, value = child.name, nil + elsif child.children.size == 1 && child.children.first.text? + key, value = child.name, string_to_bool?(child.children.first.inner_text) else - this_node[key] = [current.dup, value] + key, value = child.name, xml_node_to_hash(child) + end + + current = this_node[key] + case current + when Array: + this_node[key] << value + when nil + this_node[key] = value + else + this_node[key] = [current.dup, value] + end end + + this_node end - this_node - end + # Converts a Hash to XML. + def nested_data_to_xml(name, item) + case item + when String + make_tag(name) { item } + when Array + item.map { |subitem| nested_data_to_xml(name, subitem) }.join + when Hash + make_tag(name) do + opt_order(item).map { |tag, value| + case value + when String + make_tag(tag) { value } + when Array + value.map { |subitem| nested_data_to_xml(tag, subitem) }.join + when Hash + nested_data_to_xml(tag, value) + end + }.join + end + end + end - # Converts a Hash to XML. - def self.nested_data_to_xml(name, item) - case item - when String - make_tag(name) { item } - when Array - item.map { |subitem| nested_data_to_xml(name, subitem) }.join - when Hash - make_tag(name) do - item.map { |tag, value| - case value - when String - make_tag(tag) { value } - when Array - value.map { |subitem| nested_data_to_xml(tag, subitem) }.join - when Hash - nested_data_to_xml(tag, value) - end - }.join + # Helper to create an XML tag. Expects a block for tag content. + # Defaults to an empty element tag in case no block was supplied. + def make_tag(name) + body = yield + if body && !body.empty? + "<#{name}>" << body << "</#{name}>" + else + "<#{name} />" end end - end - # Helper to create an XML tag. Expects a block for tag content. - # Defaults to an empty element tag in case no block was supplied. - def self.make_tag(name) - body = yield - if body && !body.empty? - "<#{name}>" << body << "</#{name}>" - else - "<#{name} />" + def opt_order(hash) + if sort_keys + hash.sort_by{ |kv| kv.first } + else + hash + end end - end - - # Helper to remove line breaks and whitespace between XML tags. - def self.clean_xml(xml) - xml = xml.gsub(/\n+/, "") - xml = xml.gsub(/(>)\s*(<)/, '\1\2') - end - # Helper to convert "true" and "false" strings to boolean values. - # Returns the original string in case it doesn't match "true" or "false". - def self.string_to_bool?(string) - return true if string == "true" - return false if string == "false" - string - end + # Helper to remove line breaks and whitespace between XML tags. + def clean_xml(xml) + xml = xml.gsub(/\n+/, "") + xml = xml.gsub(/(>)\s*(<)/, '\1\2') + end + # Helper to convert "true" and "false" strings to boolean values. + # Returns the original string in case it doesn't match "true" or "false". + def string_to_bool?(string) + return true if string == "true" + return false if string == "false" + string + end + + end end # Shortcut method for translating between XML documents and Hashes. # Calls xml_to_hash in case the first parameter is of type String. # Calls hash_to_xml in case the first parameter is of type Hash. \ No newline at end of file