lutaml-model 0.8.106 → 0.8.107
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 +4 -4
- data/Gemfile +1 -1
- data/lib/lutaml/model/attribute.rb +9 -3
- data/lib/lutaml/model/configuration.rb +1 -1
- data/lib/lutaml/model/serialize/initialization.rb +13 -1
- data/lib/lutaml/model/version.rb +1 -1
- data/lib/lutaml/xml/leptris/plan_hydrator.rb +56 -26
- data/lib/lutaml/xml/leptris.rb +8 -6
- data/spec/lutaml/xml/plan_fast_path_spec.rb +4 -3
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 1bcd55c9589b05b0eb9c4c9e0231a8e92b9063c3c34a5f9c76da5fc99e2c28f9
|
|
4
|
+
data.tar.gz: eb53b700df96a27985ff448b264fc9c4f022200d0efc8ebb313db635c6618f49
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 3e865d6c056755e187437ebf83d2423b9027df62f0f91822ff85f52541b935219869fb76e3cdd5f98c7422aef828ecea618c454259e82f0c86fa9457ca4e43dc
|
|
7
|
+
data.tar.gz: 65b581acd3765958cbf7d0e2f1607dfc552d60b441ea42d85bbd247a5c04b4487957cd951853c1fc822b3bd79ad701e6c02820476b022af5f73398d7dc8191e4
|
data/Gemfile
CHANGED
|
@@ -49,7 +49,7 @@ gem "rexml"
|
|
|
49
49
|
# exact-URI attribute rows, nested-attribute capture, wildcard rows,
|
|
50
50
|
# walk-side ns retention and leniency, :unqualified rows, and the
|
|
51
51
|
# address-based text attach all ride it.
|
|
52
|
-
gem "leptris", ">= 1.9.
|
|
52
|
+
gem "leptris", ">= 1.9.351.1"
|
|
53
53
|
gem "teptris"
|
|
54
54
|
gem "yeptris" unless Gem.win_platform?
|
|
55
55
|
# TODO: revert rng branch to main when lutaml/rng#32 is merged
|
|
@@ -389,12 +389,18 @@ module Lutaml
|
|
|
389
389
|
|
|
390
390
|
def cast_value(value, register)
|
|
391
391
|
if list_like?(value)
|
|
392
|
-
|
|
392
|
+
items = cast_items(value, register)
|
|
393
393
|
elsif coerce_to_collection?(value)
|
|
394
|
-
|
|
394
|
+
items = cast_items([value], register)
|
|
395
395
|
else
|
|
396
|
-
cast_element(value, register)
|
|
396
|
+
return cast_element(value, register)
|
|
397
397
|
end
|
|
398
|
+
# cast_items already returns a fresh array, so the plain-Array
|
|
399
|
+
# collection needs no re-wrap — build_collection's
|
|
400
|
+
# collection_class.new would copy it again.
|
|
401
|
+
return items if !custom_collection? && collection_class == ::Array
|
|
402
|
+
|
|
403
|
+
build_collection(items, register: register)
|
|
398
404
|
end
|
|
399
405
|
|
|
400
406
|
# Plain Arrays count as list input. On singular attributes
|
|
@@ -223,6 +223,7 @@ module Lutaml
|
|
|
223
223
|
# Clear memoized attribute merge (see .attributes)
|
|
224
224
|
@merged_attributes_cache = nil
|
|
225
225
|
@instantiate_writers = nil
|
|
226
|
+
@instantiate_attr_pairs = nil
|
|
226
227
|
|
|
227
228
|
# Clear per-Attribute type caches (stale entries from GC'd TypeContext objects)
|
|
228
229
|
class_attributes.each_value(&:clear_type_cache)
|
|
@@ -402,7 +403,10 @@ module Lutaml
|
|
|
402
403
|
attrs_by = attributes(register_id)
|
|
403
404
|
writers = instantiate_writers(register_id)
|
|
404
405
|
given = 0
|
|
405
|
-
|
|
406
|
+
# Memoized pair array: Hash#each mints a [name, attr] pair
|
|
407
|
+
# per attribute per instance (~5 allocs/instance on
|
|
408
|
+
# hydration-heavy corpora); Array#each reuses its elements.
|
|
409
|
+
instantiate_attr_pairs(register_id).each do |name, attr|
|
|
406
410
|
next if attr.derived?
|
|
407
411
|
|
|
408
412
|
# Symbol keys hit directly (native-extension callers);
|
|
@@ -468,6 +472,14 @@ module Lutaml
|
|
|
468
472
|
end
|
|
469
473
|
end
|
|
470
474
|
|
|
475
|
+
# [name, attr] pairs in attributes order — instantiate's loop
|
|
476
|
+
# iterates this instead of the attributes Hash so the per-call
|
|
477
|
+
# pair array disappears (see the instantiate comment).
|
|
478
|
+
def instantiate_attr_pairs(register_id = nil)
|
|
479
|
+
(@instantiate_attr_pairs ||= {})[register_id] ||=
|
|
480
|
+
attributes(register_id).to_a.freeze
|
|
481
|
+
end
|
|
482
|
+
|
|
471
483
|
# Define register-specific attribute methods on the class itself.
|
|
472
484
|
#
|
|
473
485
|
# Called once per (class, register) combination. Replaces per-instance
|
data/lib/lutaml/model/version.rb
CHANGED
|
@@ -45,7 +45,7 @@ module Lutaml
|
|
|
45
45
|
element_buckets(node, plan[:contested_names])
|
|
46
46
|
end
|
|
47
47
|
attr_kwargs = attributes_kwargs(plan, value, node)
|
|
48
|
-
child_kwargs, children, delegates =
|
|
48
|
+
child_kwargs, children, delegates, run_children =
|
|
49
49
|
children_kwargs(model_class, plan, value, buckets, register)
|
|
50
50
|
plan[:collection_defaults].each do |name|
|
|
51
51
|
next if attr_kwargs.key?(name) || child_kwargs.key?(name)
|
|
@@ -88,7 +88,8 @@ module Lutaml
|
|
|
88
88
|
child.lutaml_parent = instance
|
|
89
89
|
child.lutaml_root ||= instance.lutaml_root || instance
|
|
90
90
|
end
|
|
91
|
-
interpret_deferred(model_class, plan, value, instance, node,
|
|
91
|
+
interpret_deferred(model_class, plan, value, instance, node,
|
|
92
|
+
buckets, run_children)
|
|
92
93
|
route_delegates(delegates, instance)
|
|
93
94
|
instance
|
|
94
95
|
end
|
|
@@ -148,8 +149,12 @@ module Lutaml
|
|
|
148
149
|
kwargs = {}
|
|
149
150
|
sole_claimants = sole_claimant_names(plan)
|
|
150
151
|
ns_attr_names = plan[:ns_attr_names]
|
|
151
|
-
plan_attr_rows(plan)
|
|
152
|
-
|
|
152
|
+
rows = plan_attr_rows(plan)
|
|
153
|
+
# #1670 batch face (leptris 1.9.351.1): one FFI crossing
|
|
154
|
+
# per node instead of one per (node x attribute name).
|
|
155
|
+
bound = value.attributes_snapshot
|
|
156
|
+
rows.each do |rule, attr, name|
|
|
157
|
+
v = bound[name]
|
|
153
158
|
if v.nil? && node && sole_claimants.include?(name)
|
|
154
159
|
# #754/#790 parity: a sole-claimant attribute rule binds
|
|
155
160
|
# any qualification (the interpretive matcher's lenient
|
|
@@ -239,7 +244,8 @@ module Lutaml
|
|
|
239
244
|
def children_kwargs(_model_class, plan, value, buckets = nil,
|
|
240
245
|
register = nil)
|
|
241
246
|
register ||= Lutaml::Model::Config.default_register
|
|
242
|
-
grouped, tagged = group_children(value,
|
|
247
|
+
grouped, tagged, run_children = group_children(value,
|
|
248
|
+
plan[:row_tags])
|
|
243
249
|
buckets = nil unless buckets && plan[:needs_nodes]
|
|
244
250
|
kwargs = {}
|
|
245
251
|
children = []
|
|
@@ -250,9 +256,17 @@ module Lutaml
|
|
|
250
256
|
row_tags = plan[:row_tags]
|
|
251
257
|
wire_names = row_wire_names(plan)
|
|
252
258
|
row_memo = child_row_plans(plan, register)
|
|
259
|
+
# Array#each with a top-of-loop counter: each_with_index
|
|
260
|
+
# mints an enumerator per hydrated node — 14.7K allocations
|
|
261
|
+
# per parse on the pnas guardrail — and next must keep its
|
|
262
|
+
# continue semantics.
|
|
263
|
+
rows = plan[:rows]
|
|
264
|
+
idx = -1
|
|
253
265
|
# rubocop:disable Metrics/BlockLength -- one row-kind
|
|
254
266
|
# dispatch table; splitting it would scatter the contract
|
|
255
|
-
|
|
267
|
+
rows.each do |row|
|
|
268
|
+
idx += 1
|
|
269
|
+
rule, attr, kind, spelling, delegate, _row_type = row
|
|
256
270
|
tag = row_tags&.[](idx)
|
|
257
271
|
name = wire_names[idx]
|
|
258
272
|
case kind
|
|
@@ -284,7 +298,7 @@ module Lutaml
|
|
|
284
298
|
when :wildcard_any
|
|
285
299
|
# interpreted post-instance (interpret_deferred)
|
|
286
300
|
when :content
|
|
287
|
-
runs = content_runs(value)
|
|
301
|
+
runs = content_runs(value, run_children)
|
|
288
302
|
if runs.empty? && !attr.collection?
|
|
289
303
|
# An empty element's content binds through the rule's
|
|
290
304
|
# value map for scalar content attributes: "" for the
|
|
@@ -375,11 +389,17 @@ module Lutaml
|
|
|
375
389
|
cursor = if child_plan[:needs_nodes] && buckets
|
|
376
390
|
selected
|
|
377
391
|
end
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
392
|
+
# Indexed loop: each_with_index.map mints an
|
|
393
|
+
# enumerator per node with nested rows.
|
|
394
|
+
count = values.size
|
|
395
|
+
items = Array.new(count)
|
|
396
|
+
pos = 0
|
|
397
|
+
while pos < count
|
|
398
|
+
items[pos] = call(child_type, child_plan, values[pos],
|
|
399
|
+
node: cursor && cursor[pos],
|
|
400
|
+
register: child_register)
|
|
401
|
+
pos += 1
|
|
402
|
+
end
|
|
383
403
|
end
|
|
384
404
|
next if items.empty?
|
|
385
405
|
|
|
@@ -402,7 +422,7 @@ module Lutaml
|
|
|
402
422
|
attr.collection? ? values : values.first)
|
|
403
423
|
end
|
|
404
424
|
end
|
|
405
|
-
[kwargs, children, delegates]
|
|
425
|
+
[kwargs, children, delegates, run_children]
|
|
406
426
|
end
|
|
407
427
|
|
|
408
428
|
# Deferred islands: raw subtrees become wrapper elements via a
|
|
@@ -413,15 +433,20 @@ module Lutaml
|
|
|
413
433
|
# node (no fragment parse — the reparse dominated deferred
|
|
414
434
|
# hydration). Ordered children only land here without a node.
|
|
415
435
|
def interpret_deferred(model_class, plan, value, instance, node = nil,
|
|
416
|
-
buckets = nil)
|
|
417
|
-
|
|
436
|
+
buckets = nil, run_children = nil)
|
|
437
|
+
# Array#each + top counter: no per-node enumerator (see
|
|
438
|
+
# children_kwargs).
|
|
439
|
+
rows = plan[:rows]
|
|
440
|
+
idx = -1
|
|
441
|
+
rows.each do |row|
|
|
442
|
+
idx += 1
|
|
418
443
|
rule = row[0]
|
|
419
444
|
attr = row[1]
|
|
420
445
|
kind = row[2]
|
|
421
446
|
wire_name = row_wire_names(plan)[idx]
|
|
422
447
|
case kind
|
|
423
448
|
when :content_deferred
|
|
424
|
-
runs = content_runs(value)
|
|
449
|
+
runs = content_runs(value, run_children)
|
|
425
450
|
if runs.empty?
|
|
426
451
|
# The interpretive pipeline classifies the raw ""
|
|
427
452
|
# through the rule's value map: the :empty directive
|
|
@@ -587,15 +612,14 @@ module Lutaml
|
|
|
587
612
|
end
|
|
588
613
|
end
|
|
589
614
|
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
615
|
+
# Content runs ride the children snapshot: the nil-name
|
|
616
|
+
# entries ARE the :collection run groups (one crossing per
|
|
617
|
+
# node via group_children, none here). The per-group
|
|
618
|
+
# at/count crossings are the drain itself.
|
|
619
|
+
def content_runs(_value, run_children)
|
|
620
|
+
(run_children || []).flat_map do |group|
|
|
621
|
+
Array.new(group.count) { |j| group.at(j).string_value }
|
|
597
622
|
end
|
|
598
|
-
out
|
|
599
623
|
end
|
|
600
624
|
|
|
601
625
|
# Native collection rows echo their producing row's name
|
|
@@ -663,8 +687,14 @@ module Lutaml
|
|
|
663
687
|
grouped = {}
|
|
664
688
|
want_tags = !row_tags.nil?
|
|
665
689
|
tagged = want_tags ? {} : nil
|
|
690
|
+
run_children = nil
|
|
666
691
|
names.each_with_index do |name, i|
|
|
667
|
-
|
|
692
|
+
if name.nil?
|
|
693
|
+
# Content runs: same snapshot pass — at()/count() per
|
|
694
|
+
# child cost a crossing and a wrapper each.
|
|
695
|
+
(run_children ||= []) << children[i]
|
|
696
|
+
next
|
|
697
|
+
end
|
|
668
698
|
|
|
669
699
|
child = children[i]
|
|
670
700
|
(grouped[name] ||= []) << child
|
|
@@ -672,7 +702,7 @@ module Lutaml
|
|
|
672
702
|
(tagged[tags[i]] ||= []) << child
|
|
673
703
|
end
|
|
674
704
|
end
|
|
675
|
-
[grouped, tagged]
|
|
705
|
+
[grouped, tagged, run_children]
|
|
676
706
|
end
|
|
677
707
|
|
|
678
708
|
# Element children bucketed by local name, document order
|
data/lib/lutaml/xml/leptris.rb
CHANGED
|
@@ -8,16 +8,18 @@ module Lutaml
|
|
|
8
8
|
# reference leptris types directly (the moxml adapters and the
|
|
9
9
|
# interpretive path never enter here).
|
|
10
10
|
module Leptris
|
|
11
|
-
# Single capability floor (leptris 1.9.
|
|
11
|
+
# Single capability floor (leptris 1.9.351.1): the child-handles
|
|
12
12
|
# snapshot (1.9.273), exact-URI attribute rows (#1486, 1.9.289),
|
|
13
13
|
# nested-attribute capture (#1563, 1.9.313), wildcard rows
|
|
14
14
|
# (#1552, 1.9.313), child-row ns_uri retention and qualified
|
|
15
15
|
# attribute leniency (#1585/#1586, 1.9.320), the :unqualified ns
|
|
16
|
-
# form (#1560, 1.9.323),
|
|
17
|
-
# text attach (#408, 1.9.334/1.9.336)
|
|
18
|
-
#
|
|
19
|
-
#
|
|
20
|
-
|
|
16
|
+
# form (#1560, 1.9.323), build_result with the address-based
|
|
17
|
+
# text attach (#408, 1.9.334/1.9.336), and the plan-value
|
|
18
|
+
# attributes batch face (PlanValue#attributes_snapshot,
|
|
19
|
+
# leptris-ruby#447). Bundles resolving an older gem keep the
|
|
20
|
+
# interpretive path — PlanCompiler.compile answers nil — the
|
|
21
|
+
# opt-out protocol.
|
|
22
|
+
MIN_LEPTRIS_VERSION = "1.9.351.1"
|
|
21
23
|
|
|
22
24
|
# Memoized: the compiler consults this on every cache hit (per
|
|
23
25
|
# child row per node), and the comparison minted a Gem::Version
|
|
@@ -31,13 +31,14 @@ RSpec.describe "XML plan fast path" do
|
|
|
31
31
|
let(:root_class) { classes[1] }
|
|
32
32
|
|
|
33
33
|
around do |example|
|
|
34
|
-
|
|
34
|
+
old_fast = Lutaml::Model::Config.instance.xml_plan_fast_path
|
|
35
|
+
old_ordered = Lutaml::Model::Config.instance.xml_plan_ordered_path
|
|
35
36
|
Lutaml::Model::Config.instance.xml_plan_fast_path = true
|
|
36
37
|
Lutaml::Model::Config.instance.xml_plan_ordered_path = true
|
|
37
38
|
example.run
|
|
38
39
|
ensure
|
|
39
|
-
Lutaml::Model::Config.instance.xml_plan_fast_path =
|
|
40
|
-
Lutaml::Model::Config.instance.xml_plan_ordered_path =
|
|
40
|
+
Lutaml::Model::Config.instance.xml_plan_fast_path = old_fast
|
|
41
|
+
Lutaml::Model::Config.instance.xml_plan_ordered_path = old_ordered
|
|
41
42
|
end
|
|
42
43
|
|
|
43
44
|
# Real-corpus crash (niso-jats): sec-in-sec nesting is a
|
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.
|
|
4
|
+
version: 0.8.107
|
|
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-10-
|
|
11
|
+
date: 2026-10-14 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: base64
|