moxml 0.6.3 → 0.6.4
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/_pages/divergences.adoc +31 -0
- data/lib/moxml/adapter/leptris.rb +216 -21
- data/lib/moxml/context.rb +4 -5
- data/lib/moxml/version.rb +1 -1
- data/lib/moxml/xsd.rb +436 -2
- data/spec/moxml/adapter/leptris_children_address_spec.rb +171 -0
- data/spec/moxml/adapter/leptris_spec.rb +0 -6
- data/spec/moxml/xsd_composed_spec.rb +183 -0
- metadata +4 -3
- data/spec/moxml/adapter/leptris_children_columnar_spec.rb +0 -146
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "spec_helper"
|
|
4
|
+
require "fileutils"
|
|
5
|
+
require "tmpdir"
|
|
6
|
+
|
|
7
|
+
# moxml#522: composed-schema strict admission. The facade owns the
|
|
8
|
+
# composition — it resolves the instance's xsi:schemaLocation hints
|
|
9
|
+
# against the schema's own path, walks the schema graph
|
|
10
|
+
# (include/import/redefine) for the unit closure, and dismisses
|
|
11
|
+
# engine "not declared (strict)" rows the composed declaration index
|
|
12
|
+
# refutes — global declarations AND attributeGroup members. The
|
|
13
|
+
# engine admission gap itself is RESOLVED in leptris 1.9.409
|
|
14
|
+
# (leptris#1964 — register entry D-23). The 1.9.409 namespace-aware
|
|
15
|
+
# particle wave carries an upstream schG2 lexical regression
|
|
16
|
+
# (leptris#1976, residual leptris#1979 — register entry D-24); its rows are lexical, not
|
|
17
|
+
# admission rows, and pass through untouched here.
|
|
18
|
+
RSpec.describe "Moxml::XSD::Schema composed-schema validation (#522)" do
|
|
19
|
+
let(:ctx) { Moxml.new(:leptris) }
|
|
20
|
+
let(:main_xsd) do
|
|
21
|
+
<<~XSD
|
|
22
|
+
<?xml version="1.0"?>
|
|
23
|
+
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">
|
|
24
|
+
<xsd:attribute name="att1" type="xsd:string"/>
|
|
25
|
+
<xsd:element name="doc">
|
|
26
|
+
<xsd:complexType>
|
|
27
|
+
<xsd:attributeGroup ref="main"/>
|
|
28
|
+
</xsd:complexType>
|
|
29
|
+
</xsd:element>
|
|
30
|
+
<xsd:attributeGroup name="main">
|
|
31
|
+
<xsd:attribute ref="att1"/>
|
|
32
|
+
<xsd:anyAttribute namespace="##any"/>
|
|
33
|
+
</xsd:attributeGroup>
|
|
34
|
+
</xsd:schema>
|
|
35
|
+
XSD
|
|
36
|
+
end
|
|
37
|
+
# The auxiliary unit: a global declaration the head unit never saw.
|
|
38
|
+
let(:aux_xsd) do
|
|
39
|
+
<<~XSD
|
|
40
|
+
<?xml version="1.0"?>
|
|
41
|
+
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
|
|
42
|
+
targetNamespace="http://aux">
|
|
43
|
+
<xsd:attribute name="ext" type="xsd:string"/>
|
|
44
|
+
</xsd:schema>
|
|
45
|
+
XSD
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# Per-example fixture tempdir: created by the let, removed in the
|
|
49
|
+
# after hook. Plain lets + methods keep the suite ivar-free
|
|
50
|
+
# (house law).
|
|
51
|
+
let(:dir) { Dir.mktmpdir("moxml522") }
|
|
52
|
+
|
|
53
|
+
after { FileUtils.remove_entry(dir) }
|
|
54
|
+
|
|
55
|
+
def unit(name)
|
|
56
|
+
File.join(dir, name)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
before do
|
|
60
|
+
File.write(unit("main.xsd"), main_xsd)
|
|
61
|
+
File.write(unit("aux.xsd"), aux_xsd)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
it "unions the instance's hint documents into the validation" do
|
|
65
|
+
schema = ctx.xsd_file(unit("main.xsd"))
|
|
66
|
+
doc = ctx.parse(
|
|
67
|
+
'<doc xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" ' \
|
|
68
|
+
'xmlns:e="http://aux" xsi:noNamespaceSchemaLocation="main.xsd" ' \
|
|
69
|
+
'xsi:schemaLocation="http://aux aux.xsd" att1="x" e:ext="y"/>',
|
|
70
|
+
)
|
|
71
|
+
expect(schema.validate(doc)).to be_empty
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
it "dismisses the engine's not-declared rows the union refutes" do
|
|
75
|
+
# Without the union the engine reports the aux attribute as
|
|
76
|
+
# undeclared; with it, the composed declaration index refutes
|
|
77
|
+
# the row. The engine may carry either the attribute-level row
|
|
78
|
+
# or the element-level root row (mixed-tns mis-admission) —
|
|
79
|
+
# both are refutable.
|
|
80
|
+
schema = ctx.xsd_file(unit("main.xsd"))
|
|
81
|
+
doc = ctx.parse(
|
|
82
|
+
'<doc xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" ' \
|
|
83
|
+
'xmlns:e="http://aux" xsi:noNamespaceSchemaLocation="main.xsd" ' \
|
|
84
|
+
'xsi:schemaLocation="http://aux aux.xsd" att1="x" e:ext="y">text</doc>',
|
|
85
|
+
)
|
|
86
|
+
errors = schema.validate(doc)
|
|
87
|
+
expect(errors.map(&:message)).not_to include(
|
|
88
|
+
/not declared \(strict\)/,
|
|
89
|
+
)
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
it "keeps genuinely undeclared attribute rows" do
|
|
93
|
+
schema = ctx.xsd_file(unit("main.xsd"))
|
|
94
|
+
doc = ctx.parse(
|
|
95
|
+
'<doc xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" ' \
|
|
96
|
+
'xsi:noNamespaceSchemaLocation="main.xsd" att1="x" ghost="z"/>',
|
|
97
|
+
)
|
|
98
|
+
# `ghost` is declared nowhere: the strict row (when the engine
|
|
99
|
+
# emits one) must survive the dismissal.
|
|
100
|
+
errors = schema.validate(doc).map(&:message)
|
|
101
|
+
expect(errors.grep_v(/ghost.*not declared/)).to be_empty
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
it "does not build a union for string-compiled schemas" do
|
|
105
|
+
schema = ctx.xsd(main_xsd)
|
|
106
|
+
doc = ctx.parse("<doc att1=\"x\"/>")
|
|
107
|
+
expect(schema.validate(doc)).to be_empty
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# The attgC class (moxml#522): the attribute is a member of an
|
|
111
|
+
# attributeGroup reached through xs:redefine (the body hooks the
|
|
112
|
+
# original with a self-ref — 4.2.2) and an import. The instance
|
|
113
|
+
# carries no xsi hints; the composed unit closure must see the
|
|
114
|
+
# redefined/imported documents through the schema graph alone.
|
|
115
|
+
describe "group-member admission through the schema graph" do
|
|
116
|
+
let(:group_xsd) do
|
|
117
|
+
<<~XSD
|
|
118
|
+
<?xml version="1.0"?>
|
|
119
|
+
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
|
|
120
|
+
xmlns:imp="http://imported">
|
|
121
|
+
<xsd:import namespace="http://imported" schemaLocation="group.imp"/>
|
|
122
|
+
<xsd:redefine schemaLocation="group.red">
|
|
123
|
+
<xsd:attributeGroup name="car">
|
|
124
|
+
<xsd:attributeGroup ref="car"/>
|
|
125
|
+
<xsd:attributeGroup ref="imp:bar"/>
|
|
126
|
+
</xsd:attributeGroup>
|
|
127
|
+
</xsd:redefine>
|
|
128
|
+
<xsd:element name="doc" type="test"/>
|
|
129
|
+
<xsd:complexType name="test">
|
|
130
|
+
<xsd:attributeGroup ref="car"/>
|
|
131
|
+
</xsd:complexType>
|
|
132
|
+
</xsd:schema>
|
|
133
|
+
XSD
|
|
134
|
+
end
|
|
135
|
+
let(:group_red_xsd) do
|
|
136
|
+
<<~XSD
|
|
137
|
+
<?xml version="1.0"?>
|
|
138
|
+
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">
|
|
139
|
+
<xsd:attributeGroup name="car">
|
|
140
|
+
<xsd:attribute name="att1" type="xsd:int"/>
|
|
141
|
+
</xsd:attributeGroup>
|
|
142
|
+
</xsd:schema>
|
|
143
|
+
XSD
|
|
144
|
+
end
|
|
145
|
+
let(:group_imp_xsd) do
|
|
146
|
+
<<~XSD
|
|
147
|
+
<?xml version="1.0"?>
|
|
148
|
+
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
|
|
149
|
+
targetNamespace="http://imported">
|
|
150
|
+
<xsd:attributeGroup name="bar">
|
|
151
|
+
<xsd:attribute name="att4" type="xsd:int"/>
|
|
152
|
+
</xsd:attributeGroup>
|
|
153
|
+
</xsd:schema>
|
|
154
|
+
XSD
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
before do
|
|
158
|
+
File.write(unit("group.xsd"), group_xsd)
|
|
159
|
+
File.write(unit("group.red"), group_red_xsd)
|
|
160
|
+
File.write(unit("group.imp"), group_imp_xsd)
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
it "admits redefined and imported group members on the element" do
|
|
164
|
+
schema = ctx.xsd_file(unit("group.xsd"))
|
|
165
|
+
doc = ctx.parse(
|
|
166
|
+
'<doc xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" ' \
|
|
167
|
+
'xmlns:imp="http://imported" ' \
|
|
168
|
+
'xsi:noNamespaceSchemaLocation="group.xsd" att1="1" imp:att4="2"/>',
|
|
169
|
+
)
|
|
170
|
+
expect(schema.validate(doc)).to be_empty
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
it "keeps rows for attributes no unit of the composition declares" do
|
|
174
|
+
schema = ctx.xsd_file(unit("group.xsd"))
|
|
175
|
+
doc = ctx.parse(
|
|
176
|
+
'<doc xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" ' \
|
|
177
|
+
'xsi:noNamespaceSchemaLocation="group.xsd" att1="1" ghost="z"/>',
|
|
178
|
+
)
|
|
179
|
+
errors = schema.validate(doc).map(&:message)
|
|
180
|
+
expect(errors.grep_v(/ghost.*not declared/)).to be_empty
|
|
181
|
+
end
|
|
182
|
+
end
|
|
183
|
+
end
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: moxml
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.6.
|
|
4
|
+
version: 0.6.4
|
|
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-11-
|
|
11
|
+
date: 2026-11-06 00:00:00.000000000 Z
|
|
12
12
|
dependencies: []
|
|
13
13
|
description: |
|
|
14
14
|
Moxml is a unified XML manipulation library that provides a common API
|
|
@@ -478,7 +478,7 @@ files:
|
|
|
478
478
|
- spec/moxml/adapter/leptris_attribute_packet_spec.rb
|
|
479
479
|
- spec/moxml/adapter/leptris_attribute_snapshot_spec.rb
|
|
480
480
|
- spec/moxml/adapter/leptris_binding_cap_spec.rb
|
|
481
|
-
- spec/moxml/adapter/
|
|
481
|
+
- spec/moxml/adapter/leptris_children_address_spec.rb
|
|
482
482
|
- spec/moxml/adapter/leptris_composed_schema_spec.rb
|
|
483
483
|
- spec/moxml/adapter/leptris_contract_reads_spec.rb
|
|
484
484
|
- spec/moxml/adapter/leptris_crossdoc_replace_spec.rb
|
|
@@ -626,6 +626,7 @@ files:
|
|
|
626
626
|
- spec/moxml/xpath_capabilities_spec.rb
|
|
627
627
|
- spec/moxml/xpath_spec.rb
|
|
628
628
|
- spec/moxml/xsd_builtin_lexical_spec.rb
|
|
629
|
+
- spec/moxml/xsd_composed_spec.rb
|
|
629
630
|
- spec/moxml/xsd_conformance_cases_spec.rb
|
|
630
631
|
- spec/moxml/xsd_engine_meta_spec.rb
|
|
631
632
|
- spec/moxml/xsd_error_contract_spec.rb
|
|
@@ -1,146 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
begin
|
|
4
|
-
require "leptris"
|
|
5
|
-
rescue LoadError
|
|
6
|
-
# Leptris gem not available - skip all specs in this file
|
|
7
|
-
return
|
|
8
|
-
end
|
|
9
|
-
return unless Leptris::XML::Document.respond_to?(:create)
|
|
10
|
-
# The columnar children lane rides the binding's walk_columnar face
|
|
11
|
-
# (leptris-ruby#527/#532); older bindings keep the per-node children
|
|
12
|
-
# crossing, so the routing cannot be pinned there.
|
|
13
|
-
return unless defined?(Leptris::XML::Native) &&
|
|
14
|
-
Leptris::XML::Native.respond_to?(:walk_columnar)
|
|
15
|
-
|
|
16
|
-
require "moxml/adapter/leptris"
|
|
17
|
-
|
|
18
|
-
RSpec.describe "Moxml::Adapter::Leptris columnar children (SOTA B2 walk row)" do
|
|
19
|
-
around do |example|
|
|
20
|
-
Moxml.with_config(:leptris, true, "UTF-8") do
|
|
21
|
-
example.run
|
|
22
|
-
end
|
|
23
|
-
end
|
|
24
|
-
|
|
25
|
-
let(:xml) do
|
|
26
|
-
<<~XML
|
|
27
|
-
<?xml version="1.0"?><r><a x="1">t</a><b/><c><d/></c><tail>z</tail></r>
|
|
28
|
-
XML
|
|
29
|
-
end
|
|
30
|
-
|
|
31
|
-
let(:doc) { Moxml.parse(xml) }
|
|
32
|
-
let(:root) { doc.root }
|
|
33
|
-
let(:root_native) { root.native }
|
|
34
|
-
|
|
35
|
-
# Counts real Native.walk_columnar C crossings inside the block —
|
|
36
|
-
# no stubs, the actual boundary traffic the walk row pays.
|
|
37
|
-
def with_crossing_count
|
|
38
|
-
calls = 0
|
|
39
|
-
tp = TracePoint.new(:c_call) do |t|
|
|
40
|
-
calls += 1 if t.method_id == :walk_columnar
|
|
41
|
-
end
|
|
42
|
-
result = nil
|
|
43
|
-
tp.enable { result = yield }
|
|
44
|
-
[calls, result]
|
|
45
|
-
end
|
|
46
|
-
|
|
47
|
-
describe "the columnar gate" do
|
|
48
|
-
it "face-probes walk_columnar on the binding" do
|
|
49
|
-
expect(Moxml::Adapter::Leptris::COLUMNAR_WALK).to be(true)
|
|
50
|
-
expect(Leptris::XML::Native.respond_to?(:walk_columnar)).to be(true)
|
|
51
|
-
end
|
|
52
|
-
end
|
|
53
|
-
|
|
54
|
-
describe "plain children reads" do
|
|
55
|
-
it "answer natives identical to the native children face" do
|
|
56
|
-
got = Moxml::Adapter::Leptris.children(root_native)
|
|
57
|
-
expected = Leptris::XML::NativeNode
|
|
58
|
-
.instance_method(:children).bind_call(root_native).to_a
|
|
59
|
-
expect(got.size).to eq(expected.size)
|
|
60
|
-
expect(got.map { |n| Moxml::Adapter::Leptris.node_name(n) })
|
|
61
|
-
.to eq(expected.map(&:name))
|
|
62
|
-
got.each_with_index do |native, i|
|
|
63
|
-
# native-cache identity: cached handles ARE the children_read
|
|
64
|
-
# wrappers, so the NodeSet contract holds.
|
|
65
|
-
expect(native).to equal(expected[i])
|
|
66
|
-
end
|
|
67
|
-
end
|
|
68
|
-
|
|
69
|
-
# Parsed-document wrappers mint with Identity::ChildReads above
|
|
70
|
-
# Moxml::Node, so consumer iteration on those wrappers dispatches
|
|
71
|
-
# to the binding's C read layer. The children FACE is pinned by
|
|
72
|
-
# driving it for every node of the cold subtree: one walk_columnar
|
|
73
|
-
# crossing primes the whole map, every deeper face read hits it.
|
|
74
|
-
it "pay ONE columnar crossing for a whole cold subtree" do
|
|
75
|
-
calls, = with_crossing_count do
|
|
76
|
-
walk_face = lambda do |native|
|
|
77
|
-
Moxml::Adapter::Leptris.children(native).each do |child|
|
|
78
|
-
walk_face.call(child)
|
|
79
|
-
end
|
|
80
|
-
end
|
|
81
|
-
walk_face.call(root_native)
|
|
82
|
-
end
|
|
83
|
-
expect(calls).to eq(1)
|
|
84
|
-
end
|
|
85
|
-
|
|
86
|
-
it "serve repeat face reads from the primed map without a crossing" do
|
|
87
|
-
calls, first = with_crossing_count do
|
|
88
|
-
Moxml::Adapter::Leptris.children(root_native)
|
|
89
|
-
end
|
|
90
|
-
expect(calls).to eq(1)
|
|
91
|
-
first_names = first.map { |n| Moxml::Adapter::Leptris.node_name(n) }
|
|
92
|
-
calls2, second = with_crossing_count do
|
|
93
|
-
Moxml::Adapter::Leptris.children(root_native)
|
|
94
|
-
end
|
|
95
|
-
expect(calls2).to eq(0)
|
|
96
|
-
expect(second.map { |n| Moxml::Adapter::Leptris.node_name(n) })
|
|
97
|
-
.to eq(first_names)
|
|
98
|
-
end
|
|
99
|
-
end
|
|
100
|
-
|
|
101
|
-
describe "mutation invalidation" do
|
|
102
|
-
# Read back through the adapter face: the map must never serve a
|
|
103
|
-
# list a mutation has invalidated.
|
|
104
|
-
def face_children_names
|
|
105
|
-
Moxml::Adapter::Leptris.children(root_native)
|
|
106
|
-
.map { |n| Moxml::Adapter::Leptris.node_name(n) }
|
|
107
|
-
end
|
|
108
|
-
|
|
109
|
-
it "sees children added after the map was primed" do
|
|
110
|
-
face_children_names # prime the map
|
|
111
|
-
root.add_child(doc.create_element("late"))
|
|
112
|
-
expect(face_children_names).to include("late")
|
|
113
|
-
end
|
|
114
|
-
|
|
115
|
-
it "sees children removed after the map was primed" do
|
|
116
|
-
face_children_names # prime the map
|
|
117
|
-
root.children.to_a.first.remove
|
|
118
|
-
expect(face_children_names).not_to include("a")
|
|
119
|
-
end
|
|
120
|
-
|
|
121
|
-
it "sees sibling insertions after the map was primed" do
|
|
122
|
-
face_children_names # prime the map
|
|
123
|
-
anchor = root.children.to_a.first
|
|
124
|
-
anchor.add_previous_sibling(doc.create_element("before2"))
|
|
125
|
-
expect(face_children_names).to include("before2")
|
|
126
|
-
end
|
|
127
|
-
|
|
128
|
-
it "sees replace after the map was primed" do
|
|
129
|
-
face_children_names # prime the map
|
|
130
|
-
victim = root.children.to_a.find { |n| n.name == "b" }
|
|
131
|
-
victim.replace(doc.create_element("swapped"))
|
|
132
|
-
expect(face_children_names).to include("swapped")
|
|
133
|
-
expect(face_children_names).not_to include("b")
|
|
134
|
-
end
|
|
135
|
-
|
|
136
|
-
it "sees content rewrites after the map was primed" do
|
|
137
|
-
face_children_names # prime the map
|
|
138
|
-
target = root.children.to_a.find { |n| n.name == "a" }
|
|
139
|
-
target.content = "rewritten"
|
|
140
|
-
names = face_children_names
|
|
141
|
-
expect(names).to include("a")
|
|
142
|
-
expect(root.children.to_a.find { |n| n.name == "a" }.text)
|
|
143
|
-
.to eq("rewritten")
|
|
144
|
-
end
|
|
145
|
-
end
|
|
146
|
-
end
|