lutaml-model 0.8.89 → 0.8.91
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/bench/bench_common.rb +1 -1
- data/lib/lutaml/model/version.rb +1 -1
- data/lib/lutaml/xml/leptris/plan_compiler.rb +3 -3
- data/lib/lutaml/xml/namespace.rb +18 -8
- data/lib/lutaml/xml/namespace_needs.rb +77 -49
- data/lib/lutaml/xml/transformation/ordered_applier.rb +20 -14
- data/lib/lutaml/xml/transformation.rb +15 -1
- 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: f05bb7edf0d4a86ba5bc0105d3d269b2f3c28532cdd88e03be857fb4e426a505
|
|
4
|
+
data.tar.gz: 806a73d21e506337a5d039563225e3b000e7f1a3c0e701ca58ae8d58fdbe3c8d
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 0d02bf01cd956193aeab21358508339542b2f104c767347dd7fd2fbc7134c461692d4358f755e952082570683e7ecaf0e39c989d977d5b458b87807892b24997
|
|
7
|
+
data.tar.gz: e833ac9280da9e33ebb3427f6d34d53f3da5dd5f7d43d408144cefc4c5087507cc92013b956d1d7b857a4d4e5bde119e58ea926195f07d95eba4ceef4e73a704
|
data/bench/bench_common.rb
CHANGED
data/lib/lutaml/model/version.rb
CHANGED
|
@@ -26,9 +26,9 @@ module Lutaml
|
|
|
26
26
|
# metadata) completes.
|
|
27
27
|
module PlanCompiler
|
|
28
28
|
# TODO.perf/15: env-gated histogram of why models opt out of the
|
|
29
|
-
# plan path. Set PLAN_COMPILE_STATS=1 and read
|
|
30
|
-
# after a parse. Zero cost when disabled.
|
|
31
|
-
def self.
|
|
29
|
+
# plan path. Set PLAN_COMPILE_STATS=1 and read
|
|
30
|
+
# PlanCompiler.plan_stats after a parse. Zero cost when disabled.
|
|
31
|
+
def self.plan_stats
|
|
32
32
|
@plan_stats ||= Hash.new(0)
|
|
33
33
|
end
|
|
34
34
|
|
data/lib/lutaml/xml/namespace.rb
CHANGED
|
@@ -58,7 +58,10 @@ module Lutaml
|
|
|
58
58
|
# @param value [String, nil] the namespace URI
|
|
59
59
|
# @return [String, nil] the namespace URI
|
|
60
60
|
def uri(value = nil)
|
|
61
|
-
|
|
61
|
+
if value
|
|
62
|
+
@uri_value = value
|
|
63
|
+
@to_key = nil
|
|
64
|
+
end
|
|
62
65
|
@uri_value
|
|
63
66
|
end
|
|
64
67
|
|
|
@@ -130,6 +133,7 @@ module Lutaml
|
|
|
130
133
|
def prefix_default(value = nil)
|
|
131
134
|
if value
|
|
132
135
|
@prefix_default_value = value.to_s
|
|
136
|
+
@to_key = nil
|
|
133
137
|
end
|
|
134
138
|
@prefix_default_value
|
|
135
139
|
end
|
|
@@ -286,13 +290,19 @@ module Lutaml
|
|
|
286
290
|
#
|
|
287
291
|
# @api private
|
|
288
292
|
def to_key
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
293
|
+
# Called per element on the serialize path (90k births per
|
|
294
|
+
# 200-paragraph to_xml); memoized since uri/prefix_default
|
|
295
|
+
# are set at class-definition time. The setters above
|
|
296
|
+
# invalidate on reconfiguration.
|
|
297
|
+
@to_key ||= begin
|
|
298
|
+
prefix = prefix_default
|
|
299
|
+
namespace_uri = uri
|
|
300
|
+
|
|
301
|
+
if prefix && !prefix.empty?
|
|
302
|
+
"#{prefix}:#{namespace_uri}"
|
|
303
|
+
else
|
|
304
|
+
":#{namespace_uri}"
|
|
305
|
+
end
|
|
296
306
|
end
|
|
297
307
|
end
|
|
298
308
|
|
|
@@ -8,20 +8,45 @@ module Lutaml
|
|
|
8
8
|
# This class maintains MECE responsibility: it only stores and organizes
|
|
9
9
|
# namespace needs data. It does NOT make decisions or build XML.
|
|
10
10
|
class NamespaceNeeds
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
@
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
11
|
+
# Lazy readers: leaf nodes never touch most containers; the
|
|
12
|
+
# eager 4-Sets + 3-Hashes + 2-Arrays was the serialize path's
|
|
13
|
+
# top allocation site (150k births per 200-paragraph to_xml).
|
|
14
|
+
# Readers get the frozen empty truth; writers build on first
|
|
15
|
+
# touch through the ensure_* helpers below.
|
|
16
|
+
EMPTY_HASH = {}.freeze
|
|
17
|
+
EMPTY_SET = ::Set.new.freeze
|
|
18
|
+
EMPTY_ARRAY = [].freeze
|
|
19
|
+
|
|
20
|
+
def namespaces
|
|
21
|
+
@namespaces || EMPTY_HASH
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def children
|
|
25
|
+
@children || EMPTY_HASH
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def type_namespaces
|
|
29
|
+
@type_namespaces || EMPTY_HASH
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def type_namespace_classes
|
|
33
|
+
@type_namespace_classes || EMPTY_SET
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def type_attribute_namespaces
|
|
37
|
+
@type_attribute_namespaces || EMPTY_SET
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def type_element_namespaces
|
|
41
|
+
@type_element_namespaces || EMPTY_SET
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def type_refs
|
|
45
|
+
@type_refs || EMPTY_ARRAY
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def namespace_scope_configs
|
|
49
|
+
@namespace_scope_configs || EMPTY_ARRAY
|
|
25
50
|
end
|
|
26
51
|
|
|
27
52
|
# Add namespace usage for a specific namespace
|
|
@@ -32,29 +57,29 @@ module Lutaml
|
|
|
32
57
|
raise ArgumentError, "Expected NamespaceUsage, got #{usage.class}"
|
|
33
58
|
end
|
|
34
59
|
|
|
35
|
-
@namespaces[key] = usage
|
|
60
|
+
(@namespaces ||= {})[key] = usage
|
|
36
61
|
end
|
|
37
62
|
|
|
38
63
|
# Track a type namespace for a specific attribute
|
|
39
64
|
# @param attr_name [Symbol] Attribute name
|
|
40
65
|
# @param ns_class [Class] XmlNamespace class
|
|
41
66
|
def add_type_namespace(attr_name, ns_class)
|
|
42
|
-
@type_namespaces[attr_name] = ns_class
|
|
43
|
-
@type_namespace_classes << ns_class
|
|
67
|
+
(@type_namespaces ||= {})[attr_name] = ns_class
|
|
68
|
+
(@type_namespace_classes ||= ::Set.new) << ns_class
|
|
44
69
|
end
|
|
45
70
|
|
|
46
71
|
# Add type attribute namespace to tracking set
|
|
47
72
|
# @param ns_class [Class] XmlNamespace class
|
|
48
73
|
def add_type_attribute_namespace(ns_class)
|
|
49
|
-
@type_attribute_namespaces << ns_class
|
|
50
|
-
@type_namespace_classes << ns_class
|
|
74
|
+
(@type_attribute_namespaces ||= ::Set.new) << ns_class
|
|
75
|
+
(@type_namespace_classes ||= ::Set.new) << ns_class
|
|
51
76
|
end
|
|
52
77
|
|
|
53
78
|
# Add type element namespace to tracking set
|
|
54
79
|
# @param ns_class [Class] XmlNamespace class
|
|
55
80
|
def add_type_element_namespace(ns_class)
|
|
56
|
-
@type_element_namespaces << ns_class
|
|
57
|
-
@type_namespace_classes << ns_class
|
|
81
|
+
(@type_element_namespaces ||= ::Set.new) << ns_class
|
|
82
|
+
(@type_namespace_classes ||= ::Set.new) << ns_class
|
|
58
83
|
end
|
|
59
84
|
|
|
60
85
|
# Add a type reference for lazy resolution
|
|
@@ -65,13 +90,13 @@ module Lutaml
|
|
|
65
90
|
"Expected TypeNamespace::Reference, got #{reference.class}"
|
|
66
91
|
end
|
|
67
92
|
|
|
68
|
-
@type_refs << reference
|
|
93
|
+
(@type_refs ||= []) << reference
|
|
69
94
|
end
|
|
70
95
|
|
|
71
96
|
# Clear type references after resolution
|
|
72
97
|
# Used by TypeNamespaceResolver to prevent reprocessing
|
|
73
98
|
def clear_type_refs
|
|
74
|
-
@type_refs
|
|
99
|
+
@type_refs&.clear
|
|
75
100
|
end
|
|
76
101
|
|
|
77
102
|
# Add namespace scope configuration
|
|
@@ -82,7 +107,7 @@ module Lutaml
|
|
|
82
107
|
"Expected NamespaceScopeConfig, got #{config.class}"
|
|
83
108
|
end
|
|
84
109
|
|
|
85
|
-
@namespace_scope_configs << config
|
|
110
|
+
(@namespace_scope_configs ||= []) << config
|
|
86
111
|
end
|
|
87
112
|
|
|
88
113
|
# Add child needs
|
|
@@ -94,7 +119,7 @@ module Lutaml
|
|
|
94
119
|
"Expected NamespaceNeeds, got #{child_needs.class}"
|
|
95
120
|
end
|
|
96
121
|
|
|
97
|
-
@children[name] = child_needs
|
|
122
|
+
(@children ||= {})[name] = child_needs
|
|
98
123
|
end
|
|
99
124
|
|
|
100
125
|
# Merge another NamespaceNeeds into this one
|
|
@@ -105,9 +130,9 @@ module Lutaml
|
|
|
105
130
|
raise ArgumentError, "Expected NamespaceNeeds, got #{other.class}"
|
|
106
131
|
end
|
|
107
132
|
|
|
108
|
-
# Merge namespaces
|
|
133
|
+
# Merge namespaces (build containers on first touch)
|
|
109
134
|
other.namespaces.each do |key, usage|
|
|
110
|
-
if @namespaces.key?(key)
|
|
135
|
+
if (@namespaces ||= {}).key?(key)
|
|
111
136
|
@namespaces[key].merge(usage)
|
|
112
137
|
else
|
|
113
138
|
@namespaces[key] = usage
|
|
@@ -116,7 +141,7 @@ module Lutaml
|
|
|
116
141
|
|
|
117
142
|
# Merge children
|
|
118
143
|
other.children.each do |name, child_needs|
|
|
119
|
-
if @children.key?(name)
|
|
144
|
+
if (@children ||= {}).key?(name)
|
|
120
145
|
@children[name].merge(child_needs)
|
|
121
146
|
else
|
|
122
147
|
@children[name] = child_needs
|
|
@@ -124,17 +149,20 @@ module Lutaml
|
|
|
124
149
|
end
|
|
125
150
|
|
|
126
151
|
# Merge type namespaces
|
|
127
|
-
@type_namespaces.merge!(other.type_namespaces)
|
|
128
|
-
@type_namespace_classes.
|
|
129
|
-
|
|
130
|
-
@
|
|
152
|
+
(@type_namespaces ||= {}).merge!(other.type_namespaces)
|
|
153
|
+
(@type_namespace_classes ||= ::Set.new)
|
|
154
|
+
.merge(other.type_namespace_classes)
|
|
155
|
+
(@type_attribute_namespaces ||= ::Set.new)
|
|
156
|
+
.merge(other.type_attribute_namespaces)
|
|
157
|
+
(@type_element_namespaces ||= ::Set.new)
|
|
158
|
+
.merge(other.type_element_namespaces)
|
|
131
159
|
|
|
132
160
|
# Merge type refs
|
|
133
|
-
@type_refs.concat(other.type_refs)
|
|
161
|
+
(@type_refs ||= []).concat(other.type_refs)
|
|
134
162
|
|
|
135
163
|
# Merge namespace scope configs (avoiding duplicates)
|
|
136
164
|
other.namespace_scope_configs.each do |config|
|
|
137
|
-
unless @namespace_scope_configs.any? do |c|
|
|
165
|
+
unless (@namespace_scope_configs ||= []).any? do |c|
|
|
138
166
|
c.namespace_class == config.namespace_class
|
|
139
167
|
end
|
|
140
168
|
@namespace_scope_configs << config
|
|
@@ -147,32 +175,32 @@ module Lutaml
|
|
|
147
175
|
# Check if needs are empty
|
|
148
176
|
# @return [Boolean]
|
|
149
177
|
def empty?
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
178
|
+
namespaces.empty? &&
|
|
179
|
+
children.empty? &&
|
|
180
|
+
type_refs.empty? &&
|
|
181
|
+
type_namespaces.empty? &&
|
|
182
|
+
namespace_scope_configs.empty?
|
|
155
183
|
end
|
|
156
184
|
|
|
157
185
|
# Get namespace usage by key
|
|
158
186
|
# @param key [String] Namespace key
|
|
159
187
|
# @return [NamespaceUsage, nil]
|
|
160
188
|
def namespace(key)
|
|
161
|
-
|
|
189
|
+
namespaces[key]
|
|
162
190
|
end
|
|
163
191
|
|
|
164
192
|
# Get child needs by name
|
|
165
193
|
# @param name [Symbol] Child attribute name
|
|
166
194
|
# @return [NamespaceNeeds, nil]
|
|
167
195
|
def child(name)
|
|
168
|
-
|
|
196
|
+
children[name]
|
|
169
197
|
end
|
|
170
198
|
|
|
171
199
|
# Check if a namespace is in scope configuration
|
|
172
200
|
# @param ns_class [Class] XmlNamespace class
|
|
173
201
|
# @return [NamespaceScopeConfig, nil]
|
|
174
202
|
def scope_config_for(ns_class)
|
|
175
|
-
|
|
203
|
+
namespace_scope_configs.find do |config|
|
|
176
204
|
config.namespace_class == ns_class
|
|
177
205
|
end
|
|
178
206
|
end
|
|
@@ -180,28 +208,28 @@ module Lutaml
|
|
|
180
208
|
# Get all namespace classes (from usage and type namespaces)
|
|
181
209
|
# @return [Set<Class>]
|
|
182
210
|
def all_namespace_classes
|
|
183
|
-
|
|
184
|
-
|
|
211
|
+
classes = Set.new(namespaces.values.map(&:namespace_class))
|
|
212
|
+
classes.merge(type_namespace_classes)
|
|
185
213
|
end
|
|
186
214
|
|
|
187
215
|
# Validate internal consistency
|
|
188
216
|
# @raise [RuntimeError] if inconsistent state detected
|
|
189
217
|
def validate!
|
|
190
218
|
# Type attribute and element namespaces should be mutually exclusive
|
|
191
|
-
overlap =
|
|
219
|
+
overlap = type_attribute_namespaces & type_element_namespaces
|
|
192
220
|
unless overlap.empty?
|
|
193
221
|
raise "Type namespaces appear in both attribute and element contexts: #{overlap.to_a}"
|
|
194
222
|
end
|
|
195
223
|
|
|
196
224
|
# All type namespace classes should be in type_namespace_classes
|
|
197
|
-
|
|
198
|
-
unless
|
|
225
|
+
type_attribute_namespaces.each do |ns_class|
|
|
226
|
+
unless type_namespace_classes.include?(ns_class)
|
|
199
227
|
raise "Type attribute namespace #{ns_class} not in type_namespace_classes"
|
|
200
228
|
end
|
|
201
229
|
end
|
|
202
230
|
|
|
203
|
-
|
|
204
|
-
unless
|
|
231
|
+
type_element_namespaces.each do |ns_class|
|
|
232
|
+
unless type_namespace_classes.include?(ns_class)
|
|
205
233
|
raise "Type element namespace #{ns_class} not in type_namespace_classes"
|
|
206
234
|
end
|
|
207
235
|
end
|
|
@@ -101,20 +101,26 @@ model_class, register_id)
|
|
|
101
101
|
return
|
|
102
102
|
end
|
|
103
103
|
|
|
104
|
-
#
|
|
105
|
-
|
|
106
|
-
#
|
|
107
|
-
#
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
104
|
+
# Fast path: the ordered walk pre-sets the parent context keys
|
|
105
|
+
# for every value it emits (one hash per walk, not per value).
|
|
106
|
+
# The three keys are always written together, so a parent
|
|
107
|
+
# identity match implies the namespace context matches too.
|
|
108
|
+
if options[:parent_element] == parent
|
|
109
|
+
child_options = options
|
|
110
|
+
else
|
|
111
|
+
parent_ns_class = parent.namespace_class
|
|
112
|
+
# Only pass element_form_default VALUE if it was explicitly set
|
|
113
|
+
# When not set (defaults to :unqualified), pass nil to avoid incorrect blank namespace treatment
|
|
114
|
+
parent_element_form_default = if parent_ns_class&.element_form_default_set?
|
|
115
|
+
parent_ns_class.element_form_default
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
child_options = options.dup
|
|
119
|
+
child_options[:parent_namespace_class] = parent_ns_class
|
|
120
|
+
child_options[:parent_element_form_default] =
|
|
121
|
+
parent_element_form_default
|
|
122
|
+
child_options[:parent_element] = parent
|
|
123
|
+
end
|
|
118
124
|
|
|
119
125
|
element = yield(rule, value, child_options)
|
|
120
126
|
parent.add_child(element) if element
|
|
@@ -265,6 +265,20 @@ module Lutaml
|
|
|
265
265
|
# Loop-invariant for the whole ordered walk (see
|
|
266
266
|
# apply_standard_rules): one merge per model, not per rule.
|
|
267
267
|
rule_options = options.merge(current_model: model_instance)
|
|
268
|
+
# `root` is the parent of every element value this walk emits,
|
|
269
|
+
# so the parent context keys are walk-invariant too: set them
|
|
270
|
+
# once here and apply_element_rule_single's fast path reuses
|
|
271
|
+
# this hash for every value instead of merging per value.
|
|
272
|
+
# rule_options itself keeps the caller's parent context for
|
|
273
|
+
# the attribute/content/raw rules dispatched via apply_rule.
|
|
274
|
+
element_value_options = rule_options.dup
|
|
275
|
+
root_ns_class = root.namespace_class
|
|
276
|
+
element_value_options[:parent_namespace_class] = root_ns_class
|
|
277
|
+
element_value_options[:parent_element_form_default] =
|
|
278
|
+
if root_ns_class&.element_form_default_set?
|
|
279
|
+
root_ns_class.element_form_default
|
|
280
|
+
end
|
|
281
|
+
element_value_options[:parent_element] = root
|
|
268
282
|
apply_rules_in_order(
|
|
269
283
|
root, model_instance, options,
|
|
270
284
|
compiled_rules, model_class, register_id
|
|
@@ -281,7 +295,7 @@ module Lutaml
|
|
|
281
295
|
parent: root,
|
|
282
296
|
rule: rule,
|
|
283
297
|
value: value,
|
|
284
|
-
options:
|
|
298
|
+
options: element_value_options,
|
|
285
299
|
) do |r, v, child_opts|
|
|
286
300
|
element = create_element_for_value(r, v, child_opts, model_class,
|
|
287
301
|
register_id, register)
|
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.91
|
|
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-03 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: base64
|