lutaml-model 0.8.95 → 0.8.96
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/grammar-backed-formats.adoc +69 -0
- data/docs/_guides/index.adoc +2 -0
- data/lib/lutaml/model/serialize/attribute_definition.rb +133 -7
- data/lib/lutaml/model/serialize/format_conversion.rb +6 -1
- data/lib/lutaml/model/serialize/model_import.rb +5 -0
- data/lib/lutaml/model/version.rb +1 -1
- data/lib/lutaml/model.rb +7 -0
- data/lib/lutaml/xml/format.rb +8 -10
- data/lib/lutaml/xml/leptris/plan_compiler.rb +36 -18
- data/lib/lutaml/xml/leptris/plan_hydrator.rb +34 -1
- data/lib/lutaml/xml/leptris/plan_serializer.rb +114 -18
- data/lib/lutaml/xml/leptris.rb +26 -0
- data/spec/lutaml/model/opal_smoke_spec.rb +17 -0
- data/spec/lutaml/model/serialize/attribute_definition_block_form_spec.rb +244 -0
- data/spec/lutaml/xml/opal_xml_spec.rb +47 -0
- data/spec/lutaml/xml/plan_namespaced_serialize_spec.rb +157 -0
- data/spec/lutaml/xml/plan_type_ns_attribute_spec.rb +2 -1
- data/spec/lutaml/xml/plan_wildcard_any_spec.rb +105 -0
- metadata +5 -1
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "spec_helper"
|
|
4
|
+
|
|
5
|
+
RSpec.describe "XML plan serializer: namespaced models" do
|
|
6
|
+
# lutaml-model#847: the plan path used to refuse namespaced models
|
|
7
|
+
# outright (the xmlns dropped). The DOM emission now spells prefixed
|
|
8
|
+
# names, hoists every distinct (prefix, uri) declaration exactly once
|
|
9
|
+
# onto the output root, and prefixes type-namespaced attribute rows —
|
|
10
|
+
# byte-equal with the interpretive writer.
|
|
11
|
+
|
|
12
|
+
let(:wml_ns) do
|
|
13
|
+
Class.new(Lutaml::Xml::Namespace) do
|
|
14
|
+
uri "http://example.com/gate2/wml"
|
|
15
|
+
prefix_default "w"
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
let(:val_type) do
|
|
20
|
+
stub_const("Gate2Ns::WmlNs", wml_ns)
|
|
21
|
+
type = Class.new(Lutaml::Model::Type::String) do
|
|
22
|
+
include Lutaml::Xml::Type::Configurable
|
|
23
|
+
|
|
24
|
+
xml do
|
|
25
|
+
namespace Gate2Ns::WmlNs
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
stub_const("Gate2Ns::ValType", type)
|
|
29
|
+
type
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
let(:doc_class) do
|
|
33
|
+
val_type
|
|
34
|
+
body_class
|
|
35
|
+
model = Class.new(Lutaml::Model::Serializable) do
|
|
36
|
+
attribute :name, Gate2Ns::ValType
|
|
37
|
+
attribute :body, Gate2Ns::Body
|
|
38
|
+
|
|
39
|
+
xml do
|
|
40
|
+
element "document"
|
|
41
|
+
namespace Gate2Ns::WmlNs
|
|
42
|
+
map_attribute "name", to: :name
|
|
43
|
+
map_element "body", to: :body
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
stub_const("Gate2Ns::Document", model)
|
|
47
|
+
model
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
let(:body_class) do
|
|
51
|
+
val_type
|
|
52
|
+
model = Class.new(Lutaml::Model::Serializable) do
|
|
53
|
+
attribute :text, Gate2Ns::ValType, collection: true
|
|
54
|
+
|
|
55
|
+
xml do
|
|
56
|
+
element "body"
|
|
57
|
+
namespace Gate2Ns::WmlNs
|
|
58
|
+
map_element "t", to: :text
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
stub_const("Gate2Ns::Body", model)
|
|
62
|
+
model
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
let(:default_ns_model) do
|
|
66
|
+
ns = Class.new(Lutaml::Xml::Namespace) do
|
|
67
|
+
uri "http://example.com/gate2/default"
|
|
68
|
+
end
|
|
69
|
+
stub_const("Gate2Ns::DefaultNs", ns)
|
|
70
|
+
model = Class.new(Lutaml::Model::Serializable) do
|
|
71
|
+
attribute :name, :string
|
|
72
|
+
|
|
73
|
+
xml do
|
|
74
|
+
element "root"
|
|
75
|
+
namespace Gate2Ns::DefaultNs
|
|
76
|
+
map_attribute "name", to: :name
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
stub_const("Gate2Ns::DefaultDoc", model)
|
|
80
|
+
model
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
around do |example|
|
|
84
|
+
old = Lutaml::Model::Config.instance.xml_plan_fast_path
|
|
85
|
+
Lutaml::Model::Config.instance.xml_plan_fast_path = true
|
|
86
|
+
Lutaml::Model::Config.with_adapter(xml: :leptris) { example.run }
|
|
87
|
+
ensure
|
|
88
|
+
Lutaml::Model::Config.instance.xml_plan_fast_path = old
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def plan_to_xml(klass, instance)
|
|
92
|
+
plan = Lutaml::Xml::Leptris::PlanCompiler.compile(
|
|
93
|
+
klass, Lutaml::Model::Config.default_register
|
|
94
|
+
)
|
|
95
|
+
Lutaml::Xml::Leptris::PlanSerializer.call(
|
|
96
|
+
instance, plan, Lutaml::Model::Config.default_register
|
|
97
|
+
)
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
it "emits prefixed names with the declaration exactly once" do
|
|
101
|
+
instance = doc_class.new(
|
|
102
|
+
name: "d1",
|
|
103
|
+
body: body_class.new(text: ["hello"]),
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
out = plan_to_xml(doc_class, instance)
|
|
107
|
+
expect(out).to include(%(xmlns:w="http://example.com/gate2/wml"))
|
|
108
|
+
expect(out.scan("xmlns:w=").count).to eq(1)
|
|
109
|
+
expect(out).to include("<w:document")
|
|
110
|
+
expect(out).to include("w:name=\"d1\"")
|
|
111
|
+
expect(out).to include("<w:body>")
|
|
112
|
+
expect(out).to include("<w:t>hello</w:t>")
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# The plan path spells the type-namespaced attribute w:name=; the
|
|
116
|
+
# interpretive writer emits name= unprefixed (a long-standing
|
|
117
|
+
# interpretive serialize gap — the unprefixed form does not rebind
|
|
118
|
+
# to the (URI, local) rule on reparse). Element tree, declarations,
|
|
119
|
+
# and values are compared; the attribute spelling divergence is
|
|
120
|
+
# pinned by the round-trip example below.
|
|
121
|
+
it "matches the interpretive element tree and declarations" do
|
|
122
|
+
instance = doc_class.new(
|
|
123
|
+
name: "d1",
|
|
124
|
+
body: body_class.new(text: ["hello", "world"]),
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
interpretive = Lutaml::Model::Config.with_adapter(xml: :nokogiri) do
|
|
128
|
+
instance.to_xml
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
plan_out = plan_to_xml(doc_class, instance)
|
|
132
|
+
expect(plan_out.sub(" w:name=\"d1\"", "")).to eq(
|
|
133
|
+
interpretive.sub(" name=\"d1\"", "").strip,
|
|
134
|
+
)
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
it "round-trips through the plan parse path" do
|
|
138
|
+
instance = doc_class.new(
|
|
139
|
+
name: "d1",
|
|
140
|
+
body: body_class.new(text: ["hello"]),
|
|
141
|
+
)
|
|
142
|
+
xml = plan_to_xml(doc_class, instance)
|
|
143
|
+
|
|
144
|
+
reparsed = doc_class.from_xml(xml)
|
|
145
|
+
expect(reparsed.name).to eq("d1")
|
|
146
|
+
expect(reparsed.body.text).to eq(["hello"])
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
it "spells default-namespace models unprefixed with xmlns on the root" do
|
|
150
|
+
instance = default_ns_model.new(name: "plain")
|
|
151
|
+
|
|
152
|
+
out = plan_to_xml(default_ns_model, instance)
|
|
153
|
+
expect(out).to include(%(xmlns="http://example.com/gate2/default"))
|
|
154
|
+
expect(out).to include("<root")
|
|
155
|
+
expect(out).not_to include("xmlns:xmlns")
|
|
156
|
+
end
|
|
157
|
+
end
|
|
@@ -59,7 +59,8 @@ RSpec.describe "XML plan fast path: type-namespaced attribute rows" do
|
|
|
59
59
|
|
|
60
60
|
expect(plan).not_to be_nil
|
|
61
61
|
ns_row = plan[:tree][:attributes].find { |a| a[:name] == "val" }
|
|
62
|
-
expect(ns_row[:ns]).to
|
|
62
|
+
expect(ns_row[:ns]).to include(exact: "http://example.com/type-ns/wml")
|
|
63
|
+
expect(ns_row[:ns][:prefix]).to eq("w")
|
|
63
64
|
end
|
|
64
65
|
|
|
65
66
|
it "binds the attribute from the type's namespace" do
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "spec_helper"
|
|
4
|
+
|
|
5
|
+
RSpec.describe "XML plan fast path: map_any_element catch-all" do
|
|
6
|
+
# leptris#1552: the wildcard child row rides a two-pass walk where
|
|
7
|
+
# named rows take precedence; the hydrator bridges the unclaimed
|
|
8
|
+
# remainder in document order and casts it through the attribute —
|
|
9
|
+
# the interpretive pass's contract for map_any_element.
|
|
10
|
+
|
|
11
|
+
let(:host_class) do
|
|
12
|
+
klass = Class.new(Lutaml::Model::Serializable) do
|
|
13
|
+
attribute :title, :string
|
|
14
|
+
attribute :others, :string, collection: true
|
|
15
|
+
|
|
16
|
+
xml do
|
|
17
|
+
element "host"
|
|
18
|
+
map_element "title", to: :title
|
|
19
|
+
map_any_element to: :others
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
stub_const("Gate4Any::Host", klass)
|
|
23
|
+
klass
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
let(:typed_class) do
|
|
27
|
+
item = Class.new(Lutaml::Model::Serializable) do
|
|
28
|
+
attribute :content, :string
|
|
29
|
+
|
|
30
|
+
xml do
|
|
31
|
+
element "item"
|
|
32
|
+
map_content to: :content
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
stub_const("Gate4Any::Item", item)
|
|
36
|
+
model = Class.new(Lutaml::Model::Serializable) do
|
|
37
|
+
attribute :label, :string
|
|
38
|
+
attribute :items, Gate4Any::Item, collection: true
|
|
39
|
+
|
|
40
|
+
xml do
|
|
41
|
+
element "bag"
|
|
42
|
+
map_element "label", to: :label
|
|
43
|
+
map_any_element to: :items
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
stub_const("Gate4Any::Bag", model)
|
|
47
|
+
model
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
around do |example|
|
|
51
|
+
old = Lutaml::Model::Config.instance.xml_plan_fast_path
|
|
52
|
+
Lutaml::Model::Config.instance.xml_plan_fast_path = true
|
|
53
|
+
Lutaml::Model::Config.with_adapter(xml: :leptris) { example.run }
|
|
54
|
+
ensure
|
|
55
|
+
Lutaml::Model::Config.instance.xml_plan_fast_path = old
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Members flow through the attribute's cast exactly as the
|
|
59
|
+
# interpretive pass does: String attributes stringify the wrapper
|
|
60
|
+
# (both paths identically), Serializable attributes build instances.
|
|
61
|
+
def member_shapes(elements)
|
|
62
|
+
elements.map { |e| e.class.name.split("::").last }
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
it "captures unclaimed children in document order" do
|
|
66
|
+
doc = "<host><title>t</title><extra1>a</extra1>" \
|
|
67
|
+
"<extra2>b</extra2></host>"
|
|
68
|
+
|
|
69
|
+
parsed = host_class.from_xml(doc)
|
|
70
|
+
expect(parsed.title).to eq("t")
|
|
71
|
+
expect(parsed.others.size).to eq(2)
|
|
72
|
+
expect(member_shapes(parsed.others)).to eq(%w[String String])
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
it "captures typed members as model instances" do
|
|
76
|
+
doc = "<bag><label>L</label><item>one</item><item>two</item></bag>"
|
|
77
|
+
|
|
78
|
+
parsed = typed_class.from_xml(doc)
|
|
79
|
+
expect(parsed.label).to eq("L")
|
|
80
|
+
expect(parsed.items.map(&:content)).to eq(%w[one two])
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
it "matches the interpretive path's capture" do
|
|
84
|
+
doc = "<host><title>t</title><extra1>a</extra1><extra2>b</extra2></host>"
|
|
85
|
+
|
|
86
|
+
interpretive = Lutaml::Model::Config.with_adapter(xml: :nokogiri) do
|
|
87
|
+
host_class.from_xml(doc)
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
parsed = host_class.from_xml(doc)
|
|
91
|
+
expect(parsed.others.size).to eq(interpretive.others.size)
|
|
92
|
+
expect(member_shapes(parsed.others)).to eq(
|
|
93
|
+
member_shapes(interpretive.others).map { |n| n.sub("Nokogiri", "Leptris") },
|
|
94
|
+
)
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
it "compiles under engines without wildcard rows to nil" do
|
|
98
|
+
allow(Lutaml::Xml::Leptris).to receive(:wildcard_rows_compatible?)
|
|
99
|
+
.and_return(false)
|
|
100
|
+
|
|
101
|
+
expect(Lutaml::Xml::Leptris::PlanCompiler.compile(
|
|
102
|
+
host_class, Lutaml::Model::Config.default_register
|
|
103
|
+
)).to be_nil
|
|
104
|
+
end
|
|
105
|
+
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
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.96
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Ribose Inc.
|
|
@@ -207,6 +207,7 @@ files:
|
|
|
207
207
|
- docs/_guides/creating-xsd.adoc
|
|
208
208
|
- docs/_guides/custom-methods.adoc
|
|
209
209
|
- docs/_guides/document-validation.adoc
|
|
210
|
+
- docs/_guides/grammar-backed-formats.adoc
|
|
210
211
|
- docs/_guides/index.adoc
|
|
211
212
|
- docs/_guides/jsonld-serialization.adoc
|
|
212
213
|
- docs/_guides/keyvalue-serialization.adoc
|
|
@@ -1811,6 +1812,7 @@ files:
|
|
|
1811
1812
|
- spec/lutaml/model/serializable_instantiate_spec.rb
|
|
1812
1813
|
- spec/lutaml/model/serializable_spec.rb
|
|
1813
1814
|
- spec/lutaml/model/serializable_validation_spec.rb
|
|
1815
|
+
- spec/lutaml/model/serialize/attribute_definition_block_form_spec.rb
|
|
1814
1816
|
- spec/lutaml/model/serialize/builder_spec.rb
|
|
1815
1817
|
- spec/lutaml/model/serialize_perf_guard_spec.rb
|
|
1816
1818
|
- spec/lutaml/model/services/transformer_spec.rb
|
|
@@ -1986,10 +1988,12 @@ files:
|
|
|
1986
1988
|
- spec/lutaml/xml/pipeline_integration_spec.rb
|
|
1987
1989
|
- spec/lutaml/xml/plain_model_accessors_spec.rb
|
|
1988
1990
|
- spec/lutaml/xml/plan_fast_path_spec.rb
|
|
1991
|
+
- spec/lutaml/xml/plan_namespaced_serialize_spec.rb
|
|
1989
1992
|
- spec/lutaml/xml/plan_ns_less_document_spec.rb
|
|
1990
1993
|
- spec/lutaml/xml/plan_partition_spec.rb
|
|
1991
1994
|
- spec/lutaml/xml/plan_type_ns_attribute_spec.rb
|
|
1992
1995
|
- spec/lutaml/xml/plan_walk_spec.rb
|
|
1996
|
+
- spec/lutaml/xml/plan_wildcard_any_spec.rb
|
|
1993
1997
|
- spec/lutaml/xml/prefix_control_spec.rb
|
|
1994
1998
|
- spec/lutaml/xml/register_spec.rb
|
|
1995
1999
|
- spec/lutaml/xml/reusable_mapping_spec.rb
|