import Testing @testable import PureXML /// Unique Particle Attribution (cos-nonambig, XSD 1.0 3.8.6): a content model /// must be deterministic, so each element in an instance is attributable to a /// single particle without lookahead. The check runs on a literal, QName-resolved /// view of the raw schema tree (refs are not substitution-group expanded, the /// overlap test is QName-only), and is per particle: a particle competing with a /// repetition of itself is never a violation. These cases pin both the rejections /// and the deterministic shapes that must keep compiling (XSTS valid mgZ005, /// ctZ008, particlesHa142, groupH001). @Suite("Content model determinism (UPA)") struct ContentModelDeterminismTests { private func compile(_ body: String) throws { _ = try PureXML.Schema.Document(""" \(body) """) } private func rejects(_ body: String) -> Bool { do { try compile(body) return false } catch { return true } } private func complexType(_ model: String) -> String { #"\#(model)"# } // MARK: - Ambiguous models are rejected @Test("a choice of two same-name elements is ambiguous") func test_choiceOfSameName() { #expect(rejects(complexType(#""#))) } @Test("an optional element followed by a same-name element is ambiguous") func test_optionalThenSameName() { #expect(rejects(complexType(#""#))) } @Test("a repeating element followed by a same-name element is ambiguous") func test_repeatThenSameName() { #expect(rejects(complexType(#""#))) } @Test("a choice of ##other and an overlapping namespace wildcard is ambiguous") func test_choiceOfOtherAndOverlappingWildcard() { // `##other` admits namespace A (A is neither absent nor the target), and so // does `namespace="A"`, so an A-namespaced element matches both (XSTS wildI009). #expect(rejects(complexType(""))) } @Test("a choice of two ##other wildcards is ambiguous") func test_choiceOfTwoOtherWildcards() { #expect(rejects(complexType(""))) } @Test("a choice of ##other and ##local is deterministic and compiles") func test_choiceOfOtherAndLocalCompiles() { // `##other` never admits the absent namespace and `##local` admits only it, // so they are disjoint: the model is deterministic and must not be rejected. #expect(!rejects(complexType(""))) } @Test("an extension is checked against its base's effective content model") func test_extensionEffectiveModelAmbiguous() { // The base's trailing `a*` is followed by the extension's `a`, so an `a` // matches either across the extension boundary (XSTS particlesZ022). The // check must see the base content prepended, not the extension's own only. let body = """ """ #expect(rejects(body)) } @Test("an extension adding distinct content is deterministic and compiles") func test_extensionEffectiveModelDeterministic() { // base `a` followed by the extension's `b` is `a, b`: deterministic, so // prepending the base content must not reject a valid extension. let body = """ """ #expect(!rejects(body)) } @Test("two same-name members of an all group are ambiguous") func test_allWithSameName() { #expect(rejects(complexType(#""#))) } @Test("an element competing with a wildcard that admits it is ambiguous") func test_elementAndWildcard() { #expect(rejects(complexType(#""#))) } // MARK: - Deterministic models keep compiling @Test("distinct elements in sequence and choice are deterministic") func test_distinctNames() throws { try compile(complexType(#""#)) try compile(complexType(#""#)) try compile(complexType(#""#)) } @Test("a fixed-count element followed by a distinct same-name element is deterministic (mgZ005)") func test_fixedCountThenSameName() throws { try compile(complexType(#""" """#)) } @Test("a fixed-count group wrapping a repeating element is deterministic (ctZ008)") func test_fixedCountGroupWithRepeat() throws { try compile(complexType(#""" """#)) } @Test("an element followed by its own optional repeat is deterministic (particlesHa142)") func test_elementThenOptionalSameName() throws { try compile(complexType(#""" """#)) } @Test("a bounded-repeat choice of group references is deterministic (groupH001)") func test_repeatedChoiceOfGroups() throws { try compile(#""" """#) } // MARK: - Redefine composition @Test("a redefine whose self-reference duplicates a particle is ambiguous across documents (schN10)") func test_redefineSelfReferenceDuplicatesParticle() { // The base declares group g = choice(a, b) and a complex type that uses it. // The redefinition expands g to choice(, a): the // self-reference brings the original's `a` back, so an `a` is attributable // to two particles -> a UPA violation. The ambiguous content model lives in // the BASE document's complex type, reached only through the redefined group, // so this exercises both the self-reference expansion in the determinism // engine and running the UPA check over every composition root. let base = """ """ let redefining = """ """ let rejected: Bool do { _ = try PureXML.Schema.Document(redefining, schemaLoader: { $0.hasSuffix("base.xsd") ? base : nil }) rejected = false } catch { rejected = true } #expect(rejected) } @Test("a redefine self-reference that does not duplicate a particle still compiles") func test_redefineSelfReferenceWithoutDuplicateCompiles() { // Same shape, but the redefinition adds a DISTINCT element `c`, so the // expanded model choice(choice(a, b), c) has no overlap and stays valid: // the FP-guard for the case above (the change must not over-reject). let base = """ """ let redefining = """ """ let rejected: Bool do { _ = try PureXML.Schema.Document(redefining, schemaLoader: { $0.hasSuffix("base.xsd") ? base : nil }) rejected = false } catch { rejected = true } #expect(!rejected) } }