diff --git a/CHANGELOG.md b/CHANGELOG.md index ceca15f..5866074 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): 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 `` 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. diff --git a/Sources/Schema/SchemaCompileValidation.swift b/Sources/Schema/SchemaCompileValidation.swift index 43a0fa6..209ca7e 100644 --- a/Sources/Schema/SchemaCompileValidation.swift +++ b/Sources/Schema/SchemaCompileValidation.swift @@ -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 { + 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 { diff --git a/Sources/Schema/SchemaExtensionAllGroup.swift b/Sources/Schema/SchemaExtensionAllGroup.swift index 66cbb6c..754558e 100644 --- a/Sources/Schema/SchemaExtensionAllGroup.swift +++ b/Sources/Schema/SchemaExtensionAllGroup.swift @@ -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 `` + /// 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 `` contributes no content (the sibling rule treats extending /// empty content as valid), so it is not flagged. diff --git a/Tests/ValidationConfigurationTests.swift b/Tests/ValidationConfigurationTests.swift index 4d978ff..af09471 100644 --- a/Tests/ValidationConfigurationTests.swift +++ b/Tests/ValidationConfigurationTests.swift @@ -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", diff --git a/Tests/XSTSSuiteTests.swift b/Tests/XSTSSuiteTests.swift index e0ec23f..356953c 100644 --- a/Tests/XSTSSuiteTests.swift +++ b/Tests/XSTSSuiteTests.swift @@ -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 `` + /// 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