troelskn-handsoap 0.1.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/README.markdown +4 -0
- data/VERSION.yml +1 -1
- data/generators/handsoap/USAGE +0 -0
- data/generators/handsoap/handsoap_generator.rb +313 -0
- data/generators/handsoap/templates/gateway.rbt +27 -0
- data/lib/handsoap/xml_mason.rb +12 -10
- metadata +5 -2
data/README.markdown
CHANGED
@@ -111,6 +111,10 @@ The configuration could look like this:
|
|
111
111
|
:version => 2
|
112
112
|
}
|
113
113
|
|
114
|
+
If you use Rails, you will need to load the gem from the `config/environment.rb` file, using:
|
115
|
+
|
116
|
+
config.gem 'troelskn-handsoap', :lib => 'handsoap', :source => "http://gems.github.com"
|
117
|
+
|
114
118
|
###Service class
|
115
119
|
|
116
120
|
Put your service in a file under `app/models`. You should extend `Handsoap::Service`.
|
data/VERSION.yml
CHANGED
File without changes
|
@@ -0,0 +1,313 @@
|
|
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
|
28
|
+
def initialize(runtime_args, runtime_options = {})
|
29
|
+
super
|
30
|
+
# Wsdl argument is required.
|
31
|
+
usage if @args.empty?
|
32
|
+
@wsdl_uri = @args.shift
|
33
|
+
end
|
34
|
+
|
35
|
+
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"
|
40
|
+
end
|
41
|
+
|
42
|
+
def manifest
|
43
|
+
record do |m|
|
44
|
+
@wsdl = Handsoap::Wsdl.new(@wsdl_uri)
|
45
|
+
@wsdl.parse!
|
46
|
+
@xsd = Handsoap::XsdSpider.new(@wsdl_uri)
|
47
|
+
@xsd.process!
|
48
|
+
m.directory "app"
|
49
|
+
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])
|
119
|
+
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 }
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
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
|
149
|
+
|
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}"
|
186
|
+
end
|
187
|
+
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
|
+
|
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)
|
274
|
+
end
|
275
|
+
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
|
+
end
|
312
|
+
end
|
313
|
+
end
|
@@ -0,0 +1,27 @@
|
|
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
|
data/lib/handsoap/xml_mason.rb
CHANGED
@@ -14,9 +14,9 @@ module Handsoap
|
|
14
14
|
def initialize
|
15
15
|
@namespaces = {}
|
16
16
|
end
|
17
|
-
def add(node_name, value = nil)
|
17
|
+
def add(node_name, value = nil, options = {})
|
18
18
|
prefix, name = parse_ns(node_name)
|
19
|
-
node = append_child Element.new(self, prefix, name, value)
|
19
|
+
node = append_child Element.new(self, prefix, name, value, options)
|
20
20
|
if block_given?
|
21
21
|
yield node
|
22
22
|
end
|
@@ -73,13 +73,13 @@ module Handsoap
|
|
73
73
|
def initialize(text)
|
74
74
|
@text = text
|
75
75
|
end
|
76
|
-
def to_s
|
77
|
-
XmlMason.html_escape
|
76
|
+
def to_s(indentation = '')
|
77
|
+
XmlMason.html_escape(@text).gsub(/\n/, "\n" + indentation)
|
78
78
|
end
|
79
79
|
end
|
80
80
|
|
81
81
|
class Element < Node
|
82
|
-
def initialize(parent, prefix, node_name, value = nil)
|
82
|
+
def initialize(parent, prefix, node_name, value = nil, options = {})
|
83
83
|
super()
|
84
84
|
# if prefix.to_s == ""
|
85
85
|
# raise "missing prefix"
|
@@ -89,6 +89,7 @@ module Handsoap
|
|
89
89
|
@node_name = node_name
|
90
90
|
@children = []
|
91
91
|
@attributes = {}
|
92
|
+
@indent_children = options[:indent] != false # default to true, can override to false
|
92
93
|
if not value.nil?
|
93
94
|
set_value value.to_s
|
94
95
|
end
|
@@ -151,7 +152,7 @@ module Handsoap
|
|
151
152
|
def defines_namespace?(prefix)
|
152
153
|
@attributes.keys.include?("xmlns:#{prefix}") || @parent.defines_namespace?(prefix)
|
153
154
|
end
|
154
|
-
def to_s
|
155
|
+
def to_s(indentation = '')
|
155
156
|
# todo resolve attribute prefixes aswell
|
156
157
|
if @prefix && (not defines_namespace?(@prefix))
|
157
158
|
set_attr "xmlns:#{@prefix}", get_namespace(@prefix)
|
@@ -159,14 +160,15 @@ module Handsoap
|
|
159
160
|
name = XmlMason.html_escape(full_name)
|
160
161
|
attr = (@attributes.any? ? (" " + @attributes.map { |key, value| XmlMason.html_escape(key) + '="' + XmlMason.html_escape(value) + '"' }.join(" ")) : "")
|
161
162
|
if @children.any?
|
163
|
+
child_indent = @indent_children ? (indentation + " ") : ""
|
162
164
|
if value_node?
|
163
|
-
children = @children[0].to_s
|
165
|
+
children = @children[0].to_s(child_indent)
|
164
166
|
else
|
165
|
-
children = @children.map { |node| "\n" + node.to_s }.join("")
|
167
|
+
children = @children.map { |node| "\n" + node.to_s(child_indent) }.join("") + "\n" + indentation
|
166
168
|
end
|
167
|
-
"<" + name + attr + ">" + children + "</" + name + ">"
|
169
|
+
indentation + "<" + name + attr + ">" + children + "</" + name + ">"
|
168
170
|
else
|
169
|
-
"<" + name + attr + " />"
|
171
|
+
indentation + "<" + name + attr + " />"
|
170
172
|
end
|
171
173
|
end
|
172
174
|
end
|
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.
|
4
|
+
version: 0.1.1
|
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-
|
12
|
+
date: 2009-04-28 00:00:00 -07:00
|
13
13
|
default_executable:
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
@@ -44,6 +44,9 @@ files:
|
|
44
44
|
- README.markdown
|
45
45
|
- Rakefile
|
46
46
|
- VERSION.yml
|
47
|
+
- generators/handsoap/USAGE
|
48
|
+
- generators/handsoap/handsoap_generator.rb
|
49
|
+
- generators/handsoap/templates/gateway.rbt
|
47
50
|
- lib/handsoap.rb
|
48
51
|
- lib/handsoap/service.rb
|
49
52
|
- lib/handsoap/xml_mason.rb
|