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 +17 -15
- data/lib/apricoteatsgorilla.rb +65 -11
- data/tests/hash_to_xml_test.rb +39 -0
- data/tests/{apricoteatsgorilla_test.rb → xml_to_hash_test.rb} +13 -14
- metadata +5 -4
data/README.rdoc
CHANGED
@@ -1,14 +1,15 @@
|
|
1
1
|
= Apricot eats Gorilla
|
2
2
|
|
3
|
-
Apricot eats Gorilla
|
4
|
-
It's based on CobraVsMongoose but uses Hpricot instead of REXML
|
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
|
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
|
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
|
41
|
-
require
|
42
|
-
hash = ApricotEatsGorilla.
|
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
|
-
*
|
58
|
-
|
59
|
-
|
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
|
-
*
|
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
|
67
|
+
Take a look at the tests for some more examples.
|
data/lib/apricoteatsgorilla.rb
CHANGED
@@ -2,23 +2,42 @@
|
|
2
2
|
require 'rubygems'
|
3
3
|
require 'hpricot'
|
4
4
|
|
5
|
-
# Apricot eats Gorilla
|
6
|
-
# It's based on CobraVsMongoose but uses Hpricot instead of REXML
|
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
|
11
|
-
|
12
|
-
|
13
|
-
|
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
|
-
|
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.
|
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.
|
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
|
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.
|
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.
|
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.
|
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.
|
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.
|
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.
|
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.
|
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
|
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
|
62
|
+
summary: Apricot eats Gorilla translates between XML documents and Ruby hashes.
|
63
63
|
test_files:
|
64
|
-
- tests/
|
64
|
+
- tests/hash_to_xml_test.rb
|
65
|
+
- tests/xml_to_hash_test.rb
|