smacks-apricoteatsgorilla 0.2.8 → 0.3.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,68 +1,37 @@
1
1
  = Apricot eats Gorilla
2
2
 
3
- Apricot eats Gorilla is a helper for working with XML and SOAP messages.
4
- It's based on CobraVsMongoose but without REXML and the BadgerFish convention.
5
- Also it offers some extras for working with SOAP messages.
3
+ Apricot eats Gorilla is a SOAP communication helper.
4
+
5
+ It translates between SOAP response messages (XML) and Ruby Hashes and may
6
+ be used to build a SOAP request envelope. It is based on CobraVsMongoose but
7
+ uses Hpricot instead of REXML and doesn't follow the BadgerFish convention.
6
8
 
7
9
  == Install
8
10
 
9
- $ sudo gem install smacks-apricoteatsgorilla --source http://gems.github.com
11
+ $ gem install smacks-apricoteatsgorilla -s http://gems.github.com
10
12
 
11
13
  == Dependencies
12
14
 
13
15
  Hpricot 0.6.164 (also available for JRuby)
14
16
 
15
- == An example
16
-
17
- Let's assume you receive the following SOAP response:
18
-
19
- xml = '<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
20
- <soap:Body>
21
- <ns2:findCustomerByIdResponse xmlns:ns2="http://v1_0.ws.example.com/">
22
- <return>
23
- <empty>false</empty>
24
- <customer>
25
- <id>449810</id>
26
- <title xsi:nil="true" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"/>
27
- <name>Jungle Julia</name>
28
- <email>jj@example.com</email>
29
- <address />
30
- </customer>
31
- </return>
32
- </ns2:findCustomerByIdResponse>
33
- </soap:Body>
34
- </soap:Envelope>'
35
-
36
- Just pass in the raw XML string:
37
-
38
- require "rubygems"
39
- require "apricoteatsgorilla
40
- hash = ApricotEatsGorilla.xml_to_hash(xml, "//return")
41
-
42
- Or use the shortcut:
43
-
44
- hash = ApricotEatsGorilla(xml, "//return")
17
+ == Translating an XML String into a Ruby Hash
45
18
 
46
- And it gets converted into a nice little Hash:
19
+ xml = "<apricot><eats>Gorilla</eats></apricot>"
20
+ ApricotEatsGorilla[xml]
21
+ # => { :eats => "Gorilla" }
47
22
 
48
- "empty" => false,
49
- "customer" => {
50
- "address" => nil,
51
- "name" => "Jungle Julia",
52
- "title" => nil,
53
- "id" => "449810",
54
- "email" => "jj@example.com"
55
- }
23
+ == Translating a Ruby Hash into an XML String
56
24
 
57
- == Conclusions
25
+ hash = { :apricot => { :eats => "Gorilla" } }
26
+ ApricotEatsGorilla[hash]
27
+ # => "<apricot><eats>Gorilla</eats></apricot>"
58
28
 
59
- * The xml_to_hash method starts parsing the XML at the root node by default.
60
- By calling the method with an XPath expression as second parameter, we're
61
- able to define a custom root node.
62
- * Node attributes are ignored and won't be included in the hash.
63
- * The value of empty element nodes will be nil.
64
- * Node values of "true" or "false" will be converted to boolean objects.
29
+ == Creating a SOAP request envelope
65
30
 
66
- == More information
31
+ ApricotEatsGorilla.soap_envelope { "<authenticate>me</authenticate>" }
67
32
 
68
- Take a look at the tests for some more examples.
33
+ # => '<env:Envelope xmlns:env="http://schemas.xmlsoap.org/soap/envelope/">
34
+ # => <env:Body>
35
+ # => <authenticate>me</authenticate>
36
+ # => </env:Body>
37
+ # => </env:Envelope>'
@@ -1,24 +1,77 @@
1
- # -*- coding: utf-8 -*-
2
- require 'rubygems'
3
- require 'hpricot'
4
-
5
- # Apricot eats Gorilla is a helper for working with XML and SOAP messages.
6
- # It's based on CobraVsMongoose but without REXML and the BadgerFish convention.
7
- # Also it offers some extras for working with SOAP messages.
1
+ require "rubygems"
2
+ require "hpricot"
3
+
4
+ # Apricot eats Gorilla is a SOAP communication helper.
5
+ #
6
+ # It translates between SOAP response messages (XML) and Ruby Hashes and may
7
+ # be used to build a SOAP request envelope. It is based on CobraVsMongoose but
8
+ # uses Hpricot instead of REXML and doesn't follow the BadgerFish convention.
9
+ #
10
+ # == Translating an XML String into a Ruby Hash
11
+ #
12
+ # xml = "<apricot><eats>Gorilla</eats></apricot>"
13
+ # ApricotEatsGorilla[xml]
14
+ # # => { :eats => "Gorilla" }
15
+ #
16
+ # == Translating a Ruby Hash into an XML String
17
+ #
18
+ # hash = { :apricot => { :eats => "Gorilla" } }
19
+ # ApricotEatsGorilla[hash]
20
+ # # => "<apricot><eats>Gorilla</eats></apricot>"
21
+ #
22
+ # == Creating a SOAP request envelope
23
+ #
24
+ # ApricotEatsGorilla.soap_envelope { "<authenticate>me</authenticate>" }
25
+ #
26
+ # # => '<env:Envelope xmlns:env="http://schemas.xmlsoap.org/soap/envelope/">
27
+ # # => <env:Body>
28
+ # # => <authenticate>me</authenticate>
29
+ # # => </env:Body>
30
+ # # => </env:Envelope>'
8
31
  class ApricotEatsGorilla
9
- class << self
32
+ class << self # Class methods
10
33
 
11
- # Flag to enable optional sorting of Hash keys. Especially useful for
12
- # comparing expected values with actual results while testing.
34
+ # Flag to enable optional sorting of Hash keys.
13
35
  attr_accessor :sort_keys
14
36
 
15
- # Converts XML into a Hash. Starts parsing at root node by default.
16
- # Call with XPath expession (Hpricot search) as second parameter to define
17
- # a custom root node. The root node itself will not be included in the Hash.
37
+ # Shortcut method for translating between XML Strings and Ruby Hashes.
38
+ # Delegates to xml_to_hash in case +source+ is of type String or delegates
39
+ # to hash_to_xml in case +source+ is of type Hash. Returns nil otherwise.
40
+ #
41
+ # ==== Parameters
18
42
  #
19
- # xml = "<dude><likes>beer</likes><hates>appletini</hates></dude>"
20
- # ApricotEatsGorilla.xml_to_hash(xml)
21
- # # => { "hates" => "appletini", "likes" => "beer" }
43
+ # * +source+ - The XML String or Ruby Hash to translate.
44
+ # * +root_node+ - Optional. Custom root node to start parsing the given XML.
45
+ def [](source, root_node = nil)
46
+ case source
47
+ when String
48
+ xml_to_hash(source, root_node)
49
+ when Hash
50
+ hash_to_xml(source)
51
+ else
52
+ nil
53
+ end
54
+ end
55
+
56
+ # Converts a given +xml+ String into a Ruby Hash. Starts parsing at root
57
+ # node by default. The optional +root_node+ parameter can be used to specify
58
+ # a custom root node to start parsing at via XPath (Hpricot search).
59
+ # The root node itself won't be included in the Hash.
60
+ #
61
+ # ==== Parameters
62
+ #
63
+ # * +xml+ - The XML String to translate into a Ruby Hash.
64
+ # * +root_node+ - Optional. Custom root node to start parsing the given XML.
65
+ #
66
+ # ==== Examples
67
+ #
68
+ # xml = "<apricot><eats>Gorilla</eats></apricot>"
69
+ # ApricotEatsGorilla[xml]
70
+ # # => { :eats => "Gorilla" }
71
+ #
72
+ # xml = "<apricot><eats><lotsOf>Gorillas</lotsOf></eats></apricot>"
73
+ # ApricotEatsGorilla[xml, "//eats"]
74
+ # # => { :lots_of => "Gorillas" }
22
75
  def xml_to_hash(xml, root_node = nil)
23
76
  xml = clean_xml(xml)
24
77
  doc = Hpricot.XML(xml)
@@ -26,24 +79,54 @@ class ApricotEatsGorilla
26
79
  xml_node_to_hash(root)
27
80
  end
28
81
 
29
- # Converts a Hash to XML.
30
- #
31
- # hash = { "dude" => { "likes" => "beer", "hates" => "appletini" } }
32
- # ApricotEatsGorilla.hash_to_xml(hash)
33
- # # => "<dude><hates>appletini</hates><likes>beer</likes></dude>"
82
+ # Converts a given +hash+ into an XML String.
83
+ #
84
+ # ==== Parameters
85
+ #
86
+ # * +hash+ - The Ruby Hash to translate into an XML String.
87
+ #
88
+ # ==== Examples
89
+ #
90
+ # hash = { :apricot => { :eats => "Gorilla" } }
91
+ # ApricotEatsGorilla[hash]
92
+ # # => "<apricot><eats>Gorilla</eats></apricot>"
93
+ #
94
+ # hash = { :apricot => { :eats => [ "Gorillas", "Snakes" ] } }
95
+ # ApricotEatsGorilla[hash]
96
+ # # => "<apricot><eats>Gorillas</eats><eats>Snakes</eats></apricot>"
34
97
  def hash_to_xml(hash)
35
98
  nested_data_to_xml(hash.keys.first, hash.values.first)
36
99
  end
37
100
 
38
- # Builds a SOAP envelope and includes the content from an expected block
39
- # into the envelope body. Pass in a Hash of namespaces and their URI to
40
- # set custom namespaces.
101
+ # Builds a SOAP request envelope and includes the content from a given
102
+ # +block+ into the envelope body. Accepts a Hash of +namespaces+ and their
103
+ # corresponding URI for specifying custom namespaces.
104
+ #
105
+ # ==== Parameters
106
+ #
107
+ # * +namespaces+ - A Hash of namespaces and their corresponding URI.
108
+ #
109
+ # ==== Examples
41
110
  #
42
- # <env:Envelope xmlns:env="http://schemas.xmlsoap.org/soap/envelope/">
43
- # <env:Body>
44
- # yield
45
- # </env:Body>
46
- # </env:Envelope>
111
+ # ApricotEatsGorilla.soap_envelope { "<authenticate>me</authenticate>" }
112
+ #
113
+ # # => '<env:Envelope xmlns:env="http://schemas.xmlsoap.org/soap/envelope/">
114
+ # # => <env:Body>
115
+ # # => <authenticate>me</authenticate>
116
+ # # => </env:Body>
117
+ # # => </env:Envelope>'
118
+ #
119
+ # ApricotEatsGorilla.soap_envelope :wsdl => "http://example.com" do
120
+ # "<authenticate>me</authenticate>"
121
+ # end
122
+ #
123
+ # # => '<env:Envelope
124
+ # # => xmlns:wsdl="http://example.com"
125
+ # # => xmlns:env="http://schemas.xmlsoap.org/soap/envelope/">
126
+ # # => <env:Body>
127
+ # # => <authenticate>me</authenticate>
128
+ # # => </env:Body>
129
+ # # => </env:Envelope>'
47
130
  def soap_envelope(namespaces = {})
48
131
  namespaces["env"] = "http://schemas.xmlsoap.org/soap/envelope/"
49
132
  tag("env:Envelope", namespaces) do
@@ -53,33 +136,28 @@ class ApricotEatsGorilla
53
136
  end
54
137
  end
55
138
 
56
- # Creates an XML tag. Expects a block for tag content.
57
- # Defaults to an empty element tag in case no block was supplied.
58
- def tag(name, attributes = {})
59
- return "<#{name} />" unless block_given?
60
-
61
- attr = opt_order(attributes).map { |k, v| %Q( xmlns:#{k}="#{v}") }.to_s
62
- body = (yield && !yield.empty?) ? yield : ""
63
- "<#{name}#{attr}>" << body << "</#{name}>"
64
- end
65
-
66
139
  private
67
140
 
68
- # Converts XML into a Hash.
69
- def xml_node_to_hash(node)
141
+ # Actual implementation for xml_to_hash. Takes and iterates through a given
142
+ # Hpricot +element+ and returns a Ruby Hash equal to the given content.
143
+ #
144
+ # ==== Parameters
145
+ #
146
+ # * +element+ - The Hpricot element to translate into a Ruby Hash.
147
+ def xml_node_to_hash(element)
70
148
  this_node = {}
71
- node.each_child do |child|
149
+ element.each_child do |child|
72
150
  # hpricot 0.6.1 returns an empty array, while 0.8 returns nil
73
151
  if child.children.nil? || child.children.empty?
74
152
  key, value = child.name, nil
75
153
  elsif child.children.size == 1 && child.children.first.text?
76
- key, value = child.name, booleanize(child.children.first.inner_text)
154
+ key, value = child.name, booleanize(child.children.first.to_html)
77
155
  else
78
156
  key, value = child.name, xml_node_to_hash(child)
79
157
  end
80
158
 
81
159
  key = remove_namespace(key)
82
- key = to_snake_case(key)
160
+ key = to_snake_case(key).intern
83
161
  current = this_node[key]
84
162
  case current
85
163
  when Array
@@ -93,7 +171,13 @@ class ApricotEatsGorilla
93
171
  this_node
94
172
  end
95
173
 
96
- # Converts a Hash to XML.
174
+ # Actual implementation for hash_to_xml. Takes a Hash key +name+ and a
175
+ # value +item+ and returns an XML String equal to the given content.
176
+ #
177
+ # ==== Parameters
178
+ #
179
+ # * +name+ - A Hash key to translate into an XML String.
180
+ # * +item+ - A Hash value to translate into an XML String.
97
181
  def nested_data_to_xml(name, item)
98
182
  case item
99
183
  when String
@@ -116,52 +200,53 @@ class ApricotEatsGorilla
116
200
  end
117
201
  end
118
202
 
119
- # Returns a sorted version of a given Hash in case :sort_keys is enabled.
120
- def opt_order(hash)
121
- return hash unless sort_keys
122
- hash.sort_by { |kv| kv.first }
203
+ # Creates an XML tag. Expects a block for tag content. Defaults to an empty
204
+ # element tag in case no block was supplied.
205
+ #
206
+ # ==== Parameters
207
+ #
208
+ # * +name+ - The name of the XML tag.
209
+ # * +attributes+ - Optional. Hash of attributes for the XML tag.
210
+ def tag(name, attributes = {})
211
+ return "<#{name} />" unless block_given?
212
+
213
+ attr = opt_order(attributes).map { |k, v| %Q( xmlns:#{k}="#{v}") }.to_s
214
+ body = (yield && !yield.empty?) ? yield : ""
215
+ "<#{name}#{attr}>" << body << "</#{name}>"
123
216
  end
124
217
 
125
- # Removes line breaks and whitespace between XML tags.
218
+ # Removes line breaks and whitespace between tags from a given +xml+ String.
126
219
  def clean_xml(xml)
127
220
  xml = xml.gsub(/\n+/, "")
128
221
  xml.gsub(/(>)\s*(<)/, '\1\2')
129
222
  end
130
223
 
131
- # Removes namespaces from XML tags.
224
+ # Removes the namespace from a given XML +tag+.
132
225
  def remove_namespace(tag)
133
226
  tag.sub(/.+:(.+)/, '\1')
134
227
  end
135
228
 
136
- # Converts CamelCase and lowerCamelCase to snake_case.
229
+ # Converts a given +string+ from CamelCase/lowerCamelCase to snake_case.
137
230
  def to_snake_case(string)
138
231
  string = string.gsub(/[A-Z]+/, '\1_\0').downcase
139
232
  string = string[1, string.length-1] if string[0, 1] == "_"
140
233
  string
141
234
  end
142
235
 
143
- # Converts "true" and "false" strings to boolean values.
144
- # Returns the original string in case it doesn't match "true" or "false".
236
+ # Checks to see if a given +string+ matches "true" or "false" and converts
237
+ # these values to actual Boolean objects. Returns the original string in
238
+ # case it does not match "true" or "false".
145
239
  def booleanize(string)
146
240
  return true if string == "true"
147
241
  return false if string == "false"
148
242
  string
149
243
  end
150
244
 
151
- end
152
- end
153
-
154
- # Shortcut method for translating between XML documents and Hashes.
155
- # Calls xml_to_hash in case the first parameter is of type String.
156
- # Calls hash_to_xml in case the first parameter is of type Hash.
157
- # Returns nil otherwise.
158
- def ApricotEatsGorilla(source, root_node = nil)
159
- case source
160
- when String
161
- ApricotEatsGorilla.xml_to_hash(source, root_node)
162
- when Hash
163
- ApricotEatsGorilla.hash_to_xml(source)
164
- else
165
- nil
245
+ # Returns a sorted version of a given +hash+ if :sort_keys was enabled.
246
+ def opt_order(hash)
247
+ return hash unless sort_keys
248
+ hash.sort_by { |kv| kv.first }
249
+ end
250
+
166
251
  end
167
252
  end
@@ -1,125 +1,5 @@
1
- # -*- coding: utf-8 -*-
2
- require 'test/unit'
3
- require File.join(File.dirname(__FILE__), "..", "lib", "apricoteatsgorilla")
4
-
5
- class ApricotEatsGorillaTest < Test::Unit::TestCase
6
-
7
- def setup
8
- ApricotEatsGorilla.sort_keys = true
9
- end
10
-
11
- # xml_to_hash
12
- def test_xml_to_hash_with_soap_response_example
13
- xml = '<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
14
- <soap:Body>
15
- <ns2:authenticateResponse xmlns:ns2="http://v1_0.ws.example.com/">
16
- <return>
17
- <authValue>
18
- <token>secret</token>
19
- <client>example</client>
20
- </authValue>
21
- </return>
22
- </ns2:authenticateResponse>
23
- </soap:Body>
24
- </soap:Envelope>'
25
- expected = { 'auth_value' => { 'token' => 'secret', 'client' => 'example' } }
26
-
27
- result = ApricotEatsGorilla.xml_to_hash(xml, '//return')
28
- assert_equal expected, result
29
- end
30
-
31
- def test_xml_to_hash_make_sure_boolean_values_get_converted_to_boolean_objects
32
- xml = '<root><yes>true</yes><no>false</no><txt>something</txt></root>'
33
- expected = { 'yes' => true, 'no' => false, 'txt' => 'something' }
34
-
35
- result = ApricotEatsGorilla.xml_to_hash(xml)
36
- assert_equal expected, result
37
- end
38
-
39
- def test_xml_to_hash_make_sure_empty_element_tags_get_converted_to_nil
40
- xml = '<contact><name>Jungle Julia</name><email /><phone/></contact>'
41
- expected = { 'name' => 'Jungle Julia', 'email' => nil, 'phone' => nil }
42
-
43
- result = ApricotEatsGorilla.xml_to_hash(xml)
44
- assert_equal expected, result
45
- end
46
-
47
- def test_xml_to_hash_make_sure_node_attributes_get_removed
48
- xml = '<todo><paint subject="chair" color="#000000">black</paint></todo>'
49
- expected = { 'paint' => 'black' }
50
-
51
- result = ApricotEatsGorilla.xml_to_hash(xml)
52
- assert_equal expected, result
53
- end
54
-
55
- def test_xml_to_hash_make_sure_node_groups_with_text_values_get_converted_to_arrays
56
- xml = '<root><items>first</items><items>second</items></root>'
57
- expected = { 'items' => [ 'first', 'second' ] }
58
-
59
- result = ApricotEatsGorilla.xml_to_hash(xml)
60
- assert_equal expected, result
61
- end
62
-
63
- def test_xml_to_hash_make_sure_node_groups_with_nesting_get_converted_to_arrays_with_hashes
64
- xml = '<root><items><name>first</name></items><items><name>second</name></items></root>'
65
- expected = { 'items' => [ { 'name' => 'first' }, { 'name' => 'second' } ] }
66
-
67
- result = ApricotEatsGorilla.xml_to_hash(xml)
68
- assert_equal expected, result
69
- end
70
-
71
- # hash_to_xml
72
- def test_hash_to_xml_with_simple_hash
73
- hash = { "dude" => "likes beer" }
74
- expected = "<dude>likes beer</dude>"
75
-
76
- result = ApricotEatsGorilla.hash_to_xml(hash)
77
- assert_equal expected, result
78
- end
79
-
80
- def test_hash_to_xml_with_nested_hash
81
- hash = { "dude" => { "likes" => "beer", "hates" => "appletini" } }
82
- expected = "<dude><hates>appletini</hates><likes>beer</likes></dude>"
83
-
84
- result = ApricotEatsGorilla.hash_to_xml(hash)
85
- assert_equal expected, result
86
- end
87
-
88
- def test_hash_to_xml_with_nested_hash_and_array
89
- hash = { "dude" => { "likes" => [ "beer", "helicopters" ] } }
90
- expected = "<dude><likes>beer</likes><likes>helicopters</likes></dude>"
91
-
92
- result = ApricotEatsGorilla.hash_to_xml(hash)
93
- assert_equal expected, result
94
- end
95
-
96
- def test_hash_to_xml_with_nested_hash_and_array_containing_a_hash
97
- hash = { "dude" => { "likes" => [ { "beer" => "a lot" }, { "helicopters" => "a little more" } ] } }
98
- expected = "<dude><likes><beer>a lot</beer></likes><likes><helicopters>a little more</helicopters></likes></dude>"
99
-
100
- result = ApricotEatsGorilla.hash_to_xml(hash)
101
- assert_equal expected, result
102
- end
103
-
104
- # soap_envelope
105
- def test_soap_envelope_without_namespace_and_block_returns_pure_envelope
106
- expected = '<env:Envelope xmlns:env="http://schemas.xmlsoap.org/soap/envelope/">' <<
107
- '<env:Body></env:Body></env:Envelope>'
108
-
109
- result = ApricotEatsGorilla.soap_envelope
110
- assert_equal expected, result
111
- end
112
-
113
- def test_soap_envelope_with_namespace_and_block_returns_envelope_with_namespace_and_content
114
- expected = '<env:Envelope ' <<
115
- 'xmlns:env="http://schemas.xmlsoap.org/soap/envelope/" ' <<
116
- 'xmlns:wsdl="http://example.com">' <<
117
- '<env:Body><id>123</id></env:Body></env:Envelope>'
118
-
119
- result = ApricotEatsGorilla.soap_envelope("wsdl" => "http://example.com") do
120
- "<id>123</id>"
121
- end
122
- assert_equal expected, result
123
- end
124
-
125
- end
1
+ $:.unshift(File.join(File.dirname(__FILE__), "apricoteatsgorilla"))
2
+ require "shortcut_method_test"
3
+ require "xml_to_hash_test"
4
+ require "hash_to_xml_test"
5
+ require "soap_envelope_test"
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.2.8
4
+ version: 0.3.2
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 is a helper for working with XML and SOAP messages.
25
+ description: Apricot eats Gorilla is a SOAP communication helper.
26
26
  email:
27
27
  executables: []
28
28
 
@@ -59,6 +59,6 @@ rubyforge_project:
59
59
  rubygems_version: 1.2.0
60
60
  signing_key:
61
61
  specification_version: 2
62
- summary: Apricot eats Gorilla is a helper for working with XML and SOAP messages.
62
+ summary: Apricot eats Gorilla is a SOAP communication helper.
63
63
  test_files:
64
64
  - tests/apricoteatsgorilla_test.rb