lutaml-model 0.8.56 → 0.8.106
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/scripts/test_installed_gem.rb +58 -0
- data/.github/workflows/downstream-performance.yml +28 -3
- data/.github/workflows/js-pr-check.yml +4 -0
- data/.github/workflows/js-sync.yml +4 -0
- data/.github/workflows/opal.yml +9 -0
- data/.github/workflows/rake.yml +6 -41
- data/.github/workflows/release.yml +41 -0
- data/Gemfile +17 -3
- data/bench/bench_alloc_audit.rb +56 -0
- data/bench/bench_common.rb +11 -0
- data/bench/bench_merge_attempts.rb +49 -0
- data/docs/_guides/gem-integration.adoc +85 -0
- data/docs/_guides/grammar-backed-formats.adoc +69 -0
- data/docs/_guides/index.adoc +8 -1
- data/docs/_guides/native-engines.adoc +23 -5
- data/docs/_guides/schema-generation.adoc +25 -0
- data/docs/_guides/type-registers-contexts.adoc +126 -0
- data/lib/compat/opal/lutaml_model_boot.rb +7 -5
- data/lib/lutaml/json/adapter/yeptris_adapter.rb +24 -1
- data/lib/lutaml/key_value/data_model/element.rb +5 -1
- data/lib/lutaml/key_value/mapping_rule.rb +7 -0
- data/lib/lutaml/key_value/transform.rb +29 -24
- data/lib/lutaml/key_value/transformation/collection_serializer.rb +12 -29
- data/lib/lutaml/key_value/transformation/value_serializer.rb +29 -6
- data/lib/lutaml/key_value/transformation.rb +64 -116
- data/lib/lutaml/model/adapter_resolver.rb +7 -3
- data/lib/lutaml/model/attribute.rb +74 -15
- data/lib/lutaml/model/cached_type_resolver.rb +13 -6
- data/lib/lutaml/model/configuration.rb +13 -0
- data/lib/lutaml/model/mapping/mapping.rb +13 -0
- data/lib/lutaml/model/mapping/mapping_rule.rb +6 -1
- data/lib/lutaml/model/render_policy.rb +26 -8
- data/lib/lutaml/model/schema/base_schema.rb +15 -0
- data/lib/lutaml/model/schema/generator/definition.rb +52 -8
- data/lib/lutaml/model/schema/generator/definitions_collection.rb +35 -14
- data/lib/lutaml/model/schema.rb +4 -2
- data/lib/lutaml/model/serializable.rb +33 -0
- data/lib/lutaml/model/serialize/attribute_definition.rb +196 -2
- data/lib/lutaml/model/serialize/builder.rb +22 -0
- data/lib/lutaml/model/serialize/conversion_caching.rb +5 -0
- data/lib/lutaml/model/serialize/format_conversion.rb +49 -41
- data/lib/lutaml/model/serialize/initialization.rb +54 -7
- data/lib/lutaml/model/serialize/model_import.rb +60 -0
- data/lib/lutaml/model/serialize.rb +14 -6
- data/lib/lutaml/model/type/time.rb +6 -0
- data/lib/lutaml/model/type/value.rb +9 -7
- data/lib/lutaml/model/type_resolver.rb +7 -5
- data/lib/lutaml/model/utils.rb +18 -5
- data/lib/lutaml/model/version.rb +1 -1
- data/lib/lutaml/model.rb +7 -0
- data/lib/lutaml/xml/adapter/xml_parser.rb +5 -2
- data/lib/lutaml/xml/adapter_loader.rb +5 -2
- data/lib/lutaml/xml/format.rb +18 -16
- data/lib/lutaml/xml/leptris/native_emission.rb +187 -0
- data/lib/lutaml/xml/leptris/plan_compiler.rb +650 -0
- data/lib/lutaml/xml/leptris/plan_hydrator.rb +748 -0
- data/lib/lutaml/xml/leptris/plan_order.rb +55 -0
- data/lib/lutaml/xml/leptris/plan_serializer.rb +702 -0
- data/lib/lutaml/xml/leptris/plan_walk.rb +36 -0
- data/lib/lutaml/xml/leptris.rb +46 -0
- data/lib/lutaml/xml/mapping.rb +71 -18
- data/lib/lutaml/xml/model_transform.rb +231 -8
- data/lib/lutaml/xml/namespace.rb +18 -8
- data/lib/lutaml/xml/namespace_collector.rb +17 -1
- data/lib/lutaml/xml/namespace_needs.rb +77 -49
- data/lib/lutaml/xml/parse_session.rb +7 -19
- data/lib/lutaml/xml/schema/xsd/schema.rb +2 -1
- data/lib/lutaml/xml/serialization/format_conversion.rb +5 -1
- data/lib/lutaml/xml/transformation/ordered_applier.rb +21 -15
- data/lib/lutaml/xml/transformation.rb +15 -1
- data/lib/lutaml/xml/type/serializers.rb +8 -1
- data/lib/lutaml/yaml/adapter/yeptris_adapter.rb +34 -2
- data/lib/tasks/performance_probe_plan_walk.rb +1 -1
- data/lutaml-model.gemspec +7 -4
- data/spec/lutaml/json/yeptris_adapter_spec.rb +7 -2
- data/spec/lutaml/key_value/transformation/value_serializer_spec.rb +9 -5
- data/spec/lutaml/key_value/transformation_spec.rb +87 -21
- data/spec/lutaml/model/any_element_mapping_spec.rb +41 -0
- data/spec/lutaml/model/collection_validation_spec.rb +265 -0
- data/spec/lutaml/model/desc_spec.rb +50 -0
- data/spec/lutaml/model/late_attribute_declaration_spec.rb +35 -0
- data/spec/lutaml/model/model_import_reentry_spec.rb +46 -0
- data/spec/lutaml/model/opal_smoke_spec.rb +17 -0
- data/spec/lutaml/model/schema/generate_many_spec.rb +61 -0
- data/spec/lutaml/model/schema/generation_regressions_spec.rb +139 -0
- data/spec/lutaml/model/schema/json_schema_spec.rb +1 -13
- data/spec/lutaml/model/schema/yaml_schema_spec.rb +0 -11
- data/spec/lutaml/model/serialize/attribute_definition_block_form_spec.rb +244 -0
- data/spec/lutaml/model/store_spec.rb +6 -2
- data/spec/lutaml/model/unregistered_context_resolution_spec.rb +34 -0
- data/spec/lutaml/model/writer_assigned_attribute_spec.rb +62 -0
- data/spec/lutaml/model/yeptris_error_parity_spec.rb +95 -0
- data/spec/lutaml/xml/adapter/empty_document_diagnostics_spec.rb +50 -0
- data/spec/lutaml/xml/adapter/over_cap_parse_spec.rb +105 -0
- data/spec/lutaml/xml/adapter_loader_spec.rb +14 -0
- data/spec/lutaml/xml/adapter_metadata_default_spec.rb +16 -0
- data/spec/lutaml/xml/attribute_ambiguity_surface_spec.rb +5 -0
- data/spec/lutaml/xml/clear_parse_state_spec.rb +6 -1
- data/spec/lutaml/xml/compiled_rule_namespace_spec.rb +5 -0
- data/spec/lutaml/xml/cross_register_plan_spec.rb +75 -0
- data/spec/lutaml/xml/doubly_defined_namespace_spec.rb +16 -8
- data/spec/lutaml/xml/element_order_tracking_spec.rb +37 -0
- data/spec/lutaml/xml/leptris_string_encoding_spec.rb +65 -0
- data/spec/lutaml/xml/opal_xml_spec.rb +47 -0
- data/spec/lutaml/xml/parse_dom_retention_spec.rb +34 -0
- data/spec/lutaml/xml/plan_fast_path_spec.rb +122 -14
- data/spec/lutaml/xml/plan_namespaced_serialize_spec.rb +157 -0
- data/spec/lutaml/xml/plan_ns_less_document_spec.rb +86 -0
- data/spec/lutaml/xml/plan_partition_spec.rb +1 -1
- data/spec/lutaml/xml/plan_type_ns_attribute_spec.rb +131 -0
- data/spec/lutaml/xml/plan_uri_exact_dispatch_spec.rb +287 -0
- data/spec/lutaml/xml/plan_walk_spec.rb +6 -6
- data/spec/lutaml/xml/plan_wildcard_any_spec.rb +96 -0
- data/spec/lutaml/yaml/yeptris_adapter_spec.rb +29 -0
- data/spec/person_spec.rb +8 -0
- metadata +48 -17
- data/lib/lutaml/xml/plan_compiler.rb +0 -332
- data/lib/lutaml/xml/plan_hydrator.rb +0 -367
- data/lib/lutaml/xml/plan_order.rb +0 -53
- data/lib/lutaml/xml/plan_serializer.rb +0 -252
- data/lib/lutaml/xml/plan_walk.rb +0 -30
|
@@ -33,15 +33,18 @@ RSpec.describe "XML plan fast path" do
|
|
|
33
33
|
around do |example|
|
|
34
34
|
old = Lutaml::Model::Config.instance.xml_plan_fast_path
|
|
35
35
|
Lutaml::Model::Config.instance.xml_plan_fast_path = true
|
|
36
|
+
Lutaml::Model::Config.instance.xml_plan_ordered_path = true
|
|
36
37
|
example.run
|
|
37
38
|
ensure
|
|
38
39
|
Lutaml::Model::Config.instance.xml_plan_fast_path = old
|
|
40
|
+
Lutaml::Model::Config.instance.xml_plan_ordered_path = false
|
|
39
41
|
end
|
|
40
42
|
|
|
41
43
|
# Real-corpus crash (niso-jats): sec-in-sec nesting is a
|
|
42
|
-
# self-referential model — compile must
|
|
43
|
-
#
|
|
44
|
-
|
|
44
|
+
# self-referential model — compile must not recurse to
|
|
45
|
+
# SystemStackError. The cycle point defers the child ROW (per-node
|
|
46
|
+
# interpretive islands) instead of opting the model out.
|
|
47
|
+
it "defers the self-referential child row instead of recursing" do
|
|
45
48
|
node = Class.new(Lutaml::Model::Serializable) do
|
|
46
49
|
attribute :text, :string
|
|
47
50
|
xml do
|
|
@@ -57,17 +60,21 @@ RSpec.describe "XML plan fast path" do
|
|
|
57
60
|
end
|
|
58
61
|
stub_const("PlanFastPath::SelfRef", node)
|
|
59
62
|
|
|
60
|
-
|
|
63
|
+
plan = Lutaml::Xml::Leptris::PlanCompiler.compile(node, :default)
|
|
64
|
+
expect(plan).not_to be_nil
|
|
65
|
+
expect(plan[:rows].map { |r| r[2] }).to include(:child_deferred)
|
|
61
66
|
|
|
62
67
|
xml = "<n>#{'<n>t</n>' * 200}</n>"
|
|
63
68
|
parsed = node.from_xml(xml)
|
|
64
69
|
expect(parsed).to be_a(node)
|
|
65
70
|
end
|
|
66
71
|
|
|
67
|
-
# lutaml-model#744:
|
|
68
|
-
# type-namespaced attribute
|
|
69
|
-
#
|
|
70
|
-
|
|
72
|
+
# lutaml-model#744/#758: a local name claimed by BOTH a
|
|
73
|
+
# type-namespaced attribute row and a plain row stays interpretive —
|
|
74
|
+
# the walk's ns form :none matches any qualification, so the plain
|
|
75
|
+
# row would capture the qualified spelling too. The interpretive
|
|
76
|
+
# matcher dispatches the spellings correctly.
|
|
77
|
+
it "opts shared-local-name attribute claims out to the interpretive path" do
|
|
71
78
|
xmi_ns = Class.new(Lutaml::Xml::Namespace) do
|
|
72
79
|
uri "http://www.omg.org/spec/XMI/20131001"
|
|
73
80
|
prefix_default "xmi"
|
|
@@ -94,7 +101,7 @@ RSpec.describe "XML plan fast path" do
|
|
|
94
101
|
|
|
95
102
|
expect(parsed.xmi_type).to eq("uml:Parameter")
|
|
96
103
|
expect(parsed.classifier_type).to eq("EAnone_void")
|
|
97
|
-
expect(Lutaml::Xml::PlanCompiler.compile(parameter, :default)).to be_nil
|
|
104
|
+
expect(Lutaml::Xml::Leptris::PlanCompiler.compile(parameter, :default)).to be_nil
|
|
98
105
|
end
|
|
99
106
|
|
|
100
107
|
it "is on by default and can be disabled" do
|
|
@@ -156,7 +163,7 @@ RSpec.describe "XML plan fast path" do
|
|
|
156
163
|
end
|
|
157
164
|
end
|
|
158
165
|
|
|
159
|
-
expect(Lutaml::Xml::PlanCompiler.compile(klass,
|
|
166
|
+
expect(Lutaml::Xml::Leptris::PlanCompiler.compile(klass,
|
|
160
167
|
Lutaml::Model::Config.default_register)).not_to be_nil
|
|
161
168
|
expect(klass.from_xml("<doc><note>hi</note></doc>").note).to eq("HI")
|
|
162
169
|
end
|
|
@@ -223,14 +230,14 @@ RSpec.describe "XML plan fast path" do
|
|
|
223
230
|
end
|
|
224
231
|
end
|
|
225
232
|
|
|
226
|
-
expect(Lutaml::Xml::PlanCompiler.compile(klass,
|
|
233
|
+
expect(Lutaml::Xml::Leptris::PlanCompiler.compile(klass,
|
|
227
234
|
Lutaml::Model::Config.default_register)).not_to be_nil
|
|
228
235
|
parsed = klass.from_xml("<doc><note>hi</note></doc>")
|
|
229
236
|
expect(parsed.tgt.note).to eq("hi")
|
|
230
237
|
end
|
|
231
238
|
|
|
232
239
|
it "routes a single collection row natively (hybrid routing)" do
|
|
233
|
-
plan = Lutaml::Xml::PlanCompiler.compile(
|
|
240
|
+
plan = Lutaml::Xml::Leptris::PlanCompiler.compile(
|
|
234
241
|
Class.new(Lutaml::Model::Serializable) do
|
|
235
242
|
attribute :xs, :string, collection: true
|
|
236
243
|
attribute :name, :string
|
|
@@ -400,7 +407,7 @@ RSpec.describe "XML plan fast path" do
|
|
|
400
407
|
map_element "para", to: :para
|
|
401
408
|
end
|
|
402
409
|
end
|
|
403
|
-
plan = Lutaml::Xml::PlanCompiler.compile(
|
|
410
|
+
plan = Lutaml::Xml::Leptris::PlanCompiler.compile(
|
|
404
411
|
doc_class, Lutaml::Model::Config.default_register
|
|
405
412
|
)
|
|
406
413
|
expect(plan).not_to be_nil
|
|
@@ -623,7 +630,7 @@ RSpec.describe "XML plan fast path" do
|
|
|
623
630
|
expect(req.guidance.map(&:text)).to eq(%w[g1 g2])
|
|
624
631
|
expect(req.purpose.map(&:text)).to eq(["p1"])
|
|
625
632
|
|
|
626
|
-
expect(Lutaml::Xml::PlanCompiler.compile(partitioned,
|
|
633
|
+
expect(Lutaml::Xml::Leptris::PlanCompiler.compile(partitioned,
|
|
627
634
|
:default)).to be_nil
|
|
628
635
|
end
|
|
629
636
|
|
|
@@ -688,4 +695,105 @@ RSpec.describe "XML plan fast path" do
|
|
|
688
695
|
expect(parsed.to_xml).to eq(xml)
|
|
689
696
|
end
|
|
690
697
|
end
|
|
698
|
+
|
|
699
|
+
# lutaml-model#847: the plan serializer does not emit namespace
|
|
700
|
+
# declarations — namespaced models must serialize through the
|
|
701
|
+
# interpretive writer even standalone, or the element xmlns is lost.
|
|
702
|
+
it "falls back to the interpretive writer for namespaced models" do
|
|
703
|
+
ns = Class.new(Lutaml::Xml::Namespace) do
|
|
704
|
+
uri "http://example.com/ord847"
|
|
705
|
+
end
|
|
706
|
+
stub_const("PlanFastPath::Ord847Ns", ns)
|
|
707
|
+
klass = Class.new(Lutaml::Model::Serializable) do
|
|
708
|
+
attribute :value, :boolean
|
|
709
|
+
xml do
|
|
710
|
+
element "updateFields"
|
|
711
|
+
namespace PlanFastPath::Ord847Ns
|
|
712
|
+
map_attribute "val", to: :value
|
|
713
|
+
end
|
|
714
|
+
end
|
|
715
|
+
stub_const("PlanFastPath::UpdateFields847", klass)
|
|
716
|
+
|
|
717
|
+
expect(klass.new(value: false).to_xml).to include("http://example.com/ord847")
|
|
718
|
+
plan = Lutaml::Xml::Leptris::PlanCompiler.compile(
|
|
719
|
+
klass, Lutaml::Model::Config.default_register
|
|
720
|
+
)
|
|
721
|
+
expect(plan[:namespaced]).to be(true)
|
|
722
|
+
end
|
|
723
|
+
|
|
724
|
+
# lutaml-model#850: a sole-claimant attribute rule binds any
|
|
725
|
+
# qualification on the plan path (#754/#790 parity with the
|
|
726
|
+
# interpretive matcher) — the qualified spelling is read off the
|
|
727
|
+
# source node when the exact (URI, local) walk capture misses.
|
|
728
|
+
it "binds qualified spellings to sole-claimant attribute rules" do
|
|
729
|
+
ns = Class.new(Lutaml::Xml::Namespace) do
|
|
730
|
+
uri "http://example.com/w"
|
|
731
|
+
prefix_default "w"
|
|
732
|
+
end
|
|
733
|
+
stub_const("PlanFastPath::W850Ns", ns)
|
|
734
|
+
klass = Class.new(Lutaml::Model::Serializable) do
|
|
735
|
+
attribute :value, :boolean
|
|
736
|
+
xml do
|
|
737
|
+
element "b"
|
|
738
|
+
namespace PlanFastPath::W850Ns
|
|
739
|
+
map_attribute "val", to: :value
|
|
740
|
+
end
|
|
741
|
+
end
|
|
742
|
+
stub_const("PlanFastPath::Sole850", klass)
|
|
743
|
+
|
|
744
|
+
doc = %(<w:b xmlns:w="http://example.com/w" w:val="false"/>)
|
|
745
|
+
expect(klass.from_xml(doc).value).to be(false)
|
|
746
|
+
expect(klass.from_xml(
|
|
747
|
+
%(<b xmlns="http://example.com/w" val="false"/>),
|
|
748
|
+
).value).to be(false)
|
|
749
|
+
end
|
|
750
|
+
|
|
751
|
+
# lutaml-model#856: a deferred content row with no captured runs
|
|
752
|
+
# must still mark its attribute set — the interpretive pipeline
|
|
753
|
+
# marks every applied rule (model_transform apply). Without the
|
|
754
|
+
# mark, the serializer's default-suppression drops a mapped reader
|
|
755
|
+
# that derives content from other attributes.
|
|
756
|
+
it "renders derived content readers when no text was captured" do
|
|
757
|
+
klass = Class.new(Lutaml::Model::Serializable) do
|
|
758
|
+
attribute :content, :string
|
|
759
|
+
attribute :revision_date, :string
|
|
760
|
+
attribute :draft, :string
|
|
761
|
+
|
|
762
|
+
xml do
|
|
763
|
+
element "version"
|
|
764
|
+
map_content to: :content
|
|
765
|
+
map_element "revision-date", to: :revision_date
|
|
766
|
+
map_element "draft", to: :draft
|
|
767
|
+
end
|
|
768
|
+
|
|
769
|
+
define_method(:content) do
|
|
770
|
+
return @content if @content && !@content.empty?
|
|
771
|
+
|
|
772
|
+
parts = [@draft, @revision_date].compact
|
|
773
|
+
case parts.size
|
|
774
|
+
when 2 then "#{@draft} (#{@revision_date})"
|
|
775
|
+
when 1 then parts.first
|
|
776
|
+
end
|
|
777
|
+
end
|
|
778
|
+
end
|
|
779
|
+
stub_const("PlanFastPath::DerivedContent856", klass)
|
|
780
|
+
|
|
781
|
+
parsed = nil
|
|
782
|
+
Lutaml::Model::Config.with_adapter(xml: :leptris) do
|
|
783
|
+
parsed = klass.from_xml(
|
|
784
|
+
"<version><revision-date>1994-01-01</revision-date><draft>PD</draft></version>",
|
|
785
|
+
)
|
|
786
|
+
expect(parsed.using_default?(:content)).to be(false)
|
|
787
|
+
expect(parsed.to_xml).to eq(
|
|
788
|
+
"<version><revision-date>1994-01-01</revision-date>" \
|
|
789
|
+
"<draft>PD</draft>PD (1994-01-01)</version>",
|
|
790
|
+
)
|
|
791
|
+
end
|
|
792
|
+
Lutaml::Model::Config.with_adapter(xml: :nokogiri) do
|
|
793
|
+
expect(parsed.to_xml).to eq(
|
|
794
|
+
"<version><revision-date>1994-01-01</revision-date>" \
|
|
795
|
+
"<draft>PD</draft>PD (1994-01-01)</version>",
|
|
796
|
+
)
|
|
797
|
+
end
|
|
798
|
+
end
|
|
691
799
|
end
|
|
@@ -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
|
|
@@ -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
|
|
@@ -37,7 +37,7 @@ RSpec.describe "Plan fast path with when_attribute partitions" do
|
|
|
37
37
|
end
|
|
38
38
|
|
|
39
39
|
it "compiles the partitioned model with row tags" do
|
|
40
|
-
plan = Lutaml::Xml::PlanCompiler.compile(
|
|
40
|
+
plan = Lutaml::Xml::Leptris::PlanCompiler.compile(
|
|
41
41
|
model, Lutaml::Model::Config.default_register
|
|
42
42
|
)
|
|
43
43
|
expect(plan).not_to be_nil
|
|
@@ -0,0 +1,131 @@
|
|
|
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 include(exact: "http://example.com/type-ns/wml")
|
|
63
|
+
expect(ns_row[:ns][:prefix]).to eq("w")
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
it "binds the attribute from the type's namespace" do
|
|
67
|
+
doc = %(<w:keepNext xmlns:w="http://example.com/type-ns/wml" w:val="yes"/>)
|
|
68
|
+
|
|
69
|
+
expect(klass.from_xml(doc).val).to eq("yes")
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# The interpretive matcher refuses an ambiguous local name outright:
|
|
73
|
+
# a foreign spelling alongside the canonical one binds nothing. The
|
|
74
|
+
# exact-URI row mirrors that refusal (lutaml-model#744).
|
|
75
|
+
it "refuses to bind when a foreign spelling accompanies the canonical one" do
|
|
76
|
+
doc = '<w:keepNext xmlns:w="http://example.com/type-ns/wml" ' \
|
|
77
|
+
'xmlns:f="http://example.com/foreign" val="foreign" w:val="canonical"/>'
|
|
78
|
+
|
|
79
|
+
expect(klass.from_xml(doc).val).to be_nil
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# Interpretive parity: the plan path resolves exactly as the
|
|
83
|
+
# interpretive matcher (nokogiri) does on every spelling shape.
|
|
84
|
+
it "matches the interpretive path's resolution on all spelling shapes" do
|
|
85
|
+
canonical = %(<w:keepNext xmlns:w="http://example.com/type-ns/wml" w:val="yes"/>)
|
|
86
|
+
ambiguous = '<w:keepNext xmlns:w="http://example.com/type-ns/wml" ' \
|
|
87
|
+
'xmlns:f="http://example.com/foreign" val="foreign" w:val="canonical"/>'
|
|
88
|
+
foreign_only = '<w:keepNext xmlns:f="http://example.com/foreign" ' \
|
|
89
|
+
'xmlns:w="http://example.com/type-ns/wml" val="foreign"/>'
|
|
90
|
+
|
|
91
|
+
[canonical, ambiguous, foreign_only].each do |doc|
|
|
92
|
+
interpretive = Lutaml::Model::Config.with_adapter(xml: :nokogiri) do
|
|
93
|
+
klass.from_xml(doc).val
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
expect(klass.from_xml(doc).val).to eq(interpretive)
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
# A type declaring alias URI families stays interpretive: the plan row
|
|
101
|
+
# holds one exact URI and non-sole-claimant alias families have no
|
|
102
|
+
# plan-side resolution order.
|
|
103
|
+
it "keeps alias-declaring types on the interpretive path" do
|
|
104
|
+
aliased_ns = Class.new(Lutaml::Xml::Namespace) do
|
|
105
|
+
uri "http://example.com/type-ns/canonical"
|
|
106
|
+
uri_aliases "http://example.com/type-ns/alias"
|
|
107
|
+
end
|
|
108
|
+
stub_const("PlanTypeNsAttr::AliasedNs", aliased_ns)
|
|
109
|
+
type = Class.new(Lutaml::Model::Type::String) do
|
|
110
|
+
include Lutaml::Xml::Type::Configurable
|
|
111
|
+
|
|
112
|
+
xml do
|
|
113
|
+
namespace PlanTypeNsAttr::AliasedNs
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
stub_const("PlanTypeNsAttr::AliasedValType", type)
|
|
117
|
+
model = Class.new(Lutaml::Model::Serializable) do
|
|
118
|
+
attribute :val, PlanTypeNsAttr::AliasedValType
|
|
119
|
+
|
|
120
|
+
xml do
|
|
121
|
+
element "keepNext"
|
|
122
|
+
map_attribute "val", to: :val
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
stub_const("PlanTypeNsAttr::AliasedKeepNext", model)
|
|
126
|
+
|
|
127
|
+
expect(Lutaml::Xml::Leptris::PlanCompiler.compile(
|
|
128
|
+
model, Lutaml::Model::Config.default_register
|
|
129
|
+
)).to be_nil
|
|
130
|
+
end
|
|
131
|
+
end
|