import Testing @testable import PureXML @Suite("XSD Particle Valid (Restriction)") struct XSDParticleRestrictionTests { private let xsNamespace = "xmlns:xs=\"http://www.w3.org/2001/XMLSchema\"" private func schema(base: String, restriction: String) -> String { """ \(base) \(restriction) """ } private func compiles(base: String, restriction: String) -> Bool { (try? PureXML.Schema.Document(schema(base: base, restriction: restriction))) != nil } private func restrictionError(base: String, restriction: String) -> String? { do { _ = try PureXML.Schema.Document(schema(base: base, restriction: restriction)) return nil } catch let error as PureXML.Schema.SchemaError { return String(describing: error) } catch { return String(describing: error) } } @Test("Valid restrictions compile: drop an optional, narrow a choice, keep order") func test_validRestrictions() { // Dropping an optional element. #expect(compiles( base: "", restriction: "", )) // Narrowing a choice to one branch. #expect(compiles( base: "", restriction: "", )) // Narrowing an occurrence range. #expect(compiles( base: "", restriction: "", )) // A maxOccurs=0 particle never occurs (pointless particle); it is removed // before mapping, so it must not consume the base wildcard the next element // restricts (particlesJd). #expect(compiles( base: "", restriction: "", )) // A content-free restriction (an empty sequence) of an EMPTY base is valid: // both accept only the empty sequence (addB079). #expect(compiles( base: "", restriction: "", )) } @Test("An all-maxOccurs=0 derived sequence does not vacuously restrict a required base") func test_emptyDerivedRequiredBase() { // After pointless-particle removal the derived sequence is empty (accepts only // the empty sequence); restricting a required choice {a|b} is invalid. #expect(restrictionError( base: "", restriction: "", )?.contains("not a valid restriction") == true) // But restricting an emptiable base choice {a?|b} is valid. #expect(compiles( base: "", restriction: "", )) } @Test("A restriction widening the occurrence range is rejected") func test_wideningOccurrenceRejected() { let error = restrictionError( base: "", restriction: "", ) #expect(error?.contains("not a valid restriction") == true, "\(String(describing: error))") } @Test("A restriction introducing a new element name is rejected") func test_newNameRejected() { let error = restrictionError( base: "", restriction: "", ) #expect(error?.contains("not a valid restriction") == true) } @Test("A restriction reordering a sequence is rejected") func test_reorderRejected() { let error = restrictionError( base: "", restriction: "", ) #expect(error?.contains("not a valid restriction") == true) } @Test("Restricting to empty requires an emptiable base") func test_emptyRestriction() { // Emptiable base: fine. #expect(compiles( base: "", restriction: "", )) // Required base content: rejected. let error = restrictionError( base: "", restriction: "", ) #expect(error?.contains("cannot be EMPTY") == true, "\(String(describing: error))") } @Test("An element restricts a wildcard only when its namespace is admitted") func test_wildcardRestriction() { // ##any admits the unqualified element. #expect(compiles( base: "", restriction: "", )) // ##other refuses an unqualified element. let error = restrictionError( base: "", restriction: "", ) #expect(error?.contains("not a valid restriction") == true) } @Test("A single-member derived sequence narrowing an occurrence restricts a wider base sequence") func test_singleMemberSequenceNarrowsOccurrence() { // particlesW008: the derived sequence has one member, so it normalizes to the // bare element e1{2,3}; that must still be matched as a member of the base // sequence (RecurseAsIfGroup), not have its {2,3} compared to the base // sequence's own {1,1} occurrence. #expect(compiles( base: "" + "" + "", restriction: "", )) // particlesM002: a derived single-branch choice normalizes to the bare element // and still restricts a choice of sequences whose first branch begins with it. #expect(compiles( base: "" + "" + "", restriction: "", )) } @Test("A group restricting a wildcard is valid only when its effective count fits the wildcard's range") func test_groupRestrictsWildcardCardinality() { // particlesHa: a sequence of named elements restricts a wildcard when every leaf // is admitted AND the effective total count lies within the wildcard's range. #expect(compiles( base: "", restriction: "", )) // Too many leaves: three elements exceed the wildcard's maxOccurs of 2. #expect(restrictionError( base: "", restriction: "", )?.contains("not a valid restriction") == true) // Too few leaves: one element is below the wildcard's minOccurs of 2. #expect(restrictionError( base: "", restriction: "", )?.contains("not a valid restriction") == true) } @Test("A content-bearing derived group never restricts a base single element") func test_groupRestrictsSingleElement() { // mgE014: base sequence(title?) normalizes to the bare element title{0,1}; // the derived sequence(title{0,0}) normalizes to the empty (content-free) // group, which accepts only the empty sequence, valid against an emptiable // element. This is the content-free path, not a derived-group/base-element rule. #expect(compiles( base: "", restriction: "", )) // XSD 1.0 ยง3.9.6 defines NO restriction rule for a derived group against a base // element (only the reverse, an element against a base group). A content-bearing // derived group restricting a single base element is therefore invalid, even // when its leaves share the element's name and its effective count fits. #expect(restrictionError( base: "", restriction: "", )?.contains("not a valid restriction") == true) // The same holds when the derived group's effective count exceeds the base // element's maxOccurs, and when a leaf is differently named. #expect(restrictionError( base: "", restriction: "", )?.contains("not a valid restriction") == true) #expect(restrictionError( base: "", restriction: "", )?.contains("not a valid restriction") == true) } @Test("Conformance: the restriction corpus passes through the harness") func test_conformanceCases() { let valid = compiles( base: "", restriction: "", ) let invalid = compiles( base: "", restriction: "", ) let cases = [ PureXML.Validation.ConformanceCase(name: "faithful-restriction-accepted", actual: valid ? "valid" : "invalid", expected: "valid"), PureXML.Validation.ConformanceCase(name: "additive-restriction-rejected", actual: invalid ? "valid" : "invalid", expected: "invalid"), ] let failures = PureXML.Validation.Conformance.failures(in: cases) #expect(failures.isEmpty, "\(failures.map(\.reason))") } @Test("NameAndTypeOK: a same-name element must keep a type that derives from the base's") func test_elementTypeDerivation() { // Renaming the element's type to an incompatible one (string -> int) is not a // valid restriction: int does not derive from string. #expect(!compiles( base: "", restriction: "", )) // Narrowing to a derived type (integer -> int) is valid. #expect(compiles( base: "", restriction: "", )) // Keeping the same type is valid. #expect(compiles( base: "", restriction: "", )) } @Test("NameAndTypeOK: widening a concrete-typed element to the ur-type is rejected") func test_elementUrTypeWidening() { // Restricting a string element to anyType/anySimpleType is widening, invalid. #expect(!compiles( base: "", restriction: "", )) #expect(!compiles( base: "", restriction: "", )) // The reverse (base is the ur-type, restriction narrows it) is valid. #expect(compiles( base: "", restriction: "", )) // A user type whose local name is "anyType" (not the XSD ur-type) must not be // mistaken for it: this is a valid same-type restriction. #expect((try? PureXML.Schema.Document(""" """)) != nil) } @Test("NameAndTypeOK over named user and union types") func test_elementUserTypeDerivation() { func doc(_ elementC1: String, base baseC1: String, types: String) -> PureXML.Schema.Document? { try? PureXML.Schema.Document(""" \(types) """) } let userTypes = """ """ // bar restricts foo: a valid narrowing of a user type. #expect(doc("bar", base: "foo", types: userTypes) != nil) // foo restricting bar widens (foo is bar's base): invalid. #expect(doc("foo", base: "bar", types: userTypes) == nil) // two unrelated user types: invalid. #expect(doc("other", base: "foo", types: userTypes) == nil) // a union member (xs:string) validly restricts the union-typed base element. #expect(doc("xs:string", base: "u", types: userTypes) != nil) let unions = """ """ // A separately-declared union whose members merely subset the base union's // is NOT a valid derivation: cos-st-derived-ok requires the restricting // type to be the base, derive from it through a declared restriction chain, // or derive from one of its members. Structural member subsetting is not // the spec's derivation relation (cf. XSTS particlesIk011). #expect(doc("U1", base: "U2", types: unions) == nil) // The reverse (U2 has a member U1 lacks) is invalid for the same reason. #expect(doc("U2", base: "U1", types: unions) == nil) // a list type restricting a union type: invalid. #expect(doc("L1", base: "U1", types: unions) == nil) } } @Suite("XSD element-reference namespace resolution") struct XSDElementRefNamespaceTests { @Test("An element reference resolves to its prefix's namespace, not the target namespace") func test_elementRefNamespaceResolution() { // particlesJj001: a ref to an imported element (imp -> http://importedXSD) // restricts a `##other` wildcard. `##other` admits any namespace but the // target, so an imported element is admitted and the restriction is valid; // resolving the ref into the target namespace would make `##other` reject it. let imported = "" + "" + "" + "" + "" + "" #expect((try? PureXML.Schema.Document(imported)) != nil) // The same shape with a ref into the TARGET namespace (a declared global) is // invalid: `##other` forbids the target namespace, so the element is not // admitted. This proves the resolved namespace is actually checked. let targetNs = "" + "" + "" + "" + "" + "" #expect((try? PureXML.Schema.Document(targetNs)) == nil) } @Test("An unprefixed ref resolves through the default namespace, qualifying an instance element") func test_unprefixedRefUsesDefaultNamespace() { // The spec-faithful common idiom: the target namespace is also the default // xmlns, so a bare ref="child" resolves to the target namespace and a // target-namespace instance element validates against it. let schema = "" + "" + "" + "" guard let document = try? PureXML.Schema.Document(schema) else { Issue.record("schema should compile") return } #expect((try? document.validate("x"))?.isEmpty == true) // A child in the wrong namespace is rejected (the resolution is real, not a // blanket accept). let wrong = "x" #expect((try? document.validate(wrong))?.isEmpty != true) } }