diff --git a/CHANGELOG.md b/CHANGELOG.md index f54e3ef..a654410 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,8 @@ Second pre-release. Continues the W3C XSTS schema-validity campaign: **invalid-s ### Fixed +- XSTS invalid-schemas-accepted (#145): a simple type without its own `final` attribute now inherits the schema's `finalDefault` as its `{final}` property (XSD 1.0 Schemas §3.14.2), so `finalDefault="list"` or `"union"` blocks the matching list-item or union-member derivation just as an explicit `final` would. An explicit `final=""` still overrides the default back to no restriction. Caught two invalid schemas: invalid-schemas-accepted 217 → 215, no other bucket moved. + - XSTS invalid-schemas-accepted (#145): a `pattern` facet whose regular expression ends mid-token is now rejected as a schema-validity error. A trailing `\` with no character to escape (`a\`) raises `RegexError.incompleteEscape`, and a `[` character class the expression ends before closing with `]` (`a[`, `[a[:xyz:`) raises `RegexError.unterminatedClass`; both are unambiguously invalid rather than an engine limitation on an otherwise-valid pattern, so they reject. Caught thirteen invalid schemas: invalid-schemas-accepted 230 → 217, no other bucket moved. - XSTS invalid-schemas-accepted (#145): an attribute use that references a global attribute carrying a `fixed` value constraint may not contradict it. A `fixed` on the reference whose value differs from the global's (compared in the attribute type's value space, so `1` and `01` are not wrongly flagged), or a `default` on a reference to a fixed global, is invalid (`au-props-correct.2`). A reference that omits its own value constraint inherits the global's fixed value and stays valid. Caught two invalid schemas: invalid-schemas-accepted 232 → 230, no other bucket moved. diff --git a/Sources/Schema/SchemaSimpleTypeFinal.swift b/Sources/Schema/SchemaSimpleTypeFinal.swift index 678efe1..f4ce552 100644 --- a/Sources/Schema/SchemaSimpleTypeFinal.swift +++ b/Sources/Schema/SchemaSimpleTypeFinal.swift @@ -39,11 +39,20 @@ extension PureXML.Schema.XSDParser { } /// Each named simple type mapped to the derivation tokens its `final` forbids. + /// A type without its own `final` inherits the schema's `finalDefault` (its + /// `{final}` property; XSD 1.0 Schemas §3.14.2), so `finalDefault="list"` + /// makes every such type final for list derivation. An explicit `final=""` + /// still overrides the default back to no restriction. private static func simpleTypeFinalMap(_ schema: XSDTree) -> [String: Set] { + let fallback = finalTokens(FinalNode.attribute(schema, "finalDefault")) var finalOf: [String: Set] = [:] for simpleType in descendants(schema, named: "simpleType") { guard let name = FinalNode.attribute(simpleType, "name") else { continue } - finalOf[name] = finalTokens(FinalNode.attribute(simpleType, "final")) + if let own = FinalNode.attribute(simpleType, "final") { + finalOf[name] = finalTokens(own) + } else { + finalOf[name] = fallback + } } return finalOf } diff --git a/Tests/SchemaSimpleTypeFinalTests.swift b/Tests/SchemaSimpleTypeFinalTests.swift index dc39d5f..bea460a 100644 --- a/Tests/SchemaSimpleTypeFinalTests.swift +++ b/Tests/SchemaSimpleTypeFinalTests.swift @@ -46,6 +46,25 @@ struct SchemaSimpleTypeFinalTests { #expect(compiles(plain + unionOfT)) } + /// A type without its own `final` inherits the schema's `finalDefault` as its + /// `{final}`, so `finalDefault="list"`/`"union"` forbids the matching derivation + /// just as an explicit `final` would. An explicit `final=""` overrides it back. + @Test("finalDefault is inherited as the type's final") + func test_finalDefaultInherited() { + func compilesWith(_ finalDefault: String, _ body: String) -> Bool { + (try? PureXML.Schema.Document("\(body)")) != nil + } + let plain = "" + let empty = "" + #expect(!compilesWith("list", plain + listOfT)) + #expect(!compilesWith("union", plain + unionOfT)) + // finalDefault names only one direction; the other stays open. + #expect(compilesWith("list", plain + unionOfT)) + #expect(compilesWith("union", plain + listOfT)) + // An explicit empty final overrides the default back to no restriction. + #expect(compilesWith("list", empty + listOfT)) + } + /// A user type sharing a built-in's local name (here `string`, `final="list"`) /// must not be confused with a reference to the built-in `xs:string`: a list of /// `xs:string` is valid because the built-in's final is empty. The reference is diff --git a/Tests/XSTSSuiteTests.swift b/Tests/XSTSSuiteTests.swift index 0d990bc..43dfe79 100644 --- a/Tests/XSTSSuiteTests.swift +++ b/Tests/XSTSSuiteTests.swift @@ -96,8 +96,12 @@ struct XSTSSuiteTests { /// closed with `]`), both unambiguously invalid and never an engine /// limitation, then caught thirteen more invalid schemas: invalid-schemas /// accepted 230 → 217, no other bucket moved. + /// Inheriting the schema's `finalDefault` as a simple type's `{final}` when the + /// type declares no `final` of its own (so `finalDefault="list"`/`"union"` + /// blocks the matching list/union derivation) then caught two more invalid + /// schemas: invalid-schemas accepted 217 → 215, no other bucket moved. private let knownSchemaValidRejected = 1 - private let knownSchemaInvalidAccepted = 217 + private let knownSchemaInvalidAccepted = 215 private let knownInstanceValidRejected = 3 private let knownInstanceInvalidAccepted = 132