troelskn-handsoap 0.1.2 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/README.markdown +4 -0
- data/VERSION.yml +2 -2
- data/generators/handsoap/handsoap_generator.rb +57 -285
- data/generators/handsoap/templates/DUMMY +0 -0
- data/lib/handsoap/compiler.rb +163 -0
- data/lib/handsoap/parser.rb +219 -0
- data/lib/handsoap/service.rb +87 -16
- data/lib/handsoap/xml_mason.rb +5 -0
- metadata +17 -6
- data/Rakefile +0 -17
- data/generators/handsoap/templates/gateway.rbt +0 -27
data/README.markdown
CHANGED
@@ -5,6 +5,10 @@ What
|
|
5
5
|
---
|
6
6
|
Handsoap is a library for creating SOAP clients in Ruby.
|
7
7
|
|
8
|
+
[Watch a tutorial](http://www.vimeo.com/4813848), showing how to use Handsoap. The final application can be found at: [http://github.com/troelskn/handsoap-example/tree/master](http://github.com/troelskn/handsoap-example/tree/master)
|
9
|
+
|
10
|
+

|
11
|
+
|
8
12
|
Why
|
9
13
|
---
|
10
14
|
|
data/VERSION.yml
CHANGED
@@ -1,30 +1,13 @@
|
|
1
|
-
|
2
|
-
require
|
3
|
-
require
|
4
|
-
|
5
|
-
|
6
|
-
#
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
@builders = {}
|
12
|
-
end
|
13
|
-
def add(type)
|
14
|
-
@builders[type] = false unless @builders[type]
|
15
|
-
end
|
16
|
-
def each
|
17
|
-
results = []
|
18
|
-
while builder = @builders.find { |builder,is_rendered| !is_rendered }
|
19
|
-
results << yield(@xsd.get_complex_type(builder[0]))
|
20
|
-
@builders[builder[0]] = true
|
21
|
-
end
|
22
|
-
results.join("")
|
23
|
-
end
|
24
|
-
end
|
25
|
-
|
26
|
-
class HandsoapGenerator < Rails::Generator::NamedBase
|
27
|
-
attr_reader :wsdl
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
require "#{File.dirname(__FILE__)}/../../lib/handsoap/parser.rb"
|
3
|
+
require "#{File.dirname(__FILE__)}/../../lib/handsoap/compiler.rb"
|
4
|
+
|
5
|
+
# TODO
|
6
|
+
# options:
|
7
|
+
# soap_actions (true/false)
|
8
|
+
# soap_version (1/2/auto)
|
9
|
+
# basename
|
10
|
+
class HandsoapGenerator < Rails::Generator::Base
|
28
11
|
def initialize(runtime_args, runtime_options = {})
|
29
12
|
super
|
30
13
|
# Wsdl argument is required.
|
@@ -33,281 +16,70 @@ class HandsoapGenerator < Rails::Generator::NamedBase
|
|
33
16
|
end
|
34
17
|
|
35
18
|
def banner
|
36
|
-
"
|
37
|
-
|
38
|
-
|
39
|
-
"\n" + " URI
|
19
|
+
"Generates the scaffold for a Handsoap binding."+
|
20
|
+
"\n" + "You still have to fill in most of the meat, but this gives you a head start." +
|
21
|
+
"\n" + "Usage: #{$0} #{spec.name} URI" +
|
22
|
+
"\n" + " URI URI of the WSDL to generate from"
|
40
23
|
end
|
41
24
|
|
42
25
|
def manifest
|
26
|
+
wsdl = Handsoap::Parser::Wsdl.read(@wsdl_uri)
|
27
|
+
protocol = wsdl.preferred_protocol
|
28
|
+
file_name = Handsoap::Compiler.service_basename(wsdl)
|
43
29
|
record do |m|
|
44
|
-
@wsdl = Handsoap::Wsdl.new(@wsdl_uri)
|
45
|
-
@wsdl.parse!
|
46
|
-
@xsd = Handsoap::XsdSpider.new(@wsdl_uri)
|
47
|
-
@xsd.process!
|
48
30
|
m.directory "app"
|
49
31
|
m.directory "app/models"
|
50
|
-
|
51
|
-
|
52
|
-
end
|
53
|
-
end
|
54
|
-
|
55
|
-
def builders
|
56
|
-
@builders
|
57
|
-
end
|
58
|
-
|
59
|
-
def render_build(context_name, message_type, varname = nil, indentation = ' ')
|
60
|
-
if varname.nil?
|
61
|
-
ruby_name = message_type.ruby_name
|
62
|
-
else
|
63
|
-
ruby_name = "#{varname}[:#{message_type.ruby_name}]"
|
64
|
-
end
|
65
|
-
# message_type.namespaces
|
66
|
-
if message_type.attribute?
|
67
|
-
"#{context_name}.set_attr " + '"' + message_type.name + '", ' + ruby_name
|
68
|
-
elsif message_type.boolean?
|
69
|
-
"#{context_name}.add " + '"' + message_type.name + '", bool_to_str(' + ruby_name + ')'
|
70
|
-
elsif message_type.primitive?
|
71
|
-
"#{context_name}.add " + '"' + message_type.name + '", ' + ruby_name
|
72
|
-
elsif message_type.list?
|
73
|
-
list_type = @xsd.get_complex_type(message_type.type)
|
74
|
-
builders.add(list_type.type)
|
75
|
-
# TODO: a naming conflict waiting to happen hereabout
|
76
|
-
# TODO: indentation
|
77
|
-
"#{varname}.each do |#{message_type.ruby_name}|" + "\n" + indentation +
|
78
|
-
" build_#{list_type.ruby_type}!(#{context_name}, #{message_type.ruby_name})" + "\n" + indentation +
|
79
|
-
"end"
|
80
|
-
else
|
81
|
-
builders.add(message_type.type)
|
82
|
-
"build_#{message_type.ruby_type}!(#{context_name}, " + ruby_name + ")"
|
83
|
-
end
|
84
|
-
end
|
85
|
-
|
86
|
-
end
|
87
|
-
|
88
|
-
module Handsoap
|
89
|
-
|
90
|
-
class Wsdl
|
91
|
-
attr_reader :uri, :soap_actions, :soap_ports, :target_namespace
|
92
|
-
def initialize(uri)
|
93
|
-
@uri = uri
|
94
|
-
end
|
95
|
-
|
96
|
-
def parse!
|
97
|
-
wsdl = Nokogiri.XML(Kernel.open(@uri).read)
|
98
|
-
@target_namespace = wsdl.namespaces['xmlns:tns'] || wsdl.namespaces['xmlns']
|
99
|
-
@soap_actions = []
|
100
|
-
@soap_ports = []
|
101
|
-
messages = {}
|
102
|
-
|
103
|
-
wsdl.xpath('//wsdl:message').each do |message|
|
104
|
-
message_name = message['name']
|
105
|
-
messages[message_name] = message.xpath('wsdl:part').map { |part| MessageType::Part.new(part['type'] || 'xs:element', part['name']) }
|
106
|
-
end
|
107
|
-
|
108
|
-
wsdl.xpath('//*[name()="soap:operation"]').each do |operation|
|
109
|
-
operation_name = operation.parent['name']
|
110
|
-
operation_spec = wsdl.xpath('//wsdl:operation[@name="' + operation_name + '"]').first
|
111
|
-
raise RuntimeError, "Couldn't find wsdl:operation node for #{operation_name}" if operation_spec.nil?
|
112
|
-
msg_type_in = operation_spec.xpath('./wsdl:input').first["message"]
|
113
|
-
raise RuntimeError, "Couldn't find wsdl:input node for #{operation_name}" if msg_type_in.nil?
|
114
|
-
raise RuntimeError, "Invalid message type #{msg_type_in} for #{operation_name}" if messages[msg_type_in].nil?
|
115
|
-
msg_type_out = operation_spec.xpath('./wsdl:output').first["message"]
|
116
|
-
raise RuntimeError, "Couldn't find wsdl:output node for #{operation_name}" if msg_type_out.nil?
|
117
|
-
raise RuntimeError, "Invalid message type #{msg_type_out} for #{operation_name}" if messages[msg_type_out].nil?
|
118
|
-
@soap_actions << SoapAction.new(operation, messages[msg_type_in], messages[msg_type_out])
|
32
|
+
m.file_contents "app/models/#{file_name}_service.rb" do |file|
|
33
|
+
file.write Handsoap::Compiler.compile_service(wsdl, protocol, :soap_actions)
|
119
34
|
end
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
location = port.xpath('./*[@location]').first['location']
|
125
|
-
@soap_ports << { :name => name, :soap_name => port['name'], :location => location }
|
35
|
+
m.directory "tests"
|
36
|
+
m.directory "tests/integration"
|
37
|
+
m.file_contents "tests/integration/#{file_name}_service_test.rb" do |file|
|
38
|
+
file.write Handsoap::Compiler.compile_test(wsdl, protocol)
|
126
39
|
end
|
40
|
+
# TODO
|
41
|
+
# Ask user about which endpoints to use ?
|
42
|
+
# puts "Detected endpoints:"
|
43
|
+
# puts Handsoap::Compiler.compile_endpoints(wsdl, protocol)
|
127
44
|
end
|
128
45
|
end
|
129
46
|
|
130
|
-
|
131
|
-
attr_reader :input_type, :output_type
|
132
|
-
def initialize(xml_node, input_type, output_type)
|
133
|
-
@xml_node = xml_node
|
134
|
-
@input_type = input_type
|
135
|
-
@output_type = output_type
|
136
|
-
end
|
137
|
-
def name
|
138
|
-
@xml_node.parent['name'].underscore
|
139
|
-
end
|
140
|
-
def soap_name
|
141
|
-
@xml_node.parent['name']
|
142
|
-
end
|
143
|
-
def href
|
144
|
-
@xml_node['soapAction']
|
145
|
-
end
|
146
|
-
end
|
147
|
-
|
148
|
-
module MessageType
|
47
|
+
end
|
149
48
|
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
def ruby_type
|
165
|
-
type.gsub(/^.*:/, "").underscore.gsub(/-/, '_')
|
166
|
-
end
|
167
|
-
def elements
|
168
|
-
@xml_node.xpath('./xs:attribute|./xs:all/xs:element|./xs:sequence').map do |node|
|
169
|
-
case
|
170
|
-
when node.node_name == 'attribute'
|
171
|
-
Attribute.new(node['type'], node['name'])
|
172
|
-
when node.node_name == 'element'
|
173
|
-
Element.new(node['type'], node['name'], []) # TODO: elements.elements
|
174
|
-
when node.node_name == 'sequence'
|
175
|
-
choice_node = node.xpath('./xs:choice').first
|
176
|
-
if choice_node
|
177
|
-
# TODO
|
178
|
-
Attribute.new('xs:choice', 'todo')
|
179
|
-
else
|
180
|
-
entity_node = node.xpath('./xs:element').first
|
181
|
-
Sequence.new(entity_node['type'], entity_node['name'])
|
182
|
-
end
|
183
|
-
else
|
184
|
-
puts node
|
185
|
-
raise "Unknown type #{node.node_name}"
|
49
|
+
module Handsoap #:nodoc:
|
50
|
+
module Generator #:nodoc:
|
51
|
+
module Commands #:nodoc:
|
52
|
+
module Create
|
53
|
+
def file_contents(relative_destination, &block)
|
54
|
+
destination = destination_path(relative_destination)
|
55
|
+
temp_file = Tempfile.new("handsoap_generator")
|
56
|
+
temp_file_relative_path = relative_path(temp_file.path, File.expand_path(source_path("/.")))
|
57
|
+
begin
|
58
|
+
yield temp_file
|
59
|
+
temp_file.close
|
60
|
+
return self.file(temp_file_relative_path, relative_destination)
|
61
|
+
ensure
|
62
|
+
temp_file.unlink
|
186
63
|
end
|
187
64
|
end
|
188
|
-
end
|
189
|
-
end
|
190
|
-
|
191
|
-
class Base
|
192
|
-
attr_reader :type, :name
|
193
|
-
def initialize(type, name)
|
194
|
-
raise "'type' can't be nil" if type.nil?
|
195
|
-
raise "'name' can't be nil" if name.nil?
|
196
|
-
@type = type
|
197
|
-
@name = name
|
198
|
-
end
|
199
|
-
def ruby_type
|
200
|
-
type.gsub(/^.*:/, "").underscore.gsub(/-/, '_')
|
201
|
-
end
|
202
|
-
def ruby_name
|
203
|
-
name.underscore.gsub(/-/, '_')
|
204
|
-
end
|
205
|
-
def attribute?
|
206
|
-
false
|
207
|
-
end
|
208
|
-
def primitive?
|
209
|
-
/^xs:/.match type
|
210
|
-
end
|
211
|
-
def boolean?
|
212
|
-
type == "xs:boolean"
|
213
|
-
end
|
214
|
-
def list?
|
215
|
-
false
|
216
|
-
end
|
217
|
-
end
|
218
|
-
|
219
|
-
# Parts are shallow elements
|
220
|
-
# <wsdl:part name="widget-instance-id" type="xs:int" />
|
221
|
-
class Part < Base
|
222
|
-
end
|
223
|
-
|
224
|
-
# <wsdl:part name="widget-instance-id" type="xs:int" />
|
225
|
-
# <xs:element maxOccurs="1" minOccurs="0" name="description" type="xs:string"/>
|
226
|
-
class Element < Base
|
227
|
-
attr_reader :elements
|
228
|
-
def initialize(type, name, elements = [])
|
229
|
-
super(type, name)
|
230
|
-
@elements = elements
|
231
|
-
end
|
232
|
-
end
|
233
|
-
|
234
|
-
# <xs:attribute name="id" type="xs:int" use="required"/>
|
235
|
-
class Attribute < Base
|
236
|
-
def primitive?
|
237
|
-
true
|
238
|
-
end
|
239
|
-
def attribute?
|
240
|
-
true
|
241
|
-
end
|
242
|
-
end
|
243
|
-
|
244
|
-
# <xs:sequence>
|
245
|
-
# <xs:element maxOccurs="unbounded" minOccurs="0" name="widget-area" type="WidgetArea"/>
|
246
|
-
# </xs:sequence>
|
247
|
-
class Sequence < Base
|
248
|
-
def list?
|
249
|
-
true
|
250
|
-
end
|
251
|
-
end
|
252
|
-
end
|
253
|
-
end
|
254
|
-
|
255
|
-
module Handsoap
|
256
|
-
|
257
|
-
class XsdSpider
|
258
|
-
def initialize(uri)
|
259
|
-
@queue = []
|
260
|
-
@wsdl_uri = uri
|
261
|
-
end
|
262
|
-
|
263
|
-
def results
|
264
|
-
@queue.map { |element| element[:data] }
|
265
|
-
end
|
266
65
|
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
66
|
+
private
|
67
|
+
|
68
|
+
# Convert the given absolute path into a path
|
69
|
+
# relative to the second given absolute path.
|
70
|
+
# http://www.justskins.com/forums/file-relative-path-handling-97116.html
|
71
|
+
def relative_path(abspath, relative_to)
|
72
|
+
path = abspath.split(File::SEPARATOR)
|
73
|
+
rel = relative_to.split(File::SEPARATOR)
|
74
|
+
while (path.length > 0) && (path.first == rel.first)
|
75
|
+
path.shift
|
76
|
+
rel.shift
|
77
|
+
end
|
78
|
+
('..' + File::SEPARATOR) * rel.length + path.join(File::SEPARATOR)
|
274
79
|
end
|
275
80
|
end
|
276
|
-
raise "Didn't find '#{name}' (short name #{short_name})"
|
277
|
-
end
|
278
|
-
|
279
|
-
def process!
|
280
|
-
spider_href(@wsdl_uri, nil)
|
281
|
-
while process_next do end
|
282
|
-
end
|
283
|
-
|
284
|
-
private
|
285
|
-
|
286
|
-
def add_href(href, namespace)
|
287
|
-
unless @queue.find { |element| element[:href] == href }
|
288
|
-
@queue << { :href => href, :namespace => namespace, :state => :new, :data => {} }
|
289
|
-
end
|
290
|
-
end
|
291
|
-
|
292
|
-
def process_next
|
293
|
-
next_element = @queue.find { |element| element[:state] == :new }
|
294
|
-
if next_element
|
295
|
-
next_element[:data] = spider_href(next_element[:href], next_element[:namespace])
|
296
|
-
next_element[:state] = :done
|
297
|
-
return true
|
298
|
-
end
|
299
|
-
return false
|
300
|
-
end
|
301
|
-
|
302
|
-
def spider_href(href, namespace)
|
303
|
-
raise "'href' must be a String" if href.nil?
|
304
|
-
xsd = Nokogiri.XML(Kernel.open(href).read)
|
305
|
-
# <xs:include schemaLocation="...xsd"/>
|
306
|
-
# <xs:import namespace="" schemaLocation="...xsd"/>
|
307
|
-
xsd.xpath('//*[@schemaLocation]').each do |inc|
|
308
|
-
add_href(inc['schemaLocation'], inc['namespace'] || namespace)
|
309
|
-
end
|
310
|
-
{ :document => xsd, :namespace => namespace }
|
311
81
|
end
|
312
82
|
end
|
313
83
|
end
|
84
|
+
|
85
|
+
Rails::Generator::Commands::Create.send :include, Handsoap::Generator::Commands::Create
|
File without changes
|
@@ -0,0 +1,163 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
module Handsoap
|
3
|
+
module Compiler
|
4
|
+
class CodeWriter
|
5
|
+
|
6
|
+
def initialize
|
7
|
+
@buffer = ""
|
8
|
+
@indentation = 0
|
9
|
+
end
|
10
|
+
|
11
|
+
def begin(text)
|
12
|
+
puts(text)
|
13
|
+
indent
|
14
|
+
end
|
15
|
+
|
16
|
+
def end(str = "end")
|
17
|
+
unindent
|
18
|
+
puts(str)
|
19
|
+
end
|
20
|
+
|
21
|
+
def puts(text = "")
|
22
|
+
@buffer << text.gsub(/^(.*)$/, (" " * @indentation) + "\\1")
|
23
|
+
@buffer << "\n" # unless @buffer.match(/\n$/)
|
24
|
+
end
|
25
|
+
|
26
|
+
def indent
|
27
|
+
@indentation = @indentation + 1
|
28
|
+
end
|
29
|
+
|
30
|
+
def unindent
|
31
|
+
@indentation = @indentation - 1
|
32
|
+
end
|
33
|
+
|
34
|
+
def to_s
|
35
|
+
@buffer
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def self.write
|
40
|
+
writer = CodeWriter.new
|
41
|
+
yield writer
|
42
|
+
writer.to_s
|
43
|
+
end
|
44
|
+
|
45
|
+
def self.underscore(camel_cased_word)
|
46
|
+
camel_cased_word.to_s.gsub(/::/, '/').
|
47
|
+
gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
|
48
|
+
gsub(/([a-z\d])([A-Z])/,'\1_\2').
|
49
|
+
tr("-", "_").
|
50
|
+
downcase
|
51
|
+
end
|
52
|
+
|
53
|
+
def self.camelize(lower_case_and_underscored_word)
|
54
|
+
lower_case_and_underscored_word.to_s.gsub(/\/(.?)/) {
|
55
|
+
"::" + $1.upcase
|
56
|
+
}.gsub(/(^|_)(.)/) {
|
57
|
+
$2.upcase
|
58
|
+
}
|
59
|
+
end
|
60
|
+
|
61
|
+
def self.method_name(operation)
|
62
|
+
if operation.name.match /^(get|find|select|fetch)/i
|
63
|
+
"#{underscore(operation.name)}"
|
64
|
+
else
|
65
|
+
"#{underscore(operation.name)}!"
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def self.service_basename(wsdl)
|
70
|
+
wsdl.service.gsub(/service$/, "")
|
71
|
+
end
|
72
|
+
|
73
|
+
def self.service_name(wsdl)
|
74
|
+
camelize(service_basename(wsdl)) + "Service"
|
75
|
+
end
|
76
|
+
|
77
|
+
def self.endpoint_name(wsdl)
|
78
|
+
"#{service_basename(wsdl).upcase}_SERVICE_ENDPOINT"
|
79
|
+
end
|
80
|
+
|
81
|
+
def self.detect_protocol(wsdl)
|
82
|
+
if endpoints.select { |endpoint| endpoint.protocol == :soap12 }.any?
|
83
|
+
:soap12
|
84
|
+
elsif endpoints.select { |endpoint| endpoint.protocol == :soap11 }.any?
|
85
|
+
:soap11
|
86
|
+
else
|
87
|
+
raise "Can't find any soap 1.1 or soap 1.2 endpoints"
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
def self.compile_endpoints(wsdl, protocol)
|
92
|
+
version = protocol == :soap12 ? 2 : 1
|
93
|
+
wsdl.endpoints.select { |endpoint| endpoint.protocol == protocol }.map do |endpoint|
|
94
|
+
write do |w|
|
95
|
+
w.puts "# wsdl: #{wsdl.url}"
|
96
|
+
w.begin "#{endpoint_name(wsdl)} = {"
|
97
|
+
w.puts ":uri => '#{endpoint.url}',"
|
98
|
+
w.puts ":version => #{version}"
|
99
|
+
w.end "}"
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
def self.compile_service(wsdl, protocol, *options)
|
105
|
+
binding = wsdl.bindings.find { |b| b.protocol == protocol }
|
106
|
+
raise "Can't find binding for requested protocol (#{protocol})" unless binding
|
107
|
+
write do |w|
|
108
|
+
w.puts "# -*- coding: utf-8 -*-"
|
109
|
+
w.puts "require 'handsoap'"
|
110
|
+
w.puts
|
111
|
+
w.begin "class #{service_name(wsdl)} < Handsoap::Service"
|
112
|
+
w.puts "endpoint #{endpoint_name(wsdl)}"
|
113
|
+
w.begin "on_create_document do |doc|"
|
114
|
+
w.puts "doc.alias 'tns', '#{wsdl.target_ns}'"
|
115
|
+
w.end
|
116
|
+
w.puts
|
117
|
+
w.puts "# public methods"
|
118
|
+
wsdl.interface.operations.each do |operation|
|
119
|
+
action = binding.actions.find { |a| a.name == operation.name }
|
120
|
+
raise "Can't find action for operation #{operation.name}" unless action
|
121
|
+
w.puts
|
122
|
+
w.begin "def #{method_name(operation)}"
|
123
|
+
# TODO allow :soap_action => :none
|
124
|
+
if operation.name != action.soap_action && options.include?(:soap_actions)
|
125
|
+
w.puts "soap_action = '#{action.soap_action}'"
|
126
|
+
maybe_soap_action = ", soap_action"
|
127
|
+
else
|
128
|
+
maybe_soap_action = ""
|
129
|
+
end
|
130
|
+
w.begin((operation.output ? 'response = ' : '') + "invoke('tns:#{operation.name}'#{maybe_soap_action}) do |message|")
|
131
|
+
w.puts 'raise "TODO"'
|
132
|
+
w.end
|
133
|
+
w.end
|
134
|
+
end
|
135
|
+
w.puts
|
136
|
+
w.puts "private"
|
137
|
+
w.puts "# helpers"
|
138
|
+
w.puts "# TODO"
|
139
|
+
w.end
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
def self.compile_test(wsdl, protocol)
|
144
|
+
binding = wsdl.bindings.find { |b| b.protocol == protocol }
|
145
|
+
raise "Can't find binding for requested protocol (#{protocol})" unless binding
|
146
|
+
write do |w|
|
147
|
+
w.puts "# -*- coding: utf-8 -*-"
|
148
|
+
w.puts "require 'test_helper'"
|
149
|
+
w.puts
|
150
|
+
w.puts "# #{service_name(wsdl)}.logger = $stdout"
|
151
|
+
w.puts
|
152
|
+
w.begin "class #{service_name(wsdl)}Test < Test::Unit::TestCase"
|
153
|
+
wsdl.interface.operations.each do |operation|
|
154
|
+
w.puts
|
155
|
+
w.begin "def test_#{underscore(operation.name)}"
|
156
|
+
w.puts "result = #{service_name(wsdl)}.#{method_name(operation)}"
|
157
|
+
w.end
|
158
|
+
end
|
159
|
+
w.end
|
160
|
+
end
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|
@@ -0,0 +1,219 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
require 'rubygems'
|
3
|
+
require 'open-uri'
|
4
|
+
require 'uri'
|
5
|
+
require 'nokogiri'
|
6
|
+
|
7
|
+
module Handsoap
|
8
|
+
module Parser
|
9
|
+
class Interface
|
10
|
+
attr_accessor :name, :operations
|
11
|
+
|
12
|
+
def initialize(name, operations = [])
|
13
|
+
@name = name
|
14
|
+
@operations = operations || []
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
class Binding
|
19
|
+
attr_accessor :name, :protocol, :interface, :transport, :style, :encoding, :verb, :actions
|
20
|
+
|
21
|
+
def initialize(name, optional = {})
|
22
|
+
@name = name
|
23
|
+
@actions = optional[:actions] || []
|
24
|
+
@protocol = optional[:protocol]
|
25
|
+
@interface = optional[:interface]
|
26
|
+
@transport = optional[:transport]
|
27
|
+
@style = optional[:style]
|
28
|
+
@encoding = optional[:encoding]
|
29
|
+
@verb = optional[:verb]
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
class Endpoint
|
34
|
+
attr_accessor :name, :protocol, :binding, :url
|
35
|
+
|
36
|
+
def initialize(name, protocol, binding, url)
|
37
|
+
@name = name
|
38
|
+
@protocol = protocol
|
39
|
+
@binding = binding
|
40
|
+
@url = url
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
class Operation
|
45
|
+
attr_accessor :name, :input, :output
|
46
|
+
|
47
|
+
def initialize(name, optional = {})
|
48
|
+
@name = name
|
49
|
+
@input = optional[:input]
|
50
|
+
@output = optional[:output]
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
class Action
|
55
|
+
attr_accessor :name, :soap_action, :location
|
56
|
+
|
57
|
+
def initialize(name, optional = {})
|
58
|
+
@name = name
|
59
|
+
@soap_action = optional[:soap_action]
|
60
|
+
@location = optional[:location]
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
class Wsdl
|
65
|
+
attr_reader :url
|
66
|
+
|
67
|
+
def initialize(doc, url = "void://")
|
68
|
+
@doc = doc
|
69
|
+
@url = url
|
70
|
+
end
|
71
|
+
|
72
|
+
def self.read(url)
|
73
|
+
self.new(Nokogiri.XML(Kernel.open(url).read), url)
|
74
|
+
end
|
75
|
+
|
76
|
+
def ns
|
77
|
+
{
|
78
|
+
'wsdl1' => "http://schemas.xmlsoap.org/wsdl/",
|
79
|
+
'wsdl2' => "http://www.w3.org/ns/wsdl/",
|
80
|
+
'soap11' => "http://schemas.xmlsoap.org/wsdl/soap/",
|
81
|
+
'soap12' => "http://schemas.xmlsoap.org/wsdl/soap12/",
|
82
|
+
'http' => "http://schemas.xmlsoap.org/wsdl/http/"
|
83
|
+
}
|
84
|
+
end
|
85
|
+
private :ns
|
86
|
+
|
87
|
+
def protocol_from_ns(node)
|
88
|
+
case @doc.namespaces["xmlns:#{node.namespace}"]
|
89
|
+
when "http://schemas.xmlsoap.org/wsdl/soap/"
|
90
|
+
:soap11
|
91
|
+
when "http://schemas.xmlsoap.org/wsdl/soap12/"
|
92
|
+
:soap12
|
93
|
+
when "http://schemas.xmlsoap.org/wsdl/http/"
|
94
|
+
:http
|
95
|
+
else
|
96
|
+
raise "Unknown namespace '#{node.namespace}'"
|
97
|
+
end
|
98
|
+
end
|
99
|
+
private :protocol_from_ns
|
100
|
+
|
101
|
+
def is_wsdl2?(node)
|
102
|
+
case @doc.namespaces["xmlns:#{node.namespace}"]
|
103
|
+
when "http://schemas.xmlsoap.org/wsdl/"
|
104
|
+
false
|
105
|
+
when "http://www.w3.org/ns/wsdl/"
|
106
|
+
true
|
107
|
+
else
|
108
|
+
raise "Unknown namespace '#{node.namespace}'"
|
109
|
+
end
|
110
|
+
end
|
111
|
+
private :is_wsdl2?
|
112
|
+
|
113
|
+
def service
|
114
|
+
services = @doc.xpath("//wsdl1:service|//wsdl2:service", ns)
|
115
|
+
raise "Expected exactly 1 service in WSDL" if services.length != 1
|
116
|
+
services[0][:name]
|
117
|
+
end
|
118
|
+
|
119
|
+
def interface
|
120
|
+
tmp = interfaces
|
121
|
+
raise "Expected exactly 1 interface/portType in WSDL" if tmp.length != 1
|
122
|
+
tmp[0]
|
123
|
+
end
|
124
|
+
|
125
|
+
def target_ns
|
126
|
+
@doc.root[:targetNamespace] || raise("Attribute targetNamespace not defined")
|
127
|
+
end
|
128
|
+
|
129
|
+
def preferred_protocol
|
130
|
+
e = endpoints
|
131
|
+
if e.select { |endpoint| endpoint.protocol == :soap12 }.any?
|
132
|
+
:soap12
|
133
|
+
elsif e.select { |endpoint| endpoint.protocol == :soap11 }.any?
|
134
|
+
:soap11
|
135
|
+
else
|
136
|
+
raise "Can't find any soap 1.1 or soap 1.2 endpoints"
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
def interfaces
|
141
|
+
@doc.xpath("//wsdl1:portType|//wsdl2:interface", ns).map do |port_type|
|
142
|
+
operations = port_type.xpath("./wsdl1:operation|./wsdl2:operation", ns).map do |operation|
|
143
|
+
if is_wsdl2?(operation)
|
144
|
+
input_node = operation.xpath("./wsdl2:input", ns).first
|
145
|
+
input = input_node ? input_node[:element] : nil
|
146
|
+
output_node = operation.xpath("./wsdl2:output", ns).first
|
147
|
+
output = output_node ? output_node[:element] : nil
|
148
|
+
else
|
149
|
+
input_node = operation.xpath("./wsdl1:input", ns).first
|
150
|
+
input = input_node ? input_node[:message] : nil
|
151
|
+
output_node = operation.xpath("./wsdl1:output", ns).first
|
152
|
+
output = output_node ? output_node[:message] : nil
|
153
|
+
end
|
154
|
+
Operation.new(operation[:name], :input => input, :output => output)
|
155
|
+
end
|
156
|
+
Interface.new(port_type[:name], operations)
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
def endpoints
|
161
|
+
@doc.xpath("//wsdl1:service/wsdl1:port|//wsdl2:service/wsdl2:endpoint", ns).map do |port|
|
162
|
+
binding = port[:binding]
|
163
|
+
if is_wsdl2?(port)
|
164
|
+
location = port[:address]
|
165
|
+
protocol = :binding
|
166
|
+
else
|
167
|
+
address = port.xpath("./soap11:address|./soap12:address|./http:address", ns).first
|
168
|
+
location = address[:location]
|
169
|
+
protocol = protocol_from_ns(address)
|
170
|
+
end
|
171
|
+
Endpoint.new(port[:name], protocol, binding, location)
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
def bindings
|
176
|
+
@doc.xpath("//wsdl1:binding|//wsdl2:binding", ns).map do |binding|
|
177
|
+
raise "WSDL 2.0 not supported" if is_wsdl2?(binding)
|
178
|
+
soap_binding = binding.xpath("./soap11:binding|./soap12:binding|./http:binding", ns).first
|
179
|
+
protocol = protocol_from_ns(soap_binding)
|
180
|
+
actions = []
|
181
|
+
style = nil
|
182
|
+
encoding = nil
|
183
|
+
actions = binding.xpath("./wsdl1:operation", ns).map do |operation|
|
184
|
+
soap_operation = operation.xpath("./soap11:operation|./soap12:operation|./http:operation", ns).first
|
185
|
+
if soap_operation[:style]
|
186
|
+
raise "Mixed styles not supported" if style && style != soap_operation[:style]
|
187
|
+
style = soap_operation[:style]
|
188
|
+
end
|
189
|
+
xquery = []
|
190
|
+
['soap11', 'soap12', 'http'].each do |version|
|
191
|
+
['input', 'output'].each do |message_name|
|
192
|
+
['header', 'body'].each do |part_name|
|
193
|
+
xquery << "./wsdl1:#{message_name}/#{version}:#{part_name}"
|
194
|
+
end
|
195
|
+
end
|
196
|
+
end
|
197
|
+
operation.xpath(xquery.join('|'), ns).each do |thing|
|
198
|
+
raise "Mixed encodings not supported" if encoding && encoding != thing[:use]
|
199
|
+
encoding = thing[:use]
|
200
|
+
end
|
201
|
+
Action.new(
|
202
|
+
operation[:name],
|
203
|
+
:soap_action => soap_operation[:soapAction],
|
204
|
+
:location => soap_operation[:location])
|
205
|
+
end
|
206
|
+
Binding.new(
|
207
|
+
binding[:name],
|
208
|
+
:protocol => protocol,
|
209
|
+
:interface => binding[:type],
|
210
|
+
:transport => soap_binding[:transport],
|
211
|
+
:style => style,
|
212
|
+
:encoding => encoding,
|
213
|
+
:verb => soap_binding[:verb],
|
214
|
+
:actions => actions)
|
215
|
+
end
|
216
|
+
end
|
217
|
+
end
|
218
|
+
end
|
219
|
+
end
|
data/lib/handsoap/service.rb
CHANGED
@@ -4,6 +4,18 @@ require 'httpclient'
|
|
4
4
|
require 'nokogiri'
|
5
5
|
require 'curb'
|
6
6
|
require 'handsoap/xml_mason'
|
7
|
+
require 'time'
|
8
|
+
require 'htmlentities'
|
9
|
+
|
10
|
+
# Nokogiri doesn't have a way of getting plain strings out,
|
11
|
+
# so this monkeypatch adds that capability ..
|
12
|
+
module Utf8StringPatch
|
13
|
+
def to_utf8
|
14
|
+
HTMLEntities.decode_entities(self.serialize('UTF-8'))
|
15
|
+
end
|
16
|
+
end
|
17
|
+
Nokogiri::XML::Text.send :include, Utf8StringPatch
|
18
|
+
Nokogiri::XML::Nodeset.send :include, Utf8StringPatch
|
7
19
|
|
8
20
|
module Handsoap
|
9
21
|
|
@@ -47,10 +59,11 @@ module Handsoap
|
|
47
59
|
end
|
48
60
|
|
49
61
|
class Fault < Exception
|
50
|
-
attr_reader :code, :reason
|
51
|
-
def initialize(code, reason)
|
62
|
+
attr_reader :code, :reason, :details
|
63
|
+
def initialize(code, reason, details)
|
52
64
|
@code = code
|
53
65
|
@reason = reason
|
66
|
+
@details = details
|
54
67
|
end
|
55
68
|
def to_s
|
56
69
|
"Handsoap::Fault { :code => '#{@code}', :reason => '#{@reason}' }"
|
@@ -68,7 +81,8 @@ module Handsoap
|
|
68
81
|
if reason == ""
|
69
82
|
reason = node.xpath('./faultstring/text()', ns).to_s
|
70
83
|
end
|
71
|
-
|
84
|
+
details = node.xpath('./detail/*', ns)
|
85
|
+
self.new(fault_code, reason, details)
|
72
86
|
end
|
73
87
|
end
|
74
88
|
|
@@ -87,6 +101,9 @@ module Handsoap
|
|
87
101
|
end
|
88
102
|
SOAP_NAMESPACE[@protocol_version]
|
89
103
|
end
|
104
|
+
def self.request_content_type
|
105
|
+
@protocol_version == 1 ? "text/xml" : "application/soap+xml"
|
106
|
+
end
|
90
107
|
def self.map_method(mapping)
|
91
108
|
if @mapping.nil?
|
92
109
|
@mapping = {}
|
@@ -126,23 +143,76 @@ module Handsoap
|
|
126
143
|
super
|
127
144
|
end
|
128
145
|
end
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
146
|
+
def invoke(action, options = { :soap_action => :auto }, &block)
|
147
|
+
if action
|
148
|
+
if options.kind_of? String
|
149
|
+
options = { :soap_action => options }
|
150
|
+
end
|
151
|
+
if options[:soap_action] == :auto
|
152
|
+
options[:soap_action] = action.gsub(/^.+:/, "")
|
153
|
+
elsif options[:soap_action] == :none
|
154
|
+
options[:soap_action] = nil
|
155
|
+
end
|
156
|
+
doc = make_envelope do |body|
|
157
|
+
body.add action
|
158
|
+
end
|
159
|
+
if block_given?
|
160
|
+
yield doc.find(action)
|
161
|
+
end
|
162
|
+
dispatch(doc, options[:soap_action])
|
163
|
+
end
|
164
|
+
end
|
140
165
|
def on_before_dispatch
|
141
166
|
end
|
142
167
|
def on_fault(fault)
|
143
168
|
raise fault
|
144
169
|
end
|
145
170
|
private
|
171
|
+
# Helper to serialize a node into a ruby string
|
172
|
+
def xml_to_str(node, xquery = nil)
|
173
|
+
begin
|
174
|
+
n = xquery ? node.xpath(xquery, ns).first : node
|
175
|
+
n.to_utf8
|
176
|
+
rescue Exception => ex
|
177
|
+
nil
|
178
|
+
end
|
179
|
+
end
|
180
|
+
# Helper to serialize a node into a ruby integer
|
181
|
+
def xml_to_int(node, xquery = nil)
|
182
|
+
begin
|
183
|
+
n = xquery ? node.xpath(xquery, ns).first : node
|
184
|
+
n.to_s.to_i
|
185
|
+
rescue Exception => ex
|
186
|
+
nil
|
187
|
+
end
|
188
|
+
end
|
189
|
+
# Helper to serialize a node into a ruby float
|
190
|
+
def xml_to_float(node, xquery = nil)
|
191
|
+
begin
|
192
|
+
n = xquery ? node.xpath(xquery, ns).first : node
|
193
|
+
n.to_s.to_f
|
194
|
+
rescue Exception => ex
|
195
|
+
nil
|
196
|
+
end
|
197
|
+
end
|
198
|
+
# Helper to serialize a node into a ruby boolean
|
199
|
+
def xml_to_bool(node, xquery = nil)
|
200
|
+
begin
|
201
|
+
n = xquery ? node.xpath(xquery, ns).first : node
|
202
|
+
n.to_s == "true"
|
203
|
+
rescue Exception => ex
|
204
|
+
nil
|
205
|
+
end
|
206
|
+
end
|
207
|
+
# Helper to serialize a node into a ruby Time object
|
208
|
+
def xml_to_date(node, xquery = nil)
|
209
|
+
begin
|
210
|
+
n = xquery ? node.xpath(xquery, ns).first : node
|
211
|
+
Time.iso8601(n.to_s)
|
212
|
+
rescue Exception => ex
|
213
|
+
nil
|
214
|
+
end
|
215
|
+
end
|
146
216
|
def debug(message = nil)
|
147
217
|
if @@logger
|
148
218
|
if message
|
@@ -153,11 +223,12 @@ module Handsoap
|
|
153
223
|
end
|
154
224
|
end
|
155
225
|
end
|
156
|
-
def dispatch(doc)
|
226
|
+
def dispatch(doc, action)
|
157
227
|
on_before_dispatch()
|
158
228
|
headers = {
|
159
|
-
"Content-Type" => "
|
229
|
+
"Content-Type" => "#{self.class.request_content_type};charset=UTF-8"
|
160
230
|
}
|
231
|
+
headers["SOAPAction"] = action unless action.nil?
|
161
232
|
body = doc.to_s
|
162
233
|
debug do |logger|
|
163
234
|
logger.puts "==============="
|
data/lib/handsoap/xml_mason.rb
CHANGED
@@ -96,6 +96,9 @@ module Handsoap
|
|
96
96
|
yield self
|
97
97
|
end
|
98
98
|
end
|
99
|
+
def document
|
100
|
+
@parent.respond_to?(:document) ? @parent.document : @parent
|
101
|
+
end
|
99
102
|
def full_name
|
100
103
|
@prefix.nil? ? @node_name : (@prefix + ":" + @node_name)
|
101
104
|
end
|
@@ -117,6 +120,7 @@ module Handsoap
|
|
117
120
|
@attributes[name] = value
|
118
121
|
end
|
119
122
|
def find(name)
|
123
|
+
name = name.to_s if name.kind_of? Symbol
|
120
124
|
if @node_name == name || full_name == name
|
121
125
|
return self
|
122
126
|
end
|
@@ -131,6 +135,7 @@ module Handsoap
|
|
131
135
|
return nil
|
132
136
|
end
|
133
137
|
def find_all(name)
|
138
|
+
name = name.to_s if name.kind_of? Symbol
|
134
139
|
result = []
|
135
140
|
if @node_name == name || full_name == name
|
136
141
|
result << self
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: troelskn-handsoap
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Troels Knak-Nielsen
|
@@ -9,7 +9,7 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date: 2009-
|
12
|
+
date: 2009-06-07 00:00:00 -07:00
|
13
13
|
default_executable:
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
@@ -32,6 +32,16 @@ dependencies:
|
|
32
32
|
- !ruby/object:Gem::Version
|
33
33
|
version: 0.3.2
|
34
34
|
version:
|
35
|
+
- !ruby/object:Gem::Dependency
|
36
|
+
name: htmlentities
|
37
|
+
type: :runtime
|
38
|
+
version_requirement:
|
39
|
+
version_requirements: !ruby/object:Gem::Requirement
|
40
|
+
requirements:
|
41
|
+
- - ">="
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: 4.0.0
|
44
|
+
version:
|
35
45
|
description: Handsoap is a library for creating SOAP clients in Ruby
|
36
46
|
email: troelskn@gmail.com
|
37
47
|
executables: []
|
@@ -42,15 +52,16 @@ extra_rdoc_files:
|
|
42
52
|
- README.markdown
|
43
53
|
files:
|
44
54
|
- README.markdown
|
45
|
-
- Rakefile
|
46
55
|
- VERSION.yml
|
47
56
|
- generators/handsoap/USAGE
|
48
57
|
- generators/handsoap/handsoap_generator.rb
|
49
|
-
- generators/handsoap/templates/
|
58
|
+
- generators/handsoap/templates/DUMMY
|
50
59
|
- lib/handsoap.rb
|
60
|
+
- lib/handsoap/compiler.rb
|
61
|
+
- lib/handsoap/parser.rb
|
51
62
|
- lib/handsoap/service.rb
|
52
63
|
- lib/handsoap/xml_mason.rb
|
53
|
-
has_rdoc:
|
64
|
+
has_rdoc: false
|
54
65
|
homepage: http://github.com/troelskn/handsoap
|
55
66
|
post_install_message:
|
56
67
|
rdoc_options:
|
@@ -74,7 +85,7 @@ requirements: []
|
|
74
85
|
rubyforge_project:
|
75
86
|
rubygems_version: 1.2.0
|
76
87
|
signing_key:
|
77
|
-
specification_version:
|
88
|
+
specification_version: 3
|
78
89
|
summary: Handsoap is a library for creating SOAP clients in Ruby
|
79
90
|
test_files: []
|
80
91
|
|
data/Rakefile
DELETED
@@ -1,17 +0,0 @@
|
|
1
|
-
# -*- coding: utf-8 -*-
|
2
|
-
begin
|
3
|
-
require 'jeweler'
|
4
|
-
Jeweler::Tasks.new do |gemspec|
|
5
|
-
gemspec.name = "handsoap"
|
6
|
-
gemspec.summary = "Handsoap is a library for creating SOAP clients in Ruby"
|
7
|
-
gemspec.email = "troelskn@gmail.com"
|
8
|
-
gemspec.homepage = "http://github.com/troelskn/handsoap"
|
9
|
-
gemspec.description = gemspec.summary
|
10
|
-
gemspec.authors = ["Troels Knak-Nielsen"]
|
11
|
-
gemspec.add_dependency "nokogiri", ">= 1.2.3"
|
12
|
-
gemspec.add_dependency "curb", ">= 0.3.2"
|
13
|
-
# gemspec.add_dependency "httpclient", ">= 2.1.2"
|
14
|
-
end
|
15
|
-
rescue LoadError
|
16
|
-
puts "Jeweler not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
|
17
|
-
end
|
@@ -1,27 +0,0 @@
|
|
1
|
-
# -*- coding: utf-8 -*-
|
2
|
-
require 'handsoap'
|
3
|
-
|
4
|
-
class <%= class_name %>Service < Handsoap::Service
|
5
|
-
endpoint <%= file_name.upcase %>_SERVICE_ENDPOINT
|
6
|
-
on_create_document do |doc|
|
7
|
-
doc.alias 'wsdl', "<%= wsdl.target_namespace %>"
|
8
|
-
end
|
9
|
-
<% wsdl.soap_actions.each do |action| %>
|
10
|
-
def <%= action.name %>(<%= action.input_type.map { |message_type| message_type.ruby_name }.join(", ") %>)
|
11
|
-
response = invoke("wsdl:<%= action.soap_name %>") do |context|<% action.input_type.each do |message_type| %>
|
12
|
-
<%= render_build('context', message_type) %><% end %>
|
13
|
-
end
|
14
|
-
response.document.xpath('//*').map { |node| raise "TODO" }
|
15
|
-
end
|
16
|
-
<% end %>
|
17
|
-
private
|
18
|
-
# builders
|
19
|
-
<% builders.each do |type| %>
|
20
|
-
# <%= type.type %> ruby -> xml
|
21
|
-
def build_<%= type.ruby_type %>!(context, <%= type.ruby_type %>)<% type.elements.each do |element| %>
|
22
|
-
<%= render_build('context', element, type.ruby_type) %><% end %>
|
23
|
-
end
|
24
|
-
<% end %>
|
25
|
-
# parsers
|
26
|
-
# TODO
|
27
|
-
end
|