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,19 @@
1
+ module XMLable
2
+ module Handlers
3
+ module Mixins
4
+ #
5
+ # Described module contains described and null object handlers logic
6
+ #
7
+ module Described
8
+ #
9
+ # Is this handlers described by user or not?
10
+ #
11
+ # @return [Boolean]
12
+ #
13
+ def described?
14
+ !self.class.name.end_with?('None')
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,40 @@
1
+ module XMLable
2
+ module Handlers
3
+ module Mixins
4
+ #
5
+ # Namespace contains
6
+ #
7
+ module Namespace
8
+ #
9
+ # @see XMLable::Handlers::Base#initialize
10
+ #
11
+ def initialize(*args, &block)
12
+ if args.last.is_a?(Hash)
13
+ @namespace = args.last.delete(:namespace)
14
+ @namespace = @namespace.to_s if @namespace
15
+ end
16
+ super
17
+ end
18
+
19
+ #
20
+ # Get handler object namespace
21
+ #
22
+ # @return [String, nil]
23
+ #
24
+ def namespace_prefix
25
+ @namespace if @namespace
26
+ end
27
+
28
+ #
29
+ # Get handler combined key.
30
+ # It adds namespace to key if it exists.
31
+ #
32
+ # @return [String, nil]
33
+ #
34
+ def key
35
+ [@namespace, @tag].compact.map(&:to_s).join(":")
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,24 @@
1
+ module XMLable
2
+ module Handlers
3
+ module Mixins
4
+ #
5
+ # Tag module contains tagable handlers
6
+ #
7
+ module Tag
8
+ # @return [String] returns XML object tag name
9
+ attr_reader :tag
10
+
11
+ #
12
+ # @see XMLable::Handler::Base#intialize
13
+ #
14
+ def initialize(*args, &block)
15
+ super
16
+ if args.last.is_a?(Hash)
17
+ @tag = args.last.delete(:tag)
18
+ @tag = @tag ? @tag.to_s : @name
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,26 @@
1
+ module XMLable
2
+ module Handlers
3
+ #
4
+ # Namespace handles XML namespace objects
5
+ #
6
+ class Namespace
7
+ # @return [Symbol, nil] returns namespace's prefix
8
+ attr_reader :prefix
9
+ # @return [Symbol] returns namespace's URI
10
+ attr_reader :prefix, :href
11
+
12
+ #
13
+ # @param [Symbol, String, nil] prefix_or_href namespace's URI or prefix
14
+ # @param [String] href namespace's URI
15
+ #
16
+ def initialize(prefix_or_href = nil, href = nil, _opts = {})
17
+ if prefix.to_s =~ URI.regexp
18
+ @href = prefix_or_href
19
+ else
20
+ @prefix = prefix_or_href
21
+ @href = href
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,9 @@
1
+ module XMLable
2
+ module Handlers
3
+ #
4
+ # Root handles XML root element
5
+ #
6
+ class Root < Element
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,10 @@
1
+ module XMLable
2
+ module Handlers
3
+ #
4
+ # RootNone represents null object pattern for the
5
+ # XMLable::Handlers::Root class
6
+ #
7
+ class RootNone < Root
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,104 @@
1
+ module XMLable
2
+ module Handlers
3
+ #
4
+ # Storage stores handlers
5
+ #
6
+ class Storage
7
+ # @return [Array<XMLable::Handlers::Base>]
8
+ attr_reader :storage
9
+
10
+ #
11
+ # @param [Hash] opts additional options
12
+ # @option opts [XMLable::Handlers::Base] :default default handler's class
13
+ # @param [Array] storage initial handlers to store
14
+ #
15
+ def initialize(opts = {}, storage = [])
16
+ @opts = opts
17
+ @storage = storage
18
+ end
19
+
20
+ #
21
+ # Find or create handler for the XML node
22
+ #
23
+ # @param [XML::Nokogiri::Node] node
24
+ #
25
+ # @return [XMLable::Handlers::Base]
26
+ #
27
+ def for_xml_object(node)
28
+ tag = node.name.to_s
29
+ namespace = node.namespace.prefix if node.namespace
30
+ namespace = namespace.to_s if namespace
31
+ handler_with_tag!(tag, namespace: namespace)
32
+ end
33
+
34
+ #
35
+ # Append new handler to storage
36
+ #
37
+ # @note Described handler is always on top of the list
38
+ #
39
+ # @param [XMLable::Handler::Base] handler
40
+ #
41
+ def <<(handler)
42
+ @storage << handler
43
+ @storage = @storage.sort_by { |h| !h.described?.to_s }
44
+ end
45
+
46
+ #
47
+ # Find handler with given tag
48
+ #
49
+ # @param [String] tag element tag name
50
+ # @param [Hash] opts
51
+ # @option opts [String, nil] :namespace element namespace
52
+ #
53
+ # @return [XMLable::Handlers::Base, nil] returns handler if it's found,
54
+ # otherwise return +nil+
55
+ #
56
+ def handler_with_tag(tag, opts = {})
57
+ match = @storage.find { |h| h.tag == tag }
58
+ return match if !match || !opts.key?(:namespace)
59
+ return match if opts[:namespace] == false
60
+ match.namespace_prefix == opts[:namespace] ? match : nil
61
+ end
62
+
63
+ #
64
+ # Find handler with given tag or create a new one
65
+ #
66
+ # @param [String] tag element tag name
67
+ # @param [Hash] opts
68
+ # @option opts [String, nil] :namespace element namespace
69
+ #
70
+ # @return [XMLable::Handlers::Base]
71
+ #
72
+ def handler_with_tag!(tag, opts = {})
73
+ match = handler_with_tag(tag, opts)
74
+ return match if match
75
+ default_handler.build(tag, opts).tap { |h| self << h }
76
+ end
77
+
78
+ #
79
+ # Clone handlers storage
80
+ #
81
+ # @return [XMLable::Handers::Storage]
82
+ #
83
+ def clone
84
+ self.class.new(@opts, storage.clone)
85
+ end
86
+
87
+ #
88
+ # Default handler class
89
+ #
90
+ # @return [XMLable::Handlers::Base] returns null object handler
91
+ #
92
+ def default_handler
93
+ @opts[:default]
94
+ end
95
+
96
+ #
97
+ # @return [String]
98
+ #
99
+ def inspect
100
+ "#<XMLable::Handlers::Storage #{@storage.map(&:key).join(', ')} >"
101
+ end
102
+ end
103
+ end
104
+ end
@@ -0,0 +1,23 @@
1
+ module XMLable
2
+ #
3
+ # Handlers module contains the handlers objects
4
+ #
5
+ module Handlers
6
+ end
7
+ end
8
+
9
+ require_relative 'handlers/mixins/namespace'
10
+ require_relative 'handlers/mixins/described'
11
+ require_relative 'handlers/mixins/tag'
12
+
13
+ require_relative 'handlers/base'
14
+ require_relative 'handlers/attribute'
15
+ require_relative 'handlers/attribute_none'
16
+ require_relative 'handlers/element'
17
+ require_relative 'handlers/elements'
18
+ require_relative 'handlers/element_none'
19
+ require_relative 'handlers/root'
20
+ require_relative 'handlers/root_none'
21
+ require_relative 'handlers/document'
22
+ require_relative 'handlers/namespace'
23
+ require_relative 'handlers/storage'
@@ -0,0 +1,195 @@
1
+ module XMLable
2
+ module Mixins
3
+ #
4
+ # AttributesStorage module contains the logic for object that able to
5
+ # store XML attributes
6
+ #
7
+ module AttributesStorage
8
+ def self.included(base)
9
+ base.extend(ClassMethods)
10
+ end
11
+
12
+ #
13
+ # Set XML attribute
14
+ #
15
+ # @param [Nokogiri::XML::Attr, nil] att
16
+ # @param [Hash] opts
17
+ # @option opts [String] :tag attribute's name
18
+ # @option opts [String] :namespace attribute's namespace
19
+ #
20
+ # @api private
21
+ #
22
+ def __set_attribute(att, opts = {})
23
+ unless att
24
+ @__node[opts[:tag]] = att
25
+ att = @__node.attributes[opts[:tag]]
26
+ if opts[:namespace]
27
+ att.namespace = att.namespace_scopes.find { |n| n.prefix == opts[:namespace] }
28
+ end
29
+ end
30
+ h = __attributes_handlers.for_xml_object(att)
31
+ att.instance_variable_set(:@__handler, h)
32
+ __attributes[h.key] = h.from_xml_attribute(att)
33
+ end
34
+
35
+ #
36
+ # Attributes which current object holds
37
+ #
38
+ # @api private
39
+ #
40
+ # @return [Hash(String => Array<XMLable::Mixins::Object>)]
41
+ #
42
+ def __attributes
43
+ @__attributes ||= {}
44
+ end
45
+
46
+ #
47
+ # Is this object empty?
48
+ #
49
+ # @api private
50
+ #
51
+ # @return [Hash(String => Array<XMLable::Mixins::Object>)]
52
+ #
53
+ def __empty?
54
+ return false unless super
55
+ __attributes.values.all?(&:__empty?)
56
+ end
57
+
58
+ def method_missing(name, *args, &block)
59
+ h = __has_attribute_handler?(name)
60
+ return super unless h
61
+
62
+ if name.to_s.end_with?('=')
63
+ __attribute_object_set(h, args.first)
64
+ else
65
+ __attribute_object_get(h)
66
+ end
67
+ end
68
+
69
+ #
70
+ # Does this object contain attribute with given key?
71
+ #
72
+ # @return [XMLable::Mixins::Object, false]
73
+ #
74
+ def key?(key)
75
+ super || __has_attribute_handler?(key)
76
+ end
77
+
78
+ #
79
+ # Get attribute object by its key
80
+ #
81
+ # @param [String] key attribute key
82
+ #
83
+ # @return [XMLable::Mixins::Object, nil]
84
+ #
85
+ def [](key)
86
+ h = __has_attribute_handler?(key)
87
+ h ? __attribute_object_get(h) : super
88
+ end
89
+
90
+ #
91
+ # Set attribute value
92
+ #
93
+ # @param [String] key attribute key
94
+ # @param [Object] val new value
95
+ #
96
+ # @return [XMLable::Mixins::Object, nil]
97
+ #
98
+ def []=(key, val)
99
+ h = __has_attribute_handler?(key)
100
+ h ? __attribute_object_set(h, val) : super
101
+ end
102
+
103
+ #
104
+ # Find attribute handler by its key
105
+ #
106
+ # @param [Symbol, String] key
107
+ #
108
+ # @api private
109
+ #
110
+ # @return [XMLable::Handlers::Attribute, XMLable::Handlers::AttributeNone, nil]
111
+ #
112
+ def __has_attribute_handler?(key)
113
+ key = key.to_s.gsub(/[=!]$/, '')
114
+ __attributes_handlers.storage.find { |h| h.method_name == key }
115
+ end
116
+
117
+ #
118
+ # Get attribute object
119
+ #
120
+ # @param [XMLable::Handlers::Attribute, XMLable::Handlers::AttributeNone] h
121
+ #
122
+ # @api private
123
+ #
124
+ # @return [XMLable::Mixins::Object]
125
+ #
126
+ def __attribute_object_get(h)
127
+ unless __attributes.key?(h.key)
128
+ __set_attribute(nil, tag: h.tag, namespace: h.namespace_prefix)
129
+ end
130
+ __attributes[h.key]
131
+ end
132
+
133
+ #
134
+ # Set attribute object value
135
+ #
136
+ # @param [XMLable::Handlers::Attribute, XMLable::Handlers::AttributeNone, nil] h
137
+ # @param [Object] val new value
138
+ #
139
+ # @api private
140
+ #
141
+ def __attribute_object_set(h, val)
142
+ __attribute_object_get(h).__overwrite_value(val)
143
+ end
144
+
145
+ #
146
+ # Initialize attribute object with value
147
+ #
148
+ # @param [XMLable::Handlers::Attribute, XMLable::Handlers::AttributeNone, nil] h
149
+ # @param [Object] val
150
+ #
151
+ # @api private
152
+ #
153
+ def __attribute_object_initialize(h, value)
154
+ __attribute_object_get(h).__overwrite_value(value)
155
+ end
156
+
157
+ #
158
+ # Attributes handlers storage
159
+ #
160
+ # @return [XMLable::Handlers::Storage]
161
+ #
162
+ def __attributes_handlers
163
+ @__attributes_handler ||= self.class.__attributes_handlers.clone
164
+ end
165
+
166
+ module ClassMethods
167
+ #
168
+ # Attributes handlers storage
169
+ #
170
+ # @return [XMLable::Handlers::Storage]
171
+ #
172
+ def __attributes_handlers
173
+ @__attributes_handlers ||=
174
+ __nested(:@__attributes_handlers) || Handlers::Storage.new(default: Handlers::AttributeNone)
175
+ end
176
+
177
+ #
178
+ # Describe object attribute
179
+ #
180
+ # @see XMLable::Handler::Base#build
181
+ #
182
+ # @return [XMLable::Handlers::Storage]
183
+ #
184
+ def attribute(*args, &block)
185
+ opts = args.last.is_a?(Hash) ? args.pop : {}
186
+ if __default_namespace && !opts.key?(:namespace)
187
+ opts[:namespace] = __default_namespace
188
+ end
189
+ h = Handlers::Attribute.build(*args, opts, &block)
190
+ __attributes_handlers << h
191
+ end
192
+ end
193
+ end
194
+ end
195
+ end
@@ -0,0 +1,41 @@
1
+ module XMLable
2
+ module Mixins
3
+ #
4
+ # BareValue contains logic to get the bare value.
5
+ # All XML object such as Integer, Date, String, etc are wrapped with the
6
+ # proxy classes. This module helps to unwrap the real value.
7
+ #
8
+ module BareValue
9
+ def method_missing(name, *args, &block)
10
+ return super unless name.to_s =~ /!$/
11
+ return super unless key?(name)
12
+ name = name.to_s.gsub(/!$/,'').to_sym
13
+ __extract_bare_value(super)
14
+ end
15
+
16
+ #
17
+ # Get object value
18
+ # It unwraps the object value if object key ends with '!' symbol.
19
+ #
20
+ # @return [XMLable::Mixins::Object, Object, nil]
21
+ #
22
+ def [](key)
23
+ return super unless key.to_s =~ /!$/
24
+ __extract_bare_value(super)
25
+ end
26
+
27
+ #
28
+ # Unwrap objects
29
+ #
30
+ # @param [XMLable::Mixins::Object, Array<XMLable::Mixins::Object>] obj
31
+ #
32
+ # @api private
33
+ #
34
+ # @return [Object, Array<Object>]
35
+ #
36
+ def __extract_bare_value(obj)
37
+ obj.respond_to?(:map) ? obj.map(&:__object) : obj.__object
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,93 @@
1
+ module XMLable
2
+ module Mixins
3
+ #
4
+ # Castable module contains the logic that helps to cast values from
5
+ # different forms to XML/JSON and back.
6
+ #
7
+ module Castable
8
+ def self.included(base)
9
+ base.extend(ClassMethods)
10
+ end
11
+
12
+ #
13
+ # Cast object from XML value
14
+ #
15
+ # @param [String] val
16
+ #
17
+ # @api private
18
+ #
19
+ # @return [Object]
20
+ #
21
+ def __cast(val)
22
+ val
23
+ end
24
+
25
+ #
26
+ # Export value to XML/JSON
27
+ #
28
+ # @param [Object] val
29
+ #
30
+ # @api private
31
+ #
32
+ # @return [Object]
33
+ #
34
+ def __export(val)
35
+ val.to_s
36
+ end
37
+
38
+ #
39
+ # Export value to XML
40
+ #
41
+ # @param [Object] val
42
+ #
43
+ # @api private
44
+ #
45
+ # @return [String]
46
+ #
47
+ def __export_to_xml(val)
48
+ __export(val).to_s
49
+ end
50
+
51
+ #
52
+ # Export value to JSON
53
+ #
54
+ # @param [Object] val
55
+ #
56
+ # @api private
57
+ #
58
+ # @return [Object]
59
+ #
60
+ def __export_to_json(val)
61
+ __export(val).to_s
62
+ end
63
+
64
+ #
65
+ # Is this object empty?
66
+ #
67
+ # @api private
68
+ #
69
+ # @return [Boolean]
70
+ #
71
+ def __empty(val)
72
+ val.respond_to?(:empty?) && val.empty? ||
73
+ val.is_a?(String) && val == '' ||
74
+ val.nil?
75
+ end
76
+
77
+ module ClassMethods
78
+ # @return [Array<Symbol>] retuns list of the setup methods
79
+ CAST_METHODS = %i[cast export export_to_json export_to_xml empty]
80
+
81
+ #
82
+ # Override method missing to get the setup methods be available
83
+ #
84
+ # @see XMLable::Mixins::Castable::ClassMethods::CAST_METHODS
85
+ #
86
+ def method_missing(name, *args, &block)
87
+ return super unless CAST_METHODS.include?(name)
88
+ define_method("__#{name}", &block) if block_given?
89
+ end
90
+ end
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,71 @@
1
+ module XMLable
2
+ module Mixins
3
+ #
4
+ # Container module contains the logic for the XML objects groups
5
+ #
6
+ module Container
7
+ # @return [XMLable::Handlers::Elements]
8
+ attr_reader :__handler
9
+
10
+ #
11
+ # Create new element
12
+ #
13
+ # @param [Object] args element initial params
14
+ #
15
+ # @return [XMLable::Mixins::Object] returns created element
16
+ #
17
+ def new(args = nil)
18
+ xml = Nokogiri::XML::Element.new(@__handler.tag.to_s, @__parent_node)
19
+ el = @__handler.from_xml_element(xml)
20
+ el.__initialize_with(args) if args
21
+ @__parent_node << xml
22
+ self << el
23
+ el
24
+ end
25
+
26
+ #
27
+ # Set current object handler
28
+ #
29
+ # @param [XMLable::Handlers::Elements] handler
30
+ #
31
+ # @api private
32
+ #
33
+ def __set_handler(handler)
34
+ @__handler = handler
35
+ end
36
+
37
+ #
38
+ # Parent XML node
39
+ #
40
+ # @api private
41
+ #
42
+ # @param [Nokogiri::XML::Node]
43
+ #
44
+ def __set_parent_node(node)
45
+ @__parent_node = node
46
+ end
47
+
48
+ #
49
+ # Initialize container with params
50
+ #
51
+ # @api private
52
+ #
53
+ # @param [Array<Object>] val
54
+ #
55
+ def __initialize_with(val)
56
+ val.each { |v| new(v) }
57
+ end
58
+
59
+ #
60
+ # Does this container contain only empty objects?
61
+ #
62
+ # @api private
63
+ #
64
+ # @return [Boolean]
65
+ #
66
+ def __empty?
67
+ all?(&:__empty?)
68
+ end
69
+ end
70
+ end
71
+ end