lib/apricoteatsgorilla.rb in smacks-apricoteatsgorilla-0.2.8 vs lib/apricoteatsgorilla.rb in smacks-apricoteatsgorilla-0.3.2

- old
+ new

@@ -1,87 +1,165 @@ -# -*- coding: utf-8 -*- -require 'rubygems' -require 'hpricot' +require "rubygems" +require "hpricot" -# Apricot eats Gorilla is a helper for working with XML and SOAP messages. -# It's based on CobraVsMongoose but without REXML and the BadgerFish convention. -# Also it offers some extras for working with SOAP messages. +# Apricot eats Gorilla is a SOAP communication helper. +# +# It translates between SOAP response messages (XML) and Ruby Hashes and may +# be used to build a SOAP request envelope. It is based on CobraVsMongoose but +# uses Hpricot instead of REXML and doesn't follow the BadgerFish convention. +# +# == Translating an XML String into a Ruby Hash +# +# xml = "<apricot><eats>Gorilla</eats></apricot>" +# ApricotEatsGorilla[xml] +# # => { :eats => "Gorilla" } +# +# == Translating a Ruby Hash into an XML String +# +# hash = { :apricot => { :eats => "Gorilla" } } +# ApricotEatsGorilla[hash] +# # => "<apricot><eats>Gorilla</eats></apricot>" +# +# == Creating a SOAP request envelope +# +# ApricotEatsGorilla.soap_envelope { "<authenticate>me</authenticate>" } +# +# # => '<env:Envelope xmlns:env="http://schemas.xmlsoap.org/soap/envelope/"> +# # => <env:Body> +# # => <authenticate>me</authenticate> +# # => </env:Body> +# # => </env:Envelope>' class ApricotEatsGorilla - class << self + class << self # Class methods - # Flag to enable optional sorting of Hash keys. Especially useful for - # comparing expected values with actual results while testing. + # Flag to enable optional sorting of Hash keys. attr_accessor :sort_keys - # 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. + # Shortcut method for translating between XML Strings and Ruby Hashes. + # Delegates to xml_to_hash in case +source+ is of type String or delegates + # to hash_to_xml in case +source+ is of type Hash. Returns nil otherwise. # - # xml = "<dude><likes>beer</likes><hates>appletini</hates></dude>" - # ApricotEatsGorilla.xml_to_hash(xml) - # # => { "hates" => "appletini", "likes" => "beer" } + # ==== Parameters + # + # * +source+ - The XML String or Ruby Hash to translate. + # * +root_node+ - Optional. Custom root node to start parsing the given XML. + def [](source, root_node = nil) + case source + when String + xml_to_hash(source, root_node) + when Hash + hash_to_xml(source) + else + nil + end + end + + # Converts a given +xml+ String into a Ruby Hash. Starts parsing at root + # node by default. The optional +root_node+ parameter can be used to specify + # a custom root node to start parsing at via XPath (Hpricot search). + # The root node itself won't be included in the Hash. + # + # ==== Parameters + # + # * +xml+ - The XML String to translate into a Ruby Hash. + # * +root_node+ - Optional. Custom root node to start parsing the given XML. + # + # ==== Examples + # + # xml = "<apricot><eats>Gorilla</eats></apricot>" + # ApricotEatsGorilla[xml] + # # => { :eats => "Gorilla" } + # + # xml = "<apricot><eats><lotsOf>Gorillas</lotsOf></eats></apricot>" + # ApricotEatsGorilla[xml, "//eats"] + # # => { :lots_of => "Gorillas" } 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 - # Converts a Hash to XML. - # - # hash = { "dude" => { "likes" => "beer", "hates" => "appletini" } } - # ApricotEatsGorilla.hash_to_xml(hash) - # # => "<dude><hates>appletini</hates><likes>beer</likes></dude>" + # Converts a given +hash+ into an XML String. + # + # ==== Parameters + # + # * +hash+ - The Ruby Hash to translate into an XML String. + # + # ==== Examples + # + # hash = { :apricot => { :eats => "Gorilla" } } + # ApricotEatsGorilla[hash] + # # => "<apricot><eats>Gorilla</eats></apricot>" + # + # hash = { :apricot => { :eats => [ "Gorillas", "Snakes" ] } } + # ApricotEatsGorilla[hash] + # # => "<apricot><eats>Gorillas</eats><eats>Snakes</eats></apricot>" def hash_to_xml(hash) nested_data_to_xml(hash.keys.first, hash.values.first) end - # Builds a SOAP envelope and includes the content from an expected block - # into the envelope body. Pass in a Hash of namespaces and their URI to - # set custom namespaces. + # Builds a SOAP request envelope and includes the content from a given + # +block+ into the envelope body. Accepts a Hash of +namespaces+ and their + # corresponding URI for specifying custom namespaces. # - # <env:Envelope xmlns:env="http://schemas.xmlsoap.org/soap/envelope/"> - # <env:Body> - # yield - # </env:Body> - # </env:Envelope> + # ==== Parameters + # + # * +namespaces+ - A Hash of namespaces and their corresponding URI. + # + # ==== Examples + # + # ApricotEatsGorilla.soap_envelope { "<authenticate>me</authenticate>" } + # + # # => '<env:Envelope xmlns:env="http://schemas.xmlsoap.org/soap/envelope/"> + # # => <env:Body> + # # => <authenticate>me</authenticate> + # # => </env:Body> + # # => </env:Envelope>' + # + # ApricotEatsGorilla.soap_envelope :wsdl => "http://example.com" do + # "<authenticate>me</authenticate>" + # end + # + # # => '<env:Envelope + # # => xmlns:wsdl="http://example.com" + # # => xmlns:env="http://schemas.xmlsoap.org/soap/envelope/"> + # # => <env:Body> + # # => <authenticate>me</authenticate> + # # => </env:Body> + # # => </env:Envelope>' def soap_envelope(namespaces = {}) namespaces["env"] = "http://schemas.xmlsoap.org/soap/envelope/" tag("env:Envelope", namespaces) do tag("env:Body") do yield if block_given? end end end - # Creates an XML tag. Expects a block for tag content. - # Defaults to an empty element tag in case no block was supplied. - def tag(name, attributes = {}) - return "<#{name} />" unless block_given? - - attr = opt_order(attributes).map { |k, v| %Q( xmlns:#{k}="#{v}") }.to_s - body = (yield && !yield.empty?) ? yield : "" - "<#{name}#{attr}>" << body << "</#{name}>" - end - private - # Converts XML into a Hash. - def xml_node_to_hash(node) + # Actual implementation for xml_to_hash. Takes and iterates through a given + # Hpricot +element+ and returns a Ruby Hash equal to the given content. + # + # ==== Parameters + # + # * +element+ - The Hpricot element to translate into a Ruby Hash. + def xml_node_to_hash(element) this_node = {} - node.each_child do |child| + element.each_child do |child| # hpricot 0.6.1 returns an empty array, while 0.8 returns nil 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, booleanize(child.children.first.inner_text) + key, value = child.name, booleanize(child.children.first.to_html) else key, value = child.name, xml_node_to_hash(child) end key = remove_namespace(key) - key = to_snake_case(key) + key = to_snake_case(key).intern current = this_node[key] case current when Array this_node[key] << value when nil @@ -91,11 +169,17 @@ end end this_node end - # Converts a Hash to XML. + # Actual implementation for hash_to_xml. Takes a Hash key +name+ and a + # value +item+ and returns an XML String equal to the given content. + # + # ==== Parameters + # + # * +name+ - A Hash key to translate into an XML String. + # * +item+ - A Hash value to translate into an XML String. def nested_data_to_xml(name, item) case item when String tag(name) { item } when Array @@ -114,54 +198,55 @@ }.join end end end - # Returns a sorted version of a given Hash in case :sort_keys is enabled. - def opt_order(hash) - return hash unless sort_keys - hash.sort_by { |kv| kv.first } + # Creates an XML tag. Expects a block for tag content. Defaults to an empty + # element tag in case no block was supplied. + # + # ==== Parameters + # + # * +name+ - The name of the XML tag. + # * +attributes+ - Optional. Hash of attributes for the XML tag. + def tag(name, attributes = {}) + return "<#{name} />" unless block_given? + + attr = opt_order(attributes).map { |k, v| %Q( xmlns:#{k}="#{v}") }.to_s + body = (yield && !yield.empty?) ? yield : "" + "<#{name}#{attr}>" << body << "</#{name}>" end - # Removes line breaks and whitespace between XML tags. + # Removes line breaks and whitespace between tags from a given +xml+ String. def clean_xml(xml) xml = xml.gsub(/\n+/, "") xml.gsub(/(>)\s*(<)/, '\1\2') end - # Removes namespaces from XML tags. + # Removes the namespace from a given XML +tag+. def remove_namespace(tag) tag.sub(/.+:(.+)/, '\1') end - # Converts CamelCase and lowerCamelCase to snake_case. + # Converts a given +string+ from CamelCase/lowerCamelCase to snake_case. def to_snake_case(string) string = string.gsub(/[A-Z]+/, '\1_\0').downcase string = string[1, string.length-1] if string[0, 1] == "_" string end - # Converts "true" and "false" strings to boolean values. - # Returns the original string in case it doesn't match "true" or "false". + # Checks to see if a given +string+ matches "true" or "false" and converts + # these values to actual Boolean objects. Returns the original string in + # case it does not match "true" or "false". def booleanize(string) return true if string == "true" return false if string == "false" string end - end -end + # Returns a sorted version of a given +hash+ if :sort_keys was enabled. + def opt_order(hash) + return hash unless sort_keys + hash.sort_by { |kv| kv.first } + 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. -# Returns nil otherwise. -def ApricotEatsGorilla(source, root_node = nil) - case source - when String - ApricotEatsGorilla.xml_to_hash(source, root_node) - when Hash - ApricotEatsGorilla.hash_to_xml(source) - else - nil end end \ No newline at end of file