fix(schema): an untyped substitutionGroup member inherits its head's type
XSD 1.0 3.3.2: an element with neither a `type` attribute nor an inline type, but
a `substitutionGroup`, takes the {type definition} of its head. PureXML defaulted
such an element to `anyType`, so an instance of it accepted any content.
`XSDParser.elementType` now resolves the head declaration through
`context.globalElements` (transitively, with a cycle guard) and uses its type.
XSTS invalid-instances-accepted 29 -> 28 (typeDef00204m: <root>Yes</root> is no
longer accepted where root substitutes a boolean head), no other bucket moved
(valid-schemas-rejected and valid-instances-rejected held at 0 across all 14383
groups). 1451 unit tests pass.
This commit is contained in:
parent
6927eb26f1
commit
45f2e7dbf5
@ -28,6 +28,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
### Fixed
|
||||
|
||||
- An element with neither a `type` attribute nor an inline type, but a
|
||||
`substitutionGroup`, now takes the type definition of its head (XSD 1.0 3.3.2)
|
||||
instead of defaulting to `anyType`. `XSDParser.elementType` resolves the head
|
||||
declaration (transitively, guarding a `substitutionGroup` cycle) and uses its
|
||||
type, so an instance of such a member is validated against the head's type. XSTS
|
||||
invalid-instances-accepted 29 -> 28 (`typeDef00204m`), no other bucket moved
|
||||
(valid-rejected held at 0 across all 14383 groups).
|
||||
|
||||
- `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
|
||||
|
||||
@ -11,7 +11,11 @@ private typealias SimpleType = PureXML.Schema.SimpleType
|
||||
extension PureXML.Schema.XSDParser {
|
||||
// MARK: Element and type references
|
||||
|
||||
static func elementType(_ node: PureXML.Model.TreeNode, _ context: PureXML.Schema.XSDContext) -> PureXML.Schema.ElementType {
|
||||
static func elementType(
|
||||
_ node: PureXML.Model.TreeNode,
|
||||
_ context: PureXML.Schema.XSDContext,
|
||||
resolvingHeads: Set<String> = [],
|
||||
) -> PureXML.Schema.ElementType {
|
||||
if let typeName = XSDNode.attribute(node, "type") {
|
||||
return typeReference(typeName, context, at: node)
|
||||
}
|
||||
@ -21,6 +25,16 @@ extension PureXML.Schema.XSDParser {
|
||||
if let inline = XSDNode.firstChild(node, named: "complexType") {
|
||||
return .complex(complexType(inline, context))
|
||||
}
|
||||
// XSD 1.0 3.3.2: an element with neither `type` nor an inline type, but a
|
||||
// `substitutionGroup`, takes the {type definition} of its head. Resolve the
|
||||
// head declaration and use its type, guarding against a substitutionGroup cycle.
|
||||
if let headReference = XSDNode.attribute(node, "substitutionGroup") {
|
||||
let bindings = PureXML.Schema.XSDParser.namespaceBindingsInScope(of: node, defaultBindings: context.namespaceBindings)
|
||||
let headKey = derivationKey(XSDNode.stripPrefix(headReference), in: XSDNode.referenceNamespace(headReference, bindings))
|
||||
if !resolvingHeads.contains(headKey), let headNode = context.globalElements[headKey] {
|
||||
return elementType(headNode, context, resolvingHeads: resolvingHeads.union([headKey]))
|
||||
}
|
||||
}
|
||||
return .complex(anyType)
|
||||
}
|
||||
|
||||
|
||||
@ -264,4 +264,18 @@ struct SchemaSubstitutionTypeTests {
|
||||
+ "</xs:element>",
|
||||
))
|
||||
}
|
||||
|
||||
@Test("an untyped substitutionGroup member inherits its head's type for instance validation")
|
||||
func test_untypedMemberInheritsHeadType() throws {
|
||||
let schema = try PureXML.Schema.Document("""
|
||||
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
|
||||
<xs:element name="Head" type="xs:boolean"/>
|
||||
<xs:element name="root" substitutionGroup="Head"/>
|
||||
</xs:schema>
|
||||
""")
|
||||
// `root` has no type of its own but substitutes `Head` (boolean), so it
|
||||
// inherits boolean: a valid boolean is accepted, a non-boolean rejected.
|
||||
#expect(try schema.validate("<root>true</root>").isEmpty)
|
||||
#expect(try !schema.validate("<root>Yes</root>").isEmpty)
|
||||
}
|
||||
}
|
||||
|
||||
@ -150,13 +150,13 @@ struct XSTSSuiteTests {
|
||||
private let knownSchemaValidRejected = 0
|
||||
private let knownSchemaInvalidAccepted = 43
|
||||
private let knownInstanceValidRejected = 0
|
||||
/// 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
|
||||
/// Instance bucket (133 -> 28), per-step deltas in CHANGELOG.md: xsi:type must derive from the
|
||||
/// declared type (complex/atomic/list/union via the element-ref chain); anyType cannot stand in
|
||||
/// for an anySimpleType element; an untyped substitutionGroup member inherits its head's type;
|
||||
/// block/blockDefault bar substitutions on the namespace-keyed backbone; ur-type anyType wildcards
|
||||
/// are `lax`; identity fields compare in decimal value space, taking a declared default/fixed when
|
||||
/// absent/empty; an attribute wildcard is the INTERSECTION of its sources; year 0000 is invalid.
|
||||
private let knownInstanceInvalidAccepted = 28
|
||||
|
||||
@Test("Every XSTS case behaves: compile, reject, validate, invalidate")
|
||||
func test_suite() throws {
|
||||
|
||||
@ -92,7 +92,7 @@ classification is wrong.
|
||||
| Target namespace / form matching | `targetns00101m/targetNS00101m1_n` | `ComplexValidator*`, reference resolution |
|
||||
| Disallowed substitution | `disallowedsubst00105m/disallowedSubst00105m1_n`, `disallowedsubst00106m2/Negative` | `XSDSubstitutionMembers`, `ComplexValidatorXSIType` |
|
||||
| Nillability | `nillable00201m/nillable00201m2_n` | `XSDParserHelpers`, instance validator |
|
||||
| Type derivation | `typedef00204m/typeDef00204m1_n` | `ComplexValidatorXSIType`, derivation tables |
|
||||
| Type derivation | ~~`typedef00204m/typeDef00204m1_n`~~ closed: an untyped `substitutionGroup` member inherits its head's type (XSD 1.0 3.3.2) | `XSDParser.elementType` |
|
||||
| Additional-suite instance triage | `addB065/addB065.i` | to classify from fixture |
|
||||
| Datatype value-space edges | `anyURI_a004_1339/anyURI_a004_1339.i` (open); ~~`dateTime011_2008/dateTime011_2008.i`~~ closed: XSD 1.0 has no year zero, `0000` now rejected | `SimpleType`, `DateTimeParser` |
|
||||
| Element constraints | `elemO011/elemO011.i`, `elemT074/elemT074.i` | instance validator, element declarations |
|
||||
|
||||
Loading…
Reference in New Issue
Block a user