An element restricting a base choice was always checked by the
effective-total-range approximation, which exists for one reason: the
literal section 3.9.6 pairing has no allowance for occurrence counts
accumulated across the choice's own repetitions and over-rejects
W3C-read-valid schemas (particlesZ001, whose manifest calls the rule
ambiguous). That ambiguity is impossible when the choice cannot repeat:
at {max occurs} = 1 every accepted child comes from one traversal of one
branch, so the literal rule governs. elementRestrictsChoice now applies
the literal pairing (the derived element, with its own occurrence range,
must validly restrict some non-pointless branch) when base maxOccurs is
1, and keeps the effective-range reading only for repeating choices.
Closes XSTS particlesHa161 (a derived a{0,1} is not a pointwise
NameAndTypeOK restriction of a required branch a{1,1}) and particlesM034
(a derived d1+ cannot map into the branch (d1*, d2+): the required d2 is
unmapped and not emptiable). The settled-valid neighbors that pin the
boundary from the other side all still pass: Ha160, M002, M003, M035,
and the Z001 shape, each now a regression test in
XSDParticleChoiceRestrictionTests. invalid-schemas-accepted 6 -> 4.
particlesIg004 is reclassified as spec-divergent (4 -> 3): section 3.3.2
maps block="#all" and the explicit substitution/extension/restriction
list to the SAME {disallowed substitutions} set, so NameAndTypeOK clause
5's superset requirement holds and the invalid expectation has no spec
path; it is jointly unsatisfiable with the settled Ig001/002/003 (equal
sets, valid) and Ig005 (#all covers the triple, valid).
The remaining three schema deviations are documented divergences, not
open bugs: Ik025/Ik027 (cos-st-derived-ok 2.2.4 reading) and schN12,
newly adjudicated in docs/xsts-deviations.md: its chameleon redefine
fails src-redefine 6.2.2 on NameAndTypeOK clause 1 (no-namespace locals
vs qualified locals), but 6.2.2 cannot be enforced uniformly because
mgO013's settled-VALID schema also violates it; any uniform enforcement
trades a false negative for a valid-schema-rejected false positive.
Also corrects the stale deviations-doc row that listed the already-closed
particlesIk011 and omitted schN12; the failures file is the authority.
Gates: full XSTS release suite green at 0/3/0/4 (valid buckets unchanged
at zero), libxml2 differential green at 244 pinned entries (Ha161 and
M034 newly pinned, both compile under libxml2), full local gate green.
156 lines
6.9 KiB
Swift
156 lines
6.9 KiB
Swift
import Testing
|
|
@testable import PureXML
|
|
|
|
/// The two regimes of RecurseAsIfGroup against a base choice (XSD 1.0 section
|
|
/// 3.9.6): a NON-repeating choice ({max occurs} = 1) gets the literal pairing,
|
|
/// a repeating choice keeps the effective-total-range reading (the particlesZ001
|
|
/// ambiguity only exists when counts accumulate across repetitions). Shapes are
|
|
/// the XSTS cases that pin the boundary from both sides.
|
|
@Suite("XSD element-restricts-choice: literal vs effective-range regimes")
|
|
struct XSDParticleChoiceRestrictionTests {
|
|
private let xsNamespace = "xmlns:xs=\"http://www.w3.org/2001/XMLSchema\""
|
|
|
|
private func schema(base: String, derived: String) -> String {
|
|
"""
|
|
<xs:schema \(xsNamespace)>
|
|
<xs:complexType name="B">\(base)</xs:complexType>
|
|
<xs:complexType name="R">
|
|
<xs:complexContent><xs:restriction base="B">\(derived)</xs:restriction></xs:complexContent>
|
|
</xs:complexType>
|
|
<xs:element name="r" type="R"/>
|
|
</xs:schema>
|
|
"""
|
|
}
|
|
|
|
@Test("Optional element does not restrict a required branch of a non-repeating choice")
|
|
func test_ha161Rejected() {
|
|
// particlesHa161: derived a{0,1} vs branch a{1,1} in choice{0,1}. Pointwise
|
|
// NameAndTypeOK occurrence fails; the old effective-range reading accepted it.
|
|
let source = schema(
|
|
base: """
|
|
<xs:sequence><xs:choice minOccurs="0">
|
|
<xs:element name="a" type="xs:string"/>
|
|
<xs:element name="b" type="xs:string"/>
|
|
</xs:choice></xs:sequence>
|
|
""",
|
|
derived: "<xs:sequence><xs:element name=\"a\" type=\"xs:string\" minOccurs=\"0\"/></xs:sequence>",
|
|
)
|
|
#expect(throws: (any Error).self) { try PureXML.Schema.Document(source) }
|
|
}
|
|
|
|
@Test("Same-occurrence element restricting one branch of a non-repeating choice compiles")
|
|
func test_ha160Accepted() throws {
|
|
// particlesHa160, the settled-valid twin: a{1,1} vs branch a{1,1}.
|
|
let source = schema(
|
|
base: """
|
|
<xs:sequence><xs:choice>
|
|
<xs:element name="a" type="xs:string"/>
|
|
<xs:element name="b" type="xs:string"/>
|
|
</xs:choice></xs:sequence>
|
|
""",
|
|
derived: "<xs:sequence><xs:element name=\"a\" type=\"xs:string\"/></xs:sequence>",
|
|
)
|
|
_ = try PureXML.Schema.Document(source)
|
|
}
|
|
|
|
@Test("Element cannot map into a branch whose required sibling is unmapped")
|
|
func test_m034Rejected() {
|
|
// particlesM034: derived d1+ vs choice(seq(c1{2,2}, c2?), seq{1,inf}(d1*, d2+)).
|
|
// Every traversal of the only d1-bearing branch emits a d2, so pure d1 content
|
|
// is not in the base language; the leaf-admission reading accepted it.
|
|
let source = schema(
|
|
base: """
|
|
<xs:choice>
|
|
<xs:sequence minOccurs="0" maxOccurs="unbounded">
|
|
<xs:element name="c1" minOccurs="2" maxOccurs="2"/>
|
|
<xs:element name="c2" minOccurs="0"/>
|
|
</xs:sequence>
|
|
<xs:sequence maxOccurs="unbounded">
|
|
<xs:element name="d1" minOccurs="0" maxOccurs="unbounded"/>
|
|
<xs:element name="d2" maxOccurs="unbounded"/>
|
|
</xs:sequence>
|
|
</xs:choice>
|
|
""",
|
|
derived: "<xs:choice><xs:element name=\"d1\" maxOccurs=\"unbounded\"/></xs:choice>",
|
|
)
|
|
#expect(throws: (any Error).self) { try PureXML.Schema.Document(source) }
|
|
}
|
|
|
|
@Test("Element maps into a branch whose other siblings are emptiable")
|
|
func test_m035Accepted() throws {
|
|
// particlesM035, the settled-valid twin of M034: derived d2+ maps onto the
|
|
// branch's d2+ with the d1* sibling emptiable.
|
|
let source = schema(
|
|
base: """
|
|
<xs:choice>
|
|
<xs:sequence minOccurs="0" maxOccurs="unbounded">
|
|
<xs:element name="c1" minOccurs="2" maxOccurs="2"/>
|
|
<xs:element name="c2" minOccurs="0"/>
|
|
</xs:sequence>
|
|
<xs:sequence maxOccurs="unbounded">
|
|
<xs:element name="d1" minOccurs="0" maxOccurs="unbounded"/>
|
|
<xs:element name="d2" maxOccurs="unbounded"/>
|
|
</xs:sequence>
|
|
</xs:choice>
|
|
""",
|
|
derived: "<xs:choice><xs:element name=\"d2\" maxOccurs=\"unbounded\"/></xs:choice>",
|
|
)
|
|
_ = try PureXML.Schema.Document(source)
|
|
}
|
|
|
|
@Test("Element with matching in-branch occurrence restricts a non-repeating choice")
|
|
func test_m002ShapeAccepted() throws {
|
|
// particlesM002: derived c1{2,2} maps into branch seq{1,3}(c1{2,2}, c2?) of a
|
|
// non-repeating choice; the literal regime must keep accepting this.
|
|
let source = schema(
|
|
base: """
|
|
<xs:choice>
|
|
<xs:sequence maxOccurs="3">
|
|
<xs:element name="c1" minOccurs="2" maxOccurs="2"/>
|
|
<xs:element name="c2" minOccurs="0"/>
|
|
</xs:sequence>
|
|
<xs:sequence>
|
|
<xs:element name="d1"/>
|
|
<xs:element name="d2" minOccurs="0"/>
|
|
</xs:sequence>
|
|
</xs:choice>
|
|
""",
|
|
derived: "<xs:choice><xs:element name=\"c1\" minOccurs=\"2\" maxOccurs=\"2\"/></xs:choice>",
|
|
)
|
|
_ = try PureXML.Schema.Document(source)
|
|
}
|
|
|
|
@Test("Repeating base choice keeps the effective-range reading")
|
|
func test_z001ShapeAccepted() throws {
|
|
// particlesZ001's shape: element{0,inf} over choice{0,inf}(...). The literal
|
|
// pointwise rule would reject it; W3C reads it valid, and the repeating regime
|
|
// must keep accepting counts accumulated across choice repetitions.
|
|
let source = schema(
|
|
base: """
|
|
<xs:choice minOccurs="0" maxOccurs="unbounded">
|
|
<xs:element name="a" type="xs:string"/>
|
|
<xs:element name="b" type="xs:string"/>
|
|
</xs:choice>
|
|
""",
|
|
derived: "<xs:choice><xs:element name=\"a\" type=\"xs:string\" minOccurs=\"0\" maxOccurs=\"unbounded\"/></xs:choice>",
|
|
)
|
|
_ = try PureXML.Schema.Document(source)
|
|
}
|
|
|
|
@Test("Repeating base choice still bounds the effective total range")
|
|
func test_ha162ShapeRejected() {
|
|
// particlesHa162: a{1,3} over choice{1,2}(a|b): three a's exceed what two
|
|
// traversals can produce; the repeating regime's range check rejects it.
|
|
let source = schema(
|
|
base: """
|
|
<xs:sequence><xs:choice maxOccurs="2">
|
|
<xs:element name="a" type="xs:string"/>
|
|
<xs:element name="b" type="xs:string"/>
|
|
</xs:choice></xs:sequence>
|
|
""",
|
|
derived: "<xs:sequence><xs:element name=\"a\" type=\"xs:string\" maxOccurs=\"3\"/></xs:sequence>",
|
|
)
|
|
#expect(throws: (any Error).self) { try PureXML.Schema.Document(source) }
|
|
}
|
|
}
|