fix(schema): reject xsi:type=anyType on an anySimpleType-declared element
cvc-elt.4.3.2.1 requires an xsi:type to name a type DERIVED from the element's declared type. anyType is the ur-supertype of every type, so it is never derived from anySimpleType (its own subtype); an anyType override on an anySimpleType element is therefore invalid. The general not-derived check stays silent on ur-type declared types to avoid false positives, so this adds the narrow, provably-invalid anyType-for-anySimpleType pairing only, leaving anyType valid on an anyType-declared element (which resolves to a complex ur-type and is excluded). XSTS invalid-instances-accepted 30 -> 29 (stZ056), no other bucket moved (valid-schemas-rejected and valid-instances-rejected held at 0 across all 14383 groups). 1450 unit tests pass.
This commit is contained in:
parent
01650a454e
commit
6927eb26f1
@ -28,6 +28,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
### Fixed
|
||||
|
||||
- `xsi:type` naming `anyType` is now rejected on an element whose declared type is
|
||||
`anySimpleType` (cvc-elt.4.3.2.1). `anyType` is the ur-supertype of every type,
|
||||
so it is never validly *derived* from `anySimpleType` (its own subtype). The
|
||||
general not-derived check stays silent on ur-type declared types to avoid false
|
||||
positives; this adds only the provably-invalid `anyType`-for-`anySimpleType`
|
||||
pairing, leaving `anyType` valid on an `anyType`-declared element. XSTS
|
||||
invalid-instances-accepted 30 -> 29 (`stZ056`), no other bucket moved.
|
||||
|
||||
- `dateTime`, `date`, `gYearMonth`, and `gYear` now reject the year `0000`. XSD
|
||||
1.0 has no year zero (the year before `0001` is `-0001`), so `0000-01-01T00:00:00`
|
||||
and the other `0000` forms are not valid values. `DateTimeParser.parseYearOnly`
|
||||
|
||||
@ -16,6 +16,7 @@ extension PureXML.Schema.ComplexValidator {
|
||||
) -> PureXML.Validation.ValidationError? {
|
||||
if let blocked = blockedSubstitutionError(declared: declared, child: child, at: path, namespaceBindings: namespaceBindings) { return blocked }
|
||||
if let notDerived = notDerivedSubstitutionError(declared: declared, child: child, at: path, namespaceBindings: namespaceBindings) { return notDerived }
|
||||
if let anyTypeSub = anyTypeForAnySimpleTypeError(declared: declared, child: child, at: path, namespaceBindings: namespaceBindings) { return anyTypeSub }
|
||||
if let listUnion = listUnionSubstitutionError(declared: declared, child: child, at: path, namespaceBindings: namespaceBindings) { return listUnion }
|
||||
if let abstractType = abstractXsiTypeError(child: child, at: path, namespaceBindings: namespaceBindings) { return abstractType }
|
||||
if case let .typeReference(name) = declared { return abstractTypeError(named: name, child: child, at: path) }
|
||||
@ -66,6 +67,42 @@ extension PureXML.Schema.ComplexValidator {
|
||||
)
|
||||
}
|
||||
|
||||
/// cvc-elt.4.3.2.1 for an `anyType` `xsi:type`: `anyType` is the ur-supertype of
|
||||
/// every type, so it is never DERIVED from `anySimpleType` (its own subtype). An
|
||||
/// `anyType` override on an `anySimpleType`-declared element is therefore invalid.
|
||||
/// The general not-derived check stays silent on ur-type declared types to avoid
|
||||
/// false positives; this rejects only the provably-invalid anyType-for-anySimpleType
|
||||
/// pairing. `anyType` still validly substitutes for an `anyType`-declared element,
|
||||
/// which `declaredIsAnySimpleType` excludes (that resolves to a complex ur-type).
|
||||
func anyTypeForAnySimpleTypeError(
|
||||
declared: PureXML.Schema.ElementType,
|
||||
child: PureXML.Model.Element,
|
||||
at path: [PureXML.Validation.PathKey],
|
||||
namespaceBindings: [String: String],
|
||||
) -> PureXML.Validation.ValidationError? {
|
||||
guard let label = Self.xsiTypeName(child),
|
||||
let reference = Self.xsiTypeReference(child, namespaceBindings: namespaceBindings),
|
||||
case let .complex(substitute)? = Self.resolveNamedType(reference, in: types),
|
||||
Self.isUrType(substitute),
|
||||
declaredIsAnySimpleType(declared)
|
||||
else { return nil }
|
||||
return PureXML.Validation.ValidationError(
|
||||
reason: "xsi:type '\(label)' is not validly derived from the anySimpleType declared type of '\(child.name.localName)'",
|
||||
at: path,
|
||||
)
|
||||
}
|
||||
|
||||
/// Whether `declared` resolves to the simple ur-type `anySimpleType` itself, not
|
||||
/// `anyType` (a complex ur-type), a list/union, or any more specific type.
|
||||
private func declaredIsAnySimpleType(_ declared: PureXML.Schema.ElementType) -> Bool {
|
||||
let resolved: PureXML.Schema.ElementType? = switch declared {
|
||||
case .simple, .complex: declared
|
||||
case let .typeReference(reference): resolvedDeclaredType(reference)
|
||||
}
|
||||
if case let .simple(type)? = resolved { return type.isAnySimpleType }
|
||||
return false
|
||||
}
|
||||
|
||||
/// Whether a list or union substitute may validly stand in for `declared`: only
|
||||
/// the ur-types (`anySimpleType`/`anyType`) admit one. A declared type that is
|
||||
/// itself a list or union, or an unresolvable reference, is also treated as
|
||||
|
||||
@ -113,4 +113,25 @@ struct SchemaListUnionXsiTypeTests {
|
||||
// Drr restricts Dr: validly derived, accepted.
|
||||
#expect(try schema.validate(doc("Drr")).isEmpty)
|
||||
}
|
||||
|
||||
@Test("xsi:type=anyType cannot substitute for an anySimpleType-declared element")
|
||||
func test_anyTypeForAnySimpleType() throws {
|
||||
let xsi = "http://www.w3.org/2001/XMLSchema-instance"
|
||||
let schema = try PureXML.Schema.Document("""
|
||||
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
|
||||
<xs:element name="item" type="xs:anySimpleType"/>
|
||||
<xs:element name="root">
|
||||
<xs:complexType><xs:sequence><xs:element ref="item" maxOccurs="unbounded"/></xs:sequence></xs:complexType>
|
||||
</xs:element>
|
||||
</xs:schema>
|
||||
""")
|
||||
func root(_ type: String, _ value: String) -> String {
|
||||
#"<root xmlns:xsi="\#(xsi)" xmlns:xs="http://www.w3.org/2001/XMLSchema"><item xsi:type="\#(type)">\#(value)</item></root>"#
|
||||
}
|
||||
// anyType is anySimpleType's supertype, not derived from it: rejected, for that reason.
|
||||
#expect(try schema.validate(root("xs:anyType", "x")).contains { $0.reason.contains("anySimpleType declared type") })
|
||||
// anySimpleType (reflexive) and a derived atomic type are valid substitutes.
|
||||
#expect(try schema.validate(root("xs:anySimpleType", "x")).isEmpty)
|
||||
#expect(try schema.validate(root("xs:int", "123")).isEmpty)
|
||||
}
|
||||
}
|
||||
|
||||
@ -150,13 +150,13 @@ struct XSTSSuiteTests {
|
||||
private let knownSchemaValidRejected = 0
|
||||
private let knownSchemaInvalidAccepted = 43
|
||||
private let knownInstanceValidRejected = 0
|
||||
/// Instance bucket (133 -> 30), per-step deltas in CHANGELOG.md: xsi:type must derive from the
|
||||
/// declared type (complex, then atomic and list/union, resolved through the element-ref chain);
|
||||
/// block/blockDefault (incl. "substitution" and an untyped head) bar substitutions after the
|
||||
/// derivation backbone was re-keyed by namespace; ur-type `xsd:anyType` wildcards are `lax`;
|
||||
/// identity fields compare in the decimal value space and take a declared default/fixed when
|
||||
/// absent/empty; an attribute wildcard is the INTERSECTION of its sources; year 0000 is invalid.
|
||||
private let knownInstanceInvalidAccepted = 30
|
||||
/// Instance bucket (133 -> 29), per-step deltas in CHANGELOG.md: xsi:type must derive from the
|
||||
/// declared type (complex/atomic/list/union via the element-ref chain), and anyType cannot stand
|
||||
/// in for an anySimpleType element (its supertype); block/blockDefault bar substitutions on the
|
||||
/// namespace-keyed backbone; ur-type anyType wildcards are `lax`; identity fields compare in
|
||||
/// decimal value space and take a declared default/fixed when absent/empty; an attribute wildcard
|
||||
/// is the INTERSECTION of its sources; dateTime/date/gYear* reject year 0000.
|
||||
private let knownInstanceInvalidAccepted = 29
|
||||
|
||||
@Test("Every XSTS case behaves: compile, reject, validate, invalidate")
|
||||
func test_suite() throws {
|
||||
|
||||
@ -101,7 +101,7 @@ classification is wrong.
|
||||
| Known particle under-rejection fallout | `particlesZ001/particlesZ001.i` | `ParticleRestriction` |
|
||||
| Regex instance edges | `reT17/reT17.i`, `reT38/reT38.i`, `RegexTest_422/RegexTest_422.i`, `RegexTest_430/RegexTest_430.i`, `reZ006i/reZ006i.i` | `Sources/Regex/*`, `SimpleType` |
|
||||
| Schema composition instance fallout | `schA2/schA2.i`, `schA5/schA5.i`, `schA7/schA7.i`, `schU3/schU3.i`, `schU4/schU4.i`, `schU5/schU5.i` | schema composition and instance validator |
|
||||
| Simple-type instance edge | `stZ056/stZ056.i` | `SimpleType` |
|
||||
| Simple-type instance edge | ~~`stZ056/stZ056.i`~~ closed: `xsi:type=anyType` cannot substitute for an `anySimpleType` element (its supertype) | `ComplexValidatorDerivation` |
|
||||
| Wildcard instance matching | `wildZ013a/wildZ013a.i`, `wildZ013d/wildZ013d.i` | `ContentMatcher`, wildcard declaration lookup |
|
||||
|
||||
## Tasks (ordered)
|
||||
|
||||
Loading…
Reference in New Issue
Block a user