lutaml-model 0.3.0 → 0.3.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (52) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +0 -5
  3. data/.rubocop_todo.yml +20 -101
  4. data/Gemfile +3 -18
  5. data/README.adoc +1100 -140
  6. data/lib/lutaml/model/attribute.rb +15 -2
  7. data/lib/lutaml/model/config.rb +0 -1
  8. data/lib/lutaml/model/error/invalid_value_error.rb +18 -0
  9. data/lib/lutaml/model/error.rb +8 -0
  10. data/lib/lutaml/model/json_adapter/json_document.rb +20 -0
  11. data/lib/lutaml/model/json_adapter/json_object.rb +28 -0
  12. data/lib/lutaml/model/json_adapter/{multi_json.rb → multi_json_adapter.rb} +2 -3
  13. data/lib/lutaml/model/json_adapter/{standard.rb → standard_json_adapter.rb} +2 -3
  14. data/lib/lutaml/model/json_adapter.rb +1 -31
  15. data/lib/lutaml/model/key_value_mapping.rb +9 -2
  16. data/lib/lutaml/model/key_value_mapping_rule.rb +0 -1
  17. data/lib/lutaml/model/mapping_hash.rb +0 -2
  18. data/lib/lutaml/model/mapping_rule.rb +5 -3
  19. data/lib/lutaml/model/schema/json_schema.rb +0 -1
  20. data/lib/lutaml/model/schema/relaxng_schema.rb +0 -1
  21. data/lib/lutaml/model/schema/xsd_schema.rb +0 -1
  22. data/lib/lutaml/model/schema/yaml_schema.rb +0 -1
  23. data/lib/lutaml/model/schema.rb +0 -1
  24. data/lib/lutaml/model/serializable.rb +0 -1
  25. data/lib/lutaml/model/serialize.rb +241 -153
  26. data/lib/lutaml/model/toml_adapter/toml_document.rb +20 -0
  27. data/lib/lutaml/model/toml_adapter/toml_object.rb +28 -0
  28. data/lib/lutaml/model/toml_adapter/toml_rb_adapter.rb +4 -5
  29. data/lib/lutaml/model/toml_adapter/tomlib_adapter.rb +2 -3
  30. data/lib/lutaml/model/toml_adapter.rb +0 -31
  31. data/lib/lutaml/model/type/date_time.rb +20 -0
  32. data/lib/lutaml/model/type/json.rb +34 -0
  33. data/lib/lutaml/model/type/time_without_date.rb +4 -3
  34. data/lib/lutaml/model/type.rb +61 -124
  35. data/lib/lutaml/model/version.rb +1 -1
  36. data/lib/lutaml/model/xml_adapter/nokogiri_adapter.rb +20 -13
  37. data/lib/lutaml/model/xml_adapter/oga_adapter.rb +4 -5
  38. data/lib/lutaml/model/xml_adapter/ox_adapter.rb +24 -17
  39. data/lib/lutaml/model/xml_adapter/xml_attribute.rb +27 -0
  40. data/lib/lutaml/model/xml_adapter/xml_document.rb +184 -0
  41. data/lib/lutaml/model/xml_adapter/xml_element.rb +94 -0
  42. data/lib/lutaml/model/xml_adapter/xml_namespace.rb +49 -0
  43. data/lib/lutaml/model/xml_adapter.rb +0 -266
  44. data/lib/lutaml/model/xml_mapping.rb +1 -1
  45. data/lib/lutaml/model/xml_mapping_rule.rb +3 -4
  46. data/lib/lutaml/model/yaml_adapter/standard_yaml_adapter.rb +34 -0
  47. data/lib/lutaml/model/yaml_adapter/yaml_document.rb +20 -0
  48. data/lib/lutaml/model/yaml_adapter/yaml_object.rb +28 -0
  49. data/lib/lutaml/model/yaml_adapter.rb +1 -19
  50. data/lib/lutaml/model.rb +7 -5
  51. metadata +19 -5
  52. data/lib/lutaml/model/xml_namespace.rb +0 -47
@@ -0,0 +1,184 @@
1
+ require_relative "../mapping_hash"
2
+ require_relative "xml_element"
3
+ require_relative "xml_attribute"
4
+ require_relative "xml_namespace"
5
+
6
+ module Lutaml
7
+ module Model
8
+ module XmlAdapter
9
+ class XmlDocument
10
+ attr_reader :root
11
+
12
+ def initialize(root)
13
+ @root = root
14
+ end
15
+
16
+ def self.parse(xml)
17
+ raise NotImplementedError, "Subclasses must implement `parse`."
18
+ end
19
+
20
+ def children
21
+ @root.children
22
+ end
23
+
24
+ def declaration(options)
25
+ version = "1.0"
26
+ version = options[:declaration] if options[:declaration].is_a?(String)
27
+
28
+ encoding = options[:encoding] ? "UTF-8" : nil
29
+ encoding = options[:encoding] if options[:encoding].is_a?(String)
30
+
31
+ declaration = "<?xml version=\"#{version}\""
32
+ declaration += " encoding=\"#{encoding}\"" if encoding
33
+ declaration += "?>\n"
34
+ declaration
35
+ end
36
+
37
+ def to_h
38
+ parse_element(@root)
39
+ end
40
+
41
+ def order
42
+ @root.order
43
+ end
44
+
45
+ def handle_nested_elements(builder, value, rule: nil, attribute: nil)
46
+ options = build_options_for_nested_elements(attribute, rule)
47
+
48
+ case value
49
+ when Array
50
+ value.each { |val| build_element(builder, val, options) }
51
+ else
52
+ build_element(builder, value, options)
53
+ end
54
+ end
55
+
56
+ def build_options_for_nested_elements(attribute, rule)
57
+ return {} unless rule
58
+
59
+ options = {}
60
+
61
+ options[:namespace_prefix] = rule.prefix if rule&.namespace_set?
62
+ options[:mixed_content] = rule.mixed_content
63
+
64
+ options[:mapper_class] = attribute&.type if attribute
65
+
66
+ options
67
+ end
68
+
69
+ def parse_element(element)
70
+ result = Lutaml::Model::MappingHash.new
71
+ result.item_order = element.order
72
+
73
+ element.children.each_with_object(result) do |child, hash|
74
+ value = child.text? ? child.text : parse_element(child)
75
+
76
+ if hash[child.unprefixed_name]
77
+ hash[child.unprefixed_name] =
78
+ [hash[child.unprefixed_name], value].flatten
79
+ else
80
+ hash[child.unprefixed_name] = value
81
+ end
82
+ end
83
+
84
+ element.attributes.each_value do |attr|
85
+ result[attr.unprefixed_name] = attr.value
86
+ end
87
+
88
+ result
89
+ end
90
+
91
+ def build_element(xml, element, options = {})
92
+ if ordered?(element, options)
93
+ build_ordered_element(xml, element, options)
94
+ else
95
+ build_unordered_element(xml, element, options)
96
+ end
97
+ end
98
+
99
+ def ordered?(element, options = {})
100
+ return false unless element.respond_to?(:element_order)
101
+ return element.ordered? if element.respond_to?(:ordered?)
102
+ return options[:mixed_content] if options.key?(:mixed_content)
103
+
104
+ mapper_class = options[:mapper_class]
105
+ mapper_class ? mapper_class.mappings_for(:xml).mixed_content? : false
106
+ end
107
+
108
+ def build_namespace_attributes(klass, processed = {})
109
+ xml_mappings = klass.mappings_for(:xml)
110
+ attributes = klass.attributes
111
+
112
+ attrs = {}
113
+
114
+ if xml_mappings.namespace_prefix
115
+ attrs["xmlns:#{xml_mappings.namespace_prefix}"] =
116
+ xml_mappings.namespace_uri
117
+ end
118
+
119
+ xml_mappings.mappings.each do |mapping_rule|
120
+ processed[klass] ||= {}
121
+
122
+ next if processed[klass][mapping_rule.name]
123
+
124
+ processed[klass][mapping_rule.name] = true
125
+
126
+ type = if mapping_rule.delegate
127
+ attributes[mapping_rule.delegate].type.attributes[mapping_rule.to].type
128
+ else
129
+ attributes[mapping_rule.to].type
130
+ end
131
+
132
+ if type <= Lutaml::Model::Serialize
133
+ attrs = attrs.merge(build_namespace_attributes(type, processed))
134
+ end
135
+
136
+ if mapping_rule.namespace
137
+ attrs["xmlns:#{mapping_rule.prefix}"] = mapping_rule.namespace
138
+ end
139
+ end
140
+
141
+ attrs
142
+ end
143
+
144
+ def build_attributes(element, xml_mapping)
145
+ attrs = namespace_attributes(xml_mapping)
146
+
147
+ xml_mapping.attributes.each_with_object(attrs) do |mapping_rule, hash|
148
+ if mapping_rule.namespace
149
+ hash["xmlns:#{mapping_rule.prefix}"] = mapping_rule.namespace
150
+ end
151
+
152
+ hash[mapping_rule.prefixed_name] = element.send(mapping_rule.to)
153
+ end
154
+
155
+ xml_mapping.elements.each_with_object(attrs) do |mapping_rule, hash|
156
+ if mapping_rule.namespace
157
+ hash["xmlns:#{mapping_rule.prefix}"] = mapping_rule.namespace
158
+ end
159
+ end
160
+ end
161
+
162
+ def attribute_definition_for(element, rule, mapper_class: nil)
163
+ klass = mapper_class || element.class
164
+ return klass.attributes[rule.to] unless rule.delegate
165
+
166
+ element.send(rule.delegate).class.attributes[rule.to]
167
+ end
168
+
169
+ def attribute_value_for(element, rule)
170
+ return element.send(rule.to) unless rule.delegate
171
+
172
+ element.send(rule.delegate).send(rule.to)
173
+ end
174
+
175
+ def namespace_attributes(xml_mapping)
176
+ return {} unless xml_mapping.namespace_uri
177
+
178
+ key = ["xmlns", xml_mapping.namespace_prefix].compact.join(":")
179
+ { key => xml_mapping.namespace_uri }
180
+ end
181
+ end
182
+ end
183
+ end
184
+ end
@@ -0,0 +1,94 @@
1
+ require_relative "xml_attribute"
2
+
3
+ module Lutaml
4
+ module Model
5
+ module XmlAdapter
6
+ class XmlElement
7
+ attr_reader :attributes,
8
+ :children,
9
+ :text,
10
+ :namespace_prefix,
11
+ :parent_document
12
+
13
+ def initialize(
14
+ name,
15
+ attributes = {},
16
+ children = [],
17
+ text = nil,
18
+ parent_document: nil,
19
+ namespace_prefix: nil
20
+ )
21
+ @name = extract_name(name)
22
+ @namespace_prefix = namespace_prefix || extract_namespace_prefix(name)
23
+ @attributes = attributes # .map { |k, v| XmlAttribute.new(k, v) }
24
+ @children = children
25
+ @text = text
26
+ @parent_document = parent_document
27
+ end
28
+
29
+ def name
30
+ if namespace_prefix
31
+ "#{namespace_prefix}:#{@name}"
32
+ else
33
+ @name
34
+ end
35
+ end
36
+
37
+ def unprefixed_name
38
+ @name
39
+ end
40
+
41
+ def document
42
+ XmlDocument.new(self)
43
+ end
44
+
45
+ def namespaces
46
+ @namespaces || @parent_document&.namespaces || {}
47
+ end
48
+
49
+ def own_namespaces
50
+ @namespaces || {}
51
+ end
52
+
53
+ def namespace
54
+ return default_namespace unless namespace_prefix
55
+
56
+ namespaces[namespace_prefix]
57
+ end
58
+
59
+ def attribute_is_namespace?(name)
60
+ name.to_s.start_with?("xmlns")
61
+ end
62
+
63
+ def add_namespace(namespace)
64
+ @namespaces ||= {}
65
+ @namespaces[namespace.prefix] = namespace
66
+ end
67
+
68
+ def default_namespace
69
+ namespaces[nil] || @parent_document&.namespaces&.dig(nil)
70
+ end
71
+
72
+ def extract_name(name)
73
+ n = name.to_s.split(":")
74
+ return name if n.length <= 1
75
+
76
+ n[1..].join(":")
77
+ end
78
+
79
+ def extract_namespace_prefix(name)
80
+ n = name.to_s.split(":")
81
+ return if n.length <= 1
82
+
83
+ n.first
84
+ end
85
+
86
+ def order
87
+ children.each_with_object([]) do |child, arr|
88
+ arr << child.unprefixed_name
89
+ end
90
+ end
91
+ end
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,49 @@
1
+ # frozen_striing_literal: true
2
+
3
+ module Lutaml
4
+ module Model
5
+ module XmlAdapter
6
+ class XmlNamespace
7
+ # Return name
8
+ #
9
+ # @return [String]
10
+ #
11
+ # @api private
12
+ attr_accessor :uri
13
+
14
+ # Return prefix
15
+ #
16
+ # @return [String]
17
+ #
18
+ # @api private
19
+ attr_accessor :prefix
20
+
21
+ # Initialize instance
22
+ #
23
+ # @param [String, nil] name
24
+ # @param [String, nil] prefix
25
+ #
26
+ # @api private
27
+ def initialize(uri = nil, prefix = nil)
28
+ @uri = uri
29
+ @prefix = normalize_prefix(prefix)
30
+ end
31
+
32
+ def normalize_prefix(prefix)
33
+ normalized_prefix = prefix.to_s.gsub(/xmlns:?/, "")
34
+ return if normalized_prefix.empty?
35
+
36
+ normalized_prefix
37
+ end
38
+
39
+ def attr_name
40
+ if prefix && !prefix.empty?
41
+ "xmlns:#{prefix}"
42
+ else
43
+ "xmlns"
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
@@ -1,273 +1,7 @@
1
- # lib/lutaml/model/xml_adapter.rb
2
-
3
- require_relative "xml_namespace"
4
- require_relative "mapping_hash"
5
-
6
1
  module Lutaml
7
2
  module Model
8
3
  module XmlAdapter
9
4
  XML_NAMESPACE_URI = "http://www.w3.org/XML/1998/namespace".freeze
10
-
11
- class Document
12
- attr_reader :root
13
-
14
- def initialize(root)
15
- @root = root
16
- end
17
-
18
- def self.parse(xml)
19
- raise NotImplementedError, "Subclasses must implement `parse`."
20
- end
21
-
22
- def children
23
- @root.children
24
- end
25
-
26
- def declaration(options)
27
- version = "1.0"
28
- version = options[:declaration] if options[:declaration].is_a?(String)
29
-
30
- encoding = options[:encoding] ? "UTF-8" : nil
31
- encoding = options[:encoding] if options[:encoding].is_a?(String)
32
-
33
- declaration = "<?xml version=\"#{version}\""
34
- declaration += " encoding=\"#{encoding}\"" if encoding
35
- declaration += "?>\n"
36
- declaration
37
- end
38
-
39
- def to_h
40
- parse_element(@root)
41
- end
42
-
43
- def order
44
- @root.order
45
- end
46
-
47
- def handle_nested_elements(builder, value, rule = nil)
48
- options = {}
49
-
50
- if rule&.namespace_set?
51
- options[:namespace_prefix] = rule.prefix
52
- end
53
-
54
- case value
55
- when Array
56
- value.each { |val| build_element(builder, val, options) }
57
- else
58
- build_element(builder, value, options)
59
- end
60
- end
61
-
62
- def parse_element(element)
63
- result = Lutaml::Model::MappingHash.new
64
- result.item_order = element.order
65
-
66
- element.children.each_with_object(result) do |child, hash|
67
- value = child.text? ? child.text : parse_element(child)
68
-
69
- if hash[child.unprefixed_name]
70
- hash[child.unprefixed_name] =
71
- [hash[child.unprefixed_name], value].flatten
72
- else
73
- hash[child.unprefixed_name] = value
74
- end
75
- end
76
-
77
- element.attributes.each_value do |attr|
78
- result[attr.unprefixed_name] = attr.value
79
- end
80
-
81
- result
82
- end
83
-
84
- def build_element(xml, element, _options = {})
85
- if element.ordered?
86
- build_ordered_element(xml, element, _options)
87
- else
88
- build_unordered_element(xml, element, _options)
89
- end
90
- end
91
-
92
- def build_namespace_attributes(klass, processed = {})
93
- xml_mappings = klass.mappings_for(:xml)
94
- attributes = klass.attributes
95
-
96
- attrs = {}
97
-
98
- if xml_mappings.namespace_prefix
99
- attrs["xmlns:#{xml_mappings.namespace_prefix}"] =
100
- xml_mappings.namespace_uri
101
- end
102
-
103
- xml_mappings.mappings.each do |mapping_rule|
104
- processed[klass] ||= {}
105
-
106
- next if processed[klass][mapping_rule.name]
107
-
108
- processed[klass][mapping_rule.name] = true
109
-
110
- type = if mapping_rule.delegate
111
- attributes[mapping_rule.delegate].type.attributes[mapping_rule.to].type
112
- else
113
- attributes[mapping_rule.to].type
114
- end
115
-
116
- if type <= Lutaml::Model::Serialize
117
- attrs = attrs.merge(build_namespace_attributes(type, processed))
118
- end
119
-
120
- if mapping_rule.namespace
121
- attrs["xmlns:#{mapping_rule.prefix}"] = mapping_rule.namespace
122
- end
123
- end
124
-
125
- attrs
126
- end
127
-
128
- def build_attributes(element, xml_mapping)
129
- attrs = namespace_attributes(xml_mapping)
130
-
131
- xml_mapping.attributes.each_with_object(attrs) do |mapping_rule, hash|
132
- if mapping_rule.namespace
133
- hash["xmlns:#{mapping_rule.prefix}"] = mapping_rule.namespace
134
- end
135
-
136
- hash[mapping_rule.prefixed_name] = element.send(mapping_rule.to)
137
- end
138
-
139
- xml_mapping.elements.each_with_object(attrs) do |mapping_rule, hash|
140
- if mapping_rule.namespace
141
- hash["xmlns:#{mapping_rule.prefix}"] = mapping_rule.namespace
142
- end
143
- end
144
- end
145
-
146
- def attribute_definition_for(element, rule)
147
- return element.class.attributes[rule.to] unless rule.delegate
148
-
149
- element.send(rule.delegate).class.attributes[rule.to]
150
- end
151
-
152
- def attribute_value_for(element, rule)
153
- return element.send(rule.to) unless rule.delegate
154
-
155
- element.send(rule.delegate).send(rule.to)
156
- end
157
-
158
- def namespace_attributes(xml_mapping)
159
- return {} unless xml_mapping.namespace_uri
160
-
161
- key = ["xmlns", xml_mapping.namespace_prefix].compact.join(":")
162
- { key => xml_mapping.namespace_uri }
163
- end
164
- end
165
-
166
- class Element
167
- attr_reader :attributes,
168
- :children,
169
- :text,
170
- :namespace_prefix,
171
- :parent_document
172
-
173
- def initialize(
174
- name,
175
- attributes = {},
176
- children = [],
177
- text = nil,
178
- parent_document: nil,
179
- namespace_prefix: nil
180
- )
181
- @name = extract_name(name)
182
- @namespace_prefix = namespace_prefix || extract_namespace_prefix(name)
183
- @attributes = attributes # .map { |k, v| Attribute.new(k, v) }
184
- @children = children
185
- @text = text
186
- @parent_document = parent_document
187
- end
188
-
189
- def name
190
- if namespace_prefix
191
- "#{namespace_prefix}:#{@name}"
192
- else
193
- @name
194
- end
195
- end
196
-
197
- def unprefixed_name
198
- @name
199
- end
200
-
201
- def document
202
- Document.new(self)
203
- end
204
-
205
- def namespaces
206
- @namespaces || @parent_document&.namespaces || {}
207
- end
208
-
209
- def own_namespaces
210
- @namespaces || {}
211
- end
212
-
213
- def namespace
214
- return default_namespace unless namespace_prefix
215
-
216
- namespaces[namespace_prefix]
217
- end
218
-
219
- def attribute_is_namespace?(name)
220
- name.to_s.start_with?("xmlns")
221
- end
222
-
223
- def add_namespace(namespace)
224
- @namespaces ||= {}
225
- @namespaces[namespace.prefix] = namespace
226
- end
227
-
228
- def default_namespace
229
- namespaces[nil] || @parent_document&.namespaces&.dig(nil)
230
- end
231
-
232
- def extract_name(name)
233
- n = name.to_s.split(":")
234
- return name if n.length <= 1
235
-
236
- n[1..].join(":")
237
- end
238
-
239
- def extract_namespace_prefix(name)
240
- n = name.to_s.split(":")
241
- return if n.length <= 1
242
-
243
- n.first
244
- end
245
-
246
- def order
247
- children.each_with_object([]) do |child, arr|
248
- arr << child.unprefixed_name
249
- end
250
- end
251
- end
252
-
253
- class Attribute
254
- attr_reader :name, :value, :namespace, :namespace_prefix
255
-
256
- def initialize(name, value, namespace: nil, namespace_prefix: nil)
257
- @name = name
258
- @value = value
259
- @namespace = namespace
260
- @namespace_prefix = namespace_prefix
261
- end
262
-
263
- def unprefixed_name
264
- if namespace_prefix
265
- name.split(":").last
266
- else
267
- name
268
- end
269
- end
270
- end
271
5
  end
272
6
  end
273
7
  end
@@ -1,4 +1,3 @@
1
- # lib/lutaml/model/xml_mapping.rb
2
1
  require_relative "xml_mapping_rule"
3
2
 
4
3
  module Lutaml
@@ -82,6 +81,7 @@ module Lutaml
82
81
  namespace_set: namespace_set != false,
83
82
  )
84
83
  end
84
+
85
85
  # rubocop:enable Metrics/ParameterLists
86
86
 
87
87
  def map_content(
@@ -1,4 +1,3 @@
1
- # lib/lutaml/model/xml_mapping_rule.rb
2
1
  require_relative "mapping_rule"
3
2
 
4
3
  module Lutaml
@@ -24,12 +23,12 @@ module Lutaml
24
23
  with: with,
25
24
  delegate: delegate,
26
25
  mixed_content: mixed_content,
27
- namespace_set: namespace_set
26
+ namespace_set: namespace_set,
28
27
  )
29
28
 
30
29
  @namespace = if namespace.to_s == "inherit"
31
- # we are using inherit_namespace in xml builder by
32
- # default so no need to do anything here.
30
+ # we are using inherit_namespace in xml builder by
31
+ # default so no need to do anything here.
33
32
  else
34
33
  namespace
35
34
  end
@@ -0,0 +1,34 @@
1
+ require "yaml"
2
+ require_relative "yaml_document"
3
+
4
+ module Lutaml
5
+ module Model
6
+ module YamlAdapter
7
+ class StandardYamlAdapter < YamlDocument
8
+ def self.parse(yaml)
9
+ YAML.safe_load(
10
+ yaml,
11
+ permitted_classes: [Date, Time, DateTime, Symbol,
12
+ BigDecimal, Hash, Array],
13
+ )
14
+ end
15
+
16
+ def to_yaml(options = {})
17
+ YAML.dump(@attributes, options)
18
+ end
19
+
20
+ # TODO: Is this really needed?
21
+ def self.to_yaml(attributes, *args)
22
+ new(attributes).to_yaml(*args)
23
+ end
24
+
25
+ # TODO: Is this really needed?
26
+ def self.from_yaml(yaml, klass)
27
+ data = parse(yaml)
28
+ mapped_attrs = klass.send(:apply_mappings, data, :yaml)
29
+ klass.new(mapped_attrs)
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "yaml_object"
4
+
5
+ module Lutaml
6
+ module Model
7
+ module YamlAdapter
8
+ # Base class for YAML documents
9
+ class YamlDocument < YamlObject
10
+ def self.parse(yaml)
11
+ raise NotImplementedError, "Subclasses must implement `parse`."
12
+ end
13
+
14
+ def to_yaml(*args)
15
+ raise NotImplementedError, "Subclasses must implement `to_yaml`."
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end