xmlable 0.0.0.alpha1

Sign up to get free protection for your applications and to get access to all the features.
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