lutaml-model 0.8.5 → 0.8.6

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 (35) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/dependent-tests.yml +4 -1
  3. data/.rubocop_todo.yml +97 -22
  4. data/docs/_migrations/0-8-0-namespace-restructuring.adoc +90 -0
  5. data/lib/lutaml/model/version.rb +1 -1
  6. data/lib/lutaml/xml/adapter/adapter_helpers.rb +1 -42
  7. data/lib/lutaml/xml/adapter/base_adapter.rb +48 -458
  8. data/lib/lutaml/xml/adapter/namespace_data.rb +0 -17
  9. data/lib/lutaml/xml/adapter/namespace_uri_collector.rb +71 -0
  10. data/lib/lutaml/xml/adapter/nokogiri_adapter.rb +5 -1110
  11. data/lib/lutaml/xml/adapter/oga_adapter.rb +6 -846
  12. data/lib/lutaml/xml/adapter/ox_adapter.rb +7 -884
  13. data/lib/lutaml/xml/adapter/plan_based_builder.rb +929 -0
  14. data/lib/lutaml/xml/adapter/rexml_adapter.rb +10 -864
  15. data/lib/lutaml/xml/adapter/xml_parser.rb +86 -0
  16. data/lib/lutaml/xml/adapter/xml_serializer.rb +291 -0
  17. data/lib/lutaml/xml/adapter.rb +0 -1
  18. data/lib/lutaml/xml/adapter_element.rb +7 -1
  19. data/lib/lutaml/xml/builder/base.rb +0 -1
  20. data/lib/lutaml/xml/data_model.rb +9 -1
  21. data/lib/lutaml/xml/document.rb +3 -1
  22. data/lib/lutaml/xml/element.rb +13 -10
  23. data/lib/lutaml/xml/serialization/format_conversion.rb +19 -42
  24. data/lib/lutaml/xml/serialization/instance_methods.rb +26 -35
  25. data/lib/lutaml/xml/transformation/custom_method_wrapper.rb +34 -55
  26. data/lib/lutaml/xml/transformation/rule_applier.rb +1 -1
  27. data/lib/lutaml/xml/xml_element.rb +24 -20
  28. data/spec/lutaml/xml/adapter/base_adapter_regression_spec.rb +151 -0
  29. data/spec/lutaml/xml/adapter/order_spec.rb +150 -0
  30. data/spec/lutaml/xml/clear_parse_state_spec.rb +139 -0
  31. data/spec/lutaml/xml/doubly_defined_namespace_spec.rb +0 -2
  32. data/spec/lutaml/xml/schema/compiler_spec.rb +75 -69
  33. data/spec/lutaml/xml/transformation/custom_method_wrapper_spec.rb +213 -14
  34. metadata +9 -3
  35. data/lib/lutaml/xml/adapter/xml_serialization.rb +0 -145
@@ -0,0 +1,86 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lutaml
4
+ module Xml
5
+ module Adapter
6
+ # Class methods for parsing XML input.
7
+ #
8
+ # Extracted from BaseAdapter — parsing is a distinct lifecycle phase
9
+ # with no instance state dependency.
10
+ #
11
+ # Subclasses must define:
12
+ # - MOXML_ADAPTER — Moxml adapter class for parsing
13
+ # - PARSED_ELEMENT_CLASS — element wrapper class
14
+ # - PARSE_ERROR_CLASS — error class to rescue (nil to skip)
15
+ # - EMPTY_DOCUMENT_ERROR_MESSAGE — error message for empty docs
16
+ # - EMPTY_DOCUMENT_ERROR_TYPE — :invalid_format or :parse_exception
17
+ module XmlParser
18
+ def parse(xml, options = {})
19
+ parse_encoding = encoding(xml, options)
20
+ raw_xml = xml
21
+ xml = normalize_xml_for_parse(xml)
22
+ parsed = parse_with_moxml(xml, parse_encoding)
23
+ root_element = parsed.root
24
+
25
+ raise_empty_document_error if root_element.nil?
26
+
27
+ root = self::PARSED_ELEMENT_CLASS.new(root_element)
28
+ doc_pis = extract_document_processing_instructions(parsed)
29
+ root.processing_instructions = doc_pis unless doc_pis.empty?
30
+ new(root, parse_encoding, **parse_document_options(raw_xml))
31
+ end
32
+
33
+ private
34
+
35
+ def normalize_xml_for_parse(xml)
36
+ return xml unless xml.is_a?(String)
37
+ return xml if xml.encoding == Encoding::UTF_8 && xml.valid_encoding?
38
+
39
+ if xml.encoding == Encoding::ASCII_8BIT
40
+ normalized_xml = xml.dup
41
+ normalized_xml.force_encoding(Encoding::UTF_8)
42
+ return normalized_xml if normalized_xml.valid_encoding?
43
+ end
44
+
45
+ xml.encode(Encoding::UTF_8,
46
+ invalid: :replace,
47
+ undef: :replace,
48
+ replace: "?")
49
+ end
50
+
51
+ def parse_with_moxml(xml, parse_encoding)
52
+ parse_error_class = self::PARSE_ERROR_CLASS
53
+ unless parse_error_class
54
+ return self::MOXML_ADAPTER.parse(xml,
55
+ encoding: parse_encoding)
56
+ end
57
+
58
+ begin
59
+ self::MOXML_ADAPTER.parse(xml, encoding: parse_encoding)
60
+ rescue parse_error_class => e
61
+ raise Lutaml::Model::InvalidFormatError.new(:xml, e.message)
62
+ end
63
+ end
64
+
65
+ def parse_document_options(xml)
66
+ {
67
+ doctype: extract_doctype_from_xml(xml),
68
+ xml_declaration: DeclarationHandler.extract_xml_declaration(xml),
69
+ }
70
+ end
71
+
72
+ def raise_empty_document_error
73
+ message = self::EMPTY_DOCUMENT_ERROR_MESSAGE
74
+
75
+ case self::EMPTY_DOCUMENT_ERROR_TYPE
76
+ when :parse_exception
77
+ require "rexml/document"
78
+ raise REXML::ParseException.new(message)
79
+ else
80
+ raise Lutaml::Model::InvalidFormatError.new(:xml, message)
81
+ end
82
+ end
83
+ end
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,291 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lutaml
4
+ module Xml
5
+ module Adapter
6
+ # Handles the XML serialization pipeline.
7
+ #
8
+ # Responsible for converting model instances and XmlElement trees
9
+ # into XML output via builder objects. Includes the top-level
10
+ # `to_xml` entry point and supporting methods for rendering
11
+ # XmlElement structures with namespace declaration plans.
12
+ module XmlSerializer
13
+ # Add text content to XML builder
14
+ #
15
+ # @param xml [Builder] the XML builder
16
+ # @param value [Object] the value to add
17
+ # @param attribute [Attribute, nil] the attribute definition
18
+ # @param cdata [Boolean] whether to use CDATA
19
+ def add_value(xml, value, attribute, cdata: false)
20
+ if !value.nil?
21
+ if attribute.nil?
22
+ # For delegated attributes where attribute is nil, just use the raw value
23
+ xml.add_text(xml, value.to_s, cdata: cdata)
24
+ elsif attribute.transform.is_a?(Class) && attribute.transform < Lutaml::Model::ValueTransformer
25
+ # Value has already been transformed, use it directly
26
+ xml.add_text(xml, value.to_s, cdata: cdata)
27
+ else
28
+ # Normal serialization through attribute type system
29
+ serialized_value = attribute.serialize(value, :xml, register)
30
+ if attribute.raw?
31
+ xml.add_xml_fragment(xml, value)
32
+ elsif serialized_value.is_a?(Hash)
33
+ serialized_value.each do |key, val|
34
+ xml.create_and_add_element(key) do |element|
35
+ element.text(val)
36
+ end
37
+ end
38
+ else
39
+ xml.add_text(xml, serialized_value, cdata: cdata)
40
+ end
41
+ end
42
+ end
43
+ end
44
+
45
+ def to_xml(options = {})
46
+ # Accept xml_declaration from options if present (for model serialization)
47
+ @xml_declaration = options[:xml_declaration] if options[:xml_declaration]
48
+
49
+ encoding = determine_encoding(options)
50
+ builder_options = {}
51
+ builder_options[:encoding] = encoding if encoding
52
+
53
+ builder = self.class::BUILDER_CLASS.build(builder_options) do |xml|
54
+ if root.is_a?(self.class::PARSED_ELEMENT_CLASS)
55
+ root.build_xml(xml)
56
+ else
57
+ build_serializable_xml(xml, options)
58
+ end
59
+ end
60
+
61
+ finalize_adapter_xml(builder.to_xml, encoding, options)
62
+ end
63
+
64
+ def build_serializable_xml(xml, options)
65
+ original_model = nil
66
+ xml_element = transformable_xml_element(options) do |model|
67
+ original_model = model
68
+ end
69
+
70
+ if xml_element
71
+ render_xml_element(xml, xml_element, original_model, options)
72
+ else
73
+ render_legacy_model(xml, options)
74
+ end
75
+ end
76
+
77
+ # Build XML from XmlDataModel::XmlElement structure with a declaration plan
78
+ #
79
+ # @param builder [Builder] XML builder
80
+ # @param xml_element [XmlDataModel::XmlElement] root element
81
+ # @param plan [DeclarationPlan] the declaration plan
82
+ # @param options [Hash] serialization options
83
+ def build_xml_element_with_plan(builder, xml_element, plan,
84
+ options = {})
85
+ # Add processing instructions before the root element
86
+ if xml_element.respond_to?(:processing_instructions)
87
+ xml_element.processing_instructions.each do |pi|
88
+ builder.add_processing_instruction(pi.target, pi.content)
89
+ end
90
+ end
91
+
92
+ build_plan_node(builder, xml_element, plan.root_node, plan: plan,
93
+ options: options)
94
+ end
95
+
96
+ private
97
+
98
+ def transformable_xml_element(options)
99
+ return root if root.is_a?(Lutaml::Xml::DataModel::XmlElement)
100
+
101
+ mapper_class = options[:mapper_class] || root.class
102
+ xml_mapping = mapper_class.mappings_for(:xml)
103
+
104
+ return nil if xml_mapping.raw_mapping&.custom_methods&.[](:to)
105
+
106
+ yield(root)
107
+ mapper_class.transformation_for(:xml, register).transform(root,
108
+ options)
109
+ end
110
+
111
+ def render_xml_element(xml, xml_element, original_model, options)
112
+ mapper_class = options[:mapper_class] || xml_element.class
113
+ mapping = mapper_class.mappings_for(:xml)
114
+ plan = declaration_plan_for(
115
+ xml_element,
116
+ mapping,
117
+ options_with_original_namespace_data(options, original_model,
118
+ xml_element),
119
+ mapper_class,
120
+ )
121
+
122
+ render_options = options.merge(is_root_element: true)
123
+ render_options[:original_model] = original_model if original_model
124
+ build_xml_element_with_plan(xml, xml_element, plan, render_options)
125
+ end
126
+
127
+ def render_legacy_model(xml, options)
128
+ mapper_class = options[:mapper_class] || root.class
129
+ xml_mapping = mapper_class.mappings_for(:xml)
130
+ plan = declaration_plan_for(root, xml_mapping, options, mapper_class)
131
+
132
+ build_element_with_plan(xml, root, plan, options)
133
+ end
134
+
135
+ def declaration_plan_for(element, mapping, options, mapper_class)
136
+ needs = NamespaceCollector.new(register).collect(
137
+ element, mapping, mapper_class: mapper_class
138
+ )
139
+ DeclarationPlanner.new(register).plan(element, mapping, needs,
140
+ options: options)
141
+ end
142
+
143
+ def options_with_original_namespace_data(options, original_model,
144
+ xml_element)
145
+ original_ns_uris = {}
146
+ stored_plan = nil
147
+
148
+ if original_model
149
+ mapping_for_original = options[:mapper_class]&.mappings_for(:xml) ||
150
+ original_model.class.mappings_for(:xml)
151
+ original_ns_uris = collect_original_namespace_uris(
152
+ original_model, mapping_for_original
153
+ )
154
+ if original_model.is_a?(Lutaml::Model::Serialize)
155
+ stored_plan = original_model.import_declaration_plan
156
+ end
157
+ elsif xml_element.is_a?(Lutaml::Xml::DataModel::XmlElement)
158
+ original_ns_uri = xml_element.original_namespace_uri
159
+ if original_ns_uri
160
+ mapper_class = options[:mapper_class] || xml_element.class
161
+ xml_mapping = begin
162
+ mapper_class.mappings_for(:xml)
163
+ rescue StandardError
164
+ nil
165
+ end
166
+ if xml_mapping&.namespace_class
167
+ canonical_uri = xml_mapping.namespace_class.uri
168
+ if canonical_uri != original_ns_uri
169
+ original_ns_uris[canonical_uri] =
170
+ original_ns_uri
171
+ end
172
+ end
173
+ end
174
+ end
175
+
176
+ options_with_original_ns = options.merge(
177
+ __original_namespace_uris: original_ns_uris,
178
+ )
179
+ if stored_plan
180
+ options_with_original_ns[:stored_xml_declaration_plan] =
181
+ stored_plan
182
+ end
183
+ options_with_original_ns
184
+ end
185
+
186
+ def finalize_adapter_xml(xml_data, encoding, options)
187
+ result = ""
188
+ if (options[:encoding] && !options[:encoding].nil?) ||
189
+ should_include_declaration?(options)
190
+ result += generate_declaration(options)
191
+ end
192
+
193
+ doctype_to_use = options[:doctype] || @doctype
194
+ if doctype_to_use && !options[:omit_doctype]
195
+ result += generate_doctype_declaration(doctype_to_use)
196
+ end
197
+
198
+ result += xml_data
199
+ if encoding && result.encoding.to_s.upcase != encoding.to_s.upcase
200
+ result = result.encode(encoding)
201
+ end
202
+ result
203
+ end
204
+
205
+ def text_content_for_xml(value)
206
+ ::Moxml::Adapter::Base.preprocess_entities(value.to_s)
207
+ end
208
+
209
+ def build_plan_node(xml, xml_element, element_node, plan: nil,
210
+ options: {}, previous_sibling_had_xmlns_blank: false)
211
+ qualified_name = element_node.qualified_name
212
+ attributes = {}
213
+
214
+ original_ns_uris = plan&.original_namespace_uris || {}
215
+ element_node.hoisted_declarations.each do |key, uri|
216
+ next if uri == "http://www.w3.org/XML/1998/namespace"
217
+
218
+ effective_uri = if self.class.fpi?(uri)
219
+ self.class.fpi_to_urn(uri)
220
+ else
221
+ original_ns_uris[uri] || uri
222
+ end
223
+
224
+ xmlns_name = key ? "xmlns:#{key}" : "xmlns"
225
+ attributes[xmlns_name] = effective_uri
226
+ end
227
+
228
+ xml_element.attributes.each_with_index do |xml_attr, idx|
229
+ attr_node = element_node.attribute_nodes[idx]
230
+ attributes[attr_node.qualified_name] = xml_attr.value.to_s
231
+ end
232
+
233
+ if xml_element.respond_to?(:xsi_nil) && xml_element.xsi_nil
234
+ attributes["xsi:nil"] = "true"
235
+ end
236
+
237
+ attributes.merge!(element_node.schema_location_attr) if element_node.schema_location_attr
238
+ needs_xmlns_blank = element_node.needs_xmlns_blank &&
239
+ (options[:pretty] ? !previous_sibling_had_xmlns_blank : true)
240
+ attributes["xmlns"] = "" if needs_xmlns_blank
241
+
242
+ xml.create_and_add_element(qualified_name, attributes: attributes) do
243
+ if xml_element.respond_to?(:raw_content)
244
+ raw_content = xml_element.raw_content
245
+ if raw_content && !raw_content.to_s.empty?
246
+ xml.add_xml_fragment(xml, raw_content.to_s)
247
+ return
248
+ end
249
+ end
250
+
251
+ child_element_index = 0
252
+ previous_child_had_xmlns_blank = false
253
+ xml_element.children.each do |xml_child|
254
+ case xml_child
255
+ when Lutaml::Xml::DataModel::XmlElement
256
+ child_node = element_node.element_nodes[child_element_index]
257
+ child_element_index += 1
258
+
259
+ build_plan_node(
260
+ xml,
261
+ xml_child,
262
+ child_node,
263
+ plan: plan,
264
+ options: options,
265
+ previous_sibling_had_xmlns_blank: previous_child_had_xmlns_blank,
266
+ )
267
+ previous_child_had_xmlns_blank ||= child_node.needs_xmlns_blank
268
+ when Lutaml::Xml::DataModel::XmlComment
269
+ xml.add_comment(xml_child.content)
270
+ when String
271
+ if xml_element.cdata
272
+ xml.cdata(xml_child.to_s)
273
+ else
274
+ xml.text(text_content_for_xml(xml_child))
275
+ end
276
+ end
277
+ end
278
+
279
+ if xml_element.text_content
280
+ if xml_element.cdata
281
+ xml.cdata(xml_element.text_content.to_s)
282
+ else
283
+ xml.text(text_content_for_xml(xml_element.text_content))
284
+ end
285
+ end
286
+ end
287
+ end
288
+ end
289
+ end
290
+ end
291
+ end
@@ -4,7 +4,6 @@ module Lutaml
4
4
  module Xml
5
5
  # Adapter namespace for XML adapter internal classes
6
6
  module Adapter
7
- autoload :XmlSerialization, "#{__dir__}/adapter/xml_serialization"
8
7
  autoload :AdapterHelpers, "#{__dir__}/adapter/adapter_helpers"
9
8
  autoload :BaseAdapter, "#{__dir__}/adapter/base_adapter"
10
9
  autoload :NamespaceData, "#{__dir__}/adapter/namespace_data"
@@ -15,6 +15,7 @@ module Lutaml
15
15
  when Moxml::Cdata then :cdata
16
16
  when Moxml::Text then :text
17
17
  when Moxml::Comment then :comment
18
+ when Moxml::ProcessingInstruction then :processing_instruction
18
19
  else :element
19
20
  end
20
21
 
@@ -50,6 +51,10 @@ module Lutaml
50
51
  EncodingNormalizer.normalize_to_utf8(node.content)
51
52
  when Moxml::Comment
52
53
  EncodingNormalizer.normalize_to_utf8(node.content)
54
+ when Moxml::ProcessingInstruction
55
+ EncodingNormalizer.normalize_to_utf8(
56
+ node.content.to_s.sub(/\A\s+/, ""),
57
+ )
53
58
  end
54
59
 
55
60
  name = adapter_class.name_of(node)
@@ -85,6 +90,8 @@ module Lutaml
85
90
  builder.add_text(builder.current_node, @text.to_s, cdata: true)
86
91
  elsif comment?
87
92
  builder.add_comment(builder.current_node, @text.to_s)
93
+ elsif processing_instruction?
94
+ builder.add_processing_instruction(name, @text.to_s)
88
95
  elsif text? && !element?
89
96
  builder.add_text(builder.current_node, build_text_for_xml.to_s)
90
97
  else
@@ -166,7 +173,6 @@ module Lutaml
166
173
  return [] unless node.children
167
174
 
168
175
  node.children.filter_map do |child|
169
- next if child.is_a?(Moxml::ProcessingInstruction)
170
176
  next if (child.is_a?(Moxml::Text) || child.is_a?(Moxml::Cdata)) && child.content.empty?
171
177
 
172
178
  self.class.new(child, parent: self,
@@ -96,7 +96,6 @@ module Lutaml
96
96
  def add_processing_instruction(target, content)
97
97
  pi = @doc.create_processing_instruction(target.to_s, content.to_s)
98
98
  if current_element.is_a?(Moxml::Document)
99
- # Add before root element or at end if no root
100
99
  root_node = current_element.root
101
100
  if root_node
102
101
  root_node.add_previous_sibling(pi)
@@ -82,9 +82,17 @@ module Lutaml
82
82
 
83
83
  # Add a child element or text node
84
84
  #
85
- # @param child [XmlElement, String] Child to add
85
+ # @param child [XmlElement, String, XmlComment] Child to add
86
86
  # @return [self]
87
+ # @raise [TypeError] if child is not a supported type
87
88
  def add_child(child)
89
+ unless child.is_a?(XmlElement) || child.is_a?(String) ||
90
+ child.is_a?(XmlComment)
91
+ raise TypeError,
92
+ "XmlElement#add_child expects XmlElement, String, or " \
93
+ "XmlComment, got #{child.class}"
94
+ end
95
+
88
96
  @children << child
89
97
  self
90
98
  end
@@ -98,6 +98,7 @@ module Lutaml
98
98
 
99
99
  element.children.each do |child|
100
100
  next if child.respond_to?(:comment?) && child.comment?
101
+ next if child.respond_to?(:processing_instruction?) && child.processing_instruction?
101
102
 
102
103
  if klass&.<= Serialize
103
104
  attr = klass.attribute_for_child(self.class.name_of(child),
@@ -252,7 +253,8 @@ module Lutaml
252
253
  # EntityReference nodes are text-like and should not trigger Array return.
253
254
  # For text + entity without elements, return joined String.
254
255
  has_element_children = @root.children.any? do |child|
255
- !child.text? && !entity_reference_node?(child)
256
+ !child.text? && !entity_reference_node?(child) &&
257
+ !(child.respond_to?(:processing_instruction?) && child.processing_instruction?)
256
258
  end
257
259
  return @root.text_children.map(&:text) if has_element_children
258
260
 
@@ -11,7 +11,7 @@ module Lutaml
11
11
  # @param type [String] "Text" or "Element" (deprecated, use node_type)
12
12
  # @param name [String] The element name or text marker
13
13
  # @param text_content [String, nil] Actual text content for text nodes
14
- # @param node_type [Symbol, nil] The node type (:text, :cdata, :element, :comment)
14
+ # @param node_type [Symbol, nil] The node type (:text, :cdata, :element, :comment, :processing_instruction)
15
15
  # @param namespace_uri [String, nil] The namespace URI of this element
16
16
  # @param namespace_prefix [String, nil] The namespace prefix of this element
17
17
  def initialize(type, name, text_content: nil, node_type: nil,
@@ -38,18 +38,21 @@ module Lutaml
38
38
  @node_type == :cdata
39
39
  end
40
40
 
41
- # Check if this is a comment node
42
- def comment?
43
- @node_type == :comment
44
- end
45
-
46
41
  # Check if this is a regular element
47
42
  def element?
48
43
  @node_type == :element
49
44
  end
50
45
 
46
+ def processing_instruction?
47
+ @node_type == :processing_instruction
48
+ end
49
+
50
+ def comment?
51
+ @node_type == :comment
52
+ end
53
+
51
54
  def element_tag
52
- @name unless text? || cdata? || comment?
55
+ @name unless text? || cdata? || comment? || processing_instruction?
53
56
  end
54
57
 
55
58
  def eql?(other)
@@ -76,14 +79,14 @@ module Lutaml
76
79
  def infer_node_type(type, name)
77
80
  return :text if type == "Text" && name != "#cdata-section"
78
81
  return :cdata if name == "#cdata-section" || (type == "Text" && name == "#cdata-section")
79
- return :comment if type == "Comment"
82
+ return :processing_instruction if type == "ProcessingInstruction"
80
83
 
81
84
  :element
82
85
  end
83
86
 
84
87
  def register_liquid_methods
85
- %i[text? comment? element_tag type name text_content node_type
86
- cdata? namespace_uri namespace_prefix].each do |attr_name|
88
+ %i[text? element_tag type name text_content node_type
89
+ cdata? processing_instruction? namespace_uri namespace_prefix].each do |attr_name|
87
90
  self.class.register_drop_method(attr_name)
88
91
  end
89
92
 
@@ -262,7 +262,7 @@ module Lutaml
262
262
  parent = klass.superclass
263
263
  return nil unless parent < Lutaml::Model::Serializable
264
264
 
265
- parent_mapping = parent.mappings[:xml] if parent.respond_to?(:mappings)
265
+ parent_mapping = parent.mappings[:xml]
266
266
  return parent if parent_mapping
267
267
 
268
268
  superclass_with_xml_mapping(parent)
@@ -274,7 +274,7 @@ module Lutaml
274
274
  all_ns = (existing_ns + parent_ns).uniq
275
275
  @xml_mapping.namespace_scope(all_ns) if all_ns.any?
276
276
 
277
- if parent_mapping.respond_to?(:namespace_scope_config) &&
277
+ if parent_mapping.is_a?(Lutaml::Xml::Mapping) &&
278
278
  (parent_ns_config = parent_mapping.namespace_scope_config) &&
279
279
  parent_ns_config.any?
280
280
  existing_ns_config = @xml_mapping.namespace_scope_config || []
@@ -284,59 +284,36 @@ module Lutaml
284
284
  end
285
285
 
286
286
  def inherit_xml_elements(parent_mapping)
287
- parent_mapping.mapping_elements_hash.each do |key, rule|
288
- existing = @xml_mapping.elements_hash[key]
289
- if existing.nil?
290
- @xml_mapping.elements_hash[key] =
291
- rule.deep_dup
292
- elsif existing.is_a?(Array) && rule.is_a?(Array)
293
- merged = existing + rule.reject do |r|
294
- existing.any? do |e|
295
- e.eql?(r)
296
- end
297
- end
298
- @xml_mapping.elements_hash[key] = merged
299
- elsif existing.is_a?(Array)
300
- existing << rule.deep_dup unless existing.any? do |e|
301
- e.eql?(rule)
302
- end
303
- elsif rule.is_a?(Array)
304
- unless rule.any? { |r| r.eql?(existing) }
305
- @xml_mapping.elements_hash[key] =
306
- [existing, *rule]
307
- end
308
- elsif !existing.eql?(rule)
309
- @xml_mapping.elements_hash[key] =
310
- [existing, rule.deep_dup]
311
- end
312
- end
287
+ inherit_xml_mapping_hash(parent_mapping,
288
+ :mapping_elements_hash,
289
+ @xml_mapping.elements_hash)
313
290
  end
314
291
 
315
292
  def inherit_xml_attributes(parent_mapping)
316
- parent_mapping.mapping_attributes_hash.each do |key, rule|
317
- existing = @xml_mapping.attributes_hash[key]
293
+ inherit_xml_mapping_hash(parent_mapping,
294
+ :mapping_attributes_hash,
295
+ @xml_mapping.attributes_hash)
296
+ end
297
+
298
+ def inherit_xml_mapping_hash(parent_mapping, source_method, target_hash)
299
+ parent_mapping.public_send(source_method).each do |key, rule|
300
+ existing = target_hash[key]
318
301
  if existing.nil?
319
- @xml_mapping.attributes_hash[key] =
320
- rule.deep_dup
302
+ target_hash[key] = rule.deep_dup
321
303
  elsif existing.is_a?(Array) && rule.is_a?(Array)
322
- merged = existing + rule.reject do |r|
323
- existing.any? do |e|
324
- e.eql?(r)
325
- end
304
+ target_hash[key] = existing + rule.reject do |r|
305
+ existing.any? { |e| e.eql?(r) }
326
306
  end
327
- @xml_mapping.attributes_hash[key] = merged
328
307
  elsif existing.is_a?(Array)
329
308
  existing << rule.deep_dup unless existing.any? do |e|
330
309
  e.eql?(rule)
331
310
  end
332
311
  elsif rule.is_a?(Array)
333
- unless rule.any? { |r| r.eql?(existing) }
334
- @xml_mapping.attributes_hash[key] =
335
- [existing, *rule]
312
+ target_hash[key] = [existing, *rule] unless rule.any? do |r|
313
+ r.eql?(existing)
336
314
  end
337
315
  elsif !existing.eql?(rule)
338
- @xml_mapping.attributes_hash[key] =
339
- [existing, rule.deep_dup]
316
+ target_hash[key] = [existing, rule.deep_dup]
340
317
  end
341
318
  end
342
319
  end