smacks-apricoteatsgorilla 0.1.2 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/README.rdoc CHANGED
@@ -1,14 +1,15 @@
1
1
  = Apricot eats Gorilla
2
2
 
3
- Apricot eats Gorilla converts SOAP response messages to Ruby hashes.
4
- It's based on CobraVsMongoose but uses Hpricot instead of REXML and it
5
- also doesn't follow the BadgerFish convention.
3
+ Apricot eats Gorilla translates between XML documents and Ruby hashes.
4
+ It's based on CobraVsMongoose but uses Hpricot instead of REXML to parse
5
+ XML and it also doesn't follow the BadgerFish convention.
6
+
7
+ It's initial purpose was to convert SOAP response messages to Ruby hashes,
8
+ but it quickly evolved into a more general translation tool.
6
9
 
7
10
  == Install
8
11
 
9
- $ sudo gem sources -a http://gems.github.com
10
- $ sudo gem install smacks-apricoteatsgorilla
11
- $ sudo gem install hpricot
12
+ $ sudo gem install smacks-apricoteatsgorilla --source http://gems.github.com
12
13
 
13
14
  == Dependencies
14
15
 
@@ -16,7 +17,7 @@ also doesn't follow the BadgerFish convention.
16
17
 
17
18
  == An example
18
19
 
19
- Let's assume you receive the following SOAP response message:
20
+ Let's assume you receive the following SOAP response:
20
21
 
21
22
  <soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
22
23
  <soap:Body>
@@ -37,9 +38,9 @@ Let's assume you receive the following SOAP response message:
37
38
 
38
39
  Just pass in the raw XML string like this:
39
40
 
40
- require 'rubygems'
41
- require 'apricoteatsgorilla'
42
- hash = ApricotEatsGorilla.soap_response_to_hash(xml)
41
+ require "rubygems"
42
+ require "apricoteatsgorilla
43
+ hash = ApricotEatsGorilla.xml_to_hash(xml, "//return")
43
44
 
44
45
  And it gets converted into a nice little hash:
45
46
 
@@ -54,12 +55,13 @@ And it gets converted into a nice little hash:
54
55
 
55
56
  == The conclusion
56
57
 
57
- * It assumes a root node named "return", but you can pass in an XPath
58
- expession as the second parameter, to define a different root node.
59
- * All attributes are ignored and won't be included in the hash.
58
+ * The xml_to_hash method starts parsing the XML at the root node by default.
59
+ By calling the method with an XPath expression as second parameter, we define
60
+ a custom root node.
61
+ * Node attributes are ignored and won't be included in the hash.
60
62
  * The value of empty element nodes will be nil.
61
- * Values of "true" or "false" will be converted to boolean objects.
63
+ * Node values of "true" or "false" will be converted to boolean objects.
62
64
 
63
65
  == For more information
64
66
 
65
- Take a look at the tests to see some more examples.
67
+ Take a look at the tests for some more examples.
@@ -2,23 +2,42 @@
2
2
  require 'rubygems'
3
3
  require 'hpricot'
4
4
 
5
- # Apricot eats Gorilla converts SOAP response messages to Ruby hashes.
6
- # It's based on CobraVsMongoose but uses Hpricot instead of REXML and it
7
- # also doesn't follow the BadgerFish convention.
5
+ # Apricot eats Gorilla translates between XML documents and Ruby hashes.
6
+ # It's based on CobraVsMongoose but uses Hpricot instead of REXML to parse
7
+ # XML and it also doesn't follow the BadgerFish convention.
8
+ #
9
+ # It's initial purpose was to convert SOAP response messages to Ruby hashes,
10
+ # but it quickly evolved into a more general translation tool.
8
11
  class ApricotEatsGorilla
9
12
 
10
- # Converts a given SOAP response message into a Ruby Hash.
11
- def self.soap_response_to_hash(xml, root_node = nil)
12
- xml = prepare_xml(xml)
13
- root_node ||= "//return"
13
+ # Converts XML into a Ruby Hash. Starts parsing at root node by default.
14
+ # Call with XPath expession (Hpricot search) as second parameter to define
15
+ # a custom root node. The root node itself is not included in the Hash.
16
+ #
17
+ # E.g.
18
+ # xml = "<dude><likes>beer</likes><hates>appletini</hates></dude>"
19
+ # ApricotEatsGorilla.xml_to_hash(xml)
20
+ # # => { "hates" => "appletini", "likes" => "beer" }
21
+ def self.xml_to_hash(xml, root_node = nil)
22
+ xml = clean_xml(xml)
14
23
  doc = Hpricot.XML(xml)
15
- xml_node_to_hash doc.at(root_node)
24
+ root = root_node ? doc.at(root_node) : doc.root
25
+ xml_node_to_hash(root)
26
+ end
27
+
28
+ # Converts a Ruby Hash to XML.
29
+ #
30
+ # E.g.
31
+ # hash = { "dude" => { "likes" => "beer", "hates" => "appletini" } }
32
+ # ApricotEatsGorilla.hash_to_xml(hash)
33
+ # # => "<dude><hates>appletini</hates><likes>beer</likes></dude>"
34
+ def self.hash_to_xml(hash)
35
+ nested_data_to_xml(hash.keys.first, hash.values.first)
16
36
  end
17
37
 
18
38
  private
19
39
 
20
- # Converts XML nodes into a Ruby Hash. Recursively calls itself to grab
21
- # nested XML nodes and values.
40
+ # Converts XML nodes into a Ruby Hash.
22
41
  def self.xml_node_to_hash(node)
23
42
  this_node = {}
24
43
 
@@ -45,8 +64,43 @@ private
45
64
  this_node
46
65
  end
47
66
 
67
+ # Converts a Ruby Hash to XML.
68
+ def self.nested_data_to_xml(name, item)
69
+ children = {}
70
+ case item
71
+ when String
72
+ make_tag(name) { item }
73
+ when Array
74
+ item.map { |subitem| nested_data_to_xml(name, subitem) }.join
75
+ when Hash
76
+ make_tag(name) do
77
+ item.map { |tag, value|
78
+ case value
79
+ when String
80
+ make_tag(tag) { value }
81
+ when Array
82
+ value.map { |subitem| nested_data_to_xml(tag, subitem) }.join
83
+ when Hash
84
+ nested_data_to_xml(tag, value)
85
+ end
86
+ }.join
87
+ end
88
+ end
89
+ end
90
+
91
+ # Helper to create an XML tag. Expects a block for tag content.
92
+ # Defaults to an empty element tag in case no block was supplied.
93
+ def self.make_tag(name)
94
+ body = yield
95
+ if body && !body.empty?
96
+ "<#{name}>" << body << "</#{name}>"
97
+ else
98
+ "<#{name} />"
99
+ end
100
+ end
101
+
48
102
  # Helper to remove line breaks and whitespace between XML nodes.
49
- def self.prepare_xml(xml)
103
+ def self.clean_xml(xml)
50
104
  xml = xml.gsub(/\n+/, "")
51
105
  xml = xml.gsub(/(>)\s*(<)/, '\1\2')
52
106
  end
@@ -0,0 +1,39 @@
1
+ # -*- coding: utf-8 -*-
2
+ require 'test/unit'
3
+ require File.join(File.dirname(__FILE__), "..", "lib", "apricoteatsgorilla")
4
+
5
+ class HashToXmlTest < Test::Unit::TestCase
6
+
7
+ def test_dead_simple
8
+ hash = { "dude" => "likes beer" }
9
+ expected = "<dude>likes beer</dude>"
10
+
11
+ result = ApricotEatsGorilla.hash_to_xml(hash)
12
+ assert_equal expected, result
13
+ end
14
+
15
+ def test_nested_hash
16
+ hash = { "dude" => { "likes" => "beer", "hates" => "appletini" } }
17
+ expected = "<dude><hates>appletini</hates><likes>beer</likes></dude>"
18
+
19
+ result = ApricotEatsGorilla.hash_to_xml(hash)
20
+ assert_equal expected, result
21
+ end
22
+
23
+ def test_nested_hash_with_array
24
+ hash = { "dude" => { "likes" => [ "beer", "helicopters" ] } }
25
+ expected = "<dude><likes>beer</likes><likes>helicopters</likes></dude>"
26
+
27
+ result = ApricotEatsGorilla.hash_to_xml(hash)
28
+ assert_equal expected, result
29
+ end
30
+
31
+ def test_nested_hash_with_array_containing_a_hash
32
+ hash = { "dude" => { "likes" => [ { "beer" => "a lot" }, { "helicopters" => "a little more" } ] } }
33
+ expected = "<dude><likes><beer>a lot</beer></likes><likes><helicopters>a little more</helicopters></likes></dude>"
34
+
35
+ result = ApricotEatsGorilla.hash_to_xml(hash)
36
+ assert_equal expected, result
37
+ end
38
+
39
+ end
@@ -1,9 +1,8 @@
1
1
  # -*- coding: utf-8 -*-
2
- require 'rubygems'
3
2
  require 'test/unit'
4
3
  require File.join(File.dirname(__FILE__), "..", "lib", "apricoteatsgorilla")
5
4
 
6
- class ApricotEatsGorillaTest < Test::Unit::TestCase
5
+ class XmlToHashTest < Test::Unit::TestCase
7
6
 
8
7
  def test_soap_response_example
9
8
  xml = '<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
@@ -18,49 +17,49 @@ class ApricotEatsGorillaTest < Test::Unit::TestCase
18
17
  </ns2:authenticateResponse>
19
18
  </soap:Body>
20
19
  </soap:Envelope>'
21
- expected = {'authValue' => {'token' => 'secret', 'client' => 'example'}}
20
+ expected = { 'authValue' => { 'token' => 'secret', 'client' => 'example' } }
22
21
 
23
- result = ApricotEatsGorilla.soap_response_to_hash(xml)
22
+ result = ApricotEatsGorilla.xml_to_hash(xml, '//return')
24
23
  assert_equal expected, result
25
24
  end
26
25
 
27
26
  def test_that_boolean_string_values_are_converted_to_actual_boolean_objects
28
27
  xml = '<root><yes>true</yes><no>false</no><txt>something</txt></root>'
29
- expected = {'yes' => true, 'no' => false, 'txt' => 'something'}
28
+ expected = { 'yes' => true, 'no' => false, 'txt' => 'something' }
30
29
 
31
- result = ApricotEatsGorilla.soap_response_to_hash(xml, '//root')
30
+ result = ApricotEatsGorilla.xml_to_hash(xml)
32
31
  assert_equal expected, result
33
32
  end
34
33
 
35
34
  def test_that_empty_element_tags_are_converted_to_nil
36
35
  xml = '<contact><name>Jungle Julia</name><email /><phone/></contact>'
37
- expected = {'name' => 'Jungle Julia', 'email' => nil, 'phone' => nil}
36
+ expected = { 'name' => 'Jungle Julia', 'email' => nil, 'phone' => nil }
38
37
 
39
- result = ApricotEatsGorilla.soap_response_to_hash(xml, '//contact')
38
+ result = ApricotEatsGorilla.xml_to_hash(xml)
40
39
  assert_equal expected, result
41
40
  end
42
41
 
43
42
  def test_that_attributes_get_removed
44
43
  xml = '<todo><paint subject="chair" color="#000000">black</paint></todo>'
45
- expected = {'paint' => 'black'}
44
+ expected = { 'paint' => 'black' }
46
45
 
47
- result = ApricotEatsGorilla.soap_response_to_hash(xml, '//todo')
46
+ result = ApricotEatsGorilla.xml_to_hash(xml)
48
47
  assert_equal expected, result
49
48
  end
50
49
 
51
50
  def test_that_node_groups_with_text_values_get_converted_to_arrays
52
51
  xml = '<root><items>first</items><items>second</items></root>'
53
- expected = {'items' => ['first', 'second']}
52
+ expected = { 'items' => [ 'first', 'second' ] }
54
53
 
55
- result = ApricotEatsGorilla.soap_response_to_hash(xml, '//root')
54
+ result = ApricotEatsGorilla.xml_to_hash(xml)
56
55
  assert_equal expected, result
57
56
  end
58
57
 
59
58
  def test_that_node_groups_with_nesting_get_converted_to_arrays_with_hashes
60
59
  xml = '<root><items><name>first</name></items><items><name>second</name></items></root>'
61
- expected = {'items' => [{'name' => 'first'}, {'name' => 'second'}]}
60
+ expected = { 'items' => [ { 'name' => 'first' }, { 'name' => 'second' } ] }
62
61
 
63
- result = ApricotEatsGorilla.soap_response_to_hash(xml, '//root')
62
+ result = ApricotEatsGorilla.xml_to_hash(xml)
64
63
  assert_equal expected, result
65
64
  end
66
65
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: smacks-apricoteatsgorilla
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.2
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Daniel Harrington
@@ -22,7 +22,7 @@ dependencies:
22
22
  - !ruby/object:Gem::Version
23
23
  version: 0.6.164
24
24
  version:
25
- description: Apricot eats Gorilla converts SOAP response messages to Ruby hashes
25
+ description: Apricot eats Gorilla translates between XML documents and Ruby hashes.
26
26
  email:
27
27
  executables: []
28
28
 
@@ -59,6 +59,7 @@ rubyforge_project:
59
59
  rubygems_version: 1.2.0
60
60
  signing_key:
61
61
  specification_version: 2
62
- summary: Apricot eats Gorilla converts SOAP response messages to Ruby hashes
62
+ summary: Apricot eats Gorilla translates between XML documents and Ruby hashes.
63
63
  test_files:
64
- - tests/apricoteatsgorilla_test.rb
64
+ - tests/hash_to_xml_test.rb
65
+ - tests/xml_to_hash_test.rb