fix(schema): subset-check anonymous complex-type restrictions (#145)
Particle Valid (Restriction) was keyed by type name, so an inline complexType deriving by complexContent restriction was never checked against its base. Such anonymous restrictions are now compiled and run through the same subset algorithm as named types. XSTS invalid-schemas-accepted 163 to 148, no other bucket moved.
This commit is contained in:
parent
ee5d180ba3
commit
d43d1bb1bb
@ -22,6 +22,8 @@ Second pre-release. Continues the W3C XSTS schema-validity campaign: **invalid-s
|
||||
|
||||
### Fixed
|
||||
|
||||
- XSTS invalid-schemas-accepted (#145): Particle Valid (Restriction) now also covers anonymous complex types. The named-type subset check was keyed by type name, so an inline `complexType` deriving by `complexContent` restriction (the common `<element><complexType><complexContent><restriction base="...">` shape) was never validated against its base. Such restrictions are now compiled and checked with the same algorithm. Caught fifteen invalid schemas: invalid-schemas-accepted 163 → 148, no other bucket moved.
|
||||
|
||||
- XSTS invalid-schemas-accepted (#145): Particle Valid (Restriction) now enforces the NameAndTypeOK block-superset clause. A restricting element's `{disallowed substitutions}` (its `block`, or the schema's `blockDefault`) must be a superset of the base element's, so a restriction may block more substitutions but never fewer. The element particle now carries this `block` set. Caught sixteen invalid schemas: invalid-schemas-accepted 179 → 163, no other bucket moved.
|
||||
|
||||
- XSTS invalid-schemas-accepted (#145): the RecurseAsIfGroup case of Particle Valid (Restriction) now checks occurrence. An element restricting a base model group is treated as a synthetic group with `minOccurs=maxOccurs=1` holding the element (XSD 1.0 §3.9.6), so a base group that must occur two or more times cannot be restricted to a single element. The element's own occurrence is still matched against the base branch it maps to, so an optional element restricting a once-or-more base group stays valid (no false rejection). Caught two invalid schemas: invalid-schemas-accepted 181 → 179, no other bucket moved.
|
||||
|
||||
@ -266,6 +266,7 @@ extension PureXML.Validation {
|
||||
AnyValidation(idValueConstraintsValid),
|
||||
AnyValidation(userTypeValueConstraintsValid),
|
||||
AnyValidation(extensionAllGroupsValid),
|
||||
AnyValidation(anonymousRestrictionsValid),
|
||||
AnyValidation(complexExtensionBaseValid),
|
||||
AnyValidation(attributeRestrictionsFaithful),
|
||||
AnyValidation(simpleTypeBasesAreSimple),
|
||||
@ -340,6 +341,22 @@ extension PureXML.Validation {
|
||||
}
|
||||
|
||||
extension PureXML.Validation.SchemaCompile {
|
||||
/// Particle Valid (Restriction) for anonymous (inline) complex types, which the
|
||||
/// name-keyed `restrictionsAreSubsets` rule never sees.
|
||||
static var anonymousRestrictionsValid: PureXML.Validation.Validation<PureXML.Schema.SchemaCompileRoot, PureXML.Schema.SchemaCompileContext> {
|
||||
compileRule("Anonymous complex-type restrictions accept a subset of their base's") { document in
|
||||
guard let namedTypes = document.namedTypes else { return [] }
|
||||
return PureXML.Schema.SchemaLocatedFinding.unlocated(
|
||||
PureXML.Schema.XSDParser.anonymousRestrictionErrors(
|
||||
document.schema,
|
||||
document.context,
|
||||
namedTypes,
|
||||
document.derivation?.typeDerivation ?? [:],
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// cos-ct-extends.1.4.2.2: a complexContent extension adding element content
|
||||
/// must have a base with complex (or empty) content, not simpleContent.
|
||||
static var complexExtensionBaseValid: PureXML.Validation.Validation<PureXML.Schema.SchemaCompileRoot, PureXML.Schema.SchemaCompileContext> {
|
||||
|
||||
@ -39,6 +39,43 @@ extension PureXML.Schema.XSDParser {
|
||||
return errors
|
||||
}
|
||||
|
||||
/// Particle Valid (Restriction) for ANONYMOUS complex types: the named-type
|
||||
/// rule (`restrictionsAreSubsets`) is keyed by type name and so never sees an
|
||||
/// inline `complexType` that derives by `complexContent` restriction (the
|
||||
/// common `<element><complexType><complexContent><restriction base="...">`
|
||||
/// shape). This walks those, compiles each restricting type, resolves its base
|
||||
/// in this schema's own target namespace, and runs the same subset check. Only a
|
||||
/// self-contained schema is examined; the same `ParticleRestriction.violation`
|
||||
/// used for named types keeps the false-positive profile identical.
|
||||
static func anonymousRestrictionErrors(
|
||||
_ schema: XSDTree,
|
||||
_ context: PureXML.Schema.XSDContext,
|
||||
_ types: [String: PureXML.Schema.ElementType],
|
||||
_ derivation: [String: PureXML.Schema.TypeDerivation],
|
||||
) -> [String] {
|
||||
guard !skipsCrossDocumentRules(schema, compositionLoaded: context.compositionLoaded) else { return [] }
|
||||
let bindings = context.namespaceBindings
|
||||
var errors: [String] = []
|
||||
for complexType in descendants(schema, named: "complexType") where ExtAllNode.attribute(complexType, "name") == nil {
|
||||
guard let content = ExtAllNode.firstChild(complexType, named: "complexContent"),
|
||||
let restriction = ExtAllNode.firstChild(content, named: "restriction"),
|
||||
let baseRef = ExtAllNode.attribute(restriction, "base"),
|
||||
ExtAllNode.referenceNamespace(baseRef, bindings) == context.targetNamespace,
|
||||
case let .complex(base)? = types[ExtAllNode.stripPrefix(baseRef)]
|
||||
else { continue }
|
||||
let restricted = PureXML.Schema.XSDParser.complexType(complexType, context)
|
||||
if let reason = PureXML.Schema.ParticleRestriction.violation(
|
||||
restricted: restricted.content,
|
||||
base: base.content,
|
||||
types: types,
|
||||
derivation: derivation,
|
||||
) {
|
||||
errors.append("an anonymous complex type is not a valid restriction of '\(ExtAllNode.stripPrefix(baseRef))': \(reason)")
|
||||
}
|
||||
}
|
||||
return errors
|
||||
}
|
||||
|
||||
/// Whether a content type's whole content model is a NON-EMPTY `all` group. An
|
||||
/// empty `<all/>` contributes no content (the sibling rule treats extending
|
||||
/// empty content as valid), so it is not flagged.
|
||||
|
||||
@ -100,6 +100,7 @@ struct ValidationConfigurationTests {
|
||||
"ID-typed value constraints are valid",
|
||||
"Element value constraints are valid against their declared types",
|
||||
"Complex types extending xs:all groups satisfy XSD placement rules",
|
||||
"Anonymous complex-type restrictions accept a subset of their base's",
|
||||
"A complexContent extension is consistent with its base type",
|
||||
"Attribute restrictions are faithful to their bases",
|
||||
"Simple types do not derive from complex types",
|
||||
|
||||
@ -91,8 +91,12 @@ struct XSTSSuiteTests {
|
||||
/// `{disallowed substitutions}` must be a superset of the base element's, with
|
||||
/// `block`/`blockDefault` carried on the particle) then caught sixteen more
|
||||
/// invalid schemas: invalid-schemas accepted 179 → 163, no other bucket moved.
|
||||
/// Subset-checking ANONYMOUS complex-type restrictions (inline `<complexType>`
|
||||
/// declarations, which the named-type rule never saw) with the same
|
||||
/// Particle-Valid-Restriction algorithm then caught fifteen more invalid schemas:
|
||||
/// invalid-schemas accepted 163 → 148, no other bucket moved.
|
||||
private let knownSchemaValidRejected = 1
|
||||
private let knownSchemaInvalidAccepted = 163
|
||||
private let knownSchemaInvalidAccepted = 148
|
||||
private let knownInstanceValidRejected = 3
|
||||
private let knownInstanceInvalidAccepted = 132
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user