lutaml-model 0.8.94 → 0.8.95
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/.github/workflows/downstream-performance.yml +17 -6
- data/bench/bench_merge_attempts.rb +49 -0
- data/lib/lutaml/model/serialize/attribute_definition.rb +16 -13
- data/lib/lutaml/model/serialize/initialization.rb +13 -1
- data/lib/lutaml/model/serialize/model_import.rb +25 -0
- data/lib/lutaml/model/version.rb +1 -1
- data/lib/lutaml/xml/leptris/plan_compiler.rb +83 -12
- data/lib/lutaml/xml/leptris/plan_hydrator.rb +90 -9
- data/lib/lutaml/xml/leptris.rb +12 -0
- data/spec/lutaml/model/writer_assigned_attribute_spec.rb +2 -2
- data/spec/lutaml/xml/plan_fast_path_spec.rb +6 -4
- data/spec/lutaml/xml/plan_ns_less_document_spec.rb +86 -0
- data/spec/lutaml/xml/plan_type_ns_attribute_spec.rb +141 -0
- data/spec/person_spec.rb +8 -8
- metadata +5 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: c5933a3971f4f39e30541f504636641cd059e7a97751ba67a710203dbfdcc45a
|
|
4
|
+
data.tar.gz: c23b70227e80d99427b9d4125a09e33d9751112bc72a5e6abaca73d6a3897c60
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 473b9970246c24172d6040a9c5062fad06d0fea413a5e04956ac98c0990324f2543668436c9259ad2a30b8647555d1e00102f6346afd3018814d68c2841ca8e5
|
|
7
|
+
data.tar.gz: f48b568bdc715a7649a262ca5757474f56ecc52d775121be99f18f067570d48d5401ff29cd7141ff768e9ea0a3e0aef5996d32129b8907c386cfd3c72e9f5370
|
|
@@ -116,9 +116,16 @@ jobs:
|
|
|
116
116
|
File.write("Gemfile", out.join)
|
|
117
117
|
' /tmp/main-checkout
|
|
118
118
|
bundle install --jobs=4
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
119
|
+
# lutaml-model#834: min-of-N across attempts — contention
|
|
120
|
+
# inflates single-shot timings, so run three attempts and let
|
|
121
|
+
# the merge keep the least-interfered measurement per fixture.
|
|
122
|
+
for i in 1 2 3; do
|
|
123
|
+
bundle exec ruby ${{ github.workspace }}/bench/bench_${{ matrix.downstream }}.rb || \
|
|
124
|
+
echo "::warning::baseline attempt $i errored (#834) — continuing with remaining attempts"
|
|
125
|
+
cp "$BENCH_JSON" "$BENCH_JSON.attempt$i" 2>/dev/null || true
|
|
126
|
+
done
|
|
127
|
+
ruby ${{ github.workspace }}/bench/bench_merge_attempts.rb \
|
|
128
|
+
"$BENCH_JSON" "$BENCH_JSON.attempt1" "$BENCH_JSON.attempt2" "$BENCH_JSON.attempt3"
|
|
122
129
|
|
|
123
130
|
- name: Run current benchmark (PR branch)
|
|
124
131
|
env:
|
|
@@ -146,9 +153,13 @@ jobs:
|
|
|
146
153
|
File.write("Gemfile", out.join)
|
|
147
154
|
' ${{ github.workspace }}
|
|
148
155
|
bundle install --jobs=4
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
156
|
+
for i in 1 2 3; do
|
|
157
|
+
bundle exec ruby ${{ github.workspace }}/bench/bench_${{ matrix.downstream }}.rb || \
|
|
158
|
+
echo "::warning::current attempt $i errored (#834) — continuing with remaining attempts"
|
|
159
|
+
cp "$BENCH_JSON" "$BENCH_JSON.attempt$i" 2>/dev/null || true
|
|
160
|
+
done
|
|
161
|
+
ruby ${{ github.workspace }}/bench/bench_merge_attempts.rb \
|
|
162
|
+
"$BENCH_JSON" "$BENCH_JSON.attempt1" "$BENCH_JSON.attempt2" "$BENCH_JSON.attempt3"
|
|
152
163
|
|
|
153
164
|
- name: Compare baseline vs current
|
|
154
165
|
run: |
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# lutaml-model#834: merge N benchmark-attempt JSONs into one result set,
|
|
4
|
+
# taking the per-fixture minimum of every metric. Contention inflates
|
|
5
|
+
# timings; the minimum across attempts is the least-interfered
|
|
6
|
+
# measurement, so gates evaluated on merged output misfire only when
|
|
7
|
+
# EVERY attempt was slow.
|
|
8
|
+
#
|
|
9
|
+
# Usage: ruby bench_merge_attempts.rb <merged.json> <attempt1.json> [attempt2.json ...]
|
|
10
|
+
#
|
|
11
|
+
# Attempt files may be missing (crashed attempt): missing files are
|
|
12
|
+
# skipped; if none exist, nothing is written and the caller's compare
|
|
13
|
+
# step fails on the missing merged file.
|
|
14
|
+
if ARGV.length < 3
|
|
15
|
+
warn "Usage: #{$PROGRAM_NAME} <merged.json> <attempt1.json> [attempt2.json ...]"
|
|
16
|
+
exit 1
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
merged_path = ARGV[0]
|
|
20
|
+
attempt_paths = ARGV[1..]
|
|
21
|
+
|
|
22
|
+
require "json"
|
|
23
|
+
|
|
24
|
+
best = {}
|
|
25
|
+
attempt_paths.each do |path|
|
|
26
|
+
next unless File.exist?(path)
|
|
27
|
+
|
|
28
|
+
results = JSON.parse(File.read(path))
|
|
29
|
+
results.each do |label, metrics|
|
|
30
|
+
cur = best[label]
|
|
31
|
+
if cur
|
|
32
|
+
cur["avg_time"] = [cur["avg_time"], metrics["avg_time"]].min
|
|
33
|
+
cur["min_time"] = [cur["min_time"], metrics["min_time"]].min
|
|
34
|
+
cur["max_time"] = [cur["max_time"], metrics["max_time"]].min
|
|
35
|
+
cur["allocations"] = [cur["allocations"], metrics["allocations"]].min
|
|
36
|
+
cur["ips"] = [cur["ips"], metrics["ips"]].max
|
|
37
|
+
else
|
|
38
|
+
best[label] = metrics.dup
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
if best.nil? || best.empty?
|
|
44
|
+
warn "No attempt JSONs found among: #{attempt_paths.join(', ')}"
|
|
45
|
+
exit 1
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
File.write(merged_path, JSON.pretty_generate(best))
|
|
49
|
+
puts "Merged #{attempt_paths.size} attempt file(s) into #{merged_path} (#{best.size} fixtures)"
|
|
@@ -63,18 +63,14 @@ module Lutaml
|
|
|
63
63
|
end
|
|
64
64
|
RUBY
|
|
65
65
|
|
|
66
|
-
#
|
|
67
|
-
#
|
|
68
|
-
#
|
|
69
|
-
#
|
|
70
|
-
#
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
# initialized marker (sentinel / lazy collection) is unset;
|
|
75
|
-
# anything else was written since init and must never be
|
|
76
|
-
# overwritten by an unmatched rule's default assignment.
|
|
77
|
-
raw_lines = attrs.map do |name, _attr|
|
|
66
|
+
# Raw readers compiled alongside the seeding so every
|
|
67
|
+
# attribute the register can see — including late-merged
|
|
68
|
+
# imports that bypass define_attribute_methods — carries
|
|
69
|
+
# one (#933). Idempotent with the declaration-time
|
|
70
|
+
# definition.
|
|
71
|
+
raw_lines = attrs.filter_map do |name, _attr|
|
|
72
|
+
next unless name.to_s.match?(PLAIN_NAME)
|
|
73
|
+
|
|
78
74
|
<<~RUBY
|
|
79
75
|
def __lutaml_raw_#{name}
|
|
80
76
|
@#{name}
|
|
@@ -126,6 +122,9 @@ module Lutaml
|
|
|
126
122
|
end
|
|
127
123
|
remove_method(compiled) if defined_now
|
|
128
124
|
end
|
|
125
|
+
# The names cache must go with the methods: the memoized
|
|
126
|
+
# lookup answers stale names for removed methods otherwise.
|
|
127
|
+
@state_defaults_names.clear
|
|
129
128
|
end
|
|
130
129
|
|
|
131
130
|
def define_attribute_methods(attr, register = nil)
|
|
@@ -147,7 +146,11 @@ module Lutaml
|
|
|
147
146
|
# too -- XMI's extension loader declares attributes after
|
|
148
147
|
# first parse.
|
|
149
148
|
if name.to_s.match?(PLAIN_NAME)
|
|
150
|
-
|
|
149
|
+
# class_eval on self: a bare `model` receiver resolves
|
|
150
|
+
# through the host class's own namespace (omml's Base
|
|
151
|
+
# answers a Store model) and defined the reader on the
|
|
152
|
+
# wrong object (#933).
|
|
153
|
+
class_eval(<<~RUBY, __FILE__, __LINE__ + 1) # rubocop:disable Style/DocumentDynamicEvalDefinition
|
|
151
154
|
def __lutaml_raw_#{name}
|
|
152
155
|
@#{name}
|
|
153
156
|
end
|
|
@@ -345,8 +345,20 @@ module Lutaml
|
|
|
345
345
|
# @param register [Symbol, nil] The register context
|
|
346
346
|
# @return [Object] The allocated instance
|
|
347
347
|
def allocate_for_deserialization(register = nil)
|
|
348
|
-
instance = allocate
|
|
349
348
|
register_id = extract_register_id(register)
|
|
349
|
+
# Deferred imports (symbol-form import_model_attributes) must
|
|
350
|
+
# resolve before state defaults compile: the compiled seeding
|
|
351
|
+
# and the raw readers cover the post-import attribute set, and
|
|
352
|
+
# the transform's records already carry the mapping's rules
|
|
353
|
+
# for imported names — CTR's <m:br> parsed before resolution
|
|
354
|
+
# had no reader (#933). The constructor path ensures the same
|
|
355
|
+
# way (initialize_attributes). Guarded: deep object graphs
|
|
356
|
+
# (XSD schema trees) allocate recursively, and the ensure
|
|
357
|
+
# chain's frames per level pushed that recursion past the
|
|
358
|
+
# stack limit — models without pending deferred imports skip
|
|
359
|
+
# it entirely.
|
|
360
|
+
ensure_imports!(register_id) if finalized? && deferred_imports_pending?
|
|
361
|
+
instance = allocate
|
|
350
362
|
instance.finalize_deserialization(register_id)
|
|
351
363
|
instance
|
|
352
364
|
end
|
|
@@ -60,6 +60,10 @@ module Lutaml
|
|
|
60
60
|
model.attributes.each_value { |attr| define_attribute_methods(attr) }
|
|
61
61
|
@attributes.merge!(Utils.deep_dup(model.attributes))
|
|
62
62
|
@choice_attributes.concat(deep_duplicate_choice_attributes(model))
|
|
63
|
+
# Imported attributes grow the state-defaults surface (the
|
|
64
|
+
# raw readers live there) — a first use may already have
|
|
65
|
+
# compiled it for the pre-import attribute set (#933).
|
|
66
|
+
invalidate_state_defaults!
|
|
63
67
|
# Ensure @models_imported is a hash; migrate from legacy nil/false state
|
|
64
68
|
@models_imported ||= {}
|
|
65
69
|
@models_imported[reg] = true
|
|
@@ -122,6 +126,9 @@ module Lutaml
|
|
|
122
126
|
deep_duplicate_choice_attributes(model, register_id),
|
|
123
127
|
)
|
|
124
128
|
@merged_attributes_cache = nil
|
|
129
|
+
# See import_model_attributes: late-registered attributes
|
|
130
|
+
# must recompile the register's state defaults (#933).
|
|
131
|
+
invalidate_state_defaults!
|
|
125
132
|
end
|
|
126
133
|
private :register_only_import_model_attributes
|
|
127
134
|
|
|
@@ -153,6 +160,15 @@ module Lutaml
|
|
|
153
160
|
# Ensure all model imports are resolved for a specific register
|
|
154
161
|
#
|
|
155
162
|
# @param register_id [Symbol, nil] The register context
|
|
163
|
+
# Whether deferred (symbol-form) imports are still pending —
|
|
164
|
+
# gates the allocation-time ensure chain (#933).
|
|
165
|
+
def deferred_imports_pending?
|
|
166
|
+
importable_models.any? { |_method, models| models.any? } ||
|
|
167
|
+
importable_choices.any? do |_choice, imports|
|
|
168
|
+
imports.any? { |_method, models| models.any? }
|
|
169
|
+
end || restrict_attributes.any?
|
|
170
|
+
end
|
|
171
|
+
|
|
156
172
|
def ensure_model_imports!(register_id = nil)
|
|
157
173
|
register_id ||= Lutaml::Model::Config.default_register
|
|
158
174
|
@models_imported = {} if @models_imported.nil? || @models_imported == false
|
|
@@ -197,6 +213,7 @@ module Lutaml
|
|
|
197
213
|
@choices_imported[register_id] = true
|
|
198
214
|
all_resolved = true
|
|
199
215
|
|
|
216
|
+
resolved_any = false
|
|
200
217
|
importable_choices.each do |choice, choice_imports|
|
|
201
218
|
choice_imports.each do |method, models|
|
|
202
219
|
models.uniq.each do |model|
|
|
@@ -216,11 +233,19 @@ module Lutaml
|
|
|
216
233
|
end
|
|
217
234
|
|
|
218
235
|
choice.public_send(method, model_class, register_id)
|
|
236
|
+
resolved_any = true
|
|
219
237
|
end
|
|
220
238
|
end
|
|
221
239
|
end
|
|
222
240
|
|
|
223
241
|
@choices_imported[register_id] = true if all_resolved
|
|
242
|
+
# Choice imports can add attributes without passing through
|
|
243
|
+
# define_attribute_methods — recompile state defaults so the
|
|
244
|
+
# seeding and raw readers cover them (#933). Only when an
|
|
245
|
+
# import actually resolved: ensure_choice_imports! runs on
|
|
246
|
+
# every ensure_imports! (allocation-time), and a recompile
|
|
247
|
+
# per allocation would churn.
|
|
248
|
+
invalidate_state_defaults! if resolved_any
|
|
224
249
|
end
|
|
225
250
|
|
|
226
251
|
# Ensure all restrict attributes are applied
|
data/lib/lutaml/model/version.rb
CHANGED
|
@@ -99,6 +99,14 @@ module Lutaml
|
|
|
99
99
|
attr_rows = [] # [[rule, attr]] hydration metadata
|
|
100
100
|
plan_attrs = [] # [{name:, kind:}] rows for the engine plan
|
|
101
101
|
compiled = [] # [rule, attr, kind, spelling, delegate_target]
|
|
102
|
+
ns_attr_names = [] # exact-URI attribute rows (ambiguity guard)
|
|
103
|
+
# Local-name claim tallies for attribute rows: a name claimed
|
|
104
|
+
# by both an exact-URI row and a plain (any-namespace) row
|
|
105
|
+
# cannot be dispatched by the walk — the ns form :none
|
|
106
|
+
# matches any qualification, so the plain row captures the
|
|
107
|
+
# qualified spelling too (#758). The interpretive matcher
|
|
108
|
+
# owns shared names.
|
|
109
|
+
attr_local_claims = Hash.new { |h, k| h[k] = { ns: 0, plain: 0 } }
|
|
102
110
|
cdata = false
|
|
103
111
|
mixed_content = false
|
|
104
112
|
needs_nodes = false
|
|
@@ -154,15 +162,10 @@ module Lutaml
|
|
|
154
162
|
return opt_out!(:fragment_with_ns) if fragment_needed && model_ns
|
|
155
163
|
|
|
156
164
|
if rule.attribute?
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
# until plan rows carry namespace identity.
|
|
162
|
-
return opt_out!(:attr_type_ns) if attr.type_namespace_class(register)
|
|
163
|
-
|
|
164
|
-
attr_rows << [rule, attr]
|
|
165
|
-
plan_attrs << { name: rule.name.to_s }
|
|
165
|
+
opt = compile_attribute_row(rule, attr, register,
|
|
166
|
+
attr_rows, plan_attrs,
|
|
167
|
+
ns_attr_names, attr_local_claims)
|
|
168
|
+
return opt_out!(opt) if opt
|
|
166
169
|
elsif rule.content_mapping?
|
|
167
170
|
return opt_out!(:multi_content) if content_rows(rows) >= 1
|
|
168
171
|
|
|
@@ -247,12 +250,23 @@ module Lutaml
|
|
|
247
250
|
# namespaced models serialize through the interpretive
|
|
248
251
|
# writer (lutaml-model#847: standalone to_xml under leptris
|
|
249
252
|
# dropped the element xmlns entirely).
|
|
250
|
-
|
|
253
|
+
# Exact-URI attribute rows include their namespaced spelling
|
|
254
|
+
# here: the plan serializer writes attributes by local name
|
|
255
|
+
# (no prefixed wire spelling, no declaration emission —
|
|
256
|
+
# leptris#1551), so these models serialize interpretively.
|
|
257
|
+
namespaced = !model_ns.nil? || rows.any? { |r| r[:ns] } ||
|
|
258
|
+
plan_attrs.any? { |a| a[:ns] }
|
|
251
259
|
|
|
252
260
|
flags = []
|
|
253
261
|
flags << :cdata if cdata
|
|
254
262
|
flags << :mixed_content if mixed_content
|
|
255
|
-
|
|
263
|
+
# Unconditional: a namespace-less model's children match by
|
|
264
|
+
# local name even when the document declares a default or
|
|
265
|
+
# prefixed namespace (#932) — the interpretive matcher's
|
|
266
|
+
# unprefixed-any-URI behavior; elements (unlike attributes)
|
|
267
|
+
# inherit the default xmlns, so the strict no-URI default
|
|
268
|
+
# dropped every child of a namespaced document.
|
|
269
|
+
flags << :ns_lenient
|
|
256
270
|
tree = { name: mapping.root_element.to_s,
|
|
257
271
|
attributes: plan_attrs, children: rows }
|
|
258
272
|
tree[:ns] = model_ns if model_ns
|
|
@@ -267,12 +281,69 @@ module Lutaml
|
|
|
267
281
|
return nil
|
|
268
282
|
end
|
|
269
283
|
|
|
284
|
+
return opt_out!(:attr_ns_claim_conflict) if attr_local_claims
|
|
285
|
+
.any? { |_, c| c[:ns].positive? && c[:plain].positive? }
|
|
286
|
+
|
|
287
|
+
# Exact-URI attribute rows rely on the sole-claimant lenient
|
|
288
|
+
# recovery for out-of-namespace spellings (the interpretive
|
|
289
|
+
# exact-first-then-any-qualification precedence), and that
|
|
290
|
+
# recovery reads the source node — models carrying such rows
|
|
291
|
+
# must receive one through the buckets chain.
|
|
292
|
+
needs_nodes = true unless ns_attr_names.empty?
|
|
293
|
+
|
|
294
|
+
# A model combining exact-URI attribute rows with nested
|
|
295
|
+
# child rows stays interpretive: the engine's walk drops
|
|
296
|
+
# nested-children attribute capture under such plans
|
|
297
|
+
# (FontTable's w:name under an mc:Ignorable ns row —
|
|
298
|
+
# observed 1.9.311.3; filed upstream). Leaf models keep the
|
|
299
|
+
# lift — that is the measured win (uniword's deep property
|
|
300
|
+
# models are leaves).
|
|
301
|
+
return opt_out!(:attr_type_ns_nested) if plan_attrs.any? { |a| a[:ns] } &&
|
|
302
|
+
rows.any?
|
|
303
|
+
|
|
270
304
|
{ descriptor: descriptor, tree: tree, rows: compiled,
|
|
271
305
|
attr_rows: attr_rows, mapping: mapping,
|
|
272
306
|
row_tags: row_tags, namespaced: namespaced,
|
|
273
307
|
ordered: mapping.ordered? || mapping.mixed_content?,
|
|
274
308
|
needs_nodes: needs_nodes,
|
|
275
|
-
collection_defaults: collection_defaults
|
|
309
|
+
collection_defaults: collection_defaults,
|
|
310
|
+
ns_attr_names: ns_attr_names.empty? ? nil : ns_attr_names }
|
|
311
|
+
end
|
|
312
|
+
|
|
313
|
+
# Attribute rows: identity is (URI, local) (lutaml-model#744).
|
|
314
|
+
# A type-level namespace compiles into an exact-URI AttrPlan ns
|
|
315
|
+
# row (libleptris 1.9.289): the walk captures the (URI, local)
|
|
316
|
+
# match and the sole-claimant lenient recovery in PlanHydrator
|
|
317
|
+
# keeps the interpretive exact-first-then-any-qualification
|
|
318
|
+
# precedence for out-of-namespace spellings. Returns the
|
|
319
|
+
# opt-out clause when the row must stay interpretive, nil
|
|
320
|
+
# otherwise. Types declaring alias URI families stay
|
|
321
|
+
# interpretive (a plan row holds a single exact URI and
|
|
322
|
+
# non-sole-claimant alias families have no plan-side
|
|
323
|
+
# resolution order); :blank / :inherit arrive as Symbols —
|
|
324
|
+
# no URI to express in a row.
|
|
325
|
+
def compile_attribute_row(rule, attr, register, attr_rows,
|
|
326
|
+
plan_attrs, ns_attr_names,
|
|
327
|
+
attr_local_claims)
|
|
328
|
+
return :non_scalar unless scalar_type?(attr, register)
|
|
329
|
+
|
|
330
|
+
attr_rows << [rule, attr]
|
|
331
|
+
type_ns = attr.type_namespace_class(register)
|
|
332
|
+
claim = attr_local_claims[rule.name.to_s]
|
|
333
|
+
if type_ns.nil?
|
|
334
|
+
claim[:plain] += 1
|
|
335
|
+
plan_attrs << { name: rule.name.to_s }
|
|
336
|
+
return nil
|
|
337
|
+
end
|
|
338
|
+
return :attr_type_ns_symbol if type_ns.is_a?(Symbol)
|
|
339
|
+
return :attr_type_ns_aliases unless Leptris.attr_ns_rows_compatible? &&
|
|
340
|
+
type_ns.all_uris.size == 1
|
|
341
|
+
|
|
342
|
+
claim[:ns] += 1
|
|
343
|
+
plan_attrs << { name: rule.name.to_s,
|
|
344
|
+
ns: { exact: type_ns.uri.to_s } }
|
|
345
|
+
ns_attr_names << rule.name.to_s
|
|
346
|
+
nil
|
|
276
347
|
end
|
|
277
348
|
|
|
278
349
|
def compilable_mapping?(mapping)
|
|
@@ -72,6 +72,7 @@ module Lutaml
|
|
|
72
72
|
def attributes_kwargs(plan, value, node = nil)
|
|
73
73
|
kwargs = {}
|
|
74
74
|
sole_claimants = sole_claimant_names(plan)
|
|
75
|
+
ns_attr_names = plan[:ns_attr_names]
|
|
75
76
|
plan_attr_rows(plan).each do |rule, attr, name|
|
|
76
77
|
v = value.attribute(name)
|
|
77
78
|
if v.nil? && node && sole_claimants.include?(name)
|
|
@@ -83,6 +84,14 @@ module Lutaml
|
|
|
83
84
|
end
|
|
84
85
|
next if v.nil?
|
|
85
86
|
|
|
87
|
+
# (URI, local) rows mirror the interpretive matcher's
|
|
88
|
+
# ambiguity refusal: a second spelling of the same local
|
|
89
|
+
# name binds nothing (lutaml-model#744).
|
|
90
|
+
if ns_attr_names&.include?(name) &&
|
|
91
|
+
ambiguous_local_spellings?(node, name)
|
|
92
|
+
next
|
|
93
|
+
end
|
|
94
|
+
|
|
86
95
|
v = v.split(rule.delimiter) if rule.delimiter
|
|
87
96
|
if rule.as_list && rule.as_list[:import]
|
|
88
97
|
v = rule.as_list[:import].call(v)
|
|
@@ -92,6 +101,53 @@ module Lutaml
|
|
|
92
101
|
kwargs
|
|
93
102
|
end
|
|
94
103
|
|
|
104
|
+
# True when the node carries more than one spelling of the
|
|
105
|
+
# local name (any qualification). Only consulted for exact-URI
|
|
106
|
+
# attribute rows.
|
|
107
|
+
def ambiguous_local_spellings?(node, local_name)
|
|
108
|
+
return false unless node.respond_to?(:attributes)
|
|
109
|
+
|
|
110
|
+
seen = false
|
|
111
|
+
node.attributes.each_value do |attr|
|
|
112
|
+
name = attr.name.to_s
|
|
113
|
+
name = name.split(":").last if name.include?(":")
|
|
114
|
+
next unless name == local_name
|
|
115
|
+
|
|
116
|
+
return true if seen
|
|
117
|
+
|
|
118
|
+
seen = true
|
|
119
|
+
end
|
|
120
|
+
false
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
# A captured-but-empty child marshals as a nil string value.
|
|
124
|
+
# The interpretive pipeline classifies the raw "" through the
|
|
125
|
+
# rule's value map (:empty key) — the :empty directive (the
|
|
126
|
+
# plain-rule default) keeps it as "", :nil binds nil, and any
|
|
127
|
+
# other directive means uninitialized, i.e. no binding.
|
|
128
|
+
def mapped_empty_value(rule)
|
|
129
|
+
case rule.value_map(:from)[:empty]
|
|
130
|
+
when :nil then nil
|
|
131
|
+
when :empty then ""
|
|
132
|
+
else :skip
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def mapped_capture_values(values, rule)
|
|
137
|
+
return values unless values.include?(nil)
|
|
138
|
+
|
|
139
|
+
mapped = mapped_empty_value(rule)
|
|
140
|
+
out = []
|
|
141
|
+
values.each do |item|
|
|
142
|
+
if item.nil?
|
|
143
|
+
out << mapped unless mapped == :skip
|
|
144
|
+
else
|
|
145
|
+
out << item
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
out
|
|
149
|
+
end
|
|
150
|
+
|
|
95
151
|
# Wire-name strings are plan-frozen; materializing them per
|
|
96
152
|
# hydration allocated two or three strings per attribute row
|
|
97
153
|
# per instance.
|
|
@@ -128,6 +184,10 @@ module Lutaml
|
|
|
128
184
|
# Interpretive parity: several captures into a
|
|
129
185
|
# non-collection attribute arrive as the full array.
|
|
130
186
|
v = vals.map(&:string_value)
|
|
187
|
+
if v.include?(nil)
|
|
188
|
+
v = v.map { |item| item.nil? ? mapped_empty_value(rule) : item }
|
|
189
|
+
v.reject! { |item| item == :skip }
|
|
190
|
+
end
|
|
131
191
|
else
|
|
132
192
|
first = if tag
|
|
133
193
|
tagged[tag]&.first
|
|
@@ -135,16 +195,26 @@ module Lutaml
|
|
|
135
195
|
grouped.dig(rule.name.to_s, 0)
|
|
136
196
|
end
|
|
137
197
|
v = first&.string_value
|
|
198
|
+
v = mapped_empty_value(rule) if v.nil? && first
|
|
138
199
|
end
|
|
139
|
-
unless v.nil?
|
|
200
|
+
unless v.nil? || v == :skip
|
|
140
201
|
v = rule.transform_value(attr, v, :from, :xml) if rule.transform.is_a?(Class)
|
|
141
202
|
assign(kwargs, delegates, delegate, rule, attr, v)
|
|
142
203
|
end
|
|
143
204
|
when :raw, :custom_method, :polymorphic, :content_deferred
|
|
144
205
|
# interpreted post-instance (interpret_deferred)
|
|
145
206
|
when :content
|
|
146
|
-
|
|
147
|
-
|
|
207
|
+
runs = content_runs(value)
|
|
208
|
+
if runs.empty? && !attr.collection?
|
|
209
|
+
# An empty element's content binds through the rule's
|
|
210
|
+
# value map for scalar content attributes: "" for the
|
|
211
|
+
# plain :empty directive, nil for :nil, skipped for
|
|
212
|
+
# anything else (uninitialized).
|
|
213
|
+
bound = mapped_empty_value(rule)
|
|
214
|
+
assign(kwargs, delegates, delegate, rule, attr, bound) unless bound == :skip
|
|
215
|
+
else
|
|
216
|
+
assign(kwargs, delegates, delegate, rule, attr, runs)
|
|
217
|
+
end
|
|
148
218
|
when :ordered_deferred
|
|
149
219
|
# With a source node the child hydrates natively: one
|
|
150
220
|
# walk against its own node + element_order from the
|
|
@@ -165,7 +235,10 @@ module Lutaml
|
|
|
165
235
|
end
|
|
166
236
|
end
|
|
167
237
|
when :collection_cb
|
|
168
|
-
values = grouped[rule.name.to_s].to_a.map
|
|
238
|
+
values = grouped[rule.name.to_s].to_a.map do |item|
|
|
239
|
+
item&.string_value
|
|
240
|
+
end
|
|
241
|
+
values = mapped_capture_values(values, rule)
|
|
169
242
|
if rule.transform.is_a?(Class)
|
|
170
243
|
values = values.map { |v| rule.transform_value(attr, v, :from, :xml) }
|
|
171
244
|
end
|
|
@@ -178,6 +251,7 @@ module Lutaml
|
|
|
178
251
|
else
|
|
179
252
|
native_collection(value, rule.name.to_s)
|
|
180
253
|
end
|
|
254
|
+
values = mapped_capture_values(values, rule) unless values.nil?
|
|
181
255
|
unless values.nil?
|
|
182
256
|
assign(kwargs, delegates, delegate, rule, attr, values)
|
|
183
257
|
end
|
|
@@ -232,12 +306,19 @@ module Lutaml
|
|
|
232
306
|
when :content_deferred
|
|
233
307
|
runs = content_runs(value)
|
|
234
308
|
if runs.empty?
|
|
235
|
-
# The interpretive pipeline
|
|
236
|
-
#
|
|
237
|
-
#
|
|
238
|
-
#
|
|
239
|
-
#
|
|
309
|
+
# The interpretive pipeline classifies the raw ""
|
|
310
|
+
# through the rule's value map: the :empty directive
|
|
311
|
+
# (the plain-rule default) binds "" — the attribute is
|
|
312
|
+
# still marked set, which is what the #856 mark is
|
|
313
|
+
# about; :nil binds nil; other directives leave the
|
|
314
|
+
# binding unset.
|
|
240
315
|
instance.value_set_for(attr.name)
|
|
316
|
+
case rule.value_map(:from)[:empty]
|
|
317
|
+
when :nil
|
|
318
|
+
instance.public_send(:"#{attr.name}=", nil)
|
|
319
|
+
when :empty
|
|
320
|
+
instance.public_send(:"#{attr.name}=", "")
|
|
321
|
+
end
|
|
241
322
|
next
|
|
242
323
|
end
|
|
243
324
|
|
data/lib/lutaml/xml/leptris.rb
CHANGED
|
@@ -16,6 +16,11 @@ module Lutaml
|
|
|
16
16
|
# existing opt-out protocol.
|
|
17
17
|
MIN_LEPTRIS_VERSION = "1.9.273.0"
|
|
18
18
|
|
|
19
|
+
# AttrPlan rows carrying an ns form (exact-URI attribute matching,
|
|
20
|
+
# leptris#1486) shipped in libleptris 1.9.289.0. Older gems keep
|
|
21
|
+
# type-namespaced attributes on the interpretive path.
|
|
22
|
+
MIN_LEPTRIS_ATTR_NS_VERSION = "1.9.289.0"
|
|
23
|
+
|
|
19
24
|
def self.plan_path_compatible?
|
|
20
25
|
return false unless defined?(Gem)
|
|
21
26
|
|
|
@@ -23,6 +28,13 @@ module Lutaml
|
|
|
23
28
|
spec && spec.version >= Gem::Version.new(MIN_LEPTRIS_VERSION)
|
|
24
29
|
end
|
|
25
30
|
|
|
31
|
+
def self.attr_ns_rows_compatible?
|
|
32
|
+
return false unless defined?(Gem)
|
|
33
|
+
|
|
34
|
+
spec = Gem.loaded_specs["leptris"]
|
|
35
|
+
spec && spec.version >= Gem::Version.new(MIN_LEPTRIS_ATTR_NS_VERSION)
|
|
36
|
+
end
|
|
37
|
+
|
|
26
38
|
autoload :PlanCompiler, "lutaml/xml/leptris/plan_compiler"
|
|
27
39
|
autoload :PlanHydrator, "lutaml/xml/leptris/plan_hydrator"
|
|
28
40
|
autoload :PlanOrder, "lutaml/xml/leptris/plan_order"
|
|
@@ -43,9 +43,9 @@ end
|
|
|
43
43
|
# unmatched-rule replay must never overwrite a value a writer assigned.
|
|
44
44
|
RSpec.describe "attributes assigned inside another attribute's writer" do
|
|
45
45
|
%i[Plain Defaulted].each do |klass_name|
|
|
46
|
-
klass = WriterAssigned.const_get(klass_name)
|
|
47
|
-
|
|
48
46
|
describe "with #{klass_name.to_s.downcase} text attribute" do
|
|
47
|
+
let(:klass) { WriterAssigned.const_get(klass_name) }
|
|
48
|
+
|
|
49
49
|
it "keeps the derived value through from_xml" do
|
|
50
50
|
instance = klass.from_xml("<probe><code>67.060</code></probe>")
|
|
51
51
|
|
|
@@ -64,10 +64,12 @@ RSpec.describe "XML plan fast path" do
|
|
|
64
64
|
expect(parsed).to be_a(node)
|
|
65
65
|
end
|
|
66
66
|
|
|
67
|
-
# lutaml-model#744:
|
|
68
|
-
# type-namespaced attribute
|
|
69
|
-
#
|
|
70
|
-
|
|
67
|
+
# lutaml-model#744/#758: a local name claimed by BOTH a
|
|
68
|
+
# type-namespaced attribute row and a plain row stays interpretive —
|
|
69
|
+
# the walk's ns form :none matches any qualification, so the plain
|
|
70
|
+
# row would capture the qualified spelling too. The interpretive
|
|
71
|
+
# matcher dispatches the spellings correctly.
|
|
72
|
+
it "opts shared-local-name attribute claims out to the interpretive path" do
|
|
71
73
|
xmi_ns = Class.new(Lutaml::Xml::Namespace) do
|
|
72
74
|
uri "http://www.omg.org/spec/XMI/20131001"
|
|
73
75
|
prefix_default "xmi"
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "spec_helper"
|
|
4
|
+
|
|
5
|
+
RSpec.describe "XML plan fast path: namespace-less models on namespaced documents" do
|
|
6
|
+
# lutaml-model#932: elements (unlike attributes) inherit the default
|
|
7
|
+
# xmlns, so the walk's strict no-URI default dropped every child of a
|
|
8
|
+
# namespaced document for models that declare no namespace. The
|
|
9
|
+
# ns_lenient flag restores the interpretive matcher's behavior:
|
|
10
|
+
# children match by local name under any document qualification.
|
|
11
|
+
|
|
12
|
+
let(:child_class) do
|
|
13
|
+
klass = Class.new(Lutaml::Model::Serializable) do
|
|
14
|
+
attribute :fileref, :string
|
|
15
|
+
|
|
16
|
+
xml do
|
|
17
|
+
element "imagedata"
|
|
18
|
+
map_attribute "fileref", to: :fileref
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
stub_const("NsLessDoc::Imagedata", klass)
|
|
22
|
+
klass
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def define_parent
|
|
26
|
+
child_class
|
|
27
|
+
Class.new(Lutaml::Model::Serializable) do
|
|
28
|
+
attribute :imagedata, NsLessDoc::Imagedata
|
|
29
|
+
attribute :content, :string, collection: true
|
|
30
|
+
|
|
31
|
+
xml do
|
|
32
|
+
element "imageobject"
|
|
33
|
+
map_element "imagedata", to: :imagedata
|
|
34
|
+
map_content to: :content
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
around do |example|
|
|
41
|
+
old = Lutaml::Model::Config.instance.xml_plan_fast_path
|
|
42
|
+
Lutaml::Model::Config.instance.xml_plan_fast_path = true
|
|
43
|
+
Lutaml::Model::Config.with_adapter(xml: :leptris) { example.run }
|
|
44
|
+
ensure
|
|
45
|
+
Lutaml::Model::Config.instance.xml_plan_fast_path = old
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
it "binds nested children under a default namespace" do
|
|
49
|
+
parent = define_parent
|
|
50
|
+
doc = '<imageobject xmlns="http://docbook.org/ns/docbook">' \
|
|
51
|
+
'<imagedata fileref="x.png"/></imageobject>'
|
|
52
|
+
|
|
53
|
+
expect(parent.from_xml(doc).imagedata.fileref).to eq("x.png")
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
it "binds nested children under a prefixed namespace" do
|
|
57
|
+
parent = define_parent
|
|
58
|
+
doc = '<db:imageobject xmlns:db="http://docbook.org/ns/docbook">' \
|
|
59
|
+
'<db:imagedata fileref="x.png"/></db:imageobject>'
|
|
60
|
+
|
|
61
|
+
expect(parent.from_xml(doc).imagedata.fileref).to eq("x.png")
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
it "keeps binding on namespace-less documents" do
|
|
65
|
+
parent = define_parent
|
|
66
|
+
doc = %(<imageobject><imagedata fileref="x.png"/></imageobject>)
|
|
67
|
+
|
|
68
|
+
expect(parent.from_xml(doc).imagedata.fileref).to eq("x.png")
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
it "binds nested children of mixed-content models under a default namespace" do
|
|
72
|
+
parent = define_parent
|
|
73
|
+
parent.xml do
|
|
74
|
+
element "imageobject"
|
|
75
|
+
mixed_content
|
|
76
|
+
map_content to: :content
|
|
77
|
+
map_element "imagedata", to: :imagedata
|
|
78
|
+
end
|
|
79
|
+
doc = '<imageobject xmlns="http://docbook.org/ns/docbook">' \
|
|
80
|
+
'text<imagedata fileref="x.png"/>tail</imageobject>'
|
|
81
|
+
|
|
82
|
+
parsed = parent.from_xml(doc)
|
|
83
|
+
expect(parsed.imagedata.fileref).to eq("x.png")
|
|
84
|
+
expect(parsed.content).to include("text")
|
|
85
|
+
end
|
|
86
|
+
end
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "spec_helper"
|
|
4
|
+
|
|
5
|
+
RSpec.describe "XML plan fast path: type-namespaced attribute rows" do
|
|
6
|
+
# Attribute identity is (URI, local) (lutaml-model#744). A map_attribute
|
|
7
|
+
# whose TYPE carries a namespace class used to opt the whole model out
|
|
8
|
+
# of the plan path; it now compiles into an exact-URI AttrPlan ns row
|
|
9
|
+
# (libleptris 1.9.289) with the sole-claimant lenient recovery keeping
|
|
10
|
+
# the interpretive exact-first-then-any-qualification precedence.
|
|
11
|
+
|
|
12
|
+
let(:wml_ns) do
|
|
13
|
+
Class.new(Lutaml::Xml::Namespace) do
|
|
14
|
+
uri "http://example.com/type-ns/wml"
|
|
15
|
+
prefix_default "w"
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
let(:val_type) do
|
|
20
|
+
stub_const("PlanTypeNsAttr::WmlNs", wml_ns)
|
|
21
|
+
type = Class.new(Lutaml::Model::Type::String) do
|
|
22
|
+
include Lutaml::Xml::Type::Configurable
|
|
23
|
+
|
|
24
|
+
xml do
|
|
25
|
+
namespace PlanTypeNsAttr::WmlNs
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
stub_const("PlanTypeNsAttr::ValType", type)
|
|
29
|
+
type
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
let(:klass) do
|
|
33
|
+
val_type
|
|
34
|
+
model = Class.new(Lutaml::Model::Serializable) do
|
|
35
|
+
attribute :val, PlanTypeNsAttr::ValType
|
|
36
|
+
|
|
37
|
+
xml do
|
|
38
|
+
element "keepNext"
|
|
39
|
+
namespace PlanTypeNsAttr::WmlNs
|
|
40
|
+
map_attribute "val", to: :val
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
stub_const("PlanTypeNsAttr::KeepNext", model)
|
|
44
|
+
model
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
around do |example|
|
|
48
|
+
old = Lutaml::Model::Config.instance.xml_plan_fast_path
|
|
49
|
+
Lutaml::Model::Config.instance.xml_plan_fast_path = true
|
|
50
|
+
Lutaml::Model::Config.with_adapter(xml: :leptris) { example.run }
|
|
51
|
+
ensure
|
|
52
|
+
Lutaml::Model::Config.instance.xml_plan_fast_path = old
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
it "compiles a model whose attribute type carries a namespace" do
|
|
56
|
+
plan = Lutaml::Xml::Leptris::PlanCompiler.compile(
|
|
57
|
+
klass, Lutaml::Model::Config.default_register
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
expect(plan).not_to be_nil
|
|
61
|
+
ns_row = plan[:tree][:attributes].find { |a| a[:name] == "val" }
|
|
62
|
+
expect(ns_row[:ns]).to eq(exact: "http://example.com/type-ns/wml")
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
it "binds the attribute from the type's namespace" do
|
|
66
|
+
doc = %(<w:keepNext xmlns:w="http://example.com/type-ns/wml" w:val="yes"/>)
|
|
67
|
+
|
|
68
|
+
expect(klass.from_xml(doc).val).to eq("yes")
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# The interpretive matcher refuses an ambiguous local name outright:
|
|
72
|
+
# a foreign spelling alongside the canonical one binds nothing. The
|
|
73
|
+
# exact-URI row mirrors that refusal (lutaml-model#744).
|
|
74
|
+
it "refuses to bind when a foreign spelling accompanies the canonical one" do
|
|
75
|
+
doc = '<w:keepNext xmlns:w="http://example.com/type-ns/wml" ' \
|
|
76
|
+
'xmlns:f="http://example.com/foreign" val="foreign" w:val="canonical"/>'
|
|
77
|
+
|
|
78
|
+
expect(klass.from_xml(doc).val).to be_nil
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# Interpretive parity: the plan path resolves exactly as the
|
|
82
|
+
# interpretive matcher (nokogiri) does on every spelling shape.
|
|
83
|
+
it "matches the interpretive path's resolution on all spelling shapes" do
|
|
84
|
+
canonical = %(<w:keepNext xmlns:w="http://example.com/type-ns/wml" w:val="yes"/>)
|
|
85
|
+
ambiguous = '<w:keepNext xmlns:w="http://example.com/type-ns/wml" ' \
|
|
86
|
+
'xmlns:f="http://example.com/foreign" val="foreign" w:val="canonical"/>'
|
|
87
|
+
foreign_only = '<w:keepNext xmlns:f="http://example.com/foreign" ' \
|
|
88
|
+
'xmlns:w="http://example.com/type-ns/wml" val="foreign"/>'
|
|
89
|
+
|
|
90
|
+
[canonical, ambiguous, foreign_only].each do |doc|
|
|
91
|
+
interpretive = Lutaml::Model::Config.with_adapter(xml: :nokogiri) do
|
|
92
|
+
klass.from_xml(doc).val
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
expect(klass.from_xml(doc).val).to eq(interpretive)
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# A type declaring alias URI families stays interpretive: the plan row
|
|
100
|
+
# holds one exact URI and non-sole-claimant alias families have no
|
|
101
|
+
# plan-side resolution order.
|
|
102
|
+
it "keeps alias-declaring types on the interpretive path" do
|
|
103
|
+
aliased_ns = Class.new(Lutaml::Xml::Namespace) do
|
|
104
|
+
uri "http://example.com/type-ns/canonical"
|
|
105
|
+
uri_aliases "http://example.com/type-ns/alias"
|
|
106
|
+
end
|
|
107
|
+
stub_const("PlanTypeNsAttr::AliasedNs", aliased_ns)
|
|
108
|
+
type = Class.new(Lutaml::Model::Type::String) do
|
|
109
|
+
include Lutaml::Xml::Type::Configurable
|
|
110
|
+
|
|
111
|
+
xml do
|
|
112
|
+
namespace PlanTypeNsAttr::AliasedNs
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
stub_const("PlanTypeNsAttr::AliasedValType", type)
|
|
116
|
+
model = Class.new(Lutaml::Model::Serializable) do
|
|
117
|
+
attribute :val, PlanTypeNsAttr::AliasedValType
|
|
118
|
+
|
|
119
|
+
xml do
|
|
120
|
+
element "keepNext"
|
|
121
|
+
map_attribute "val", to: :val
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
stub_const("PlanTypeNsAttr::AliasedKeepNext", model)
|
|
125
|
+
|
|
126
|
+
expect(Lutaml::Xml::Leptris::PlanCompiler.compile(
|
|
127
|
+
model, Lutaml::Model::Config.default_register
|
|
128
|
+
)).to be_nil
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
# Engines older than the AttrPlan ns form (libleptris 1.9.289) keep
|
|
132
|
+
# the opt-out — the existing version-gate protocol.
|
|
133
|
+
it "opts out when the engine predates attribute ns rows" do
|
|
134
|
+
allow(Lutaml::Xml::Leptris).to receive(:attr_ns_rows_compatible?)
|
|
135
|
+
.and_return(false)
|
|
136
|
+
|
|
137
|
+
expect(Lutaml::Xml::Leptris::PlanCompiler.compile(
|
|
138
|
+
klass, Lutaml::Model::Config.default_register
|
|
139
|
+
)).to be_nil
|
|
140
|
+
end
|
|
141
|
+
end
|
data/spec/person_spec.rb
CHANGED
|
@@ -115,12 +115,13 @@ RSpec.describe Person do
|
|
|
115
115
|
expect(model.to_yaml).to eq(attributes_yaml.to_yaml)
|
|
116
116
|
end
|
|
117
117
|
|
|
118
|
-
# yeptris
|
|
119
|
-
#
|
|
120
|
-
#
|
|
121
|
-
#
|
|
122
|
-
#
|
|
123
|
-
|
|
118
|
+
# yeptris 0.6.29.x regressed the time-only scalar anchor: "07:00:00"
|
|
119
|
+
# comes back as a fixed 2025-07-19 00:00 instead of today-07:00
|
|
120
|
+
# (psych answers exactly; verified on the psych path). Re-pended
|
|
121
|
+
# until the yeptris typed-materialize ships the fix — evidence on
|
|
122
|
+
# moxml#336. 873a67bd un-pended these on the premise that the
|
|
123
|
+
# 0.6.28.x behavior held; it did not survive the 0.6.29 cut.
|
|
124
|
+
xit "deserializes from YAML" do # rubocop:disable RSpec/PendingWithoutReason -- yeptris time-only misparse; engine fix pending.
|
|
124
125
|
yaml = attributes_yaml.to_yaml
|
|
125
126
|
person = described_class.from_yaml(yaml)
|
|
126
127
|
expect(person.first_name).to eq("John")
|
|
@@ -133,7 +134,7 @@ RSpec.describe Person do
|
|
|
133
134
|
end
|
|
134
135
|
|
|
135
136
|
# Same yeptris time-only scalar misparse as above.
|
|
136
|
-
xit "deserializes from YAML array" do
|
|
137
|
+
xit "deserializes from YAML array" do # rubocop:disable RSpec/PendingWithoutReason -- yeptris time-only misparse; engine fix pending.
|
|
137
138
|
yaml = [attributes_yaml.dup, attributes_yaml.dup].to_yaml
|
|
138
139
|
|
|
139
140
|
persons = described_class.from_yaml(yaml)
|
|
@@ -154,5 +155,4 @@ RSpec.describe Person do
|
|
|
154
155
|
expect(persons[1].wakeup_time).to eq(Time.parse("07:00:00"))
|
|
155
156
|
expect(persons[1].active).to be true
|
|
156
157
|
end
|
|
157
|
-
# rubocop:enable RSpec/PendingWithoutReason
|
|
158
158
|
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.
|
|
4
|
+
version: 0.8.95
|
|
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-07 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: base64
|
|
@@ -184,6 +184,7 @@ files:
|
|
|
184
184
|
- bench/bench_alloc_audit.rb
|
|
185
185
|
- bench/bench_common.rb
|
|
186
186
|
- bench/bench_compare.rb
|
|
187
|
+
- bench/bench_merge_attempts.rb
|
|
187
188
|
- bench/bench_mml.rb
|
|
188
189
|
- bench/bench_niso.rb
|
|
189
190
|
- bench/bench_sts.rb
|
|
@@ -1985,7 +1986,9 @@ files:
|
|
|
1985
1986
|
- spec/lutaml/xml/pipeline_integration_spec.rb
|
|
1986
1987
|
- spec/lutaml/xml/plain_model_accessors_spec.rb
|
|
1987
1988
|
- spec/lutaml/xml/plan_fast_path_spec.rb
|
|
1989
|
+
- spec/lutaml/xml/plan_ns_less_document_spec.rb
|
|
1988
1990
|
- spec/lutaml/xml/plan_partition_spec.rb
|
|
1991
|
+
- spec/lutaml/xml/plan_type_ns_attribute_spec.rb
|
|
1989
1992
|
- spec/lutaml/xml/plan_walk_spec.rb
|
|
1990
1993
|
- spec/lutaml/xml/prefix_control_spec.rb
|
|
1991
1994
|
- spec/lutaml/xml/register_spec.rb
|