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.
@@ -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.3
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-05 00:00:00.000000000 Z
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/leptris_children_columnar_spec.rb
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