lutaml-model 0.6.7 → 0.7.1
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.
- checksums.yaml +4 -4
- data/.github/workflows/dependent-repos-todo.json +7 -0
- data/.github/workflows/dependent-repos.json +17 -9
- data/.rubocop_todo.yml +18 -33
- data/README.adoc +4380 -2557
- data/lib/lutaml/model/attribute.rb +94 -15
- data/lib/lutaml/model/choice.rb +7 -0
- data/lib/lutaml/model/comparable_model.rb +48 -9
- data/lib/lutaml/model/error/collection_count_out_of_range_error.rb +1 -1
- data/lib/lutaml/model/error/polymorphic_error.rb +9 -0
- data/lib/lutaml/model/error.rb +1 -0
- data/lib/lutaml/model/mapping/json_mapping.rb +17 -0
- data/lib/lutaml/model/{key_value_mapping.rb → mapping/key_value_mapping.rb} +58 -14
- data/lib/lutaml/model/{key_value_mapping_rule.rb → mapping/key_value_mapping_rule.rb} +18 -2
- data/lib/lutaml/model/mapping/mapping_rule.rb +299 -0
- data/lib/lutaml/model/mapping/toml_mapping.rb +25 -0
- data/lib/lutaml/model/{xml_mapping.rb → mapping/xml_mapping.rb} +97 -15
- data/lib/lutaml/model/{xml_mapping_rule.rb → mapping/xml_mapping_rule.rb} +20 -3
- data/lib/lutaml/model/mapping/yaml_mapping.rb +17 -0
- data/lib/lutaml/model/mapping.rb +14 -0
- data/lib/lutaml/model/schema/xml_compiler.rb +15 -15
- data/lib/lutaml/model/sequence.rb +2 -2
- data/lib/lutaml/model/serialize.rb +247 -97
- data/lib/lutaml/model/type/date.rb +1 -1
- data/lib/lutaml/model/type/date_time.rb +2 -2
- data/lib/lutaml/model/type/hash.rb +1 -1
- data/lib/lutaml/model/type/time.rb +2 -2
- data/lib/lutaml/model/type/time_without_date.rb +2 -2
- data/lib/lutaml/model/uninitialized_class.rb +64 -0
- data/lib/lutaml/model/utils.rb +14 -0
- data/lib/lutaml/model/validation.rb +1 -0
- data/lib/lutaml/model/version.rb +1 -1
- data/lib/lutaml/model/xml_adapter/nokogiri_adapter.rb +1 -1
- data/lib/lutaml/model/xml_adapter/oga_adapter.rb +1 -1
- data/lib/lutaml/model/xml_adapter/ox_adapter.rb +1 -1
- data/lib/lutaml/model/xml_adapter/xml_document.rb +38 -17
- data/lib/lutaml/model/xml_adapter/xml_element.rb +17 -7
- data/lib/lutaml/model.rb +1 -0
- data/spec/benchmarks/xml_parsing_benchmark_spec.rb +3 -3
- data/spec/fixtures/person.rb +5 -5
- data/spec/lutaml/model/attribute_spec.rb +37 -1
- data/spec/lutaml/model/cdata_spec.rb +2 -2
- data/spec/lutaml/model/collection_spec.rb +50 -2
- data/spec/lutaml/model/comparable_model_spec.rb +92 -27
- data/spec/lutaml/model/defaults_spec.rb +1 -1
- data/spec/lutaml/model/enum_spec.rb +1 -1
- data/spec/lutaml/model/group_spec.rb +316 -14
- data/spec/lutaml/model/key_value_mapping_spec.rb +41 -3
- data/spec/lutaml/model/polymorphic_spec.rb +348 -0
- data/spec/lutaml/model/render_empty_spec.rb +194 -0
- data/spec/lutaml/model/render_nil_spec.rb +206 -22
- data/spec/lutaml/model/simple_model_spec.rb +9 -9
- data/spec/lutaml/model/value_map_spec.rb +240 -0
- data/spec/lutaml/model/xml/namespace/nested_with_explicit_namespace_spec.rb +85 -0
- data/spec/lutaml/model/xml/xml_element_spec.rb +93 -0
- data/spec/lutaml/model/xml_mapping_rule_spec.rb +102 -2
- data/spec/lutaml/model/xml_mapping_spec.rb +45 -3
- data/spec/sample_model_spec.rb +3 -3
- metadata +20 -8
- data/lib/lutaml/model/mapping_rule.rb +0 -109
@@ -3,10 +3,8 @@ require_relative "xml_adapter"
|
|
3
3
|
require_relative "config"
|
4
4
|
require_relative "type"
|
5
5
|
require_relative "attribute"
|
6
|
-
require_relative "mapping_rule"
|
7
6
|
require_relative "mapping_hash"
|
8
|
-
require_relative "
|
9
|
-
require_relative "key_value_mapping"
|
7
|
+
require_relative "mapping"
|
10
8
|
require_relative "json_adapter"
|
11
9
|
require_relative "comparable_model"
|
12
10
|
require_relative "schema_location"
|
@@ -94,22 +92,15 @@ module Lutaml
|
|
94
92
|
end
|
95
93
|
end
|
96
94
|
|
97
|
-
|
98
|
-
|
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,74 @@ 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)
|
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
|
-
|
145
|
+
model.attributes.each_value do |attr|
|
146
|
+
define_attribute_methods(attr)
|
147
|
+
end
|
138
148
|
|
139
|
-
@
|
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
|
-
|
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
|
+
@mappings[format] ||= format == :xml ? XmlMapping.new : KeyValueMapping.new
|
163
|
+
|
164
|
+
if format == :xml
|
165
|
+
@mappings[format].merge_mapping_attributes(mapping)
|
166
|
+
@mappings[format].merge_mapping_elements(mapping)
|
167
|
+
@mappings[format].merge_elements_sequence(mapping)
|
168
|
+
else
|
169
|
+
@mappings[format].mappings.concat(mapping.mappings)
|
170
|
+
end
|
171
|
+
end
|
172
|
+
end
|
144
173
|
|
145
|
-
|
174
|
+
def handle_key_value_mappings(mapping, format)
|
175
|
+
@mappings[format] ||= KeyValueMapping.new
|
176
|
+
@mappings[format].mappings.concat(mapping.mappings)
|
146
177
|
end
|
147
178
|
|
148
179
|
def import_model(model)
|
149
|
-
|
150
|
-
|
180
|
+
import_model_with_root_error(model)
|
151
181
|
import_model_attributes(model)
|
152
182
|
import_model_mappings(model)
|
153
183
|
end
|
@@ -213,7 +243,8 @@ module Lutaml
|
|
213
243
|
|
214
244
|
def add_enum_setter_if_not_defined(klass, enum_name, _values, collection)
|
215
245
|
Utils.add_method_if_not_defined(klass, "#{enum_name}=") do |value|
|
216
|
-
value = [
|
246
|
+
value = [] if value.nil?
|
247
|
+
value = [value] if !value.is_a?(Array)
|
217
248
|
|
218
249
|
value_set_for(enum_name)
|
219
250
|
|
@@ -233,8 +264,8 @@ module Lutaml
|
|
233
264
|
|
234
265
|
Lutaml::Model::Config::AVAILABLE_FORMATS.each do |format|
|
235
266
|
define_method(format) do |&block|
|
236
|
-
|
237
|
-
mappings[format] ||=
|
267
|
+
mapping_class = const_get("Lutaml::Model::#{format.to_s.capitalize}Mapping")
|
268
|
+
mappings[format] ||= mapping_class.new
|
238
269
|
mappings[format].instance_eval(&block)
|
239
270
|
|
240
271
|
if format == :xml && !mappings[format].root_element && !mappings[format].no_root?
|
@@ -243,6 +274,8 @@ module Lutaml
|
|
243
274
|
end
|
244
275
|
|
245
276
|
define_method(:"from_#{format}") do |data, options = {}|
|
277
|
+
return data if Utils.uninitialized?(data)
|
278
|
+
|
246
279
|
adapter = Lutaml::Model::Config.send(:"#{format}_adapter")
|
247
280
|
|
248
281
|
doc = adapter.parse(data, options)
|
@@ -260,7 +293,7 @@ module Lutaml
|
|
260
293
|
options[:encoding] = doc.encoding
|
261
294
|
apply_mappings(doc, format, options)
|
262
295
|
else
|
263
|
-
apply_mappings(doc.to_h, format)
|
296
|
+
apply_mappings(doc.to_h, format, options)
|
264
297
|
end
|
265
298
|
end
|
266
299
|
|
@@ -298,7 +331,7 @@ module Lutaml
|
|
298
331
|
|
299
332
|
def key_value(&block)
|
300
333
|
Lutaml::Model::Config::KEY_VALUE_FORMATS.each do |format|
|
301
|
-
mappings[format] ||= KeyValueMapping.new
|
334
|
+
mappings[format] ||= KeyValueMapping.new(format)
|
302
335
|
mappings[format].instance_eval(&block)
|
303
336
|
end
|
304
337
|
end
|
@@ -310,43 +343,68 @@ module Lutaml
|
|
310
343
|
|
311
344
|
mappings.each_with_object({}) do |rule, hash|
|
312
345
|
name = rule.to
|
313
|
-
|
314
|
-
next if !rule.custom_methods[:to] && (!rule.render_default? && instance.using_default?(rule.to))
|
346
|
+
attr = attributes[name]
|
315
347
|
|
348
|
+
next if except&.include?(name) || (only && !only.include?(name))
|
316
349
|
next handle_delegate(instance, rule, hash, format) if rule.delegate
|
317
350
|
|
318
351
|
if rule.custom_methods[:to]
|
319
352
|
next instance.send(rule.custom_methods[:to], instance, hash)
|
320
353
|
end
|
321
354
|
|
322
|
-
value =
|
355
|
+
value = rule.serialize(instance)
|
323
356
|
|
324
357
|
if rule.raw_mapping?
|
325
358
|
adapter = Lutaml::Model::Config.send(:"#{format}_adapter")
|
326
359
|
return adapter.parse(value, options)
|
327
360
|
end
|
328
361
|
|
329
|
-
|
330
|
-
|
331
|
-
if export_method = rule.transform[:export] || attribute.transform_export_method
|
362
|
+
if export_method = rule.transform[:export] || attr.transform_export_method
|
332
363
|
value = export_method.call(value)
|
333
364
|
end
|
334
365
|
|
335
|
-
next hash.merge!(generate_hash_from_child_mappings(
|
366
|
+
next hash.merge!(generate_hash_from_child_mappings(attr, value, format, rule.root_mappings)) if rule.root_mapping?
|
336
367
|
|
337
368
|
value = if rule.child_mappings
|
338
|
-
generate_hash_from_child_mappings(
|
369
|
+
generate_hash_from_child_mappings(attr, value, format, rule.child_mappings)
|
339
370
|
else
|
340
|
-
|
371
|
+
attr.serialize(value, format, options)
|
341
372
|
end
|
342
373
|
|
343
|
-
next
|
374
|
+
next if !rule.render?(value, instance, options)
|
375
|
+
|
376
|
+
value = apply_value_map(value, rule.value_map(:to, options), attr)
|
344
377
|
|
345
378
|
rule_from_name = rule.multiple_mappings? ? rule.from.first.to_s : rule.from.to_s
|
346
379
|
hash[rule_from_name] = value
|
347
380
|
end
|
348
381
|
end
|
349
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
|
+
|
350
408
|
def handle_delegate(instance, rule, hash, format)
|
351
409
|
name = rule.to
|
352
410
|
value = instance.send(rule.delegate).send(name)
|
@@ -362,9 +420,9 @@ module Lutaml
|
|
362
420
|
end
|
363
421
|
|
364
422
|
def default_mappings(format)
|
365
|
-
|
423
|
+
_mappings = format == :xml ? XmlMapping.new : KeyValueMapping.new(format)
|
366
424
|
|
367
|
-
|
425
|
+
_mappings.tap do |mapping|
|
368
426
|
attributes&.each_key do |name|
|
369
427
|
mapping.map_element(
|
370
428
|
name.to_s,
|
@@ -441,7 +499,7 @@ module Lutaml
|
|
441
499
|
attr_value
|
442
500
|
end
|
443
501
|
|
444
|
-
next unless mapping_rule&.render?(attr_value)
|
502
|
+
next unless mapping_rule&.render?(attr_value, nil)
|
445
503
|
|
446
504
|
if path == :key
|
447
505
|
map_key = attr_value
|
@@ -484,46 +542,43 @@ module Lutaml
|
|
484
542
|
instance = options[:instance] || model.new
|
485
543
|
return instance if Utils.blank?(doc)
|
486
544
|
|
487
|
-
|
545
|
+
mappings = mappings_for(format)
|
488
546
|
|
547
|
+
if mappings.polymorphic_mapping
|
548
|
+
return resolve_polymorphic(doc, format, mappings, instance, options)
|
549
|
+
end
|
550
|
+
|
551
|
+
options[:mappings] = mappings.mappings
|
489
552
|
return apply_xml_mapping(doc, instance, options) if format == :xml
|
490
553
|
|
491
554
|
apply_hash_mapping(doc, instance, format, options)
|
492
555
|
end
|
493
556
|
|
557
|
+
def resolve_polymorphic(doc, format, mappings, instance, options = {})
|
558
|
+
polymorphic_mapping = mappings.polymorphic_mapping
|
559
|
+
return instance if polymorphic_mapping.polymorphic_map.empty?
|
560
|
+
|
561
|
+
klass_key = doc[polymorphic_mapping.name]
|
562
|
+
klass_name = polymorphic_mapping.polymorphic_map[klass_key]
|
563
|
+
klass = Object.const_get(klass_name)
|
564
|
+
|
565
|
+
klass.apply_mappings(doc, format, options)
|
566
|
+
end
|
567
|
+
|
494
568
|
def apply_xml_mapping(doc, instance, options = {})
|
495
|
-
options =
|
569
|
+
options = prepare_options(options)
|
496
570
|
instance.encoding = options[:encoding]
|
497
571
|
return instance unless doc
|
498
572
|
|
499
|
-
if options[:default_namespace].nil?
|
500
|
-
options[:default_namespace] = mappings_for(:xml)&.namespace_uri
|
501
|
-
end
|
502
573
|
mappings = options[:mappings] || mappings_for(:xml).mappings
|
503
574
|
|
504
|
-
|
575
|
+
validate_document!(doc, options)
|
505
576
|
|
506
|
-
|
507
|
-
|
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]
|
511
|
-
end
|
512
|
-
|
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
|
577
|
+
set_instance_ordering(instance, doc, options)
|
578
|
+
set_schema_location(instance, doc)
|
524
579
|
|
525
580
|
defaults_used = []
|
526
|
-
validate_sequence!(
|
581
|
+
validate_sequence!(doc.root.order)
|
527
582
|
|
528
583
|
mappings.each do |rule|
|
529
584
|
raise "Attribute '#{rule.to}' not found in #{self}" unless valid_rule?(rule)
|
@@ -531,26 +586,76 @@ module Lutaml
|
|
531
586
|
attr = attribute_for_rule(rule)
|
532
587
|
next if attr&.derived?
|
533
588
|
|
589
|
+
new_opts = options.dup
|
590
|
+
if rule.namespace_set?
|
591
|
+
new_opts[:default_namespace] = rule.namespace
|
592
|
+
end
|
593
|
+
|
534
594
|
value = if rule.raw_mapping?
|
535
595
|
doc.root.inner_xml
|
536
596
|
elsif rule.content_mapping?
|
537
597
|
rule.cdata ? doc.cdata : doc.text
|
538
|
-
|
539
|
-
val
|
540
|
-
|
541
|
-
|
542
|
-
|
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
|
543
607
|
end
|
544
608
|
|
545
|
-
value =
|
609
|
+
value = apply_value_map(value, rule.value_map(:from, new_opts), attr)
|
610
|
+
value = normalize_xml_value(value, rule, attr, new_opts)
|
546
611
|
rule.deserialize(instance, value, attributes, self)
|
547
612
|
end
|
548
613
|
|
549
|
-
defaults_used.each
|
614
|
+
defaults_used.each do |attr_name|
|
615
|
+
instance.using_default_for(attr_name)
|
616
|
+
end
|
550
617
|
|
551
618
|
instance
|
552
619
|
end
|
553
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
|
+
|
554
659
|
def value_for_rule(doc, rule, options, instance)
|
555
660
|
rule_names = rule.namespaced_names(options[:default_namespace])
|
556
661
|
|
@@ -567,40 +672,69 @@ module Lutaml
|
|
567
672
|
return return_child ? children.first : children
|
568
673
|
end
|
569
674
|
|
675
|
+
return handle_cdata(children) if rule.cdata
|
676
|
+
|
677
|
+
values = []
|
678
|
+
|
570
679
|
if Utils.present?(children)
|
571
680
|
instance.value_set_for(attr.name)
|
681
|
+
else
|
682
|
+
children = nil
|
683
|
+
values = Lutaml::Model::UninitializedClass.instance
|
572
684
|
end
|
573
685
|
|
574
|
-
|
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
|
580
|
-
|
581
|
-
values = children.map do |child|
|
686
|
+
children&.each do |child|
|
582
687
|
if !rule.using_custom_methods? && attr.type <= Serialize
|
583
|
-
|
688
|
+
cast_options = options.except(:mappings)
|
689
|
+
cast_options[:polymorphic] = rule.polymorphic if rule.polymorphic
|
690
|
+
|
691
|
+
values << attr.cast(child, :xml, cast_options)
|
584
692
|
elsif attr.raw?
|
585
|
-
inner_xml_of(child)
|
693
|
+
values << inner_xml_of(child)
|
586
694
|
else
|
587
|
-
child
|
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
|
588
699
|
end
|
589
700
|
end
|
590
|
-
|
701
|
+
|
702
|
+
normalized_value_for_attr(values, attr)
|
591
703
|
end
|
592
704
|
end
|
593
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
|
+
|
594
727
|
def apply_hash_mapping(doc, instance, format, options = {})
|
595
728
|
mappings = options[:mappings] || mappings_for(format).mappings
|
596
729
|
mappings.each do |rule|
|
597
730
|
raise "Attribute '#{rule.to}' not found in #{self}" unless valid_rule?(rule)
|
598
731
|
|
599
732
|
attr = attribute_for_rule(rule)
|
733
|
+
next if attr&.derived?
|
600
734
|
|
601
735
|
names = rule.multiple_mappings? ? rule.name : [rule.name]
|
602
736
|
|
603
|
-
|
737
|
+
values = names.collect do |rule_name|
|
604
738
|
if rule.root_mapping?
|
605
739
|
doc
|
606
740
|
elsif rule.raw_mapping?
|
@@ -610,10 +744,16 @@ module Lutaml
|
|
610
744
|
doc[rule_name.to_s]
|
611
745
|
elsif doc.key?(rule_name.to_sym)
|
612
746
|
doc[rule_name.to_sym]
|
613
|
-
|
747
|
+
elsif attr&.default_set?
|
614
748
|
attr&.default
|
749
|
+
else
|
750
|
+
Lutaml::Model::UninitializedClass.instance
|
615
751
|
end
|
616
|
-
end.compact
|
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)
|
617
757
|
|
618
758
|
if rule.using_custom_methods?
|
619
759
|
if Utils.present?(value)
|
@@ -624,7 +764,11 @@ module Lutaml
|
|
624
764
|
end
|
625
765
|
|
626
766
|
value = translate_mappings(value, rule.hash_mappings, attr, format)
|
627
|
-
|
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
|
628
772
|
attr.valid_collection!(value, self)
|
629
773
|
|
630
774
|
rule.deserialize(instance, value, attributes, self)
|
@@ -634,7 +778,7 @@ module Lutaml
|
|
634
778
|
end
|
635
779
|
|
636
780
|
def normalize_xml_value(value, rule, attr, options = {})
|
637
|
-
value = [value].compact if attr&.collection? && !value.is_a?(Array)
|
781
|
+
value = [value].compact if attr&.collection? && !value.is_a?(Array) && !value.nil?
|
638
782
|
|
639
783
|
return value unless cast_value?(attr, rule)
|
640
784
|
|
@@ -702,7 +846,7 @@ module Lutaml
|
|
702
846
|
attr_accessor :element_order, :schema_location, :encoding
|
703
847
|
attr_writer :ordered, :mixed
|
704
848
|
|
705
|
-
def initialize(attrs = {})
|
849
|
+
def initialize(attrs = {}, options = {})
|
706
850
|
@using_default = {}
|
707
851
|
|
708
852
|
return unless self.class.attributes
|
@@ -721,22 +865,28 @@ module Lutaml
|
|
721
865
|
|
722
866
|
value = if attrs.key?(name) || attrs.key?(name.to_s)
|
723
867
|
attr_value(attrs, name, attr)
|
724
|
-
|
868
|
+
elsif attr.default_set?
|
725
869
|
using_default_for(name)
|
726
870
|
attr.default
|
871
|
+
else
|
872
|
+
Lutaml::Model::UninitializedClass.instance
|
727
873
|
end
|
728
874
|
|
729
|
-
# Initialize collections with an empty array if no value is provided
|
730
|
-
if attr.collection? && value.nil?
|
731
|
-
value = []
|
732
|
-
end
|
733
|
-
|
734
875
|
default = using_default?(name)
|
876
|
+
value = self.class.apply_value_map(value, value_map(options), attr)
|
735
877
|
public_send(:"#{name}=", self.class.ensure_utf8(value))
|
736
878
|
using_default_for(name) if default
|
737
879
|
end
|
738
880
|
end
|
739
881
|
|
882
|
+
def value_map(options)
|
883
|
+
{
|
884
|
+
omitted: options[:omitted] || :nil,
|
885
|
+
nil: options[:nil] || :nil,
|
886
|
+
empty: options[:empty] || :empty,
|
887
|
+
}
|
888
|
+
end
|
889
|
+
|
740
890
|
def attr_value(attrs, name, attr_rule)
|
741
891
|
value = if attrs.key?(name.to_sym)
|
742
892
|
attrs[name.to_sym]
|
@@ -747,7 +897,7 @@ module Lutaml
|
|
747
897
|
end
|
748
898
|
|
749
899
|
if attr_rule.collection? || value.is_a?(Array)
|
750
|
-
|
900
|
+
value&.map do |v|
|
751
901
|
if v.is_a?(Hash)
|
752
902
|
attr_rule.type.new(v)
|
753
903
|
else
|
@@ -6,7 +6,7 @@ module Lutaml
|
|
6
6
|
# Date and time representation
|
7
7
|
class DateTime < Value
|
8
8
|
def self.cast(value)
|
9
|
-
return
|
9
|
+
return value if value.nil? || Utils.uninitialized?(value)
|
10
10
|
|
11
11
|
case value
|
12
12
|
when ::DateTime then value
|
@@ -18,7 +18,7 @@ module Lutaml
|
|
18
18
|
end
|
19
19
|
|
20
20
|
def self.serialize(value)
|
21
|
-
return
|
21
|
+
return value if value.nil? || Utils.uninitialized?(value)
|
22
22
|
|
23
23
|
cast(value)&.iso8601
|
24
24
|
end
|
@@ -5,7 +5,7 @@ module Lutaml
|
|
5
5
|
module Type
|
6
6
|
class Time < Value
|
7
7
|
def self.cast(value)
|
8
|
-
return
|
8
|
+
return value if value.nil? || Utils.uninitialized?(value)
|
9
9
|
|
10
10
|
case value
|
11
11
|
when ::Time then value
|
@@ -17,7 +17,7 @@ module Lutaml
|
|
17
17
|
end
|
18
18
|
|
19
19
|
def self.serialize(value)
|
20
|
-
return
|
20
|
+
return value if value.nil? || Utils.uninitialized?(value)
|
21
21
|
|
22
22
|
value = cast(value)
|
23
23
|
# value&.strftime("%Y-%m-%dT%H:%M:%S%:z")
|
@@ -11,7 +11,7 @@ module Lutaml
|
|
11
11
|
# ::Time.new(1, 1, 1, time.hour, time.min, time.sec)
|
12
12
|
|
13
13
|
def self.cast(value)
|
14
|
-
return
|
14
|
+
return value if value.nil? || Utils.uninitialized?(value)
|
15
15
|
|
16
16
|
case value
|
17
17
|
when ::Time then value
|
@@ -22,7 +22,7 @@ module Lutaml
|
|
22
22
|
end
|
23
23
|
|
24
24
|
def self.serialize(value)
|
25
|
-
return
|
25
|
+
return value if value.nil? || Utils.uninitialized?(value)
|
26
26
|
|
27
27
|
value = cast(value)
|
28
28
|
value.strftime("%H:%M:%S") # Format as HH:MM:SS
|