diff --git a/Sources/Schema/SchemaCompileValidation.swift b/Sources/Schema/SchemaCompileValidation.swift index c92b1b5..043184e 100644 --- a/Sources/Schema/SchemaCompileValidation.swift +++ b/Sources/Schema/SchemaCompileValidation.swift @@ -243,11 +243,11 @@ extension PureXML.Validation { static func preCompileValidator() -> Validator { Validator.defaults( nonReference: [ - AnyValidation(idAttributesValid), - AnyValidation(schemaStructureValid), + AnyValidation(idAttributesValid), AnyValidation(schemaStructureValid), AnyValidation(componentNamesUnique), AnyValidation(simpleTypeFinalControlsValid), - AnyValidation(contentModelsDeterministic), AnyValidation(referencedSchemasResolveToSchemas), + AnyValidation(contentModelsDeterministic), + AnyValidation(referencedSchemasResolveToSchemas), AnyValidation(redefinitionsDeriveFromThemselves), ], reference: [ AnyValidation(typeDerivationAcyclic), @@ -320,7 +320,7 @@ extension PureXML.Validation { )) } - private static func compileRule( + static func compileRule( _ description: String, _ findings: @escaping (PureXML.Schema.SchemaCompileContext) -> [PureXML.Schema.SchemaLocatedFinding], ) -> Validation { @@ -341,17 +341,6 @@ extension PureXML.Validation { } extension PureXML.Validation.SchemaCompile { - /// src-resolve: a resolved include/import/redefine schemaLocation must be a schema. - static var referencedSchemasResolveToSchemas: PureXML.Validation.Validation { - compileRule("A resolved schemaLocation reference is a valid schema") { document in - PureXML.Schema.SchemaLocatedFinding.unlocated( - document.context.failedSchemaReferences.map { - "the referenced schema document '\($0)' is not a valid schema" - }, - ) - } - } - /// au-props-correct: an attribute's type is a simple type, never complex. static var attributeTypesSimple: PureXML.Validation.Validation { compileRule("An attribute's type is a simple type") { document in diff --git a/Sources/Schema/SchemaCompositionValidations.swift b/Sources/Schema/SchemaCompositionValidations.swift new file mode 100644 index 0000000..c4f3cbe --- /dev/null +++ b/Sources/Schema/SchemaCompositionValidations.swift @@ -0,0 +1,27 @@ +extension PureXML.Validation.SchemaCompile { + /// src-resolve: a resolved `include`/`import`/`redefine` `schemaLocation` (one the + /// loader returned content for) must be a well-formed schema document. The + /// failures are recorded on the context during composition; an unresolved + /// location (the loader returned nothing) is not recorded, since resolution is + /// the processor's choice. + static var referencedSchemasResolveToSchemas: PureXML.Validation.Validation { + compileRule("A resolved schemaLocation reference is a valid schema") { document in + PureXML.Schema.SchemaLocatedFinding.unlocated( + document.context.failedSchemaReferences.map { + "the referenced schema document '\($0)' is not a valid schema" + }, + ) + } + } + + /// src-redefine.5: every type inside `xs:redefine` must restrict or extend the + /// type it redefines (same local name, in the redefining schema's own target + /// namespace). + static var redefinitionsDeriveFromThemselves: PureXML.Validation.Validation { + compileRule("A redefined type restricts or extends itself") { document in + PureXML.Schema.SchemaLocatedFinding.unlocated( + PureXML.Schema.XSDParser.redefineDerivationErrors(document.containers), + ) + } + } +} diff --git a/Sources/Schema/XSDDerivation.swift b/Sources/Schema/XSDDerivation.swift index a7c2fe0..a1b6a79 100644 --- a/Sources/Schema/XSDDerivation.swift +++ b/Sources/Schema/XSDDerivation.swift @@ -300,7 +300,12 @@ extension PureXML.Schema.XSDParser { /// Throws when a type inside an `xs:redefine` does not derive from itself: a /// redefinition's `base` must name the type being redefined. - static func checkRedefine(_ containers: [XSDTree]) throws { + /// src-redefine.5: every type inside `xs:redefine` must restrict or extend the + /// type it redefines (same local name, in the redefining schema's own target + /// namespace). Returns one finding per offending type, for a validation to + /// report; it does not throw, so the finding flows through the validator. + static func redefineDerivationErrors(_ containers: [XSDTree]) -> [String] { + var errors: [String] = [] for container in containers where XSDDerivNode.localName(container) == "redefine" { for type in XSDDerivNode.children(container, named: "complexType") { guard let name = XSDDerivNode.attribute(type, "name") else { continue } @@ -310,22 +315,19 @@ extension PureXML.Schema.XSDParser { .flatMap { XSDDerivNode.firstChild($0, named: "extension") ?? XSDDerivNode.firstChild($0, named: "restriction") } .flatMap { XSDDerivNode.attribute($0, "base") } if derivationInfo(type)?.base != name || redefineBaseIsForeign(type, rawBase) { - throw SchemaFault.redefineIncompatible(type: name) + errors.append("redefined type '\(name)' must restrict or extend itself") } } for type in XSDDerivNode.children(container, named: "simpleType") { - try checkRedefinedSimpleType(type) + guard let name = XSDDerivNode.attribute(type, "name") else { continue } + let rawBase = XSDDerivNode.firstChild(type, named: "restriction") + .flatMap { XSDDerivNode.attribute($0, "base") } + if rawBase.map(XSDDerivNode.stripPrefix) != name || redefineBaseIsForeign(type, rawBase) { + errors.append("redefined type '\(name)' must restrict or extend itself") + } } } - } - - private static func checkRedefinedSimpleType(_ type: XSDTree) throws { - guard let name = XSDDerivNode.attribute(type, "name") else { return } - let rawBase = XSDDerivNode.firstChild(type, named: "restriction") - .flatMap { XSDDerivNode.attribute($0, "base") } - if rawBase.map(XSDDerivNode.stripPrefix) != name || redefineBaseIsForeign(type, rawBase) { - throw SchemaFault.redefineIncompatible(type: name) - } + return errors } /// Drops from each substitution group the members a head's `block` forbids: a @@ -352,37 +354,4 @@ extension PureXML.Schema.XSDParser { } return result } - - /// Throws when any `xs:all` group violates its XSD 1.0 constraints: its members - /// must all be elements, each occurring at most once, and the group itself may - /// occur at most once. Nested model groups inside an `all` are not allowed. - static func checkAllGroups(_ containers: [XSDTree]) throws { - for container in containers { - for all in descendants(container, named: "all") { - try checkAllGroup(all) - } - } - } - - private static func checkAllGroup(_ all: XSDTree) throws { - if !atMostOnce(XSDDerivNode.attribute(all, "maxOccurs")) { - throw SchemaFault.invalidAllGroup(reason: "the group's maxOccurs must be 0 or 1") - } - for member in XSDDerivNode.elementChildren(all) { - let kind = XSDDerivNode.localName(member) ?? "" - if kind == "annotation" { continue } - guard kind == "element" else { - throw SchemaFault.invalidAllGroup(reason: "a member may only be an element, not '\(kind)'") - } - if !atMostOnce(XSDDerivNode.attribute(member, "maxOccurs")) { - let label = XSDDerivNode.attribute(member, "name") ?? XSDDerivNode.attribute(member, "ref") ?? "" - throw SchemaFault.invalidAllGroup(reason: "element '\(label)' must have maxOccurs 0 or 1") - } - } - } - - private static func atMostOnce(_ maxOccurs: String?) -> Bool { - guard let maxOccurs else { return true } - return maxOccurs == "0" || maxOccurs == "1" - } } diff --git a/Sources/Schema/XSDParser.swift b/Sources/Schema/XSDParser.swift index cf1805c..f72b0a2 100644 --- a/Sources/Schema/XSDParser.swift +++ b/Sources/Schema/XSDParser.swift @@ -42,8 +42,6 @@ extension PureXML.Schema { let failedSchemaReferences = XSDNode.failedSchemaReferences(schema, wrappedLoader, &locationCheckVisited) let containers = containerTuples.map(\.tree) let derivation = derivationTables(containers) - try checkRedefine(containers) - try checkAllGroups(containers) let containerLocations = containerLocationMap(containerTuples, rootLocation: rootLocation) let compositionLoaded = XSDNode.compositionLoaded(from: containerTuples) var context = createContext( diff --git a/Sources/Schema/XSDParserUnion.swift b/Sources/Schema/XSDParserUnion.swift index 4636791..06d079f 100644 --- a/Sources/Schema/XSDParserUnion.swift +++ b/Sources/Schema/XSDParserUnion.swift @@ -46,8 +46,6 @@ extension PureXML.Schema.XSDParser { let containers = containerTuples.map(\.tree) let derivation = derivationTables(containers) - try checkRedefine(containers) - try checkAllGroups(containers) let containerLocations = containerLocationMap(containerTuples, rootLocation: nil) let compositionLoaded = XSDNode.compositionLoaded(from: containerTuples) var context = createContext( diff --git a/Tests/ValidationConfigurationTests.swift b/Tests/ValidationConfigurationTests.swift index 2578ca7..0b3f6f4 100644 --- a/Tests/ValidationConfigurationTests.swift +++ b/Tests/ValidationConfigurationTests.swift @@ -91,6 +91,7 @@ struct ValidationConfigurationTests { "Simple-type final controls are declared consistently", "Schema content models are deterministic (UPA)", "A resolved schemaLocation reference is a valid schema", + "A redefined type restricts or extends itself", "Type derivation chains contain no cycles", "Schema type references contain no cycles", "xs:all group references appear only where permitted",