troelskn-handsoap 0.1.2 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.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
|
+
![Handsoap](http://ny-image0.etsy.com/il_430xN.68558416.jpg)
|
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
|