wasabi 3.1.0 → 3.2.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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +26 -9
- data/Gemfile +5 -1
- data/README.md +8 -6
- data/Rakefile +0 -2
- data/lib/wasabi/document.rb +24 -2
- data/lib/wasabi/parser.rb +162 -49
- data/lib/wasabi/version.rb +1 -1
- data/spec/fixtures/economic.wsdl +65660 -0
- data/spec/fixtures/encoded_endpoint.wsdl +52 -0
- data/spec/spec_helper.rb +11 -1
- data/spec/support/fixture.rb +2 -4
- data/spec/support/profiling.rb +18 -0
- data/spec/wasabi/document/authentication_spec.rb +1 -1
- data/spec/wasabi/document/economic_spec.rb +13 -0
- data/spec/wasabi/document/encoded_endpoint_spec.rb +11 -0
- data/spec/wasabi/document/geotrust_spec.rb +2 -2
- data/spec/wasabi/document/inherited_spec.rb +20 -0
- data/spec/wasabi/document/multiple_namespaces_spec.rb +1 -1
- data/spec/wasabi/document/namespaced_actions_spec.rb +3 -3
- data/spec/wasabi/document/no_namespace_spec.rb +3 -3
- data/spec/wasabi/document/savon295_spec.rb +1 -1
- data/spec/wasabi/parser/multiple_namespaces_spec.rb +6 -2
- data/spec/wasabi/parser/no_message_parts_spec.rb +5 -1
- data/spec/wasabi/parser/no_namespace_spec.rb +5 -2
- data/spec/wasabi/parser/symbolic_endpoint_spec.rb +5 -0
- data/wasabi.gemspec +3 -2
- metadata +28 -28
- data/lib/wasabi/xpath_helper.rb +0 -30
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: ad4024db6f2cc825dec1eb8223812bd8da45c884
|
4
|
+
data.tar.gz: af57d52095cb5daf9b530f9974b65b9d747cda11
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: b61533673c5f483625c6bd884236c7592a03cfa0c6c8f5a47b18600cbc2e903ff5f83864fd85d53a42c3430de7f5a53907222c2bf0b19fc53338eb47fe625be6
|
7
|
+
data.tar.gz: 59212805b9b770b20a37e175e950f9df1a4ff973ce52d0ca4b8b88f41b94678025684591480251900a930d4236ade904120a4eb14cbf5965e1d1cd9376f1db16
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,20 @@
|
|
1
|
+
## 3.2.0 (2013-07-26)
|
2
|
+
|
3
|
+
* Feature: [#20](https://github.com/savonrb/wasabi/issues/20) Limited support for listing an
|
4
|
+
operation's parameters. Please note that if your WSDL defines imports, this method might
|
5
|
+
not return all types.
|
6
|
+
|
7
|
+
* Improvement: [#7](https://github.com/savonrb/wasabi/issues/7) Major speed improvements.
|
8
|
+
|
9
|
+
* Improvement: [#16](https://github.com/savonrb/wasabi/issues/16) Various improvements regarding
|
10
|
+
element order and type information.
|
11
|
+
|
12
|
+
* Fix: [#25](https://github.com/savonrb/wasabi/issues/25) Fixes a problem where Wasabi escaped
|
13
|
+
an already escaped endpoint URL.
|
14
|
+
|
15
|
+
* Fix: [#15](https://github.com/savonrb/wasabi/issues/15) Fixes a bug where the operation tag
|
16
|
+
name was not correctly extracted from the WSDL document.
|
17
|
+
|
1
18
|
## 3.1.0 (2013-04-21)
|
2
19
|
|
3
20
|
* Feature: [#22](https://github.com/savonrb/wasabi/issues/22) added `Wasabi::Document#service_name`
|
@@ -15,22 +32,22 @@
|
|
15
32
|
|
16
33
|
## 2.5.1 (2012-08-22)
|
17
34
|
|
18
|
-
* Fix: [#14](https://github.com/
|
35
|
+
* Fix: [#14](https://github.com/savonrb/wasabi/issues/14) fixes an issue where
|
19
36
|
finding the correct SOAP input tag and namespace identifier fails when portTypes
|
20
37
|
are imported, since imports are currently not supported.
|
21
38
|
|
22
|
-
The bug was introduced in v2.2.0 by [583cf6](https://github.com/
|
39
|
+
The bug was introduced in v2.2.0 by [583cf6](https://github.com/savonrb/wasabi/commit/583cf658f1953411a7a7a4c22923fa0a046c8d6d)
|
23
40
|
|
24
41
|
* Refactoring: Removed `Object#blank?` core extension.
|
25
42
|
|
26
43
|
## 2.5.0 (2012-06-28)
|
27
44
|
|
28
|
-
* Fix: [#10](https://github.com/
|
45
|
+
* Fix: [#10](https://github.com/savonrb/wasabi/issues/10) fixes an issue where
|
29
46
|
Wasabi used the wrong operation name.
|
30
47
|
|
31
48
|
## 2.4.1 (2012-06-18)
|
32
49
|
|
33
|
-
* Fix: [
|
50
|
+
* Fix: [savonrb/savon#296](https://github.com/savonrb/savon/issues/296) fixes an issue where
|
34
51
|
the WSDL message element doesn't have part element.
|
35
52
|
|
36
53
|
## 2.4.0 (2012-06-08)
|
@@ -41,25 +58,25 @@
|
|
41
58
|
|
42
59
|
## 2.3.0 (2012-06-07)
|
43
60
|
|
44
|
-
* Improvement: [#3](https://github.com/
|
61
|
+
* Improvement: [#3](https://github.com/savonrb/wasabi/pull/3) adds object inheritance.
|
45
62
|
|
46
63
|
## 2.2.0 (2012-06-06)
|
47
64
|
|
48
|
-
* Improvement: [#5](https://github.com/
|
49
|
-
element or portType input. See [
|
65
|
+
* Improvement: [#5](https://github.com/savonrb/wasabi/pull/5) - Get input from message
|
66
|
+
element or portType input. See [savonrb/savon#277](https://github.com/savonrb/savon/pull/277)
|
50
67
|
to get the full picture on how this all works together, and enables you to pass a single
|
51
68
|
symbol into the `Savon::Client#request` method and get automatic namespace mapping, as well
|
52
69
|
as the proper operation name -> input message mapping.
|
53
70
|
|
54
71
|
## 2.1.1 (2012-05-18)
|
55
72
|
|
56
|
-
* Fix: [issue 7](https://github.com/
|
73
|
+
* Fix: [issue 7](https://github.com/savonrb/wasabi/issues/7) - Performance regression.
|
57
74
|
|
58
75
|
## 2.1.0 (2012-02-17)
|
59
76
|
|
60
77
|
* Improvement: The value of elementFormDefault can now be manually specified/overwritten.
|
61
78
|
|
62
|
-
* Improvement: [issue 2](https://github.com/
|
79
|
+
* Improvement: [issue 2](https://github.com/savonrb/wasabi/issues/2) - Allow symbolic endpoints
|
63
80
|
like "http://server:port".
|
64
81
|
|
65
82
|
## 2.0.0 (2011-07-07)
|
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -1,11 +1,14 @@
|
|
1
|
-
Wasabi
|
2
|
-
=====
|
1
|
+
# Wasabi
|
3
2
|
|
4
3
|
A simple WSDL parser.
|
5
4
|
|
5
|
+
[](http://travis-ci.org/savonrb/wasabi)
|
6
|
+
[](http://badge.fury.io/rb/wasabi)
|
7
|
+
[](https://codeclimate.com/github/savonrb/wasabi)
|
8
|
+
[](https://coveralls.io/r/savonrb/wasabi)
|
6
9
|
|
7
|
-
|
8
|
-
|
10
|
+
|
11
|
+
## Installation
|
9
12
|
|
10
13
|
Wasabi is available through [Rubygems](http://rubygems.org/gems/wasabi) and can be installed via:
|
11
14
|
|
@@ -14,8 +17,7 @@ $ gem install wasabi
|
|
14
17
|
```
|
15
18
|
|
16
19
|
|
17
|
-
Getting started
|
18
|
-
---------------
|
20
|
+
## Getting started
|
19
21
|
|
20
22
|
``` ruby
|
21
23
|
document = Wasabi.document File.read("some.wsdl")
|
data/Rakefile
CHANGED
data/lib/wasabi/document.rb
CHANGED
@@ -83,13 +83,29 @@ module Wasabi
|
|
83
83
|
|
84
84
|
attr_writer :service_name
|
85
85
|
|
86
|
+
# Returns a list of parameter names for a given +key+
|
87
|
+
def soap_action_parameters(key)
|
88
|
+
params = operation_input_parameters(key)
|
89
|
+
params.keys if params
|
90
|
+
end
|
91
|
+
|
92
|
+
# Returns a list of input parameters for a given +key+.
|
93
|
+
def operation_input_parameters(key)
|
94
|
+
parser.operations[key][:parameters] if operations[key]
|
95
|
+
end
|
96
|
+
|
86
97
|
def type_namespaces
|
87
98
|
@type_namespaces ||= begin
|
88
99
|
namespaces = []
|
100
|
+
|
89
101
|
parser.types.each do |type, info|
|
90
102
|
namespaces << [[type], info[:namespace]]
|
91
|
-
|
103
|
+
|
104
|
+
element_keys(info).each do |field|
|
105
|
+
namespaces << [[type, field], info[:namespace]]
|
106
|
+
end
|
92
107
|
end if document
|
108
|
+
|
93
109
|
namespaces
|
94
110
|
end
|
95
111
|
end
|
@@ -97,13 +113,16 @@ module Wasabi
|
|
97
113
|
def type_definitions
|
98
114
|
@type_definitions ||= begin
|
99
115
|
result = []
|
116
|
+
|
100
117
|
parser.types.each do |type, info|
|
101
|
-
(info
|
118
|
+
element_keys(info).each do |field|
|
102
119
|
field_type = info[field][:type]
|
103
120
|
tag, namespace = field_type.split(":").reverse
|
121
|
+
|
104
122
|
result << [[type, field], tag] if user_defined(namespace)
|
105
123
|
end
|
106
124
|
end if document
|
125
|
+
|
107
126
|
result
|
108
127
|
end
|
109
128
|
end
|
@@ -140,5 +159,8 @@ module Wasabi
|
|
140
159
|
parser
|
141
160
|
end
|
142
161
|
|
162
|
+
def element_keys(info)
|
163
|
+
info.keys - [:namespace, :order!, :base_type]
|
164
|
+
end
|
143
165
|
end
|
144
166
|
end
|
data/lib/wasabi/parser.rb
CHANGED
@@ -1,5 +1,4 @@
|
|
1
1
|
require "uri"
|
2
|
-
require "wasabi/xpath_helper"
|
3
2
|
require "wasabi/core_ext/string"
|
4
3
|
|
5
4
|
module Wasabi
|
@@ -8,7 +7,11 @@ module Wasabi
|
|
8
7
|
#
|
9
8
|
# Parses WSDL documents and remembers their important parts.
|
10
9
|
class Parser
|
11
|
-
|
10
|
+
|
11
|
+
XSD = "http://www.w3.org/2001/XMLSchema"
|
12
|
+
WSDL = "http://schemas.xmlsoap.org/wsdl/"
|
13
|
+
SOAP_1_1 = "http://schemas.xmlsoap.org/wsdl/soap/"
|
14
|
+
SOAP_1_2 = "http://schemas.xmlsoap.org/wsdl/soap12/"
|
12
15
|
|
13
16
|
def initialize(document)
|
14
17
|
self.document = document
|
@@ -51,16 +54,20 @@ module Wasabi
|
|
51
54
|
parse_namespaces
|
52
55
|
parse_endpoint
|
53
56
|
parse_service_name
|
57
|
+
parse_messages
|
58
|
+
parse_port_types
|
59
|
+
parse_port_type_operations
|
54
60
|
parse_operations
|
61
|
+
parse_operations_parameters
|
55
62
|
parse_types
|
56
63
|
parse_deferred_types
|
57
64
|
end
|
58
65
|
|
59
66
|
def parse_namespaces
|
60
|
-
element_form_default =
|
67
|
+
element_form_default = schemas.first && schemas.first['elementFormDefault']
|
61
68
|
@element_form_default = element_form_default.to_s.to_sym if element_form_default
|
62
69
|
|
63
|
-
namespace =
|
70
|
+
namespace = document.root['targetNamespace']
|
64
71
|
@namespace = namespace.to_s if namespace
|
65
72
|
|
66
73
|
@namespaces = @document.namespaces.inject({}) do |memo, (key, value)|
|
@@ -70,38 +77,80 @@ module Wasabi
|
|
70
77
|
end
|
71
78
|
|
72
79
|
def parse_endpoint
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
begin
|
77
|
-
@endpoint = URI(URI.escape(endpoint.to_s)) if endpoint
|
78
|
-
rescue URI::InvalidURIError
|
79
|
-
@endpoint = nil
|
80
|
+
if service_node = service
|
81
|
+
endpoint = service_node.at_xpath(".//soap11:address/@location", 'soap11' => SOAP_1_1)
|
82
|
+
endpoint ||= service_node.at_xpath(service_node, ".//soap12:address/@location", 'soap12' => SOAP_1_2)
|
80
83
|
end
|
84
|
+
|
85
|
+
@endpoint = parse_url(endpoint) if endpoint
|
86
|
+
end
|
87
|
+
|
88
|
+
def parse_url(url)
|
89
|
+
unescaped_url = URI.unescape(url.to_s)
|
90
|
+
escaped_url = URI.escape(unescaped_url)
|
91
|
+
URI.parse(escaped_url)
|
92
|
+
rescue URI::InvalidURIError
|
81
93
|
end
|
82
94
|
|
83
95
|
def parse_service_name
|
84
|
-
service_name =
|
96
|
+
service_name = document.root['name']
|
85
97
|
@service_name = service_name.to_s if service_name
|
86
98
|
end
|
87
99
|
|
100
|
+
def parse_messages
|
101
|
+
messages = document.root.element_children.select { |node| node.name == 'message' }
|
102
|
+
@messages = Hash[messages.map { |node| [node['name'], node] }]
|
103
|
+
end
|
104
|
+
|
105
|
+
def parse_port_types
|
106
|
+
port_types = document.root.element_children.select { |node| node.name == 'portType' }
|
107
|
+
@port_types = Hash[port_types.map { |node| [node['name'], node] }]
|
108
|
+
end
|
109
|
+
|
110
|
+
def parse_port_type_operations
|
111
|
+
@port_type_operations = {}
|
112
|
+
|
113
|
+
@port_types.each do |port_type_name, port_type|
|
114
|
+
operations = port_type.element_children.select { |node| node.name == 'operation' }
|
115
|
+
@port_type_operations[port_type_name] = Hash[operations.map { |node| [node['name'], node] }]
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
def parse_operations_parameters
|
120
|
+
root_elements = document.xpath("wsdl:definitions/wsdl:types/*[local-name()='schema']/*[local-name()='element']", 'wsdl' => WSDL).each do |element|
|
121
|
+
name = element.attribute("name").to_s.snakecase.to_sym
|
122
|
+
|
123
|
+
if operation = @operations[name]
|
124
|
+
element.xpath("*[local-name() ='complexType']/*[local-name() ='sequence']/*[local-name() ='element']").each do |child_element|
|
125
|
+
attr_name = child_element.attribute("name").to_s
|
126
|
+
attr_type = (attr_type = child_element.attribute("type").to_s.split(":")).size > 1 ? attr_type[1] : attr_type[0]
|
127
|
+
|
128
|
+
operation[:parameters] ||= {}
|
129
|
+
operation[:parameters][attr_name.to_sym] = { :name => attr_name, :type => attr_type }
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
88
135
|
def parse_operations
|
89
|
-
operations = xpath("wsdl:definitions/wsdl:binding/wsdl:operation")
|
136
|
+
operations = document.xpath("wsdl:definitions/wsdl:binding/wsdl:operation", 'wsdl' => WSDL)
|
90
137
|
operations.each do |operation|
|
91
138
|
name = operation.attribute("name").to_s
|
92
139
|
|
93
|
-
|
94
|
-
|
140
|
+
# TODO: check for soap namespace?
|
141
|
+
soap_operation = operation.element_children.find { |node| node.name == 'operation' }
|
142
|
+
soap_action = soap_operation['soapAction'] if soap_operation
|
95
143
|
|
96
144
|
if soap_action
|
97
145
|
soap_action = soap_action.to_s
|
98
146
|
action = soap_action && !soap_action.empty? ? soap_action : name
|
99
147
|
|
100
148
|
# There should be a matching portType for each binding, so we will lookup the input from there.
|
149
|
+
namespace_id, output = output_for(operation)
|
101
150
|
namespace_id, input = input_for(operation)
|
102
151
|
|
103
152
|
# Store namespace identifier so this operation can be mapped to the proper namespace.
|
104
|
-
@operations[name.snakecase.to_sym] = { :action => action, :input => input, :namespace_identifier => namespace_id
|
153
|
+
@operations[name.snakecase.to_sym] = { :action => action, :input => input, :output => output, :namespace_identifier => namespace_id}
|
105
154
|
elsif !@operations[name.snakecase.to_sym]
|
106
155
|
@operations[name.snakecase.to_sym] = { :action => name, :input => name }
|
107
156
|
end
|
@@ -109,38 +158,65 @@ module Wasabi
|
|
109
158
|
end
|
110
159
|
|
111
160
|
def parse_types
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
161
|
+
schemas.each do |schema|
|
162
|
+
schema_namespace = schema['targetNamespace']
|
163
|
+
|
164
|
+
schema.element_children.each do |node|
|
165
|
+
namespace = schema_namespace || @namespace
|
166
|
+
|
167
|
+
case node.name
|
168
|
+
when 'element'
|
169
|
+
complex_type = node.at_xpath('./xs:complexType', 'xs' => XSD)
|
170
|
+
process_type namespace, complex_type, node['name'].to_s if complex_type
|
171
|
+
when 'complexType'
|
172
|
+
process_type namespace, node, node['name'].to_s
|
173
|
+
end
|
174
|
+
end
|
175
|
+
end
|
117
176
|
end
|
118
177
|
|
119
|
-
def process_type(type, name)
|
120
|
-
|
121
|
-
@types[name]
|
178
|
+
def process_type(namespace, type, name)
|
179
|
+
@types[name] ||= { :namespace => namespace }
|
180
|
+
@types[name][:order!] = []
|
122
181
|
|
123
|
-
xpath(
|
124
|
-
|
182
|
+
type.xpath("./xs:sequence/xs:element", 'xs' => XSD).each do |inner|
|
183
|
+
element_name = inner.attribute("name").to_s
|
184
|
+
@types[name][element_name] = { :type => inner.attribute("type").to_s }
|
125
185
|
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
186
|
+
[ :nillable, :minOccurs, :maxOccurs ].each do |attr|
|
187
|
+
if v = inner.attribute(attr.to_s)
|
188
|
+
@types[name][element_name][attr] = v.to_s
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
@types[name][:order!] << element_name
|
193
|
+
end
|
194
|
+
|
195
|
+
type.xpath("./xs:complexContent/xs:extension/xs:sequence/xs:element", 'xs' => XSD).each do |inner_element|
|
196
|
+
element_name = inner_element.attribute('name').to_s
|
197
|
+
@types[name][element_name] = { :type => inner_element.attribute('type').to_s }
|
198
|
+
|
199
|
+
@types[name][:order!] << element_name
|
132
200
|
end
|
133
201
|
|
134
|
-
type.xpath('./xs:complexContent/xs:extension[@base]',
|
135
|
-
"xs" => "http://www.w3.org/2001/XMLSchema"
|
136
|
-
).each do |inherits|
|
202
|
+
type.xpath('./xs:complexContent/xs:extension[@base]', 'xs' => XSD).each do |inherits|
|
137
203
|
base = inherits.attribute('base').value.match(/\w+$/).to_s
|
204
|
+
|
138
205
|
if @types[base]
|
139
|
-
|
206
|
+
# Reverse merge because we don't want subclass attributes to be overriden by base class
|
207
|
+
@types[name] = types[base].merge(types[name])
|
208
|
+
@types[name][:order!] = @types[base][:order!] | @types[name][:order!]
|
209
|
+
@types[name][:base_type] = base
|
140
210
|
else
|
141
|
-
|
142
|
-
|
143
|
-
|
211
|
+
p = Proc.new do
|
212
|
+
if @types[base]
|
213
|
+
# Reverse merge because we don't want subclass attributes to be overriden by base class
|
214
|
+
@types[name] = @types[base].merge(@types[name])
|
215
|
+
@types[name][:order!] = @types[base][:order!] | @types[name][:order!]
|
216
|
+
@types[name][:base_type] = base
|
217
|
+
end
|
218
|
+
end
|
219
|
+
deferred_types << p
|
144
220
|
end
|
145
221
|
end
|
146
222
|
end
|
@@ -149,28 +225,39 @@ module Wasabi
|
|
149
225
|
deferred_types.each(&:call)
|
150
226
|
end
|
151
227
|
|
152
|
-
def
|
153
|
-
|
154
|
-
schema_namespace ? schema_namespace.to_s : @namespace
|
228
|
+
def input_for(operation)
|
229
|
+
input_output_for(operation, "input")
|
155
230
|
end
|
156
231
|
|
157
|
-
def
|
232
|
+
def output_for(operation)
|
233
|
+
input_output_for(operation, "output")
|
234
|
+
end
|
235
|
+
|
236
|
+
def input_output_for(operation, input_output)
|
158
237
|
operation_name = operation["name"]
|
159
238
|
|
160
239
|
# Look up the input by walking up to portType, then up to the message.
|
161
240
|
|
162
|
-
binding_type =
|
163
|
-
|
241
|
+
binding_type = operation.parent['type'].to_s.split(':').last
|
242
|
+
if @port_type_operations[binding_type]
|
243
|
+
port_type_operation = @port_type_operations[binding_type][operation_name]
|
244
|
+
end
|
245
|
+
|
246
|
+
port_type_input_output = port_type_operation &&
|
247
|
+
port_type_operation.element_children.find { |node| node.name == input_output }
|
164
248
|
|
165
249
|
# TODO: Stupid fix for missing support for imports.
|
166
250
|
# Sometimes portTypes are actually included in a separate WSDL.
|
167
|
-
if
|
168
|
-
port_message_ns_id, port_message_type =
|
251
|
+
if port_type_input_output
|
252
|
+
port_message_ns_id, port_message_type = port_type_input_output.attribute("message").to_s.split(':')
|
169
253
|
|
170
254
|
message_ns_id, message_type = nil
|
171
255
|
|
172
256
|
# TODO: Support multiple 'part' elements in the message.
|
173
|
-
|
257
|
+
message = @messages[port_message_type]
|
258
|
+
port_message_part = message.element_children.find { |node| node.name == 'part' }
|
259
|
+
|
260
|
+
if port_message_part
|
174
261
|
if (port_message_part_element = port_message_part.attribute("element"))
|
175
262
|
message_ns_id, message_type = port_message_part_element.to_s.split(':')
|
176
263
|
end
|
@@ -180,12 +267,38 @@ module Wasabi
|
|
180
267
|
if message_type
|
181
268
|
[message_ns_id, message_type]
|
182
269
|
else
|
183
|
-
[port_message_ns_id,
|
270
|
+
[port_message_ns_id, port_message_type]
|
184
271
|
end
|
185
272
|
else
|
186
273
|
[nil, operation_name]
|
187
274
|
end
|
188
275
|
end
|
189
276
|
|
277
|
+
def schemas
|
278
|
+
types = section('types').first
|
279
|
+
types ? types.element_children : []
|
280
|
+
end
|
281
|
+
|
282
|
+
def service
|
283
|
+
services = section('service')
|
284
|
+
services.first if services # service nodes could be imported?
|
285
|
+
end
|
286
|
+
|
287
|
+
def section(section_name)
|
288
|
+
sections[section_name] || []
|
289
|
+
end
|
290
|
+
|
291
|
+
def sections
|
292
|
+
return @sections if @sections
|
293
|
+
|
294
|
+
sections = {}
|
295
|
+
document.root.element_children.each do |node|
|
296
|
+
(sections[node.name] ||= []) << node
|
297
|
+
end
|
298
|
+
|
299
|
+
@sections = sections
|
300
|
+
end
|
301
|
+
|
190
302
|
end
|
303
|
+
|
191
304
|
end
|