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 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