lutaml-model 0.8.42 → 0.8.43
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/docs/_guides/advanced-mapping.adoc +32 -0
- data/lib/lutaml/key_value/mapping.rb +3 -1
- data/lib/lutaml/key_value/mapping_rule.rb +4 -1
- data/lib/lutaml/key_value/transform.rb +2 -0
- data/lib/lutaml/key_value/transformation/rule_compiler.rb +3 -0
- data/lib/lutaml/model/collection_handler.rb +11 -1
- data/lib/lutaml/model/mapping/mapping_rule.rb +23 -1
- data/lib/lutaml/model/serialize/initialization.rb +7 -2
- data/lib/lutaml/model/serialize.rb +12 -3
- data/lib/lutaml/model/version.rb +1 -1
- data/lib/lutaml/xml/mapping.rb +20 -1
- data/lib/lutaml/xml/mapping_rule.rb +13 -1
- data/lib/lutaml/xml/model_transform.rb +56 -5
- data/lib/lutaml/xml/parse_session.rb +11 -2
- data/lib/lutaml/xml/transformation/element_builder.rb +25 -6
- data/lib/lutaml/xml/transformation/rule_compiler.rb +2 -0
- data/spec/lutaml/key_value/transformation/rule_compiler_spec.rb +16 -6
- data/spec/lutaml/key_value/transformation_spec.rb +4 -1
- data/spec/lutaml/model/attributes_memo_clear_race_spec.rb +90 -0
- data/spec/lutaml/model/hydrate_only_mapping_spec.rb +47 -0
- data/spec/lutaml/xml/when_attribute_spec.rb +133 -0
- 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: fe2357c36c2a199a3788090dffd420874612482e21e7ac678bd5f1e6fdd575ce
|
|
4
|
+
data.tar.gz: a3d601af07558b2915dce9f4df383c00b905505c8fbcd192a89ad88b3e66ea49
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: c82bb3761127b4beb8f2d267a60d107d69eb509cd2fd094763eed5ab5d0814497252db4cd1311709072ab829b156aac46f839852bcc4f20f37e627aa7b5daea7
|
|
7
|
+
data.tar.gz: 32aa679d229c03ccccf0ee8f1359f9559309b92f5493a0207fd5a495e0f8ef1a0cc3255a0e4e30c2e7c5319e8910a8863286116d6a0e4bfe72af1a0b8be2c71f
|
|
@@ -562,3 +562,35 @@ end
|
|
|
562
562
|
====
|
|
563
563
|
|
|
564
564
|
|
|
565
|
+
|
|
566
|
+
=== Attribute-value dispatch on a shared element name
|
|
567
|
+
|
|
568
|
+
When several distinct attributes share one wire element, selected by a
|
|
569
|
+
sibling attribute's value, declare the discriminator with `when_attribute:`
|
|
570
|
+
|
|
571
|
+
[source,ruby]
|
|
572
|
+
----
|
|
573
|
+
class Requirement < Lutaml::Model::Serializable
|
|
574
|
+
attribute :guidance, Component, collection: true
|
|
575
|
+
attribute :purpose, Component, collection: true
|
|
576
|
+
|
|
577
|
+
xml do
|
|
578
|
+
element "requirement"
|
|
579
|
+
map_element "component", to: :guidance,
|
|
580
|
+
when_attribute: { "type" => "guidance" }
|
|
581
|
+
map_element "component", to: :purpose,
|
|
582
|
+
when_attribute: { "type" => "purpose" }
|
|
583
|
+
end
|
|
584
|
+
end
|
|
585
|
+
----
|
|
586
|
+
|
|
587
|
+
Each rule hydrates only from the `<component>` occurrences whose `type`
|
|
588
|
+
attribute carries the expected value, in document order; uncovered values
|
|
589
|
+
are ignored by all discriminator rules. On serialization the discriminator
|
|
590
|
+
pair is re-emitted on the element (unless the value's own mapping already
|
|
591
|
+
writes that attribute).
|
|
592
|
+
|
|
593
|
+
Co-occurrence semantics: several `when_attribute` rules may share the
|
|
594
|
+
element name; each target attribute fills independently, so declare each
|
|
595
|
+
as `collection: true` when the element repeats. Rules without
|
|
596
|
+
`when_attribute` keep claiming every occurrence of their name as before.
|
|
@@ -78,7 +78,8 @@ module Lutaml
|
|
|
78
78
|
polymorphic: {},
|
|
79
79
|
polymorphic_map: {},
|
|
80
80
|
transform: {},
|
|
81
|
-
value_map: {}
|
|
81
|
+
value_map: {},
|
|
82
|
+
serialize: true
|
|
82
83
|
)
|
|
83
84
|
mapping_name = name_for_mapping(root_mappings, name)
|
|
84
85
|
validate!(mapping_name, to, with, render_nil, render_empty)
|
|
@@ -100,6 +101,7 @@ module Lutaml
|
|
|
100
101
|
polymorphic_map: polymorphic_map,
|
|
101
102
|
transform: transform,
|
|
102
103
|
value_map: value_map,
|
|
104
|
+
serialize: serialize,
|
|
103
105
|
)
|
|
104
106
|
end
|
|
105
107
|
|
|
@@ -22,7 +22,8 @@ module Lutaml
|
|
|
22
22
|
polymorphic: {},
|
|
23
23
|
polymorphic_map: {},
|
|
24
24
|
transform: {},
|
|
25
|
-
value_map: {}
|
|
25
|
+
value_map: {},
|
|
26
|
+
serialize: true
|
|
26
27
|
)
|
|
27
28
|
super(
|
|
28
29
|
name,
|
|
@@ -41,6 +42,7 @@ module Lutaml
|
|
|
41
42
|
polymorphic_map: polymorphic_map,
|
|
42
43
|
transform: transform,
|
|
43
44
|
value_map: value_map,
|
|
45
|
+
serialize: serialize,
|
|
44
46
|
)
|
|
45
47
|
|
|
46
48
|
@child_mappings = child_mappings
|
|
@@ -65,6 +67,7 @@ module Lutaml
|
|
|
65
67
|
child_mappings: Lutaml::Model::Utils.deep_dup(child_mappings),
|
|
66
68
|
root_mappings: Lutaml::Model::Utils.deep_dup(root_mappings),
|
|
67
69
|
value_map: Lutaml::Model::Utils.deep_dup(@value_map),
|
|
70
|
+
serialize: serialize?,
|
|
68
71
|
)
|
|
69
72
|
end
|
|
70
73
|
|
|
@@ -230,6 +230,7 @@ transformation_factory:)
|
|
|
230
230
|
custom_methods: custom_methods,
|
|
231
231
|
delegate: delegate,
|
|
232
232
|
root_mappings: mapping_rule.root_mappings,
|
|
233
|
+
serialize: mapping_rule.serialize?,
|
|
233
234
|
)
|
|
234
235
|
end
|
|
235
236
|
|
|
@@ -239,6 +240,8 @@ transformation_factory:)
|
|
|
239
240
|
# @param options [Hash] Transformation options (may contain :only, :except)
|
|
240
241
|
# @return [Boolean] true if the rule should be applied
|
|
241
242
|
def valid_mapping?(rule, options)
|
|
243
|
+
return false if rule.option(:serialize) == false
|
|
244
|
+
|
|
242
245
|
only = options[:only]
|
|
243
246
|
except = options[:except]
|
|
244
247
|
name = rule.attribute_name
|
|
@@ -61,7 +61,17 @@ module Lutaml
|
|
|
61
61
|
# @param register [Symbol, nil] Register for custom-collection type resolution
|
|
62
62
|
# @return [Object] A new collection instance
|
|
63
63
|
def build_collection(*args, register: nil)
|
|
64
|
-
|
|
64
|
+
# args.flatten allocated a fresh Array even for the dominant
|
|
65
|
+
# calls: no args, or one already-flat Array from cast_items
|
|
66
|
+
# (71,949 flatten allocations per ISO-13849 parse).
|
|
67
|
+
items = if args.empty?
|
|
68
|
+
[]
|
|
69
|
+
elsif args.length == 1 && (first = args[0]).is_a?(::Array) &&
|
|
70
|
+
first.none?(::Array)
|
|
71
|
+
first
|
|
72
|
+
else
|
|
73
|
+
args.flatten
|
|
74
|
+
end
|
|
65
75
|
unless register && initialize_accepts_register?
|
|
66
76
|
return collection_class.new(items)
|
|
67
77
|
end
|
|
@@ -70,7 +70,9 @@ module Lutaml
|
|
|
70
70
|
polymorphic: {},
|
|
71
71
|
polymorphic_map: {},
|
|
72
72
|
transform: {},
|
|
73
|
-
value_map: {}
|
|
73
|
+
value_map: {},
|
|
74
|
+
serialize: true,
|
|
75
|
+
when_attribute: {}
|
|
74
76
|
)
|
|
75
77
|
@name = name
|
|
76
78
|
@to = to
|
|
@@ -92,6 +94,11 @@ module Lutaml
|
|
|
92
94
|
@polymorphic = polymorphic
|
|
93
95
|
@polymorphic_map = polymorphic_map
|
|
94
96
|
@transform = transform
|
|
97
|
+
@serialize_mapping = serialize
|
|
98
|
+
# Attribute-value discriminator (lutaml-model#88): select this
|
|
99
|
+
# rule's wire occurrences by a sibling attribute value, e.g.
|
|
100
|
+
# `when_attribute: { "type" => "guidance" }`.
|
|
101
|
+
@when_attribute = (when_attribute || {}).freeze
|
|
95
102
|
|
|
96
103
|
# Cache whether this rule needs the full deserialize chain.
|
|
97
104
|
# Over 95% of rules are "simple" (no custom method, no delegate).
|
|
@@ -174,6 +181,11 @@ module Lutaml
|
|
|
174
181
|
|
|
175
182
|
alias from name
|
|
176
183
|
alias render_default? render_default
|
|
184
|
+
|
|
185
|
+
# Whether this rule participates in serialization output. False
|
|
186
|
+
# marks a hydrate-only mapping: consumed by from_* but never
|
|
187
|
+
# rendered by to_*. (The value callback #serialize is separate.)
|
|
188
|
+
def serialize? = @serialize_mapping
|
|
177
189
|
alias attribute? attribute
|
|
178
190
|
|
|
179
191
|
def render?(value, instance = nil, options = {})
|
|
@@ -328,6 +340,16 @@ context = nil)
|
|
|
328
340
|
# each time (240k allocations on the ISO-13849 serialize profile).
|
|
329
341
|
# Rules may be frozen, so no per-instance memo: one to_s instead
|
|
330
342
|
# of two is the frozen-safe diet.
|
|
343
|
+
# Discriminator pairs this rule selects (and re-emits) on, empty
|
|
344
|
+
# for an ordinary rule.
|
|
345
|
+
def when_attribute
|
|
346
|
+
@when_attribute ||= {}.freeze
|
|
347
|
+
end
|
|
348
|
+
|
|
349
|
+
def when_attribute?
|
|
350
|
+
!when_attribute.empty?
|
|
351
|
+
end
|
|
352
|
+
|
|
331
353
|
def custom_method_only?
|
|
332
354
|
name = attribute_name.to_s
|
|
333
355
|
(name.start_with?("__") && name.end_with?("__")) ||
|
|
@@ -133,8 +133,13 @@ module Lutaml
|
|
|
133
133
|
return cached if cached
|
|
134
134
|
|
|
135
135
|
ensure_imports!(register) if finalized?
|
|
136
|
-
|
|
137
|
-
|
|
136
|
+
# ensure_imports! may re-entrantly run clear_cache (restrict /
|
|
137
|
+
# import resolution), which nils @merged_attributes_cache —
|
|
138
|
+
# compute the merge first and re-materialize the memo store
|
|
139
|
+
# at write time (#815: standalone to_xml on register-bound
|
|
140
|
+
# models crashed with nil[] here).
|
|
141
|
+
merged = @attributes.merge(@register_records[register_id][:attributes])
|
|
142
|
+
(@merged_attributes_cache ||= {})[register_id] = merged
|
|
138
143
|
else
|
|
139
144
|
ensure_imports!(register) if finalized?
|
|
140
145
|
@attributes
|
|
@@ -241,12 +241,21 @@ module Lutaml
|
|
|
241
241
|
end
|
|
242
242
|
|
|
243
243
|
def respond_to_missing?(method_name, include_private = false)
|
|
244
|
-
|
|
245
|
-
|
|
244
|
+
# Setter probes from non-compiled dispatch land here ~24k times per
|
|
245
|
+
# ISO-13849 parse; strip the suffix once instead of re-walking
|
|
246
|
+
# attribute_exist?'s to_s/chomp/to_sym chain.
|
|
247
|
+
name = method_name.to_s
|
|
248
|
+
if name.end_with?("=") &&
|
|
249
|
+
self.class.attributes(lutaml_register)
|
|
250
|
+
.key?(name.delete_suffix("=").to_sym)
|
|
251
|
+
return true
|
|
252
|
+
end
|
|
253
|
+
|
|
254
|
+
super
|
|
246
255
|
end
|
|
247
256
|
|
|
248
257
|
def attribute_exist?(name)
|
|
249
|
-
name = name.to_s.
|
|
258
|
+
name = name.to_s.delete_suffix("=").to_sym if name.end_with?("=")
|
|
250
259
|
|
|
251
260
|
self.class.attributes(lutaml_register).key?(name)
|
|
252
261
|
end
|
data/lib/lutaml/model/version.rb
CHANGED
data/lib/lutaml/xml/mapping.rb
CHANGED
|
@@ -527,6 +527,7 @@ module Lutaml
|
|
|
527
527
|
nil),
|
|
528
528
|
transform: {},
|
|
529
529
|
value_map: {},
|
|
530
|
+
when_attribute: {},
|
|
530
531
|
form: nil,
|
|
531
532
|
documentation: nil,
|
|
532
533
|
xsd_type: (xsd_type_provided = false
|
|
@@ -537,6 +538,8 @@ module Lutaml
|
|
|
537
538
|
name, to, with, render_nil, render_empty, type: TYPES[:element]
|
|
538
539
|
)
|
|
539
540
|
|
|
541
|
+
validate_when_attribute!(when_attribute) unless when_attribute.empty?
|
|
542
|
+
|
|
540
543
|
# Warn if prefix parameter is provided
|
|
541
544
|
if prefix_provided != false
|
|
542
545
|
warn "[DEPRECATED] The prefix parameter on map_element is deprecated. " \
|
|
@@ -584,6 +587,7 @@ module Lutaml
|
|
|
584
587
|
form: form,
|
|
585
588
|
documentation: documentation,
|
|
586
589
|
raw: raw,
|
|
590
|
+
when_attribute: when_attribute,
|
|
587
591
|
)
|
|
588
592
|
# Store rules with the same element name in an array to support
|
|
589
593
|
# multiple mapping rules for the same element name with different target types
|
|
@@ -607,6 +611,20 @@ module Lutaml
|
|
|
607
611
|
end
|
|
608
612
|
end
|
|
609
613
|
|
|
614
|
+
# lutaml-model#88: attribute-value discriminator. Keys are wire
|
|
615
|
+
# attribute names, values the expected string value.
|
|
616
|
+
def validate_when_attribute!(when_attribute)
|
|
617
|
+
when_attribute.each do |k, v|
|
|
618
|
+
next if (k.is_a?(::String) || k.is_a?(::Symbol)) &&
|
|
619
|
+
(v.is_a?(::String) || v.is_a?(::Symbol))
|
|
620
|
+
|
|
621
|
+
raise Lutaml::Model::IncorrectMappingArgumentsError,
|
|
622
|
+
"when_attribute expects string/symbol attribute names " \
|
|
623
|
+
"mapped to string/symbol values, got " \
|
|
624
|
+
"#{k.inspect} => #{v.inspect}"
|
|
625
|
+
end
|
|
626
|
+
end
|
|
627
|
+
|
|
610
628
|
def map_attribute(
|
|
611
629
|
name,
|
|
612
630
|
to: nil,
|
|
@@ -1150,7 +1168,8 @@ module Lutaml
|
|
|
1150
1168
|
grouped = {}
|
|
1151
1169
|
mappings(reg_key).each do |r|
|
|
1152
1170
|
next if r.attribute? || r.raw_mapping? || r.content_mapping? ||
|
|
1153
|
-
r.cdata || r.
|
|
1171
|
+
r.cdata || r.when_attribute? ||
|
|
1172
|
+
r.has_custom_method_for_deserialization? ||
|
|
1154
1173
|
(r.transform.is_a?(Hash) && !r.transform.empty?) ||
|
|
1155
1174
|
r.transform.is_a?(Class)
|
|
1156
1175
|
|
|
@@ -16,7 +16,7 @@ module Lutaml
|
|
|
16
16
|
# Writers for deep_dup (preserves exact object references)
|
|
17
17
|
attr_accessor :namespace, :prefix, :namespace_class
|
|
18
18
|
|
|
19
|
-
def initialize(
|
|
19
|
+
def initialize( # rubocop:disable Metrics/ParameterLists -- mirrors the DSL surface
|
|
20
20
|
name,
|
|
21
21
|
to:,
|
|
22
22
|
render_nil: false,
|
|
@@ -37,6 +37,7 @@ module Lutaml
|
|
|
37
37
|
polymorphic_map: {},
|
|
38
38
|
transform: {},
|
|
39
39
|
value_map: {},
|
|
40
|
+
when_attribute: {},
|
|
40
41
|
as_list: nil,
|
|
41
42
|
delimiter: nil,
|
|
42
43
|
form: nil,
|
|
@@ -59,6 +60,7 @@ module Lutaml
|
|
|
59
60
|
polymorphic_map: polymorphic_map,
|
|
60
61
|
transform: transform,
|
|
61
62
|
value_map: value_map,
|
|
63
|
+
when_attribute: when_attribute,
|
|
62
64
|
)
|
|
63
65
|
|
|
64
66
|
# Store original namespace parameter to preserve :inherit symbol
|
|
@@ -225,6 +227,15 @@ module Lutaml
|
|
|
225
227
|
end
|
|
226
228
|
end
|
|
227
229
|
|
|
230
|
+
# Whether a parsed element satisfies this rule's when_attribute
|
|
231
|
+
# discriminator: every expected attribute value matches.
|
|
232
|
+
def matches_when_attribute?(element)
|
|
233
|
+
when_attribute.all? do |name, expected|
|
|
234
|
+
actual = element.find_attribute_value(name.to_s)
|
|
235
|
+
!actual.nil? && actual.to_s == expected.to_s
|
|
236
|
+
end
|
|
237
|
+
end
|
|
238
|
+
|
|
228
239
|
def deep_dup
|
|
229
240
|
# Preserve @namespace_param exactly as it was (string, Class, :inherit, or nil)
|
|
230
241
|
# This ensures the duplicate has the same internal state as the original
|
|
@@ -261,6 +272,7 @@ module Lutaml
|
|
|
261
272
|
form: @form,
|
|
262
273
|
documentation: @documentation,
|
|
263
274
|
raw: @raw,
|
|
275
|
+
when_attribute: when_attribute,
|
|
264
276
|
).tap do |dup_rule|
|
|
265
277
|
# Manually preserve the exact @namespace_class object to avoid
|
|
266
278
|
# recreating anonymous classes (which would have different object_ids)
|
|
@@ -40,6 +40,10 @@ module Lutaml
|
|
|
40
40
|
# Performance: Frozen empty hash to reduce allocations
|
|
41
41
|
EMPTY_HASH = {}.freeze
|
|
42
42
|
|
|
43
|
+
# ":".getbyte(0) — getbyte comparisons avoid the per-candidate
|
|
44
|
+
# string slice in namespace-prefix checks on the lenient paths.
|
|
45
|
+
COLON_BYTE = 58
|
|
46
|
+
|
|
43
47
|
def data_to_model(data, _format, options = {})
|
|
44
48
|
# Use child's own default register if it has one
|
|
45
49
|
# This ensures versioned schemas (e.g., MML v2 with lutaml_default_register = :mml_v2)
|
|
@@ -600,6 +604,21 @@ _effective_register)
|
|
|
600
604
|
def convert_rule_name_to_attribute_name(doc, rule_name, session: nil)
|
|
601
605
|
return nil unless rule_name.include?(":")
|
|
602
606
|
|
|
607
|
+
# Pure function of (element attributes, spelling) — the same
|
|
608
|
+
# conversions repeat per rule application (~450k find/slice
|
|
609
|
+
# allocations per ISO-13849 parse before this memo).
|
|
610
|
+
conversions = session&.rule_name_resolution(doc.root)
|
|
611
|
+
if conversions
|
|
612
|
+
return conversions[rule_name] if conversions.key?(rule_name)
|
|
613
|
+
|
|
614
|
+
converted = convert_rule_name_uncached(doc, rule_name, session)
|
|
615
|
+
conversions[rule_name] = converted
|
|
616
|
+
else
|
|
617
|
+
convert_rule_name_uncached(doc, rule_name, session)
|
|
618
|
+
end
|
|
619
|
+
end
|
|
620
|
+
|
|
621
|
+
def convert_rule_name_uncached(doc, rule_name, session)
|
|
603
622
|
# URI-vs-prefix detection works on the whole rule name: the
|
|
604
623
|
# namespace part is a prefix of rule_name, so "://" anywhere in
|
|
605
624
|
# the name means a URI-form namespace, and a "urn:" namespace
|
|
@@ -620,10 +639,13 @@ _effective_register)
|
|
|
620
639
|
candidates = session.element_local_attribute_index(doc.root)[local_name]
|
|
621
640
|
# No candidate carries this local name: nothing in the
|
|
622
641
|
# element can match, the scan is skipped entirely.
|
|
642
|
+
# getbyte, not a [i] slice — the slice allocated one
|
|
643
|
+
# string per probed candidate (~380k per ISO-13849 parse).
|
|
623
644
|
matched = candidates&.find do |attr|
|
|
624
645
|
ns = attr.namespace
|
|
625
646
|
next false if ns.nil? || ns.length > last_colon_index
|
|
626
|
-
next false unless rule_name.start_with?(ns) &&
|
|
647
|
+
next false unless rule_name.start_with?(ns) &&
|
|
648
|
+
rule_name.getbyte(ns.length) == COLON_BYTE
|
|
627
649
|
|
|
628
650
|
attr.unprefixed_name == local_name
|
|
629
651
|
end
|
|
@@ -631,7 +653,8 @@ _effective_register)
|
|
|
631
653
|
matched = doc.root.attributes.each_value.find do |attr|
|
|
632
654
|
ns = attr.namespace
|
|
633
655
|
next false if ns.nil? || ns.length > last_colon_index
|
|
634
|
-
next false unless rule_name.start_with?(ns) &&
|
|
656
|
+
next false unless rule_name.start_with?(ns) &&
|
|
657
|
+
rule_name.getbyte(ns.length) == COLON_BYTE
|
|
635
658
|
|
|
636
659
|
attr.unprefixed_name == local_name
|
|
637
660
|
end
|
|
@@ -661,13 +684,27 @@ _effective_register)
|
|
|
661
684
|
def find_attribute_by_local_name(doc, rule_names,
|
|
662
685
|
flexible_local: false, session: nil)
|
|
663
686
|
root_index = session&.element_local_attribute_index(doc.root)
|
|
687
|
+
# Matched VALUE per (element, spelling, flexible flag) — pure
|
|
688
|
+
# while the element is parse-frozen.
|
|
689
|
+
memo = if session
|
|
690
|
+
session.lenient_local_matches(doc.root,
|
|
691
|
+
flexible_local)
|
|
692
|
+
else
|
|
693
|
+
nil
|
|
694
|
+
end
|
|
664
695
|
|
|
665
696
|
rule_names.each do |rule_name|
|
|
666
697
|
next unless rule_name.include?(":")
|
|
667
698
|
|
|
668
|
-
resolved =
|
|
669
|
-
|
|
670
|
-
|
|
699
|
+
resolved = if memo&.key?(rule_name)
|
|
700
|
+
memo[rule_name]
|
|
701
|
+
else
|
|
702
|
+
match = match_attribute_by_local_name(
|
|
703
|
+
doc, rule_name, root_index, flexible_local
|
|
704
|
+
)
|
|
705
|
+
memo[rule_name] = match if memo
|
|
706
|
+
match
|
|
707
|
+
end
|
|
671
708
|
return resolved if resolved
|
|
672
709
|
end
|
|
673
710
|
nil
|
|
@@ -686,6 +723,11 @@ _effective_register)
|
|
|
686
723
|
local_name, rule_uri = parts
|
|
687
724
|
|
|
688
725
|
candidates = root_index&.[](local_name)
|
|
726
|
+
# The index carries every local form of every attribute on the
|
|
727
|
+
# element — a miss means no attribute can match, so the full
|
|
728
|
+
# each_value scan below is dead whenever the index exists.
|
|
729
|
+
return nil if root_index && !candidates
|
|
730
|
+
|
|
689
731
|
matched_attr = if candidates
|
|
690
732
|
candidates.find do |attr|
|
|
691
733
|
local_name_match?(attr, local_name, rule_uri,
|
|
@@ -1009,6 +1051,15 @@ _effective_register)
|
|
|
1009
1051
|
end
|
|
1010
1052
|
end
|
|
1011
1053
|
|
|
1054
|
+
# lutaml-model#88: attribute-value discriminator — among the
|
|
1055
|
+
# name-matched occurrences, keep only those whose sibling
|
|
1056
|
+
# attributes carry the expected values.
|
|
1057
|
+
if rule.when_attribute?
|
|
1058
|
+
children = children.select do |child|
|
|
1059
|
+
rule.matches_when_attribute?(child)
|
|
1060
|
+
end
|
|
1061
|
+
end
|
|
1062
|
+
|
|
1012
1063
|
# Performance: Cache rule method check before loop to avoid repeated method calls
|
|
1013
1064
|
rule_has_custom_method = rule.has_custom_method_for_deserialization?
|
|
1014
1065
|
if rule_has_custom_method || attr_type == ::Lutaml::Model::Type::Hash
|
|
@@ -29,8 +29,17 @@ module Lutaml
|
|
|
29
29
|
# functions of the element and the name; ISO-13849-shaped
|
|
30
30
|
# documents repeat the same resolutions per rule application, so
|
|
31
31
|
# the computed answer is memoized per element for the parse.
|
|
32
|
-
def rule_name_resolution
|
|
33
|
-
@rule_name_resolution ||= {}
|
|
32
|
+
def rule_name_resolution(element)
|
|
33
|
+
@rule_name_resolution ||= {}.compare_by_identity
|
|
34
|
+
@rule_name_resolution[element] ||= {}
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Lenient local-name fallback matches, per (element, flexible_local):
|
|
38
|
+
# spelling -> matched attribute VALUE. Same purity argument as
|
|
39
|
+
# rule_name_resolution — elements are parse-frozen.
|
|
40
|
+
def lenient_local_matches(element, flexible_local)
|
|
41
|
+
@lenient_local_matches ||= {}.compare_by_identity
|
|
42
|
+
(@lenient_local_matches[element] ||= {})[flexible_local] ||= {}
|
|
34
43
|
end
|
|
35
44
|
|
|
36
45
|
# Local-name -> [attributes] index over one element's attributes,
|
|
@@ -42,12 +42,31 @@ register_id, register)
|
|
|
42
42
|
rule.attribute_type < Lutaml::Model::Serialize
|
|
43
43
|
end
|
|
44
44
|
|
|
45
|
-
if is_nested_model
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
45
|
+
element = if is_nested_model
|
|
46
|
+
create_nested_model_element(rule, value, options,
|
|
47
|
+
register, union: union)
|
|
48
|
+
else
|
|
49
|
+
create_simple_value_element(rule, value, options,
|
|
50
|
+
model_class, register_id)
|
|
51
|
+
end
|
|
52
|
+
apply_when_attribute(element, rule)
|
|
53
|
+
element
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# lutaml-model#88: re-emit the discriminator attributes on the
|
|
57
|
+
# element created for this rule, unless the value's own mapping
|
|
58
|
+
# already wrote them.
|
|
59
|
+
def apply_when_attribute(element, rule)
|
|
60
|
+
pairs = rule.option(:when_attribute)
|
|
61
|
+
return if pairs.nil? || pairs.empty?
|
|
62
|
+
return if element.nil?
|
|
63
|
+
|
|
64
|
+
pairs.each do |name, expected|
|
|
65
|
+
next if element.attributes.any? { |a| a.name == name.to_s }
|
|
66
|
+
|
|
67
|
+
element.attributes << ::Lutaml::Xml::DataModel::XmlAttribute.new(
|
|
68
|
+
name.to_s, expected.to_s
|
|
69
|
+
)
|
|
51
70
|
end
|
|
52
71
|
end
|
|
53
72
|
|
|
@@ -337,6 +337,7 @@ register_id, register, attr_name, custom_methods_value)
|
|
|
337
337
|
value_map: value_map,
|
|
338
338
|
custom_methods: custom_methods_value,
|
|
339
339
|
polymorphic: mapping_rule.polymorphic,
|
|
340
|
+
when_attribute: mapping_rule.when_attribute,
|
|
340
341
|
form: mapping_rule.form,
|
|
341
342
|
delegate_from: delegate_target,
|
|
342
343
|
)
|
|
@@ -417,6 +418,7 @@ register_id, register, custom_methods_value)
|
|
|
417
418
|
value_map: value_map,
|
|
418
419
|
custom_methods: custom_methods_value,
|
|
419
420
|
polymorphic: mapping_rule.polymorphic,
|
|
421
|
+
when_attribute: mapping_rule.when_attribute,
|
|
420
422
|
form: mapping_rule.form,
|
|
421
423
|
alias_names: alias_names,
|
|
422
424
|
)
|
|
@@ -137,26 +137,36 @@ RSpec.describe Lutaml::KeyValue::Transformation::RuleCompiler do
|
|
|
137
137
|
end
|
|
138
138
|
|
|
139
139
|
describe "#valid_mapping?" do
|
|
140
|
-
|
|
140
|
+
def compiled_rule(serialize: true)
|
|
141
|
+
Lutaml::Model::CompiledRule.new(
|
|
142
|
+
attribute_name: :name,
|
|
143
|
+
serialized_name: "name",
|
|
144
|
+
serialize: serialize,
|
|
145
|
+
)
|
|
146
|
+
end
|
|
141
147
|
|
|
142
148
|
it "returns true when no only/except options" do
|
|
143
|
-
expect(compiler.valid_mapping?(
|
|
149
|
+
expect(compiler.valid_mapping?(compiled_rule, {})).to be true
|
|
144
150
|
end
|
|
145
151
|
|
|
146
152
|
it "returns true when attribute is in only list" do
|
|
147
|
-
expect(compiler.valid_mapping?(
|
|
153
|
+
expect(compiler.valid_mapping?(compiled_rule, { only: [:name] })).to be true
|
|
148
154
|
end
|
|
149
155
|
|
|
150
156
|
it "returns false when attribute is not in only list" do
|
|
151
|
-
expect(compiler.valid_mapping?(
|
|
157
|
+
expect(compiler.valid_mapping?(compiled_rule, { only: [:age] })).to be false
|
|
152
158
|
end
|
|
153
159
|
|
|
154
160
|
it "returns true when attribute is not in except list" do
|
|
155
|
-
expect(compiler.valid_mapping?(
|
|
161
|
+
expect(compiler.valid_mapping?(compiled_rule, { except: [:age] })).to be true
|
|
156
162
|
end
|
|
157
163
|
|
|
158
164
|
it "returns false when attribute is in except list" do
|
|
159
|
-
expect(compiler.valid_mapping?(
|
|
165
|
+
expect(compiler.valid_mapping?(compiled_rule, { except: [:name] })).to be false
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
it "returns false for a hydrate-only rule (serialize: false)" do
|
|
169
|
+
expect(compiler.valid_mapping?(compiled_rule(serialize: false), {})).to be false
|
|
160
170
|
end
|
|
161
171
|
end
|
|
162
172
|
|
|
@@ -283,7 +283,10 @@ RSpec.describe Lutaml::KeyValue::Transformation do
|
|
|
283
283
|
|
|
284
284
|
expect(transformation).to be_frozen
|
|
285
285
|
|
|
286
|
-
rule =
|
|
286
|
+
rule = Lutaml::Model::CompiledRule.new(
|
|
287
|
+
attribute_name: :name,
|
|
288
|
+
serialized_name: "name",
|
|
289
|
+
)
|
|
287
290
|
expect do
|
|
288
291
|
transformation.send(:valid_mapping?, rule, {})
|
|
289
292
|
end.not_to raise_error
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "spec_helper"
|
|
4
|
+
|
|
5
|
+
# lutaml-model#815: standalone `SomeModel.new.to_xml` crashed on 0.8.42 when
|
|
6
|
+
# the model's attributes are merged through register records whose imports
|
|
7
|
+
# clear @merged_attributes_cache re-entrantly from inside ensure_imports! —
|
|
8
|
+
# the memo store was materialized before ensure_imports! and the write after
|
|
9
|
+
# it hit nil[].
|
|
10
|
+
RSpec.describe "attributes memo across import-triggered cache clears" do
|
|
11
|
+
let(:namespace_class) do
|
|
12
|
+
Class.new(Lutaml::Xml::W3c::XmlNamespace) do
|
|
13
|
+
uri "https://example.com/ns815"
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
let(:register) do
|
|
18
|
+
Lutaml::Model::Register.new(:attrs815, fallback: [:default]).tap do |reg|
|
|
19
|
+
reg.bind_namespace(namespace_class)
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
let(:base_model) do
|
|
24
|
+
Class.new(Lutaml::Model::Serializable) do
|
|
25
|
+
attribute :shared, :string
|
|
26
|
+
|
|
27
|
+
xml do
|
|
28
|
+
element "Base815"
|
|
29
|
+
namespace namespace_class
|
|
30
|
+
map_element "shared", to: :shared
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
let(:consumer_model) do
|
|
36
|
+
Class.new(Lutaml::Model::Serializable) do
|
|
37
|
+
attribute :own, :string
|
|
38
|
+
|
|
39
|
+
xml do
|
|
40
|
+
element "Consumer815"
|
|
41
|
+
namespace namespace_class
|
|
42
|
+
map_element "own", to: :own
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
before do
|
|
48
|
+
stub_const("Attrs815::NamespaceClass", namespace_class)
|
|
49
|
+
Lutaml::Model::GlobalContext.reset!
|
|
50
|
+
Lutaml::Model::GlobalRegister.instance.register(register)
|
|
51
|
+
# Populate @register_records through the import the parse path uses.
|
|
52
|
+
base_model.import_model_attributes(consumer_model, register.id)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
after do
|
|
56
|
+
Lutaml::Model::GlobalContext.reset!
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
it "serializes a register-bound model as its own document root" do
|
|
60
|
+
instance = base_model.new(shared: "x")
|
|
61
|
+
expect(instance.to_xml).to include("<shared>x</shared>")
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
it "merges register attributes after an import-triggered cache clear" do
|
|
65
|
+
instance = consumer_model.new(own: "y")
|
|
66
|
+
expect(instance.to_xml).to include("<own>y</own>")
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
it "keeps answering attributes() from the memo after imports" do
|
|
70
|
+
first = consumer_model.attributes(:attrs815)
|
|
71
|
+
expect(consumer_model.attributes(:attrs815)).to equal(first)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
it "survives a re-entrant cache clear from inside ensure_imports!" do
|
|
75
|
+
# uniword's register-bound models (#815): choice/restrict resolution
|
|
76
|
+
# runs clear_cache while ensure_imports! is still on the stack, which
|
|
77
|
+
# nils @merged_attributes_cache after the reader materialized it.
|
|
78
|
+
reentrant = Class.new(base_model) do
|
|
79
|
+
def self.finalized? = true
|
|
80
|
+
|
|
81
|
+
def self.ensure_format_mapping_imports!(_register = nil)
|
|
82
|
+
clear_cache
|
|
83
|
+
super
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
expect { reentrant.attributes(:attrs815) }.not_to raise_error
|
|
88
|
+
expect(reentrant.attributes(:attrs815)[:shared].name).to eq(:shared)
|
|
89
|
+
end
|
|
90
|
+
end
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "spec_helper"
|
|
4
|
+
require_relative "../../../lib/lutaml/model"
|
|
5
|
+
|
|
6
|
+
module HydrateOnlyMapping
|
|
7
|
+
class Message < Lutaml::Model::Serializable
|
|
8
|
+
attribute :body, :string
|
|
9
|
+
attribute :source_offset, :integer
|
|
10
|
+
|
|
11
|
+
key_value do
|
|
12
|
+
map "body", to: :body
|
|
13
|
+
map "source_offset", to: :source_offset, serialize: false
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
RSpec.describe "hydrate-only mappings (serialize: false)" do
|
|
19
|
+
let(:input) { { "body" => "hello", "source_offset" => 42 } }
|
|
20
|
+
|
|
21
|
+
it "hydrates the attribute from input" do
|
|
22
|
+
message = HydrateOnlyMapping::Message.from_hash(input)
|
|
23
|
+
|
|
24
|
+
expect(message.body).to eq("hello")
|
|
25
|
+
expect(message.source_offset).to eq(42)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
it "omits the attribute from serialized output" do
|
|
29
|
+
message = HydrateOnlyMapping::Message.from_hash(input)
|
|
30
|
+
|
|
31
|
+
expect(message.to_hash).to eq("body" => "hello")
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
it "round-trips through JSON without the hidden key" do
|
|
35
|
+
message = HydrateOnlyMapping::Message.from_json(input.to_json)
|
|
36
|
+
|
|
37
|
+
expect(JSON.parse(message.to_json)).to eq("body" => "hello")
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
it "keeps the mapping through deep-copied mappings" do
|
|
41
|
+
mapping = HydrateOnlyMapping::Message.mappings_for(:hash)
|
|
42
|
+
rule = mapping.mappings.find { |r| r.to == :source_offset }
|
|
43
|
+
|
|
44
|
+
expect(mapping.deep_dup.mappings.find { |r| r.to == :source_offset }
|
|
45
|
+
.serialize?).to eq(rule.serialize?)
|
|
46
|
+
end
|
|
47
|
+
end
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "spec_helper"
|
|
4
|
+
|
|
5
|
+
# lutaml-model#88: attribute-value dispatch on a shared wire name.
|
|
6
|
+
# `map_element "component", to: :guidance, when_attribute: { "type" =>
|
|
7
|
+
# "guidance" }` selects only the occurrences whose sibling attribute carries
|
|
8
|
+
# the expected value, and re-emits the discriminator on serialization.
|
|
9
|
+
class WhenAttrComponent < Lutaml::Model::Serializable
|
|
10
|
+
attribute :text, :string
|
|
11
|
+
|
|
12
|
+
xml do
|
|
13
|
+
element "component"
|
|
14
|
+
map_element "text", to: :text
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
class WhenAttrRequirement < Lutaml::Model::Serializable
|
|
19
|
+
attribute :guidance, WhenAttrComponent, collection: true
|
|
20
|
+
attribute :purpose, WhenAttrComponent, collection: true
|
|
21
|
+
attribute :test_method, WhenAttrComponent, collection: true
|
|
22
|
+
|
|
23
|
+
xml do
|
|
24
|
+
element "requirement"
|
|
25
|
+
map_element "component", to: :guidance,
|
|
26
|
+
when_attribute: { "type" => "guidance" }
|
|
27
|
+
map_element "component", to: :purpose,
|
|
28
|
+
when_attribute: { "type" => "purpose" }
|
|
29
|
+
map_element "component", to: :test_method,
|
|
30
|
+
when_attribute: { type: "test-method" }
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
RSpec.describe "when_attribute discriminator mappings" do
|
|
35
|
+
before do
|
|
36
|
+
stub_const("WhenAttr::Component", WhenAttrComponent)
|
|
37
|
+
stub_const("WhenAttr::Requirement", WhenAttrRequirement)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
let(:xml) do
|
|
41
|
+
<<~XML
|
|
42
|
+
<requirement>
|
|
43
|
+
<component type="guidance"><text>g1</text></component>
|
|
44
|
+
<component type="purpose"><text>p1</text></component>
|
|
45
|
+
<component type="guidance"><text>g2</text></component>
|
|
46
|
+
<component type="test-method"><text>t1</text></component>
|
|
47
|
+
</requirement>
|
|
48
|
+
XML
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
it "dispatches occurrences by the discriminator value" do
|
|
52
|
+
req = WhenAttr::Requirement.from_xml(xml)
|
|
53
|
+
|
|
54
|
+
expect(req.guidance.map(&:text)).to eq(%w[g1 g2])
|
|
55
|
+
expect(req.purpose.map(&:text)).to eq(["p1"])
|
|
56
|
+
expect(req.test_method.map(&:text)).to eq(["t1"])
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
it "keeps document order within each target attribute" do
|
|
60
|
+
req = WhenAttr::Requirement.from_xml(xml)
|
|
61
|
+
expect(req.guidance.map(&:text)).to eq(%w[g1 g2])
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
it "drops occurrences whose discriminator is not covered" do
|
|
65
|
+
doc = '<requirement><component type="unknown"><text>x</text></component></requirement>'
|
|
66
|
+
req = WhenAttr::Requirement.from_xml(doc)
|
|
67
|
+
|
|
68
|
+
expect(req.guidance).to be_empty
|
|
69
|
+
expect(req.purpose).to be_empty
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
it "re-emits the discriminator attribute on serialization" do
|
|
73
|
+
round = WhenAttr::Requirement.from_xml(WhenAttr::Requirement.from_xml(xml).to_xml)
|
|
74
|
+
|
|
75
|
+
expect(round.guidance.map(&:text)).to eq(%w[g1 g2])
|
|
76
|
+
expect(round.purpose.map(&:text)).to eq(["p1"])
|
|
77
|
+
expect(round.test_method.map(&:text)).to eq(["t1"])
|
|
78
|
+
|
|
79
|
+
serialized = WhenAttr::Requirement.from_xml(xml).to_xml
|
|
80
|
+
expect(serialized).to include('type="guidance"')
|
|
81
|
+
expect(serialized).to include('type="purpose"')
|
|
82
|
+
expect(serialized).to include('type="test-method"')
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
it "does not emit a discriminator for ordinary rules" do
|
|
86
|
+
plain = Class.new(Lutaml::Model::Serializable) do
|
|
87
|
+
attribute :text, :string
|
|
88
|
+
|
|
89
|
+
xml do
|
|
90
|
+
element "plain"
|
|
91
|
+
map_element "text", to: :text
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
expect(plain.from_xml("<plain><text>t</text></plain>").to_xml)
|
|
96
|
+
.not_to include("type=")
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
it "survives deep_dup of mappings" do
|
|
100
|
+
mapping = WhenAttr::Requirement.mappings_for(:xml)
|
|
101
|
+
duped = mapping.deep_dup.mappings.find do |r|
|
|
102
|
+
r.to == :guidance
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
expect(duped.when_attribute).to eq("type" => "guidance")
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
it "rejects non-string/symbol discriminator shapes" do
|
|
109
|
+
expect do
|
|
110
|
+
Class.new(Lutaml::Model::Serializable) do
|
|
111
|
+
attribute :x, :string
|
|
112
|
+
|
|
113
|
+
xml do
|
|
114
|
+
element "x"
|
|
115
|
+
map_element "x", to: :x, when_attribute: { "type" => 42 }
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
end.to raise_error(Lutaml::Model::IncorrectMappingArgumentsError, /when_attribute/)
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
it "is rejected on attribute mappings" do
|
|
122
|
+
expect do
|
|
123
|
+
Class.new(Lutaml::Model::Serializable) do
|
|
124
|
+
attribute :x, :string
|
|
125
|
+
|
|
126
|
+
xml do
|
|
127
|
+
element "x"
|
|
128
|
+
map_attribute "x", to: :x, when_attribute: { "type" => "guidance" }
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
end.to raise_error(ArgumentError)
|
|
132
|
+
end
|
|
133
|
+
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.43
|
|
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-
|
|
11
|
+
date: 2026-09-20 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: base64
|
|
@@ -1695,6 +1695,7 @@ files:
|
|
|
1695
1695
|
- spec/lutaml/model/attribute_form_default_spec.rb
|
|
1696
1696
|
- spec/lutaml/model/attribute_restriction_spec.rb
|
|
1697
1697
|
- spec/lutaml/model/attribute_spec.rb
|
|
1698
|
+
- spec/lutaml/model/attributes_memo_clear_race_spec.rb
|
|
1698
1699
|
- spec/lutaml/model/boot_manifest_spec.rb
|
|
1699
1700
|
- spec/lutaml/model/cached_type_resolver_spec.rb
|
|
1700
1701
|
- spec/lutaml/model/cardinality_strict_spec.rb
|
|
@@ -1737,6 +1738,7 @@ files:
|
|
|
1737
1738
|
- spec/lutaml/model/global_register_spec.rb
|
|
1738
1739
|
- spec/lutaml/model/group_spec.rb
|
|
1739
1740
|
- spec/lutaml/model/hash/adapter_spec.rb
|
|
1741
|
+
- spec/lutaml/model/hydrate_only_mapping_spec.rb
|
|
1740
1742
|
- spec/lutaml/model/import_registry_spec.rb
|
|
1741
1743
|
- spec/lutaml/model/included_spec.rb
|
|
1742
1744
|
- spec/lutaml/model/inheritance_spec.rb
|
|
@@ -2004,6 +2006,7 @@ files:
|
|
|
2004
2006
|
- spec/lutaml/xml/validate_xml_with_constructs_spec.rb
|
|
2005
2007
|
- spec/lutaml/xml/validate_xml_with_spec.rb
|
|
2006
2008
|
- spec/lutaml/xml/w3c_types_spec.rb
|
|
2009
|
+
- spec/lutaml/xml/when_attribute_spec.rb
|
|
2007
2010
|
- spec/lutaml/xml/xml_adapter_spec.rb
|
|
2008
2011
|
- spec/lutaml/xml/xml_declaration_spec.rb
|
|
2009
2012
|
- spec/lutaml/xml/xml_element_guard_spec.rb
|