lutaml-model 0.6.7 → 0.7.2

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 (148) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/dependent-repos-todo.json +7 -0
  3. data/.github/workflows/dependent-repos.json +17 -9
  4. data/.rubocop.yml +1 -1
  5. data/.rubocop_todo.yml +51 -65
  6. data/Gemfile +4 -1
  7. data/README.adoc +5083 -2612
  8. data/RELEASE_NOTES.adoc +346 -0
  9. data/docs/custom_adapters.adoc +144 -0
  10. data/lib/lutaml/model/attribute.rb +101 -16
  11. data/lib/lutaml/model/choice.rb +7 -0
  12. data/lib/lutaml/model/comparable_model.rb +48 -9
  13. data/lib/lutaml/model/config.rb +48 -42
  14. data/lib/lutaml/model/error/collection_count_out_of_range_error.rb +1 -1
  15. data/lib/lutaml/model/error/polymorphic_error.rb +14 -0
  16. data/lib/lutaml/model/error.rb +1 -0
  17. data/lib/lutaml/model/format_registry.rb +41 -0
  18. data/lib/lutaml/model/hash/document.rb +11 -0
  19. data/lib/lutaml/model/hash/mapping.rb +19 -0
  20. data/lib/lutaml/model/hash/mapping_rule.rb +9 -0
  21. data/lib/lutaml/model/hash/standard_adapter.rb +17 -0
  22. data/lib/lutaml/model/hash/transform.rb +8 -0
  23. data/lib/lutaml/model/hash.rb +21 -0
  24. data/lib/lutaml/model/json/document.rb +11 -0
  25. data/lib/lutaml/model/json/mapping.rb +19 -0
  26. data/lib/lutaml/model/json/mapping_rule.rb +9 -0
  27. data/lib/lutaml/model/{json_adapter → json}/multi_json_adapter.rb +4 -5
  28. data/lib/lutaml/model/{json_adapter/standard_json_adapter.rb → json/standard_adapter.rb} +5 -3
  29. data/lib/lutaml/model/json/transform.rb +8 -0
  30. data/lib/lutaml/model/json.rb +21 -0
  31. data/lib/lutaml/model/key_value_document.rb +27 -0
  32. data/lib/lutaml/model/{key_value_mapping.rb → mapping/key_value_mapping.rb} +64 -16
  33. data/lib/lutaml/model/{key_value_mapping_rule.rb → mapping/key_value_mapping_rule.rb} +18 -2
  34. data/lib/lutaml/model/mapping/mapping.rb +13 -0
  35. data/lib/lutaml/model/mapping/mapping_rule.rb +300 -0
  36. data/lib/lutaml/model/schema/xml_compiler.rb +15 -15
  37. data/lib/lutaml/model/sequence.rb +2 -2
  38. data/lib/lutaml/model/serialization_adapter.rb +22 -0
  39. data/lib/lutaml/model/serialize.rb +219 -444
  40. data/lib/lutaml/model/services/logger.rb +54 -0
  41. data/lib/lutaml/model/services/transformer.rb +48 -0
  42. data/lib/lutaml/model/services.rb +2 -0
  43. data/lib/lutaml/model/toml/document.rb +11 -0
  44. data/lib/lutaml/model/toml/mapping.rb +27 -0
  45. data/lib/lutaml/model/toml/mapping_rule.rb +9 -0
  46. data/lib/lutaml/model/{toml_adapter → toml}/toml_rb_adapter.rb +3 -3
  47. data/lib/lutaml/model/toml/tomlib_adapter.rb +19 -0
  48. data/lib/lutaml/model/toml/transform.rb +8 -0
  49. data/lib/lutaml/model/toml.rb +30 -0
  50. data/lib/lutaml/model/transform/key_value_transform.rb +291 -0
  51. data/lib/lutaml/model/transform/xml_transform.rb +239 -0
  52. data/lib/lutaml/model/transform.rb +78 -0
  53. data/lib/lutaml/model/type/date.rb +1 -1
  54. data/lib/lutaml/model/type/date_time.rb +2 -2
  55. data/lib/lutaml/model/type/hash.rb +1 -1
  56. data/lib/lutaml/model/type/time.rb +2 -2
  57. data/lib/lutaml/model/type/time_without_date.rb +2 -2
  58. data/lib/lutaml/model/type/value.rb +6 -9
  59. data/lib/lutaml/model/uninitialized_class.rb +64 -0
  60. data/lib/lutaml/model/utils.rb +44 -0
  61. data/lib/lutaml/model/validation.rb +1 -0
  62. data/lib/lutaml/model/version.rb +1 -1
  63. data/lib/lutaml/model/{xml_adapter → xml}/builder/nokogiri.rb +2 -2
  64. data/lib/lutaml/model/{xml_adapter → xml}/builder/oga.rb +10 -10
  65. data/lib/lutaml/model/{xml_adapter → xml}/builder/ox.rb +1 -1
  66. data/lib/lutaml/model/{xml_adapter/xml_document.rb → xml/document.rb} +41 -21
  67. data/lib/lutaml/model/xml/element.rb +32 -0
  68. data/lib/lutaml/model/xml/mapping.rb +410 -0
  69. data/lib/lutaml/model/xml/mapping_rule.rb +141 -0
  70. data/lib/lutaml/model/xml/nokogiri_adapter.rb +232 -0
  71. data/lib/lutaml/model/{xml_adapter → xml}/oga/document.rb +1 -1
  72. data/lib/lutaml/model/{xml_adapter → xml}/oga/element.rb +3 -1
  73. data/lib/lutaml/model/xml/oga_adapter.rb +171 -0
  74. data/lib/lutaml/model/xml/ox_adapter.rb +215 -0
  75. data/lib/lutaml/model/xml/transform.rb +8 -0
  76. data/lib/lutaml/model/{xml_adapter → xml}/xml_attribute.rb +1 -1
  77. data/lib/lutaml/model/{xml_adapter → xml}/xml_element.rb +23 -10
  78. data/lib/lutaml/model/{xml_adapter → xml}/xml_namespace.rb +1 -1
  79. data/lib/lutaml/model/xml.rb +31 -0
  80. data/lib/lutaml/model/xml_adapter/element.rb +11 -25
  81. data/lib/lutaml/model/xml_adapter/nokogiri_adapter.rb +6 -223
  82. data/lib/lutaml/model/xml_adapter/oga_adapter.rb +13 -163
  83. data/lib/lutaml/model/xml_adapter/ox_adapter.rb +10 -207
  84. data/lib/lutaml/model/yaml/document.rb +10 -0
  85. data/lib/lutaml/model/yaml/mapping.rb +19 -0
  86. data/lib/lutaml/model/yaml/mapping_rule.rb +9 -0
  87. data/lib/lutaml/model/{yaml_adapter/standard_yaml_adapter.rb → yaml/standard_adapter.rb} +4 -3
  88. data/lib/lutaml/model/yaml/transform.rb +8 -0
  89. data/lib/lutaml/model/yaml.rb +21 -0
  90. data/lib/lutaml/model.rb +40 -4
  91. data/lutaml-model.gemspec +0 -4
  92. data/spec/benchmarks/xml_parsing_benchmark_spec.rb +7 -7
  93. data/spec/fixtures/person.rb +5 -5
  94. data/spec/lutaml/model/attribute_spec.rb +37 -1
  95. data/spec/lutaml/model/cdata_spec.rb +9 -9
  96. data/spec/lutaml/model/collection_spec.rb +50 -2
  97. data/spec/lutaml/model/comparable_model_spec.rb +92 -27
  98. data/spec/lutaml/model/custom_bibtex_adapter_spec.rb +598 -0
  99. data/spec/lutaml/model/custom_vobject_adapter_spec.rb +1226 -0
  100. data/spec/lutaml/model/defaults_spec.rb +1 -1
  101. data/spec/lutaml/model/enum_spec.rb +1 -1
  102. data/spec/lutaml/model/group_spec.rb +333 -20
  103. data/spec/lutaml/model/hash/adapter_spec.rb +255 -0
  104. data/spec/lutaml/model/json_adapter_spec.rb +6 -6
  105. data/spec/lutaml/model/key_value_mapping_spec.rb +65 -3
  106. data/spec/lutaml/model/mixed_content_spec.rb +24 -24
  107. data/spec/lutaml/model/multiple_mapping_spec.rb +5 -5
  108. data/spec/lutaml/model/ordered_content_spec.rb +6 -6
  109. data/spec/lutaml/model/polymorphic_spec.rb +526 -0
  110. data/spec/lutaml/model/render_empty_spec.rb +194 -0
  111. data/spec/lutaml/model/render_nil_spec.rb +206 -22
  112. data/spec/lutaml/model/root_mappings_spec.rb +3 -3
  113. data/spec/lutaml/model/schema/xml_compiler_spec.rb +6 -6
  114. data/spec/lutaml/model/serializable_spec.rb +179 -103
  115. data/spec/lutaml/model/simple_model_spec.rb +9 -9
  116. data/spec/lutaml/model/toml_adapter_spec.rb +6 -6
  117. data/spec/lutaml/model/toml_spec.rb +51 -0
  118. data/spec/lutaml/model/transformation_spec.rb +72 -15
  119. data/spec/lutaml/model/uninitialized_class_spec.rb +96 -0
  120. data/spec/lutaml/model/value_map_spec.rb +240 -0
  121. data/spec/lutaml/model/xml/namespace/nested_with_explicit_namespace_spec.rb +85 -0
  122. data/spec/lutaml/model/xml/namespace_spec.rb +57 -0
  123. data/spec/lutaml/model/xml/xml_element_spec.rb +93 -0
  124. data/spec/lutaml/model/xml_adapter/nokogiri_adapter_spec.rb +2 -2
  125. data/spec/lutaml/model/xml_adapter/oga_adapter_spec.rb +2 -2
  126. data/spec/lutaml/model/xml_adapter/ox_adapter_spec.rb +2 -2
  127. data/spec/lutaml/model/xml_adapter/xml_namespace_spec.rb +6 -6
  128. data/spec/lutaml/model/xml_adapter_spec.rb +6 -6
  129. data/spec/lutaml/model/xml_mapping_rule_spec.rb +105 -5
  130. data/spec/lutaml/model/xml_mapping_spec.rb +70 -16
  131. data/spec/lutaml/model/xml_spec.rb +63 -0
  132. data/spec/lutaml/model/yaml_adapter_spec.rb +3 -5
  133. data/spec/sample_model_spec.rb +3 -3
  134. data/spec/spec_helper.rb +3 -3
  135. metadata +76 -59
  136. data/lib/lutaml/model/json_adapter/json_document.rb +0 -20
  137. data/lib/lutaml/model/json_adapter/json_object.rb +0 -28
  138. data/lib/lutaml/model/loggable.rb +0 -15
  139. data/lib/lutaml/model/mapping_rule.rb +0 -109
  140. data/lib/lutaml/model/toml_adapter/toml_document.rb +0 -20
  141. data/lib/lutaml/model/toml_adapter/toml_object.rb +0 -28
  142. data/lib/lutaml/model/toml_adapter/tomlib_adapter.rb +0 -20
  143. data/lib/lutaml/model/toml_adapter.rb +0 -6
  144. data/lib/lutaml/model/xml_mapping.rb +0 -307
  145. data/lib/lutaml/model/xml_mapping_rule.rb +0 -122
  146. data/lib/lutaml/model/yaml_adapter/yaml_document.rb +0 -20
  147. data/lib/lutaml/model/yaml_adapter/yaml_object.rb +0 -28
  148. data/lib/lutaml/model/yaml_adapter.rb +0 -8
@@ -1,12 +1,9 @@
1
- require_relative "yaml_adapter"
2
1
  require_relative "xml_adapter"
3
2
  require_relative "config"
4
3
  require_relative "type"
5
4
  require_relative "attribute"
6
- require_relative "mapping_rule"
7
5
  require_relative "mapping_hash"
8
- require_relative "xml_mapping"
9
- require_relative "key_value_mapping"
6
+ # require_relative "mapping"
10
7
  require_relative "json_adapter"
11
8
  require_relative "comparable_model"
12
9
  require_relative "schema_location"
@@ -15,6 +12,7 @@ require_relative "error"
15
12
  require_relative "choice"
16
13
  require_relative "sequence"
17
14
  require_relative "liquefiable"
15
+ require_relative "transform"
18
16
 
19
17
  module Lutaml
20
18
  module Model
@@ -94,22 +92,15 @@ module Lutaml
94
92
  end
95
93
  end
96
94
 
97
- # Define an attribute for the model
98
- def attribute(name, type, options = {})
99
- if type.is_a?(Hash)
100
- options[:method_name] = type[:method]
101
- type = nil
102
- end
103
-
104
- attr = Attribute.new(name, type, options)
105
- attributes[name] = attr
95
+ def define_attribute_methods(attr)
96
+ name = attr.name
106
97
 
107
98
  if attr.enum?
108
99
  add_enum_methods_to_model(
109
100
  model,
110
101
  name,
111
- options[:values],
112
- collection: options[:collection],
102
+ attr.options[:values],
103
+ collection: attr.options[:collection],
113
104
  )
114
105
  elsif attr.derived? && name != attr.method_name
115
106
  define_method(name) do
@@ -119,35 +110,75 @@ module Lutaml
119
110
  define_method(name) do
120
111
  instance_variable_get(:"@#{name}")
121
112
  end
122
-
123
113
  define_method(:"#{name}=") do |value|
124
114
  value_set_for(name)
125
115
  instance_variable_set(:"@#{name}", attr.cast_value(value))
126
116
  end
127
117
  end
118
+ end
119
+
120
+ # Define an attribute for the model
121
+ def attribute(name, type, options = {})
122
+ if type.is_a?(Hash)
123
+ options[:method_name] = type[:method]
124
+ type = nil
125
+ end
126
+
127
+ attr = Attribute.new(name, type, options)
128
+ attributes[name] = attr
129
+ define_attribute_methods(attr)
128
130
 
129
131
  attr
130
132
  end
131
133
 
132
134
  def root?
133
- mappings_for(:xml).root?
135
+ mappings_for(:xml)&.root?
136
+ end
137
+
138
+ def import_model_with_root_error(model)
139
+ return unless model.mappings.key?(:xml) && model.root?
140
+
141
+ raise Lutaml::Model::ImportModelWithRootError.new(model)
134
142
  end
135
143
 
136
144
  def import_model_attributes(model)
137
- raise Lutaml::Model::ImportModelWithRootError.new(model) if model.root?
145
+ model.attributes.each_value do |attr|
146
+ define_attribute_methods(attr)
147
+ end
138
148
 
139
- @attributes.merge!(model.attributes)
149
+ @choice_attributes.concat(Utils.deep_dup(model.choice_attributes))
150
+ @attributes.merge!(Utils.deep_dup(model.attributes))
140
151
  end
141
152
 
142
153
  def import_model_mappings(model)
143
- raise Lutaml::Model::ImportModelWithRootError.new(model) if model.root?
154
+ import_model_with_root_error(model)
155
+
156
+ Lutaml::Model::Config::AVAILABLE_FORMATS.each do |format|
157
+ next unless model.mappings.key?(format)
158
+
159
+ mapping = model.mappings_for(format)
160
+ mapping = Utils.deep_dup(mapping)
161
+
162
+ klass = ::Lutaml::Model::Config.mappings_class_for(format)
163
+ @mappings[format] ||= klass.new
144
164
 
145
- @mappings.merge!(model.mappings)
165
+ if format == :xml
166
+ @mappings[format].merge_mapping_attributes(mapping)
167
+ @mappings[format].merge_mapping_elements(mapping)
168
+ @mappings[format].merge_elements_sequence(mapping)
169
+ else
170
+ @mappings[format].mappings.concat(mapping.mappings)
171
+ end
172
+ end
146
173
  end
147
174
 
148
- def import_model(model)
149
- raise Lutaml::Model::ImportModelWithRootError.new(model) if model.root?
175
+ def handle_key_value_mappings(mapping, format)
176
+ @mappings[format] ||= KeyValueMapping.new
177
+ @mappings[format].mappings.concat(mapping.mappings)
178
+ end
150
179
 
180
+ def import_model(model)
181
+ import_model_with_root_error(model)
151
182
  import_model_attributes(model)
152
183
  import_model_mappings(model)
153
184
  end
@@ -213,7 +244,8 @@ module Lutaml
213
244
 
214
245
  def add_enum_setter_if_not_defined(klass, enum_name, _values, collection)
215
246
  Utils.add_method_if_not_defined(klass, "#{enum_name}=") do |value|
216
- value = [value] unless value.is_a?(Array)
247
+ value = [] if value.nil?
248
+ value = [value] if !value.is_a?(Array)
217
249
 
218
250
  value_set_for(enum_name)
219
251
 
@@ -231,140 +263,86 @@ module Lutaml
231
263
  attributes.select { |_, attr| attr.enum? }
232
264
  end
233
265
 
234
- Lutaml::Model::Config::AVAILABLE_FORMATS.each do |format|
235
- define_method(format) do |&block|
236
- klass = format == :xml ? XmlMapping : KeyValueMapping
237
- mappings[format] ||= klass.new
238
- mappings[format].instance_eval(&block)
266
+ def process_mapping(format, &block)
267
+ # klass = Lutaml::Model.const_get("#{format.to_s.capitalize}Mapping")
268
+ # mappings[format] ||= klass.new
269
+ # mappings[format].instance_eval(&block)
239
270
 
240
- if format == :xml && !mappings[format].root_element && !mappings[format].no_root?
241
- mappings[format].root(model.to_s)
242
- end
243
- end
271
+ # handle_root_assignment(mappings, format)
272
+
273
+ klass = ::Lutaml::Model::Config.mappings_class_for(format)
274
+ mappings[format] ||= klass.new
244
275
 
245
- define_method(:"from_#{format}") do |data, options = {}|
246
- adapter = Lutaml::Model::Config.send(:"#{format}_adapter")
276
+ mappings[format].instance_eval(&block)
247
277
 
248
- doc = adapter.parse(data, options)
249
- public_send(:"of_#{format}", doc, options)
278
+ if mappings[format].respond_to?(:finalize)
279
+ mappings[format].finalize(self)
250
280
  end
281
+ end
251
282
 
252
- define_method(:"of_#{format}") do |doc, options = {}|
253
- if doc.is_a?(Array)
254
- return doc.map { |item| send(:"of_#{format}", item) }
255
- end
283
+ def from(format, data, options = {})
284
+ return data if Utils.uninitialized?(data)
256
285
 
257
- if format == :xml
258
- raise Lutaml::Model::NoRootMappingError.new(self) unless root?
286
+ adapter = Lutaml::Model::Config.adapter_for(format)
259
287
 
260
- options[:encoding] = doc.encoding
261
- apply_mappings(doc, format, options)
262
- else
263
- apply_mappings(doc.to_h, format)
264
- end
288
+ doc = adapter.parse(data, options)
289
+ public_send(:"of_#{format}", doc, options)
290
+ end
291
+
292
+ def of(format, doc, options = {})
293
+ if doc.is_a?(Array)
294
+ return doc.map { |item| send(:"of_#{format}", item) }
265
295
  end
266
296
 
267
- define_method(:"to_#{format}") do |instance|
268
- value = public_send(:"as_#{format}", instance)
269
- adapter = Lutaml::Model::Config.public_send(:"#{format}_adapter")
297
+ if format == :xml
298
+ raise Lutaml::Model::NoRootMappingError.new(self) unless root?
270
299
 
271
- if format == :xml
272
- xml_options = { mapper_class: self }
273
- adapter.new(value).public_send(:"to_#{format}", xml_options)
274
- else
275
- adapter.new(value).public_send(:"to_#{format}")
276
- end
300
+ options[:encoding] = doc.encoding
277
301
  end
278
302
 
279
- define_method(:"as_#{format}") do |instance, options = {}|
280
- if instance.is_a?(Array)
281
- return instance.map { |item| public_send(:"as_#{format}", item) }
282
- end
303
+ transformer = Lutaml::Model::Config.transformer_for(format)
304
+ transformer.data_to_model(self, doc, format, options)
305
+ end
283
306
 
284
- unless instance.is_a?(model)
285
- msg = "argument is a '#{instance.class}' but should be a '#{model}'"
286
- raise Lutaml::Model::IncorrectModelError, msg
287
- end
307
+ def to(format, instance, options = {})
308
+ value = public_send(:"as_#{format}", instance, options)
309
+ adapter = Lutaml::Model::Config.adapter_for(format)
288
310
 
289
- return instance if format == :xml
311
+ options[:mapper_class] = self if format == :xml
290
312
 
291
- hash_representation(instance, format, options)
292
- end
313
+ adapter.new(value).public_send(:"to_#{format}", options)
293
314
  end
294
315
 
295
316
  def as(format, instance, options = {})
296
- public_send(:"as_#{format}", instance, options)
317
+ if instance.is_a?(Array)
318
+ return instance.map { |item| public_send(:"as_#{format}", item) }
319
+ end
320
+
321
+ unless instance.is_a?(model)
322
+ msg = "argument is a '#{instance.class}' but should be a '#{model}'"
323
+ raise Lutaml::Model::IncorrectModelError, msg
324
+ end
325
+
326
+ transformer = Lutaml::Model::Config.transformer_for(format)
327
+ transformer.model_to_data(self, instance, format, options)
297
328
  end
298
329
 
299
330
  def key_value(&block)
300
331
  Lutaml::Model::Config::KEY_VALUE_FORMATS.each do |format|
301
- mappings[format] ||= KeyValueMapping.new
332
+ mappings[format] ||= KeyValueMapping.new(format)
302
333
  mappings[format].instance_eval(&block)
303
334
  end
304
335
  end
305
336
 
306
- def hash_representation(instance, format, options = {})
307
- only = options[:only]
308
- except = options[:except]
309
- mappings = mappings_for(format).mappings
310
-
311
- mappings.each_with_object({}) do |rule, hash|
312
- name = rule.to
313
- next if except&.include?(name) || (only && !only.include?(name))
314
- next if !rule.custom_methods[:to] && (!rule.render_default? && instance.using_default?(rule.to))
315
-
316
- next handle_delegate(instance, rule, hash, format) if rule.delegate
317
-
318
- if rule.custom_methods[:to]
319
- next instance.send(rule.custom_methods[:to], instance, hash)
320
- end
321
-
322
- value = instance.send(name)
323
-
324
- if rule.raw_mapping?
325
- adapter = Lutaml::Model::Config.send(:"#{format}_adapter")
326
- return adapter.parse(value, options)
327
- end
328
-
329
- attribute = attributes[name]
330
-
331
- if export_method = rule.transform[:export] || attribute.transform_export_method
332
- value = export_method.call(value)
333
- end
334
-
335
- next hash.merge!(generate_hash_from_child_mappings(attribute, value, format, rule.root_mappings)) if rule.root_mapping?
336
-
337
- value = if rule.child_mappings
338
- generate_hash_from_child_mappings(attribute, value, format, rule.child_mappings)
339
- else
340
- attribute.serialize(value, format, options)
341
- end
342
-
343
- next unless rule.render?(value)
344
-
345
- rule_from_name = rule.multiple_mappings? ? rule.from.first.to_s : rule.from.to_s
346
- hash[rule_from_name] = value
347
- end
348
- end
349
-
350
- def handle_delegate(instance, rule, hash, format)
351
- name = rule.to
352
- value = instance.send(rule.delegate).send(name)
353
- return if value.nil? && !rule.render_nil
354
-
355
- attribute = instance.send(rule.delegate).class.attributes[name]
356
- rule_from_name = rule.multiple_mappings? ? rule.from.first.to_s : rule.from.to_s
357
- hash[rule_from_name] = attribute.serialize(value, format)
358
- end
359
-
360
337
  def mappings_for(format)
361
338
  mappings[format] || default_mappings(format)
362
339
  end
363
340
 
364
341
  def default_mappings(format)
365
- klass = format == :xml ? XmlMapping : KeyValueMapping
342
+ klass = ::Lutaml::Model::Config.mappings_class_for(format)
343
+ mappings = klass.new
366
344
 
367
- klass.new.tap do |mapping|
345
+ mappings.tap do |mapping|
368
346
  attributes&.each_key do |name|
369
347
  mapping.map_element(
370
348
  name.to_s,
@@ -376,288 +354,55 @@ module Lutaml
376
354
  end
377
355
  end
378
356
 
379
- def translate_mappings(hash, child_mappings, attr, format)
380
- return hash unless child_mappings
381
-
382
- hash.map do |key, value|
383
- child_hash = child_mappings.to_h do |attr_name, path|
384
- attr_value = if path == :key
385
- key
386
- elsif path == :value
387
- value
388
- else
389
- path = [path] unless path.is_a?(Array)
390
- value.dig(*path.map(&:to_s))
391
- end
392
-
393
- attr_rule = attr.type.mappings_for(format).find_by_to(attr_name)
394
- [attr_rule.from.to_s, attr_value]
395
- end
396
-
397
- if child_mappings.values == [:key] && hash.values.all?(Hash)
398
- child_hash.merge!(value)
399
- end
400
-
401
- attr.type.apply_hash_mapping(
402
- child_hash,
403
- attr.type.model.new,
404
- format,
405
- { mappings: attr.type.mappings_for(format).mappings },
406
- )
407
- end
408
- end
409
-
410
- def generate_hash_from_child_mappings(attr, value, format, child_mappings)
411
- return value unless child_mappings
412
-
413
- hash = {}
414
-
415
- if child_mappings.values == [:key]
416
- klass = value.first.class
417
- mappings = klass.mappings_for(format)
418
-
419
- klass.attributes.each_key do |name|
420
- next if child_mappings.key?(name.to_sym) || child_mappings.key?(name.to_s)
421
-
422
- child_mappings[name.to_sym] = mappings.find_by_to(name)&.name.to_s || name.to_s
423
- end
424
- end
425
-
426
- value.each do |child_obj|
427
- map_key = nil
428
- map_value = {}
429
- mapping_rules = attr.type.mappings_for(format)
430
-
431
- child_mappings.each do |attr_name, path|
432
- mapping_rule = mapping_rules.find_by_to(attr_name)
433
-
434
- attr_value = child_obj.send(attr_name)
435
-
436
- attr_value = if attr_value.is_a?(Lutaml::Model::Serialize)
437
- attr_value.to_yaml_hash
438
- elsif attr_value.is_a?(Array) && attr_value.first.is_a?(Lutaml::Model::Serialize)
439
- attr_value.map(&:to_yaml_hash)
440
- else
441
- attr_value
442
- end
443
-
444
- next unless mapping_rule&.render?(attr_value)
445
-
446
- if path == :key
447
- map_key = attr_value
448
- elsif path == :value
449
- map_value = attr_value
450
- else
451
- path = [path] unless path.is_a?(Array)
452
- path[0...-1].inject(map_value) do |acc, k|
453
- acc[k.to_s] ||= {}
454
- end.public_send(:[]=, path.last.to_s, attr_value)
455
- end
456
- end
457
-
458
- map_value = nil if map_value.empty?
459
- hash[map_key] = map_value
460
- end
461
-
462
- hash
463
- end
464
-
465
- def valid_rule?(rule)
466
- attribute = attribute_for_rule(rule)
467
-
468
- !!attribute || rule.custom_methods[:from]
469
- end
470
-
471
- def attribute_for_rule(rule)
472
- return attributes[rule.to] unless rule.delegate
473
-
474
- attributes[rule.delegate].type.attributes[rule.to]
475
- end
476
-
477
- def attribute_for_child(child_name, format)
478
- mapping_rule = mappings_for(format).find_by_name(child_name)
479
-
480
- attribute_for_rule(mapping_rule) if mapping_rule
481
- end
482
-
483
357
  def apply_mappings(doc, format, options = {})
484
358
  instance = options[:instance] || model.new
485
359
  return instance if Utils.blank?(doc)
486
360
 
487
- options[:mappings] = mappings_for(format).mappings
488
-
489
- return apply_xml_mapping(doc, instance, options) if format == :xml
490
-
491
- apply_hash_mapping(doc, instance, format, options)
492
- end
493
-
494
- def apply_xml_mapping(doc, instance, options = {})
495
- options = Utils.deep_dup(options)
496
- instance.encoding = options[:encoding]
497
- return instance unless doc
498
-
499
- if options[:default_namespace].nil?
500
- options[:default_namespace] = mappings_for(:xml)&.namespace_uri
501
- end
502
- mappings = options[:mappings] || mappings_for(:xml).mappings
503
-
504
- raise Lutaml::Model::CollectionTrueMissingError(self, option[:caller_class]) if doc.is_a?(Array)
361
+ mappings = mappings_for(format)
505
362
 
506
- doc_order = doc.root.order
507
- if instance.respond_to?(:ordered=)
508
- instance.element_order = doc_order
509
- instance.ordered = mappings_for(:xml).ordered? || options[:ordered]
510
- instance.mixed = mappings_for(:xml).mixed_content? || options[:mixed_content]
363
+ if mappings.polymorphic_mapping
364
+ return resolve_polymorphic(doc, format, mappings, instance, options)
511
365
  end
512
366
 
513
- schema_location = doc.attributes.values.find do |a|
514
- a.unprefixed_name == "schemaLocation"
515
- end
516
-
517
- if !schema_location.nil?
518
- instance.schema_location = Lutaml::Model::SchemaLocation.new(
519
- schema_location: schema_location.value,
520
- prefix: schema_location.namespace_prefix,
521
- namespace: schema_location.namespace,
522
- )
523
- end
524
-
525
- defaults_used = []
526
- validate_sequence!(doc_order)
527
-
528
- mappings.each do |rule|
529
- raise "Attribute '#{rule.to}' not found in #{self}" unless valid_rule?(rule)
530
-
531
- attr = attribute_for_rule(rule)
532
- next if attr&.derived?
533
-
534
- value = if rule.raw_mapping?
535
- doc.root.inner_xml
536
- elsif rule.content_mapping?
537
- rule.cdata ? doc.cdata : doc.text
538
- elsif val = value_for_rule(doc, rule, options, instance)
539
- val
540
- elsif instance.using_default?(rule.to) || rule.render_default
541
- defaults_used << rule.to
542
- attr&.default || rule.to_value_for(instance)
543
- end
544
-
545
- value = normalize_xml_value(value, rule, attr, options)
546
- rule.deserialize(instance, value, attributes, self)
547
- end
548
-
549
- defaults_used.each { |attr_name| instance.using_default_for(attr_name) }
550
-
551
- instance
367
+ # options[:mappings] = mappings.mappings
368
+ transformer = Lutaml::Model::Config.transformer_for(format)
369
+ transformer.data_to_model(self, doc, format, options)
552
370
  end
553
371
 
554
- def value_for_rule(doc, rule, options, instance)
555
- rule_names = rule.namespaced_names(options[:default_namespace])
556
-
557
- if rule.attribute?
558
- doc.root.find_attribute_value(rule_names)
559
- else
560
- attr = attribute_for_rule(rule)
561
- children = doc.children.select do |child|
562
- rule_names.include?(child.namespaced_name) && !child.text?
563
- end
564
-
565
- if rule.using_custom_methods? || attr.type == Lutaml::Model::Type::Hash
566
- return_child = attr.type == Lutaml::Model::Type::Hash || !attr.collection? if attr
567
- return return_child ? children.first : children
568
- end
372
+ def resolve_polymorphic(doc, format, mappings, instance, options = {})
373
+ polymorphic_mapping = mappings.polymorphic_mapping
374
+ return instance if polymorphic_mapping.polymorphic_map.empty?
569
375
 
570
- if Utils.present?(children)
571
- instance.value_set_for(attr.name)
572
- end
573
-
574
- if rule.cdata
575
- values = children.map do |child|
576
- child.cdata_children&.map(&:text)
577
- end.flatten
578
- return children.count > 1 ? values : values.first
579
- end
376
+ klass_key = doc[polymorphic_mapping.name]
377
+ klass_name = polymorphic_mapping.polymorphic_map[klass_key]
378
+ klass = Object.const_get(klass_name)
580
379
 
581
- values = children.map do |child|
582
- if !rule.using_custom_methods? && attr.type <= Serialize
583
- attr.cast(child, :xml, options.except(:mappings))
584
- elsif attr.raw?
585
- inner_xml_of(child)
586
- else
587
- child&.children&.first&.text
588
- end
589
- end
590
- attr&.collection? ? values : values.first
591
- end
380
+ klass.apply_mappings(doc, format, options)
592
381
  end
593
382
 
594
- def apply_hash_mapping(doc, instance, format, options = {})
595
- mappings = options[:mappings] || mappings_for(format).mappings
596
- mappings.each do |rule|
597
- raise "Attribute '#{rule.to}' not found in #{self}" unless valid_rule?(rule)
598
-
599
- attr = attribute_for_rule(rule)
600
-
601
- names = rule.multiple_mappings? ? rule.name : [rule.name]
602
-
603
- value = names.collect do |rule_name|
604
- if rule.root_mapping?
605
- doc
606
- elsif rule.raw_mapping?
607
- adapter = Lutaml::Model::Config.public_send(:"#{format}_adapter")
608
- adapter.new(doc).public_send(:"to_#{format}")
609
- elsif doc.key?(rule_name.to_s)
610
- doc[rule_name.to_s]
611
- elsif doc.key?(rule_name.to_sym)
612
- doc[rule_name.to_sym]
613
- else
614
- attr&.default
615
- end
616
- end.compact.first
617
-
618
- if rule.using_custom_methods?
619
- if Utils.present?(value)
620
- value = new.send(rule.custom_methods[:from], instance, value)
621
- end
622
-
623
- next
624
- end
625
-
626
- value = translate_mappings(value, rule.hash_mappings, attr, format)
627
- value = attr.cast(value, format) unless rule.hash_mappings
628
- attr.valid_collection!(value, self)
629
-
630
- rule.deserialize(instance, value, attributes, self)
383
+ def apply_value_map(value, value_map, attr)
384
+ if value.nil?
385
+ value_for_option(value_map[:nil], attr)
386
+ elsif Utils.empty?(value)
387
+ value_for_option(value_map[:empty], attr, value)
388
+ elsif Utils.uninitialized?(value)
389
+ value_for_option(value_map[:omitted], attr)
390
+ else
391
+ value
631
392
  end
632
-
633
- instance
634
393
  end
635
394
 
636
- def normalize_xml_value(value, rule, attr, options = {})
637
- value = [value].compact if attr&.collection? && !value.is_a?(Array)
638
-
639
- return value unless cast_value?(attr, rule)
640
-
641
- options.merge(caller_class: self, mixed_content: rule.mixed_content)
642
- attr.cast(
643
- value,
644
- :xml,
645
- options,
646
- )
647
- end
395
+ def value_for_option(option, attr, empty_value = nil)
396
+ return nil if option == :nil
397
+ return empty_value || empty_object(attr) if option == :empty
648
398
 
649
- def cast_value?(attr, rule)
650
- attr &&
651
- !rule.raw_mapping? &&
652
- !rule.content_mapping? &&
653
- !rule.custom_methods[:from]
399
+ Lutaml::Model::UninitializedClass.instance
654
400
  end
655
401
 
656
- def text_hash?(attr, value)
657
- return false unless value.is_a?(Hash)
658
- return value.one? && value.text? unless attr
402
+ def empty_object(attr)
403
+ return [] if attr.collection?
659
404
 
660
- !(attr.type <= Serialize) && attr.type != Lutaml::Model::Type::Hash
405
+ ""
661
406
  end
662
407
 
663
408
  def ensure_utf8(value)
@@ -677,64 +422,61 @@ module Lutaml
677
422
  value
678
423
  end
679
424
  end
425
+ end
680
426
 
681
- def validate_sequence!(element_order)
682
- mapping_sequence = mappings_for(:xml).element_sequence
683
- current_order = element_order.filter_map(&:element_tag)
427
+ def self.register_format_mapping_method(format)
428
+ method_name = format == :hash ? :hsh : format
684
429
 
685
- mapping_sequence.each do |mapping|
686
- mapping.validate_content!(current_order)
687
- end
430
+ ::Lutaml::Model::Serialize::ClassMethods.define_method(method_name) do |&block|
431
+ process_mapping(format, &block)
688
432
  end
433
+ end
689
434
 
690
- private
435
+ def self.register_from_format_method(format)
436
+ ClassMethods.define_method(:"from_#{format}") do |data, options = {}|
437
+ from(format, data, options)
438
+ end
691
439
 
692
- def inner_xml_of(node)
693
- case node
694
- when XmlAdapter::XmlElement
695
- node.inner_xml
696
- else
697
- node.children.map(&:to_xml).join
698
- end
440
+ ClassMethods.define_method(:"of_#{format}") do |doc, options = {}|
441
+ of(format, doc, options)
699
442
  end
700
443
  end
701
444
 
702
- attr_accessor :element_order, :schema_location, :encoding
703
- attr_writer :ordered, :mixed
704
-
705
- def initialize(attrs = {})
706
- @using_default = {}
707
-
708
- return unless self.class.attributes
445
+ def self.register_to_format_method(format)
446
+ ClassMethods.define_method(:"to_#{format}") do |instance, options = {}|
447
+ to(format, instance, options)
448
+ end
709
449
 
710
- if attrs.is_a?(Lutaml::Model::MappingHash)
711
- @ordered = attrs.ordered?
712
- @element_order = attrs.item_order
450
+ ClassMethods.define_method(:"as_#{format}") do |instance, options = {}|
451
+ as(format, instance, options)
713
452
  end
714
453
 
715
- if attrs.key?(:schema_location)
716
- self.schema_location = attrs[:schema_location]
454
+ define_method(:"to_#{format}") do |options = {}|
455
+ raise Lutaml::Model::NoRootMappingError.new(self.class) if format == :xml && !self.class.root?
456
+
457
+ options[:parse_encoding] = encoding if encoding
458
+ self.class.to(format, self, options)
717
459
  end
460
+ end
718
461
 
719
- self.class.attributes.each do |name, attr|
720
- next if attr.derived?
462
+ attr_accessor :element_order, :schema_location, :encoding
463
+ attr_writer :ordered, :mixed
721
464
 
722
- value = if attrs.key?(name) || attrs.key?(name.to_s)
723
- attr_value(attrs, name, attr)
724
- else
725
- using_default_for(name)
726
- attr.default
727
- end
465
+ def initialize(attrs = {}, options = {})
466
+ @using_default = {}
467
+ return unless self.class.attributes
728
468
 
729
- # Initialize collections with an empty array if no value is provided
730
- if attr.collection? && value.nil?
731
- value = []
732
- end
469
+ set_ordering(attrs)
470
+ set_schema_location(attrs)
471
+ initialize_attributes(attrs, options)
472
+ end
733
473
 
734
- default = using_default?(name)
735
- public_send(:"#{name}=", self.class.ensure_utf8(value))
736
- using_default_for(name) if default
737
- end
474
+ def value_map(options)
475
+ {
476
+ omitted: options[:omitted] || :nil,
477
+ nil: options[:nil] || :nil,
478
+ empty: options[:empty] || :empty,
479
+ }
738
480
  end
739
481
 
740
482
  def attr_value(attrs, name, attr_rule)
@@ -747,7 +489,7 @@ module Lutaml
747
489
  end
748
490
 
749
491
  if attr_rule.collection? || value.is_a?(Array)
750
- (value || []).map do |v|
492
+ value&.map do |v|
751
493
  if v.is_a?(Hash)
752
494
  attr_rule.type.new(v)
753
495
  else
@@ -776,7 +518,7 @@ module Lutaml
776
518
  end
777
519
 
778
520
  def method_missing(method_name, *args)
779
- if method_name.to_s.end_with?("=") && self.class.attributes.key?(method_name.to_s.chomp("=").to_sym)
521
+ if method_name.to_s.end_with?("=") && attribute_exist?(method_name)
780
522
  define_singleton_method(method_name) do |value|
781
523
  instance_variable_set(:"@#{method_name.to_s.chomp('=')}", value)
782
524
  end
@@ -786,6 +528,17 @@ module Lutaml
786
528
  end
787
529
  end
788
530
 
531
+ def respond_to_missing?(method_name, include_private = false)
532
+ (method_name.to_s.end_with?("=") && attribute_exist?(method_name)) ||
533
+ super
534
+ end
535
+
536
+ def attribute_exist?(name)
537
+ name = name.to_s.chomp("=").to_sym if name.end_with?("=")
538
+
539
+ self.class.attributes.key?(name)
540
+ end
541
+
789
542
  def validate_attribute!(attr_name)
790
543
  attr = self.class.attributes[attr_name]
791
544
  value = instance_variable_get(:"@#{attr_name}")
@@ -816,20 +569,42 @@ module Lutaml
816
569
  self.class.as_yaml(self)
817
570
  end
818
571
 
819
- Lutaml::Model::Config::AVAILABLE_FORMATS.each do |format|
820
- define_method(:"to_#{format}") do |options = {}|
821
- adapter = Lutaml::Model::Config.public_send(:"#{format}_adapter")
822
- raise Lutaml::Model::NoRootMappingError.new(self.class) unless self.class.root?
572
+ private
823
573
 
824
- representation = if format == :xml
825
- self
826
- else
827
- self.class.hash_representation(self, format,
828
- options)
829
- end
574
+ def set_ordering(attrs)
575
+ return unless attrs.respond_to?(:ordered?)
830
576
 
831
- options[:parse_encoding] = encoding if encoding
832
- adapter.new(representation).public_send(:"to_#{format}", options)
577
+ @ordered = attrs.ordered?
578
+ @element_order = attrs.item_order
579
+ end
580
+
581
+ def set_schema_location(attrs)
582
+ return unless attrs.key?(:schema_location)
583
+
584
+ self.schema_location = attrs[:schema_location]
585
+ end
586
+
587
+ def initialize_attributes(attrs, options = {})
588
+ self.class.attributes.each do |name, attr|
589
+ next if attr.derived?
590
+
591
+ value = determine_value(attrs, name, attr)
592
+
593
+ default = using_default?(name)
594
+ value = self.class.apply_value_map(value, value_map(options), attr)
595
+ public_send(:"#{name}=", self.class.ensure_utf8(value))
596
+ using_default_for(name) if default
597
+ end
598
+ end
599
+
600
+ def determine_value(attrs, name, attr)
601
+ if attrs.key?(name) || attrs.key?(name.to_s)
602
+ attr_value(attrs, name, attr)
603
+ elsif attr.default_set?
604
+ using_default_for(name)
605
+ attr.default
606
+ else
607
+ Lutaml::Model::UninitializedClass.instance
833
608
  end
834
609
  end
835
610
  end