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
@@ -6,8 +6,8 @@ require_relative "element"
6
6
 
7
7
  module Lutaml
8
8
  module Model
9
- module XmlAdapter
10
- class XmlDocument
9
+ module Xml
10
+ class Document
11
11
  attr_reader :root, :encoding
12
12
 
13
13
  def initialize(root, encoding = nil)
@@ -154,11 +154,11 @@ module Lutaml
154
154
  end
155
155
 
156
156
  # Only transform when recursion is not called
157
- if (!attribute.collection? || value.is_a?(Array)) && transform_method = rule.transform[:export] || attribute.transform_export_method
158
- value = transform_method.call(value)
157
+ if !attribute.collection? || value.is_a?(Array)
158
+ value = ExportTransformer.call(value, rule, attribute)
159
159
  end
160
160
 
161
- if value.is_a?(Array)
161
+ if value.is_a?(Array) && !Utils.empty_collection?(value)
162
162
  value.each do |item|
163
163
  add_to_xml(xml, element, prefix, item, options)
164
164
  end
@@ -168,12 +168,18 @@ module Lutaml
168
168
 
169
169
  return if !render_element?(rule, element, value)
170
170
 
171
+ value = rule.render_value_for(value)
172
+
171
173
  if value && (attribute&.type&.<= Lutaml::Model::Serialize)
172
174
  handle_nested_elements(
173
175
  xml,
174
176
  value,
175
177
  options.merge({ rule: rule, attribute: attribute }),
176
178
  )
179
+ elsif value.nil?
180
+ xml.create_and_add_element(rule.name, attributes: { "xsi:nil" => true })
181
+ elsif Utils.empty?(value)
182
+ xml.create_and_add_element(rule.name)
177
183
  elsif rule.raw_mapping?
178
184
  xml.add_xml_fragment(xml, value)
179
185
  elsif rule.prefix_set?
@@ -205,19 +211,12 @@ module Lutaml
205
211
  end
206
212
 
207
213
  def build_unordered_element(xml, element, options = {})
208
- mapper_class = options[:mapper_class] || element.class
214
+ mapper_class = determine_mapper_class(element, options)
209
215
  xml_mapping = mapper_class.mappings_for(:xml)
210
216
  return xml unless xml_mapping
211
217
 
212
- attributes = options[:xml_attributes] ||= {}
213
- attributes = build_attributes(element,
214
- xml_mapping, options).merge(attributes)&.compact
215
-
216
- prefix = if options.key?(:namespace_prefix)
217
- options[:namespace_prefix]
218
- elsif xml_mapping.namespace_prefix
219
- xml_mapping.namespace_prefix
220
- end
218
+ attributes = build_element_attributes(element, xml_mapping, options)
219
+ prefix = determine_namespace_prefix(options, xml_mapping)
221
220
 
222
221
  prefixed_xml = xml.add_namespace_prefix(prefix)
223
222
  tag_name = options[:tag_name] || xml_mapping.root_element
@@ -241,7 +240,7 @@ module Lutaml
241
240
  if attribute_def
242
241
  value = attribute_value_for(element, element_rule)
243
242
 
244
- next if value.nil? && !element_rule.render_nil?
243
+ next if !element_rule.render?(value, element)
245
244
 
246
245
  value = [value] if attribute_def.collection? && !value.is_a?(Array)
247
246
  end
@@ -289,11 +288,7 @@ module Lutaml
289
288
  end
290
289
 
291
290
  def render_element?(rule, element, value)
292
- render_default?(rule, element) && render_value?(rule, value)
293
- end
294
-
295
- def render_value?(rule, value)
296
- rule.attribute? || rule.render_nil? || !value.nil?
291
+ rule.render?(value, element)
297
292
  end
298
293
 
299
294
  def render_default?(rule, element)
@@ -370,6 +365,8 @@ module Lutaml
370
365
  attr = attribute_definition_for(element, mapping_rule, mapper_class: options[:mapper_class])
371
366
  value = attr.serialize(value, :xml) if attr
372
367
 
368
+ value = ExportTransformer.call(value, mapping_rule, attr)
369
+
373
370
  if render_element?(mapping_rule, element, value)
374
371
  hash[mapping_rule.prefixed_name] = value ? value.to_s : value
375
372
  end
@@ -433,6 +430,29 @@ module Lutaml
433
430
  def cdata
434
431
  @root.cdata
435
432
  end
433
+
434
+ private
435
+
436
+ def determine_mapper_class(element, options)
437
+ if options[:mapper_class] && element.is_a?(options[:mapper_class])
438
+ element.class
439
+ else
440
+ options[:mapper_class] || element.class
441
+ end
442
+ end
443
+
444
+ def determine_namespace_prefix(options, mapping)
445
+ return options[:namespace_prefix] if options.key?(:namespace_prefix)
446
+
447
+ mapping.namespace_prefix
448
+ end
449
+
450
+ def build_element_attributes(element, mapping, options)
451
+ xml_attributes = options[:xml_attributes] ||= {}
452
+ attributes = build_attributes(element, mapping, options)
453
+
454
+ attributes.merge(xml_attributes)&.compact
455
+ end
436
456
  end
437
457
  end
438
458
  end
@@ -0,0 +1,32 @@
1
+ module Lutaml
2
+ module Model
3
+ module Xml
4
+ class Element
5
+ attr_reader :type, :name
6
+
7
+ def initialize(type, name)
8
+ @type = type
9
+ @name = name
10
+ end
11
+
12
+ def text?
13
+ @type == "Text" && @name != "#cdata-section"
14
+ end
15
+
16
+ def element_tag
17
+ @name unless text?
18
+ end
19
+
20
+ def eql?(other)
21
+ return false unless other.is_a?(self.class)
22
+
23
+ instance_variables.all? do |var|
24
+ instance_variable_get(var) == other.instance_variable_get(var)
25
+ end
26
+ end
27
+
28
+ alias == eql?
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,410 @@
1
+ require_relative "../mapping/mapping"
2
+ require_relative "mapping_rule"
3
+
4
+ module Lutaml
5
+ module Model
6
+ module Xml
7
+ class Mapping < Mapping
8
+ TYPES = {
9
+ attribute: :map_attribute,
10
+ element: :map_element,
11
+ content: :map_content,
12
+ all_content: :map_all,
13
+ }.freeze
14
+
15
+ attr_reader :root_element,
16
+ :namespace_uri,
17
+ :namespace_prefix,
18
+ :mixed_content,
19
+ :ordered,
20
+ :element_sequence
21
+
22
+ def initialize
23
+ super
24
+
25
+ @elements = {}
26
+ @attributes = {}
27
+ @element_sequence = []
28
+ @content_mapping = nil
29
+ @raw_mapping = nil
30
+ @mixed_content = false
31
+ @format = :xml
32
+ end
33
+
34
+ def finalize(mapper_class)
35
+ if !root_element && !no_root?
36
+ root(mapper_class.model.to_s)
37
+ end
38
+ end
39
+
40
+ alias mixed_content? mixed_content
41
+ alias ordered? ordered
42
+
43
+ def root(name, mixed: false, ordered: false)
44
+ @root_element = name
45
+ @mixed_content = mixed
46
+ @ordered = ordered || mixed # mixed contenet will always be ordered
47
+ end
48
+
49
+ def root?
50
+ !!root_element
51
+ end
52
+
53
+ def no_root
54
+ @no_root = true
55
+ end
56
+
57
+ def no_root?
58
+ !!@no_root
59
+ end
60
+
61
+ def prefixed_root
62
+ if namespace_uri && namespace_prefix
63
+ "#{namespace_prefix}:#{root_element}"
64
+ else
65
+ root_element
66
+ end
67
+ end
68
+
69
+ def namespace(uri, prefix = nil)
70
+ raise Lutaml::Model::NoRootNamespaceError if no_root?
71
+
72
+ @namespace_uri = uri
73
+ @namespace_prefix = prefix
74
+ end
75
+
76
+ # rubocop:disable Metrics/ParameterLists
77
+ def map_element(
78
+ name,
79
+ to: nil,
80
+ render_nil: false,
81
+ render_default: false,
82
+ render_empty: false,
83
+ treat_nil: :nil,
84
+ treat_empty: :empty,
85
+ treat_omitted: :nil,
86
+ with: {},
87
+ delegate: nil,
88
+ cdata: false,
89
+ polymorphic: {},
90
+ namespace: (namespace_set = false
91
+ nil),
92
+ prefix: (prefix_set = false
93
+ nil),
94
+ transform: {},
95
+ value_map: {}
96
+ )
97
+ validate!(
98
+ name, to, with, render_nil, render_empty, type: TYPES[:element]
99
+ )
100
+
101
+ rule = MappingRule.new(
102
+ name,
103
+ to: to,
104
+ render_nil: render_nil,
105
+ render_default: render_default,
106
+ render_empty: render_empty,
107
+ treat_nil: treat_nil,
108
+ treat_empty: treat_empty,
109
+ treat_omitted: treat_omitted,
110
+ with: with,
111
+ delegate: delegate,
112
+ cdata: cdata,
113
+ namespace: namespace,
114
+ default_namespace: namespace_uri,
115
+ prefix: prefix,
116
+ polymorphic: polymorphic,
117
+ namespace_set: namespace_set != false,
118
+ prefix_set: prefix_set != false,
119
+ transform: transform,
120
+ value_map: value_map,
121
+ )
122
+ @elements[rule.namespaced_name] = rule
123
+ end
124
+
125
+ def map_attribute(
126
+ name,
127
+ to: nil,
128
+ render_nil: false,
129
+ render_default: false,
130
+ render_empty: false,
131
+ with: {},
132
+ delegate: nil,
133
+ polymorphic_map: {},
134
+ namespace: (namespace_set = false
135
+ nil),
136
+ prefix: (prefix_set = false
137
+ nil),
138
+ transform: {},
139
+ value_map: {}
140
+ )
141
+ validate!(
142
+ name, to, with, render_nil, render_empty, type: TYPES[:attribute]
143
+ )
144
+
145
+ if name == "schemaLocation"
146
+ Logger.warn_auto_handling(
147
+ name: name,
148
+ caller_file: File.basename(caller_locations(1, 1)[0].path),
149
+ caller_line: caller_locations(1, 1)[0].lineno,
150
+ )
151
+ end
152
+
153
+ rule = MappingRule.new(
154
+ name,
155
+ to: to,
156
+ render_nil: render_nil,
157
+ render_default: render_default,
158
+ with: with,
159
+ delegate: delegate,
160
+ namespace: namespace,
161
+ prefix: prefix,
162
+ attribute: true,
163
+ polymorphic_map: polymorphic_map,
164
+ default_namespace: namespace_uri,
165
+ namespace_set: namespace_set != false,
166
+ prefix_set: prefix_set != false,
167
+ transform: transform,
168
+ value_map: value_map,
169
+ )
170
+ @attributes[rule.namespaced_name] = rule
171
+ end
172
+
173
+ # rubocop:enable Metrics/ParameterLists
174
+
175
+ def map_content(
176
+ to: nil,
177
+ render_nil: false,
178
+ render_default: false,
179
+ render_empty: false,
180
+ with: {},
181
+ delegate: nil,
182
+ mixed: false,
183
+ cdata: false,
184
+ transform: {},
185
+ value_map: {}
186
+ )
187
+ validate!(
188
+ "content", to, with, render_nil, render_empty, type: TYPES[:content]
189
+ )
190
+
191
+ @content_mapping = MappingRule.new(
192
+ nil,
193
+ to: to,
194
+ render_nil: render_nil,
195
+ render_default: render_default,
196
+ render_empty: render_empty,
197
+ with: with,
198
+ delegate: delegate,
199
+ mixed_content: mixed,
200
+ cdata: cdata,
201
+ transform: transform,
202
+ value_map: value_map,
203
+ )
204
+ end
205
+
206
+ def map_all(
207
+ to:,
208
+ render_nil: false,
209
+ render_default: false,
210
+ delegate: nil,
211
+ with: {},
212
+ namespace: (namespace_set = false
213
+ nil),
214
+ prefix: (prefix_set = false
215
+ nil),
216
+ render_empty: false
217
+ )
218
+ validate!(
219
+ Constants::RAW_MAPPING_KEY,
220
+ to,
221
+ with,
222
+ render_nil,
223
+ render_empty,
224
+ type: TYPES[:all_content],
225
+ )
226
+
227
+ rule = MappingRule.new(
228
+ Constants::RAW_MAPPING_KEY,
229
+ to: to,
230
+ render_nil: render_nil,
231
+ render_default: render_default,
232
+ with: with,
233
+ delegate: delegate,
234
+ namespace: namespace,
235
+ prefix: prefix,
236
+ default_namespace: namespace_uri,
237
+ namespace_set: namespace_set != false,
238
+ prefix_set: prefix_set != false,
239
+ )
240
+
241
+ @raw_mapping = rule
242
+ end
243
+
244
+ alias map_all_content map_all
245
+
246
+ def sequence(&block)
247
+ @element_sequence << Sequence.new(self).tap do |s|
248
+ s.instance_eval(&block)
249
+ end
250
+ end
251
+
252
+ def import_model_mappings(model)
253
+ raise Lutaml::Model::ImportModelWithRootError.new(model) if model.root?
254
+
255
+ mappings = model.mappings_for(:xml)
256
+ @elements.merge!(mappings.instance_variable_get(:@elements))
257
+ @attributes.merge!(mappings.instance_variable_get(:@attributes))
258
+ (@element_sequence << mappings.element_sequence).flatten!
259
+ end
260
+
261
+ def validate!(key, to, with, render_nil, render_empty, type: nil)
262
+ validate_raw_mappings!(type)
263
+ validate_to_and_with_arguments!(key, to, with)
264
+
265
+ if render_nil == :as_empty || render_empty == :as_empty
266
+ raise IncorrectMappingArgumentsError.new(
267
+ ":as_empty is not supported for XML mappings",
268
+ )
269
+ end
270
+ end
271
+
272
+ def validate_to_and_with_arguments!(key, to, with)
273
+ if to.nil? && with.empty?
274
+ raise IncorrectMappingArgumentsError.new(
275
+ ":to or :with argument is required for mapping '#{key}'",
276
+ )
277
+ end
278
+
279
+ validate_with_options!(key, to, with)
280
+ end
281
+
282
+ def validate_with_options!(key, to, with)
283
+ return true if to
284
+
285
+ if !with.empty? && (with[:from].nil? || with[:to].nil?)
286
+ raise IncorrectMappingArgumentsError.new(
287
+ ":with argument for mapping '#{key}' requires :to and :from keys",
288
+ )
289
+ end
290
+ end
291
+
292
+ def validate_raw_mappings!(type)
293
+ if !@raw_mapping.nil? && type != TYPES[:attribute]
294
+ raise StandardError, "#{type} is not allowed, only #{TYPES[:attribute]} " \
295
+ "is allowed with #{TYPES[:all_content]}"
296
+ end
297
+
298
+ if !(elements.empty? && content_mapping.nil?) && type == TYPES[:all_content]
299
+ raise StandardError, "#{TYPES[:all_content]} is not allowed with other mappings"
300
+ end
301
+ end
302
+
303
+ def elements
304
+ @elements.values
305
+ end
306
+
307
+ def attributes
308
+ @attributes.values
309
+ end
310
+
311
+ def content_mapping
312
+ @content_mapping
313
+ end
314
+
315
+ def raw_mapping
316
+ @raw_mapping
317
+ end
318
+
319
+ def mappings
320
+ elements + attributes + [content_mapping, raw_mapping].compact
321
+ end
322
+
323
+ def element(name)
324
+ elements.detect do |rule|
325
+ name == rule.to
326
+ end
327
+ end
328
+
329
+ def attribute(name)
330
+ attributes.detect do |rule|
331
+ name == rule.to
332
+ end
333
+ end
334
+
335
+ def find_by_name(name)
336
+ if ["text", "#cdata-section"].include?(name.to_s)
337
+ content_mapping
338
+ else
339
+ mappings.detect do |rule|
340
+ rule.name == name.to_s || rule.name == name.to_sym
341
+ end
342
+ end
343
+ end
344
+
345
+ def mapping_attributes_hash
346
+ @attributes
347
+ end
348
+
349
+ def mapping_elements_hash
350
+ @elements
351
+ end
352
+
353
+ def merge_mapping_attributes(mapping)
354
+ mapping_attributes_hash.merge!(mapping.mapping_attributes_hash)
355
+ end
356
+
357
+ def merge_mapping_elements(mapping)
358
+ mapping_elements_hash.merge!(mapping.mapping_elements_hash)
359
+ end
360
+
361
+ def merge_elements_sequence(mapping)
362
+ mapping.element_sequence.each do |sequence|
363
+ element_sequence << Lutaml::Model::Sequence.new(self).tap do |instance|
364
+ sequence.attributes.each do |attr|
365
+ instance.attributes << attr.deep_dup
366
+ end
367
+ end
368
+ end
369
+ end
370
+
371
+ def deep_dup
372
+ self.class.new.tap do |xml_mapping|
373
+ xml_mapping.root(@root_element.dup, mixed: @mixed_content,
374
+ ordered: @ordered)
375
+ xml_mapping.namespace(@namespace_uri.dup, @namespace_prefix.dup)
376
+
377
+ attributes_to_dup.each do |var_name|
378
+ value = instance_variable_get(var_name)
379
+ xml_mapping.instance_variable_set(var_name, Utils.deep_dup(value))
380
+ end
381
+ end
382
+ end
383
+
384
+ def polymorphic_mapping
385
+ mappings.find(&:polymorphic_mapping?)
386
+ end
387
+
388
+ def attributes_to_dup
389
+ @attributes_to_dup ||= %i[
390
+ @content_mapping
391
+ @raw_mapping
392
+ @element_sequence
393
+ @attributes
394
+ @elements
395
+ ]
396
+ end
397
+
398
+ def dup_mappings(mappings)
399
+ new_mappings = {}
400
+
401
+ mappings.each do |key, mapping_rule|
402
+ new_mappings[key] = mapping_rule.deep_dup
403
+ end
404
+
405
+ new_mappings
406
+ end
407
+ end
408
+ end
409
+ end
410
+ end
@@ -0,0 +1,141 @@
1
+ require_relative "../mapping/mapping_rule"
2
+
3
+ module Lutaml
4
+ module Model
5
+ module Xml
6
+ class MappingRule < MappingRule
7
+ attr_reader :namespace, :prefix, :mixed_content, :default_namespace, :cdata
8
+
9
+ def initialize(
10
+ name,
11
+ to:,
12
+ render_nil: false,
13
+ render_default: false,
14
+ render_empty: false,
15
+ treat_nil: nil,
16
+ treat_empty: nil,
17
+ treat_omitted: nil,
18
+ with: {},
19
+ delegate: nil,
20
+ namespace: nil,
21
+ prefix: nil,
22
+ mixed_content: false,
23
+ cdata: false,
24
+ namespace_set: false,
25
+ prefix_set: false,
26
+ attribute: false,
27
+ default_namespace: nil,
28
+ polymorphic: {},
29
+ polymorphic_map: {},
30
+ transform: {},
31
+ value_map: {}
32
+ )
33
+ super(
34
+ name,
35
+ to: to,
36
+ render_nil: render_nil,
37
+ render_default: render_default,
38
+ render_empty: render_empty,
39
+ treat_nil: treat_nil,
40
+ treat_empty: treat_empty,
41
+ treat_omitted: treat_omitted,
42
+ with: with,
43
+ delegate: delegate,
44
+ attribute: attribute,
45
+ polymorphic: polymorphic,
46
+ polymorphic_map: polymorphic_map,
47
+ transform: transform,
48
+ value_map: value_map,
49
+ )
50
+
51
+ @namespace = if namespace.to_s == "inherit"
52
+ # we are using inherit_namespace in xml builder by
53
+ # default so no need to do anything here.
54
+ else
55
+ namespace
56
+ end
57
+ @prefix = prefix
58
+ @mixed_content = mixed_content
59
+ @cdata = cdata
60
+
61
+ @default_namespace = default_namespace
62
+
63
+ @namespace_set = namespace_set
64
+ @prefix_set = prefix_set
65
+ end
66
+
67
+ def namespace_set?
68
+ !!@namespace_set
69
+ end
70
+
71
+ def prefix_set?
72
+ !!@prefix_set
73
+ end
74
+
75
+ def content_mapping?
76
+ name.nil?
77
+ end
78
+
79
+ def content_key
80
+ cdata ? "#cdata-section" : "text"
81
+ end
82
+
83
+ def mixed_content?
84
+ !!@mixed_content
85
+ end
86
+
87
+ def prefixed_name
88
+ rule_name = multiple_mappings? ? name.first : name
89
+ if prefix
90
+ "#{prefix}:#{rule_name}"
91
+ else
92
+ rule_name
93
+ end
94
+ end
95
+
96
+ def namespaced_names(parent_namespace = nil)
97
+ if multiple_mappings?
98
+ name.map { |rule_name| namespaced_name(parent_namespace, rule_name) }
99
+ else
100
+ [namespaced_name(parent_namespace)]
101
+ end
102
+ end
103
+
104
+ def namespaced_name(parent_namespace = nil, name = self.name)
105
+ if name.to_s == "lang"
106
+ "#{prefix}:#{name}"
107
+ elsif namespace_set? || @attribute
108
+ [namespace, name].compact.join(":")
109
+ elsif default_namespace
110
+ "#{default_namespace}:#{name}"
111
+ else
112
+ [parent_namespace, name].compact.join(":")
113
+ end
114
+ end
115
+
116
+ def deep_dup
117
+ self.class.new(
118
+ name.dup,
119
+ to: to,
120
+ render_nil: render_nil,
121
+ render_default: render_default,
122
+ with: Utils.deep_dup(custom_methods),
123
+ delegate: delegate,
124
+ namespace: namespace.dup,
125
+ prefix: prefix.dup,
126
+ mixed_content: mixed_content,
127
+ cdata: cdata,
128
+ namespace_set: namespace_set?,
129
+ prefix_set: prefix_set?,
130
+ attribute: attribute,
131
+ polymorphic: polymorphic.dup,
132
+ default_namespace: default_namespace.dup,
133
+ transform: transform.dup,
134
+ render_empty: render_empty.dup,
135
+ value_map: Utils.deep_dup(@value_map),
136
+ )
137
+ end
138
+ end
139
+ end
140
+ end
141
+ end