troelskn-handsoap 0.2.9 → 0.3.0
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 +10 -3
- data/VERSION.yml +2 -2
- data/lib/handsoap/compiler.rb +29 -27
- data/lib/handsoap/parser.rb +4 -1
- data/lib/handsoap/service.rb +49 -37
- data/lib/handsoap/xml_mason.rb +36 -9
- data/lib/handsoap/xml_query_front.rb +249 -0
- data/lib/handsoap.rb +1 -0
- metadata +11 -15
data/README.markdown
CHANGED
|
@@ -5,7 +5,7 @@ Install
|
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
gem sources -a http://gems.github.com
|
|
8
|
-
sudo gem install troelskn-handsoap curb
|
|
8
|
+
sudo gem install troelskn-handsoap curb nokogiri
|
|
9
9
|
|
|
10
10
|
What
|
|
11
11
|
---
|
|
@@ -13,6 +13,8 @@ Handsoap is a library for creating SOAP clients in Ruby.
|
|
|
13
13
|
|
|
14
14
|
[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)
|
|
15
15
|
|
|
16
|
+
API docs are at [http://rdoc.info/projects/troelskn/handsoap](http://rdoc.info/projects/troelskn/handsoap)
|
|
17
|
+
|
|
16
18
|

|
|
17
19
|
|
|
18
20
|
Why
|
|
@@ -68,6 +70,11 @@ The protocol also contains a large and unwieldy specification of how to do the (
|
|
|
68
70
|
|
|
69
71
|
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.
|
|
70
72
|
|
|
73
|
+
API documnetation
|
|
74
|
+
---
|
|
75
|
+
|
|
76
|
+
In addition to this guide, there's autogenerated API documentation available at [http://rdoc.info/projects/troelskn/handsoap](http://rdoc.info/projects/troelskn/handsoap)
|
|
77
|
+
|
|
71
78
|
The toolbox
|
|
72
79
|
---
|
|
73
80
|
|
|
@@ -75,7 +82,7 @@ The Handsoap toolbox consists of the following components.
|
|
|
75
82
|
|
|
76
83
|
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.
|
|
77
84
|
|
|
78
|
-
For parsing XML, Handsoap
|
|
85
|
+
For parsing XML, Handsoap defaults to use [Nokogiri](http://github.com/tenderlove/nokogiri/tree/master). Handsoap has an abstraction layer, so that you can switch between REXML, Nokogiri and ruby-libxml. Besides providing portability between these parsers, Handsop also gives some helper functions that are meaningful when parsing SOAP envelopes.
|
|
79
86
|
|
|
80
87
|
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.
|
|
81
88
|
|
|
@@ -101,7 +108,7 @@ Recommendations
|
|
|
101
108
|
|
|
102
109
|
7. Write Ruby-code (using XmlMason) to generate a request that is similar to the example from soapUI. (In your copy+paste buffer)
|
|
103
110
|
|
|
104
|
-
8. Write Ruby-code to parse the response (
|
|
111
|
+
8. Write Ruby-code to parse the response (an XML-document) into Ruby data types.
|
|
105
112
|
|
|
106
113
|
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).
|
|
107
114
|
|
data/VERSION.yml
CHANGED
data/lib/handsoap/compiler.rb
CHANGED
|
@@ -1,41 +1,43 @@
|
|
|
1
1
|
# -*- coding: utf-8 -*-
|
|
2
2
|
module Handsoap
|
|
3
|
-
|
|
3
|
+
# Used internally to generate Ruby source code
|
|
4
|
+
class CodeWriter #:nodoc: all
|
|
4
5
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
6
|
+
def initialize
|
|
7
|
+
@buffer = ""
|
|
8
|
+
@indentation = 0
|
|
9
|
+
end
|
|
9
10
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
11
|
+
def begin(text)
|
|
12
|
+
puts(text)
|
|
13
|
+
indent
|
|
14
|
+
end
|
|
14
15
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
16
|
+
def end(str = "end")
|
|
17
|
+
unindent
|
|
18
|
+
puts(str)
|
|
19
|
+
end
|
|
19
20
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
21
|
+
def puts(text = "")
|
|
22
|
+
@buffer << text.gsub(/^(.*)$/, (" " * @indentation) + "\\1")
|
|
23
|
+
@buffer << "\n" # unless @buffer.match(/\n$/)
|
|
24
|
+
end
|
|
24
25
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
26
|
+
def indent
|
|
27
|
+
@indentation = @indentation + 1
|
|
28
|
+
end
|
|
28
29
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
30
|
+
def unindent
|
|
31
|
+
@indentation = @indentation - 1
|
|
32
|
+
end
|
|
32
33
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
end
|
|
34
|
+
def to_s
|
|
35
|
+
@buffer
|
|
36
36
|
end
|
|
37
|
+
end
|
|
37
38
|
|
|
38
|
-
|
|
39
|
+
# Used internally by the generator to generate a Service stub.
|
|
40
|
+
class Compiler #:nodoc: all
|
|
39
41
|
|
|
40
42
|
def initialize(wsdl, basename = nil)
|
|
41
43
|
@wsdl = wsdl
|
data/lib/handsoap/parser.rb
CHANGED
data/lib/handsoap/service.rb
CHANGED
|
@@ -1,35 +1,7 @@
|
|
|
1
1
|
# -*- coding: utf-8 -*-
|
|
2
|
-
require 'rubygems'
|
|
3
|
-
require 'nokogiri'
|
|
4
2
|
require 'time'
|
|
5
3
|
require 'handsoap/xml_mason'
|
|
6
|
-
|
|
7
|
-
# Nokogiri doesn't have a way of getting plain strings out,
|
|
8
|
-
# so this monkeypatch adds that capability ..
|
|
9
|
-
module Utf8StringPatch
|
|
10
|
-
def to_utf8
|
|
11
|
-
# HTMLEntities.decode_entities(self.serialize(:encoding => 'UTF-8'))
|
|
12
|
-
if Gem.loaded_specs['nokogiri'].version >= Gem::Version.new('1.3.0')
|
|
13
|
-
self.serialize(:encoding => 'UTF-8').gsub('<', '<').gsub('>', '>').gsub('"', '"').gsub(''', "'").gsub('&', '&')
|
|
14
|
-
else
|
|
15
|
-
self.serialize('UTF-8').gsub('<', '<').gsub('>', '>').gsub('"', '"').gsub(''', "'").gsub('&', '&')
|
|
16
|
-
end
|
|
17
|
-
end
|
|
18
|
-
end
|
|
19
|
-
|
|
20
|
-
module Nokogiri
|
|
21
|
-
module XML
|
|
22
|
-
class Text
|
|
23
|
-
include Utf8StringPatch
|
|
24
|
-
end
|
|
25
|
-
class Nodeset
|
|
26
|
-
include Utf8StringPatch
|
|
27
|
-
end
|
|
28
|
-
class Attr
|
|
29
|
-
include Utf8StringPatch
|
|
30
|
-
end
|
|
31
|
-
end
|
|
32
|
-
end
|
|
4
|
+
require 'handsoap/xml_query_front'
|
|
33
5
|
|
|
34
6
|
module Handsoap
|
|
35
7
|
|
|
@@ -44,6 +16,14 @@ module Handsoap
|
|
|
44
16
|
return driver
|
|
45
17
|
end
|
|
46
18
|
|
|
19
|
+
def self.xml_query_driver
|
|
20
|
+
@xml_query_driver || (self.xml_query_driver = :nokogiri)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def self.xml_query_driver=(driver)
|
|
24
|
+
@xml_query_driver = Handsoap::XmlQueryFront.load_driver!(driver)
|
|
25
|
+
end
|
|
26
|
+
|
|
47
27
|
SOAP_NAMESPACE = { 1 => 'http://schemas.xmlsoap.org/soap/envelope/', 2 => 'http://www.w3.org/2001/12/soap-encoding' }
|
|
48
28
|
|
|
49
29
|
class Response
|
|
@@ -58,8 +38,11 @@ module Handsoap
|
|
|
58
38
|
end
|
|
59
39
|
def document
|
|
60
40
|
if @document == :lazy
|
|
61
|
-
|
|
62
|
-
|
|
41
|
+
begin
|
|
42
|
+
@document = Handsoap::XmlQueryFront.parse_string(@http_body, Handsoap.xml_query_driver)
|
|
43
|
+
rescue Handsoap::XmlQueryFront::ParseError => ex
|
|
44
|
+
@document = nil
|
|
45
|
+
end
|
|
63
46
|
end
|
|
64
47
|
return @document
|
|
65
48
|
end
|
|
@@ -160,7 +143,18 @@ module Handsoap
|
|
|
160
143
|
super
|
|
161
144
|
end
|
|
162
145
|
end
|
|
163
|
-
|
|
146
|
+
# Creates an XML document and sends it over HTTP.
|
|
147
|
+
#
|
|
148
|
+
# +action+ is the QName of the rootnode of the envelope.
|
|
149
|
+
#
|
|
150
|
+
# +options+ currently takes one option +:soap_action+, which can be one of:
|
|
151
|
+
#
|
|
152
|
+
# :auto sends a SOAPAction http header, deduced from the action name. (This is the default)
|
|
153
|
+
#
|
|
154
|
+
# +String+ sends a SOAPAction http header.
|
|
155
|
+
#
|
|
156
|
+
# +nil+ sends no SOAPAction http header.
|
|
157
|
+
def invoke(action, options = { :soap_action => :auto }, &block) # :yields: Handsoap::XmlMason::Element
|
|
164
158
|
if action
|
|
165
159
|
if options.kind_of? String
|
|
166
160
|
options = { :soap_action => options }
|
|
@@ -179,13 +173,21 @@ module Handsoap
|
|
|
179
173
|
dispatch(doc, options[:soap_action])
|
|
180
174
|
end
|
|
181
175
|
end
|
|
176
|
+
# Hook that is called before the message is dispatched.
|
|
177
|
+
#
|
|
178
|
+
# You can override this to provide filtering and logging.
|
|
182
179
|
def on_before_dispatch
|
|
183
180
|
end
|
|
181
|
+
# Hook that is called if the dispatch returns a +Fault+.
|
|
182
|
+
#
|
|
183
|
+
# Default behaviour is to raise the Fault, but you can override this to provide logging and more fine-grained handling faults.
|
|
184
184
|
def on_fault(fault)
|
|
185
185
|
raise fault
|
|
186
186
|
end
|
|
187
187
|
private
|
|
188
188
|
# Helper to serialize a node into a ruby string
|
|
189
|
+
#
|
|
190
|
+
# *deprecated*. Use Handsoap::XmlQueryFront::BaseDriver#to_s
|
|
189
191
|
def xml_to_str(node, xquery = nil)
|
|
190
192
|
n = xquery ? node.xpath(xquery, ns).first : node
|
|
191
193
|
return if n.nil?
|
|
@@ -193,6 +195,8 @@ module Handsoap
|
|
|
193
195
|
end
|
|
194
196
|
alias_method :xml_to_s, :xml_to_str
|
|
195
197
|
# Helper to serialize a node into a ruby integer
|
|
198
|
+
#
|
|
199
|
+
# *deprecated*. Use Handsoap::XmlQueryFront::BaseDriver#to_i
|
|
196
200
|
def xml_to_int(node, xquery = nil)
|
|
197
201
|
n = xquery ? node.xpath(xquery, ns).first : node
|
|
198
202
|
return if n.nil?
|
|
@@ -200,6 +204,8 @@ module Handsoap
|
|
|
200
204
|
end
|
|
201
205
|
alias_method :xml_to_i, :xml_to_int
|
|
202
206
|
# Helper to serialize a node into a ruby float
|
|
207
|
+
#
|
|
208
|
+
# *deprecated*. Use Handsoap::XmlQueryFront::BaseDriver#to_f
|
|
203
209
|
def xml_to_float(node, xquery = nil)
|
|
204
210
|
n = xquery ? node.xpath(xquery, ns).first : node
|
|
205
211
|
return if n.nil?
|
|
@@ -207,18 +213,22 @@ module Handsoap
|
|
|
207
213
|
end
|
|
208
214
|
alias_method :xml_to_f, :xml_to_float
|
|
209
215
|
# Helper to serialize a node into a ruby boolean
|
|
216
|
+
#
|
|
217
|
+
# *deprecated*. Use Handsoap::XmlQueryFront::BaseDriver#to_boolean
|
|
210
218
|
def xml_to_bool(node, xquery = nil)
|
|
211
219
|
n = xquery ? node.xpath(xquery, ns).first : node
|
|
212
220
|
return if n.nil?
|
|
213
221
|
n.to_s == "true"
|
|
214
222
|
end
|
|
215
223
|
# Helper to serialize a node into a ruby Time object
|
|
224
|
+
#
|
|
225
|
+
# *deprecated*. Use Handsoap::XmlQueryFront::BaseDriver#to_date
|
|
216
226
|
def xml_to_date(node, xquery = nil)
|
|
217
227
|
n = xquery ? node.xpath(xquery, ns).first : node
|
|
218
228
|
return if n.nil?
|
|
219
229
|
Time.iso8601(n.to_s)
|
|
220
230
|
end
|
|
221
|
-
def debug(message = nil)
|
|
231
|
+
def debug(message = nil) #:nodoc:
|
|
222
232
|
if @@logger
|
|
223
233
|
if message
|
|
224
234
|
@@logger.puts(message)
|
|
@@ -228,8 +238,9 @@ module Handsoap
|
|
|
228
238
|
end
|
|
229
239
|
end
|
|
230
240
|
end
|
|
241
|
+
# Takes care of the HTTP level dispatch.
|
|
231
242
|
def dispatch(doc, action)
|
|
232
|
-
on_before_dispatch
|
|
243
|
+
on_before_dispatch
|
|
233
244
|
headers = {
|
|
234
245
|
"Content-Type" => "#{self.class.request_content_type};charset=UTF-8"
|
|
235
246
|
}
|
|
@@ -273,7 +284,8 @@ module Handsoap
|
|
|
273
284
|
end
|
|
274
285
|
return soap_response
|
|
275
286
|
end
|
|
276
|
-
|
|
287
|
+
# Creates a standard SOAP envelope and yields the +Body+ element.
|
|
288
|
+
def make_envelope # :yields: Handsoap::XmlMason::Element
|
|
277
289
|
doc = XmlMason::Document.new do |doc|
|
|
278
290
|
doc.alias 'env', self.class.envelope_namespace
|
|
279
291
|
doc.add "env:Envelope" do |env|
|
|
@@ -292,11 +304,11 @@ module Handsoap
|
|
|
292
304
|
def self.pretty_format_envelope(xml_string)
|
|
293
305
|
if /^<.*:Envelope/.match(xml_string)
|
|
294
306
|
begin
|
|
295
|
-
doc =
|
|
307
|
+
doc = Handsoap::XmlQueryFront.parse_string(xml_string, Handsoap.xml_query_driver)
|
|
296
308
|
rescue Exception => ex
|
|
297
309
|
return "Formatting failed: " + ex.to_s
|
|
298
310
|
end
|
|
299
|
-
return doc.
|
|
311
|
+
return doc.to_xml
|
|
300
312
|
# return "\n\e[1;33m" + doc.to_s + "\e[0m"
|
|
301
313
|
end
|
|
302
314
|
return xml_string
|
data/lib/handsoap/xml_mason.rb
CHANGED
|
@@ -2,28 +2,44 @@
|
|
|
2
2
|
|
|
3
3
|
module Handsoap
|
|
4
4
|
|
|
5
|
+
# XmlMason is a simple XML builder.
|
|
5
6
|
module XmlMason
|
|
6
7
|
|
|
7
|
-
|
|
8
|
+
XML_ESCAPE = { '&' => '&', '"' => '"', '>' => '>', '<' => '<' }
|
|
8
9
|
|
|
9
|
-
def self.
|
|
10
|
-
s.to_s.gsub(/[&"><]/) { |special|
|
|
10
|
+
def self.xml_escape(s)
|
|
11
|
+
s.to_s.gsub(/[&"><]/) { |special| XML_ESCAPE[special] }
|
|
11
12
|
end
|
|
12
13
|
|
|
13
14
|
class Node
|
|
14
15
|
def initialize
|
|
15
16
|
@namespaces = {}
|
|
16
17
|
end
|
|
17
|
-
def add(node_name, value = nil, *flags)
|
|
18
|
+
def add(node_name, value = nil, *flags) # :yields: Handsoap::XmlMason::Element
|
|
18
19
|
prefix, name = parse_ns(node_name)
|
|
19
20
|
node = append_child Element.new(self, prefix, name, value, flags)
|
|
20
21
|
if block_given?
|
|
21
22
|
yield node
|
|
22
23
|
end
|
|
23
24
|
end
|
|
25
|
+
# Registers a prefix for a namespace.
|
|
26
|
+
#
|
|
27
|
+
# You must register a namespace, before you can refer it.
|
|
24
28
|
def alias(prefix, namespaces)
|
|
25
29
|
@namespaces[prefix] = namespaces
|
|
26
30
|
end
|
|
31
|
+
# Finds the first element whos +node_name+ equals +name+
|
|
32
|
+
#
|
|
33
|
+
# Doesn't regard namespaces/prefixes.
|
|
34
|
+
def find(name)
|
|
35
|
+
raise NotImplementedError.new
|
|
36
|
+
end
|
|
37
|
+
# Finds all elements whos +node_name+ equals +name+
|
|
38
|
+
#
|
|
39
|
+
# Doesn't regard namespaces/prefixes.
|
|
40
|
+
def find_all(name)
|
|
41
|
+
raise NotImplementedError.new
|
|
42
|
+
end
|
|
27
43
|
def parse_ns(name)
|
|
28
44
|
matches = name.match /^([^:]+):(.*)$/
|
|
29
45
|
if matches
|
|
@@ -36,7 +52,7 @@ module Handsoap
|
|
|
36
52
|
end
|
|
37
53
|
|
|
38
54
|
class Document < Node
|
|
39
|
-
def initialize
|
|
55
|
+
def initialize # :yields: Document
|
|
40
56
|
super
|
|
41
57
|
@document_element = nil
|
|
42
58
|
if block_given?
|
|
@@ -74,7 +90,7 @@ module Handsoap
|
|
|
74
90
|
@text = text
|
|
75
91
|
end
|
|
76
92
|
def to_s(indentation = '')
|
|
77
|
-
XmlMason.
|
|
93
|
+
XmlMason.xml_escape(@text)
|
|
78
94
|
end
|
|
79
95
|
end
|
|
80
96
|
|
|
@@ -85,7 +101,7 @@ module Handsoap
|
|
|
85
101
|
end
|
|
86
102
|
|
|
87
103
|
class Element < Node
|
|
88
|
-
def initialize(parent, prefix, node_name, value = nil, flags = [])
|
|
104
|
+
def initialize(parent, prefix, node_name, value = nil, flags = []) # :yields: Handsoap::XmlMason::Element
|
|
89
105
|
super()
|
|
90
106
|
# if prefix.to_s == ""
|
|
91
107
|
# raise "missing prefix"
|
|
@@ -102,12 +118,17 @@ module Handsoap
|
|
|
102
118
|
yield self
|
|
103
119
|
end
|
|
104
120
|
end
|
|
121
|
+
# Returns the document that this element belongs to, or self if this is the document.
|
|
105
122
|
def document
|
|
106
123
|
@parent.respond_to?(:document) ? @parent.document : @parent
|
|
107
124
|
end
|
|
125
|
+
# Returns the qname (prefix:nodename)
|
|
108
126
|
def full_name
|
|
109
127
|
@prefix.nil? ? @node_name : (@prefix + ":" + @node_name)
|
|
110
128
|
end
|
|
129
|
+
# Adds a child node.
|
|
130
|
+
#
|
|
131
|
+
# You usually won't need to call this method, but will rather use +add+
|
|
111
132
|
def append_child(node)
|
|
112
133
|
if value_node?
|
|
113
134
|
raise "Element already has a text value. Can't add nodes"
|
|
@@ -115,6 +136,11 @@ module Handsoap
|
|
|
115
136
|
@children << node
|
|
116
137
|
return node
|
|
117
138
|
end
|
|
139
|
+
# Sets the inner text of this element.
|
|
140
|
+
#
|
|
141
|
+
# By default the string is escaped, but you can pass the flag :raw to inject XML.
|
|
142
|
+
#
|
|
143
|
+
# You usually won't need to call this method, but will rather use +add+
|
|
118
144
|
def set_value(value, *flags)
|
|
119
145
|
if @children.length > 0
|
|
120
146
|
raise "Element already has children. Can't set value"
|
|
@@ -125,6 +151,7 @@ module Handsoap
|
|
|
125
151
|
@children = [TextNode.new(value)]
|
|
126
152
|
end
|
|
127
153
|
end
|
|
154
|
+
# Sets the value of an attribute.
|
|
128
155
|
def set_attr(name, value)
|
|
129
156
|
full_name = parse_ns(name).join(":")
|
|
130
157
|
@attributes[name] = value
|
|
@@ -171,8 +198,8 @@ module Handsoap
|
|
|
171
198
|
if @prefix && (not defines_namespace?(@prefix))
|
|
172
199
|
set_attr "xmlns:#{@prefix}", get_namespace(@prefix)
|
|
173
200
|
end
|
|
174
|
-
name = XmlMason.
|
|
175
|
-
attr = (@attributes.any? ? (" " + @attributes.map { |key, value| XmlMason.
|
|
201
|
+
name = XmlMason.xml_escape(full_name)
|
|
202
|
+
attr = (@attributes.any? ? (" " + @attributes.map { |key, value| XmlMason.xml_escape(key) + '="' + XmlMason.xml_escape(value) + '"' }.join(" ")) : "")
|
|
176
203
|
if @children.any?
|
|
177
204
|
if value_node?
|
|
178
205
|
children = @children[0].to_s(indentation + " ")
|
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
module Handsoap
|
|
3
|
+
#
|
|
4
|
+
# A simple frontend for parsing XML document with Xpath.
|
|
5
|
+
#
|
|
6
|
+
# This provides a unified interface for multiple xpath-capable dom-parsers,
|
|
7
|
+
# allowing seamless switching between the underlying implementations.
|
|
8
|
+
#
|
|
9
|
+
# A document is loaded using the function Handsoap::XmlQueryFront.parse_string, passing
|
|
10
|
+
# the xml source string and a driver, which can (currently) be one of:
|
|
11
|
+
#
|
|
12
|
+
# :rexml
|
|
13
|
+
# :nokogiri
|
|
14
|
+
# :libxml
|
|
15
|
+
#
|
|
16
|
+
# The resulting object is a wrapper, of the type Handsoap::XmlQueryFront::BaseDriver.
|
|
17
|
+
#
|
|
18
|
+
module XmlQueryFront
|
|
19
|
+
|
|
20
|
+
# This error is raised if the document didn't parse
|
|
21
|
+
class ParseError < RuntimeError; end
|
|
22
|
+
|
|
23
|
+
# Loads requirements for a driver.
|
|
24
|
+
#
|
|
25
|
+
# This function is implicitly called by +parse_string+.
|
|
26
|
+
def self.load_driver!(driver)
|
|
27
|
+
if driver == :rexml
|
|
28
|
+
require 'rexml/document'
|
|
29
|
+
elsif driver == :nokogiri
|
|
30
|
+
require 'nokogiri'
|
|
31
|
+
elsif driver == :libxml
|
|
32
|
+
require 'libxml'
|
|
33
|
+
else
|
|
34
|
+
raise "Unknown driver #{driver}"
|
|
35
|
+
end
|
|
36
|
+
return driver
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Returns a wrapped XML parser, using the requested driver.
|
|
40
|
+
#
|
|
41
|
+
# +driver+ can be one of the following:
|
|
42
|
+
# :rexml
|
|
43
|
+
# :nokogiri
|
|
44
|
+
# :libxml
|
|
45
|
+
def self.parse_string(xml_string, driver)
|
|
46
|
+
load_driver!(driver)
|
|
47
|
+
if driver == :rexml
|
|
48
|
+
doc = REXML::Document.new(xml_string)
|
|
49
|
+
raise ParseError.new if doc.root.nil?
|
|
50
|
+
XmlQueryFront::REXMLDriver.new(doc)
|
|
51
|
+
elsif driver == :nokogiri
|
|
52
|
+
doc = Nokogiri::XML(xml_string)
|
|
53
|
+
raise ParseError.new unless (doc && doc.root && doc.errors.empty?)
|
|
54
|
+
XmlQueryFront::NokogiriDriver.new(doc)
|
|
55
|
+
elsif driver == :libxml
|
|
56
|
+
begin
|
|
57
|
+
LibXML::XML::Error.set_handler &LibXML::XML::Error::QUIET_HANDLER
|
|
58
|
+
doc = XmlQueryFront::LibXMLDriver.new(LibXML::XML::Parser.string(xml_string).parse)
|
|
59
|
+
rescue ArgumentError, LibXML::XML::Error => ex
|
|
60
|
+
raise ParseError.new
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Wraps the underlying (native) xml driver, and provides a uniform interface.
|
|
66
|
+
module BaseDriver
|
|
67
|
+
def initialize(element, namespaces = {})
|
|
68
|
+
@element = element
|
|
69
|
+
@namespaces = namespaces
|
|
70
|
+
end
|
|
71
|
+
# Registers a prefix to refer to a namespace.
|
|
72
|
+
#
|
|
73
|
+
# You can either register a nemspace with this function or pass it explicitly to the +xpath+ method.
|
|
74
|
+
def add_namespace(prefix, uri)
|
|
75
|
+
@namespaces[prefix] = uri
|
|
76
|
+
end
|
|
77
|
+
# Checks that an xpath-query doesn't refer to any undefined prefixes in +ns+
|
|
78
|
+
def assert_prefixes!(expression, ns)
|
|
79
|
+
expression.scan(/([a-zA-Z_][a-zA-Z0-9_.-]*):[^:]+/).map{|m| m[0] }.each do |prefix|
|
|
80
|
+
raise "Undefined prefix '#{prefix}'" if ns[prefix].nil?
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
# Returns the value of the element as an integer.
|
|
84
|
+
#
|
|
85
|
+
# See +to_s+
|
|
86
|
+
def to_i
|
|
87
|
+
t = self.to_s
|
|
88
|
+
return if t.nil?
|
|
89
|
+
t.to_i
|
|
90
|
+
end
|
|
91
|
+
# Returns the value of the element as a float.
|
|
92
|
+
#
|
|
93
|
+
# See +to_s+
|
|
94
|
+
def to_f
|
|
95
|
+
t = self.to_s
|
|
96
|
+
return if t.nil?
|
|
97
|
+
t.to_f
|
|
98
|
+
end
|
|
99
|
+
# Returns the value of the element as an boolean.
|
|
100
|
+
#
|
|
101
|
+
# See +to_s+
|
|
102
|
+
def to_boolean
|
|
103
|
+
t = self.to_s
|
|
104
|
+
return if t.nil?
|
|
105
|
+
t.downcase == 'true'
|
|
106
|
+
end
|
|
107
|
+
# Returns the value of the element as a ruby Time object.
|
|
108
|
+
#
|
|
109
|
+
# See +to_s+
|
|
110
|
+
def to_date
|
|
111
|
+
t = self.to_s
|
|
112
|
+
return if t.nil?
|
|
113
|
+
Time.iso8601(t)
|
|
114
|
+
end
|
|
115
|
+
# Returns the underlying native element.
|
|
116
|
+
#
|
|
117
|
+
# You shouldn't need to use this, since doing so would void portability.
|
|
118
|
+
def native_element
|
|
119
|
+
@element
|
|
120
|
+
end
|
|
121
|
+
# Returns the node name of the current element.
|
|
122
|
+
def node_name
|
|
123
|
+
raise NotImplementedError.new
|
|
124
|
+
end
|
|
125
|
+
# Queries the document with XPath, relative to the current element.
|
|
126
|
+
#
|
|
127
|
+
# +ns+ Should be a Hash of prefix => namespace
|
|
128
|
+
#
|
|
129
|
+
# Returns an Array of wrapped elements.
|
|
130
|
+
#
|
|
131
|
+
# See add_namespace
|
|
132
|
+
def xpath(expression, ns = nil)
|
|
133
|
+
raise NotImplementedError.new
|
|
134
|
+
end
|
|
135
|
+
# Returns the inner text content of this element, or the value (if it's an attr or textnode).
|
|
136
|
+
#
|
|
137
|
+
# The output is a UTF-8 encoded string, without xml-entities.
|
|
138
|
+
def to_s
|
|
139
|
+
raise NotImplementedError.new
|
|
140
|
+
end
|
|
141
|
+
# Returns the outer XML for this element.
|
|
142
|
+
def to_xml
|
|
143
|
+
raise NotImplementedError.new
|
|
144
|
+
end
|
|
145
|
+
# Alias for +xpath+
|
|
146
|
+
def /(expression)
|
|
147
|
+
self.xpath(expression)
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
# Driver for +libxml+.
|
|
152
|
+
#
|
|
153
|
+
# http://libxml.rubyforge.org/
|
|
154
|
+
class LibXMLDriver
|
|
155
|
+
include BaseDriver
|
|
156
|
+
def node_name
|
|
157
|
+
@element.name
|
|
158
|
+
end
|
|
159
|
+
def xpath(expression, ns = nil)
|
|
160
|
+
ns = {} if ns.nil?
|
|
161
|
+
ns = @namespaces.merge(ns)
|
|
162
|
+
assert_prefixes!(expression, ns)
|
|
163
|
+
@element.find(expression, ns.map{|k,v| "#{k}:#{v}" }).to_a.map{|node| LibXMLDriver.new(node, ns) }
|
|
164
|
+
end
|
|
165
|
+
def to_xml
|
|
166
|
+
@element.to_s
|
|
167
|
+
end
|
|
168
|
+
def to_s
|
|
169
|
+
if @element.kind_of? LibXML::XML::Attr
|
|
170
|
+
@element.value
|
|
171
|
+
else
|
|
172
|
+
@element.content
|
|
173
|
+
end
|
|
174
|
+
end
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
# Driver for +REXML+
|
|
178
|
+
#
|
|
179
|
+
# http://www.germane-software.com/software/rexml/
|
|
180
|
+
class REXMLDriver
|
|
181
|
+
include BaseDriver
|
|
182
|
+
def node_name
|
|
183
|
+
@element.name
|
|
184
|
+
end
|
|
185
|
+
def xpath(expression, ns = nil)
|
|
186
|
+
ns = {} if ns.nil?
|
|
187
|
+
ns = @namespaces.merge(ns)
|
|
188
|
+
assert_prefixes!(expression, ns)
|
|
189
|
+
REXML::XPath.match(@element, expression, ns).map{|node| REXMLDriver.new(node, ns) }
|
|
190
|
+
end
|
|
191
|
+
def to_xml
|
|
192
|
+
require 'rexml/formatters/pretty'
|
|
193
|
+
formatter = REXML::Formatters::Pretty.new
|
|
194
|
+
out = String.new
|
|
195
|
+
formatter.write(@element, out)
|
|
196
|
+
out
|
|
197
|
+
end
|
|
198
|
+
def to_s
|
|
199
|
+
if @element.kind_of? REXML::Attribute
|
|
200
|
+
@element.value
|
|
201
|
+
else
|
|
202
|
+
@element.text
|
|
203
|
+
end
|
|
204
|
+
end
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
# Driver for +Nokogiri+
|
|
208
|
+
#
|
|
209
|
+
# http://nokogiri.rubyforge.org/nokogiri/
|
|
210
|
+
class NokogiriDriver
|
|
211
|
+
include BaseDriver
|
|
212
|
+
def node_name
|
|
213
|
+
@element.name
|
|
214
|
+
end
|
|
215
|
+
def self.serialize_args #:nodoc:
|
|
216
|
+
@serialize_args ||= if Gem.loaded_specs['nokogiri'].version >= Gem::Version.new('1.3.0')
|
|
217
|
+
{ :encoding => 'UTF-8' }
|
|
218
|
+
else
|
|
219
|
+
'UTF-8'
|
|
220
|
+
end
|
|
221
|
+
end
|
|
222
|
+
def xpath(expression, ns = nil)
|
|
223
|
+
ns = {} if ns.nil?
|
|
224
|
+
ns = @namespaces.merge(ns)
|
|
225
|
+
assert_prefixes!(expression, ns)
|
|
226
|
+
@element.xpath(expression, ns).map{|node| NokogiriDriver.new(node, ns) }
|
|
227
|
+
end
|
|
228
|
+
def to_xml
|
|
229
|
+
@element.serialize(NokogiriDriver.serialize_args)
|
|
230
|
+
end
|
|
231
|
+
def to_s
|
|
232
|
+
if @element.kind_of?(Nokogiri::XML::Text) || @element.kind_of?(Nokogiri::XML::CDATA)
|
|
233
|
+
element = @element
|
|
234
|
+
elsif @element.kind_of?(Nokogiri::XML::Attr)
|
|
235
|
+
return @element.value
|
|
236
|
+
else
|
|
237
|
+
element = @element.children.first
|
|
238
|
+
end
|
|
239
|
+
return if element.nil?
|
|
240
|
+
# This looks messy because it is .. Nokogiri's interface is in a flux
|
|
241
|
+
if element.kind_of?(Nokogiri::XML::CDATA)
|
|
242
|
+
element.serialize(NokogiriDriver.serialize_args).gsub(/^<!\[CDATA\[/, "").gsub(/\]\]>$/, "")
|
|
243
|
+
else
|
|
244
|
+
element.serialize(NokogiriDriver.serialize_args).gsub('<', '<').gsub('>', '>').gsub('"', '"').gsub(''', "'").gsub('&', '&')
|
|
245
|
+
end
|
|
246
|
+
end
|
|
247
|
+
end
|
|
248
|
+
end
|
|
249
|
+
end
|
data/lib/handsoap.rb
CHANGED
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.
|
|
4
|
+
version: 0.3.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Troels Knak-Nielsen
|
|
@@ -9,19 +9,10 @@ autorequire:
|
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
11
|
|
|
12
|
-
date: 2009-06-
|
|
12
|
+
date: 2009-06-23 00:00:00 -07:00
|
|
13
13
|
default_executable:
|
|
14
|
-
dependencies:
|
|
15
|
-
|
|
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:
|
|
14
|
+
dependencies: []
|
|
15
|
+
|
|
25
16
|
description: Handsoap is a library for creating SOAP clients in Ruby
|
|
26
17
|
email: troelskn@gmail.com
|
|
27
18
|
executables: []
|
|
@@ -41,6 +32,7 @@ files:
|
|
|
41
32
|
- lib/handsoap/parser.rb
|
|
42
33
|
- lib/handsoap/service.rb
|
|
43
34
|
- lib/handsoap/xml_mason.rb
|
|
35
|
+
- lib/handsoap/xml_query_front.rb
|
|
44
36
|
has_rdoc: true
|
|
45
37
|
homepage: http://github.com/troelskn/handsoap
|
|
46
38
|
post_install_message:
|
|
@@ -60,8 +52,12 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
60
52
|
- !ruby/object:Gem::Version
|
|
61
53
|
version: "0"
|
|
62
54
|
version:
|
|
63
|
-
requirements:
|
|
64
|
-
|
|
55
|
+
requirements:
|
|
56
|
+
- |-
|
|
57
|
+
You need to install either "curb" or "httpclient", using one of:
|
|
58
|
+
gem install curb
|
|
59
|
+
gem install httpclient
|
|
60
|
+
- It is recommended that you install either "nokogiri" or "libxml-ruby"
|
|
65
61
|
rubyforge_project:
|
|
66
62
|
rubygems_version: 1.2.0
|
|
67
63
|
signing_key:
|