smacks-apricoteatsgorilla 0.1.2 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|