digitaria-handsoap 0.1.3
Sign up to get free protection for your applications and to get access to all the features.
- data/README.markdown +227 -0
- data/Rakefile +17 -0
- data/VERSION.yml +4 -0
- data/generators/handsoap/USAGE +0 -0
- data/generators/handsoap/handsoap_generator.rb +314 -0
- data/generators/handsoap/templates/gateway.rbt +27 -0
- data/lib/handsoap.rb +5 -0
- data/lib/handsoap/service.rb +287 -0
- data/lib/handsoap/xml_mason.rb +175 -0
- metadata +80 -0
data/README.markdown
ADDED
@@ -0,0 +1,227 @@
|
|
1
|
+
Handsoap
|
2
|
+
===
|
3
|
+
|
4
|
+
What
|
5
|
+
---
|
6
|
+
Handsoap is a library for creating SOAP clients in Ruby.
|
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
|
+
|
12
|
+
Why
|
13
|
+
---
|
14
|
+
|
15
|
+
Ruby already has a SOAP-client library, [soap4r](http://dev.ctor.org/soap4r), so why create another one?
|
16
|
+
|
17
|
+
> Let me summarize SOAP4R: it smells like Java code built on a Monday morning by an EJB coder.
|
18
|
+
>
|
19
|
+
> -- [Ruby In Practice: REST, SOAP, WebSphere MQ and SalesForce](http://blog.labnotes.org/2008/01/28/ruby-in-practice-rest-soap-websphere-mq-and-salesforce/)
|
20
|
+
|
21
|
+
OK, not entirely fair, but soap4r has problems. It's incomplete and buggy. If you try to use it for any real-world services, you quickly run into compatibility issues. You can get around some of them, if you have control over the service, but you may not always be that lucky. In the end, even if you get it working, it has a bulky un-Rubyish feel to it.
|
22
|
+
|
23
|
+
Handsoap tries to do better by taking a minimalistic approach. Instead of a full abstraction layer, it is more like a toolbox with which you can write SOAP bindings. You could think of it as a [ffi](http://c2.com/cgi/wiki?ForeignFunctionInterface) targeting SOAP.
|
24
|
+
|
25
|
+
This means that you generally need to do more manual labor in the cases where soap4r would have automated the mapping. It also means that you need to get your hands dirty with wsdl, xsd and other heavyweight specifications. However, it does give you some tools to help you stay sane.
|
26
|
+
|
27
|
+
There are several benefits of using Handsoap:
|
28
|
+
|
29
|
+
* It supports the entire SOAP specification, all versions (because you have to implement it your self).
|
30
|
+
* You actually get a sporting chance to debug and fix protocol level bugs.
|
31
|
+
* It's much faster than soap4r, because it uses fast low-level libraries for xml-parsing and http-communication.
|
32
|
+
|
33
|
+
To summarise, soap4r takes an optimistic approach, where Handsoap expects things to fail. If soap4r works for you today, it's probably the better choice. If you find your self strugling with it, Handsoap will offer a more smooth ride. It won't magically fix things for you though.
|
34
|
+
|
35
|
+
Handsoap vs. soap4r benchmark
|
36
|
+
---
|
37
|
+
|
38
|
+
Benchmarks are always unfair, but my experiments has placed Handsoap at being approximately double as fast as soap4r. I'd love any suggestions for a more precise measure.
|
39
|
+
|
40
|
+
$ ruby tests/benchmark_test.rb 1000
|
41
|
+
Benchmarking 1000 calls ...
|
42
|
+
user system total real
|
43
|
+
handsoap 0.750000 0.090000 0.840000 ( 1.992437)
|
44
|
+
soap4r 2.240000 0.140000 2.380000 ( 3.605836)
|
45
|
+
---------------
|
46
|
+
Legend:
|
47
|
+
The user CPU time, system CPU time, the sum of the user and system CPU times,
|
48
|
+
and the elapsed real time. The unit of time is seconds.
|
49
|
+
|
50
|
+
SOAP basics
|
51
|
+
---
|
52
|
+
|
53
|
+
SOAP is a protocol that is tunneled through XML over HTTP. Apart from using the technology for transportation, it doesn't have much to do with HTTP. Some times, it hasn't even got much to do with XML either.
|
54
|
+
|
55
|
+
A SOAP client basically consists of three parts:
|
56
|
+
|
57
|
+
* A http-connectivity layer,
|
58
|
+
* a mechanism for marshalling native data types to XML,
|
59
|
+
* and a mechanism for unmarshalling XML to native data types.
|
60
|
+
|
61
|
+
The protocol also contains a large and unwieldy specification of how to do the (un)marshalling, which can be used as the basis for automatically mapping to a rich type model. This makes the protocol fitting for .net/Java, but is a huge overhead for a very dynamically typed language such as Ruby. Much of the complexity of clients such as soap4r, is in the parts that tries to use this specification. Handsoap expects you to manually write the code that marshals/unmarshals, thereby bypassing this complexity (or rather - pass it to the programmer)
|
62
|
+
|
63
|
+
Handsoap only supports RPC-style SOAP. This seems to be the most common style. It's probably possible to add support for Document-style with little effort, but until I see the need I'm not going there.
|
64
|
+
|
65
|
+
The toolbox
|
66
|
+
---
|
67
|
+
|
68
|
+
The Handsoap toolbox consists of the following components.
|
69
|
+
|
70
|
+
Handsoap can use either [curb](http://curb.rubyforge.org/) or [httpclient](http://dev.ctor.org/http-access2) for HTTP-connectivity. The former is recommended, and default, but for portability you might choose the latter. You usually don't need to interact at the HTTP-level, but if you do (for example, if you have to use SSL), you can.
|
71
|
+
|
72
|
+
For parsing XML, Handsoap uses [Nokogiri](http://github.com/tenderlove/nokogiri/tree/master). While this may become optional in the future, the dependency is a bit tighter. The XML-parser is used internally in Handsoap, as well as by the code that maps from SOAP to Ruby (The code you're writing). Nokogiri is very fast (being based om libxml) and has a polished and stable api.
|
73
|
+
|
74
|
+
There is also a library for generating XML, which you'll use when mapping from Ruby to SOAP. It's quite similar to [Builder](http://builder.rubyforge.org/), but is tailored towards being used for writing SOAP-messages. The name of this library is `XmlMason` and it is included/part of Handsoap.
|
75
|
+
|
76
|
+
Recommendations
|
77
|
+
---
|
78
|
+
|
79
|
+
###Workflow
|
80
|
+
|
81
|
+
1. Find the wsdl for the service you want to consume.
|
82
|
+
|
83
|
+
2. Figure out the url for the endpoint, as well as the protocol version. Put this in a config file.
|
84
|
+
* To find the endpoint, look inside the wsdl, for `<soap:address location="..">`
|
85
|
+
|
86
|
+
3. Create a service class. Add endpoints and protocol. Alias needed namespace(s).
|
87
|
+
* To find the namespace(s), look in the samples from soapUI. It will be imported as `v1`
|
88
|
+
|
89
|
+
4. Open the wsdl in [soapUI](http://www.soapui.org/).
|
90
|
+
|
91
|
+
5. In soapUI, find a sample request for the method you want to use. Copy+paste the body-part.
|
92
|
+
|
93
|
+
6. Create a method in your service class (Use ruby naming convention)
|
94
|
+
|
95
|
+
7. Write Ruby-code (using XmlMason) to generate a request that is similar to the example from soapUI. (In your copy+paste buffer)
|
96
|
+
|
97
|
+
8. Write Ruby-code to parse the response (a Nokogiri XML-document) into Ruby data types.
|
98
|
+
|
99
|
+
9. Write an integration test to verify that your method works as expected. You can use soapUI to [generate a mock-service](http://www.soapui.org/userguide/mock/getting_started.html).
|
100
|
+
|
101
|
+
Repeat point 5..9 for each method that you need to use.
|
102
|
+
Between each iteration, you should refactor shared code into helper functions.
|
103
|
+
|
104
|
+
###Configuration
|
105
|
+
|
106
|
+
If you use Rails, you should put the endpoint in a constant in the environment file. That way, you can have different endpoints for test/development/production/etc.
|
107
|
+
|
108
|
+
If you don't use Rails, it's still a good idea to move this information to a config file.
|
109
|
+
|
110
|
+
The configuration could look like this:
|
111
|
+
|
112
|
+
# wsdl: http://example.org/ws/service?WSDL
|
113
|
+
EXAMPLE_SERVICE_ENDPOINT = {
|
114
|
+
:uri => 'http://example.org/ws/service',
|
115
|
+
:version => 2
|
116
|
+
}
|
117
|
+
|
118
|
+
If you use Rails, you will need to load the gem from the `config/environment.rb` file, using:
|
119
|
+
|
120
|
+
config.gem 'troelskn-handsoap', :lib => 'handsoap', :source => "http://gems.github.com"
|
121
|
+
|
122
|
+
###Service class
|
123
|
+
|
124
|
+
Put your service in a file under `app/models`. You should extend `Handsoap::Service`.
|
125
|
+
|
126
|
+
You need to provide the endpoint and the SOAP version (1.1 or 1.2). If in doubt, use version 2.
|
127
|
+
|
128
|
+
A service usually has a namespace for describing the message-body ([RPC/Litteral style](http://www.ibm.com/developerworks/webservices/library/ws-whichwsdl/#N1011F)). You should set this in the `on_create_document` handler.
|
129
|
+
|
130
|
+
A typical service looks like the following:
|
131
|
+
|
132
|
+
# -*- coding: utf-8 -*-
|
133
|
+
require 'handsoap'
|
134
|
+
|
135
|
+
class Example::FooService < Handsoap::Service
|
136
|
+
endpoint EXAMPLE_SERVICE_ENDPOINT
|
137
|
+
on_create_document do |doc|
|
138
|
+
doc.alias 'wsdl', "http://example.org/ws/spec"
|
139
|
+
end
|
140
|
+
# public methods
|
141
|
+
# todo
|
142
|
+
|
143
|
+
private
|
144
|
+
# helpers
|
145
|
+
# todo
|
146
|
+
end
|
147
|
+
|
148
|
+
The above would go in the file `app/models/example/foo_service.rb`
|
149
|
+
|
150
|
+
###Integration tests
|
151
|
+
|
152
|
+
Since you're writing mappings manually, it's a good idea to write tests that verify that the service works. If you use standard Rails with `Test::Unit`, you should put these in an integration-test.
|
153
|
+
|
154
|
+
For the sample service above, you would create a file in `test/integration/example/foo_service.rb`, with the following content:
|
155
|
+
|
156
|
+
# -*- coding: utf-8 -*-
|
157
|
+
require 'test_helper'
|
158
|
+
|
159
|
+
# Example::FooService.logger = $stdout
|
160
|
+
|
161
|
+
class Example::FooServiceTest < Test::Unit::TestCase
|
162
|
+
def test_update_icon
|
163
|
+
icon = { :href => 'http://www.example.com/icon.jpg', :type => 'image/jpeg' }
|
164
|
+
result = Example::FooService.update_icon!(icon)
|
165
|
+
assert_equal icon.type, result.type
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
Note the commented-out line. If you set a logger on the service-class, you can see exactly which XML goes forth and back, which is very useful for debugging.
|
170
|
+
|
171
|
+
###Methods
|
172
|
+
|
173
|
+
You should use Ruby naming-conventions for methods names. If the method has side-effects, you should postfix it with an exclamation.
|
174
|
+
Repeat code inside the invoke-block, should be refactored out to *builders*, and the response should be parsed with a *parser*.
|
175
|
+
|
176
|
+
def update_icon!(icon)
|
177
|
+
response = invoke("wsdl:UpdateIcon") do |message|
|
178
|
+
build_icon!(message, icon)
|
179
|
+
end
|
180
|
+
parse_icon(response.document.xpath('//icon').first)
|
181
|
+
end
|
182
|
+
|
183
|
+
|
184
|
+
###Helpers
|
185
|
+
|
186
|
+
You'll end up with two kinds of helpers; Ruby->XML transformers (aka. *builders*) and XML->Ruby transformers (aka. *parsers*).
|
187
|
+
It's recommended that you stick to the following style/naming scheme:
|
188
|
+
|
189
|
+
# icon -> xml
|
190
|
+
def build_icon!(message, icon)
|
191
|
+
message.add "icon" do |i|
|
192
|
+
i.set_attr "href", icon[:href]
|
193
|
+
i.set_attr "type", icon[:type]
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
# xml -> icon
|
198
|
+
def parse_icon(node)
|
199
|
+
{ :href => node['href'], :type => node['type'] }
|
200
|
+
end
|
201
|
+
|
202
|
+
or, if you prefer, you can use a class to represent entities:
|
203
|
+
|
204
|
+
# icon -> xml
|
205
|
+
def build_icon!(message, icon)
|
206
|
+
message.add "icon" do |i|
|
207
|
+
i.set_attr "href", icon.href
|
208
|
+
i.set_attr "type", icon.type
|
209
|
+
end
|
210
|
+
end
|
211
|
+
|
212
|
+
# xml -> icon
|
213
|
+
def parse_icon(node)
|
214
|
+
Icon.new :href => node['href'],
|
215
|
+
:type => node['type']
|
216
|
+
end
|
217
|
+
|
218
|
+
License
|
219
|
+
---
|
220
|
+
|
221
|
+
Copyright: [Unwire A/S](http://www.unwire.dk), 2009
|
222
|
+
|
223
|
+
License: [Creative Commons Attribution 2.5 Denmark License](http://creativecommons.org/licenses/by/2.5/dk/)
|
224
|
+
|
225
|
+
___
|
226
|
+
|
227
|
+
troelskn@gmail.com - April, 2009
|
data/Rakefile
ADDED
@@ -0,0 +1,17 @@
|
|
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
|
data/VERSION.yml
ADDED
File without changes
|
@@ -0,0 +1,314 @@
|
|
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
|
+
# TODO: http://www.crossedconnections.org/w/?p=51 -- The 'typens' namespace is magical ...
|
8
|
+
|
9
|
+
class Builders
|
10
|
+
def initialize(xsd)
|
11
|
+
@xsd = xsd
|
12
|
+
@builders = {}
|
13
|
+
end
|
14
|
+
def add(type)
|
15
|
+
@builders[type] = false unless @builders[type]
|
16
|
+
end
|
17
|
+
def each
|
18
|
+
results = []
|
19
|
+
while builder = @builders.find { |builder,is_rendered| !is_rendered }
|
20
|
+
results << yield(@xsd.get_complex_type(builder[0]))
|
21
|
+
@builders[builder[0]] = true
|
22
|
+
end
|
23
|
+
results.join("")
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
class HandsoapGenerator < Rails::Generator::NamedBase
|
28
|
+
attr_reader :wsdl
|
29
|
+
def initialize(runtime_args, runtime_options = {})
|
30
|
+
super
|
31
|
+
# Wsdl argument is required.
|
32
|
+
usage if @args.empty?
|
33
|
+
@wsdl_uri = @args.shift
|
34
|
+
end
|
35
|
+
|
36
|
+
def banner
|
37
|
+
"WARNING: This generator is rather incomplete and buggy. Use at your own risk." +
|
38
|
+
"\n" + "Usage: #{$0} #{spec.name} name URI [options]" +
|
39
|
+
"\n" + " name Basename of the service class" +
|
40
|
+
"\n" + " URI URI of the WSDL to generate from"
|
41
|
+
end
|
42
|
+
|
43
|
+
def manifest
|
44
|
+
record do |m|
|
45
|
+
@wsdl = Handsoap::Wsdl.new(@wsdl_uri)
|
46
|
+
@wsdl.parse!
|
47
|
+
@xsd = Handsoap::XsdSpider.new(@wsdl_uri)
|
48
|
+
@xsd.process!
|
49
|
+
m.directory "app"
|
50
|
+
m.directory "app/models"
|
51
|
+
@builders = Builders.new(@xsd)
|
52
|
+
m.template "gateway.rbt", "app/models/#{file_name}_service.rb"
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def builders
|
57
|
+
@builders
|
58
|
+
end
|
59
|
+
|
60
|
+
def render_build(context_name, message_type, varname = nil, indentation = ' ')
|
61
|
+
if varname.nil?
|
62
|
+
ruby_name = message_type.ruby_name
|
63
|
+
else
|
64
|
+
ruby_name = "#{varname}[:#{message_type.ruby_name}]"
|
65
|
+
end
|
66
|
+
# message_type.namespaces
|
67
|
+
if message_type.attribute?
|
68
|
+
"#{context_name}.set_attr " + '"' + message_type.name + '", ' + ruby_name
|
69
|
+
elsif message_type.boolean?
|
70
|
+
"#{context_name}.add " + '"' + message_type.name + '", bool_to_str(' + ruby_name + ')'
|
71
|
+
elsif message_type.primitive?
|
72
|
+
"#{context_name}.add " + '"' + message_type.name + '", ' + ruby_name
|
73
|
+
elsif message_type.list?
|
74
|
+
list_type = @xsd.get_complex_type(message_type.type)
|
75
|
+
builders.add(list_type.type)
|
76
|
+
# TODO: a naming conflict waiting to happen hereabout
|
77
|
+
# TODO: indentation
|
78
|
+
"#{varname}.each do |#{message_type.ruby_name}|" + "\n" + indentation +
|
79
|
+
" build_#{list_type.ruby_type}!(#{context_name}, #{message_type.ruby_name})" + "\n" + indentation +
|
80
|
+
"end"
|
81
|
+
else
|
82
|
+
builders.add(message_type.type)
|
83
|
+
"build_#{message_type.ruby_type}!(#{context_name}, " + ruby_name + ")"
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
end
|
88
|
+
|
89
|
+
module Handsoap
|
90
|
+
|
91
|
+
class Wsdl
|
92
|
+
attr_reader :uri, :soap_actions, :soap_ports, :target_namespace
|
93
|
+
def initialize(uri)
|
94
|
+
@uri = uri
|
95
|
+
end
|
96
|
+
|
97
|
+
def parse!
|
98
|
+
wsdl = Nokogiri.XML(Kernel.open(@uri).read)
|
99
|
+
@target_namespace = wsdl.namespaces['xmlns:tns'] || wsdl.namespaces['xmlns']
|
100
|
+
@soap_actions = []
|
101
|
+
@soap_ports = []
|
102
|
+
messages = {}
|
103
|
+
|
104
|
+
wsdl.xpath('//wsdl:message').each do |message|
|
105
|
+
message_name = message['name']
|
106
|
+
messages[message_name] = message.xpath('wsdl:part').map { |part| MessageType::Part.new(part['type'] || 'xs:element', part['name']) }
|
107
|
+
end
|
108
|
+
|
109
|
+
wsdl.xpath('//*[name()="soap:operation"]').each do |operation|
|
110
|
+
operation_name = operation.parent['name']
|
111
|
+
operation_spec = wsdl.xpath('//wsdl:operation[@name="' + operation_name + '"]').first
|
112
|
+
raise RuntimeError, "Couldn't find wsdl:operation node for #{operation_name}" if operation_spec.nil?
|
113
|
+
msg_type_in = operation_spec.xpath('./wsdl:input').first["message"]
|
114
|
+
raise RuntimeError, "Couldn't find wsdl:input node for #{operation_name}" if msg_type_in.nil?
|
115
|
+
raise RuntimeError, "Invalid message type #{msg_type_in} for #{operation_name}" if messages[msg_type_in].nil?
|
116
|
+
msg_type_out = operation_spec.xpath('./wsdl:output').first["message"]
|
117
|
+
raise RuntimeError, "Couldn't find wsdl:output node for #{operation_name}" if msg_type_out.nil?
|
118
|
+
raise RuntimeError, "Invalid message type #{msg_type_out} for #{operation_name}" if messages[msg_type_out].nil?
|
119
|
+
@soap_actions << SoapAction.new(operation, messages[msg_type_in], messages[msg_type_out])
|
120
|
+
end
|
121
|
+
raise RuntimeError, "Could not parse WSDL" if soap_actions.empty?
|
122
|
+
|
123
|
+
wsdl.xpath('//wsdl:port', {"xmlns:wsdl" => 'http://schemas.xmlsoap.org/wsdl/'}).each do |port|
|
124
|
+
name = port['name'].underscore
|
125
|
+
location = port.xpath('./*[@location]').first['location']
|
126
|
+
@soap_ports << { :name => name, :soap_name => port['name'], :location => location }
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
class SoapAction
|
132
|
+
attr_reader :input_type, :output_type
|
133
|
+
def initialize(xml_node, input_type, output_type)
|
134
|
+
@xml_node = xml_node
|
135
|
+
@input_type = input_type
|
136
|
+
@output_type = output_type
|
137
|
+
end
|
138
|
+
def name
|
139
|
+
@xml_node.parent['name'].underscore
|
140
|
+
end
|
141
|
+
def soap_name
|
142
|
+
@xml_node.parent['name']
|
143
|
+
end
|
144
|
+
def href
|
145
|
+
@xml_node['soapAction']
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
module MessageType
|
150
|
+
|
151
|
+
# complex-type is a spec (class), not an element ... (object)
|
152
|
+
# <xs:complexType name="User">
|
153
|
+
# <xs:annotation>
|
154
|
+
# <xs:documentation>The element specifies a user</xs:documentation>
|
155
|
+
# </xs:annotation>
|
156
|
+
# <xs:attribute name="dn" type="xs:string" use="required"/>
|
157
|
+
# </xs:complexType>
|
158
|
+
class ComplexType
|
159
|
+
def initialize(xml_node)
|
160
|
+
@xml_node = xml_node
|
161
|
+
end
|
162
|
+
def type
|
163
|
+
@xml_node['name']
|
164
|
+
end
|
165
|
+
def ruby_type
|
166
|
+
type.gsub(/^.*:/, "").underscore.gsub(/-/, '_')
|
167
|
+
end
|
168
|
+
def elements
|
169
|
+
@xml_node.xpath('./xs:attribute|./xs:all/xs:element|./xs:sequence').map do |node|
|
170
|
+
case
|
171
|
+
when node.node_name == 'attribute'
|
172
|
+
Attribute.new(node['type'], node['name'])
|
173
|
+
when node.node_name == 'element'
|
174
|
+
Element.new(node['type'], node['name'], []) # TODO: elements.elements
|
175
|
+
when node.node_name == 'sequence'
|
176
|
+
choice_node = node.xpath('./xs:choice').first
|
177
|
+
if choice_node
|
178
|
+
# TODO
|
179
|
+
Attribute.new('xs:choice', 'todo')
|
180
|
+
else
|
181
|
+
entity_node = node.xpath('./xs:element').first
|
182
|
+
Sequence.new(entity_node['type'], entity_node['name'])
|
183
|
+
end
|
184
|
+
else
|
185
|
+
puts node
|
186
|
+
raise "Unknown type #{node.node_name}"
|
187
|
+
end
|
188
|
+
end
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
class Base
|
193
|
+
attr_reader :type, :name
|
194
|
+
def initialize(type, name)
|
195
|
+
raise "'type' can't be nil" if type.nil?
|
196
|
+
raise "'name' can't be nil" if name.nil?
|
197
|
+
@type = type
|
198
|
+
@name = name
|
199
|
+
end
|
200
|
+
def ruby_type
|
201
|
+
type.gsub(/^.*:/, "").underscore.gsub(/-/, '_')
|
202
|
+
end
|
203
|
+
def ruby_name
|
204
|
+
name.underscore.gsub(/-/, '_')
|
205
|
+
end
|
206
|
+
def attribute?
|
207
|
+
false
|
208
|
+
end
|
209
|
+
def primitive?
|
210
|
+
/^xs:/.match type
|
211
|
+
end
|
212
|
+
def boolean?
|
213
|
+
type == "xs:boolean"
|
214
|
+
end
|
215
|
+
def list?
|
216
|
+
false
|
217
|
+
end
|
218
|
+
end
|
219
|
+
|
220
|
+
# Parts are shallow elements
|
221
|
+
# <wsdl:part name="widget-instance-id" type="xs:int" />
|
222
|
+
class Part < Base
|
223
|
+
end
|
224
|
+
|
225
|
+
# <wsdl:part name="widget-instance-id" type="xs:int" />
|
226
|
+
# <xs:element maxOccurs="1" minOccurs="0" name="description" type="xs:string"/>
|
227
|
+
class Element < Base
|
228
|
+
attr_reader :elements
|
229
|
+
def initialize(type, name, elements = [])
|
230
|
+
super(type, name)
|
231
|
+
@elements = elements
|
232
|
+
end
|
233
|
+
end
|
234
|
+
|
235
|
+
# <xs:attribute name="id" type="xs:int" use="required"/>
|
236
|
+
class Attribute < Base
|
237
|
+
def primitive?
|
238
|
+
true
|
239
|
+
end
|
240
|
+
def attribute?
|
241
|
+
true
|
242
|
+
end
|
243
|
+
end
|
244
|
+
|
245
|
+
# <xs:sequence>
|
246
|
+
# <xs:element maxOccurs="unbounded" minOccurs="0" name="widget-area" type="WidgetArea"/>
|
247
|
+
# </xs:sequence>
|
248
|
+
class Sequence < Base
|
249
|
+
def list?
|
250
|
+
true
|
251
|
+
end
|
252
|
+
end
|
253
|
+
end
|
254
|
+
end
|
255
|
+
|
256
|
+
module Handsoap
|
257
|
+
|
258
|
+
class XsdSpider
|
259
|
+
def initialize(uri)
|
260
|
+
@queue = []
|
261
|
+
@wsdl_uri = uri
|
262
|
+
end
|
263
|
+
|
264
|
+
def results
|
265
|
+
@queue.map { |element| element[:data] }
|
266
|
+
end
|
267
|
+
|
268
|
+
def get_complex_type(name)
|
269
|
+
# TODO namespace
|
270
|
+
short_name = name.gsub(/^.*:/, "")
|
271
|
+
results.each do |data|
|
272
|
+
search = data[:document].xpath('//xs:complexType[@name="' + short_name + '"]')
|
273
|
+
if search.any?
|
274
|
+
return MessageType::ComplexType.new(search.first)
|
275
|
+
end
|
276
|
+
end
|
277
|
+
raise "Didn't find '#{name}' (short name #{short_name})"
|
278
|
+
end
|
279
|
+
|
280
|
+
def process!
|
281
|
+
spider_href(@wsdl_uri, nil)
|
282
|
+
while process_next do end
|
283
|
+
end
|
284
|
+
|
285
|
+
private
|
286
|
+
|
287
|
+
def add_href(href, namespace)
|
288
|
+
unless @queue.find { |element| element[:href] == href }
|
289
|
+
@queue << { :href => href, :namespace => namespace, :state => :new, :data => {} }
|
290
|
+
end
|
291
|
+
end
|
292
|
+
|
293
|
+
def process_next
|
294
|
+
next_element = @queue.find { |element| element[:state] == :new }
|
295
|
+
if next_element
|
296
|
+
next_element[:data] = spider_href(next_element[:href], next_element[:namespace])
|
297
|
+
next_element[:state] = :done
|
298
|
+
return true
|
299
|
+
end
|
300
|
+
return false
|
301
|
+
end
|
302
|
+
|
303
|
+
def spider_href(href, namespace)
|
304
|
+
raise "'href' must be a String" if href.nil?
|
305
|
+
xsd = Nokogiri.XML(Kernel.open(href).read)
|
306
|
+
# <xs:include schemaLocation="...xsd"/>
|
307
|
+
# <xs:import namespace="" schemaLocation="...xsd"/>
|
308
|
+
xsd.xpath('//*[@schemaLocation]').each do |inc|
|
309
|
+
add_href(inc['schemaLocation'], inc['namespace'] || namespace)
|
310
|
+
end
|
311
|
+
{ :document => xsd, :namespace => namespace }
|
312
|
+
end
|
313
|
+
end
|
314
|
+
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.rb
ADDED
@@ -0,0 +1,287 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
require 'rubygems'
|
3
|
+
require 'httpclient'
|
4
|
+
require 'nokogiri'
|
5
|
+
require 'curb'
|
6
|
+
require 'handsoap/xml_mason'
|
7
|
+
require 'time'
|
8
|
+
|
9
|
+
module Handsoap
|
10
|
+
|
11
|
+
def self.http_driver
|
12
|
+
@http_driver || :curb
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.http_driver=(driver)
|
16
|
+
@http_driver = driver
|
17
|
+
end
|
18
|
+
|
19
|
+
SOAP_NAMESPACE = { 1 => 'http://schemas.xmlsoap.org/soap/envelope/', 2 => 'http://www.w3.org/2001/12/soap-encoding' }
|
20
|
+
|
21
|
+
class Response
|
22
|
+
def initialize(http_body, soap_namespace)
|
23
|
+
@http_body = http_body
|
24
|
+
@soap_namespace = soap_namespace
|
25
|
+
@document = :lazy
|
26
|
+
@fault = :lazy
|
27
|
+
end
|
28
|
+
def document?
|
29
|
+
!! document
|
30
|
+
end
|
31
|
+
def document
|
32
|
+
if @document == :lazy
|
33
|
+
doc = Nokogiri::XML(@http_body)
|
34
|
+
@document = (doc && doc.root && doc.errors.empty?) ? doc : nil
|
35
|
+
end
|
36
|
+
return @document
|
37
|
+
end
|
38
|
+
def fault?
|
39
|
+
!! fault
|
40
|
+
end
|
41
|
+
def fault
|
42
|
+
if @fault == :lazy
|
43
|
+
nodes = document? ? document.xpath('/env:Envelope/env:Body/descendant-or-self::env:Fault', { 'env' => @soap_namespace }) : []
|
44
|
+
@fault = nodes.any? ? Fault.from_xml(nodes.first, :namespace => @soap_namespace) : nil
|
45
|
+
end
|
46
|
+
return @fault
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
class Fault < Exception
|
51
|
+
attr_reader :code, :reason, :details
|
52
|
+
def initialize(code, reason, details)
|
53
|
+
@code = code
|
54
|
+
@reason = reason
|
55
|
+
@details = details
|
56
|
+
end
|
57
|
+
def to_s
|
58
|
+
"Handsoap::Fault { :code => '#{@code}', :reason => '#{@reason}' }"
|
59
|
+
end
|
60
|
+
def self.from_xml(node, options = { :namespace => nil })
|
61
|
+
if not options[:namespace]
|
62
|
+
raise "Missing option :namespace"
|
63
|
+
end
|
64
|
+
ns = { 'env' => options[:namespace] }
|
65
|
+
fault_code = node.xpath('./env:Code/env:Value/text()', ns).to_s
|
66
|
+
if fault_code == ""
|
67
|
+
fault_code = node.xpath('./faultcode/text()', ns).to_s
|
68
|
+
end
|
69
|
+
reason = node.xpath('./env:Reason/env:Text[1]/text()', ns).to_s
|
70
|
+
if reason == ""
|
71
|
+
reason = node.xpath('./faultstring/text()', ns).to_s
|
72
|
+
end
|
73
|
+
details = node.xpath('./detail/*', ns)
|
74
|
+
self.new(fault_code, reason, details)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
class Service
|
79
|
+
@@logger = nil
|
80
|
+
def self.logger=(io)
|
81
|
+
@@logger = io
|
82
|
+
end
|
83
|
+
def self.endpoint(args = {})
|
84
|
+
@protocol_version = args[:version] || raise("Missing option :version")
|
85
|
+
@uri = args[:uri] || raise("Missing option :uri")
|
86
|
+
end
|
87
|
+
def self.envelope_namespace
|
88
|
+
if SOAP_NAMESPACE[@protocol_version].nil?
|
89
|
+
raise "Unknown protocol version '#{@protocol_version.inspect}'"
|
90
|
+
end
|
91
|
+
SOAP_NAMESPACE[@protocol_version]
|
92
|
+
end
|
93
|
+
def self.request_content_type
|
94
|
+
@protocol_version == 1 ? "text/xml" : "application/soap+xml"
|
95
|
+
end
|
96
|
+
def self.map_method(mapping)
|
97
|
+
if @mapping.nil?
|
98
|
+
@mapping = {}
|
99
|
+
end
|
100
|
+
@mapping.merge! mapping
|
101
|
+
end
|
102
|
+
def self.on_create_document(&block)
|
103
|
+
@create_document_callback = block
|
104
|
+
end
|
105
|
+
def self.fire_on_create_document(doc)
|
106
|
+
if @create_document_callback
|
107
|
+
@create_document_callback.call doc
|
108
|
+
end
|
109
|
+
end
|
110
|
+
def self.uri
|
111
|
+
@uri
|
112
|
+
end
|
113
|
+
def self.get_mapping(name)
|
114
|
+
@mapping[name] if @mapping
|
115
|
+
end
|
116
|
+
@@instance = {}
|
117
|
+
def self.instance
|
118
|
+
@@instance[self.to_s] ||= self.new
|
119
|
+
end
|
120
|
+
def self.method_missing(method, *args)
|
121
|
+
if instance.respond_to?(method)
|
122
|
+
instance.__send__ method, *args
|
123
|
+
else
|
124
|
+
super
|
125
|
+
end
|
126
|
+
end
|
127
|
+
def method_missing(method, *args)
|
128
|
+
action = self.class.get_mapping(method)
|
129
|
+
if action
|
130
|
+
invoke(action, *args)
|
131
|
+
else
|
132
|
+
super
|
133
|
+
end
|
134
|
+
end
|
135
|
+
def invoke(action, options = { :soap_action => :auto }, &block)
|
136
|
+
if action
|
137
|
+
if options.kind_of? String
|
138
|
+
options = { :soap_action => options }
|
139
|
+
end
|
140
|
+
if options[:soap_action] == :auto
|
141
|
+
options[:soap_action] = action.gsub(/^.+:/, "")
|
142
|
+
elsif options[:soap_action] == :none
|
143
|
+
options[:soap_action] = nil
|
144
|
+
end
|
145
|
+
doc = make_envelope do |body|
|
146
|
+
body.add action
|
147
|
+
end
|
148
|
+
if block_given?
|
149
|
+
yield doc.find(action)
|
150
|
+
end
|
151
|
+
dispatch(doc, options[:soap_action])
|
152
|
+
end
|
153
|
+
end
|
154
|
+
def on_before_dispatch
|
155
|
+
end
|
156
|
+
def on_fault(fault)
|
157
|
+
raise fault
|
158
|
+
end
|
159
|
+
private
|
160
|
+
# Helper to serialize a node into a ruby string
|
161
|
+
def xml_to_str(node, xquery = nil)
|
162
|
+
begin
|
163
|
+
n = xquery ? node.xpath(xquery, ns).first : node
|
164
|
+
n.serialize('UTF-8')
|
165
|
+
rescue Exception => ex
|
166
|
+
nil
|
167
|
+
end
|
168
|
+
end
|
169
|
+
# Helper to serialize a node into a ruby integer
|
170
|
+
def xml_to_int(node, xquery = nil)
|
171
|
+
begin
|
172
|
+
n = xquery ? node.xpath(xquery, ns).first : node
|
173
|
+
n.to_s.to_i
|
174
|
+
rescue Exception => ex
|
175
|
+
nil
|
176
|
+
end
|
177
|
+
end
|
178
|
+
# Helper to serialize a node into a ruby float
|
179
|
+
def xml_to_float(node, xquery = nil)
|
180
|
+
begin
|
181
|
+
n = xquery ? node.xpath(xquery, ns).first : node
|
182
|
+
n.to_s.to_f
|
183
|
+
rescue Exception => ex
|
184
|
+
nil
|
185
|
+
end
|
186
|
+
end
|
187
|
+
# Helper to serialize a node into a ruby boolean
|
188
|
+
def xml_to_bool(node, xquery = nil)
|
189
|
+
begin
|
190
|
+
n = xquery ? node.xpath(xquery, ns).first : node
|
191
|
+
n.to_s == "true"
|
192
|
+
rescue Exception => ex
|
193
|
+
nil
|
194
|
+
end
|
195
|
+
end
|
196
|
+
# Helper to serialize a node into a ruby Time object
|
197
|
+
def xml_to_date(node, xquery = nil)
|
198
|
+
begin
|
199
|
+
n = xquery ? node.xpath(xquery, ns).first : node
|
200
|
+
Time.iso8601(n.to_s)
|
201
|
+
rescue Exception => ex
|
202
|
+
nil
|
203
|
+
end
|
204
|
+
end
|
205
|
+
def debug(message = nil)
|
206
|
+
if @@logger
|
207
|
+
if message
|
208
|
+
@@logger.puts(message)
|
209
|
+
end
|
210
|
+
if block_given?
|
211
|
+
yield @@logger
|
212
|
+
end
|
213
|
+
end
|
214
|
+
end
|
215
|
+
def dispatch(doc, action)
|
216
|
+
on_before_dispatch()
|
217
|
+
headers = {
|
218
|
+
"Content-Type" => "#{self.class.request_content_type};charset=UTF-8"
|
219
|
+
}
|
220
|
+
headers["SOAPAction"] = action unless action.nil?
|
221
|
+
body = doc.to_s
|
222
|
+
debug do |logger|
|
223
|
+
logger.puts "==============="
|
224
|
+
logger.puts "--- Request ---"
|
225
|
+
logger.puts "URI: %s" % [self.class.uri]
|
226
|
+
logger.puts headers.map { |key,value| key + ": " + value }.join("\n")
|
227
|
+
logger.puts "---"
|
228
|
+
logger.puts body
|
229
|
+
end
|
230
|
+
if Handsoap.http_driver == :curb
|
231
|
+
http_client = Curl::Easy.new(self.class.uri)
|
232
|
+
http_client.headers = headers
|
233
|
+
http_client.http_post body
|
234
|
+
debug do |logger|
|
235
|
+
logger.puts "--- Response ---"
|
236
|
+
logger.puts "HTTP Status: %s" % [http_client.response_code]
|
237
|
+
logger.puts "Content-Type: %s" % [http_client.content_type]
|
238
|
+
logger.puts "---"
|
239
|
+
logger.puts Handsoap.pretty_format_envelope(http_client.body_str)
|
240
|
+
end
|
241
|
+
soap_response = Response.new(http_client.body_str, self.class.envelope_namespace)
|
242
|
+
else
|
243
|
+
response = HTTPClient.new.post(self.class.uri, body, headers)
|
244
|
+
debug do |logger|
|
245
|
+
logger.puts "--- Response ---"
|
246
|
+
logger.puts "HTTP Status: %s" % [response.status]
|
247
|
+
logger.puts "Content-Type: %s" % [response.contenttype]
|
248
|
+
logger.puts "---"
|
249
|
+
logger.puts Handsoap.pretty_format_envelope(response.content)
|
250
|
+
end
|
251
|
+
soap_response = Response.new(response.content, self.class.envelope_namespace)
|
252
|
+
end
|
253
|
+
if soap_response.fault?
|
254
|
+
return self.on_fault(soap_response.fault)
|
255
|
+
end
|
256
|
+
return soap_response
|
257
|
+
end
|
258
|
+
def make_envelope
|
259
|
+
doc = XmlMason::Document.new do |doc|
|
260
|
+
doc.alias 'env', self.class.envelope_namespace
|
261
|
+
doc.add "env:Envelope" do |env|
|
262
|
+
env.add "*:Header"
|
263
|
+
env.add "*:Body"
|
264
|
+
end
|
265
|
+
end
|
266
|
+
self.class.fire_on_create_document doc
|
267
|
+
if block_given?
|
268
|
+
yield doc.find("Body")
|
269
|
+
end
|
270
|
+
return doc
|
271
|
+
end
|
272
|
+
end
|
273
|
+
|
274
|
+
def self.pretty_format_envelope(xml_string)
|
275
|
+
if /^<.*:Envelope/.match(xml_string)
|
276
|
+
begin
|
277
|
+
doc = Nokogiri::XML(xml_string)
|
278
|
+
rescue Exception => ex
|
279
|
+
return "Formatting failed: " + ex.to_s
|
280
|
+
end
|
281
|
+
return doc.to_s
|
282
|
+
# return "\n\e[1;33m" + doc.to_s + "\e[0m"
|
283
|
+
end
|
284
|
+
return xml_string
|
285
|
+
end
|
286
|
+
|
287
|
+
end
|
@@ -0,0 +1,175 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
|
3
|
+
module Handsoap
|
4
|
+
|
5
|
+
module XmlMason
|
6
|
+
|
7
|
+
HTML_ESCAPE = { '&' => '&', '"' => '"', '>' => '>', '<' => '<' }
|
8
|
+
|
9
|
+
def self.html_escape(s)
|
10
|
+
s.to_s.gsub(/[&"><]/) { |special| HTML_ESCAPE[special] }
|
11
|
+
end
|
12
|
+
|
13
|
+
class Node
|
14
|
+
def initialize
|
15
|
+
@namespaces = {}
|
16
|
+
end
|
17
|
+
def add(node_name, value = nil)
|
18
|
+
prefix, name = parse_ns(node_name)
|
19
|
+
node = append_child Element.new(self, prefix, name, value)
|
20
|
+
if block_given?
|
21
|
+
yield node
|
22
|
+
end
|
23
|
+
end
|
24
|
+
def alias(prefix, namespaces)
|
25
|
+
@namespaces[prefix] = namespaces
|
26
|
+
end
|
27
|
+
def parse_ns(name)
|
28
|
+
matches = name.match /^([^:]+):(.*)$/
|
29
|
+
if matches
|
30
|
+
[matches[1] == '*' ? @prefix : matches[1], matches[2]]
|
31
|
+
else
|
32
|
+
[nil, name]
|
33
|
+
end
|
34
|
+
end
|
35
|
+
private :parse_ns
|
36
|
+
end
|
37
|
+
|
38
|
+
class Document < Node
|
39
|
+
def initialize
|
40
|
+
super
|
41
|
+
@document_element = nil
|
42
|
+
if block_given?
|
43
|
+
yield self
|
44
|
+
end
|
45
|
+
end
|
46
|
+
def append_child(node)
|
47
|
+
if not @document_element.nil?
|
48
|
+
raise "There can only be one element at the top level."
|
49
|
+
end
|
50
|
+
@document_element = node
|
51
|
+
end
|
52
|
+
def find(name)
|
53
|
+
@document_element.find(name)
|
54
|
+
end
|
55
|
+
def find_all(name)
|
56
|
+
@document_element.find_all(name)
|
57
|
+
end
|
58
|
+
def get_namespace(prefix)
|
59
|
+
@namespaces[prefix] || raise("No alias registered for prefix '#{prefix}'")
|
60
|
+
end
|
61
|
+
def defines_namespace?(prefix)
|
62
|
+
false
|
63
|
+
end
|
64
|
+
def to_s
|
65
|
+
if @document_element.nil?
|
66
|
+
raise "No document element added."
|
67
|
+
end
|
68
|
+
"<?xml version='1.0' ?>" + "\n" + @document_element.to_s
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
class TextNode
|
73
|
+
def initialize(text)
|
74
|
+
@text = text
|
75
|
+
end
|
76
|
+
def to_s(indentation = '')
|
77
|
+
XmlMason.html_escape(@text)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
class Element < Node
|
82
|
+
def initialize(parent, prefix, node_name, value = nil)
|
83
|
+
super()
|
84
|
+
# if prefix.to_s == ""
|
85
|
+
# raise "missing prefix"
|
86
|
+
# end
|
87
|
+
@parent = parent
|
88
|
+
@prefix = prefix
|
89
|
+
@node_name = node_name
|
90
|
+
@children = []
|
91
|
+
@attributes = {}
|
92
|
+
if not value.nil?
|
93
|
+
set_value value.to_s
|
94
|
+
end
|
95
|
+
if block_given?
|
96
|
+
yield self
|
97
|
+
end
|
98
|
+
end
|
99
|
+
def full_name
|
100
|
+
@prefix.nil? ? @node_name : (@prefix + ":" + @node_name)
|
101
|
+
end
|
102
|
+
def append_child(node)
|
103
|
+
if value_node?
|
104
|
+
raise "Element already has a text value. Can't add nodes"
|
105
|
+
end
|
106
|
+
@children << node
|
107
|
+
return node
|
108
|
+
end
|
109
|
+
def set_value(value)
|
110
|
+
if @children.length > 0
|
111
|
+
raise "Element already has children. Can't set value"
|
112
|
+
end
|
113
|
+
@children = [TextNode.new(value)]
|
114
|
+
end
|
115
|
+
def set_attr(name, value)
|
116
|
+
full_name = parse_ns(name).join(":")
|
117
|
+
@attributes[name] = value
|
118
|
+
end
|
119
|
+
def find(name)
|
120
|
+
if @node_name == name || full_name == name
|
121
|
+
return self
|
122
|
+
end
|
123
|
+
@children.each do |node|
|
124
|
+
if node.respond_to? :find
|
125
|
+
tmp = node.find(name)
|
126
|
+
if tmp
|
127
|
+
return tmp
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
return nil
|
132
|
+
end
|
133
|
+
def find_all(name)
|
134
|
+
result = []
|
135
|
+
if @node_name == name || full_name == name
|
136
|
+
result << self
|
137
|
+
end
|
138
|
+
@children.each do |node|
|
139
|
+
if node.respond_to? :find
|
140
|
+
result = result.concat(node.find_all(name))
|
141
|
+
end
|
142
|
+
end
|
143
|
+
return result
|
144
|
+
end
|
145
|
+
def value_node?
|
146
|
+
@children.length == 1 && @children[0].kind_of?(TextNode)
|
147
|
+
end
|
148
|
+
def get_namespace(prefix)
|
149
|
+
@namespaces[prefix] || @parent.get_namespace(prefix)
|
150
|
+
end
|
151
|
+
def defines_namespace?(prefix)
|
152
|
+
@attributes.keys.include?("xmlns:#{prefix}") || @parent.defines_namespace?(prefix)
|
153
|
+
end
|
154
|
+
def to_s(indentation = '')
|
155
|
+
# todo resolve attribute prefixes aswell
|
156
|
+
if @prefix && (not defines_namespace?(@prefix))
|
157
|
+
set_attr "xmlns:#{@prefix}", get_namespace(@prefix)
|
158
|
+
end
|
159
|
+
name = XmlMason.html_escape(full_name)
|
160
|
+
attr = (@attributes.any? ? (" " + @attributes.map { |key, value| XmlMason.html_escape(key) + '="' + XmlMason.html_escape(value) + '"' }.join(" ")) : "")
|
161
|
+
if @children.any?
|
162
|
+
if value_node?
|
163
|
+
children = @children[0].to_s(indentation + " ")
|
164
|
+
else
|
165
|
+
children = @children.map { |node| "\n" + node.to_s(indentation + " ") }.join("") + "\n" + indentation
|
166
|
+
end
|
167
|
+
indentation + "<" + name + attr + ">" + children + "</" + name + ">"
|
168
|
+
else
|
169
|
+
indentation + "<" + name + attr + " />"
|
170
|
+
end
|
171
|
+
end
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
end
|
metadata
ADDED
@@ -0,0 +1,80 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: digitaria-handsoap
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.3
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Troels Knak-Nielsen
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2009-04-29 00:00:00 -07:00
|
13
|
+
default_executable:
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: nokogiri
|
17
|
+
type: :runtime
|
18
|
+
version_requirement:
|
19
|
+
version_requirements: !ruby/object:Gem::Requirement
|
20
|
+
requirements:
|
21
|
+
- - ">="
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: 1.2.3
|
24
|
+
version:
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: curb
|
27
|
+
type: :runtime
|
28
|
+
version_requirement:
|
29
|
+
version_requirements: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 0.3.2
|
34
|
+
version:
|
35
|
+
description: Handsoap is a library for creating SOAP clients in Ruby
|
36
|
+
email: troelskn@gmail.com
|
37
|
+
executables: []
|
38
|
+
|
39
|
+
extensions: []
|
40
|
+
|
41
|
+
extra_rdoc_files:
|
42
|
+
- README.markdown
|
43
|
+
files:
|
44
|
+
- README.markdown
|
45
|
+
- Rakefile
|
46
|
+
- VERSION.yml
|
47
|
+
- generators/handsoap/USAGE
|
48
|
+
- generators/handsoap/handsoap_generator.rb
|
49
|
+
- generators/handsoap/templates/gateway.rbt
|
50
|
+
- lib/handsoap.rb
|
51
|
+
- lib/handsoap/service.rb
|
52
|
+
- lib/handsoap/xml_mason.rb
|
53
|
+
has_rdoc: true
|
54
|
+
homepage: http://github.com/troelskn/handsoap
|
55
|
+
post_install_message:
|
56
|
+
rdoc_options:
|
57
|
+
- --charset=UTF-8
|
58
|
+
require_paths:
|
59
|
+
- lib
|
60
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
61
|
+
requirements:
|
62
|
+
- - ">="
|
63
|
+
- !ruby/object:Gem::Version
|
64
|
+
version: "0"
|
65
|
+
version:
|
66
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
67
|
+
requirements:
|
68
|
+
- - ">="
|
69
|
+
- !ruby/object:Gem::Version
|
70
|
+
version: "0"
|
71
|
+
version:
|
72
|
+
requirements: []
|
73
|
+
|
74
|
+
rubyforge_project:
|
75
|
+
rubygems_version: 1.2.0
|
76
|
+
signing_key:
|
77
|
+
specification_version: 2
|
78
|
+
summary: Handsoap is a library for creating SOAP clients in Ruby
|
79
|
+
test_files: []
|
80
|
+
|