xmlable 0.0.0.alpha1

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.
Files changed (51) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE.txt +21 -0
  3. data/README.md +245 -0
  4. data/lib/xmlable/attribute.rb +16 -0
  5. data/lib/xmlable/builder.rb +189 -0
  6. data/lib/xmlable/document.rb +16 -0
  7. data/lib/xmlable/element.rb +19 -0
  8. data/lib/xmlable/exports/base.rb +78 -0
  9. data/lib/xmlable/exports/json_exporter.rb +208 -0
  10. data/lib/xmlable/exports/xml_exporter.rb +179 -0
  11. data/lib/xmlable/exports.rb +11 -0
  12. data/lib/xmlable/handlers/attribute.rb +41 -0
  13. data/lib/xmlable/handlers/attribute_none.rb +10 -0
  14. data/lib/xmlable/handlers/base.rb +103 -0
  15. data/lib/xmlable/handlers/document.rb +33 -0
  16. data/lib/xmlable/handlers/element.rb +89 -0
  17. data/lib/xmlable/handlers/element_none.rb +10 -0
  18. data/lib/xmlable/handlers/elements.rb +15 -0
  19. data/lib/xmlable/handlers/mixins/described.rb +19 -0
  20. data/lib/xmlable/handlers/mixins/namespace.rb +40 -0
  21. data/lib/xmlable/handlers/mixins/tag.rb +24 -0
  22. data/lib/xmlable/handlers/namespace.rb +26 -0
  23. data/lib/xmlable/handlers/root.rb +9 -0
  24. data/lib/xmlable/handlers/root_none.rb +10 -0
  25. data/lib/xmlable/handlers/storage.rb +104 -0
  26. data/lib/xmlable/handlers.rb +23 -0
  27. data/lib/xmlable/mixins/attributes_storage.rb +195 -0
  28. data/lib/xmlable/mixins/bare_value.rb +41 -0
  29. data/lib/xmlable/mixins/castable.rb +93 -0
  30. data/lib/xmlable/mixins/container.rb +71 -0
  31. data/lib/xmlable/mixins/content_storage.rb +138 -0
  32. data/lib/xmlable/mixins/document_storage.rb +136 -0
  33. data/lib/xmlable/mixins/elements_storage.rb +219 -0
  34. data/lib/xmlable/mixins/export.rb +45 -0
  35. data/lib/xmlable/mixins/instantiable.rb +47 -0
  36. data/lib/xmlable/mixins/namespace_definitions_storage.rb +105 -0
  37. data/lib/xmlable/mixins/object.rb +95 -0
  38. data/lib/xmlable/mixins/options_storage.rb +39 -0
  39. data/lib/xmlable/mixins/root_storage.rb +162 -0
  40. data/lib/xmlable/mixins/standalone_attribute.rb +34 -0
  41. data/lib/xmlable/mixins/standalone_element.rb +47 -0
  42. data/lib/xmlable/mixins/value_storage.rb +84 -0
  43. data/lib/xmlable/mixins/wrapper.rb +37 -0
  44. data/lib/xmlable/mixins.rb +25 -0
  45. data/lib/xmlable/options/nokogiri_export.rb +19 -0
  46. data/lib/xmlable/options/storage.rb +97 -0
  47. data/lib/xmlable/options.rb +9 -0
  48. data/lib/xmlable/types.rb +31 -0
  49. data/lib/xmlable/version.rb +4 -0
  50. data/lib/xmlable.rb +49 -0
  51. metadata +149 -0
@@ -0,0 +1,208 @@
1
+ module XMLable
2
+ module Exports
3
+ #
4
+ # JSONExporter class exports object into JSON format
5
+ #
6
+ class JSONExporter < Base
7
+ #
8
+ # Export into JSON format
9
+ #
10
+ # @return [Hash]
11
+ #
12
+ def export
13
+ opts = node_nested_options(@element.__node)
14
+ export_object(@element, opts)
15
+ end
16
+
17
+ #
18
+ # Is object empty?
19
+ #
20
+ # @param [XMLable::Mixins::Object] el
21
+ #
22
+ # @return [Boolean]
23
+ #
24
+ def empty?(el)
25
+ el.__empty?
26
+ end
27
+
28
+ #
29
+ # Is object described by user?
30
+ #
31
+ # @param [XMLable::Handlers::Base, XMLable::Mixins::Object]
32
+ #
33
+ # @return [Boolean]
34
+ #
35
+ #
36
+ def described?(obj)
37
+ handler = obj.is_a?(XMLable::Handlers::Base) ? obj : obj.__handler
38
+ handler.described?
39
+ end
40
+
41
+ #
42
+ # Get object's key
43
+ #
44
+ # @param [XMLable::Handlers::Base, XMLable::Mixins::Object]
45
+ #
46
+ # @return [String]
47
+ #
48
+ def key(obj, opts)
49
+ handler = obj.is_a?(XMLable::Handlers::Base) ? obj : obj.__handler
50
+ handler.method_name
51
+ end
52
+
53
+ #
54
+ # Get object's handlers
55
+ #
56
+ # @param [XMLable::Mixins::Object] el
57
+ # @param [XMLable::Options::Storage] opts
58
+ #
59
+ # @return [Array<XMLable::Handler::Base>]
60
+ #
61
+ def object_handlers(el, opts)
62
+ handlers = []
63
+ if el.respond_to?(:__attributes_handlers)
64
+ el.__attributes_handlers.storage.each do |h|
65
+ next if opts.drop_undescribed_attributes? && !described?(h)
66
+ handlers << h
67
+ end
68
+ end
69
+ if el.respond_to?(:__elements_handlers)
70
+ el.__elements_handlers.storage.each do |h|
71
+ next if opts.drop_undescribed_elements? && !described?(h)
72
+ handlers << h
73
+ end
74
+ end
75
+ handlers
76
+ end
77
+
78
+ #
79
+ # Export group of elements
80
+ #
81
+ # @param [XMLable::Mixins::Container<XMLable::Mixins::Object>] els
82
+ # @param [XMLable::Options::Storage] opts
83
+ #
84
+ # @return [Array<Hash>]
85
+ #
86
+ def export_elements(els, opts)
87
+ els.each_with_object([]) do |e, arr|
88
+ next if opts.drop_empty_elements? && empty?(e)
89
+ arr << export_object(e, opts)
90
+ end
91
+ end
92
+
93
+ #
94
+ # Export attribute object's value
95
+ #
96
+ # @param [XMLable::Mixins::Object] el
97
+ # @param [XMLable::Options::Storage] opts
98
+ #
99
+ # @return [Object]
100
+ #
101
+ def export_value(el, opts)
102
+ el.__export_to_json(el.__object)
103
+ end
104
+
105
+ #
106
+ # Export element object's content
107
+ #
108
+ # @param [XMLable::Mixins::Object] el
109
+ # @param [XMLable::Options::Storage] opts
110
+ #
111
+ # @return [Object]
112
+ #
113
+ def export_content(el, opts)
114
+ el.__export_to_json(el.__object)
115
+ end
116
+
117
+ #
118
+ # Get object key
119
+ #
120
+ # @param [XMLable::Handlers::Base, XMLable::Mixins::Object]
121
+ #
122
+ def key(obj, opts)
123
+ handler = obj.is_a?(XMLable::Handlers::Base) ? obj : obj.__handler
124
+ handler.method_name
125
+ end
126
+
127
+ #
128
+ # Export element object
129
+ #
130
+ # @param [XMLable::Mixins::Object] el
131
+ # @param [XMLable::Options::Storage] opts
132
+ #
133
+ # @return [Object]
134
+ #
135
+ def export_element(el, opts)
136
+ opts = node_merged_opts(el.__node, opts)
137
+ handlers = object_handlers(el, opts)
138
+ content = export_content(el, opts)
139
+ return content if handlers.size == 0
140
+
141
+ ret = export_element_children(el, opts)
142
+
143
+ if !content.to_s.empty? || !opts.drop_empty_elements?
144
+ content_method = el.__content_method
145
+ if (content_method || !opts.drop_undescribed_elements?) && content_method != false
146
+ ret["#{content_method || '__content'}"] = content unless content.to_s.empty?
147
+ end
148
+ end
149
+
150
+ ret
151
+ end
152
+
153
+ #
154
+ # Export element's nested objects
155
+ #
156
+ # @param [XMLable::Mixins::Object] el
157
+ # @param [XMLable::Options::Storage] opts
158
+ #
159
+ # @return [Hash]
160
+ #
161
+ def export_element_children(el, opts)
162
+ object_handlers(el, opts).each_with_object({}) do |h, memo|
163
+ obj = el[h.method_name]
164
+ if h.is_a?(Handlers::Element)
165
+ next if opts.drop_empty_elements? && empty?(obj)
166
+ elsif h.is_a?(Handlers::Attribute)
167
+ next if opts.drop_empty_attributes? && empty?(obj)
168
+ end
169
+ memo[key(h, opts)] = export_object(obj, opts)
170
+ end
171
+ end
172
+
173
+ #
174
+ # Export root object
175
+ #
176
+ # @param [XMLable::Mixins::Object] el
177
+ # @param [XMLable::Options::Storage] opts
178
+ #
179
+ # @return [Hash{String => Object}]
180
+ #
181
+ def export_root(el, opts)
182
+ tag = described?(el.root) ? key(el.root, opts) : el.__node.root.name
183
+ if !opts.drop_empty_elements? || !empty?(el.root)
184
+ value = export_object(el.root, opts)
185
+ end
186
+ { tag => value }
187
+ end
188
+
189
+ #
190
+ # Export object
191
+ #
192
+ # @param [XMLable::Mixins::Object, XMLable::Mixins::Container] obj
193
+ # @param [XMLable::Options::Storage] opts
194
+ #
195
+ # @return [Object]
196
+ #
197
+ def export_object(obj, opts)
198
+ case obj
199
+ when Mixins::Container then export_elements(obj, opts)
200
+ when Mixins::ElementsStorage then export_element(obj, opts)
201
+ when Mixins::RootStorage then export_root(obj, opts)
202
+ when Mixins::ValueStorage then export_value(obj, opts)
203
+ else fail("Don't know how to export #{obj.class.ancestors.inspect}.")
204
+ end
205
+ end
206
+ end
207
+ end
208
+ end
@@ -0,0 +1,179 @@
1
+ module XMLable
2
+ module Exports
3
+ #
4
+ # XMLExporter class exports the data into XML format
5
+ #
6
+ class XMLExporter < Base
7
+ #
8
+ # @see XMLable::Exports::Base#initialize
9
+ #
10
+ def initialize(*)
11
+ super
12
+ @node = @element.__node
13
+ @document = @node.document? ? @node : @node.document
14
+ end
15
+
16
+ #
17
+ # Export the data into XML format
18
+ #
19
+ # @return [String]
20
+ #
21
+ def export
22
+ opts = node_nested_options(@element.__node)
23
+ builder.tap { |b| export_node(@element.__node, opts.merge(xml: b)) }
24
+ end
25
+
26
+ private
27
+
28
+ #
29
+ # XML builder
30
+ #
31
+ # @return [Nokogiri::XML:::Builder]
32
+ #
33
+ def builder
34
+ opts = {}
35
+ opts[:encoding] = @document.encoding if @document.encoding
36
+ ::Nokogiri::XML::Builder.new(opts)
37
+ end
38
+
39
+ #
40
+ # Get element's attributes
41
+ #
42
+ # @param [Nokogiri::XML::Element] node XML element
43
+ # @param [XMLable::Options::Storage] opts
44
+ #
45
+ # @return [Hash{String => String}]
46
+ #
47
+ def node_attributes(node, opts)
48
+ return {} unless node.respond_to?(:attributes)
49
+ attrs = node.attributes.values.select do |att|
50
+ next if opts.drop_undescribed_attributes? && !described?(att)
51
+ next if opts.drop_empty_attributes? && empty?(att)
52
+ true
53
+ end
54
+ attrs.each_with_object({}) do |a, hash|
55
+ name = [a.name]
56
+ name.unshift(a.namespace.prefix) if a.namespace && a.namespace.prefix
57
+ hash[name.join(':')] = a.value
58
+ end
59
+ end
60
+
61
+ #
62
+ # Get node's namespaces
63
+ #
64
+ # @param [Nokogiri::XML::Node] node
65
+ # @param [XMLable::Options::Storage] opts
66
+ #
67
+ # @return [Hash{String => String}]
68
+ #
69
+ def node_namespaces(node, opts)
70
+ return {} unless node.respond_to?(:namespace_definitions)
71
+
72
+ node.namespace_definitions.each_with_object({}) do |n, obj|
73
+ name = ["xmlns", n.prefix].compact.join(":")
74
+ obj[name] = n.href
75
+ end
76
+ end
77
+
78
+ #
79
+ # Export XML document
80
+ #
81
+ # @param [Nokogiri::XML::Document] doc
82
+ # @param [XMLable::Options::Storage] opts
83
+ #
84
+ def export_document(doc, opts)
85
+ export_node(doc.root, opts)
86
+ end
87
+
88
+ #
89
+ # Export XML element
90
+ #
91
+ # @param [Nokogiri::XML::Element] node
92
+ # @param [XMLable::Options::Storage] opts
93
+ #
94
+ def export_element(node, opts)
95
+ opts = node_merged_opts(node, opts)
96
+ ns = node.namespace
97
+ if ns && !node_ns_definition(node, ns.prefix)
98
+ opts[:xml] = opts[:xml][ns.prefix] if ns.prefix
99
+ end
100
+ opts[:xml].send("#{node.name}_", element_args(node, opts)) do |xml|
101
+ if ns && node_ns_definition(node, ns)
102
+ xml.parent.namespace = node_ns_definition(xml.parent, ns.prefix)
103
+ end
104
+ if !opts.drop_empty_elements? || !empty?(node)
105
+ export_node_children(node, opts.merge(xml: xml))
106
+ end
107
+ end
108
+ end
109
+
110
+ #
111
+ # Get XML element's attributes and namespace definitions
112
+ #
113
+ # @param [Nokogiri::XML::Element] node
114
+ # @param [XMLable::Options::Storage] opts
115
+ #
116
+ # @return [Hash{String => String}]
117
+ #
118
+ def element_args(node, opts)
119
+ node_namespaces(node, opts).merge(node_attributes(node, opts))
120
+ end
121
+
122
+ #
123
+ # Get element's namespace definition
124
+ #
125
+ # @param [Nokogiri::XML::Element] node
126
+ # @param [Nokogiri::XML::Namespace, String] ns
127
+ #
128
+ # @return [Nokogiri::XML::Namespace, nil] returns namespace if it's found,
129
+ # otherwise +nil+
130
+ #
131
+ def node_ns_definition(node, ns)
132
+ prefix = ns.is_a?(Nokogiri::XML::Namespace) ? ns.prefix : ns
133
+ node.namespace_definitions.find { |n| n.prefix == prefix }
134
+ end
135
+
136
+ #
137
+ # Export nodes' children
138
+ #
139
+ # @param [Nokogiri::XML::Element] node
140
+ # @param [XMLable::Options::Storage] opts
141
+ #
142
+ def export_node_children(node, opts)
143
+ node.children.each do |child|
144
+ next if child.is_a?(Nokogiri::XML::Attr)
145
+ if child.is_a?(Nokogiri::XML::Element)
146
+ next if opts.drop_empty_elements? && empty?(child)
147
+ next if opts.drop_undescribed_elements? && !described?(child)
148
+ end
149
+ export_node(child, opts)
150
+ end
151
+ end
152
+
153
+ #
154
+ # Export XML text node
155
+ #
156
+ # @param [Nokogiri::XML::Text] node
157
+ # @param [XMLable::Options::Storage] opts
158
+ #
159
+ def export_text(node, opts)
160
+ opts[:xml].text(node.text)
161
+ end
162
+
163
+ #
164
+ # Export XML node
165
+ #
166
+ # @param [Nokogiri::XML::Node] node
167
+ # @param [XMLable::Options::Storage] opts
168
+ #
169
+ def export_node(node, opts)
170
+ case node
171
+ when Nokogiri::XML::Document then export_document(node, opts)
172
+ when Nokogiri::XML::Element then export_element(node, opts)
173
+ when Nokogiri::XML::Text then export_text(node, opts)
174
+ else fail "Don't know how to export node: #{node}"
175
+ end
176
+ end
177
+ end
178
+ end
179
+ end
@@ -0,0 +1,11 @@
1
+ module XMLable
2
+ #
3
+ # Exports module contains the export classes
4
+ #
5
+ module Exports
6
+ end
7
+ end
8
+
9
+ require_relative 'exports/base'
10
+ require_relative 'exports/xml_exporter'
11
+ require_relative 'exports/json_exporter'
@@ -0,0 +1,41 @@
1
+ module XMLable
2
+ module Handlers
3
+ #
4
+ # Attribute handles XML attributes objects
5
+ #
6
+ class Attribute < Base
7
+ include Mixins::Namespace
8
+ include Mixins::Described
9
+ include Mixins::Tag
10
+
11
+ #
12
+ # @see XMLable::Handler::Base#inject_class
13
+ #
14
+ def inject_wraped(klass)
15
+ klass.class_eval do
16
+ include XMLable::Mixins::ValueStorage
17
+ include XMLable::Mixins::Instantiable
18
+ end
19
+ klass
20
+ end
21
+
22
+ #
23
+ # @see XMLable::Handler::Base#proxy
24
+ #
25
+ def proxy
26
+ @proxy ||= type_class.tap { |a| a.class_eval(&@block) if block_settings? }
27
+ end
28
+
29
+ #
30
+ # Create attribute object from the XML attribute
31
+ #
32
+ # @param [Nokogiri::XML::Attr] attribute
33
+ #
34
+ # @return [XMLable::Mixins::Object]
35
+ #
36
+ def from_xml_attribute(attribute)
37
+ Builder.build_attribute(attribute, self)
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,10 @@
1
+ module XMLable
2
+ module Handlers
3
+ #
4
+ # AttributeNone represents null object pattern for the
5
+ # XMLable::Handlers::Attribute class
6
+ #
7
+ class AttributeNone < Attribute
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,103 @@
1
+ module XMLable
2
+ module Handlers
3
+ #
4
+ # Base contains base handlers logic
5
+ #
6
+ class Base
7
+ # @return [Object]
8
+ attr_reader :type
9
+
10
+ # @return [#call] returns block with additional settings
11
+ attr_reader :block
12
+
13
+ #
14
+ # @param [String, Symbol] name element/attribute name
15
+ # @param [Hash] opts adtional handler options
16
+ # @option opts [Object] :tag element/attribute tag name
17
+ # @option opts [Object] :type element/attribute class object
18
+ # @option opts [Object] :container elements container
19
+ #
20
+ def initialize(name, opts = {}, &block)
21
+ @name = name.to_s
22
+ @type = opts.delete(:type) || String
23
+ @block = block
24
+ end
25
+
26
+ #
27
+ # Type class for the handler element
28
+ #
29
+ # @return [#new] returns class to store elements and attributes
30
+ #
31
+ def type_class
32
+ klass = Builder.proxy_for(@type)
33
+ wrapped_type? ? inject_wraped(klass) : klass
34
+ end
35
+
36
+ #
37
+ # Proxy object which holds element data
38
+ #
39
+ # @return [#new]
40
+ #
41
+ def proxy
42
+ raise NotImplementedError
43
+ end
44
+
45
+ #
46
+ # Inject type class with addtional logic
47
+ #
48
+ # @param [Class] klass
49
+ #
50
+ # @return [Class]
51
+ #
52
+ def inject_wraped(klass)
53
+ end
54
+
55
+ def wrapped_type?
56
+ Builder.wrapped_type?(type)
57
+ end
58
+
59
+ def options?
60
+ !options.nil?
61
+ end
62
+
63
+ def options
64
+ proxy.__options
65
+ end
66
+
67
+ #
68
+ # Handler's element method name
69
+ #
70
+ # @return [String]
71
+ #
72
+ def method_name
73
+ @name
74
+ end
75
+
76
+ #
77
+ # Does the handler have additional settins
78
+ #
79
+ # @return [Boolean]
80
+ #
81
+ def block_settings?
82
+ @block != nil
83
+ end
84
+
85
+ #
86
+ # Factory to build a handler
87
+ #
88
+ # @return [XMLable::Handlers::Base]
89
+ #
90
+ def self.build(*args, &block)
91
+ name = args.shift.to_s
92
+ opts = args.last.is_a?(Hash) ? args.pop : {}
93
+ opts[:type] = args.shift if args.size > 0
94
+ opts[:container] = args.shift if args.size > 0
95
+
96
+ # standalone
97
+ opts[:tag] = opts[:type].__tag if opts[:type].respond_to?(:__tag)
98
+
99
+ new(name, opts, &block)
100
+ end
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,33 @@
1
+ module XMLable
2
+ module Handlers
3
+ #
4
+ # Document class handles XML document
5
+ #
6
+ class Document < Base
7
+ #
8
+ # @param [Class] type
9
+ #
10
+ def initialize(type)
11
+ @type = type
12
+ end
13
+
14
+ #
15
+ # @see XMLable::Handler::Base#proxy
16
+ #
17
+ def proxy
18
+ @type
19
+ end
20
+
21
+ #
22
+ # Create document object from the XML document
23
+ #
24
+ # @param [Nokogiri::XML::Document] doc
25
+ #
26
+ # @return [XMLable::Mixins::Object]
27
+ #
28
+ def from_xml_document(doc)
29
+ Builder.build_document(doc, self)
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,89 @@
1
+ module XMLable
2
+ module Handlers
3
+ class Element < Base
4
+ include Mixins::Namespace
5
+ include Mixins::Described
6
+ include Mixins::Tag
7
+
8
+ #
9
+ # @see XMLable::Handler::Base#initialize
10
+ #
11
+ def initialize(name, opts = {}, &block)
12
+ @container_type = opts.delete(:container) || Array
13
+ super(name, opts, &block)
14
+ end
15
+
16
+ #
17
+ # Proxy class for elements objects
18
+ #
19
+ # @return [Class]
20
+ #
21
+ def container_proxy
22
+ @container_proxy ||= Builder.container_proxy_for(@container_type)
23
+ end
24
+
25
+ #
26
+ # Create elements container for XML element
27
+ #
28
+ # @parent [Nokogiri::XML::Element]
29
+ #
30
+ # @return [#each]
31
+ #
32
+ def container_for_xml_element(parent)
33
+ container_proxy.new.tap do |c|
34
+ c.__set_parent_node(parent)
35
+ c.__set_handler(self)
36
+ end
37
+ end
38
+
39
+ #
40
+ # @see XMLable::Handler::Base#inject_wraped
41
+ #
42
+ def inject_wraped(klass)
43
+ klass.class_eval do
44
+ include XMLable::Mixins::ContentStorage
45
+ include XMLable::Mixins::AttributesStorage
46
+ include XMLable::Mixins::ElementsStorage
47
+ include XMLable::Mixins::NamespaceDefinitionsStorage
48
+ include XMLable::Mixins::BareValue
49
+ include XMLable::Mixins::Instantiable
50
+ end
51
+ klass
52
+ end
53
+
54
+ #
55
+ # @see XMLable::Handler::Base#proxy
56
+ #
57
+ def proxy
58
+ @proxy ||= type_class.tap do |p|
59
+ p.__default_namespace = namespace_prefix
60
+ p.class_eval(&@block) if block_settings?
61
+ end
62
+ end
63
+
64
+ #def dynamic?
65
+ #@block != nil
66
+ #end
67
+
68
+ #
69
+ # Is this handler for multiple elements objects or not?
70
+ #
71
+ # @return [Boolean]
72
+ #
73
+ def single?
74
+ true
75
+ end
76
+
77
+ #
78
+ # Create element object from the XML element
79
+ #
80
+ # @param [Nokogiri::XML::Element] element
81
+ #
82
+ # @return [XMLable::Mixins::Object]
83
+ #
84
+ def from_xml_element(element)
85
+ Builder.build_element(element, self)
86
+ end
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,10 @@
1
+ module XMLable
2
+ module Handlers
3
+ #
4
+ # ElementNone represents null object pattern for the
5
+ # XMLable::Handlers::Element class
6
+ #
7
+ class ElementNone < Elements
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,15 @@
1
+ module XMLable
2
+ module Handlers
3
+ #
4
+ # Elements handles group of XML elements
5
+ #
6
+ class Elements < Element
7
+ #
8
+ # @see XMLable::Handlers::Element#single?
9
+ #
10
+ def single?
11
+ false
12
+ end
13
+ end
14
+ end
15
+ end