An xs:all group's own minOccurs="0" makes the GROUP optional (it occurs zero or one times), not each member optional. The parser forced every member's minOccurs to 0 whenever the all-group was optional, so a non-empty but incomplete optional all-group (a required member missing) was wrongly accepted, and a complexContent restriction that relaxed such a required member also slipped past the particle-restriction check (the zeroed minOccurs made it look already optional). Members now keep their declared occurrence; the absent-group case is handled at validation time by skipping the required-member check only when the all-group contributes no children at all. Applied identically on the tree and streaming paths (both call allStructureErrors). A required all-group is unchanged. Catches mgZ001 (instance) and particlesK006 (schema). XSTS invalid- instances-accepted 21 to 20 and invalid-schemas-accepted 18 to 17, both valid- rejected buckets held at 0.
61 lines
2.3 KiB
Swift
61 lines
2.3 KiB
Swift
import Testing
|
|
@testable import PureXML
|
|
|
|
/// An `xs:all` group's own `minOccurs="0"` makes the GROUP optional (it occurs
|
|
/// zero or one times), not its members optional. An absent group (no children)
|
|
/// is valid; a present group (any child) must still satisfy each member's own
|
|
/// `minOccurs`. Earlier the parser forced every member's `minOccurs` to 0, so a
|
|
/// non-empty but incomplete optional all-group was wrongly accepted. Mirrors XSTS
|
|
/// mgZ001.
|
|
@Suite("optional xs:all group member requirements")
|
|
struct SchemaAllGroupMinOccursTests {
|
|
private func schema() throws -> PureXML.Schema.Document {
|
|
try PureXML.Schema.Document("""
|
|
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
|
|
<xs:element name="opt">
|
|
<xs:complexType>
|
|
<xs:all minOccurs="0">
|
|
<xs:element name="a"/>
|
|
<xs:element name="b" minOccurs="0"/>
|
|
<xs:element name="c"/>
|
|
</xs:all>
|
|
</xs:complexType>
|
|
</xs:element>
|
|
<xs:element name="req">
|
|
<xs:complexType>
|
|
<xs:all>
|
|
<xs:element name="a"/>
|
|
<xs:element name="b" minOccurs="0"/>
|
|
</xs:all>
|
|
</xs:complexType>
|
|
</xs:element>
|
|
</xs:schema>
|
|
""")
|
|
}
|
|
|
|
@Test("an absent optional all-group is valid")
|
|
func test_optionalAbsent() throws {
|
|
#expect(try schema().validate("<opt/>").isEmpty)
|
|
}
|
|
|
|
@Test("a present optional all-group missing a required member is rejected")
|
|
func test_optionalPresentIncomplete() throws {
|
|
// 'a' is required once the group is present (c alone is not enough).
|
|
#expect(try !schema().validate("<opt><c/></opt>").isEmpty)
|
|
// 'c' is required too.
|
|
#expect(try !schema().validate("<opt><a/></opt>").isEmpty)
|
|
}
|
|
|
|
@Test("a complete optional all-group is valid")
|
|
func test_optionalPresentComplete() throws {
|
|
#expect(try schema().validate("<opt><a/><c/></opt>").isEmpty)
|
|
#expect(try schema().validate("<opt><a/><b/><c/></opt>").isEmpty)
|
|
}
|
|
|
|
@Test("a required all-group still enforces its members when empty")
|
|
func test_requiredEmptyRejected() throws {
|
|
#expect(try !schema().validate("<req/>").isEmpty) // 'a' required
|
|
#expect(try schema().validate("<req><a/></req>").isEmpty)
|
|
}
|
|
}
|