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 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,4 +1,4 @@
1
1
  ---
2
- :patch: 2
2
+ :minor: 2
3
+ :patch: 0
3
4
  :major: 0
4
- :minor: 1
@@ -1,30 +1,13 @@
1
- require 'open-uri'
2
- require 'uri'
3
- require 'cgi'
4
- require 'nokogiri'
5
-
6
- # TODO: inline builders, if they are only ever used in one place
7
-
8
- class Builders
9
- def initialize(xsd)
10
- @xsd = xsd
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
- "WARNING: This generator is rather incomplete and buggy. Use at your own risk." +
37
- "\n" + "Usage: #{$0} #{spec.name} name URI [options]" +
38
- "\n" + " name Basename of the service class" +
39
- "\n" + " URI URI of the WSDL to generate from"
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
- @builders = Builders.new(@xsd)
51
- m.template "gateway.rbt", "app/models/#{file_name}_service.rb"
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
- raise RuntimeError, "Could not parse WSDL" if soap_actions.empty?
121
-
122
- wsdl.xpath('//wsdl:port', {"xmlns:wsdl" => 'http://schemas.xmlsoap.org/wsdl/'}).each do |port|
123
- name = port['name'].underscore
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
- class SoapAction
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
- # complex-type is a spec (class), not an element ... (object)
151
- # <xs:complexType name="User">
152
- # <xs:annotation>
153
- # <xs:documentation>The element specifies a user</xs:documentation>
154
- # </xs:annotation>
155
- # <xs:attribute name="dn" type="xs:string" use="required"/>
156
- # </xs:complexType>
157
- class ComplexType
158
- def initialize(xml_node)
159
- @xml_node = xml_node
160
- end
161
- def type
162
- @xml_node['name']
163
- end
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
- def get_complex_type(name)
268
- # TODO namespace
269
- short_name = name.gsub(/^.*:/, "")
270
- results.each do |data|
271
- search = data[:document].xpath('//xs:complexType[@name="' + short_name + '"]')
272
- if search.any?
273
- return MessageType::ComplexType.new(search.first)
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
@@ -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
- self.new(fault_code, reason)
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
- def invoke(action, &block)
130
- if action
131
- doc = make_envelope do |body|
132
- body.add action
133
- end
134
- if block_given?
135
- yield doc.find(action)
136
- end
137
- dispatch doc
138
- end
139
- end
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" => "text/xml;charset=UTF-8"
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 "==============="
@@ -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.1.2
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-04-29 00:00:00 -07:00
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/gateway.rbt
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: true
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: 2
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