PureXML/Tests/XSDParticleSubstitutionRestrictionTests.swift
Mihaela Mihaljevic 2e3d2e25f8 fix(schema): reject substitution-member swaps in particle restriction
NameAndTypeOK clause 1 (XSD 1.0 section 3.9.6, Elt:Elt) pairs the declared
element declarations and requires the same {name} and {target namespace};
no clause grants substitution an allowance. The substitution-group
expansion rewrites the compiled term (an abstract head is dropped, members
join a synthetic choice), so the checker previously compared the expanded
names and a head-for-member swap looked name-identical.

Particle.declaredReference now preserves the pre-expansion identity of an
element-ref particle, and the restriction check pairs the declared
references before the structural pairing table (extracted as
structuralValid). Closes XSTS elemZ026 and particlesV020:
invalid-schemas-accepted 8 -> 6, valid buckets unchanged at zero.
Stricter than libxml2, which does not implement this edge; elemZ026 is
pinned in the differential baseline accordingly.

particlesZ008, byte-for-byte the same construct as elemZ026 but carrying
a settled valid expectation contradicting elemZ026's settled invalid, is
reclassified as a spec-divergent schema test with the adjudication
recorded in the suite (exclusion tables split into
Tests/XSTSSuiteExclusions.swift for file length).

Separately attributed: the differential baseline gains ten further pinned
entries (addB070a, addB108, addB177, attO025, elemZ027_c, errC008,
groupH021v, notatB005, notatE002, particlesEb040) that reproduce with
this change stashed. They are pre-existing drift from earlier main-line
fixes, not effects of this commit. Baseline 231 -> 242, differential
green.
2026-07-05 08:27:55 +02:00

62 lines
2.8 KiB
Swift

import Testing
@testable import PureXML
@Suite("XSD Particle Restriction over substitution groups")
struct XSDParticleSubstitutionRestrictionTests {
private let xsNamespace = "xmlns:xs=\"http://www.w3.org/2001/XMLSchema\""
@Test("A restriction may not swap an element ref for a substitution-group member")
func test_substitutionMemberSwapRejected() {
// elemZ026: NameAndTypeOK clause 1 pairs the DECLARED references, and requires
// the same name and target namespace; substitution has no allowance in Elt:Elt.
// Swapping the head ref for a member ref names a different declaration.
let schema = """
<xs:schema \(xsNamespace)>
<xs:complexType name="headType" abstract="true">
<xs:sequence><xs:element name="leaf" type="xs:token"/></xs:sequence>
</xs:complexType>
<xs:element name="head" type="headType" abstract="true"/>
<xs:element name="member" type="headType" substitutionGroup="head"/>
<xs:complexType name="B">
<xs:sequence><xs:element ref="head" maxOccurs="unbounded"/></xs:sequence>
</xs:complexType>
<xs:complexType name="R">
<xs:complexContent><xs:restriction base="B">
<xs:sequence><xs:element ref="member" maxOccurs="unbounded"/></xs:sequence>
</xs:restriction></xs:complexContent>
</xs:complexType>
<xs:element name="r" type="R"/>
</xs:schema>
"""
#expect(throws: (any Error).self) {
try PureXML.Schema.Document(schema)
}
}
@Test("A restriction that repeats the declared ref and narrows occurrence compiles")
func test_sameRefNarrowedOccurrenceCompiles() throws {
// The positive control for the declared-reference check: the same head ref with
// a narrowed range is the spec's idiom, and the substitution expansion behind
// both sides must not disturb it.
let schema = """
<xs:schema \(xsNamespace)>
<xs:complexType name="headType" abstract="true">
<xs:sequence><xs:element name="leaf" type="xs:token"/></xs:sequence>
</xs:complexType>
<xs:element name="head" type="headType" abstract="true"/>
<xs:element name="member" type="headType" substitutionGroup="head"/>
<xs:complexType name="B">
<xs:sequence><xs:element ref="head" minOccurs="0" maxOccurs="unbounded"/></xs:sequence>
</xs:complexType>
<xs:complexType name="R">
<xs:complexContent><xs:restriction base="B">
<xs:sequence><xs:element ref="head" maxOccurs="2"/></xs:sequence>
</xs:restriction></xs:complexContent>
</xs:complexType>
<xs:element name="r" type="R"/>
</xs:schema>
"""
_ = try PureXML.Schema.Document(schema)
}
}