lutaml-model 0.8.9 → 0.8.10

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 (90) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop_todo.yml +12 -32
  3. data/lib/lutaml/key_value/transform.rb +5 -5
  4. data/lib/lutaml/key_value/transformation/collection_serializer.rb +25 -11
  5. data/lib/lutaml/key_value/transformation/value_serializer.rb +7 -7
  6. data/lib/lutaml/key_value/transformation.rb +27 -17
  7. data/lib/lutaml/model/adapter_resolver.rb +4 -6
  8. data/lib/lutaml/model/attribute.rb +26 -23
  9. data/lib/lutaml/model/cached_type_resolver.rb +10 -9
  10. data/lib/lutaml/model/cli.rb +1 -1
  11. data/lib/lutaml/model/collection.rb +4 -4
  12. data/lib/lutaml/model/comparable_model.rb +11 -11
  13. data/lib/lutaml/model/config.rb +1 -1
  14. data/lib/lutaml/model/consolidation/dispatcher.rb +1 -1
  15. data/lib/lutaml/model/consolidation/pattern_chunker.rb +3 -3
  16. data/lib/lutaml/model/format_registry.rb +6 -4
  17. data/lib/lutaml/model/global_context.rb +2 -2
  18. data/lib/lutaml/model/global_register.rb +1 -1
  19. data/lib/lutaml/model/instrumentation.rb +1 -1
  20. data/lib/lutaml/model/mapping/mapping_rule.rb +3 -3
  21. data/lib/lutaml/model/mapping/model_mapping.rb +1 -1
  22. data/lib/lutaml/model/mapping/model_mapping_rule.rb +1 -1
  23. data/lib/lutaml/model/register.rb +3 -3
  24. data/lib/lutaml/model/render_policy.rb +11 -17
  25. data/lib/lutaml/model/runtime_compatibility.rb +0 -1
  26. data/lib/lutaml/model/schema/xml_compiler/group.rb +1 -1
  27. data/lib/lutaml/model/schema/xml_compiler/registry_generator.rb +1 -1
  28. data/lib/lutaml/model/schema/xml_compiler/sequence.rb +0 -2
  29. data/lib/lutaml/model/schema/xml_compiler.rb +14 -14
  30. data/lib/lutaml/model/serialize/attribute_definition.rb +1 -1
  31. data/lib/lutaml/model/serialize/deserialization_context.rb +50 -0
  32. data/lib/lutaml/model/serialize/format_conversion.rb +2 -2
  33. data/lib/lutaml/model/serialize/initialization.rb +44 -7
  34. data/lib/lutaml/model/serialize/model_import.rb +1 -1
  35. data/lib/lutaml/model/serialize.rb +8 -1
  36. data/lib/lutaml/model/services/rule_value_extractor.rb +2 -1
  37. data/lib/lutaml/model/store.rb +27 -21
  38. data/lib/lutaml/model/transformation_registry.rb +1 -1
  39. data/lib/lutaml/model/type_context.rb +7 -1
  40. data/lib/lutaml/model/type_resolver.rb +1 -6
  41. data/lib/lutaml/model/utils.rb +19 -6
  42. data/lib/lutaml/model/validation_framework.rb +1 -1
  43. data/lib/lutaml/model/value_transformer.rb +2 -2
  44. data/lib/lutaml/model/version.rb +1 -1
  45. data/lib/lutaml/xml/adapter/adapter_helpers.rb +1 -1
  46. data/lib/lutaml/xml/adapter/base_adapter.rb +10 -14
  47. data/lib/lutaml/xml/adapter/namespace_uri_collector.rb +3 -3
  48. data/lib/lutaml/xml/adapter/plan_based_builder.rb +14 -14
  49. data/lib/lutaml/xml/adapter/xml_serializer.rb +3 -3
  50. data/lib/lutaml/xml/configurable.rb +2 -1
  51. data/lib/lutaml/xml/data_model.rb +2 -2
  52. data/lib/lutaml/xml/decisions/decision_context.rb +3 -3
  53. data/lib/lutaml/xml/decisions/rules/element_form_default_unqualified_rule.rb +1 -1
  54. data/lib/lutaml/xml/decisions/rules/element_form_option_rule.rb +1 -1
  55. data/lib/lutaml/xml/decisions/rules/used_prefix_rule.rb +1 -1
  56. data/lib/lutaml/xml/declaration_plan.rb +2 -2
  57. data/lib/lutaml/xml/declaration_planner.rb +12 -13
  58. data/lib/lutaml/xml/document.rb +13 -13
  59. data/lib/lutaml/xml/format_chooser.rb +3 -3
  60. data/lib/lutaml/xml/hoisting_algorithm.rb +1 -1
  61. data/lib/lutaml/xml/mapping.rb +2 -2
  62. data/lib/lutaml/xml/mapping_rule.rb +16 -3
  63. data/lib/lutaml/xml/model_transform.rb +17 -19
  64. data/lib/lutaml/xml/namespace_collector.rb +10 -10
  65. data/lib/lutaml/xml/namespace_declaration.rb +2 -2
  66. data/lib/lutaml/xml/namespace_declaration_data.rb +5 -8
  67. data/lib/lutaml/xml/namespace_scope_config.rb +3 -2
  68. data/lib/lutaml/xml/namespace_type_resolver.rb +4 -4
  69. data/lib/lutaml/xml/nokogiri/element.rb +2 -2
  70. data/lib/lutaml/xml/polymorphic_value_handler.rb +1 -1
  71. data/lib/lutaml/xml/schema/xsd/base.rb +7 -7
  72. data/lib/lutaml/xml/schema/xsd/choice.rb +2 -2
  73. data/lib/lutaml/xml/schema/xsd/complex_type.rb +5 -5
  74. data/lib/lutaml/xml/schema/xsd/errors/message_builder.rb +3 -3
  75. data/lib/lutaml/xml/schema/xsd/group.rb +2 -2
  76. data/lib/lutaml/xml/schema/xsd/sequence.rb +2 -2
  77. data/lib/lutaml/xml/schema/xsd_schema.rb +5 -5
  78. data/lib/lutaml/xml/serialization/format_conversion.rb +4 -3
  79. data/lib/lutaml/xml/transformation/element_builder.rb +4 -2
  80. data/lib/lutaml/xml/transformation/rule_applier.rb +2 -2
  81. data/lib/lutaml/xml/transformation/value_serializer.rb +4 -6
  82. data/lib/lutaml/xml/transformation.rb +4 -4
  83. data/lib/lutaml/xml/type/configurable.rb +0 -4
  84. data/lib/lutaml/xml/xml_element.rb +21 -13
  85. data/lutaml-model.gemspec +1 -1
  86. data/spec/lutaml/model/cached_type_resolver_spec.rb +3 -3
  87. data/spec/lutaml/model/optimization_spec.rb +228 -0
  88. data/spec/lutaml/model/store_spec.rb +41 -0
  89. data/spec/lutaml/xml/data_model_spec.rb +10 -28
  90. metadata +6 -4
@@ -79,7 +79,7 @@ module Lutaml
79
79
  klass = ::Lutaml::Model::Config.mappings_class_for(format)
80
80
  @mappings[format] ||= klass.new
81
81
  mapping = @mappings[format]
82
- if mapping.respond_to?(:import_model_mappings)
82
+ if mapping.is_a?(Mapping)
83
83
  mapping.import_model_mappings(model,
84
84
  register_id)
85
85
  end
@@ -6,6 +6,8 @@ module Lutaml
6
6
  # Autoload subdirectory modules
7
7
  autoload :Initialization, "#{__dir__}/serialize/initialization"
8
8
  autoload :AttributeDefinition, "#{__dir__}/serialize/attribute_definition"
9
+ autoload :DeserializationContext,
10
+ "#{__dir__}/serialize/deserialization_context"
9
11
  autoload :EnumHandling, "#{__dir__}/serialize/enum_handling"
10
12
  autoload :ModelImport, "#{__dir__}/serialize/model_import"
11
13
  autoload :FormatConversion, "#{__dir__}/serialize/format_conversion"
@@ -161,6 +163,11 @@ module Lutaml
161
163
  @using_default[attribute_name] = false
162
164
  end
163
165
 
166
+ def values_set_for(attribute_names)
167
+ @using_default ||= ::Hash.new(true)
168
+ attribute_names.each { |name| @using_default[name] = false }
169
+ end
170
+
164
171
  def using_default?(attribute_name)
165
172
  # nil means "all attributes using default" — return true without allocation
166
173
  return true if @using_default.nil?
@@ -259,7 +266,7 @@ module Lutaml
259
266
  end
260
267
 
261
268
  def register_in_reference_store
262
- Lutaml::Model::Store.register(self)
269
+ Lutaml::Model::Store.register(self) if self.class.reference_resolvable?
263
270
  end
264
271
 
265
272
  private
@@ -36,7 +36,8 @@ instance_object)
36
36
  # When name is nil but document is a hash-like object with a single key matching
37
37
  # the attribute name, extract that value. This handles the case where
38
38
  # map to: :content is used and the document is {"content": "value"}
39
- # Note: doc may be JSON::Ext::Generator::GeneratorMethods::Hash which is_a?(Hash) returns false
39
+ # Note: doc may be an Oj-generated Hash which is_a?(Hash) returns false for,
40
+ # so we use respond_to? for the hash-like protocol check.
40
41
  if name.nil? && doc.respond_to?(:key?) && doc.respond_to?(:values) && doc.size == 1
41
42
  attr_name = rule.to
42
43
  if doc.key?(attr_name.to_s) || doc.key?(attr_name.to_sym)
@@ -36,8 +36,8 @@ module Lutaml
36
36
 
37
37
  def initialize
38
38
  @store = ::Hash.new { |hash, key| hash[key] = [] }
39
- # Lazy index: built on first resolve for a given (class, key) pair.
40
- # Key: [class_name, reference_method] { value => WeakRef(object) }
39
+ # Nested index: { model_key => { reference_key => { value => WeakRef(object) } } }
40
+ # Grouped by model_key so register only iterates this class's own indices.
41
41
  @index = {}
42
42
  end
43
43
 
@@ -53,23 +53,17 @@ module Lutaml
53
53
 
54
54
  def resolve(model_class, reference_key, reference_value)
55
55
  model_key = model_class.to_s
56
- index_key = [model_key, reference_key]
56
+ model_indices = @index[model_key]
57
57
 
58
- # Build index lazily on first resolve for this (class, key) pair
59
- unless @index.key?(index_key)
60
- ensure_index(index_key, model_key,
61
- reference_key)
58
+ unless model_indices&.key?(reference_key)
59
+ model_indices = ensure_model_index(model_key)
60
+ build_index(model_indices, model_key, reference_key)
62
61
  end
63
62
 
64
- # O(1) indexed lookup
65
- entry = @index[index_key][reference_value]
63
+ entry = model_indices[reference_key][reference_value]
66
64
  return nil unless entry
67
65
 
68
- begin
69
- entry.__getobj__ if entry.weakref_alive?
70
- rescue WeakRef::RefError
71
- nil
72
- end
66
+ dereference(entry)
73
67
  end
74
68
 
75
69
  def clear
@@ -89,9 +83,13 @@ module Lutaml
89
83
 
90
84
  private
91
85
 
86
+ def ensure_model_index(model_key)
87
+ @index[model_key] ||= {}
88
+ end
89
+
92
90
  # Build index for a (model_class, reference_key) pair by scanning existing instances.
93
- def ensure_index(index_key, model_key, reference_key)
94
- entries = @index[index_key] = {}
91
+ def build_index(model_indices, model_key, reference_key)
92
+ entries = model_indices[reference_key] = {}
95
93
  @store[model_key]&.each do |ref|
96
94
  obj = ref.__getobj__
97
95
  value = obj.public_send(reference_key)
@@ -101,19 +99,27 @@ module Lutaml
101
99
  end
102
100
  end
103
101
 
104
- # Update indices that already exist for this model class.
102
+ # Update indices that already exist for this model class only.
103
+ # O(K) where K = number of reference keys indexed for this class,
104
+ # not O(N×K) across all classes.
105
105
  def update_existing_indices(object, model_key)
106
- @index.each do |index_key, entries|
107
- next unless index_key[0] == model_key
106
+ model_indices = @index[model_key]
107
+ return unless model_indices
108
108
 
109
- key_method = index_key[1]
110
- value = object.public_send(key_method)
109
+ model_indices.each do |reference_key, entries|
110
+ value = object.public_send(reference_key)
111
111
  entries[value] = WeakRef.new(object) if value
112
112
  rescue WeakRef::RefError
113
113
  next
114
114
  end
115
115
  end
116
116
 
117
+ def dereference(entry)
118
+ entry.__getobj__ if entry.weakref_alive?
119
+ rescue WeakRef::RefError
120
+ nil
121
+ end
122
+
117
123
  def compact_if_needed(refs)
118
124
  return unless refs.size > COMPACTION_THRESHOLD
119
125
 
@@ -185,7 +185,7 @@ module Lutaml
185
185
  # Build mapping OUTSIDE any lock to avoid deadlock
186
186
  # (ensure_mappings_imported! may recursively call get_or_build_mapping)
187
187
  mapping = model_class.mappings[format]
188
- mapping = mapping || model_class.send(:default_mappings, format)
188
+ mapping = mapping || model_class.default_mappings(format)
189
189
 
190
190
  mapping.ensure_mappings_imported!(register_id)
191
191
 
@@ -47,6 +47,9 @@ module Lutaml
47
47
  # @return [Array<TypeSubstitution>] Type substitution rules
48
48
  attr_reader :substitutions
49
49
 
50
+ # @return [Hash{Class => Class}] Pre-built Hash for O(1) substitution lookup
51
+ attr_reader :substitution_hash
52
+
50
53
  # @return [Array<TypeContext>] Fallback contexts (in order)
51
54
  attr_reader :fallback_contexts
52
55
 
@@ -60,6 +63,9 @@ module Lutaml
60
63
  @id = id.to_sym
61
64
  @registry = registry
62
65
  @substitutions = Array(substitutions).freeze
66
+ @substitution_hash = @substitutions.to_h do |s|
67
+ [s.from_type, s.to_type]
68
+ end.freeze
63
69
  @fallback_contexts = Array(fallback_contexts).freeze
64
70
  freeze
65
71
  end
@@ -254,7 +260,7 @@ module Lutaml
254
260
  ctx
255
261
  when Symbol, String
256
262
  # Try to look up from GlobalContext if available
257
- if defined?(GlobalContext) && GlobalContext.respond_to?(:registry)
263
+ if defined?(GlobalContext)
258
264
  GlobalContext.registry.lookup(ctx)
259
265
  end
260
266
  end
@@ -157,12 +157,7 @@ module Lutaml
157
157
  def self.apply_substitutions(type, context)
158
158
  return type if context.nil? || context.substitutions.empty?
159
159
 
160
- context.substitutions.each do |sub|
161
- substituted = sub.apply(type)
162
- return substituted if substituted
163
- end
164
-
165
- type
160
+ context.substitution_hash[type] || type
166
161
  end
167
162
 
168
163
  # Get all available type names from context and fallbacks.
@@ -163,12 +163,25 @@ module Lutaml
163
163
  # fetch_str_or_sym(hash, :key) # => "value"
164
164
  # fetch_str_or_sym(hash, "invalid_key") # => nil
165
165
  def fetch_str_or_sym(hash, key, default = nil)
166
- if hash.key?(key.to_s)
167
- hash[key.to_s]
168
- elsif hash.key?(key.to_sym)
169
- hash[key.to_sym]
166
+ case key
167
+ when String
168
+ if hash.key?(key)
169
+ hash[key]
170
+ else
171
+ (hash.key?(key.to_sym) ? hash[key.to_sym] : default)
172
+ end
173
+ when Symbol
174
+ if hash.key?(key)
175
+ hash[key]
176
+ else
177
+ (hash.key?(key.to_s) ? hash[key.to_s] : default)
178
+ end
170
179
  else
171
- default
180
+ if hash.key?(key.to_s)
181
+ hash[key.to_s]
182
+ else
183
+ (hash.key?(key.to_sym) ? hash[key.to_sym] : default)
184
+ end
172
185
  end
173
186
  end
174
187
 
@@ -183,7 +196,7 @@ module Lutaml
183
196
  end
184
197
 
185
198
  def add_singleton_method_if_not_defined(instance, method_name, &)
186
- return if instance.respond_to?(method_name)
199
+ return if instance.singleton_class.method_defined?(method_name, false)
187
200
 
188
201
  instance.define_singleton_method(method_name, &)
189
202
  end
@@ -38,7 +38,7 @@ module Lutaml
38
38
  next unless rule.applicable?(context)
39
39
 
40
40
  issues = rule.check(context)
41
- if context.respond_to?(:add_error)
41
+ if context.is_a?(Validation::Context)
42
42
  issues.each { |i| context.add_error(i) }
43
43
  end
44
44
  all_issues.concat(issues)
@@ -10,11 +10,11 @@ module Lutaml
10
10
  end
11
11
 
12
12
  def self.from(value, format)
13
- new.send(:"from_#{format}", value)
13
+ new.public_send(:"from_#{format}", value)
14
14
  end
15
15
 
16
16
  def self.to(value, format)
17
- new(value).send(:"to_#{format}")
17
+ new(value).public_send(:"to_#{format}")
18
18
  end
19
19
 
20
20
  def self.can_transform?(method, format)
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Lutaml
4
4
  module Model
5
- VERSION = "0.8.9"
5
+ VERSION = "0.8.10"
6
6
  end
7
7
  end
@@ -90,7 +90,7 @@ module Lutaml
90
90
  # @param node [Moxml::Node] The XML node
91
91
  # @return [String] The namespaced name (e.g., "uri:name" or "name")
92
92
  def namespaced_name_of(node)
93
- return name_of(node) unless node.respond_to?(:namespace)
93
+ return name_of(node) unless node.is_a?(Lutaml::Xml::XmlElement)
94
94
 
95
95
  [node&.namespace&.uri, node.name].compact.join(":")
96
96
  end
@@ -126,7 +126,7 @@ module Lutaml
126
126
  # @param options [Hash] serialization options
127
127
  # @return [Boolean] true if element has ordered content
128
128
  def ordered?(element, options = {})
129
- return false unless element.respond_to?(:element_order)
129
+ return false unless element.is_a?(Lutaml::Model::Serialize)
130
130
 
131
131
  mapper_class = options[:mapper_class]
132
132
  xml_mapping = mapper_class&.mappings_for(:xml)
@@ -156,7 +156,7 @@ module Lutaml
156
156
  klass = mapper_class || element.class
157
157
  return klass.attributes[rule.to] unless rule.delegate
158
158
 
159
- delegated_obj = element.send(rule.delegate)
159
+ delegated_obj = element.public_send(rule.delegate)
160
160
  return nil if delegated_obj.nil?
161
161
 
162
162
  delegated_obj.class.attributes[rule.to]
@@ -168,12 +168,12 @@ module Lutaml
168
168
  # @param rule [MappingRule] the mapping rule
169
169
  # @return [Object, nil] the attribute value or nil if delegate is nil
170
170
  def attribute_value_for(element, rule)
171
- return element.send(rule.to) unless rule.delegate
171
+ return element.public_send(rule.to) unless rule.delegate
172
172
 
173
- delegate_obj = element.send(rule.delegate)
173
+ delegate_obj = element.public_send(rule.delegate)
174
174
  return nil if delegate_obj.nil?
175
175
 
176
- delegate_obj.send(rule.to)
176
+ delegate_obj.public_send(rule.to)
177
177
  end
178
178
 
179
179
  # Process content mapping for an element
@@ -186,7 +186,7 @@ module Lutaml
186
186
  return unless content_rule
187
187
 
188
188
  if content_rule.custom_methods[:to]
189
- mapper_class.new.send(
189
+ mapper_class.new.public_send(
190
190
  content_rule.custom_methods[:to],
191
191
  element,
192
192
  xml.parent,
@@ -225,15 +225,11 @@ module Lutaml
225
225
  private
226
226
 
227
227
  def attribute_values(element, &)
228
- if element.respond_to?(:attributes_each_value)
229
- element.attributes_each_value(&)
230
- else
231
- element.attributes.each_value(&)
232
- end
228
+ element.attributes.each_value(&)
233
229
  end
234
230
 
235
231
  def schema_location_attribute?(attr)
236
- attr_name = if attr.respond_to?(:unprefixed_name)
232
+ attr_name = if attr.is_a?(Lutaml::Xml::XmlAttribute)
237
233
  attr.unprefixed_name
238
234
  else
239
235
  attr.name
@@ -242,7 +238,7 @@ module Lutaml
242
238
  end
243
239
 
244
240
  def attribute_namespace_prefix(attr)
245
- if attr.respond_to?(:namespace_prefix)
241
+ if attr.is_a?(Lutaml::Xml::XmlAttribute)
246
242
  attr.namespace_prefix
247
243
  else
248
244
  attr.namespace&.prefix
@@ -250,7 +246,7 @@ module Lutaml
250
246
  end
251
247
 
252
248
  def attribute_hash_name(attr)
253
- if attr.respond_to?(:namespaced_name)
249
+ if attr.is_a?(Lutaml::Xml::XmlAttribute)
254
250
  attr.namespaced_name
255
251
  else
256
252
  self.class.namespaced_attr_name(attr)
@@ -29,7 +29,7 @@ module Lutaml
29
29
 
30
30
  visited.add(model.object_id)
31
31
 
32
- if model.respond_to?(:original_namespace_uri) && model.original_namespace_uri
32
+ if model.is_a?(::Lutaml::Model::Serialize) && model.class.attributes.key?(:original_namespace_uri) && model.original_namespace_uri
33
33
  original_uri = model.original_namespace_uri
34
34
  if original_uri && !original_uri.empty?
35
35
  ns_class = model.class.mappings_for(:xml)&.namespace_class
@@ -47,12 +47,12 @@ module Lutaml
47
47
  next unless attr_def
48
48
 
49
49
  child_type = attr_def.type(Lutaml::Model::Config.default_register)
50
- next unless child_type.respond_to?(:<) && child_type < ::Lutaml::Model::Serializable
50
+ next unless child_type.is_a?(Class) && child_type < ::Lutaml::Model::Serializable
51
51
 
52
52
  child_mapping = child_type.mappings_for(:xml)
53
53
  next unless child_mapping
54
54
 
55
- child_instance = model.public_send(elem_rule.to) if model.respond_to?(elem_rule.to)
55
+ child_instance = model.public_send(elem_rule.to) if model.class.attributes.key?(elem_rule.to)
56
56
 
57
57
  if child_instance.is_a?(Array) || child_instance.is_a?(::Lutaml::Model::Collection)
58
58
  instances = child_instance.is_a?(::Lutaml::Model::Collection) ? child_instance.collection : child_instance
@@ -158,8 +158,8 @@ module Lutaml
158
158
  prefix: prefix) do |inner_xml|
159
159
  # Call attribute custom methods now that element is created
160
160
  attribute_custom_methods.each do |attribute_rule|
161
- mapper_class.new.send(attribute_rule.custom_methods[:to],
162
- element, inner_xml.parent, inner_xml)
161
+ mapper_class.new.public_send(attribute_rule.custom_methods[:to],
162
+ element, inner_xml.parent, inner_xml)
163
163
  end
164
164
 
165
165
  if ordered?(element, options.merge(mapper_class: mapper_class))
@@ -292,7 +292,7 @@ module Lutaml
292
292
  end
293
293
 
294
294
  # Check if element was created from nil value with render_nil option
295
- if element.respond_to?(:xsi_nil) && element.xsi_nil
295
+ if element.is_a?(Lutaml::Xml::DataModel::XmlElement) && element.xsi_nil
296
296
  attributes["xsi:nil"] = true
297
297
  end
298
298
 
@@ -301,7 +301,7 @@ module Lutaml
301
301
  prefix: prefix) do |inner_xml|
302
302
  # Handle raw content (map_all directive)
303
303
  has_raw_content = false
304
- if element.respond_to?(:raw_content)
304
+ if element.is_a?(Lutaml::Xml::DataModel::XmlElement)
305
305
  raw_content = element.raw_content
306
306
  if raw_content && !raw_content.to_s.empty?
307
307
  inner_xml.add_xml_fragment(inner_xml, raw_content.to_s)
@@ -361,8 +361,8 @@ module Lutaml
361
361
 
362
362
  # Handle custom methods
363
363
  if element_rule.custom_methods[:to]
364
- mapper_class.new.send(element_rule.custom_methods[:to], element,
365
- xml.parent, xml)
364
+ mapper_class.new.public_send(element_rule.custom_methods[:to], element,
365
+ xml.parent, xml)
366
366
  next
367
367
  end
368
368
 
@@ -432,8 +432,8 @@ module Lutaml
432
432
 
433
433
  # Handle custom methods
434
434
  if element_rule.custom_methods[:to]
435
- mapper_class.new.send(element_rule.custom_methods[:to], element,
436
- xml.parent, xml)
435
+ mapper_class.new.public_send(element_rule.custom_methods[:to], element,
436
+ xml.parent, xml)
437
437
  next
438
438
  end
439
439
 
@@ -468,7 +468,7 @@ module Lutaml
468
468
  attribute_def = mapper_class.attributes[element_rule.to]
469
469
  next unless attribute_def
470
470
 
471
- value = element.send(element_rule.to)
471
+ value = element.public_send(element_rule.to)
472
472
  next unless element_rule.render?(value, element)
473
473
 
474
474
  # For type-only models, children plans may not be available
@@ -500,7 +500,7 @@ module Lutaml
500
500
  attribute_def = mapper_class.attributes[element_rule.to]
501
501
  next unless attribute_def
502
502
 
503
- value = element.send(element_rule.to)
503
+ value = element.public_send(element_rule.to)
504
504
  next unless element_rule.render?(value, element)
505
505
 
506
506
  child_plan = plan.child_plan(element_rule.to)
@@ -805,10 +805,10 @@ module Lutaml
805
805
  value = nil
806
806
 
807
807
  if element_rule.delegate
808
- delegate_obj = element.send(element_rule.delegate)
809
- if delegate_obj.respond_to?(element_rule.to)
808
+ delegate_obj = element.public_send(element_rule.delegate)
809
+ if delegate_obj.is_a?(Lutaml::Model::Serialize) && delegate_obj.class.attributes.key?(element_rule.to)
810
810
  attribute_def = delegate_obj.class.attributes[element_rule.to]
811
- value = delegate_obj.send(element_rule.to)
811
+ value = delegate_obj.public_send(element_rule.to)
812
812
  end
813
813
  else
814
814
  attribute_def = attribute_definition_for(element, element_rule,
@@ -828,7 +828,7 @@ module Lutaml
828
828
  # @param content [Array] accumulated content strings
829
829
  def process_ordered_content(element, xml_mapping, xml, curr_index,
830
830
  content)
831
- text = element.send(xml_mapping.content_mapping.to)
831
+ text = element.public_send(xml_mapping.content_mapping.to)
832
832
  text = text[curr_index] if text.is_a?(Array)
833
833
 
834
834
  if element.mixed?
@@ -83,7 +83,7 @@ module Lutaml
83
83
  def build_xml_element_with_plan(builder, xml_element, plan,
84
84
  options = {})
85
85
  # Add processing instructions before the root element
86
- if xml_element.respond_to?(:processing_instructions)
86
+ if xml_element.is_a?(Lutaml::Xml::DataModel::XmlElement)
87
87
  xml_element.processing_instructions.each do |pi|
88
88
  builder.add_processing_instruction(pi.target, pi.content)
89
89
  end
@@ -230,7 +230,7 @@ module Lutaml
230
230
  attributes[attr_node.qualified_name] = xml_attr.value.to_s
231
231
  end
232
232
 
233
- if xml_element.respond_to?(:xsi_nil) && xml_element.xsi_nil
233
+ if xml_element.is_a?(Lutaml::Xml::DataModel::XmlElement) && xml_element.xsi_nil
234
234
  attributes["xsi:nil"] = "true"
235
235
  end
236
236
 
@@ -240,7 +240,7 @@ module Lutaml
240
240
  attributes["xmlns"] = "" if needs_xmlns_blank
241
241
 
242
242
  xml.create_and_add_element(qualified_name, attributes: attributes) do
243
- if xml_element.respond_to?(:raw_content)
243
+ if xml_element.is_a?(Lutaml::Xml::DataModel::XmlElement)
244
244
  raw_content = xml_element.raw_content
245
245
  if raw_content && !raw_content.to_s.empty?
246
246
  xml.add_xml_fragment(xml, raw_content.to_s)
@@ -119,7 +119,8 @@ module Lutaml
119
119
  # @return [void]
120
120
  def inherit_mapping_from(mapping_class)
121
121
  # Get the parent mapping instance that has DSL already evaluated
122
- parent_mapping = if mapping_class.respond_to?(:xml_mapping_instance) &&
122
+ parent_mapping = if mapping_class.is_a?(Class) &&
123
+ (mapping_class < Lutaml::Xml::Mapping || mapping_class.include?(Lutaml::Model::Serialize)) &&
123
124
  mapping_class.xml_mapping_instance
124
125
  mapping_class.xml_mapping_instance
125
126
  else
@@ -133,7 +133,7 @@ module Lutaml
133
133
  def qualified_name(prefix = nil)
134
134
  if prefix
135
135
  "#{prefix}:#{name}"
136
- elsif namespace_class.respond_to?(:prefix_default)
136
+ elsif namespace_class.is_a?(Class) && namespace_class < Lutaml::Xml::Namespace
137
137
  ns_prefix = namespace_class.prefix_default
138
138
  ns_prefix ? "#{ns_prefix}:#{name}" : name
139
139
  else
@@ -202,7 +202,7 @@ module Lutaml
202
202
  def qualified_name(prefix = nil)
203
203
  if prefix
204
204
  "#{prefix}:#{name}"
205
- elsif namespace_class.respond_to?(:prefix_default)
205
+ elsif namespace_class.is_a?(Class) && namespace_class < Lutaml::Xml::Namespace
206
206
  ns_prefix = namespace_class.prefix_default
207
207
  ns_prefix ? "#{ns_prefix}:#{name}" : name
208
208
  else
@@ -129,7 +129,7 @@ module Lutaml
129
129
  return nil unless @element
130
130
 
131
131
  if @element.is_a?(Lutaml::Xml::XmlElement)
132
- @element.namespace_prefix if @element.respond_to?(:namespace_prefix)
132
+ @element.namespace_prefix
133
133
  else
134
134
  # For DataModel::XmlElement, check xml_namespace_prefix first
135
135
  prefix = @element.xml_namespace_prefix
@@ -137,7 +137,7 @@ module Lutaml
137
137
 
138
138
  # Fall back to original XmlElement wrapper if available
139
139
  original = @element.original_xml_element
140
- if original.respond_to?(:namespace_prefix)
140
+ if original.is_a?(Lutaml::Xml::XmlElement)
141
141
  return original.namespace_prefix
142
142
  end
143
143
 
@@ -213,7 +213,7 @@ module Lutaml
213
213
 
214
214
  # Returns all URIs for the current namespace (canonical + aliases).
215
215
  def namespace_all_uris
216
- if @namespace_class.respond_to?(:all_uris)
216
+ if @namespace_class.is_a?(Class) && @namespace_class < Lutaml::Xml::Namespace
217
217
  @namespace_class.all_uris
218
218
  else
219
219
  [@namespace_uri]
@@ -51,7 +51,7 @@ module Lutaml
51
51
 
52
52
  # Only apply if no explicit form option on the element
53
53
  # ElementFormOptionRule (Priority -0.5) handles explicit form options
54
- return false if context.element.respond_to?(:form) && !context.element.form.nil?
54
+ return false if context.element.is_a?(Lutaml::Xml::DataModel::XmlElement) && !context.element.form.nil?
55
55
 
56
56
  true
57
57
  end
@@ -30,7 +30,7 @@ module Lutaml
30
30
  def applies?(context)
31
31
  return false unless context.element
32
32
  # Check if element responds to :form before accessing it
33
- return false unless context.element.respond_to?(:form)
33
+ return false unless context.element.is_a?(Lutaml::Xml::DataModel::XmlElement)
34
34
 
35
35
  # Check if element has form attribute
36
36
  form = context.element.form
@@ -44,7 +44,7 @@ module Lutaml
44
44
  # Skip if namespace does NOT have element_form_default :qualified
45
45
  # Namespaces without explicit :qualified should use default format
46
46
  # (e.g., dcterms namespace uses default format, not prefix)
47
- return false unless ns_class.respond_to?(:element_form_default_set?) &&
47
+ return false unless ns_class.is_a?(Class) && ns_class < Lutaml::Xml::Namespace &&
48
48
  ns_class.element_form_default_set? &&
49
49
  ns_class.element_form_default == :qualified
50
50
 
@@ -441,7 +441,7 @@ module Lutaml
441
441
  ns_classes = []
442
442
  collect_ns_classes_recursive(@root_node, ns_classes)
443
443
  ns_classes.uniq.select do |ns_class|
444
- ns_class.respond_to?(:schema_location) && ns_class.schema_location
444
+ ns_class.is_a?(Class) && ns_class < Lutaml::Xml::Namespace && ns_class.schema_location
445
445
  end
446
446
  end
447
447
 
@@ -452,7 +452,7 @@ module Lutaml
452
452
  # @return [void]
453
453
  def collect_ns_classes_recursive(element_node, ns_classes)
454
454
  # Add namespace from element's own namespace_class
455
- if element_node.respond_to?(:qualified_name) && @namespace_classes
455
+ if element_node.is_a?(Lutaml::Xml::DeclarationPlan::ElementNode) && @namespace_classes
456
456
  # Try to find namespace class from namespace_classes by matching URI
457
457
  # The ElementNode itself doesn't store ns_class, but we can check if
458
458
  # any of our namespace_classes match the element's context