troelskn-handsoap 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.markdown ADDED
@@ -0,0 +1,219 @@
1
+ Handsoap
2
+ ===
3
+
4
+ What
5
+ ---
6
+ Handsoap is a library for creating SOAP clients in Ruby.
7
+
8
+ Why
9
+ ---
10
+
11
+ Ruby already has a SOAP-client library, [soap4r](http://dev.ctor.org/soap4r), so why create another one?
12
+
13
+ > Let me summarize SOAP4R: it smells like Java code built on a Monday morning by an EJB coder.
14
+ >
15
+ > -- [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/)
16
+
17
+ 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.
18
+
19
+ 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.
20
+
21
+ 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.
22
+
23
+ There are several benefits of using Handsoap:
24
+
25
+ * It supports the entire SOAP specification, all versions (because you have to implement it your self).
26
+ * You actually get a sporting chance to debug and fix protocol level bugs.
27
+ * It's much faster than soap4r, because it uses fast low-level libraries for xml-parsing and http-communication.
28
+
29
+ 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.
30
+
31
+ Handsoap vs. soap4r benchmark
32
+ ---
33
+
34
+ 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.
35
+
36
+ $ ruby tests/benchmark_test.rb 1000
37
+ Benchmarking 1000 calls ...
38
+ user system total real
39
+ handsoap 0.750000 0.090000 0.840000 ( 1.992437)
40
+ soap4r 2.240000 0.140000 2.380000 ( 3.605836)
41
+ ---------------
42
+ Legend:
43
+ The user CPU time, system CPU time, the sum of the user and system CPU times,
44
+ and the elapsed real time. The unit of time is seconds.
45
+
46
+ SOAP basics
47
+ ---
48
+
49
+ 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.
50
+
51
+ A SOAP client basically consists of three parts:
52
+
53
+ * A http-connectivity layer,
54
+ * a mechanism for marshalling native data types to XML,
55
+ * and a mechanism for unmarshalling XML to native data types.
56
+
57
+ 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)
58
+
59
+ 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.
60
+
61
+ The toolbox
62
+ ---
63
+
64
+ The Handsoap toolbox consists of the following components.
65
+
66
+ 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.
67
+
68
+ 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.
69
+
70
+ 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.
71
+
72
+ Recommendations
73
+ ---
74
+
75
+ ###Workflow
76
+
77
+ 1. Find the wsdl for the service you want to consume.
78
+
79
+ 2. Figure out the url for the endpoint, as well as the protocol version. Put this in a config file.
80
+ * To find the endpoint, look inside the wsdl, for `<soap:address location="..">`
81
+
82
+ 3. Create a service class. Add endpoints and protocol. Alias needed namespace(s).
83
+ * To find the namespace(s), look in the samples from soapUI. It will be imported as `v1`
84
+
85
+ 4. Open the wsdl in [soapUI](http://www.soapui.org/).
86
+
87
+ 5. In soapUI, find a sample request for the method you want to use. Copy+paste the body-part.
88
+
89
+ 6. Create a method in your service class (Use ruby naming convention)
90
+
91
+ 7. Write Ruby-code (using XmlMason) to generate a request that is similar to the example from soapUI. (In your copy+paste buffer)
92
+
93
+ 8. Write Ruby-code to parse the response (a Nokogiri XML-document) into Ruby data types.
94
+
95
+ 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).
96
+
97
+ Repeat point 5..9 for each method that you need to use.
98
+ Between each iteration, you should refactor shared code into helper functions.
99
+
100
+ ###Configuration
101
+
102
+ 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.
103
+
104
+ If you don't use Rails, it's still a good idea to move this information to a config file.
105
+
106
+ The configuration could look like this:
107
+
108
+ # wsdl: http://example.org/ws/service?WSDL
109
+ EXAMPLE_SERVICE_ENDPOINT = {
110
+ :uri => 'http://example.org/ws/service',
111
+ :version => 2
112
+ }
113
+
114
+ ###Service class
115
+
116
+ Put your service in a file under `app/models`. You should extend `Handsoap::Service`.
117
+
118
+ You need to provide the endpoint and the SOAP version (1.1 or 1.2). If in doubt, use version 2.
119
+
120
+ 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.
121
+
122
+ A typical service looks like the following:
123
+
124
+ # -*- coding: utf-8 -*-
125
+ require 'handsoap'
126
+
127
+ class Example::FooService < Handsoap::Service
128
+ endpoint EXAMPLE_SERVICE_ENDPOINT
129
+ on_create_document do |doc|
130
+ doc.alias 'wsdl', "http://example.org/ws/spec"
131
+ end
132
+ # public methods
133
+ # todo
134
+
135
+ private
136
+ # helpers
137
+ # todo
138
+ end
139
+
140
+ The above would go in the file `app/models/example/foo_service.rb`
141
+
142
+ ###Integration tests
143
+
144
+ 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.
145
+
146
+ For the sample service above, you would create a file in `test/integration/example/foo_service.rb`, with the following content:
147
+
148
+ # -*- coding: utf-8 -*-
149
+ require 'test_helper'
150
+
151
+ # Example::FooService.logger = $stdout
152
+
153
+ class Example::FooServiceTest < Test::Unit::TestCase
154
+ def test_update_icon
155
+ icon = { :href => 'http://www.example.com/icon.jpg', :type => 'image/jpeg' }
156
+ result = Example::FooService.update_icon!(icon)
157
+ assert_equal icon.type, result.type
158
+ end
159
+ end
160
+
161
+ 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.
162
+
163
+ ###Methods
164
+
165
+ You should use Ruby naming-conventions for methods names. If the method has side-effects, you should postfix it with an exclamation.
166
+ Repeat code inside the invoke-block, should be refactored out to *builders*, and the response should be parsed with a *parser*.
167
+
168
+ def update_icon!(icon)
169
+ response = invoke("wsdl:UpdateIcon") do |message|
170
+ build_icon!(message, icon)
171
+ end
172
+ parse_icon(response.document.xpath('//icon').first)
173
+ end
174
+
175
+
176
+ ###Helpers
177
+
178
+ You'll end up with two kinds of helpers; Ruby->XML transformers (aka. *builders*) and XML->Ruby transformers (aka. *parsers*).
179
+ It's recommended that you stick to the following style/naming scheme:
180
+
181
+ # icon -> xml
182
+ def build_icon!(message, icon)
183
+ message.add "icon" do |i|
184
+ i.set_attr "href", icon[:href]
185
+ i.set_attr "type", icon[:type]
186
+ end
187
+ end
188
+
189
+ # xml -> icon
190
+ def parse_icon(node)
191
+ { :href => node['href'], :type => node['type'] }
192
+ end
193
+
194
+ or, if you prefer, you can use a class to represent entities:
195
+
196
+ # icon -> xml
197
+ def build_icon!(message, icon)
198
+ message.add "icon" do |i|
199
+ i.set_attr "href", icon.href
200
+ i.set_attr "type", icon.type
201
+ end
202
+ end
203
+
204
+ # xml -> icon
205
+ def parse_icon(node)
206
+ Icon.new :href => node['href'],
207
+ :type => node['type']
208
+ end
209
+
210
+ License
211
+ ---
212
+
213
+ Copyright: [Unwire A/S](http://www.unwire.dk), 2009
214
+
215
+ License: [Creative Commons Attribution 2.5 Denmark License](http://creativecommons.org/licenses/by/2.5/dk/)
216
+
217
+ ___
218
+
219
+ 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
@@ -0,0 +1,4 @@
1
+ ---
2
+ :patch: 0
3
+ :major: 0
4
+ :minor: 1
@@ -0,0 +1,227 @@
1
+ # -*- coding: utf-8 -*-
2
+ require 'rubygems'
3
+ require 'httpclient'
4
+ require 'nokogiri'
5
+ require 'curb'
6
+ require 'handsoap/xml_mason'
7
+
8
+ module Handsoap
9
+
10
+ def self.http_driver
11
+ @http_driver || :curb
12
+ end
13
+
14
+ def self.http_driver=(driver)
15
+ @http_driver = driver
16
+ end
17
+
18
+ SOAP_NAMESPACE = { 1 => 'http://schemas.xmlsoap.org/soap/envelope/', 2 => 'http://www.w3.org/2001/12/soap-encoding' }
19
+
20
+ class Response
21
+ def initialize(http_body, soap_namespace)
22
+ @http_body = http_body
23
+ @soap_namespace = soap_namespace
24
+ @document = :lazy
25
+ @fault = :lazy
26
+ end
27
+ def document?
28
+ !! document
29
+ end
30
+ def document
31
+ if @document == :lazy
32
+ doc = Nokogiri::XML(@http_body)
33
+ @document = (doc && doc.root && doc.errors.empty?) ? doc : nil
34
+ end
35
+ return @document
36
+ end
37
+ def fault?
38
+ !! fault
39
+ end
40
+ def fault
41
+ if @fault == :lazy
42
+ nodes = document? ? document.xpath('/env:Envelope/env:Body/descendant-or-self::env:Fault', { 'env' => @soap_namespace }) : []
43
+ @fault = nodes.any? ? Fault.from_xml(nodes.first, :namespace => @soap_namespace) : nil
44
+ end
45
+ return @fault
46
+ end
47
+ end
48
+
49
+ class Fault < Exception
50
+ attr_reader :code, :reason
51
+ def initialize(code, reason)
52
+ @code = code
53
+ @reason = reason
54
+ end
55
+ def to_s
56
+ "Handsoap::Fault { :code => '#{@code}', :reason => '#{@reason}' }"
57
+ end
58
+ def self.from_xml(node, options = { :namespace => nil })
59
+ if not options[:namespace]
60
+ raise "Missing option :namespace"
61
+ end
62
+ ns = { 'env' => options[:namespace] }
63
+ fault_code = node.xpath('./env:Code/env:Value/text()', ns).to_s
64
+ if fault_code == ""
65
+ fault_code = node.xpath('./faultcode/text()', ns).to_s
66
+ end
67
+ reason = node.xpath('./env:Reason/env:Text[1]/text()', ns).to_s
68
+ if reason == ""
69
+ reason = node.xpath('./faultstring/text()', ns).to_s
70
+ end
71
+ self.new(fault_code, reason)
72
+ end
73
+ end
74
+
75
+ class Service
76
+ @@logger = nil
77
+ def self.logger=(io)
78
+ @@logger = io
79
+ end
80
+ def self.endpoint(args = {})
81
+ @protocol_version = args[:version] || raise("Missing option :version")
82
+ @uri = args[:uri] || raise("Missing option :uri")
83
+ end
84
+ def self.envelope_namespace
85
+ if SOAP_NAMESPACE[@protocol_version].nil?
86
+ raise "Unknown protocol version '#{@protocol_version.inspect}'"
87
+ end
88
+ SOAP_NAMESPACE[@protocol_version]
89
+ end
90
+ def self.map_method(mapping)
91
+ if @mapping.nil?
92
+ @mapping = {}
93
+ end
94
+ @mapping.merge! mapping
95
+ end
96
+ def self.on_create_document(&block)
97
+ @create_document_callback = block
98
+ end
99
+ def self.fire_on_create_document(doc)
100
+ if @create_document_callback
101
+ @create_document_callback.call doc
102
+ end
103
+ end
104
+ def self.uri
105
+ @uri
106
+ end
107
+ def self.get_mapping(name)
108
+ @mapping[name] if @mapping
109
+ end
110
+ @@instance = {}
111
+ def self.instance
112
+ @@instance[self.to_s] ||= self.new
113
+ end
114
+ def self.method_missing(method, *args)
115
+ if instance.respond_to?(method)
116
+ instance.__send__ method, *args
117
+ else
118
+ super
119
+ end
120
+ end
121
+ def method_missing(method, *args)
122
+ action = self.class.get_mapping(method)
123
+ if action
124
+ invoke(action, *args)
125
+ else
126
+ super
127
+ end
128
+ 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
140
+ def on_before_dispatch
141
+ end
142
+ def on_fault(fault)
143
+ raise fault
144
+ end
145
+ private
146
+ def debug(message = nil)
147
+ if @@logger
148
+ if message
149
+ @@logger.puts(message)
150
+ end
151
+ if block_given?
152
+ yield @@logger
153
+ end
154
+ end
155
+ end
156
+ def dispatch(doc)
157
+ on_before_dispatch()
158
+ headers = {
159
+ "Content-Type" => "text/xml;charset=UTF-8"
160
+ }
161
+ body = doc.to_s
162
+ debug do |logger|
163
+ logger.puts "==============="
164
+ logger.puts "--- Request ---"
165
+ logger.puts "URI: %s" % [self.class.uri]
166
+ logger.puts headers.map { |key,value| key + ": " + value }.join("\n")
167
+ logger.puts "---"
168
+ logger.puts body
169
+ end
170
+ if Handsoap.http_driver == :curb
171
+ http_client = Curl::Easy.new(self.class.uri)
172
+ http_client.headers = headers
173
+ http_client.http_post body
174
+ debug do |logger|
175
+ logger.puts "--- Response ---"
176
+ logger.puts "HTTP Status: %s" % [http_client.response_code]
177
+ logger.puts "Content-Type: %s" % [http_client.content_type]
178
+ logger.puts "---"
179
+ logger.puts Handsoap.pretty_format_envelope(http_client.body_str)
180
+ end
181
+ soap_response = Response.new(http_client.body_str, self.class.envelope_namespace)
182
+ else
183
+ response = HTTPClient.new.post(self.class.uri, body, headers)
184
+ debug do |logger|
185
+ logger.puts "--- Response ---"
186
+ logger.puts "HTTP Status: %s" % [response.status]
187
+ logger.puts "Content-Type: %s" % [response.contenttype]
188
+ logger.puts "---"
189
+ logger.puts Handsoap.pretty_format_envelope(response.content)
190
+ end
191
+ soap_response = Response.new(response.content, self.class.envelope_namespace)
192
+ end
193
+ if soap_response.fault?
194
+ return self.on_fault(soap_response.fault)
195
+ end
196
+ return soap_response
197
+ end
198
+ def make_envelope
199
+ doc = XmlMason::Document.new do |doc|
200
+ doc.alias 'env', self.class.envelope_namespace
201
+ doc.add "env:Envelope" do |env|
202
+ env.add "*:Header"
203
+ env.add "*:Body"
204
+ end
205
+ end
206
+ self.class.fire_on_create_document doc
207
+ if block_given?
208
+ yield doc.find("Body")
209
+ end
210
+ return doc
211
+ end
212
+ end
213
+
214
+ def self.pretty_format_envelope(xml_string)
215
+ if /^<.*:Envelope/.match(xml_string)
216
+ begin
217
+ doc = Nokogiri::XML(xml_string)
218
+ rescue Exception => ex
219
+ return "Formatting failed: " + ex.to_s
220
+ end
221
+ return doc.to_s
222
+ # return "\n\e[1;33m" + doc.to_s + "\e[0m"
223
+ end
224
+ return xml_string
225
+ end
226
+
227
+ end
@@ -0,0 +1,175 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ module Handsoap
4
+
5
+ module XmlMason
6
+
7
+ HTML_ESCAPE = { '&' => '&amp;', '"' => '&quot;', '>' => '&gt;', '<' => '&lt;' }
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
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
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
164
+ else
165
+ children = @children.map { |node| "\n" + node.to_s }.join("").gsub(/\n/, "\n ") + "\n"
166
+ end
167
+ "<" + name + attr + ">" + children + "</" + name + ">"
168
+ else
169
+ "<" + name + attr + " />"
170
+ end
171
+ end
172
+ end
173
+ end
174
+
175
+ end
data/lib/handsoap.rb ADDED
@@ -0,0 +1,5 @@
1
+ # -*- coding: utf-8 -*-
2
+ require 'handsoap/xml_mason'
3
+ require 'handsoap/service'
4
+
5
+
metadata ADDED
@@ -0,0 +1,77 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: troelskn-handsoap
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Troels Knak-Nielsen
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-04-22 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
+ - lib/handsoap.rb
48
+ - lib/handsoap/service.rb
49
+ - lib/handsoap/xml_mason.rb
50
+ has_rdoc: true
51
+ homepage: http://github.com/troelskn/handsoap
52
+ post_install_message:
53
+ rdoc_options:
54
+ - --charset=UTF-8
55
+ require_paths:
56
+ - lib
57
+ required_ruby_version: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: "0"
62
+ version:
63
+ required_rubygems_version: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - ">="
66
+ - !ruby/object:Gem::Version
67
+ version: "0"
68
+ version:
69
+ requirements: []
70
+
71
+ rubyforge_project:
72
+ rubygems_version: 1.2.0
73
+ signing_key:
74
+ specification_version: 2
75
+ summary: Handsoap is a library for creating SOAP clients in Ruby
76
+ test_files: []
77
+