lutaml-model 0.5.3 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (86) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/dependent-tests.yml +2 -0
  3. data/.rubocop_todo.yml +86 -23
  4. data/Gemfile +2 -0
  5. data/README.adoc +1441 -220
  6. data/lib/lutaml/model/attribute.rb +33 -10
  7. data/lib/lutaml/model/choice.rb +56 -0
  8. data/lib/lutaml/model/config.rb +1 -0
  9. data/lib/lutaml/model/constants.rb +7 -0
  10. data/lib/lutaml/model/error/choice_lower_bound_error.rb +9 -0
  11. data/lib/lutaml/model/error/choice_upper_bound_error.rb +9 -0
  12. data/lib/lutaml/model/error/import_model_with_root_error.rb +9 -0
  13. data/lib/lutaml/model/error/incorrect_sequence_error.rb +9 -0
  14. data/lib/lutaml/model/error/invalid_choice_range_error.rb +20 -0
  15. data/lib/lutaml/model/error/no_root_mapping_error.rb +9 -0
  16. data/lib/lutaml/model/error/no_root_namespace_error.rb +9 -0
  17. data/lib/lutaml/model/error/type/invalid_value_error.rb +19 -0
  18. data/lib/lutaml/model/error/unknown_sequence_mapping_error.rb +9 -0
  19. data/lib/lutaml/model/error.rb +9 -0
  20. data/lib/lutaml/model/json_adapter/standard_json_adapter.rb +6 -1
  21. data/lib/lutaml/model/key_value_mapping.rb +34 -3
  22. data/lib/lutaml/model/key_value_mapping_rule.rb +4 -2
  23. data/lib/lutaml/model/liquefiable.rb +59 -0
  24. data/lib/lutaml/model/mapping_hash.rb +9 -1
  25. data/lib/lutaml/model/mapping_rule.rb +19 -2
  26. data/lib/lutaml/model/schema/templates/simple_type.rb +247 -0
  27. data/lib/lutaml/model/schema/xml_compiler.rb +762 -0
  28. data/lib/lutaml/model/schema.rb +5 -0
  29. data/lib/lutaml/model/schema_location.rb +7 -0
  30. data/lib/lutaml/model/sequence.rb +71 -0
  31. data/lib/lutaml/model/serialize.rb +139 -33
  32. data/lib/lutaml/model/toml_adapter/toml_rb_adapter.rb +1 -2
  33. data/lib/lutaml/model/type/decimal.rb +0 -4
  34. data/lib/lutaml/model/type/hash.rb +11 -11
  35. data/lib/lutaml/model/type/time.rb +3 -3
  36. data/lib/lutaml/model/utils.rb +19 -15
  37. data/lib/lutaml/model/validation.rb +12 -1
  38. data/lib/lutaml/model/version.rb +1 -1
  39. data/lib/lutaml/model/xml_adapter/builder/oga.rb +10 -7
  40. data/lib/lutaml/model/xml_adapter/builder/ox.rb +20 -13
  41. data/lib/lutaml/model/xml_adapter/element.rb +32 -0
  42. data/lib/lutaml/model/xml_adapter/nokogiri_adapter.rb +13 -9
  43. data/lib/lutaml/model/xml_adapter/oga/element.rb +14 -13
  44. data/lib/lutaml/model/xml_adapter/oga_adapter.rb +86 -19
  45. data/lib/lutaml/model/xml_adapter/ox_adapter.rb +19 -15
  46. data/lib/lutaml/model/xml_adapter/xml_document.rb +82 -25
  47. data/lib/lutaml/model/xml_adapter/xml_element.rb +57 -3
  48. data/lib/lutaml/model/xml_mapping.rb +53 -9
  49. data/lib/lutaml/model/xml_mapping_rule.rb +8 -6
  50. data/lib/lutaml/model.rb +2 -0
  51. data/lutaml-model.gemspec +5 -0
  52. data/spec/benchmarks/xml_parsing_benchmark_spec.rb +75 -0
  53. data/spec/ceramic_spec.rb +39 -0
  54. data/spec/fixtures/ceramic.rb +23 -0
  55. data/spec/fixtures/xml/address_example_260.xsd +9 -0
  56. data/spec/fixtures/xml/invalid_math_document.xml +4 -0
  57. data/spec/fixtures/xml/math_document_schema.xsd +56 -0
  58. data/spec/fixtures/xml/test_schema.xsd +53 -0
  59. data/spec/fixtures/xml/user.xsd +10 -0
  60. data/spec/fixtures/xml/valid_math_document.xml +4 -0
  61. data/spec/lutaml/model/cdata_spec.rb +4 -5
  62. data/spec/lutaml/model/choice_spec.rb +168 -0
  63. data/spec/lutaml/model/collection_spec.rb +1 -1
  64. data/spec/lutaml/model/custom_model_spec.rb +7 -21
  65. data/spec/lutaml/model/custom_serialization_spec.rb +74 -2
  66. data/spec/lutaml/model/defaults_spec.rb +3 -1
  67. data/spec/lutaml/model/delegation_spec.rb +7 -5
  68. data/spec/lutaml/model/enum_spec.rb +35 -0
  69. data/spec/lutaml/model/group_spec.rb +160 -0
  70. data/spec/lutaml/model/inheritance_spec.rb +25 -0
  71. data/spec/lutaml/model/key_value_mapping_spec.rb +27 -0
  72. data/spec/lutaml/model/liquefiable_spec.rb +121 -0
  73. data/spec/lutaml/model/map_all_spec.rb +188 -0
  74. data/spec/lutaml/model/mixed_content_spec.rb +95 -56
  75. data/spec/lutaml/model/multiple_mapping_spec.rb +22 -10
  76. data/spec/lutaml/model/schema/xml_compiler_spec.rb +1624 -0
  77. data/spec/lutaml/model/sequence_spec.rb +216 -0
  78. data/spec/lutaml/model/transformation_spec.rb +230 -0
  79. data/spec/lutaml/model/type_spec.rb +138 -31
  80. data/spec/lutaml/model/utils_spec.rb +32 -0
  81. data/spec/lutaml/model/with_child_mapping_spec.rb +2 -2
  82. data/spec/lutaml/model/xml_adapter/oga_adapter_spec.rb +11 -7
  83. data/spec/lutaml/model/xml_adapter/xml_namespace_spec.rb +52 -0
  84. data/spec/lutaml/model/xml_mapping_rule_spec.rb +51 -0
  85. data/spec/lutaml/model/xml_mapping_spec.rb +250 -112
  86. metadata +77 -2
@@ -0,0 +1,762 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "erb"
4
+ require "tmpdir"
5
+ require "lutaml/xsd"
6
+ require_relative "templates/simple_type"
7
+
8
+ module Lutaml
9
+ module Model
10
+ module Schema
11
+ module XmlCompiler
12
+ extend self
13
+
14
+ DEFAULT_CLASSES = %w[string integer int boolean].freeze
15
+ ELEMENT_ORDER_IGNORABLE = %w[import include].freeze
16
+
17
+ MODEL_TEMPLATE = ERB.new(<<~TEMPLATE, trim_mode: "-")
18
+ # frozen_string_literal: true
19
+ require "lutaml/model"
20
+ <%=
21
+ requiring_files = resolve_required_files(content)
22
+ if requiring_files.any?
23
+ requiring_files.map { |file| "require_relative \\\"\#{file}\\\"" }.join("\n") + "\n"
24
+ end
25
+ -%>
26
+
27
+ class <%= Utils.camel_case(name) %> < <%= resolve_parent_class(content) %>
28
+ <%=
29
+ if content&.key_exist?(:attributes)
30
+ output = content.attributes.map do |attribute|
31
+ attribute = @attributes[attribute.ref_class.split(":").last] if attribute.key?(:ref_class)
32
+ " attribute :\#{Utils.snake_case(attribute.name)}, \#{resolve_attribute_class(attribute)}\#{resolve_attribute_default(attribute) if attribute.key_exist?(:default)}"
33
+ end.join("\n")
34
+ output + "\n" if output && !output&.empty?
35
+ end
36
+ -%>
37
+ <%=
38
+ if content&.key_exist?(:sequence) || content&.key_exist?(:choice) || content&.key_exist?(:group)
39
+ output = resolve_content(content).map do |element_name, element|
40
+ element = @elements[element.ref_class.split(":")&.last] if element&.key_exist?(:ref_class)
41
+ " attribute :\#{Utils.snake_case(element_name)}, \#{resolve_element_class(element)}\#{resolve_occurs(element.arguments) if element.key_exist?(:arguments)}"
42
+ end.join("\n")
43
+ output + "\n" if output && !output&.empty?
44
+ end
45
+ -%>
46
+ <%=
47
+ if content&.key_exist?(:complex_content)
48
+ resolve_complex_content(content.complex_content).map do |element_name, element|
49
+ if element_name == :attributes
50
+ element.map { |attribute| " attribute :\#{Utils.snake_case(attribute.name)}, \#{resolve_attribute_class(attribute)}\#{resolve_attribute_default(attribute.default) if attribute.key_exist?(:default)}" }.join("\n")
51
+ else
52
+ element = @elements[element.ref_class.split(":")&.last] if element&.key_exist?(:ref_class)
53
+ " attribute :\#{Utils.snake_case(element_name)}, \#{resolve_element_class(element)}\#{resolve_occurs(element.arguments) if element.key_exist?(:arguments)}"
54
+ end
55
+ end.join("\n")
56
+ output + "\n" if output && !output&.empty?
57
+ end
58
+ -%>
59
+ <%= " attribute :content, \#{content[:mixed] ? ':string' : content.simple_content.extension_base}" if content_exist = (content.key_exist?(:simple_content) && content.simple_content.key_exist?(:extension_base)) || content[:mixed] -%>
60
+
61
+ xml do
62
+ root "<%= name %>", mixed: true
63
+ <%= resolve_namespace(options) %>
64
+ <%= " map_content to: :content\n" if content_exist -%>
65
+ <%=
66
+ if content&.key_exist?(:attributes)
67
+ output = content.attributes.map do |attribute|
68
+ attribute = @attributes[attribute.ref_class.split(":").last] if attribute.key?(:ref_class)
69
+ " map_attribute :\#{Utils.snake_case(attribute.name)}, to: :\#{Utils.snake_case(attribute.name)}"
70
+ end.join("\n")
71
+ output + "\n" if output && !output&.empty?
72
+ end
73
+ -%>
74
+ <%=
75
+ if content&.key_exist?(:sequence) || content&.key_exist?(:choice) || content&.key_exist?(:group)
76
+ output = resolve_content(content).map do |element_name, element|
77
+ element = @elements[element.ref_class.split(":")&.last] if element&.key_exist?(:ref_class)
78
+ " map_element :\#{element_name}, to: :\#{Utils.snake_case(element_name)}"
79
+ end.join("\n")
80
+ output + "\n" if output && !output&.empty?
81
+ end
82
+ -%>
83
+ <%=
84
+ if content&.key_exist?(:complex_content)
85
+ output = resolve_complex_content(content.complex_content).map do |element_name, element|
86
+ if element_name == :attributes
87
+ element.map { |attribute| " map_attribute :\#{Utils.snake_case(attribute.name)}, to: :\#{Utils.snake_case(attribute.name)}" }.join("\n")
88
+ else
89
+ element = @elements[element.ref_class.split(":")&.last] if element&.key_exist?(:ref_class)
90
+ " map_element :\#{element_name}, to: :\#{Utils.snake_case(element_name)}"
91
+ end
92
+ end.join("\n")
93
+ output + "\n" if output && !output&.empty?
94
+ end
95
+ -%>
96
+ end
97
+ end
98
+
99
+ TEMPLATE
100
+
101
+ XML_ADAPTER_NOT_SET_MESSAGE = <<~MSG
102
+ Nokogiri is not set as XML Adapter.
103
+ Make sure Nokogiri is installed and set as XML Adapter eg.
104
+ execute: gem install nokogiri
105
+ require 'lutaml/model/adapter/nokogiri'
106
+ Lutaml::Model.xml_adapter = Lutaml::Model::Adapter::Nokogiri
107
+ MSG
108
+
109
+ def to_models(schema, options = {})
110
+ as_models(schema, options: options)
111
+ @data_types_classes = Templates::SimpleType.create_simple_types(@simple_types)
112
+ if options[:create_files]
113
+ dir = options.fetch(:output_dir, "lutaml_models_#{Time.now.to_i}")
114
+ FileUtils.mkdir_p(dir)
115
+ @data_types_classes.each do |name, content|
116
+ create_file(name, content, dir)
117
+ end
118
+ @complex_types.each do |name, content|
119
+ create_file(name, MODEL_TEMPLATE.result(binding), dir)
120
+ end
121
+ nil
122
+ else
123
+ simple_types = @data_types_classes.transform_keys do |key|
124
+ Utils.camel_case(key.to_s)
125
+ end
126
+ complex_types = @complex_types.to_h do |name, content|
127
+ [Utils.camel_case(name), MODEL_TEMPLATE.result(binding)]
128
+ end
129
+ classes_hash = simple_types.merge(complex_types)
130
+ require_classes(classes_hash) if options[:load_classes]
131
+ classes_hash
132
+ end
133
+ end
134
+
135
+ private
136
+
137
+ def create_file(name, content, dir)
138
+ File.write("#{dir}/#{Utils.snake_case(name)}.rb", content)
139
+ end
140
+
141
+ def require_classes(classes_hash)
142
+ Dir.mktmpdir do |dir|
143
+ classes_hash.each do |name, klass|
144
+ create_file(name, klass, dir)
145
+ require "#{dir}/#{Utils.snake_case(name)}"
146
+ end
147
+ end
148
+ end
149
+
150
+ # START: STRUCTURE SETUP METHODS
151
+
152
+ def as_models(schema, options: {})
153
+ raise Error, XML_ADAPTER_NOT_SET_MESSAGE unless Config.xml_adapter.name.end_with?("NokogiriAdapter")
154
+
155
+ parsed_schema = Xsd.parse(schema, location: options[:location])
156
+
157
+ @elements = MappingHash.new
158
+ @attributes = MappingHash.new
159
+ @group_types = MappingHash.new
160
+ @simple_types = MappingHash.new
161
+ @complex_types = MappingHash.new
162
+ @attribute_groups = MappingHash.new
163
+
164
+ schema_to_models(Array(parsed_schema))
165
+ end
166
+
167
+ def schema_to_models(schemas)
168
+ return if schemas.empty?
169
+
170
+ schemas.each do |schema|
171
+ schema_to_models(schema.include) if schema.include.any?
172
+ schema_to_models(schema.import) if schema.import.any?
173
+ resolved_element_order(schema).each do |order_item|
174
+ item_name = order_item&.name
175
+ case order_item
176
+ when Xsd::SimpleType
177
+ @simple_types[item_name] = setup_simple_type(order_item)
178
+ when Xsd::Group
179
+ @group_types[item_name] = setup_group_type(order_item)
180
+ when Xsd::ComplexType
181
+ @complex_types[item_name] = setup_complex_type(order_item)
182
+ when Xsd::Element
183
+ @elements[item_name] = setup_element(order_item)
184
+ when Xsd::Attribute
185
+ @attributes[item_name] = setup_attribute(order_item)
186
+ when Xsd::AttributeGroup
187
+ @attribute_groups[item_name] = setup_attribute_groups(order_item)
188
+ end
189
+ end
190
+ end
191
+ nil
192
+ end
193
+
194
+ def setup_simple_type(simple_type)
195
+ MappingHash.new.tap do |hash|
196
+ setup_restriction(simple_type.restriction, hash) if simple_type&.restriction
197
+ hash[:union] = setup_union(simple_type.union) if simple_type.union
198
+ end
199
+ end
200
+
201
+ def restriction_content(hash, restriction)
202
+ return hash unless restriction.respond_to?(:max_length)
203
+
204
+ hash[:max_length] = restriction.max_length.map(&:value).min if restriction.max_length&.any?
205
+ hash[:min_length] = restriction.min_length.map(&:value).max if restriction.min_length&.any?
206
+ hash[:min_inclusive] = restriction.min_inclusive.map(&:value).max if restriction.min_inclusive&.any?
207
+ hash[:max_inclusive] = restriction.max_inclusive.map(&:value).min if restriction.max_inclusive&.any?
208
+ hash[:length] = restriction_length(restriction.length) if restriction.length.any?
209
+ end
210
+
211
+ def restriction_length(lengths)
212
+ lengths.map do |length|
213
+ MappingHash.new.tap do |hash|
214
+ hash[:value] = length.value
215
+ hash[:fixed] = length.fixed if length.fixed
216
+ end
217
+ end
218
+ end
219
+
220
+ def setup_complex_type(complex_type)
221
+ MappingHash.new.tap do |hash|
222
+ hash[:attributes] = [] if complex_type.attribute.any?
223
+ hash[:attribute_groups] = [] if complex_type.attribute_group.any?
224
+ hash[:mixed] = complex_type.mixed
225
+ resolved_element_order(complex_type).each do |element|
226
+ case element
227
+ when Xsd::Attribute
228
+ hash[:attributes] << setup_attribute(element)
229
+ when Xsd::Sequence
230
+ hash[:sequence] = setup_sequence(element)
231
+ when Xsd::Choice
232
+ hash[:choice] = setup_choice(element)
233
+ when Xsd::ComplexContent
234
+ hash[:complex_content] = setup_complex_content(element)
235
+ when Xsd::AttributeGroup
236
+ hash[:attribute_groups] << setup_attribute_groups(element)
237
+ when Xsd::Group
238
+ hash[:group] = setup_group_type(element)
239
+ when Xsd::SimpleContent
240
+ hash[:simple_content] = setup_simple_content(element)
241
+ end
242
+ end
243
+ end
244
+ end
245
+
246
+ def setup_simple_content(simple_content)
247
+ if simple_content.extension
248
+ setup_extension(simple_content.extension)
249
+ elsif simple_content.restriction
250
+ setup_restriction(simple_content.restriction, {})
251
+ end
252
+ end
253
+
254
+ def setup_sequence(sequence)
255
+ MappingHash.new.tap do |hash|
256
+ hash[:sequences] = [] if sequence.sequence.any?
257
+ hash[:elements] = [] if sequence.element.any?
258
+ hash[:choice] = [] if sequence.choice.any?
259
+ hash[:groups] = [] if sequence.group.any?
260
+ resolved_element_order(sequence).each do |instance|
261
+ case instance
262
+ when Xsd::Sequence
263
+ hash[:sequences] << setup_sequence(instance)
264
+ when Xsd::Element
265
+ hash[:elements] << if instance.name
266
+ setup_element(instance)
267
+ else
268
+ create_mapping_hash(instance.ref, hash_key: :ref_class)
269
+ end
270
+ when Xsd::Choice
271
+ hash[:choice] << setup_choice(instance)
272
+ when Xsd::Group
273
+ hash[:groups] << if instance.name
274
+ setup_group_type(instance)
275
+ else
276
+ create_mapping_hash(instance.ref, hash_key: :ref_class)
277
+ end
278
+ when Xsd::Any
279
+ # No implementation yet!
280
+ end
281
+ end
282
+ end
283
+ end
284
+
285
+ def setup_group_type(group)
286
+ MappingHash.new.tap do |hash|
287
+ if group.ref
288
+ hash[:ref_class] = group.ref
289
+ else
290
+ resolved_element_order(group).map do |instance|
291
+ case instance
292
+ when Xsd::Sequence
293
+ hash[:sequence] = setup_sequence(instance)
294
+ when Xsd::Choice
295
+ hash[:choice] = setup_choice(instance)
296
+ end
297
+ end
298
+ end
299
+ end
300
+ end
301
+
302
+ def setup_choice(choice)
303
+ MappingHash.new.tap do |hash|
304
+ resolved_element_order(choice).each do |element|
305
+ case element
306
+ when Xsd::Element
307
+ element_name = element.name || @elements[element.ref.split(":").last]&.element_name
308
+ hash[element_name] = setup_element(element)
309
+ when Xsd::Sequence
310
+ hash[:sequence] = setup_sequence(element)
311
+ when Xsd::Group
312
+ hash[:group] = setup_group_type(element)
313
+ when Xsd::Choice
314
+ hash[:choice] = setup_choice(element)
315
+ end
316
+ end
317
+ end
318
+ end
319
+
320
+ def setup_union(union)
321
+ union.member_types.split.map do |member_type|
322
+ @simple_types[member_type]
323
+ end.flatten
324
+ end
325
+
326
+ def setup_attribute(attribute)
327
+ MappingHash.new.tap do |attr_hash|
328
+ if attribute.ref
329
+ attr_hash[:ref_class] = attribute.ref
330
+ else
331
+ attr_hash[:name] = attribute.name
332
+ attr_hash[:base_class] = attribute.type
333
+ attr_hash[:default] = attribute.default if attribute.default
334
+ end
335
+ end
336
+ end
337
+
338
+ def setup_attribute_groups(attribute_group)
339
+ MappingHash.new.tap do |hash|
340
+ if attribute_group.ref
341
+ hash[:ref_class] = attribute_group.ref
342
+ else
343
+ hash[:attributes] = [] if attribute_group.attribute.any?
344
+ hash[:attribute_groups] = [] if attribute_group.attribute_group.any?
345
+ resolved_element_order(attribute_group).each do |instance|
346
+ case instance
347
+ when Xsd::Attribute
348
+ hash[:attributes] << setup_attribute(instance)
349
+ when Xsd::AttributeGroup
350
+ hash[:attribute_groups] << setup_attribute_groups(instance)
351
+ end
352
+ end
353
+ end
354
+ end
355
+ end
356
+
357
+ def create_mapping_hash(value, hash_key: :class_name)
358
+ MappingHash.new.tap do |hash|
359
+ hash[hash_key] = value
360
+ end
361
+ end
362
+
363
+ def setup_element(element)
364
+ MappingHash.new.tap do |hash|
365
+ if element.ref
366
+ hash[:ref_class] = element.ref
367
+ else
368
+ hash[:type_name] = element.type
369
+ hash[:element_name] = element.name
370
+ element_arguments(element, hash)
371
+ return hash unless complex_type = element.complex_type
372
+
373
+ hash[:complex_type] = setup_complex_type(complex_type)
374
+ @complex_types[complex_type.name] = hash[:complex_type]
375
+ end
376
+ end
377
+ end
378
+
379
+ def setup_restriction(restriction, hash)
380
+ hash[:base_class] = restriction.base
381
+ restriction_patterns(restriction.pattern, hash) if restriction.respond_to?(:pattern)
382
+ restriction_content(hash, restriction)
383
+ return hash unless restriction.respond_to?(:enumeration) && restriction.enumeration.any?
384
+
385
+ hash[:values] = restriction.enumeration.map(&:value)
386
+ hash
387
+ end
388
+
389
+ def restriction_patterns(patterns, hash)
390
+ return if patterns.empty?
391
+
392
+ hash[:pattern] = patterns.map { |p| "(#{p.value})" }.join("|")
393
+ hash
394
+ end
395
+
396
+ def setup_complex_content(complex_content)
397
+ MappingHash.new.tap do |hash|
398
+ hash[:mixed] = true if complex_content.mixed
399
+ if complex_content.extension
400
+ hash[:extension] = setup_extension(complex_content.extension)
401
+ elsif restriction = complex_content.restriction
402
+ setup_restriction(restriction, hash)
403
+ end
404
+ end
405
+ end
406
+
407
+ def setup_extension(extension)
408
+ MappingHash.new.tap do |hash|
409
+ hash[:extension_base] = extension.base
410
+ hash[:attribute_groups] = [] if extension&.attribute_group&.any?
411
+ hash[:attributes] = [] if extension&.attribute&.any?
412
+ resolved_element_order(extension).each do |element|
413
+ case element
414
+ when Xsd::Attribute
415
+ hash[:attributes] << setup_attribute(element)
416
+ when Xsd::Sequence
417
+ hash[:sequence] = setup_sequence(element)
418
+ when Xsd::Choice
419
+ hash[:choice] = setup_choice(element)
420
+ end
421
+ end
422
+ end
423
+ end
424
+
425
+ def element_arguments(element, element_hash)
426
+ MappingHash.new.tap do |hash|
427
+ hash[:min_occurs] = element.min_occurs if element.min_occurs
428
+ hash[:max_occurs] = element.max_occurs if element.max_occurs
429
+ element_hash[:arguments] = hash if hash.any?
430
+ end
431
+ end
432
+
433
+ def resolved_element_order(object)
434
+ return [] if object.element_order.nil?
435
+
436
+ object.element_order.each_with_object(object.element_order.dup) do |builder_instance, array|
437
+ next array.delete(builder_instance) if builder_instance.text?
438
+ next array.delete(builder_instance) if ELEMENT_ORDER_IGNORABLE.include?(builder_instance.name)
439
+
440
+ index = 0
441
+ array.each_with_index do |element, i|
442
+ next unless element == builder_instance
443
+
444
+ array[i] = Array(object.send(Utils.snake_case(builder_instance.name)))[index]
445
+ index += 1
446
+ end
447
+ end
448
+ end
449
+
450
+ # END: STRUCTURE SETUP METHODS
451
+
452
+ # START: TEMPLATE RESOLVER METHODS
453
+ def resolve_parent_class(content)
454
+ return "Lutaml::Model::Serializable" unless content.dig(:complex_content, :extension)
455
+
456
+ Utils.camel_case(content.dig(:complex_content, :extension, :extension_base))
457
+ end
458
+
459
+ def resolve_attribute_class(attribute)
460
+ attr_class = attribute.base_class.split(":")&.last
461
+ case attr_class
462
+ when *DEFAULT_CLASSES
463
+ ":#{attr_class}"
464
+ else
465
+ Utils.camel_case(attr_class)
466
+ end
467
+ end
468
+
469
+ def resolve_element_class(element)
470
+ element_class = element.type_name.split(":").last
471
+ case element_class
472
+ when *DEFAULT_CLASSES
473
+ ":#{element_class}"
474
+ else
475
+ Utils.camel_case(element_class)
476
+ end
477
+ end
478
+
479
+ def resolve_occurs(arguments)
480
+ min_occurs = arguments[:min_occurs]
481
+ max_occurs = arguments[:max_occurs]
482
+ max_occurs = max_occurs.to_s&.match?(/[A-Za-z]+/) ? nil : max_occurs.to_i if max_occurs
483
+ ", collection: #{max_occurs ? min_occurs.to_i..max_occurs : true}"
484
+ end
485
+
486
+ def resolve_content(content, hash = MappingHash.new)
487
+ content.each do |key, value|
488
+ case key
489
+ when :sequence
490
+ resolve_sequence(value, hash)
491
+ when :choice
492
+ resolve_choice(value, hash)
493
+ when :group
494
+ resolve_group(value, hash)
495
+ end
496
+ end
497
+ hash
498
+ end
499
+
500
+ def resolve_elements(elements, hash = MappingHash.new)
501
+ elements.each do |element|
502
+ if element.key?(:ref_class)
503
+ new_element = @elements[element.ref_class.split(":").last]
504
+ hash[new_element.element_name] = new_element
505
+ else
506
+ hash[element.element_name] = element
507
+ end
508
+ end
509
+ hash
510
+ end
511
+
512
+ def resolve_sequence(sequence, hash = MappingHash.new)
513
+ sequence.each do |key, value|
514
+ case key
515
+ when :sequence
516
+ resolve_sequence(value, hash)
517
+ when :elements
518
+ resolve_elements(value, hash)
519
+ when :groups
520
+ value.each { |group| resolve_group(group, hash) }
521
+ when :choice
522
+ value.each { |choice| resolve_choice(choice, hash) }
523
+ end
524
+ end
525
+ hash
526
+ end
527
+
528
+ def resolve_choice(choice, hash = MappingHash.new)
529
+ choice.each do |key, value|
530
+ case key
531
+ when :element
532
+ [resolve_elements(value, hash)]
533
+ when :group
534
+ resolve_group(value, hash)
535
+ when String
536
+ hash[key] = value
537
+ when :sequence
538
+ resolve_sequence(value, hash)
539
+ end
540
+ end
541
+ hash
542
+ end
543
+
544
+ def resolve_group(group, hash = MappingHash.new)
545
+ group.each do |key, value|
546
+ case key
547
+ when :ref_class
548
+ resolve_group(@group_types[value.split(":").last], hash)
549
+ when :choice
550
+ resolve_choice(value, hash)
551
+ when :group
552
+ resolve_group(value, hash)
553
+ when :sequence
554
+ resolve_sequence(value, hash)
555
+ end
556
+ end
557
+ hash
558
+ end
559
+
560
+ def resolve_complex_content(complex_content, hash = MappingHash.new)
561
+ complex_content.each do |key, value|
562
+ case key
563
+ when :extension
564
+ resolve_extension(value, hash)
565
+ when :restriction
566
+ # TODO: No implementation yet!
567
+ hash
568
+ end
569
+ end
570
+ hash
571
+ end
572
+
573
+ def resolve_extension(extension, hash = MappingHash.new)
574
+ hash[:attributes] = extension.attributes if extension.key?(:attributes)
575
+ resolve_sequence(extension.sequence, hash) if extension.key?(:sequence)
576
+ resolve_choice(extension.choice, hash) if extension.key?(:choice)
577
+ hash
578
+ end
579
+
580
+ def resolve_attribute_default(attribute)
581
+ klass = attribute.base_class.split(":").last
582
+ default = attribute[:default]
583
+ ", default: #{resolve_attribute_default_value(klass, default)}"
584
+ end
585
+
586
+ def resolve_attribute_default_value(klass, default)
587
+ return default.inspect unless DEFAULT_CLASSES.include?(klass)
588
+
589
+ klass = "integer" if klass == "int"
590
+ type_klass = Lutaml::Model::Type.const_get(klass.capitalize)
591
+ type_klass.cast(default)
592
+ end
593
+
594
+ def resolve_namespace(options)
595
+ namespace_str = "namespace \"#{options[:namespace]}\"" if options.key?(:namespace)
596
+ namespace_str += ", \"#{options[:prefix]}\"" if options.key?(:prefix) && options.key?(:namespace)
597
+ namespace_str += "\n" if namespace_str
598
+ namespace_str
599
+ end
600
+
601
+ def resolve_required_files(content)
602
+ @required_files = []
603
+ content.each do |key, value|
604
+ case key
605
+ when :sequence
606
+ required_files_sequence(value)
607
+ when :choice
608
+ required_files_choice(value)
609
+ when :group
610
+ required_files_group(value)
611
+ when :attributes
612
+ required_files_attribute(value)
613
+ when :attribute_groups
614
+ value.each { |attribute_group| required_files_attribute_groups(attribute_group) }
615
+ when :complex_content
616
+ required_files_complex_content(value)
617
+ when :simple_content
618
+ required_files_simple_content(value)
619
+ end
620
+ end
621
+ @required_files.uniq.sort_by(&:length)
622
+ end
623
+
624
+ # END: TEMPLATE RESOLVER METHODS
625
+
626
+ # START: REQUIRED FILES LIST COMPILER METHODS
627
+ def required_files_simple_content(simple_content)
628
+ simple_content.each do |key, value|
629
+ case key
630
+ when :extension_base
631
+ # Do nothing.
632
+ when :attributes
633
+ required_files_attribute(value)
634
+ when :extension
635
+ required_files_extension(value)
636
+ when :restriction
637
+ required_files_restriction(value)
638
+ end
639
+ end
640
+ end
641
+
642
+ def required_files_complex_content(complex_content)
643
+ complex_content.each do |key, value|
644
+ case key
645
+ when :extension
646
+ required_files_extension(value)
647
+ when :restriction
648
+ required_files_restriction(value)
649
+ end
650
+ end
651
+ end
652
+
653
+ def required_files_extension(extension)
654
+ extension.each do |key, value|
655
+ case key
656
+ when :attribute_group
657
+ required_files_attribute_groups(value)
658
+ when :attribute, :attributes
659
+ required_files_attribute(value)
660
+ when :extension_base
661
+ # Do nothing.
662
+ when :sequence
663
+ required_files_sequence(value)
664
+ when :choice
665
+ required_files_choice(value)
666
+ end
667
+ end
668
+ end
669
+
670
+ def required_files_restriction(restriction)
671
+ restriction.each do |key, value|
672
+ case key
673
+ when :base
674
+ @required_files << Utils.snake_case(value.split(":").last)
675
+ end
676
+ end
677
+ end
678
+
679
+ def required_files_attribute_groups(attr_groups)
680
+ attr_groups.each do |key, value|
681
+ case key
682
+ when :ref_class
683
+ required_files_attribute_groups(@attribute_groups[value.split(":").last])
684
+ when :attribute, :attributes
685
+ required_files_attribute(value)
686
+ end
687
+ end
688
+ end
689
+
690
+ def required_files_attribute(attributes)
691
+ attributes.each do |attribute|
692
+ next if attribute[:ref_class]&.start_with?("xml") || attribute[:base_class]&.start_with?("xml")
693
+
694
+ attribute = @attributes[attribute.ref_class.split(":").last] if attribute.key_exist?(:ref_class)
695
+ attr_class = attribute.base_class.split(":")&.last
696
+ next if DEFAULT_CLASSES.include?(attr_class)
697
+
698
+ @required_files << Utils.snake_case(attr_class)
699
+ end
700
+ end
701
+
702
+ def required_files_choice(choice)
703
+ choice.each do |key, value|
704
+ case key
705
+ when String
706
+ value = @elements[value.ref_class.split(":").last] if value.key?(:ref_class)
707
+ @required_files << Utils.snake_case(value.type_name.split(":").last)
708
+ when :element
709
+ required_files_elements(value)
710
+ when :group
711
+ required_files_group(value)
712
+ when :choice
713
+ required_files_choice(value)
714
+ when :sequence
715
+ required_files_sequence(value)
716
+ end
717
+ end
718
+ end
719
+
720
+ def required_files_group(group)
721
+ group.each do |key, value|
722
+ case key
723
+ when :ref_class
724
+ required_files_group(@group_types[value.split(":").last])
725
+ when :choice
726
+ required_files_choice(value)
727
+ when :sequence
728
+ required_files_sequence(value)
729
+ end
730
+ end
731
+ end
732
+
733
+ def required_files_sequence(sequence)
734
+ sequence.each do |key, value|
735
+ case key
736
+ when :elements
737
+ required_files_elements(value)
738
+ when :sequence
739
+ required_files_sequence(value)
740
+ when :groups
741
+ value.each { |group| required_files_group(group) }
742
+ when :choice
743
+ value.each { |choice| required_files_choice(choice) }
744
+ end
745
+ end
746
+ end
747
+
748
+ def required_files_elements(elements)
749
+ elements.each do |element|
750
+ element = @elements[element.ref_class.split(":").last] if element.key_exist?(:ref_class)
751
+ element_class = element.type_name.split(":").last
752
+ next if DEFAULT_CLASSES.include?(element_class)
753
+
754
+ @required_files << Utils.snake_case(element_class)
755
+ end
756
+ end
757
+
758
+ # END: REQUIRED FILES LIST COMPILER METHODS
759
+ end
760
+ end
761
+ end
762
+ end