lutaml-model 0.7.1 → 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 (124) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +1 -1
  3. data/.rubocop_todo.yml +49 -48
  4. data/Gemfile +4 -1
  5. data/README.adoc +791 -143
  6. data/RELEASE_NOTES.adoc +346 -0
  7. data/docs/custom_adapters.adoc +144 -0
  8. data/lib/lutaml/model/attribute.rb +17 -11
  9. data/lib/lutaml/model/config.rb +48 -42
  10. data/lib/lutaml/model/error/polymorphic_error.rb +7 -2
  11. data/lib/lutaml/model/format_registry.rb +41 -0
  12. data/lib/lutaml/model/hash/document.rb +11 -0
  13. data/lib/lutaml/model/hash/mapping.rb +19 -0
  14. data/lib/lutaml/model/hash/mapping_rule.rb +9 -0
  15. data/lib/lutaml/model/hash/standard_adapter.rb +17 -0
  16. data/lib/lutaml/model/hash/transform.rb +8 -0
  17. data/lib/lutaml/model/hash.rb +21 -0
  18. data/lib/lutaml/model/json/document.rb +11 -0
  19. data/lib/lutaml/model/json/mapping.rb +19 -0
  20. data/lib/lutaml/model/json/mapping_rule.rb +9 -0
  21. data/lib/lutaml/model/{json_adapter → json}/multi_json_adapter.rb +4 -5
  22. data/lib/lutaml/model/{json_adapter/standard_json_adapter.rb → json/standard_adapter.rb} +5 -3
  23. data/lib/lutaml/model/json/transform.rb +8 -0
  24. data/lib/lutaml/model/json.rb +21 -0
  25. data/lib/lutaml/model/key_value_document.rb +27 -0
  26. data/lib/lutaml/model/mapping/key_value_mapping.rb +8 -4
  27. data/lib/lutaml/model/mapping/mapping.rb +13 -0
  28. data/lib/lutaml/model/mapping/mapping_rule.rb +7 -6
  29. data/lib/lutaml/model/serialization_adapter.rb +22 -0
  30. data/lib/lutaml/model/serialize.rb +146 -521
  31. data/lib/lutaml/model/services/logger.rb +54 -0
  32. data/lib/lutaml/model/services/transformer.rb +48 -0
  33. data/lib/lutaml/model/services.rb +2 -0
  34. data/lib/lutaml/model/toml/document.rb +11 -0
  35. data/lib/lutaml/model/toml/mapping.rb +27 -0
  36. data/lib/lutaml/model/toml/mapping_rule.rb +9 -0
  37. data/lib/lutaml/model/{toml_adapter → toml}/toml_rb_adapter.rb +3 -3
  38. data/lib/lutaml/model/toml/tomlib_adapter.rb +19 -0
  39. data/lib/lutaml/model/toml/transform.rb +8 -0
  40. data/lib/lutaml/model/toml.rb +30 -0
  41. data/lib/lutaml/model/transform/key_value_transform.rb +291 -0
  42. data/lib/lutaml/model/transform/xml_transform.rb +239 -0
  43. data/lib/lutaml/model/transform.rb +78 -0
  44. data/lib/lutaml/model/type/value.rb +6 -9
  45. data/lib/lutaml/model/uninitialized_class.rb +1 -1
  46. data/lib/lutaml/model/utils.rb +30 -0
  47. data/lib/lutaml/model/version.rb +1 -1
  48. data/lib/lutaml/model/{xml_adapter → xml}/builder/nokogiri.rb +2 -2
  49. data/lib/lutaml/model/{xml_adapter → xml}/builder/oga.rb +10 -10
  50. data/lib/lutaml/model/{xml_adapter → xml}/builder/ox.rb +1 -1
  51. data/lib/lutaml/model/{xml_adapter/xml_document.rb → xml/document.rb} +6 -7
  52. data/lib/lutaml/model/xml/element.rb +32 -0
  53. data/lib/lutaml/model/xml/mapping.rb +410 -0
  54. data/lib/lutaml/model/xml/mapping_rule.rb +141 -0
  55. data/lib/lutaml/model/xml/nokogiri_adapter.rb +232 -0
  56. data/lib/lutaml/model/{xml_adapter → xml}/oga/document.rb +1 -1
  57. data/lib/lutaml/model/{xml_adapter → xml}/oga/element.rb +3 -1
  58. data/lib/lutaml/model/xml/oga_adapter.rb +171 -0
  59. data/lib/lutaml/model/xml/ox_adapter.rb +215 -0
  60. data/lib/lutaml/model/xml/transform.rb +8 -0
  61. data/lib/lutaml/model/{xml_adapter → xml}/xml_attribute.rb +1 -1
  62. data/lib/lutaml/model/{xml_adapter → xml}/xml_element.rb +6 -3
  63. data/lib/lutaml/model/{xml_adapter → xml}/xml_namespace.rb +1 -1
  64. data/lib/lutaml/model/xml.rb +31 -0
  65. data/lib/lutaml/model/xml_adapter/element.rb +11 -25
  66. data/lib/lutaml/model/xml_adapter/nokogiri_adapter.rb +6 -223
  67. data/lib/lutaml/model/xml_adapter/oga_adapter.rb +13 -163
  68. data/lib/lutaml/model/xml_adapter/ox_adapter.rb +10 -207
  69. data/lib/lutaml/model/yaml/document.rb +10 -0
  70. data/lib/lutaml/model/yaml/mapping.rb +19 -0
  71. data/lib/lutaml/model/yaml/mapping_rule.rb +9 -0
  72. data/lib/lutaml/model/{yaml_adapter/standard_yaml_adapter.rb → yaml/standard_adapter.rb} +4 -3
  73. data/lib/lutaml/model/yaml/transform.rb +8 -0
  74. data/lib/lutaml/model/yaml.rb +21 -0
  75. data/lib/lutaml/model.rb +39 -4
  76. data/lutaml-model.gemspec +0 -4
  77. data/spec/benchmarks/xml_parsing_benchmark_spec.rb +4 -4
  78. data/spec/lutaml/model/cdata_spec.rb +7 -7
  79. data/spec/lutaml/model/custom_bibtex_adapter_spec.rb +598 -0
  80. data/spec/lutaml/model/custom_vobject_adapter_spec.rb +1226 -0
  81. data/spec/lutaml/model/group_spec.rb +18 -7
  82. data/spec/lutaml/model/hash/adapter_spec.rb +255 -0
  83. data/spec/lutaml/model/json_adapter_spec.rb +6 -6
  84. data/spec/lutaml/model/key_value_mapping_spec.rb +25 -1
  85. data/spec/lutaml/model/mixed_content_spec.rb +24 -24
  86. data/spec/lutaml/model/multiple_mapping_spec.rb +5 -5
  87. data/spec/lutaml/model/ordered_content_spec.rb +6 -6
  88. data/spec/lutaml/model/polymorphic_spec.rb +178 -0
  89. data/spec/lutaml/model/root_mappings_spec.rb +3 -3
  90. data/spec/lutaml/model/schema/xml_compiler_spec.rb +6 -6
  91. data/spec/lutaml/model/serializable_spec.rb +179 -103
  92. data/spec/lutaml/model/toml_adapter_spec.rb +6 -6
  93. data/spec/lutaml/model/toml_spec.rb +51 -0
  94. data/spec/lutaml/model/transformation_spec.rb +72 -15
  95. data/spec/lutaml/model/uninitialized_class_spec.rb +96 -0
  96. data/spec/lutaml/model/xml/namespace_spec.rb +57 -0
  97. data/spec/lutaml/model/xml/xml_element_spec.rb +1 -1
  98. data/spec/lutaml/model/xml_adapter/nokogiri_adapter_spec.rb +2 -2
  99. data/spec/lutaml/model/xml_adapter/oga_adapter_spec.rb +2 -2
  100. data/spec/lutaml/model/xml_adapter/ox_adapter_spec.rb +2 -2
  101. data/spec/lutaml/model/xml_adapter/xml_namespace_spec.rb +6 -6
  102. data/spec/lutaml/model/xml_adapter_spec.rb +6 -6
  103. data/spec/lutaml/model/xml_mapping_rule_spec.rb +3 -3
  104. data/spec/lutaml/model/xml_mapping_spec.rb +26 -14
  105. data/spec/lutaml/model/xml_spec.rb +63 -0
  106. data/spec/lutaml/model/yaml_adapter_spec.rb +3 -5
  107. data/spec/spec_helper.rb +3 -3
  108. metadata +64 -59
  109. data/lib/lutaml/model/json_adapter/json_document.rb +0 -20
  110. data/lib/lutaml/model/json_adapter/json_object.rb +0 -28
  111. data/lib/lutaml/model/loggable.rb +0 -15
  112. data/lib/lutaml/model/mapping/json_mapping.rb +0 -17
  113. data/lib/lutaml/model/mapping/toml_mapping.rb +0 -25
  114. data/lib/lutaml/model/mapping/xml_mapping.rb +0 -389
  115. data/lib/lutaml/model/mapping/xml_mapping_rule.rb +0 -139
  116. data/lib/lutaml/model/mapping/yaml_mapping.rb +0 -17
  117. data/lib/lutaml/model/mapping.rb +0 -14
  118. data/lib/lutaml/model/toml_adapter/toml_document.rb +0 -20
  119. data/lib/lutaml/model/toml_adapter/toml_object.rb +0 -28
  120. data/lib/lutaml/model/toml_adapter/tomlib_adapter.rb +0 -20
  121. data/lib/lutaml/model/toml_adapter.rb +0 -6
  122. data/lib/lutaml/model/yaml_adapter/yaml_document.rb +0 -20
  123. data/lib/lutaml/model/yaml_adapter/yaml_object.rb +0 -28
  124. data/lib/lutaml/model/yaml_adapter.rb +0 -8
@@ -1,10 +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
5
  require_relative "mapping_hash"
7
- require_relative "mapping"
6
+ # require_relative "mapping"
8
7
  require_relative "json_adapter"
9
8
  require_relative "comparable_model"
10
9
  require_relative "schema_location"
@@ -13,6 +12,7 @@ require_relative "error"
13
12
  require_relative "choice"
14
13
  require_relative "sequence"
15
14
  require_relative "liquefiable"
15
+ require_relative "transform"
16
16
 
17
17
  module Lutaml
18
18
  module Model
@@ -159,7 +159,8 @@ module Lutaml
159
159
  mapping = model.mappings_for(format)
160
160
  mapping = Utils.deep_dup(mapping)
161
161
 
162
- @mappings[format] ||= format == :xml ? XmlMapping.new : KeyValueMapping.new
162
+ klass = ::Lutaml::Model::Config.mappings_class_for(format)
163
+ @mappings[format] ||= klass.new
163
164
 
164
165
  if format == :xml
165
166
  @mappings[format].merge_mapping_attributes(mapping)
@@ -262,71 +263,68 @@ module Lutaml
262
263
  attributes.select { |_, attr| attr.enum? }
263
264
  end
264
265
 
265
- Lutaml::Model::Config::AVAILABLE_FORMATS.each do |format|
266
- define_method(format) do |&block|
267
- mapping_class = const_get("Lutaml::Model::#{format.to_s.capitalize}Mapping")
268
- mappings[format] ||= mapping_class.new
269
- 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)
270
270
 
271
- if format == :xml && !mappings[format].root_element && !mappings[format].no_root?
272
- mappings[format].root(model.to_s)
273
- end
274
- end
271
+ # handle_root_assignment(mappings, format)
275
272
 
276
- define_method(:"from_#{format}") do |data, options = {}|
277
- return data if Utils.uninitialized?(data)
273
+ klass = ::Lutaml::Model::Config.mappings_class_for(format)
274
+ mappings[format] ||= klass.new
278
275
 
279
- adapter = Lutaml::Model::Config.send(:"#{format}_adapter")
276
+ mappings[format].instance_eval(&block)
280
277
 
281
- doc = adapter.parse(data, options)
282
- public_send(:"of_#{format}", doc, options)
278
+ if mappings[format].respond_to?(:finalize)
279
+ mappings[format].finalize(self)
283
280
  end
281
+ end
284
282
 
285
- define_method(:"of_#{format}") do |doc, options = {}|
286
- if doc.is_a?(Array)
287
- return doc.map { |item| send(:"of_#{format}", item) }
288
- end
283
+ def from(format, data, options = {})
284
+ return data if Utils.uninitialized?(data)
289
285
 
290
- if format == :xml
291
- raise Lutaml::Model::NoRootMappingError.new(self) unless root?
286
+ adapter = Lutaml::Model::Config.adapter_for(format)
292
287
 
293
- options[:encoding] = doc.encoding
294
- apply_mappings(doc, format, options)
295
- else
296
- apply_mappings(doc.to_h, format, options)
297
- 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) }
298
295
  end
299
296
 
300
- define_method(:"to_#{format}") do |instance|
301
- value = public_send(:"as_#{format}", instance)
302
- adapter = Lutaml::Model::Config.public_send(:"#{format}_adapter")
297
+ if format == :xml
298
+ raise Lutaml::Model::NoRootMappingError.new(self) unless root?
303
299
 
304
- if format == :xml
305
- xml_options = { mapper_class: self }
306
- adapter.new(value).public_send(:"to_#{format}", xml_options)
307
- else
308
- adapter.new(value).public_send(:"to_#{format}")
309
- end
300
+ options[:encoding] = doc.encoding
310
301
  end
311
302
 
312
- define_method(:"as_#{format}") do |instance, options = {}|
313
- if instance.is_a?(Array)
314
- return instance.map { |item| public_send(:"as_#{format}", item) }
315
- end
303
+ transformer = Lutaml::Model::Config.transformer_for(format)
304
+ transformer.data_to_model(self, doc, format, options)
305
+ end
316
306
 
317
- unless instance.is_a?(model)
318
- msg = "argument is a '#{instance.class}' but should be a '#{model}'"
319
- raise Lutaml::Model::IncorrectModelError, msg
320
- end
307
+ def to(format, instance, options = {})
308
+ value = public_send(:"as_#{format}", instance, options)
309
+ adapter = Lutaml::Model::Config.adapter_for(format)
321
310
 
322
- return instance if format == :xml
311
+ options[:mapper_class] = self if format == :xml
323
312
 
324
- hash_representation(instance, format, options)
325
- end
313
+ adapter.new(value).public_send(:"to_#{format}", options)
326
314
  end
327
315
 
328
316
  def as(format, instance, options = {})
329
- 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)
330
328
  end
331
329
 
332
330
  def key_value(&block)
@@ -336,93 +334,15 @@ module Lutaml
336
334
  end
337
335
  end
338
336
 
339
- def hash_representation(instance, format, options = {})
340
- only = options[:only]
341
- except = options[:except]
342
- mappings = mappings_for(format).mappings
343
-
344
- mappings.each_with_object({}) do |rule, hash|
345
- name = rule.to
346
- attr = attributes[name]
347
-
348
- next if except&.include?(name) || (only && !only.include?(name))
349
- next handle_delegate(instance, rule, hash, format) if rule.delegate
350
-
351
- if rule.custom_methods[:to]
352
- next instance.send(rule.custom_methods[:to], instance, hash)
353
- end
354
-
355
- value = rule.serialize(instance)
356
-
357
- if rule.raw_mapping?
358
- adapter = Lutaml::Model::Config.send(:"#{format}_adapter")
359
- return adapter.parse(value, options)
360
- end
361
-
362
- if export_method = rule.transform[:export] || attr.transform_export_method
363
- value = export_method.call(value)
364
- end
365
-
366
- next hash.merge!(generate_hash_from_child_mappings(attr, value, format, rule.root_mappings)) if rule.root_mapping?
367
-
368
- value = if rule.child_mappings
369
- generate_hash_from_child_mappings(attr, value, format, rule.child_mappings)
370
- else
371
- attr.serialize(value, format, options)
372
- end
373
-
374
- next if !rule.render?(value, instance, options)
375
-
376
- value = apply_value_map(value, rule.value_map(:to, options), attr)
377
-
378
- rule_from_name = rule.multiple_mappings? ? rule.from.first.to_s : rule.from.to_s
379
- hash[rule_from_name] = value
380
- end
381
- end
382
-
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
392
- end
393
- end
394
-
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
398
-
399
- Lutaml::Model::UninitializedClass.instance
400
- end
401
-
402
- def empty_object(attr)
403
- return [] if attr.collection?
404
-
405
- ""
406
- end
407
-
408
- def handle_delegate(instance, rule, hash, format)
409
- name = rule.to
410
- value = instance.send(rule.delegate).send(name)
411
- return if value.nil? && !rule.render_nil
412
-
413
- attribute = instance.send(rule.delegate).class.attributes[name]
414
- rule_from_name = rule.multiple_mappings? ? rule.from.first.to_s : rule.from.to_s
415
- hash[rule_from_name] = attribute.serialize(value, format)
416
- end
417
-
418
337
  def mappings_for(format)
419
338
  mappings[format] || default_mappings(format)
420
339
  end
421
340
 
422
341
  def default_mappings(format)
423
- _mappings = format == :xml ? XmlMapping.new : KeyValueMapping.new(format)
342
+ klass = ::Lutaml::Model::Config.mappings_class_for(format)
343
+ mappings = klass.new
424
344
 
425
- _mappings.tap do |mapping|
345
+ mappings.tap do |mapping|
426
346
  attributes&.each_key do |name|
427
347
  mapping.map_element(
428
348
  name.to_s,
@@ -434,110 +354,6 @@ module Lutaml
434
354
  end
435
355
  end
436
356
 
437
- def translate_mappings(hash, child_mappings, attr, format)
438
- return hash unless child_mappings
439
-
440
- hash.map do |key, value|
441
- child_hash = child_mappings.to_h do |attr_name, path|
442
- attr_value = if path == :key
443
- key
444
- elsif path == :value
445
- value
446
- else
447
- path = [path] unless path.is_a?(Array)
448
- value.dig(*path.map(&:to_s))
449
- end
450
-
451
- attr_rule = attr.type.mappings_for(format).find_by_to(attr_name)
452
- [attr_rule.from.to_s, attr_value]
453
- end
454
-
455
- if child_mappings.values == [:key] && hash.values.all?(Hash)
456
- child_hash.merge!(value)
457
- end
458
-
459
- attr.type.apply_hash_mapping(
460
- child_hash,
461
- attr.type.model.new,
462
- format,
463
- { mappings: attr.type.mappings_for(format).mappings },
464
- )
465
- end
466
- end
467
-
468
- def generate_hash_from_child_mappings(attr, value, format, child_mappings)
469
- return value unless child_mappings
470
-
471
- hash = {}
472
-
473
- if child_mappings.values == [:key]
474
- klass = value.first.class
475
- mappings = klass.mappings_for(format)
476
-
477
- klass.attributes.each_key do |name|
478
- next if child_mappings.key?(name.to_sym) || child_mappings.key?(name.to_s)
479
-
480
- child_mappings[name.to_sym] = mappings.find_by_to(name)&.name.to_s || name.to_s
481
- end
482
- end
483
-
484
- value.each do |child_obj|
485
- map_key = nil
486
- map_value = {}
487
- mapping_rules = attr.type.mappings_for(format)
488
-
489
- child_mappings.each do |attr_name, path|
490
- mapping_rule = mapping_rules.find_by_to(attr_name)
491
-
492
- attr_value = child_obj.send(attr_name)
493
-
494
- attr_value = if attr_value.is_a?(Lutaml::Model::Serialize)
495
- attr_value.to_yaml_hash
496
- elsif attr_value.is_a?(Array) && attr_value.first.is_a?(Lutaml::Model::Serialize)
497
- attr_value.map(&:to_yaml_hash)
498
- else
499
- attr_value
500
- end
501
-
502
- next unless mapping_rule&.render?(attr_value, nil)
503
-
504
- if path == :key
505
- map_key = attr_value
506
- elsif path == :value
507
- map_value = attr_value
508
- else
509
- path = [path] unless path.is_a?(Array)
510
- path[0...-1].inject(map_value) do |acc, k|
511
- acc[k.to_s] ||= {}
512
- end.public_send(:[]=, path.last.to_s, attr_value)
513
- end
514
- end
515
-
516
- map_value = nil if map_value.empty?
517
- hash[map_key] = map_value
518
- end
519
-
520
- hash
521
- end
522
-
523
- def valid_rule?(rule)
524
- attribute = attribute_for_rule(rule)
525
-
526
- !!attribute || rule.custom_methods[:from]
527
- end
528
-
529
- def attribute_for_rule(rule)
530
- return attributes[rule.to] unless rule.delegate
531
-
532
- attributes[rule.delegate].type.attributes[rule.to]
533
- end
534
-
535
- def attribute_for_child(child_name, format)
536
- mapping_rule = mappings_for(format).find_by_name(child_name)
537
-
538
- attribute_for_rule(mapping_rule) if mapping_rule
539
- end
540
-
541
357
  def apply_mappings(doc, format, options = {})
542
358
  instance = options[:instance] || model.new
543
359
  return instance if Utils.blank?(doc)
@@ -548,10 +364,9 @@ module Lutaml
548
364
  return resolve_polymorphic(doc, format, mappings, instance, options)
549
365
  end
550
366
 
551
- options[:mappings] = mappings.mappings
552
- return apply_xml_mapping(doc, instance, options) if format == :xml
553
-
554
- apply_hash_mapping(doc, instance, format, options)
367
+ # options[:mappings] = mappings.mappings
368
+ transformer = Lutaml::Model::Config.transformer_for(format)
369
+ transformer.data_to_model(self, doc, format, options)
555
370
  end
556
371
 
557
372
  def resolve_polymorphic(doc, format, mappings, instance, options = {})
@@ -565,243 +380,29 @@ module Lutaml
565
380
  klass.apply_mappings(doc, format, options)
566
381
  end
567
382
 
568
- def apply_xml_mapping(doc, instance, options = {})
569
- options = prepare_options(options)
570
- instance.encoding = options[:encoding]
571
- return instance unless doc
572
-
573
- mappings = options[:mappings] || mappings_for(:xml).mappings
574
-
575
- validate_document!(doc, options)
576
-
577
- set_instance_ordering(instance, doc, options)
578
- set_schema_location(instance, doc)
579
-
580
- defaults_used = []
581
- validate_sequence!(doc.root.order)
582
-
583
- mappings.each do |rule|
584
- raise "Attribute '#{rule.to}' not found in #{self}" unless valid_rule?(rule)
585
-
586
- attr = attribute_for_rule(rule)
587
- next if attr&.derived?
588
-
589
- new_opts = options.dup
590
- if rule.namespace_set?
591
- new_opts[:default_namespace] = rule.namespace
592
- end
593
-
594
- value = if rule.raw_mapping?
595
- doc.root.inner_xml
596
- elsif rule.content_mapping?
597
- rule.cdata ? doc.cdata : doc.text
598
- else
599
- val = value_for_rule(doc, rule, new_opts, instance)
600
-
601
- if (Utils.uninitialized?(val) || val.nil?) && (instance.using_default?(rule.to) || rule.render_default)
602
- defaults_used << rule.to
603
- attr&.default || rule.to_value_for(instance)
604
- else
605
- val
606
- end
607
- end
608
-
609
- value = apply_value_map(value, rule.value_map(:from, new_opts), attr)
610
- value = normalize_xml_value(value, rule, attr, new_opts)
611
- rule.deserialize(instance, value, attributes, self)
612
- end
613
-
614
- defaults_used.each do |attr_name|
615
- instance.using_default_for(attr_name)
616
- end
617
-
618
- instance
619
- end
620
-
621
- def prepare_options(options)
622
- opts = Utils.deep_dup(options)
623
- opts[:default_namespace] ||= mappings_for(:xml)&.namespace_uri
624
-
625
- opts
626
- end
627
-
628
- def validate_document!(doc, options)
629
- return unless doc.is_a?(Array)
630
-
631
- raise Lutaml::Model::CollectionTrueMissingError(
632
- self,
633
- options[:caller_class],
634
- )
635
- end
636
-
637
- def set_instance_ordering(instance, doc, options)
638
- return unless instance.respond_to?(:ordered=)
639
-
640
- instance.element_order = doc.root.order
641
- instance.ordered = mappings_for(:xml).ordered? || options[:ordered]
642
- instance.mixed = mappings_for(:xml).mixed_content? || options[:mixed_content]
643
- end
644
-
645
- def set_schema_location(instance, doc)
646
- schema_location = doc.attributes.values.find do |a|
647
- a.unprefixed_name == "schemaLocation"
648
- end
649
-
650
- return if schema_location.nil?
651
-
652
- instance.schema_location = Lutaml::Model::SchemaLocation.new(
653
- schema_location: schema_location.value,
654
- prefix: schema_location.namespace_prefix,
655
- namespace: schema_location.namespace,
656
- )
657
- end
658
-
659
- def value_for_rule(doc, rule, options, instance)
660
- rule_names = rule.namespaced_names(options[:default_namespace])
661
-
662
- if rule.attribute?
663
- doc.root.find_attribute_value(rule_names)
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)
664
390
  else
665
- attr = attribute_for_rule(rule)
666
- children = doc.children.select do |child|
667
- rule_names.include?(child.namespaced_name) && !child.text?
668
- end
669
-
670
- if rule.using_custom_methods? || attr.type == Lutaml::Model::Type::Hash
671
- return_child = attr.type == Lutaml::Model::Type::Hash || !attr.collection? if attr
672
- return return_child ? children.first : children
673
- end
674
-
675
- return handle_cdata(children) if rule.cdata
676
-
677
- values = []
678
-
679
- if Utils.present?(children)
680
- instance.value_set_for(attr.name)
681
- else
682
- children = nil
683
- values = Lutaml::Model::UninitializedClass.instance
684
- end
685
-
686
- children&.each do |child|
687
- if !rule.using_custom_methods? && attr.type <= Serialize
688
- cast_options = options.except(:mappings)
689
- cast_options[:polymorphic] = rule.polymorphic if rule.polymorphic
690
-
691
- values << attr.cast(child, :xml, cast_options)
692
- elsif attr.raw?
693
- values << inner_xml_of(child)
694
- else
695
- return nil if rule.render_nil_as_nil? && child.nil_element?
696
-
697
- text = child.nil_element? ? nil : (child&.text&.+ child&.cdata)
698
- values << text
699
- end
700
- end
701
-
702
- normalized_value_for_attr(values, attr)
703
- end
704
- end
705
-
706
- def handle_cdata(children)
707
- values = children.map do |child|
708
- child.cdata_children&.map(&:text)
709
- end.flatten
710
-
711
- children.count > 1 ? values : values.first
712
- end
713
-
714
- def normalized_value_for_attr(values, attr)
715
- # for xml collection true cases like
716
- # <store><items /></store>
717
- # <store><items xsi:nil="true"/></store>
718
- # <store><items></items></store>
719
- #
720
- # these are considered empty collection
721
- return [] if attr&.collection? && [[nil], [""]].include?(values)
722
- return values if attr&.collection?
723
-
724
- values.is_a?(Array) ? values.first : values
725
- end
726
-
727
- def apply_hash_mapping(doc, instance, format, options = {})
728
- mappings = options[:mappings] || mappings_for(format).mappings
729
- mappings.each do |rule|
730
- raise "Attribute '#{rule.to}' not found in #{self}" unless valid_rule?(rule)
731
-
732
- attr = attribute_for_rule(rule)
733
- next if attr&.derived?
734
-
735
- names = rule.multiple_mappings? ? rule.name : [rule.name]
736
-
737
- values = names.collect do |rule_name|
738
- if rule.root_mapping?
739
- doc
740
- elsif rule.raw_mapping?
741
- adapter = Lutaml::Model::Config.public_send(:"#{format}_adapter")
742
- adapter.new(doc).public_send(:"to_#{format}")
743
- elsif doc.key?(rule_name.to_s)
744
- doc[rule_name.to_s]
745
- elsif doc.key?(rule_name.to_sym)
746
- doc[rule_name.to_sym]
747
- elsif attr&.default_set?
748
- attr&.default
749
- else
750
- Lutaml::Model::UninitializedClass.instance
751
- end
752
- end.compact
753
-
754
- value = values.find { |v| Utils.initialized?(v) } || values.first
755
-
756
- value = apply_value_map(value, rule.value_map(:from, options), attr)
757
-
758
- if rule.using_custom_methods?
759
- if Utils.present?(value)
760
- value = new.send(rule.custom_methods[:from], instance, value)
761
- end
762
-
763
- next
764
- end
765
-
766
- value = translate_mappings(value, rule.hash_mappings, attr, format)
767
-
768
- cast_options = {}
769
- cast_options[:polymorphic] = rule.polymorphic if rule.polymorphic
770
-
771
- value = attr.cast(value, format, cast_options) unless rule.hash_mappings
772
- attr.valid_collection!(value, self)
773
-
774
- rule.deserialize(instance, value, attributes, self)
391
+ value
775
392
  end
776
-
777
- instance
778
393
  end
779
394
 
780
- def normalize_xml_value(value, rule, attr, options = {})
781
- value = [value].compact if attr&.collection? && !value.is_a?(Array) && !value.nil?
782
-
783
- return value unless cast_value?(attr, rule)
784
-
785
- options.merge(caller_class: self, mixed_content: rule.mixed_content)
786
- attr.cast(
787
- value,
788
- :xml,
789
- options,
790
- )
791
- 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
792
398
 
793
- def cast_value?(attr, rule)
794
- attr &&
795
- !rule.raw_mapping? &&
796
- !rule.content_mapping? &&
797
- !rule.custom_methods[:from]
399
+ Lutaml::Model::UninitializedClass.instance
798
400
  end
799
401
 
800
- def text_hash?(attr, value)
801
- return false unless value.is_a?(Hash)
802
- return value.one? && value.text? unless attr
402
+ def empty_object(attr)
403
+ return [] if attr.collection?
803
404
 
804
- !(attr.type <= Serialize) && attr.type != Lutaml::Model::Type::Hash
405
+ ""
805
406
  end
806
407
 
807
408
  def ensure_utf8(value)
@@ -821,62 +422,53 @@ module Lutaml
821
422
  value
822
423
  end
823
424
  end
425
+ end
824
426
 
825
- def validate_sequence!(element_order)
826
- mapping_sequence = mappings_for(:xml).element_sequence
827
- current_order = element_order.filter_map(&:element_tag)
427
+ def self.register_format_mapping_method(format)
428
+ method_name = format == :hash ? :hsh : format
828
429
 
829
- mapping_sequence.each do |mapping|
830
- mapping.validate_content!(current_order)
831
- end
430
+ ::Lutaml::Model::Serialize::ClassMethods.define_method(method_name) do |&block|
431
+ process_mapping(format, &block)
832
432
  end
433
+ end
833
434
 
834
- 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
835
439
 
836
- def inner_xml_of(node)
837
- case node
838
- when XmlAdapter::XmlElement
839
- node.inner_xml
840
- else
841
- node.children.map(&:to_xml).join
842
- end
440
+ ClassMethods.define_method(:"of_#{format}") do |doc, options = {}|
441
+ of(format, doc, options)
843
442
  end
844
443
  end
845
444
 
846
- attr_accessor :element_order, :schema_location, :encoding
847
- attr_writer :ordered, :mixed
848
-
849
- def initialize(attrs = {}, options = {})
850
- @using_default = {}
851
-
852
- 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
853
449
 
854
- if attrs.is_a?(Lutaml::Model::MappingHash)
855
- @ordered = attrs.ordered?
856
- @element_order = attrs.item_order
450
+ ClassMethods.define_method(:"as_#{format}") do |instance, options = {}|
451
+ as(format, instance, options)
857
452
  end
858
453
 
859
- if attrs.key?(:schema_location)
860
- 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)
861
459
  end
460
+ end
862
461
 
863
- self.class.attributes.each do |name, attr|
864
- next if attr.derived?
462
+ attr_accessor :element_order, :schema_location, :encoding
463
+ attr_writer :ordered, :mixed
865
464
 
866
- value = if attrs.key?(name) || attrs.key?(name.to_s)
867
- attr_value(attrs, name, attr)
868
- elsif attr.default_set?
869
- using_default_for(name)
870
- attr.default
871
- else
872
- Lutaml::Model::UninitializedClass.instance
873
- end
465
+ def initialize(attrs = {}, options = {})
466
+ @using_default = {}
467
+ return unless self.class.attributes
874
468
 
875
- default = using_default?(name)
876
- value = self.class.apply_value_map(value, value_map(options), attr)
877
- public_send(:"#{name}=", self.class.ensure_utf8(value))
878
- using_default_for(name) if default
879
- end
469
+ set_ordering(attrs)
470
+ set_schema_location(attrs)
471
+ initialize_attributes(attrs, options)
880
472
  end
881
473
 
882
474
  def value_map(options)
@@ -926,7 +518,7 @@ module Lutaml
926
518
  end
927
519
 
928
520
  def method_missing(method_name, *args)
929
- 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)
930
522
  define_singleton_method(method_name) do |value|
931
523
  instance_variable_set(:"@#{method_name.to_s.chomp('=')}", value)
932
524
  end
@@ -936,6 +528,17 @@ module Lutaml
936
528
  end
937
529
  end
938
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
+
939
542
  def validate_attribute!(attr_name)
940
543
  attr = self.class.attributes[attr_name]
941
544
  value = instance_variable_get(:"@#{attr_name}")
@@ -966,20 +569,42 @@ module Lutaml
966
569
  self.class.as_yaml(self)
967
570
  end
968
571
 
969
- Lutaml::Model::Config::AVAILABLE_FORMATS.each do |format|
970
- define_method(:"to_#{format}") do |options = {}|
971
- adapter = Lutaml::Model::Config.public_send(:"#{format}_adapter")
972
- raise Lutaml::Model::NoRootMappingError.new(self.class) unless self.class.root?
572
+ private
973
573
 
974
- representation = if format == :xml
975
- self
976
- else
977
- self.class.hash_representation(self, format,
978
- options)
979
- end
574
+ def set_ordering(attrs)
575
+ return unless attrs.respond_to?(:ordered?)
980
576
 
981
- options[:parse_encoding] = encoding if encoding
982
- 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
983
608
  end
984
609
  end
985
610
  end