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,138 @@
1
+ module XMLable
2
+ module Mixins
3
+ #
4
+ # ContentStorage module contains the logic that helps to store XML element
5
+ # content.
6
+ #
7
+ module ContentStorage
8
+ def self.included(base)
9
+ base.extend(ClassMethods)
10
+ end
11
+
12
+ #
13
+ # Set XML element content
14
+ #
15
+ # @param [Nokogiri::XML::Element] node
16
+ #
17
+ # @api private
18
+ #
19
+ def __set_content(node)
20
+ val = node.children.select(&:text?).map(&:content).join('').strip
21
+ @__content = __cast(val)
22
+ end
23
+
24
+ #
25
+ # Override XML node content
26
+ #
27
+ # @param [Object] value
28
+ #
29
+ # @api private
30
+ #
31
+ def __overwrite_content(val)
32
+ val = __cast(val)
33
+ @__node.content = __export_to_xml(val)
34
+ @__content = val
35
+ end
36
+
37
+ #
38
+ # Get content value
39
+ #
40
+ # @api private
41
+ #
42
+ # @return [Object]
43
+ #
44
+ def __object
45
+ __content
46
+ end
47
+
48
+ #
49
+ # Get content value
50
+ #
51
+ # @api private
52
+ #
53
+ # @return [Object]
54
+ #
55
+ def __content
56
+ @__content
57
+ end
58
+
59
+ #
60
+ # Set content value
61
+ #
62
+ # @param [Object] value
63
+ #
64
+ # @api private
65
+ #
66
+ # @return [Object]
67
+ #
68
+ def __content=(val)
69
+ __overwrite_content(val)
70
+ end
71
+
72
+ #
73
+ # Is this element?
74
+ #
75
+ # @api private
76
+ #
77
+ # @return [Boolean]
78
+ #
79
+ def __empty?
80
+ return false unless super
81
+ __empty(__content)
82
+ end
83
+
84
+ #
85
+ # @return [String]
86
+ #
87
+ def to_s
88
+ __content.to_s
89
+ end
90
+
91
+ #
92
+ # Get content alias method
93
+ #
94
+ # @api private
95
+ #
96
+ # @return [String, nil]
97
+ #
98
+ def __content_method
99
+ self.class.__content_method
100
+ end
101
+
102
+ #
103
+ # Contents methods
104
+ #
105
+ # @api private
106
+ #
107
+ # @return [Array<String>]
108
+ #
109
+ def __content_methods
110
+ ret = ['__content']
111
+ ret << __content_method.to_s if __content_method
112
+ ret
113
+ end
114
+
115
+ module ClassMethods
116
+ #
117
+ # Define content method
118
+ # If given +false+ name than content isn't available to this element.
119
+ #
120
+ # @param [String, Symbol, false] name
121
+ #
122
+ def content(name)
123
+ @__content_method = name.is_a?(String) ? name.to_sym : name
124
+ end
125
+
126
+ #
127
+ # Get content method
128
+ #
129
+ # @return [String, nil, false]
130
+ #
131
+ def __content_method
132
+ return @__content_method if instance_variable_defined?(:@__content_method)
133
+ @__content_method = __nested(:@__content_method)
134
+ end
135
+ end
136
+ end
137
+ end
138
+ end
@@ -0,0 +1,136 @@
1
+ module XMLable
2
+ module Mixins
3
+ #
4
+ # DocumentStorage module contains the logic to work with XML document
5
+ #
6
+ module DocumentStorage
7
+ def self.included(base)
8
+ base.extend(ClassMethods)
9
+ end
10
+
11
+ #
12
+ # @param [Hash] args document initial params
13
+ # @param [Nokogiri::XML::Document] doc XML document
14
+ # @param [XMLable::Handlers::Document] handler document's handler
15
+ #
16
+ def initialize(args = {}, doc = nil, handler = nil)
17
+ doc ||= Nokogiri::XML::Document.new
18
+ handler ||= Handlers::Document.new(self.class)
19
+ super
20
+ end
21
+
22
+ #
23
+ # Get XML document version
24
+ #
25
+ # @return [String, nil]
26
+ #
27
+ def version
28
+ __node.version
29
+ end
30
+
31
+ #
32
+ # Get XML document encoding
33
+ #
34
+ # @return [String, nil]
35
+ #
36
+ def encoding
37
+ __node.encoding
38
+ end
39
+
40
+ #
41
+ # Get document's handler
42
+ #
43
+ # @api private
44
+ #
45
+ # @return [XMLable::Handlers::Document]
46
+ #
47
+ def __document_handler
48
+ self.class.__document_handler
49
+ end
50
+
51
+ module ClassMethods
52
+ #
53
+ # Define document params
54
+ #
55
+ # @param [Hash] opts
56
+ #
57
+ def document(opts = {})
58
+ opt(opts) if opts.size > 0
59
+ end
60
+
61
+ #
62
+ # Set the Nokogiri's parsing params
63
+ #
64
+ def nokogiri_parse(*args)
65
+ args.each do |opt|
66
+ __nokogiri_parse_options.send(opt) if __nokogiri_parse_options.respond_to?(opt)
67
+ end
68
+ end
69
+
70
+ #
71
+ # Set the Nokogiri's export params
72
+ #
73
+ def nokogiri_export(*args)
74
+ __nokogiri_export_options.tap do |opts|
75
+ opts.merge!(args.pop) if args.last.is_a?(Hash)
76
+ args.each { |opt| opts.save_with.send(opt) if opts.save_with.respond_to?(opt) }
77
+ end
78
+ end
79
+
80
+ #
81
+ # Nokogiri's options for parsing
82
+ #
83
+ # @api private
84
+ #
85
+ # @return [Nokogiri::XML::ParseOptions]
86
+ #
87
+ def __nokogiri_parse_options
88
+ @__nokogiri_parse_options ||= Nokogiri::XML::ParseOptions.new.recover
89
+ end
90
+
91
+ #
92
+ # Nokogiri's options for exporting
93
+ #
94
+ # @api private
95
+ #
96
+ # @return [Nokogiri::XML::ParseOptions]
97
+ #
98
+ def __nokogiri_export_options
99
+ @__nokogiri_export_options ||= Options::NokogiriExport.new
100
+ end
101
+
102
+ #
103
+ # Get document's handler
104
+ #
105
+ # @api private
106
+ #
107
+ # @return [XMLable::Handlers::Document]
108
+ #
109
+ def __document_handler
110
+ @__document_handler ||= Handlers::Document.new(self)
111
+ end
112
+
113
+ #
114
+ # Initialize document from XML
115
+ #
116
+ # @param [Nokogiri::XML::Document, String] xml
117
+ # @param [Hash] _opts
118
+ #
119
+ # @return [XMLable::Document]
120
+ #
121
+ def from_xml(xml, _opts = {})
122
+ xml = '' unless xml
123
+ if xml.is_a?(String)
124
+ doc = Nokogiri::XML(xml) do |config|
125
+ config.options = __nokogiri_parse_options.to_i
126
+ end
127
+ elsif xml.is?(Nokogiri::XML::Document)
128
+ doc = xml
129
+ else raise "Don't know how to parse '#{xml.class}'"
130
+ end
131
+ __document_handler.from_xml_document(doc)
132
+ end
133
+ end
134
+ end
135
+ end
136
+ end
@@ -0,0 +1,219 @@
1
+ module XMLable
2
+ module Mixins
3
+ #
4
+ # ElementsStorage module contains the logic to store XML elements
5
+ #
6
+ module ElementsStorage
7
+ def self.included(base)
8
+ base.extend(ClassMethods)
9
+ end
10
+
11
+ #
12
+ # Set element from XML Node
13
+ #
14
+ # @param [Symbol] name tag name
15
+ # @param [Nokogiri::XML::Element, nil] el
16
+ #
17
+ # @api private
18
+ #
19
+ def __set_element(el, opts = {})
20
+ unless el.is_a?(Nokogiri::XML::Element)
21
+ el = Nokogiri::XML::Element.new(opts[:tag], @__node)
22
+ @__node << el
23
+ if opts[:namespace]
24
+ el.namespace = el.namespace_scopes.find { |n| n.prefix == opts[:namespace] }
25
+ end
26
+ end
27
+ h = __elements_handlers.for_xml_object(el)
28
+ el.instance_variable_set(:@__handler, h)
29
+ __initialize_elements_container(h) << h.from_xml_element(el)
30
+ end
31
+
32
+ #
33
+ # Initialize elements container
34
+ #
35
+ # @param [XMLable::Handlers::Element, XMLable::Handlers::Elements, XMLable::Handlers::ElementNone] h
36
+ #
37
+ # @api private
38
+ #
39
+ # @return [XMLable::Mixins::Container]
40
+ #
41
+ def __initialize_elements_container(h)
42
+ __elements[h.key] ||= h.container_for_xml_element(__node)
43
+ end
44
+
45
+ #
46
+ # Elements which current object holds
47
+ #
48
+ # @api private
49
+ #
50
+ # @return [Hash(String => Array<XMLable::Mixins::Object>)]
51
+ #
52
+ def __elements
53
+ @__elements ||= {}
54
+ end
55
+
56
+ #
57
+ # Is this object empty?
58
+ #
59
+ # @api private
60
+ #
61
+ # @return [Boolean]
62
+ #
63
+ def __empty?
64
+ return false unless super
65
+ __elements.values.all?(&:__empty?)
66
+ end
67
+
68
+ def method_missing(name, *args, &blocks)
69
+ h = __has_element_handler?(name)
70
+ return super unless h
71
+
72
+ if name.to_s.end_with?('=')
73
+ __element_object_set(h, args.first)
74
+ else
75
+ __element_object_get(h)
76
+ end
77
+ end
78
+
79
+ #
80
+ # Elements handlers storage
81
+ #
82
+ # @api private
83
+ #
84
+ # @return [XMLable::Handlers::Storage]
85
+ #
86
+ def __elements_handlers
87
+ @__elements_handler ||= self.class.__elements_handlers.clone
88
+ end
89
+
90
+ #
91
+ # Does this object contain element with given key?
92
+ #
93
+ # @return [XMLable::Mixins::Object, false]
94
+ #
95
+ def key?(key)
96
+ super || __has_element_handler?(key)
97
+ end
98
+
99
+ #
100
+ # Get element object by its key
101
+ #
102
+ # @param [String] key element key
103
+ #
104
+ # @return [XMLable::Mixins::Object, nil]
105
+ #
106
+ def [](key)
107
+ h = __has_element_handler?(key)
108
+ h ? __element_object_get(h) : super
109
+ end
110
+
111
+ #
112
+ # Set element value
113
+ #
114
+ # @param [String] key element key
115
+ # @param [Object] val new value
116
+ #
117
+ # @return [XMLable::Mixins::Object, nil]
118
+ #
119
+ def []=(key, val)
120
+ h = __has_element_handler?(key)
121
+ h ? __element_object_set(h, val) : super
122
+ end
123
+
124
+ #
125
+ # Find element handler by its key
126
+ #
127
+ # @param [Symbol, String] key
128
+ #
129
+ # @api private
130
+ #
131
+ # @return [XMLable::Handlers::Element, XMLable::Handlers::ElementNone, nil]
132
+ #
133
+ def __has_element_handler?(key)
134
+ key = key.to_s.gsub(/[=!]$/, '')
135
+ __elements_handlers.storage.find { |h| h.method_name == key }
136
+ end
137
+
138
+ #
139
+ # Get element object
140
+ #
141
+ # @param [XMLable::Handlers::Element, XMLable::Handlers::Elements, XMLable::Handlers::ElementNone] h
142
+ #
143
+ # @api private
144
+ #
145
+ # @return [XMLable::Mixins::Object]
146
+ #
147
+ def __element_object_get(h)
148
+ unless __elements.key?(h.key)
149
+ if h.single?
150
+ __set_element(nil, tag: h.tag, namespace: h.namespace_prefix)
151
+ else
152
+ __initialize_elements_container(h)
153
+ end
154
+ end
155
+ ret = __elements[h.key]
156
+ h.single? ? ret.first : ret
157
+ end
158
+
159
+ #
160
+ # Set element object value
161
+ #
162
+ # @param [XMLable::Handlers::Element, XMLable::Handlers::Elements, XMLable::Handlers::ElementNone] h
163
+ # @param [Object] val
164
+ #
165
+ # @api private
166
+ #
167
+ # @return [XMLable::Mixins::Object]
168
+ #
169
+ def __element_object_set(h, val)
170
+ __element_object_get(h).__overwrite_content(val)
171
+ end
172
+
173
+ #
174
+ # Set element object value
175
+ #
176
+ # @param [XMLable::Handlers::Element, XMLable::Handlers::Elements, XMLable::Handlers::ElementNone] h
177
+ # @param [Object] val
178
+ #
179
+ # @api private
180
+ #
181
+ def __element_object_initialize(h, val)
182
+ obj = __element_object_get(h)
183
+
184
+ val = { '__content' => val } unless val.respond_to?(:each)
185
+ if h.single?
186
+ obj.__initialize_with(val)
187
+ else
188
+ val.map { |v| obj.new(v) }
189
+ end
190
+ end
191
+
192
+ module ClassMethods
193
+
194
+ #
195
+ # Elements handlers storage
196
+ #
197
+ # @api private
198
+ #
199
+ # @return [XMLable::Handlers::Storage]
200
+ #
201
+ def __elements_handlers
202
+ @__elements_handlers ||=
203
+ __nested(:@__elements_handlers) || Handlers::Storage.new(default: Handlers::ElementNone)
204
+ end
205
+
206
+ %i[element elements].each do |m|
207
+ define_method m do |*args, &block|
208
+ opts = args.last.is_a?(Hash) ? args.pop : {}
209
+ if __default_namespace && !opts.key?(:namespace)
210
+ opts[:namespace] = __default_namespace
211
+ end
212
+ h = XMLable::Handlers.const_get(__method__.to_s.capitalize).build(*args, opts, &block)
213
+ __elements_handlers << h
214
+ end
215
+ end
216
+ end
217
+ end
218
+ end
219
+ end
@@ -0,0 +1,45 @@
1
+ module XMLable
2
+ module Mixins
3
+ #
4
+ # Export modules contains method to export
5
+ #
6
+ module Export
7
+ #
8
+ # Export to XML
9
+ #
10
+ # @param [Hash] opts
11
+ #
12
+ # @return [String] returns exported XML
13
+ #
14
+ def to_xml(opts = {})
15
+ exporter = Exports::XMLExporter.new(self, opts)
16
+ opts = self.class.__nokogiri_export_options.merge(opts)
17
+ exporter.export.to_xml(opts).strip
18
+ end
19
+
20
+ #
21
+ # Export to JSON
22
+ #
23
+ # @param [Hash] opts
24
+ # @option opts [Boolean] :pretty enable pretty print
25
+ #
26
+ # @return [String] returns exported JSON
27
+ #
28
+ def to_json(opts = {})
29
+ ret = to_h(opts)
30
+ opts[:pretty] ? JSON.pretty_generate(ret) : ret.to_json
31
+ end
32
+
33
+ #
34
+ # Export to hash
35
+ #
36
+ # @param [Hash] opts
37
+ #
38
+ # @return [Hash]
39
+ #
40
+ def to_h(opts = {})
41
+ Exports::JSONExporter.new(self, opts).export
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,47 @@
1
+ module XMLable
2
+ module Mixins
3
+ #
4
+ # Instantiable module contains the logic to initialize the new elements
5
+ #
6
+ module Instantiable
7
+ #
8
+ # @param [Object] items initial params
9
+ # @param [Nokogiri::XML::Node] node
10
+ # @param [XMLable::Handlers::Base] handler
11
+ #
12
+ def initialize(items = nil, node = nil, handler = nil)
13
+ super(node, handler)
14
+ __initialize_with(items) if items
15
+ end
16
+
17
+ #
18
+ # Initialize object with given value
19
+ #
20
+ # @param [Object] value
21
+ #
22
+ def __initialize_with(value)
23
+ if value.is_a?(Hash)
24
+ value.each do |key, val|
25
+ if respond_to?(:__overwrite_content) && __content_methods.include?(key.to_s)
26
+ next __overwrite_content(val)
27
+ end
28
+
29
+ h = key?(key)
30
+ case h
31
+ when Handlers::Attribute then __attribute_object_initialize(h, val)
32
+ when Handlers::Root then __root_object_initialize(h, val)
33
+ when Handlers::Element then __element_object_initialize(h, val)
34
+ else raise h.inspect
35
+ end
36
+ end
37
+ else
38
+ case __handler
39
+ when Handlers::Attribute then __overwrite_value(value)
40
+ when Handlers::Element then __overwrite_content(value)
41
+ else raise h.inspect
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,105 @@
1
+ module XMLable
2
+ module Mixins
3
+ #
4
+ # NamespaceDefinitionsStorage module contains the logic to manage with XML
5
+ # namespaces definitions
6
+ #
7
+ module NamespaceDefinitionsStorage
8
+ def self.included(base)
9
+ base.extend(ClassMethods)
10
+ end
11
+
12
+ #
13
+ # @see XMLable::Mixins::Object#initialize
14
+ #
15
+ def initialize(*)
16
+ super
17
+ __define_namespaces(__namespace_definitions)
18
+ end
19
+
20
+ #
21
+ # Define XML namespaces
22
+ #
23
+ # @param [Array<XMLable::Handlers::Namespace>] handlers
24
+ #
25
+ # @api private
26
+ #
27
+ def __define_namespaces(handlers)
28
+ handlers.each do |h|
29
+ next if __node.namespace_scopes.find { |ns| ns.prefix == h.prefix }
30
+ __node.add_namespace_definition(h.prefix ? h.prefix.to_s : nil, h.href)
31
+ end
32
+ end
33
+
34
+ #
35
+ # Set XML namespace
36
+ #
37
+ # @param [Nokogiri::XML::Namespace] ns
38
+ #
39
+ # @api private
40
+ #
41
+ def __set_namespace_definition(ns)
42
+ __namespace_definitions << ns
43
+ end
44
+
45
+ #
46
+ # Get namespace definition handlers
47
+ #
48
+ # @api private
49
+ #
50
+ # @return [XMLable::Handlers::Storage]
51
+ #
52
+ def __namespace_definitions
53
+ @__namespace_definitions ||= self.class.__namespace_definitions.clone
54
+ end
55
+
56
+ module ClassMethods
57
+ #
58
+ # Define XML namespace
59
+ #
60
+ # @see XMLable::Handlers::Base#build
61
+ #
62
+ def namespace(*args)
63
+ opts = args.last.is_a?(Hash) ? args.pop : {}
64
+ default = opts.delete(:default) || true
65
+ h = Handlers::Namespace.new(*args, opts)
66
+ self.__default_namespace = h.prefix if default
67
+ __namespace_definitions << h
68
+ end
69
+
70
+ #
71
+ # Default namespace prefix
72
+ #
73
+ # @api private
74
+ #
75
+ # @return [Symbol, nil]
76
+ #
77
+ def __default_namespace
78
+ @__default_namespace
79
+ end
80
+
81
+ #
82
+ # Set default namespace prefix
83
+ #
84
+ # @api private
85
+ #
86
+ # @param [Symbol, nil]
87
+ #
88
+ def __default_namespace=(val)
89
+ @__default_namespace = val
90
+ end
91
+
92
+ #
93
+ # Get namespace definition handlers
94
+ #
95
+ # @api private
96
+ #
97
+ # @return [XMLable::Handlers::Storage]
98
+ #
99
+ def __namespace_definitions
100
+ @__namespace_definitions ||= []
101
+ end
102
+ end
103
+ end
104
+ end
105
+ end