moxml 0.5.162 → 0.5.163
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/_references/processing-model.adoc +5 -3
- data/lib/moxml/version.rb +1 -1
- data/lib/moxml/xsd/schema_rules.rb +154 -39
- data/spec/moxml/xsd_underdetection_spec.rb +171 -5
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 66e9a76f3fb1a9a8e41d39d3f49c6ea97536a1d08a7aaf145169eb2ca8b2e72e
|
|
4
|
+
data.tar.gz: 5190677a144235ce163990b3cd3f7f6ef2a123ef4e02b1b81626e2467d74bcaf
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 8dae5f5b85c572bf5f31efff0dcd1694854a5fffcf03feb066997136b53a44e2423a8208c08944c2159cd28ecdf83954cfe9856ed8354597d8ae8a664d7c434f
|
|
7
|
+
data.tar.gz: 0347c21ed0aecc388117e2c69632f9be715f0f1a073b5ac1dfad06fc8a0760cf07d652fc7cb39b0641ff5358b8a0d075f22de71befa72bdd687e24bf9ea1f2b4
|
|
@@ -263,9 +263,11 @@ checks). `validate` returns accumulated `ValidationError` entries —
|
|
|
263
263
|
message always; line/column/path where the engine reports them, nil
|
|
264
264
|
where it does not; `[]` means valid. Documents outside the engine's
|
|
265
265
|
scope validate laxly, except that strict `xs:any` /
|
|
266
|
-
`xs:anyAttribute` wildcard matches
|
|
267
|
-
|
|
268
|
-
|
|
266
|
+
`xs:anyAttribute` wildcard matches, `xsi:nil`, and the XSD 1.1
|
|
267
|
+
negative wildcards (`notQName` / `notNamespace` — an item excluded
|
|
268
|
+
by them has no particle and the content model cannot accept it) are
|
|
269
|
+
re-checked from the schema source on every adapter — the re-check
|
|
270
|
+
can only under-report, never false-report. A compiled schema reports the
|
|
269
271
|
version its source declares: root `vc:minVersion="1.1"` scopes the
|
|
270
272
|
document to 1.1, anything else is 1.0 (consumers scope 1.0-only
|
|
271
273
|
checks with it; applying 1.1 semantics is the engine-side program).
|
data/lib/moxml/version.rb
CHANGED
|
@@ -8,7 +8,11 @@ module Moxml
|
|
|
8
8
|
#
|
|
9
9
|
# * a strict `xs:any` wildcard with an undeclared element,
|
|
10
10
|
# * a strict `xs:anyAttribute` with an undeclared attribute,
|
|
11
|
-
# * `xsi:nil` on an element whose declaration is not nillable
|
|
11
|
+
# * `xsi:nil` on an element whose declaration is not nillable,
|
|
12
|
+
# * the XSD 1.1 negative wildcards — `notQName` /
|
|
13
|
+
# `notNamespace` on `xs:any` / `xs:anyAttribute` (#460):
|
|
14
|
+
# engines that compile them without enforcing them absorb
|
|
15
|
+
# excluded items the content model cannot accept.
|
|
12
16
|
#
|
|
13
17
|
# The rules run from an index of the schema SOURCE (this gem's
|
|
14
18
|
# own XML machinery — no engine involvement) and merge with the
|
|
@@ -20,22 +24,18 @@ module Moxml
|
|
|
20
24
|
module SchemaRules
|
|
21
25
|
XSI_NS = "http://www.w3.org/2001/XMLSchema-instance"
|
|
22
26
|
|
|
23
|
-
# A wildcard's namespace
|
|
24
|
-
# Structures §3.10.2 (##any default;
|
|
25
|
-
# strict
|
|
26
|
-
#
|
|
27
|
+
# A wildcard's namespace constraint and disallowed names, per
|
|
28
|
+
# XML Schema Structures §3.10.1–3.10.2 (##any default;
|
|
29
|
+
# processContents default strict; notNamespace / notQName are
|
|
30
|
+
# XSD 1.1). :tns / :local are resolved against the indexed
|
|
31
|
+
# schema at check time; notQName QNames are resolved against
|
|
32
|
+
# the schema element's in-scope bindings at index time.
|
|
27
33
|
class Wildcard
|
|
28
|
-
def initialize(value, process
|
|
34
|
+
def initialize(value, process, not_namespace: nil, not_qname: nil,
|
|
35
|
+
element: nil)
|
|
29
36
|
@process = (process || "strict").to_sym
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
@mode = :any
|
|
33
|
-
when "##other"
|
|
34
|
-
@mode = :other
|
|
35
|
-
else
|
|
36
|
-
@mode = :list
|
|
37
|
-
@namespaces = value.split(/\s+/).reject(&:empty?).freeze
|
|
38
|
-
end
|
|
37
|
+
@mode, @namespaces = namespace_constraint(value, not_namespace)
|
|
38
|
+
@disallowed = disallowed_names(not_qname, element).freeze
|
|
39
39
|
end
|
|
40
40
|
|
|
41
41
|
def strict?
|
|
@@ -47,13 +47,89 @@ module Moxml
|
|
|
47
47
|
when :any then true
|
|
48
48
|
when :other
|
|
49
49
|
namespace != :local && namespace != :tns
|
|
50
|
+
when :not
|
|
51
|
+
@namespaces.none? { |entry| entry_covers?(entry, namespace) }
|
|
50
52
|
else
|
|
51
53
|
@namespaces.any? { |entry| entry_covers?(entry, namespace) }
|
|
52
54
|
end
|
|
53
55
|
end
|
|
54
56
|
|
|
57
|
+
# Why the wildcard refuses an item outright (Structures
|
|
58
|
+
# §3.10.4.1 Item Valid): a refusal is a match-level failure,
|
|
59
|
+
# so it applies at every processContents — the content model
|
|
60
|
+
# has no particle for the item and cannot accept it. 1.0
|
|
61
|
+
# namespace lists stay the engine's enforcement and never
|
|
62
|
+
# name an exclusion here.
|
|
63
|
+
def negative_exclusion(qname, namespace, defined:,
|
|
64
|
+
sibling_names: [])
|
|
65
|
+
@disallowed.each do |entry|
|
|
66
|
+
case entry
|
|
67
|
+
when :defined
|
|
68
|
+
return :not_qname if defined
|
|
69
|
+
when :sibling
|
|
70
|
+
return :not_qname if sibling_names.include?(qname)
|
|
71
|
+
else
|
|
72
|
+
return :not_qname if entry == qname
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
return :not_namespace if @mode == :not &&
|
|
76
|
+
@namespaces.any? { |entry| entry_covers?(entry, namespace) }
|
|
77
|
+
|
|
78
|
+
nil
|
|
79
|
+
end
|
|
80
|
+
|
|
55
81
|
private
|
|
56
82
|
|
|
83
|
+
def namespace_constraint(value, not_namespace)
|
|
84
|
+
if not_namespace
|
|
85
|
+
[:not, not_namespace.split(/\s+/).reject(&:empty?)]
|
|
86
|
+
elsif value.nil? || value == "##any" || value == ""
|
|
87
|
+
[:any, []]
|
|
88
|
+
elsif value == "##other"
|
|
89
|
+
[:other, []]
|
|
90
|
+
else
|
|
91
|
+
[:list, value.split(/\s+/).reject(&:empty?)]
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# notQName entries (§3.10.2): QName | ##defined |
|
|
96
|
+
# ##definedSibling (xs:any; ##defined only on
|
|
97
|
+
# xs:anyAttribute). Prefixed QNames resolve against the
|
|
98
|
+
# wildcard element's in-scope bindings; unprefixed QNames
|
|
99
|
+
# carry no namespace — XSD QName values take no default
|
|
100
|
+
# namespace. Unresolvable prefixes are dropped: the index
|
|
101
|
+
# under-reports, never false-reports.
|
|
102
|
+
def disallowed_names(not_qname, element)
|
|
103
|
+
not_qname.to_s.split(/\s+/).reject(&:empty?).filter_map do |token|
|
|
104
|
+
case token
|
|
105
|
+
when "##defined" then :defined
|
|
106
|
+
when "##definedSibling" then :sibling
|
|
107
|
+
when /\A([^:]+):(.+)\z/
|
|
108
|
+
uri = prefix_uri(Regexp.last_match(1), element)
|
|
109
|
+
"{#{uri}}#{Regexp.last_match(2)}" unless uri.empty?
|
|
110
|
+
else
|
|
111
|
+
"{}#{token}"
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
# node.namespaces is adapter-divergent (some engines return
|
|
117
|
+
# in-scope bindings, others own-node declarations only), so
|
|
118
|
+
# the in-scope map is walked out by hand — per-node
|
|
119
|
+
# definitions merged along the ancestor axis, deeper
|
|
120
|
+
# overriding shallower, identically on every adapter.
|
|
121
|
+
def prefix_uri(prefix, element)
|
|
122
|
+
bindings = {}
|
|
123
|
+
node = element
|
|
124
|
+
while node
|
|
125
|
+
node.namespaces.each do |namespace|
|
|
126
|
+
bindings[namespace.prefix] = namespace.uri
|
|
127
|
+
end
|
|
128
|
+
node = node.parent&.element? ? node.parent : nil
|
|
129
|
+
end
|
|
130
|
+
bindings[prefix].to_s
|
|
131
|
+
end
|
|
132
|
+
|
|
57
133
|
def entry_covers?(entry, namespace)
|
|
58
134
|
namespace == case entry
|
|
59
135
|
when "##targetNamespace" then :tns
|
|
@@ -193,12 +269,9 @@ module Moxml
|
|
|
193
269
|
add_particle(child, index, model,
|
|
194
270
|
model.declared_attributes)
|
|
195
271
|
when "any"
|
|
196
|
-
model.any ||=
|
|
197
|
-
child["processContents"])
|
|
272
|
+
model.any ||= wildcard_from(child)
|
|
198
273
|
when "anyAttribute"
|
|
199
|
-
model.any_attribute ||=
|
|
200
|
-
Wildcard.new(child["namespace"],
|
|
201
|
-
child["processContents"])
|
|
274
|
+
model.any_attribute ||= wildcard_from(child)
|
|
202
275
|
else
|
|
203
276
|
collect_model(child, index, model)
|
|
204
277
|
end
|
|
@@ -234,19 +307,25 @@ module Moxml
|
|
|
234
307
|
end
|
|
235
308
|
end
|
|
236
309
|
|
|
310
|
+
def wildcard_from(node)
|
|
311
|
+
Wildcard.new(
|
|
312
|
+
node["namespace"], node["processContents"],
|
|
313
|
+
not_namespace: node["notNamespace"],
|
|
314
|
+
not_qname: node["notQName"],
|
|
315
|
+
element: node
|
|
316
|
+
)
|
|
317
|
+
end
|
|
318
|
+
|
|
237
319
|
def child_findings(child, model, index)
|
|
238
320
|
qname = qname(child)
|
|
239
321
|
findings = []
|
|
240
322
|
if model.declared_children.include?(qname)
|
|
241
323
|
declaration = index.elements[qname]
|
|
242
324
|
findings << nil_finding(child, declaration) if declaration
|
|
243
|
-
elsif
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
"wildcard but is not declared",
|
|
248
|
-
path: path_of(child),
|
|
249
|
-
)
|
|
325
|
+
elsif model.any &&
|
|
326
|
+
(finding = wildcard_child_finding(child, model, index,
|
|
327
|
+
qname))
|
|
328
|
+
findings << finding
|
|
250
329
|
end
|
|
251
330
|
findings.concat(
|
|
252
331
|
attribute_findings(child, index.contents[qname], index),
|
|
@@ -254,13 +333,34 @@ module Moxml
|
|
|
254
333
|
findings
|
|
255
334
|
end
|
|
256
335
|
|
|
257
|
-
def
|
|
336
|
+
def wildcard_child_finding(child, model, index, qname)
|
|
258
337
|
wildcard = model.any
|
|
259
|
-
return false unless wildcard&.strict?
|
|
260
|
-
return false if index.elements.key?(qname)
|
|
261
|
-
|
|
262
338
|
namespace = resolve_namespace(qname, index)
|
|
263
|
-
|
|
339
|
+
exclusion = wildcard.negative_exclusion(
|
|
340
|
+
qname, namespace,
|
|
341
|
+
defined: index.elements.key?(qname),
|
|
342
|
+
sibling_names: model.declared_children
|
|
343
|
+
)
|
|
344
|
+
return excluded_child_finding(child, qname, exclusion) if exclusion
|
|
345
|
+
|
|
346
|
+
return unless wildcard.covers?(namespace) && wildcard.strict?
|
|
347
|
+
return if index.elements.key?(qname)
|
|
348
|
+
|
|
349
|
+
Moxml::XSD::ValidationError.new(
|
|
350
|
+
message: "element <#{local(qname)}> matches a strict " \
|
|
351
|
+
"wildcard but is not declared",
|
|
352
|
+
path: path_of(child),
|
|
353
|
+
)
|
|
354
|
+
end
|
|
355
|
+
|
|
356
|
+
def excluded_child_finding(child, qname, exclusion)
|
|
357
|
+
reason = exclusion == :not_namespace ? "notNamespace" : "notQName"
|
|
358
|
+
Moxml::XSD::ValidationError.new(
|
|
359
|
+
message: "element <#{local(qname)}> is disallowed by a " \
|
|
360
|
+
"wildcard (#{reason}) and is not declared in the " \
|
|
361
|
+
"content model",
|
|
362
|
+
path: path_of(child),
|
|
363
|
+
)
|
|
264
364
|
end
|
|
265
365
|
|
|
266
366
|
def attribute_findings(element, model, index)
|
|
@@ -271,14 +371,19 @@ module Moxml
|
|
|
271
371
|
next if attribute.namespace&.uri == XSI_NS
|
|
272
372
|
|
|
273
373
|
qname = attribute_qname(attribute)
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
374
|
+
next if model.declared_attributes.include?(qname)
|
|
375
|
+
next unless any_attribute
|
|
376
|
+
|
|
377
|
+
namespace = resolve_namespace(qname, index)
|
|
378
|
+
exclusion = any_attribute.negative_exclusion(
|
|
379
|
+
qname, namespace,
|
|
380
|
+
defined: index.attributes.key?(qname)
|
|
281
381
|
)
|
|
382
|
+
next excluded_attribute_finding(element, qname, exclusion) if exclusion
|
|
383
|
+
|
|
384
|
+
next unless any_attribute.covers?(namespace)
|
|
385
|
+
next unless any_attribute.strict?
|
|
386
|
+
next if index.attributes.key?(qname)
|
|
282
387
|
|
|
283
388
|
Moxml::XSD::ValidationError.new(
|
|
284
389
|
message: "attribute #{local(qname)} matches a strict " \
|
|
@@ -288,6 +393,16 @@ module Moxml
|
|
|
288
393
|
end
|
|
289
394
|
end
|
|
290
395
|
|
|
396
|
+
def excluded_attribute_finding(element, qname, exclusion)
|
|
397
|
+
reason = exclusion == :not_namespace ? "notNamespace" : "notQName"
|
|
398
|
+
Moxml::XSD::ValidationError.new(
|
|
399
|
+
message: "attribute #{local(qname)} is disallowed by a " \
|
|
400
|
+
"wildcard (#{reason}) and is not declared in the " \
|
|
401
|
+
"content model",
|
|
402
|
+
path: path_of(element),
|
|
403
|
+
)
|
|
404
|
+
end
|
|
405
|
+
|
|
291
406
|
def nil_finding(element, declaration)
|
|
292
407
|
return if declaration[:nillable]
|
|
293
408
|
|
|
@@ -4,11 +4,11 @@ require "spec_helper"
|
|
|
4
4
|
|
|
5
5
|
# Processing model §Validation (normative): pinned here.
|
|
6
6
|
# #452: tier-1 engines can accept, inside their recognized scope,
|
|
7
|
-
# instances that violate strict wildcards or xsi:nil.
|
|
8
|
-
#
|
|
9
|
-
#
|
|
10
|
-
#
|
|
11
|
-
# corpus shapes.
|
|
7
|
+
# instances that violate strict wildcards or xsi:nil. #460: engines
|
|
8
|
+
# may compile the 1.1 negative wildcards without enforcing them.
|
|
9
|
+
# The Ruby-side SchemaRules post-validation closes those gaps from
|
|
10
|
+
# the schema source; findings merge with the engine's (duplicates
|
|
11
|
+
# dropped), so every adapter answers invalid for the corpus shapes.
|
|
12
12
|
RSpec.describe "Moxml::XSD tier-1 under-detections (#452)" do
|
|
13
13
|
%i[nokogiri leptris].each do |adapter_name|
|
|
14
14
|
describe "on #{adapter_name}" do
|
|
@@ -152,6 +152,172 @@ RSpec.describe "Moxml::XSD tier-1 under-detections (#452)" do
|
|
|
152
152
|
end
|
|
153
153
|
end
|
|
154
154
|
|
|
155
|
+
describe "negative wildcards (XSD 1.1, #460)" do
|
|
156
|
+
before do
|
|
157
|
+
if adapter_name == :nokogiri
|
|
158
|
+
skip "#{adapter_name} requires an XSD 1.1 wildcard-capable " \
|
|
159
|
+
"engine"
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
describe "xs:any notQName naming an excluded element" do
|
|
164
|
+
let(:schema) do
|
|
165
|
+
ctx.xsd(<<~XSD)
|
|
166
|
+
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
|
|
167
|
+
xmlns:w="http://example.com/w"
|
|
168
|
+
targetNamespace="http://example.com/w"
|
|
169
|
+
elementFormDefault="qualified">
|
|
170
|
+
<xs:element name="container">
|
|
171
|
+
<xs:complexType>
|
|
172
|
+
<xs:sequence>
|
|
173
|
+
<xs:any processContents="strict"
|
|
174
|
+
notQName="w:banned" minOccurs="0"
|
|
175
|
+
maxOccurs="unbounded"/>
|
|
176
|
+
</xs:sequence>
|
|
177
|
+
</xs:complexType>
|
|
178
|
+
</xs:element>
|
|
179
|
+
<xs:element name="banned" type="xs:string"/>
|
|
180
|
+
<xs:element name="known" type="xs:string"/>
|
|
181
|
+
</xs:schema>
|
|
182
|
+
XSD
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
it "rejects the excluded element although it is declared" do
|
|
186
|
+
doc = ctx.parse(
|
|
187
|
+
'<container xmlns="http://example.com/w"><banned>x</banned></container>',
|
|
188
|
+
)
|
|
189
|
+
expect(schema.valid?(doc)).to be(false)
|
|
190
|
+
expect(schema.validate(doc).map(&:message)).to include(
|
|
191
|
+
/disallowed by a wildcard \(notQName\)/,
|
|
192
|
+
)
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
it "accepts a wildcard-matched declared element" do
|
|
196
|
+
doc = ctx.parse(
|
|
197
|
+
'<container xmlns="http://example.com/w"><known>x</known></container>',
|
|
198
|
+
)
|
|
199
|
+
expect(schema.valid?(doc)).to be(true)
|
|
200
|
+
end
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
describe "xs:any notQName=##defined" do
|
|
204
|
+
let(:schema) do
|
|
205
|
+
ctx.xsd(<<~XSD)
|
|
206
|
+
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
|
|
207
|
+
targetNamespace="http://example.com/w"
|
|
208
|
+
elementFormDefault="qualified">
|
|
209
|
+
<xs:element name="container">
|
|
210
|
+
<xs:complexType>
|
|
211
|
+
<xs:sequence>
|
|
212
|
+
<xs:any processContents="lax"
|
|
213
|
+
notQName="##defined" minOccurs="0"
|
|
214
|
+
maxOccurs="unbounded"/>
|
|
215
|
+
</xs:sequence>
|
|
216
|
+
</xs:complexType>
|
|
217
|
+
</xs:element>
|
|
218
|
+
<xs:element name="known" type="xs:string"/>
|
|
219
|
+
</xs:schema>
|
|
220
|
+
XSD
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
it "rejects a declared element the ##defined wildcard excludes" do
|
|
224
|
+
doc = ctx.parse(
|
|
225
|
+
'<container xmlns="http://example.com/w"><known>x</known></container>',
|
|
226
|
+
)
|
|
227
|
+
expect(schema.valid?(doc)).to be(false)
|
|
228
|
+
expect(schema.validate(doc).map(&:message)).to include(
|
|
229
|
+
/disallowed by a wildcard \(notQName\)/,
|
|
230
|
+
)
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
it "absorbs an undeclared element laxly" do
|
|
234
|
+
doc = ctx.parse(
|
|
235
|
+
'<container xmlns="http://example.com/w"><unknown>x</unknown></container>',
|
|
236
|
+
)
|
|
237
|
+
expect(schema.valid?(doc)).to be(true)
|
|
238
|
+
end
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
describe "xs:any notNamespace" do
|
|
242
|
+
let(:schema) do
|
|
243
|
+
ctx.xsd(<<~XSD)
|
|
244
|
+
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
|
|
245
|
+
targetNamespace="http://example.com/w"
|
|
246
|
+
elementFormDefault="qualified">
|
|
247
|
+
<xs:element name="container">
|
|
248
|
+
<xs:complexType>
|
|
249
|
+
<xs:sequence>
|
|
250
|
+
<xs:any processContents="lax"
|
|
251
|
+
notNamespace="##targetNamespace"
|
|
252
|
+
minOccurs="0" maxOccurs="unbounded"/>
|
|
253
|
+
</xs:sequence>
|
|
254
|
+
</xs:complexType>
|
|
255
|
+
</xs:element>
|
|
256
|
+
</xs:schema>
|
|
257
|
+
XSD
|
|
258
|
+
end
|
|
259
|
+
|
|
260
|
+
it "rejects an element in the disallowed namespace" do
|
|
261
|
+
doc = ctx.parse(
|
|
262
|
+
'<container xmlns="http://example.com/w"><stray/></container>',
|
|
263
|
+
)
|
|
264
|
+
expect(schema.valid?(doc)).to be(false)
|
|
265
|
+
expect(schema.validate(doc).map(&:message)).to include(
|
|
266
|
+
/disallowed by a wildcard \(notNamespace\)/,
|
|
267
|
+
)
|
|
268
|
+
end
|
|
269
|
+
|
|
270
|
+
it "absorbs an element from an allowed namespace" do
|
|
271
|
+
doc = ctx.parse(
|
|
272
|
+
'<container xmlns="http://example.com/w">' \
|
|
273
|
+
'<stray xmlns="http://example.com/other"/></container>',
|
|
274
|
+
)
|
|
275
|
+
expect(schema.valid?(doc)).to be(true)
|
|
276
|
+
end
|
|
277
|
+
end
|
|
278
|
+
|
|
279
|
+
describe "xs:anyAttribute notQName=##defined" do
|
|
280
|
+
let(:schema) do
|
|
281
|
+
ctx.xsd(<<~XSD)
|
|
282
|
+
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
|
|
283
|
+
targetNamespace="http://example.com/w"
|
|
284
|
+
elementFormDefault="qualified"
|
|
285
|
+
attributeFormDefault="qualified">
|
|
286
|
+
<xs:element name="settings">
|
|
287
|
+
<xs:complexType>
|
|
288
|
+
<xs:complexContent>
|
|
289
|
+
<xs:restriction base="xs:anyType">
|
|
290
|
+
<xs:anyAttribute processContents="lax"
|
|
291
|
+
notQName="##defined"/>
|
|
292
|
+
</xs:restriction>
|
|
293
|
+
</xs:complexContent>
|
|
294
|
+
</xs:complexType>
|
|
295
|
+
</xs:element>
|
|
296
|
+
<xs:attribute name="marked" type="xs:string"/>
|
|
297
|
+
</xs:schema>
|
|
298
|
+
XSD
|
|
299
|
+
end
|
|
300
|
+
|
|
301
|
+
it "rejects a declared attribute the ##defined wildcard excludes" do
|
|
302
|
+
doc = ctx.parse(
|
|
303
|
+
'<settings xmlns="http://example.com/w" ' \
|
|
304
|
+
'xmlns:w="http://example.com/w" w:marked="x"/>',
|
|
305
|
+
)
|
|
306
|
+
expect(schema.valid?(doc)).to be(false)
|
|
307
|
+
expect(schema.validate(doc).map(&:message)).to include(
|
|
308
|
+
/attribute marked is disallowed by a wildcard \(notQName\)/,
|
|
309
|
+
)
|
|
310
|
+
end
|
|
311
|
+
|
|
312
|
+
it "absorbs an undeclared attribute laxly" do
|
|
313
|
+
doc = ctx.parse(
|
|
314
|
+
'<settings xmlns="http://example.com/w" free="x"/>',
|
|
315
|
+
)
|
|
316
|
+
expect(schema.valid?(doc)).to be(true)
|
|
317
|
+
end
|
|
318
|
+
end
|
|
319
|
+
end
|
|
320
|
+
|
|
155
321
|
describe "wrong-namespace root" do
|
|
156
322
|
let(:schema) do
|
|
157
323
|
ctx.xsd(<<~XSD)
|
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.5.
|
|
4
|
+
version: 0.5.163
|
|
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-10-
|
|
11
|
+
date: 2026-10-25 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
|