smacks-apricoteatsgorilla 0.4.5 → 0.4.6
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +17 -62
- data/lib/apricoteatsgorilla.rb +3 -244
- data/lib/apricoteatsgorilla/apricoteatsgorilla.rb +222 -0
- data/lib/apricoteatsgorilla/xml_node.rb +75 -0
- data/test/apricoteatsgorilla/test_hash_to_xml.rb +17 -4
- data/test/apricoteatsgorilla/test_shortcut_method.rb +2 -1
- data/test/apricoteatsgorilla/test_soap_envelope.rb +3 -3
- data/test/apricoteatsgorilla/test_xml_node.rb +87 -0
- data/test/apricoteatsgorilla/test_xml_to_hash.rb +13 -2
- data/test/test_apricoteatsgorilla.rb +3 -1
- metadata +6 -3
data/README.rdoc
CHANGED
@@ -1,10 +1,8 @@
|
|
1
1
|
= Apricot eats Gorilla
|
2
2
|
|
3
|
-
Apricot eats Gorilla is a SOAP communication helper.
|
4
|
-
|
5
|
-
|
6
|
-
helpful methods for working with SOAP webservices. Apricot eats Gorilla was
|
7
|
-
initially based on CobraVsMongoose but uses Hpricot instead of REXML.
|
3
|
+
Apricot eats Gorilla is a SOAP communication helper. It translates between
|
4
|
+
SOAP messages (XML) and Ruby Hashes and comes with some additional helpers
|
5
|
+
for working with SOAP services.
|
8
6
|
|
9
7
|
== Install
|
10
8
|
|
@@ -14,84 +12,41 @@ initially based on CobraVsMongoose but uses Hpricot instead of REXML.
|
|
14
12
|
|
15
13
|
hpricot 0.6.164 (also available for JRuby)
|
16
14
|
|
17
|
-
==
|
18
|
-
|
19
|
-
=== xml_to_hash(xml, root_node = nil)
|
20
|
-
|
21
|
-
Converts a given XML String into a Ruby Hash. Starts parsing at root node by
|
22
|
-
default. The optional root_node parameter can be used to specify a custom root
|
23
|
-
node to start parsing at using an XPath expression (Hpricot search). The root
|
24
|
-
node itself won't be included in the Hash. Converts tag names from CamelCase/
|
25
|
-
lowerCamelCase to snake_case and into Symbols by default.
|
15
|
+
== Translate an XML String into a Ruby Hash
|
26
16
|
|
27
17
|
xml = '<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
|
28
18
|
<soap:Body>
|
29
19
|
<ns2:authenticateResponse xmlns:ns2="http://v1_0.ws.example.com/">
|
30
20
|
<return>
|
31
|
-
<
|
32
|
-
<
|
33
|
-
|
34
|
-
</authValue>
|
21
|
+
<apricot>
|
22
|
+
<eats>Gorilla</eats>
|
23
|
+
</apricot>
|
35
24
|
</return>
|
36
25
|
</ns2:authenticateResponse>
|
37
26
|
</soap:Body>
|
38
27
|
</soap:Envelope>'
|
39
28
|
|
40
29
|
ApricotEatsGorilla[xml, "//return"]
|
41
|
-
# => { :
|
42
|
-
|
43
|
-
=== hash_to_xml(hash)
|
30
|
+
# => { :apricot => { :eats => "Gorilla" } }
|
44
31
|
|
45
|
-
|
46
|
-
to lowerCamelCase by default.
|
32
|
+
== Translate a Ruby Hash into an XML String
|
47
33
|
|
48
|
-
hash = { :apricot => { :eats =>
|
34
|
+
hash = { :apricot => { :eats => "Gorilla" } }
|
49
35
|
|
50
36
|
ApricotEatsGorilla[hash]
|
51
|
-
# => "<apricot><eats
|
37
|
+
# => "<apricot><eats>Gorilla</eats></apricot>"
|
52
38
|
|
53
|
-
|
39
|
+
== Build a SOAP request envelope
|
54
40
|
|
55
|
-
|
56
|
-
into the envelope body. Accepts a Hash (namespace => namespace_uri) of additional
|
57
|
-
namespaces to set.
|
58
|
-
|
59
|
-
ApricotEatsGorilla.soap_envelope { "<authenticate>me</authenticate>" }
|
41
|
+
ApricotEatsGorilla.soap_envelope { "<apricot><eats>Gorilla</eats></apricot>" }
|
60
42
|
|
61
43
|
# => '<env:Envelope xmlns:env="http://schemas.xmlsoap.org/soap/envelope/">
|
62
44
|
# => <env:Body>
|
63
|
-
# => <
|
45
|
+
# => <apricot><eats>Gorilla</eats></apricot>
|
64
46
|
# => </env:Body>
|
65
47
|
# => </env:Envelope>'
|
66
48
|
|
67
|
-
==
|
68
|
-
|
69
|
-
There are several options for changing the default behavior. Options can be
|
70
|
-
set using a setup block or by setting one option at a time.
|
71
|
-
|
72
|
-
ApricotEatsGorilla.setup do |s|
|
73
|
-
s.disable_tag_names_to_lower_camel_case = true
|
74
|
-
s.disable_hash_keys_to_snake_case = true
|
75
|
-
s.disable_hash_keys_to_symbol = true
|
76
|
-
end
|
77
|
-
|
78
|
-
=== Disable conversion of tag names to lowerCamelCase
|
79
|
-
|
80
|
-
By default XML tags created by hash_to_xml are converted from snake_case to
|
81
|
-
lowerCamelCase. Disable by setting disable_tag_names_to_lower_camel_case to true.
|
82
|
-
|
83
|
-
ApricotEatsGorilla.disable_tag_names_to_lower_camel_case = true
|
84
|
-
|
85
|
-
=== Disable conversion of Hash keys to snake_case
|
86
|
-
|
87
|
-
By default Hash keys created by xml_to_hash are converted from lowerCamelCase/
|
88
|
-
CamelCase to snake_case. Disable by setting disable_hash_keys_to_snake_case to true.
|
89
|
-
|
90
|
-
ApricotEatsGorilla.disable_hash_keys_to_snake_case = true
|
91
|
-
|
92
|
-
=== Disable conversion of Hash keys to Symbols
|
93
|
-
|
94
|
-
By default Hash keys created by xml_to_hash are converted to Symbols.
|
95
|
-
Disable by setting disable_hash_keys_to_symbol to true.
|
49
|
+
== Read more
|
96
50
|
|
97
|
-
|
51
|
+
For more detailed information, please take a look at the
|
52
|
+
{GitHub Wiki}[http://wiki.github.com/smacks/apricoteatsgorilla].
|
data/lib/apricoteatsgorilla.rb
CHANGED
@@ -1,245 +1,4 @@
|
|
1
|
-
|
2
|
-
require "iconv"
|
3
|
-
require "hpricot"
|
1
|
+
$:.unshift(File.join(File.dirname(__FILE__), "apricoteatsgorilla"))
|
4
2
|
|
5
|
-
|
6
|
-
|
7
|
-
# It translates between SOAP messages (XML) and Ruby Hashes while offering some
|
8
|
-
# helpful methods for working with SOAP webservices. Apricot eats Gorilla was
|
9
|
-
# initially based on CobraVsMongoose but uses Hpricot instead of REXML.
|
10
|
-
class ApricotEatsGorilla
|
11
|
-
class << self # Class methods
|
12
|
-
|
13
|
-
# Flag to enable sorting of Hash keys.
|
14
|
-
attr_accessor :sort_keys
|
15
|
-
|
16
|
-
# Flag to disable conversion of tag names to lowerCamelCase.
|
17
|
-
attr_accessor :disable_tag_names_to_lower_camel_case
|
18
|
-
|
19
|
-
# Flag to disable conversion of Hash keys to snake_case.
|
20
|
-
attr_accessor :disable_hash_keys_to_snake_case
|
21
|
-
|
22
|
-
# Flag to disable conversion of Hash keys to Symbols.
|
23
|
-
attr_accessor :disable_hash_keys_to_symbol
|
24
|
-
|
25
|
-
# Array of XML nodes to add a namespace to.
|
26
|
-
attr_accessor :nodes_to_namespace
|
27
|
-
|
28
|
-
# The namespace for nodes set by nodes_to_namespace.
|
29
|
-
attr_accessor :node_namespace
|
30
|
-
|
31
|
-
# Shortcut method for translating between XML Strings and Ruby Hashes.
|
32
|
-
# Delegates to xml_to_hash in case +source+ is of type String or delegates
|
33
|
-
# to hash_to_xml in case +source+ is of type Hash. Returns nil otherwise.
|
34
|
-
def [](source, root_node = nil)
|
35
|
-
case source
|
36
|
-
when String
|
37
|
-
xml_to_hash(source, root_node)
|
38
|
-
when Hash
|
39
|
-
hash_to_xml(source)
|
40
|
-
else
|
41
|
-
nil
|
42
|
-
end
|
43
|
-
end
|
44
|
-
|
45
|
-
# Yields this class object in case a +block+ was given. Nice way for setting
|
46
|
-
# multiple options at once.
|
47
|
-
def setup
|
48
|
-
yield self if block_given?
|
49
|
-
end
|
50
|
-
|
51
|
-
# Converts a given +xml+ String into a Ruby Hash. Starts parsing at root
|
52
|
-
# node by default. The optional +root_node+ parameter can be used to specify
|
53
|
-
# a custom root node to start parsing at using an XPath (Hpricot search).
|
54
|
-
# The root node itself won't be included in the Hash. Converts tag names
|
55
|
-
# from CamelCase/lowerCamelCase to snake_case and into Symbols by default.
|
56
|
-
#
|
57
|
-
# ==== Examples
|
58
|
-
#
|
59
|
-
# xml = "<apricot><eats>Gorilla</eats></apricot>"
|
60
|
-
# ApricotEatsGorilla[xml]
|
61
|
-
# # => { :eats => "Gorilla" }
|
62
|
-
#
|
63
|
-
# xml = "<apricot><eats><lotsOf>Gorillas</lotsOf></eats></apricot>"
|
64
|
-
# ApricotEatsGorilla[xml, "//eats"]
|
65
|
-
# # => { :lots_of => "Gorillas" }
|
66
|
-
def xml_to_hash(xml, root_node = nil)
|
67
|
-
doc = Hpricot.XML remove_whitespace(xml)
|
68
|
-
root = root_node ? doc.search(root_node) : doc.root
|
69
|
-
|
70
|
-
return nil if root.nil?
|
71
|
-
return xml_node_to_hash(root) unless root.respond_to? :each
|
72
|
-
|
73
|
-
if root.size == 1
|
74
|
-
return root.first.children.to_s if root.first.children.first.kind_of?(Hpricot::Text)
|
75
|
-
xml_node_to_hash(root.first)
|
76
|
-
else
|
77
|
-
root.map do |node|
|
78
|
-
if node.children.first.kind_of?(Hpricot::Text)
|
79
|
-
node.children.to_s
|
80
|
-
else
|
81
|
-
xml_node_to_hash(node)
|
82
|
-
end
|
83
|
-
end
|
84
|
-
end
|
85
|
-
end
|
86
|
-
|
87
|
-
# Converts a given Ruby Hash into an XML String. Converts Hash keys from
|
88
|
-
# snake_case to lowerCamelCase by default.
|
89
|
-
#
|
90
|
-
# ==== Examples
|
91
|
-
#
|
92
|
-
# hash = { :apricot => { :eats => "Gorilla" } }
|
93
|
-
# ApricotEatsGorilla[hash]
|
94
|
-
# # => "<apricot><eats>Gorilla</eats></apricot>"
|
95
|
-
#
|
96
|
-
# hash = { :apricot => { :eats => [ "Gorillas", "Snakes" ] } }
|
97
|
-
# ApricotEatsGorilla[hash]
|
98
|
-
# # => "<apricot><eats>Gorillas</eats><eats>Snakes</eats></apricot>"
|
99
|
-
def hash_to_xml(hash)
|
100
|
-
nested_data_to_xml(hash.keys.first, hash.values.first)
|
101
|
-
end
|
102
|
-
|
103
|
-
# Builds a SOAP request envelope and includes the content from a given +block+
|
104
|
-
# into the envelope body. Accepts a Hash (namespace => namespace_uri) of
|
105
|
-
# additional +namespaces+ to set.
|
106
|
-
#
|
107
|
-
# ==== Examples
|
108
|
-
#
|
109
|
-
# ApricotEatsGorilla.soap_envelope { "<authenticate>me</authenticate>" }
|
110
|
-
#
|
111
|
-
# # => '<env:Envelope xmlns:env="http://schemas.xmlsoap.org/soap/envelope/">
|
112
|
-
# # => <env:Body>
|
113
|
-
# # => <authenticate>me</authenticate>
|
114
|
-
# # => </env:Body>
|
115
|
-
# # => </env:Envelope>'
|
116
|
-
#
|
117
|
-
# ApricotEatsGorilla.soap_envelope :wsdl => "http://example.com" do
|
118
|
-
# "<authenticate>me</authenticate>"
|
119
|
-
# end
|
120
|
-
#
|
121
|
-
# # => '<env:Envelope
|
122
|
-
# # => xmlns:wsdl="http://example.com"
|
123
|
-
# # => xmlns:env="http://schemas.xmlsoap.org/soap/envelope/">
|
124
|
-
# # => <env:Body>
|
125
|
-
# # => <authenticate>me</authenticate>
|
126
|
-
# # => </env:Body>
|
127
|
-
# # => </env:Envelope>'
|
128
|
-
def soap_envelope(namespaces = {})
|
129
|
-
namespaces["env"] = "http://schemas.xmlsoap.org/soap/envelope/"
|
130
|
-
tag("env:Envelope", namespaces) do
|
131
|
-
tag("env:Body") do
|
132
|
-
yield if block_given?
|
133
|
-
end
|
134
|
-
end
|
135
|
-
end
|
136
|
-
|
137
|
-
# Converts a given +string+ from CamelCase/lowerCamelCase to snake_case.
|
138
|
-
def to_snake_case(string)
|
139
|
-
string = string.gsub(/[A-Z]+/, '\1_\0').downcase
|
140
|
-
string = string[1, string.length-1] if string[0, 1] == "_"
|
141
|
-
string
|
142
|
-
end
|
143
|
-
|
144
|
-
# Converts a given +string+ from snake_case to lowerCamelCase.
|
145
|
-
def to_lower_camel_case(string)
|
146
|
-
string.to_s.gsub(/_(.)/) { $1.upcase }
|
147
|
-
end
|
148
|
-
|
149
|
-
private
|
150
|
-
|
151
|
-
# Actual implementation for xml_to_hash. Takes and iterates through a given
|
152
|
-
# Hpricot +element+ and returns a Ruby Hash equal to the given content.
|
153
|
-
def xml_node_to_hash(element)
|
154
|
-
this_node = {}
|
155
|
-
element.each_child do |child|
|
156
|
-
# hpricot 0.6.1 returns an empty array, while 0.8 returns nil
|
157
|
-
if child.children.nil? || child.children.empty?
|
158
|
-
key, value = child.name, nil
|
159
|
-
elsif child.children.size == 1 && child.children.first.text?
|
160
|
-
key, value = child.name, map_to_boolean(child.children.first.to_html)
|
161
|
-
else
|
162
|
-
key, value = child.name, xml_node_to_hash(child)
|
163
|
-
end
|
164
|
-
|
165
|
-
key = remove_namespace(key)
|
166
|
-
key = to_snake_case(key) unless disable_hash_keys_to_snake_case
|
167
|
-
key = key.intern unless disable_hash_keys_to_symbol
|
168
|
-
current = this_node[key]
|
169
|
-
case current
|
170
|
-
when Array
|
171
|
-
this_node[key] << value
|
172
|
-
when nil
|
173
|
-
this_node[key] = value
|
174
|
-
else
|
175
|
-
this_node[key] = [current.dup, value]
|
176
|
-
end
|
177
|
-
end
|
178
|
-
this_node
|
179
|
-
end
|
180
|
-
|
181
|
-
# Actual implementation for hash_to_xml. Takes a Hash key +name+ and a
|
182
|
-
# value +item+ and returns an XML String equal to the given content.
|
183
|
-
def nested_data_to_xml(name, item)
|
184
|
-
case item
|
185
|
-
when Array
|
186
|
-
item.map { |subitem| nested_data_to_xml(name, subitem) }.join
|
187
|
-
when Hash
|
188
|
-
tag(name) do
|
189
|
-
opt_order(item).map { |tag, value|
|
190
|
-
case value
|
191
|
-
when Array
|
192
|
-
value.map { |subitem| nested_data_to_xml(tag, subitem) }.join
|
193
|
-
when Hash
|
194
|
-
nested_data_to_xml(tag, value)
|
195
|
-
else
|
196
|
-
tag(tag) { value.to_s } if value.respond_to? :to_s
|
197
|
-
end
|
198
|
-
}.join
|
199
|
-
end
|
200
|
-
else
|
201
|
-
tag(name) { item.to_s } if item.respond_to? :to_s
|
202
|
-
end
|
203
|
-
end
|
204
|
-
|
205
|
-
# Creates an XML tag. Expects a block for tag content. Defaults to an empty
|
206
|
-
# element tag in case no block was supplied.
|
207
|
-
def tag(name, attributes = {})
|
208
|
-
name = to_lower_camel_case(name) unless disable_tag_names_to_lower_camel_case
|
209
|
-
if nodes_to_namespace.kind_of? Array
|
210
|
-
name = "#{node_namespace}:#{name}" if node_namespace && nodes_to_namespace.include?(name)
|
211
|
-
end
|
212
|
-
return "<#{name} />" unless block_given?
|
213
|
-
|
214
|
-
attr = opt_order(attributes).map { |k, v| %Q( xmlns:#{k}="#{v}") }.to_s
|
215
|
-
body = yield || ""
|
216
|
-
"<#{name}#{attr}>" << body << "</#{name}>"
|
217
|
-
end
|
218
|
-
|
219
|
-
# Removes whitespace between tags of a given +xml+ String.
|
220
|
-
def remove_whitespace(xml)
|
221
|
-
xml.gsub(/(>)\s*(<)/, '\1\2')
|
222
|
-
end
|
223
|
-
|
224
|
-
# Removes the namespace from a given XML +tag+.
|
225
|
-
def remove_namespace(tag)
|
226
|
-
tag.sub(/.+:(.+)/, '\1')
|
227
|
-
end
|
228
|
-
|
229
|
-
# Checks to see if a given +string+ matches "true" or "false" and converts
|
230
|
-
# these values to actual Boolean objects. Returns the original String in
|
231
|
-
# case it does not match "true" or "false".
|
232
|
-
def map_to_boolean(string)
|
233
|
-
return true if string == "true"
|
234
|
-
return false if string == "false"
|
235
|
-
Iconv.new("iso-8859-1", "utf-8").iconv(string)
|
236
|
-
end
|
237
|
-
|
238
|
-
# Returns a sorted version of a given +hash+ if sort_keys is enabled.
|
239
|
-
def opt_order(hash)
|
240
|
-
return hash unless sort_keys
|
241
|
-
hash.keys.sort_by { |s| s.to_s }.map { |key| [key, hash[key]] }
|
242
|
-
end
|
243
|
-
|
244
|
-
end
|
245
|
-
end
|
3
|
+
require "apricoteatsgorilla"
|
4
|
+
require "xml_node"
|
@@ -0,0 +1,222 @@
|
|
1
|
+
require "rubygems"
|
2
|
+
require "hpricot"
|
3
|
+
require "iconv"
|
4
|
+
|
5
|
+
# == Apricot eats Gorilla
|
6
|
+
#
|
7
|
+
# Apricot eats Gorilla is a SOAP communication helper. It translates between
|
8
|
+
# SOAP messages (XML) and Ruby Hashes and comes with some additional helpers
|
9
|
+
# for working with SOAP services.
|
10
|
+
class ApricotEatsGorilla
|
11
|
+
class << self
|
12
|
+
|
13
|
+
# Flag to enable sorting of Hash keys.
|
14
|
+
attr_accessor :sort_keys
|
15
|
+
|
16
|
+
# Flag to disable conversion of XML tags names to lowerCamelCase.
|
17
|
+
attr_accessor :disable_tag_names_to_lower_camel_case
|
18
|
+
|
19
|
+
# Flag to disable conversion of Hash keys to snake_case.
|
20
|
+
attr_accessor :disable_hash_keys_to_snake_case
|
21
|
+
|
22
|
+
# Flag to disable conversion of Hash keys to Symbols.
|
23
|
+
attr_accessor :disable_hash_keys_to_symbols
|
24
|
+
|
25
|
+
# Hash of namespaces and XML nodes to apply these namespaces to.
|
26
|
+
attr_accessor :nodes_to_namespace
|
27
|
+
|
28
|
+
# Shortcut method for translating between XML Strings and Ruby Hashes.
|
29
|
+
# Delegates to +xml_to_hash+ in case +source+ is a String or delegates
|
30
|
+
# to +hash_to_xml+ in case +source+ is a Hash. Returns nil otherwise.
|
31
|
+
def [](source, root_node = nil)
|
32
|
+
case source
|
33
|
+
when String
|
34
|
+
xml_to_hash(source, root_node)
|
35
|
+
when Hash
|
36
|
+
hash_to_xml(source)
|
37
|
+
else
|
38
|
+
nil
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
# Yields this class in case a +block+ was given.
|
43
|
+
def setup
|
44
|
+
yield self if block_given?
|
45
|
+
end
|
46
|
+
|
47
|
+
# Translates a given +xml+ String into a Ruby Hash.
|
48
|
+
#
|
49
|
+
# Starts parsing at the XML root node by default. Accepts an optional
|
50
|
+
# +root_node+ parameter for defining a custom root node to start parsing
|
51
|
+
# at using an XPath (Hpricot search).
|
52
|
+
#
|
53
|
+
# The root node itself won't be included in the Hash.
|
54
|
+
#
|
55
|
+
# ==== Examples
|
56
|
+
#
|
57
|
+
# xml = '<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
|
58
|
+
# <soap:Body>
|
59
|
+
# <ns2:authenticateResponse xmlns:ns2="http://v1_0.ws.example.com/">
|
60
|
+
# <return>
|
61
|
+
# <apricot>
|
62
|
+
# <eats>Gorilla</eats>
|
63
|
+
# </apricot>
|
64
|
+
# </return>
|
65
|
+
# </ns2:authenticateResponse>
|
66
|
+
# </soap:Body>
|
67
|
+
# </soap:Envelope>'
|
68
|
+
#
|
69
|
+
# ApricotEatsGorilla.xml_to_hash(xml, "//return")
|
70
|
+
# # => { :apricot => { :eats => "Gorilla" } }
|
71
|
+
def xml_to_hash(xml, root_node = nil)
|
72
|
+
doc = Hpricot.XML remove_whitespace(xml)
|
73
|
+
root = root_node ? doc.search(root_node) : doc.root
|
74
|
+
|
75
|
+
return nil if root.nil?
|
76
|
+
return xml_node_to_hash(root) unless root.respond_to? :each
|
77
|
+
|
78
|
+
if root.size == 1
|
79
|
+
if root.first.children.first.kind_of?(Hpricot::Text)
|
80
|
+
map_xml_value(root.first.children.to_s)
|
81
|
+
else
|
82
|
+
xml_node_to_hash(root.first)
|
83
|
+
end
|
84
|
+
else
|
85
|
+
root.map do |node|
|
86
|
+
if node.children.first.kind_of?(Hpricot::Text)
|
87
|
+
map_xml_value(node.children.to_s)
|
88
|
+
else
|
89
|
+
xml_node_to_hash(node)
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
# Translates a given Ruby +hash+ into an XML String.
|
96
|
+
#
|
97
|
+
# ==== Examples
|
98
|
+
#
|
99
|
+
# hash = { :apricot => { :eats => "Gorilla" } }
|
100
|
+
#
|
101
|
+
# ApricotEatsGorilla.hash_to_xml(hash)
|
102
|
+
# # => "<apricot><eats>Gorilla</eats></apricot>"
|
103
|
+
def hash_to_xml(hash)
|
104
|
+
nested_data_to_xml(hash.keys.first, hash.values.first)
|
105
|
+
end
|
106
|
+
|
107
|
+
# Builds a SOAP request envelope and includes the content from a given
|
108
|
+
# +block+ into the envelope body. Accepts a Hash of additional +namespaces+
|
109
|
+
# to set.
|
110
|
+
#
|
111
|
+
# ==== Examples
|
112
|
+
#
|
113
|
+
# ApricotEatsGorilla.soap_envelope do
|
114
|
+
# "<apricot><eats>Gorilla</eats></apricot>"
|
115
|
+
# end
|
116
|
+
#
|
117
|
+
# # => '<env:Envelope xmlns:env="http://schemas.xmlsoap.org/soap/envelope/">
|
118
|
+
# # => <env:Body>
|
119
|
+
# # => <apricot><eats>Gorilla</eats></apricot>
|
120
|
+
# # => </env:Body>
|
121
|
+
# # => </env:Envelope>'
|
122
|
+
def soap_envelope(namespaces = {})
|
123
|
+
namespaces[:env] = "http://schemas.xmlsoap.org/soap/envelope/"
|
124
|
+
|
125
|
+
xml_node("env:Envelope", namespaces) do
|
126
|
+
xml_node("env:Body") { yield if block_given? }
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
private
|
131
|
+
|
132
|
+
# Iterates through an expected Hpricot +element+ and returns a Ruby Hash
|
133
|
+
# equal to the XML content of the given element.
|
134
|
+
def xml_node_to_hash(element)
|
135
|
+
hash = {}
|
136
|
+
element.each_child do |child|
|
137
|
+
key = XMLNode.new(child.name)
|
138
|
+
key.strip_namespace!
|
139
|
+
key.to_snake_case! unless disable_hash_keys_to_snake_case
|
140
|
+
key = disable_hash_keys_to_symbols ? key.to_s : key.to_sym
|
141
|
+
|
142
|
+
# hpricot 0.6.1 returns an empty array, while 0.8 returns nil
|
143
|
+
if child.children.nil? || child.children.empty?
|
144
|
+
value = nil
|
145
|
+
elsif child.children.size == 1 && child.children.first.text?
|
146
|
+
value = map_xml_value(child.children.first.to_html)
|
147
|
+
else
|
148
|
+
value = xml_node_to_hash(child)
|
149
|
+
end
|
150
|
+
|
151
|
+
case hash[key]
|
152
|
+
when Array
|
153
|
+
hash[key] << value
|
154
|
+
when nil
|
155
|
+
hash[key] = value
|
156
|
+
else
|
157
|
+
hash[key] = [hash[key].dup, value]
|
158
|
+
end
|
159
|
+
end
|
160
|
+
hash
|
161
|
+
end
|
162
|
+
|
163
|
+
# Expects a Hash +key+ and a Hash +value+. Iterates through the given Hash
|
164
|
+
# +value+ and returns an XML String of the given Hash structure.
|
165
|
+
def nested_data_to_xml(key, value)
|
166
|
+
case value
|
167
|
+
when Array
|
168
|
+
value.map { |subitem| nested_data_to_xml(key, subitem) }.join
|
169
|
+
when Hash
|
170
|
+
xml_node(key) do
|
171
|
+
sort_hash_keys(value).map do |subkey, subvalue|
|
172
|
+
case subvalue
|
173
|
+
when Array
|
174
|
+
subvalue.map { |subitem| nested_data_to_xml(subkey, subitem) }.join
|
175
|
+
when Hash
|
176
|
+
nested_data_to_xml(subkey, subvalue)
|
177
|
+
else
|
178
|
+
xml_node(subkey) { subvalue.to_s } if subvalue.respond_to?(:to_s)
|
179
|
+
end
|
180
|
+
end.join
|
181
|
+
end
|
182
|
+
else
|
183
|
+
xml_node(key) { value.to_s } if value.respond_to?(:to_s)
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
# Returns an XML tag with a given +name+. Accepts a +block+ for tag content.
|
188
|
+
# Defaults to returning an empty element tag in case no block was given.
|
189
|
+
# Also accepts a Hash of +attributes+ to be added to the XML tag.
|
190
|
+
def xml_node(name, attributes = {})
|
191
|
+
node = XMLNode.new(name.to_s)
|
192
|
+
|
193
|
+
node.to_lower_camel_case! unless disable_tag_names_to_lower_camel_case
|
194
|
+
node.namespace_from_hash!(nodes_to_namespace)
|
195
|
+
node.attributes = sort_hash_keys(attributes)
|
196
|
+
node.body = yield if block_given?
|
197
|
+
|
198
|
+
node.to_tag
|
199
|
+
end
|
200
|
+
|
201
|
+
# Removes whitespace between tags from a given +xml+ String.
|
202
|
+
def remove_whitespace(xml)
|
203
|
+
xml.gsub(/(>)\s*(<)/, '\1\2')
|
204
|
+
end
|
205
|
+
|
206
|
+
# Maps an XML value to a more natural Ruby object. Converts String values
|
207
|
+
# of "true" and "false" to TrueClass and FalseClass. Converts other String
|
208
|
+
# values from "iso-8859-1" to "utf-8".
|
209
|
+
def map_xml_value(value)
|
210
|
+
return true if value == "true"
|
211
|
+
return false if value == "false"
|
212
|
+
Iconv.new("iso-8859-1", "utf-8").iconv(value)
|
213
|
+
end
|
214
|
+
|
215
|
+
# Returns a sorted version of a given +hash+ if +sort_keys+ is enabled.
|
216
|
+
def sort_hash_keys(hash)
|
217
|
+
return hash unless sort_keys
|
218
|
+
hash.keys.sort_by { |key| key.to_s }.map { |key| [ key, hash[key] ] }
|
219
|
+
end
|
220
|
+
|
221
|
+
end
|
222
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
# == XMLNode
|
2
|
+
#
|
3
|
+
# Representation of an XML node.
|
4
|
+
class XMLNode < String
|
5
|
+
|
6
|
+
# Hash of attributes.
|
7
|
+
attr_writer :attributes
|
8
|
+
|
9
|
+
# Body content.
|
10
|
+
attr_writer :body
|
11
|
+
|
12
|
+
# Strips the namespace from this node.
|
13
|
+
def strip_namespace!
|
14
|
+
sub!(/.+:(.+)/, '\1')
|
15
|
+
end
|
16
|
+
|
17
|
+
# Converts the name of this node to snake_case.
|
18
|
+
def to_snake_case!
|
19
|
+
self.gsub!(/[A-Z]/, '_\0')
|
20
|
+
self.gsub!(/^_/, '')
|
21
|
+
self.downcase!
|
22
|
+
end
|
23
|
+
|
24
|
+
# Converts the name of this node to lowerCamelCase.
|
25
|
+
def to_lower_camel_case!
|
26
|
+
self.gsub!(/_(.)/) { $1.upcase }
|
27
|
+
end
|
28
|
+
|
29
|
+
# Checks if the name of this node is included in a given Hash of +namespaces+
|
30
|
+
# and sets the namespace for this node in case it was found in the Hash.
|
31
|
+
def namespace_from_hash!(namespaces)
|
32
|
+
return if namespaces.nil? || namespaces.empty?
|
33
|
+
|
34
|
+
namespaces.each do |namespace, nodes|
|
35
|
+
@namespace = namespace if self_included?(nodes)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
# Returns this node as a complete XML tag including a +namespace+, +attributes+
|
40
|
+
# and a +body+ in case these values were supplied.
|
41
|
+
def to_tag
|
42
|
+
return "<#{namespace}#{self}#{attributes} />" unless @body
|
43
|
+
"<#{namespace}#{self}#{attributes}>#{body}</#{namespace}#{self}>"
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
|
48
|
+
# Returns +true+ if self as a String or a Symbol is included in a given
|
49
|
+
# +array+. Returns +false+ otherwise.
|
50
|
+
def self_included?(array)
|
51
|
+
array.include?(self.to_s) || array.include?(self.to_sym)
|
52
|
+
end
|
53
|
+
|
54
|
+
# Returns the namespace of this node. Defaults to an empty String in case
|
55
|
+
# no namespace was defined.
|
56
|
+
def namespace
|
57
|
+
return "" if @namespace.nil?
|
58
|
+
"#{@namespace}:"
|
59
|
+
end
|
60
|
+
|
61
|
+
# Returns the attributes of this node. Defaults to an empty String in case
|
62
|
+
# no attributes were defined.
|
63
|
+
def attributes
|
64
|
+
return "" if @attributes.nil?
|
65
|
+
@attributes.map { |key, value| %Q( xmlns:#{key}="#{value}") }
|
66
|
+
end
|
67
|
+
|
68
|
+
# Returns the body of this node. Defaults to an empty String in case no body
|
69
|
+
# was defined.
|
70
|
+
def body
|
71
|
+
return "" if @body.nil?
|
72
|
+
@body
|
73
|
+
end
|
74
|
+
|
75
|
+
end
|
@@ -8,14 +8,15 @@ class TestHashToXml < Test::Unit::TestCase
|
|
8
8
|
s.sort_keys = true
|
9
9
|
s.disable_tag_names_to_lower_camel_case = false
|
10
10
|
s.disable_hash_keys_to_snake_case = false
|
11
|
-
s.
|
11
|
+
s.disable_hash_keys_to_symbols = false
|
12
|
+
s.nodes_to_namespace = nil
|
12
13
|
end
|
13
14
|
end
|
14
15
|
|
15
|
-
context "with a Hash
|
16
|
+
context "with a Hash containing only a single key-value-pair" do
|
16
17
|
should "return an XML String containing one node and a value" do
|
17
|
-
hash = { "apricot" => "eats
|
18
|
-
expected = "<apricot>eats
|
18
|
+
hash = { "apricot" => "eats Gorilla" }
|
19
|
+
expected = "<apricot>eats Gorilla</apricot>"
|
19
20
|
|
20
21
|
result = ApricotEatsGorilla.hash_to_xml(hash)
|
21
22
|
assert_equal expected, result
|
@@ -103,6 +104,18 @@ class TestHashToXml < Test::Unit::TestCase
|
|
103
104
|
assert_equal expected, result
|
104
105
|
end
|
105
106
|
end
|
107
|
+
|
108
|
+
context "with some Hash and nodes to namespace" do
|
109
|
+
setup { ApricotEatsGorilla.nodes_to_namespace = { :wsdl => [ :apricot ] } }
|
110
|
+
|
111
|
+
should "apply the defined namespaces" do
|
112
|
+
hash = { :apricot => { :eats => "Gorilla" } }
|
113
|
+
expected = "<wsdl:apricot><eats>Gorilla</eats></wsdl:apricot>"
|
114
|
+
|
115
|
+
result = ApricotEatsGorilla.hash_to_xml(hash)
|
116
|
+
assert_equal expected, result
|
117
|
+
end
|
118
|
+
end
|
106
119
|
end
|
107
120
|
|
108
121
|
end
|
@@ -8,7 +8,8 @@ class TestShortcutMethod < Test::Unit::TestCase
|
|
8
8
|
s.sort_keys = true
|
9
9
|
s.disable_tag_names_to_lower_camel_case = false
|
10
10
|
s.disable_hash_keys_to_snake_case = false
|
11
|
-
s.
|
11
|
+
s.disable_hash_keys_to_symbols = false
|
12
|
+
s.nodes_to_namespace = nil
|
12
13
|
end
|
13
14
|
end
|
14
15
|
|
@@ -6,9 +6,9 @@ class TestSoapEnvelope < Test::Unit::TestCase
|
|
6
6
|
setup { ApricotEatsGorilla.sort_keys = true }
|
7
7
|
|
8
8
|
context "without parameter and block" do
|
9
|
-
should "returns a SOAP envelope
|
9
|
+
should "returns a SOAP envelope with an empty element body tag" do
|
10
10
|
expected = '<env:Envelope xmlns:env="http://schemas.xmlsoap.org/soap/envelope/">' <<
|
11
|
-
'<env:Body
|
11
|
+
'<env:Body /></env:Envelope>'
|
12
12
|
|
13
13
|
result = ApricotEatsGorilla.soap_envelope
|
14
14
|
assert_equal expected, result
|
@@ -16,7 +16,7 @@ class TestSoapEnvelope < Test::Unit::TestCase
|
|
16
16
|
end
|
17
17
|
|
18
18
|
context "with a Hash containing a custom namespace and a block" do
|
19
|
-
should "returns a SOAP envelope with custom namespace and body content" do
|
19
|
+
should "returns a SOAP envelope with a custom namespace and body content" do
|
20
20
|
expected = '<env:Envelope ' <<
|
21
21
|
'xmlns:env="http://schemas.xmlsoap.org/soap/envelope/" ' <<
|
22
22
|
'xmlns:wsdl="http://example.com">' <<
|
@@ -0,0 +1,87 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), "..", "helper")
|
2
|
+
|
3
|
+
class TestXMLNode < Test::Unit::TestCase
|
4
|
+
|
5
|
+
context "strip_namespace!" do
|
6
|
+
should "strip the namespace from the XMLNode" do
|
7
|
+
node = XMLNode.new("wsdl:apricot")
|
8
|
+
node.strip_namespace!
|
9
|
+
|
10
|
+
assert_equal "apricot", node.to_s
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
context "to_snake_case!" do
|
15
|
+
should "convert the XMLNode from CamelCase to snake_case" do
|
16
|
+
node = XMLNode.new("ApricotEatsGorilla")
|
17
|
+
node.to_snake_case!
|
18
|
+
|
19
|
+
assert_equal "apricot_eats_gorilla", node.to_s
|
20
|
+
end
|
21
|
+
|
22
|
+
should "convert the XMLNode from lowerCamelCase to snake_case" do
|
23
|
+
node = XMLNode.new("apricotEatsGorilla")
|
24
|
+
node.to_snake_case!
|
25
|
+
|
26
|
+
assert_equal "apricot_eats_gorilla", node.to_s
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
context "namespace_from_hash!" do
|
31
|
+
context "with a Hash containing the name of the XMLNode" do
|
32
|
+
should "set the namespace of the XMLNode" do
|
33
|
+
node = XMLNode.new("apricot")
|
34
|
+
node.namespace_from_hash!(:wsdl => [ :apricot ])
|
35
|
+
|
36
|
+
assert_equal "apricot", node.to_s
|
37
|
+
assert_equal "<wsdl:apricot />", node.to_tag
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
context "with a Hash that does not contain the name of the XMLNode" do
|
42
|
+
should "not set the namespace of the XMLNode" do
|
43
|
+
node = XMLNode.new("apricot")
|
44
|
+
node.namespace_from_hash!(:wsdl => [ :some_key ])
|
45
|
+
|
46
|
+
assert_equal "<apricot />", node.to_tag
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
context "to_tag" do
|
52
|
+
context "with a simple XMLNode" do
|
53
|
+
should "return an empty element tag" do
|
54
|
+
node = XMLNode.new("apricot")
|
55
|
+
assert_equal "<apricot />", node.to_tag
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
context "with an XMLNode containing a namespace" do
|
60
|
+
should "return a namespaced empty element tag" do
|
61
|
+
node = XMLNode.new("apricot")
|
62
|
+
node.namespace_from_hash!(:wsdl => [ :apricot ])
|
63
|
+
|
64
|
+
assert_equal "<wsdl:apricot />", node.to_tag
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
context "with an XMLNode containing an attribute" do
|
69
|
+
should "return an empty element tag with an xmlns attribute" do
|
70
|
+
node = XMLNode.new("apricot")
|
71
|
+
node.attributes = { :wsdl => "http://example.com" }
|
72
|
+
|
73
|
+
assert_equal '<apricot xmlns:wsdl="http://example.com" />', node.to_tag
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
context "with an XMLNode containing a body" do
|
78
|
+
should "return an element with a body" do
|
79
|
+
node = XMLNode.new("apricot")
|
80
|
+
node.body = "eats Gorilla"
|
81
|
+
|
82
|
+
assert_equal '<apricot>eats Gorilla</apricot>', node.to_tag
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
end
|
@@ -8,7 +8,8 @@ class TestXmlToHash < Test::Unit::TestCase
|
|
8
8
|
s.sort_keys = true
|
9
9
|
s.disable_tag_names_to_lower_camel_case = false
|
10
10
|
s.disable_hash_keys_to_snake_case = false
|
11
|
-
s.
|
11
|
+
s.disable_hash_keys_to_symbols = false
|
12
|
+
s.nodes_to_namespace = nil
|
12
13
|
end
|
13
14
|
end
|
14
15
|
|
@@ -33,6 +34,16 @@ class TestXmlToHash < Test::Unit::TestCase
|
|
33
34
|
end
|
34
35
|
end
|
35
36
|
|
37
|
+
context "with XML containing namespaced nodes" do
|
38
|
+
should "remove namespaces from nodes" do
|
39
|
+
xml = "<return><wsdl:apricot><eats>Gorilla</eats></wsdl:apricot></return>"
|
40
|
+
expected = { :apricot => { :eats => "Gorilla" } }
|
41
|
+
|
42
|
+
result = ApricotEatsGorilla.xml_to_hash(xml)
|
43
|
+
assert_equal expected, result
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
36
47
|
context "with XML containing 'true' and 'false' Strings" do
|
37
48
|
should "convert these Strings into actual Boolean objects" do
|
38
49
|
xml = "<root><yes>true</yes><no>false</no><text>something</text></root>"
|
@@ -135,7 +146,7 @@ class TestXmlToHash < Test::Unit::TestCase
|
|
135
146
|
end
|
136
147
|
|
137
148
|
context "and converting Hash keys to Symbols turned off" do
|
138
|
-
setup { ApricotEatsGorilla.
|
149
|
+
setup { ApricotEatsGorilla.disable_hash_keys_to_symbols = true }
|
139
150
|
|
140
151
|
should "not convert Hash keys to Symbols" do
|
141
152
|
xml = "<contact><name>Jungle Julia</name><address/></contact>"
|
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.
|
4
|
+
version: 0.4.6
|
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:
|
25
|
+
description: SOAP communication helper.
|
26
26
|
email:
|
27
27
|
executables: []
|
28
28
|
|
@@ -33,6 +33,8 @@ extra_rdoc_files:
|
|
33
33
|
files:
|
34
34
|
- README.rdoc
|
35
35
|
- lib/apricoteatsgorilla.rb
|
36
|
+
- lib/apricoteatsgorilla/apricoteatsgorilla.rb
|
37
|
+
- lib/apricoteatsgorilla/xml_node.rb
|
36
38
|
has_rdoc: true
|
37
39
|
homepage: http://github.com/smacks/apricoteatsgorilla
|
38
40
|
post_install_message:
|
@@ -59,7 +61,7 @@ rubyforge_project:
|
|
59
61
|
rubygems_version: 1.2.0
|
60
62
|
signing_key:
|
61
63
|
specification_version: 2
|
62
|
-
summary:
|
64
|
+
summary: SOAP communication helper.
|
63
65
|
test_files:
|
64
66
|
- test/test_apricoteatsgorilla.rb
|
65
67
|
- test/helper.rb
|
@@ -67,3 +69,4 @@ test_files:
|
|
67
69
|
- test/apricoteatsgorilla/test_xml_to_hash.rb
|
68
70
|
- test/apricoteatsgorilla/test_shortcut_method.rb
|
69
71
|
- test/apricoteatsgorilla/test_soap_envelope.rb
|
72
|
+
- test/apricoteatsgorilla/test_xml_node.rb
|