fix(schema): inherit finalDefault as a simple type's final (#145)
A simple type that declares no `final` of its own takes the schema's
`finalDefault` as its `{final}` property (XSD 1.0 Schemas section 3.14.2). The
list and union derivation checks read the `final` attribute directly but
ignored `finalDefault`, so `finalDefault="list"` or `"union"` did not block a
list item type or union member type and the schema was wrongly accepted.
The final map now falls back to `finalDefault` for a type without its own
`final`; an explicit `final=""` still overrides the default back to no
restriction, and a built-in item or member type stays unaffected (it is matched
only when the reference resolves to the schema's own namespace).
Caught two invalid schemas: XSTS invalid-schemas-accepted 217 -> 215, no other
bucket moved.
This commit is contained in:
parent
50c6fb9b1c
commit
8420c7e2aa
@ -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.
|
||||
|
||||
@ -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<String>] {
|
||||
let fallback = finalTokens(FinalNode.attribute(schema, "finalDefault"))
|
||||
var finalOf: [String: Set<String>] = [:]
|
||||
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
|
||||
}
|
||||
|
||||
@ -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("<xs:schema \(xsd) finalDefault=\"\(finalDefault)\">\(body)</xs:schema>")) != nil
|
||||
}
|
||||
let plain = "<xs:simpleType name=\"T\"><xs:restriction base=\"xs:string\"/></xs:simpleType>"
|
||||
let empty = "<xs:simpleType name=\"T\" final=\"\"><xs:restriction base=\"xs:string\"/></xs:simpleType>"
|
||||
#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
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user