lutaml-model 0.8.87 → 0.8.88

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 86ca328809a1a0e065b430ec56f016fbe70ee892cf705ceb618a7d1869aa8d77
4
- data.tar.gz: 5f23509351e875b5e43c459de66a134cb2d3106288528a43a9adfa97f411a2c6
3
+ metadata.gz: edd074b78dadf665677a6c6e7d738b7d409aa63e4bbef51c9a6b0a61d36a6454
4
+ data.tar.gz: 9b88edc42b0b865cc2ea3523a9d923f1b988980255508e31da22fb66c06e87fe
5
5
  SHA512:
6
- metadata.gz: 8172239de2524442bca61fdfd9111f9163f8fd03b5b84f620a54940b22aab359a2e7d4ade1a127b651af717dbce2172b244670047ec9a1ff5890abce8ce5457d
7
- data.tar.gz: 579e6c690fabd240ddb1ced902c38dee5af5d86bf3d0d2ecfd0c3ccf01749b62843f1189660579b193bca8edc1f1f127e8ccc1fbfaa5c0209d79b2fc8841bf23
6
+ metadata.gz: e90cbcc5a0046c0bd3b6d5e687fb6a9e137f3e9a8d63149296082cd468a747bec1690e5720b6a423b1a17bbe8706ce2773687671388aa303b68f3e2d37805e90
7
+ data.tar.gz: 163d5e386a27f6e6f4806096866de4f3005191acba9183929017c9292ecfae68fb066e6d19054102effb9eb8ed0118bc8e322d613f0cb5345183f16f328c6c02
@@ -20,7 +20,7 @@ if ENV["PLAN_COMPILE_STATS"]
20
20
  at_exit do
21
21
  require "pp"
22
22
  puts "\nPlanCompiler opt-out histogram:"
23
- pp(Lutaml::Xml::PlanCompiler.stats)
23
+ pp(Lutaml::Xml::Leptris::PlanCompiler.stats)
24
24
  end
25
25
  end
26
26
  require "benchmark"
@@ -8,7 +8,7 @@ module Lutaml
8
8
  # families that carry the feature override (KeyValue's
9
9
  # root_mappings, Xml's namespace_class); the base defaults make
10
10
  # the absence explicit instead of probed.
11
- def root_mappings
11
+ def root_mappings?
12
12
  false
13
13
  end
14
14
 
@@ -26,7 +26,8 @@ module Lutaml
26
26
  parts = (instance_variables - INSPECT_EXCLUDED_IVARS).map do |iv|
27
27
  "#{iv}=#{self.class.inspect_value(instance_variable_get(iv))}"
28
28
  end
29
- "#<#{self.class.name}#{' ' + parts.join(', ') unless parts.empty?}>"
29
+ attrs = " #{parts.join(', ')}" unless parts.empty?
30
+ "#<#{self.class.name}#{attrs}>"
30
31
  end
31
32
 
32
33
  class << self
@@ -150,48 +150,41 @@ module Lutaml
150
150
  #
151
151
  # @return [Array<Class>] List of error classes
152
152
  def format_error_types
153
- @format_error_types_base ||= begin
154
- errors = [
155
- Lutaml::Model::RuntimeCompatibility.safe_constantize("Psych::SyntaxError"),
156
- Lutaml::Model::RuntimeCompatibility.safe_constantize("JSON::ParserError"),
157
- NoMethodError,
158
- TypeError,
159
- ArgumentError,
160
- ]
161
-
162
- # Collect format-specific error types from FormatRegistry
163
- compatibility = Lutaml::Model::RuntimeCompatibility
164
- FormatRegistry.all.each_value do |info|
165
- next unless info[:error_types]
166
-
167
- info[:error_types].each do |error_class|
168
- cls = if error_class.is_a?(String)
169
- compatibility.safe_constantize(error_class)
170
- else
171
- error_class
172
- end
173
- errors << cls
174
- end
153
+ # Resolved per call, not memoized: adapter engines load lazily,
154
+ # and a cache built before an engine loads permanently drops
155
+ # that engine's parse errors from the rescue list (an
156
+ # REXML::ParseException leaked un-wrapped when any earlier
157
+ # parse ran before rexml was required). The constantize pass
158
+ # is a handful of lookups — negligible against a parse.
159
+ compatibility = Lutaml::Model::RuntimeCompatibility
160
+ errors = [
161
+ compatibility.safe_constantize("Psych::SyntaxError"),
162
+ compatibility.safe_constantize("JSON::ParserError"),
163
+ NoMethodError,
164
+ TypeError,
165
+ ArgumentError,
166
+ ]
167
+
168
+ FormatRegistry.all.each_value do |info|
169
+ next unless info[:error_types]
170
+
171
+ info[:error_types].each do |error_class|
172
+ errors << if error_class.is_a?(String)
173
+ compatibility.safe_constantize(error_class)
174
+ else
175
+ error_class
176
+ end
175
177
  end
176
-
177
- errors.compact.freeze
178
178
  end
179
179
 
180
180
  # Legacy TOML error types are lazy, so check them on each call.
181
- compatibility = Lutaml::Model::RuntimeCompatibility
182
- toml_errors = compatibility.safe_constantize("TomlRB::ParseError")
183
- toml_errors = Array(toml_errors)
181
+ toml_errors = Array(compatibility.safe_constantize("TomlRB::ParseError"))
184
182
  tomllib_err = compatibility.safe_constantize("Tomlib::ParseError")
185
183
  toml_errors << tomllib_err if tomllib_err
186
184
  teptris_err = compatibility.safe_constantize("Teptris::ParseError")
187
185
  toml_errors << teptris_err if teptris_err
188
186
 
189
- @format_error_types_base + toml_errors
190
- end
191
-
192
- # Reset cached error types (for test isolation)
193
- def reset_format_error_types_cache!
194
- @format_error_types_base = nil
187
+ (errors + toml_errors).compact
195
188
  end
196
189
 
197
190
  # Create a model instance from a parsed document
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Lutaml
4
4
  module Model
5
- VERSION = "0.8.87"
5
+ VERSION = "0.8.88"
6
6
  end
7
7
  end
@@ -285,7 +285,7 @@ module Lutaml
285
285
  # plan learns the catch-all row.
286
286
  return false if mapping.any_element_rule
287
287
 
288
- mapping.root_element && !mapping.root_mappings
288
+ mapping.root_element && !mapping.root_mappings?
289
289
  end
290
290
 
291
291
  # Partition bookkeeping for when_attribute rows (#88, TODO
@@ -38,21 +38,7 @@ module Lutaml
38
38
  @xml_instance
39
39
  end
40
40
 
41
- attr_reader :root_element,
42
- :namespace_uri,
43
- :namespace_prefix,
44
- :element_sequence,
45
- :mappings_imported,
46
- :namespace_class,
47
- :namespace_param,
48
- :element_name,
49
- :documentation_text,
50
- :type_name_value,
51
- :namespace_scope,
52
- :mapper_class,
53
- :xml_space,
54
- :consolidation_maps,
55
- :processing_instruction_mappings
41
+ attr_reader :root_element, :namespace_uri, :namespace_prefix, :element_sequence, :mappings_imported, :namespace_class, :namespace_param, :element_name, :documentation_text, :type_name_value, :namespace_scope, :mapper_class, :xml_space, :consolidation_maps, :processing_instruction_mappings, :any_element_rule
56
42
 
57
43
  def initialize
58
44
  super
@@ -523,8 +509,6 @@ module Lutaml
523
509
  @any_element_rule
524
510
  end
525
511
 
526
- attr_reader :any_element_rule
527
-
528
512
  def map_instances(to:, polymorphic: {})
529
513
  map_element(to, to: to, polymorphic: polymorphic)
530
514
  end
@@ -114,7 +114,14 @@ module Lutaml
114
114
  # Only needed for root elements (no lutaml_parent in options).
115
115
  # Three modes: :lazy (default), :eager, :skip.
116
116
  if !options.key?(:lutaml_parent)
117
- plan_mode = options.fetch(:import_declaration_plan, :lazy)
117
+ # Default :eager — the plan data is tiny, but :lazy pins the
118
+ # adapter root element on every parsed instance, which keeps the
119
+ # entire native DOM (moxml wrappers + engine nodes) alive until
120
+ # first serialization. Models that are never serialized retain
121
+ # the DOM forever: a 10.5MB XMI held 578MB of wrappers this
122
+ # way. :lazy remains available for parse-serialize-immediately
123
+ # workflows that want to skip the namespace walk.
124
+ plan_mode = options.fetch(:import_declaration_plan, :eager)
118
125
  case plan_mode
119
126
  when :skip
120
127
  # Skip plan building entirely (fastest)
@@ -205,7 +212,9 @@ module Lutaml
205
212
  }
206
213
  end
207
214
 
208
- result[path] = input_namespaces unless input_namespaces.empty?
215
+ # The path array is shared and mutated during the descent;
216
+ # snapshot it as a result key only where a declaration lives.
217
+ result[path.dup] = input_namespaces unless input_namespaces.empty?
209
218
  end
210
219
 
211
220
  # Recurse into children with path extended by child local name
@@ -223,8 +232,9 @@ module Lutaml
223
232
  else
224
233
  full_name
225
234
  end
226
- child_path = path + [child_local_name]
227
- collect_element_namespaces(child, child_path, result, visited)
235
+ path.push(child_local_name)
236
+ collect_element_namespaces(child, path, result, visited)
237
+ path.pop
228
238
  end
229
239
 
230
240
  result
@@ -148,7 +148,8 @@ module Lutaml
148
148
  def target_namespace_from(model, value)
149
149
  model.target_namespace = value
150
150
  model.target_namespace_prefix =
151
- namespace_prefix_for(model.pending_plan_root_element, value)
151
+ model.import_declaration_plan
152
+ &.global_prefix_registry&.[](value)
152
153
  end
153
154
 
154
155
  # Find a type definition by local name
@@ -67,7 +67,7 @@ module Lutaml
67
67
  # class method; a plain (non-Serializable) superclass ends
68
68
  # the chain.
69
69
  inherited = if superclass.is_a?(Class) &&
70
- superclass.include?(Lutaml::Model::Serialize)
70
+ superclass.include?(Lutaml::Model::Serialize)
71
71
  superclass.xml_schema_paths
72
72
  else
73
73
  []
@@ -48,7 +48,7 @@ RSpec.describe "#clear_xml_parse_state!" do
48
48
  end
49
49
 
50
50
  it "clears pending_plan_root_element set by :lazy mode" do
51
- model = model_class.from_xml(xml_with_ns)
51
+ model = model_class.from_xml(xml_with_ns, import_declaration_plan: :lazy)
52
52
  expect(model.pending_plan_root_element).not_to be_nil
53
53
  model.clear_xml_parse_state!
54
54
  expect(model.pending_plan_root_element).to be_nil
@@ -343,26 +343,30 @@ RSpec.describe "Doubly-defined namespace prefixes" do
343
343
  XML
344
344
  end
345
345
 
346
- describe ":lazy (default)" do
346
+ describe ":lazy" do
347
347
  it "stores element reference for lazy plan building during parsing" do
348
- model = model_class.from_xml(prefixed_xml)
348
+ model = model_class.from_xml(prefixed_xml,
349
+ import_declaration_plan: :lazy)
349
350
  expect(model.pending_plan_root_element).not_to be_nil
350
351
  end
351
352
 
352
353
  it "builds plan via import_declaration_plan method" do
353
- model = model_class.from_xml(prefixed_xml)
354
+ model = model_class.from_xml(prefixed_xml,
355
+ import_declaration_plan: :lazy)
354
356
  expect(model.import_declaration_plan).to be_a(Lutaml::Xml::DeclarationPlan)
355
357
  end
356
358
 
357
359
  it "clears element reference after plan is built" do
358
- model = model_class.from_xml(prefixed_xml)
360
+ model = model_class.from_xml(prefixed_xml,
361
+ import_declaration_plan: :lazy)
359
362
  expect(model.pending_plan_root_element).not_to be_nil
360
363
  _ = model.import_declaration_plan
361
364
  expect(model.pending_plan_root_element).to be_nil
362
365
  end
363
366
 
364
367
  it "imports plan automatically on to_xml" do
365
- model = model_class.from_xml(prefixed_xml)
368
+ model = model_class.from_xml(prefixed_xml,
369
+ import_declaration_plan: :lazy)
366
370
  # Plan not yet built (instance variable is nil)
367
371
  expect(model.instance_variable_get(:@xml_declaration_plan)).to be_nil
368
372
  model.to_xml
@@ -371,12 +375,16 @@ RSpec.describe "Doubly-defined namespace prefixes" do
371
375
  end
372
376
  end
373
377
 
374
- describe ":eager" do
378
+ describe ":eager (default)" do
375
379
  it "builds plan immediately during parsing" do
376
- model = model_class.from_xml(prefixed_xml,
377
- import_declaration_plan: :eager)
380
+ model = model_class.from_xml(prefixed_xml)
378
381
  expect(model.import_declaration_plan).to be_a(Lutaml::Xml::DeclarationPlan)
379
382
  end
383
+
384
+ it "does not retain the adapter root element" do
385
+ model = model_class.from_xml(prefixed_xml)
386
+ expect(model.pending_plan_root_element).to be_nil
387
+ end
380
388
  end
381
389
 
382
390
  describe ":skip" do
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "spec_helper"
4
+
5
+ RSpec.describe "XML parse DOM retention" do
6
+ let(:model_class) do
7
+ Class.new(Lutaml::Model::Serializable) do
8
+ attribute :item, :string
9
+
10
+ xml do
11
+ element "root"
12
+ map_element "item", to: :item
13
+ end
14
+ end
15
+ end
16
+
17
+ let(:xml) do
18
+ <<~XML
19
+ <root xmlns:xyz="http://example.com/items">
20
+ <xyz:item>hello</xyz:item>
21
+ </root>
22
+ XML
23
+ end
24
+
25
+ it "does not retain the adapter root element after parsing" do
26
+ model = model_class.from_xml(xml)
27
+ expect(model.pending_plan_root_element).to be_nil
28
+ end
29
+
30
+ it "still captures the input namespace declarations" do
31
+ model = model_class.from_xml(xml)
32
+ expect(model.import_declaration_plan).to be_a(Lutaml::Xml::DeclarationPlan)
33
+ end
34
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: lutaml-model
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.8.87
4
+ version: 0.8.88
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ribose Inc.
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2026-09-30 00:00:00.000000000 Z
11
+ date: 2026-10-01 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: base64
@@ -1977,6 +1977,7 @@ files:
1977
1977
  - spec/lutaml/xml/opal_xml_spec.rb
1978
1978
  - spec/lutaml/xml/ordered_method_spec.rb
1979
1979
  - spec/lutaml/xml/out_of_namespace_parsing_spec.rb
1980
+ - spec/lutaml/xml/parse_dom_retention_spec.rb
1980
1981
  - spec/lutaml/xml/pipeline_integration_spec.rb
1981
1982
  - spec/lutaml/xml/plain_model_accessors_spec.rb
1982
1983
  - spec/lutaml/xml/plan_fast_path_spec.rb