smacks-apricoteatsgorilla 0.4.5 → 0.4.6
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 -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
|