lutaml-model 0.8.39 → 0.8.40
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/lib/lutaml/model/serialize/attribute_definition.rb +1 -0
- data/lib/lutaml/model/store.rb +19 -0
- data/lib/lutaml/model/version.rb +1 -1
- data/lib/lutaml/xml/adapter/plan_based_builder.rb +16 -3
- data/lib/lutaml/xml/adapter/xml_serializer.rb +13 -2
- data/lib/lutaml/xml/adapter_element.rb +7 -0
- data/lib/lutaml/xml/mapping.rb +8 -3
- data/lib/lutaml/xml/model_transform.rb +25 -5
- data/spec/lutaml/model/map_all_spec.rb +23 -19
- data/spec/lutaml/model/store_spec.rb +7 -1
- data/spec/lutaml/xml/mapping_spec.rb +6 -7
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 597b465123fe390703bdb760a4f6e18e4069b55bea8998633e2a4c0c7f5fcb9c
|
|
4
|
+
data.tar.gz: e848597e1ad2cef05670d8dc8de6f7480bf028fde627021c5a61f554ad99a2f0
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 231f9601a9ce76ae516a6435b2c7185e15c2cc6680d0aefbad27ef328b25f67f0ed2635be8319eff6c3a64338f3bd2f9855ab1daa51058018345eb77cf3aa0d9
|
|
7
|
+
data.tar.gz: 88af6f65b2fadf91990f6f6f10a03a799007acda351d87a059f00325c85514291fbaccfd99580dd1daac3090bd61f4aa1ec26efffd058438c7e6abdd9a53cea4
|
data/lib/lutaml/model/store.rb
CHANGED
|
@@ -33,11 +33,30 @@ module Lutaml
|
|
|
33
33
|
@instance ||= new
|
|
34
34
|
end
|
|
35
35
|
|
|
36
|
+
# lutaml-model#808: Ruby 3.3's GC has a WeakMap-mark hazard under
|
|
37
|
+
# heavy insert churn (freed slot dereferenced at wmap mark time).
|
|
38
|
+
# Registration is only ever consumed by `ref:` resolution — when
|
|
39
|
+
# no Reference-typed attribute exists anywhere, nothing can
|
|
40
|
+
# resolve, so registration is skipped entirely and the WeakMap
|
|
41
|
+
# stays quiet. Set the first time an attribute with a Reference
|
|
42
|
+
# type is defined.
|
|
43
|
+
# Not memoized `||=` — the setter flips it to true for the
|
|
44
|
+
# process lifetime and reads must see that.
|
|
45
|
+
def reference_types_in_use?
|
|
46
|
+
@reference_types_in_use ? true : false
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def reference_types_in_use!
|
|
50
|
+
@reference_types_in_use = true
|
|
51
|
+
end
|
|
52
|
+
|
|
36
53
|
def reset!
|
|
37
54
|
@instance = new
|
|
38
55
|
end
|
|
39
56
|
|
|
40
57
|
def register(object)
|
|
58
|
+
return unless reference_types_in_use?
|
|
59
|
+
|
|
41
60
|
instance.register(object)
|
|
42
61
|
end
|
|
43
62
|
|
data/lib/lutaml/model/version.rb
CHANGED
|
@@ -299,13 +299,23 @@ module Lutaml
|
|
|
299
299
|
# Create element
|
|
300
300
|
xml.create_and_add_element(tag_name, attributes: attributes,
|
|
301
301
|
prefix: prefix) do |inner_xml|
|
|
302
|
-
# Handle raw content (map_all directive)
|
|
302
|
+
# Handle raw content (map_all directive). lutaml-model#223:
|
|
303
|
+
# with coexisting mappings the raw remainder is the TAIL —
|
|
304
|
+
# mapped children render first through the normal paths
|
|
305
|
+
# below, then the fragment is appended. Standalone map_all
|
|
306
|
+
# (raw is the whole content) keeps the historical
|
|
307
|
+
# raw-only rendering by fronting the fragment and skipping
|
|
308
|
+
# children only when there is nothing mapped.
|
|
303
309
|
has_raw_content = false
|
|
304
310
|
if element.is_a?(Lutaml::Xml::DataModel::XmlElement)
|
|
305
311
|
raw_content = element.raw_content
|
|
306
312
|
if raw_content && !raw_content.to_s.empty?
|
|
307
|
-
|
|
308
|
-
|
|
313
|
+
if element.children.empty?
|
|
314
|
+
inner_xml.add_xml_fragment(inner_xml, raw_content.to_s)
|
|
315
|
+
has_raw_content = true
|
|
316
|
+
else
|
|
317
|
+
(tail_raw_fragments ||= []) << raw_content.to_s
|
|
318
|
+
end
|
|
309
319
|
end
|
|
310
320
|
end
|
|
311
321
|
|
|
@@ -343,6 +353,9 @@ module Lutaml
|
|
|
343
353
|
end
|
|
344
354
|
end
|
|
345
355
|
end
|
|
356
|
+
(tail_raw_fragments || []).each do |fragment|
|
|
357
|
+
inner_xml.add_xml_fragment(inner_xml, fragment)
|
|
358
|
+
end
|
|
346
359
|
end
|
|
347
360
|
end
|
|
348
361
|
|
|
@@ -268,11 +268,20 @@ module Lutaml
|
|
|
268
268
|
attributes["xmlns"] = "" if needs_xmlns_blank
|
|
269
269
|
|
|
270
270
|
xml.create_and_add_element(qualified_name, attributes: attributes) do
|
|
271
|
+
tail_raw = nil
|
|
271
272
|
if xml_element.is_a?(Lutaml::Xml::DataModel::XmlElement)
|
|
272
273
|
raw_content = xml_element.raw_content
|
|
273
274
|
if raw_content && !raw_content.to_s.empty?
|
|
274
|
-
|
|
275
|
-
|
|
275
|
+
# lutaml-model#223: with coexisting map_all, the raw
|
|
276
|
+
# remainder is the tail — mapped children render first.
|
|
277
|
+
# Standalone map_all (no mapped children) keeps the
|
|
278
|
+
# historical raw-only rendering.
|
|
279
|
+
if xml_element.children.empty?
|
|
280
|
+
xml.add_xml_fragment(xml, raw_content.to_s)
|
|
281
|
+
return
|
|
282
|
+
end
|
|
283
|
+
|
|
284
|
+
tail_raw = raw_content.to_s
|
|
276
285
|
end
|
|
277
286
|
end
|
|
278
287
|
|
|
@@ -306,6 +315,8 @@ module Lutaml
|
|
|
306
315
|
end
|
|
307
316
|
end
|
|
308
317
|
|
|
318
|
+
xml.add_xml_fragment(xml, tail_raw) if tail_raw
|
|
319
|
+
|
|
309
320
|
if xml_element.text_content
|
|
310
321
|
if xml_element.cdata
|
|
311
322
|
xml.cdata(xml_element.text_content.to_s)
|
|
@@ -109,6 +109,13 @@ module Lutaml
|
|
|
109
109
|
children.map(&:to_xml).join
|
|
110
110
|
end
|
|
111
111
|
|
|
112
|
+
# The element including its own tags — the building block for
|
|
113
|
+
# scoped raw capture (#223): an unclaimed child contributes its
|
|
114
|
+
# whole subtree verbatim.
|
|
115
|
+
def inner_xml_or_self
|
|
116
|
+
to_xml
|
|
117
|
+
end
|
|
118
|
+
|
|
112
119
|
private
|
|
113
120
|
|
|
114
121
|
def build_element_xml(builder)
|
data/lib/lutaml/xml/mapping.rb
CHANGED
|
@@ -979,9 +979,14 @@ module Lutaml
|
|
|
979
979
|
"is allowed with #{TYPES[:all_content]}"
|
|
980
980
|
end
|
|
981
981
|
|
|
982
|
-
if
|
|
983
|
-
|
|
984
|
-
|
|
982
|
+
if type == TYPES[:all_content] &&
|
|
983
|
+
!(elements.empty? && content_mapping.nil?)
|
|
984
|
+
# lutaml-model#223: map_all may coexist with mapped siblings.
|
|
985
|
+
# The raw capture receives only the UNCLAIMED content — the
|
|
986
|
+
# verbatim subtree minus children matched by element rules.
|
|
987
|
+
# The historical exclusivity raised here; the coexistence
|
|
988
|
+
# contract is enforced by scoped capture on parse and
|
|
989
|
+
# remainder-after-mapped emission on serialize.
|
|
985
990
|
end
|
|
986
991
|
end
|
|
987
992
|
|
|
@@ -55,14 +55,17 @@ module Lutaml
|
|
|
55
55
|
# ISO-13849 profile). Memoize one instance per (element, class)
|
|
56
56
|
# for the whole parse; the memo travels in the propagated
|
|
57
57
|
# deserialization options.
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
58
|
+
# Nested identity maps keyed on the element object itself —
|
|
59
|
+
# flat object_id pairs could collide after GC id reuse on
|
|
60
|
+
# long parses (#808 audit).
|
|
61
|
+
memo = options[:__lutaml_child_memo] ||= {}.compare_by_identity
|
|
62
|
+
per_class = (memo[data] ||= {}.compare_by_identity)
|
|
63
|
+
if (existing = per_class[model_class])
|
|
61
64
|
return existing
|
|
62
65
|
end
|
|
63
66
|
|
|
64
67
|
instance = model_class.allocate_for_deserialization(child_register)
|
|
65
|
-
|
|
68
|
+
per_class[model_class] = instance
|
|
66
69
|
else
|
|
67
70
|
instance = model_class.new
|
|
68
71
|
register_accessor_methods_for(instance, child_register)
|
|
@@ -345,7 +348,7 @@ module Lutaml
|
|
|
345
348
|
end
|
|
346
349
|
|
|
347
350
|
value = if rule.raw_mapping?
|
|
348
|
-
doc
|
|
351
|
+
scoped_raw_inner_xml(doc, xml_mapping)
|
|
349
352
|
elsif rule.content_mapping?
|
|
350
353
|
rule.cdata ? doc.cdata : doc.text
|
|
351
354
|
elsif (group = grouped_plain_rules[rule_to]) &&
|
|
@@ -713,6 +716,23 @@ _effective_register)
|
|
|
713
716
|
# lutaml-model#154: decode HTML entities for mappings that opted in
|
|
714
717
|
# (`html_entities` in the xml block). Values stay untouched
|
|
715
718
|
# otherwise — XML keeps undefined entities literal by design.
|
|
719
|
+
# lutaml-model#223: with coexisting map_all, the raw capture is
|
|
720
|
+
# the verbatim subtree MINUS the children claimed by element
|
|
721
|
+
# rules (they hydrate their own attributes). Standalone map_all
|
|
722
|
+
# (no element rules) keeps the full inner_xml.
|
|
723
|
+
def scoped_raw_inner_xml(doc, xml_mapping)
|
|
724
|
+
claimed = []
|
|
725
|
+
xml_mapping&.elements&.each do |element_rule|
|
|
726
|
+
Array(element_rule.name).each { |n| claimed << n.to_s }
|
|
727
|
+
end
|
|
728
|
+
return doc.root.inner_xml if claimed.empty?
|
|
729
|
+
|
|
730
|
+
doc.root.element_children.reject do |child|
|
|
731
|
+
claimed.include?(child.unprefixed_name) ||
|
|
732
|
+
claimed.include?(child.name)
|
|
733
|
+
end.map(&:inner_xml_or_self).join
|
|
734
|
+
end
|
|
735
|
+
|
|
716
736
|
def decode_html_entities_value(value, enabled)
|
|
717
737
|
return value if value.nil? || !value.is_a?(String)
|
|
718
738
|
return value unless enabled
|
|
@@ -185,28 +185,32 @@ module MapAllSpec
|
|
|
185
185
|
end
|
|
186
186
|
end
|
|
187
187
|
|
|
188
|
-
describe "
|
|
189
|
-
it "
|
|
190
|
-
|
|
191
|
-
|
|
188
|
+
describe "coexistence with other mappings (#223)" do
|
|
189
|
+
it "captures only unclaimed content alongside mapped elements" do
|
|
190
|
+
doc_class = Class.new(Lutaml::Model::Serializable) do
|
|
191
|
+
attribute :title, :string
|
|
192
|
+
attribute :rest, :string
|
|
193
|
+
|
|
194
|
+
xml do
|
|
195
|
+
element "eref"
|
|
192
196
|
map_element "title", to: :title
|
|
197
|
+
map_all to: :rest
|
|
193
198
|
end
|
|
194
|
-
end
|
|
195
|
-
StandardError,
|
|
196
|
-
"map_all is not allowed with other mappings",
|
|
197
|
-
)
|
|
198
|
-
end
|
|
199
|
+
end
|
|
199
200
|
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
201
|
+
source = %(<eref><title>T</title><p>para</p><note>N</note></eref>)
|
|
202
|
+
parsed = doc_class.from_xml(source)
|
|
203
|
+
expect(parsed.title).to eq("T")
|
|
204
|
+
expect(parsed.rest).to eq("<p>para</p><note>N</note>")
|
|
205
|
+
|
|
206
|
+
out = doc_class.to_xml(parsed)
|
|
207
|
+
expect(out).to include("<title>T</title>")
|
|
208
|
+
expect(out).to include("<p>para</p>")
|
|
209
|
+
expect(out).to include("<note>N</note>")
|
|
210
|
+
|
|
211
|
+
reparsed = doc_class.from_xml(out)
|
|
212
|
+
expect(reparsed.title).to eq("T")
|
|
213
|
+
expect(reparsed.rest).to eq("<p>para</p><note>N</note>")
|
|
210
214
|
end
|
|
211
215
|
end
|
|
212
216
|
end
|
|
@@ -3,6 +3,13 @@
|
|
|
3
3
|
require "spec_helper"
|
|
4
4
|
|
|
5
5
|
RSpec.describe Lutaml::Model::Store do
|
|
6
|
+
before do
|
|
7
|
+
# Registration only runs when a Reference-typed attribute exists
|
|
8
|
+
# somewhere (#808); the internal specs opt in explicitly.
|
|
9
|
+
described_class.reference_types_in_use!
|
|
10
|
+
described_class.clear
|
|
11
|
+
end
|
|
12
|
+
|
|
6
13
|
let(:model_class) do
|
|
7
14
|
Class.new(Lutaml::Model::Serializable) do
|
|
8
15
|
attribute :id, :string
|
|
@@ -10,7 +17,6 @@ RSpec.describe Lutaml::Model::Store do
|
|
|
10
17
|
end
|
|
11
18
|
end
|
|
12
19
|
|
|
13
|
-
before { described_class.clear }
|
|
14
20
|
after { described_class.clear }
|
|
15
21
|
|
|
16
22
|
describe "#register and #resolve" do
|
|
@@ -974,7 +974,6 @@ RSpec.describe Lutaml::Xml::Mapping do
|
|
|
974
974
|
end
|
|
975
975
|
|
|
976
976
|
context "when mixed content is true and child is content_mapping" do
|
|
977
|
-
let(:map_all_error) { "map_all is not allowed with other mappings" }
|
|
978
977
|
let(:invalid_element) { "must be defined without namespace" }
|
|
979
978
|
let(:mfenced) do
|
|
980
979
|
<<~XML
|
|
@@ -994,7 +993,7 @@ RSpec.describe Lutaml::Xml::Mapping do
|
|
|
994
993
|
XML
|
|
995
994
|
end
|
|
996
995
|
|
|
997
|
-
it "
|
|
996
|
+
it "allows map_all alongside element mappings (#223)" do
|
|
998
997
|
original_mapping = XmlMappingSpec::MmlMath.mappings[:xml].deep_dup
|
|
999
998
|
|
|
1000
999
|
begin
|
|
@@ -1005,7 +1004,7 @@ RSpec.describe Lutaml::Xml::Mapping do
|
|
|
1005
1004
|
XmlMappingSpec::MmlMath.xml do
|
|
1006
1005
|
map_all to: :all_content
|
|
1007
1006
|
end
|
|
1008
|
-
end.
|
|
1007
|
+
end.not_to raise_error
|
|
1009
1008
|
ensure
|
|
1010
1009
|
XmlMappingSpec::MmlMath.instance_variable_set(:@mappings,
|
|
1011
1010
|
{ xml: original_mapping })
|
|
@@ -1013,16 +1012,16 @@ RSpec.describe Lutaml::Xml::Mapping do
|
|
|
1013
1012
|
end
|
|
1014
1013
|
end
|
|
1015
1014
|
|
|
1016
|
-
it "
|
|
1015
|
+
it "still validates its own arguments when combined (#223)" do
|
|
1017
1016
|
original_mapping = XmlMappingSpec::MmlMath.mappings[:xml].deep_dup
|
|
1018
1017
|
|
|
1019
1018
|
begin
|
|
1020
1019
|
expect do
|
|
1021
1020
|
XmlMappingSpec::MmlMath.xml do
|
|
1022
|
-
map_all to: :all_content
|
|
1023
|
-
namespace XmiNewNamespace
|
|
1021
|
+
map_all to: :all_content, namespace: "http://explicit.ns"
|
|
1024
1022
|
end
|
|
1025
|
-
end.to raise_error(
|
|
1023
|
+
end.to raise_error(Lutaml::Model::IncorrectMappingArgumentsError,
|
|
1024
|
+
/namespace is not allowed/)
|
|
1026
1025
|
ensure
|
|
1027
1026
|
XmlMappingSpec::MmlMath.instance_variable_set(:@mappings,
|
|
1028
1027
|
{ xml: original_mapping })
|