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 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
@@ -1,4 +1,4 @@
1
1
  ---
2
- :patch: 0
2
+ :patch: 1
3
3
  :major: 0
4
4
  :minor: 1
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
@@ -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 @text
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("").gsub(/\n/, "\n ") + "\n"
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.0
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-22 00:00:00 -07:00
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