From 615f668fc2872195c94eba90657c459779dd9589 Mon Sep 17 00:00:00 2001 From: Mihaela Mihaljevic Date: Wed, 17 Jun 2026 22:12:01 +0200 Subject: [PATCH] fix(schema): an attribute's type must be a simple type (au-props-correct); invalid-schemas-accepted 96 to 95 (#145) The {type definition} of an attribute is a simple type (au-props-correct / src-attribute). An attribute whose type resolves to a complex type, or to the complex ur-type xs:anyType, is now rejected; a built-in or user simple type, an inline simpleType, or no type (anySimpleType) stays valid. A locally- declared type takes precedence over the built-in reading, consistent with the src-ct rules. Strictly zero false positive: an attribute can never carry a complex type, so no valid schema is affected. XSTS: invalid-schemas-accepted 96 to 95 (attD002), valid-schemas-rejected held at 0, no other bucket moved. Differential: PureXML-stricter unchanged at 87 over 9618 schemas. --- CHANGELOG.md | 2 ++ .../Schema/SchemaAttributeUniqueness.swift | 26 +++++++++++++++++++ Sources/Schema/SchemaCompileValidation.swift | 12 ++++++++- Tests/SchemaTopLevelAttributeTests.swift | 14 ++++++++++ Tests/ValidationConfigurationTests.swift | 1 + Tests/XSTSSuiteTests.swift | 5 ++-- 6 files changed, 57 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b06f182..b4fc436 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed +- XSTS invalid-schemas-accepted (#145): an attribute's type must be a simple type (`au-props-correct` / `src-attribute`). An `attribute` whose `type` resolves to a complex type, or to the complex ur-type `xs:anyType`, is rejected; a built-in or user simple type, an inline `simpleType`, or no type (`anySimpleType`) stays valid. A locally-declared type takes precedence over the built-in reading. invalid-schemas-accepted 96 → 95, no false positive (valid-schemas-rejected held at 0; differential PureXML-stricter unchanged at 87 over 9618 schemas), no other bucket moved. + - XSTS invalid-schemas-accepted (#145): an element's `default`/`fixed` value constraint requires a content type that admits character data (`cos-valid-default`). An element whose type is, or whose inline complex type has, element-only or empty content may not carry a value constraint; a simple type, mixed content, the ur-type `xs:anyType`, and an untyped element (also `anyType`) stay valid. The check flags only a content type that is certainly element-only or empty, so it cannot over-reject. invalid-schemas-accepted 99 → 96, no false positive (valid-schemas-rejected held at 0; differential PureXML-stricter unchanged at 87 over 9618 schemas), no other bucket moved. - XSTS invalid-schemas-accepted (#145): the base-type kind of a content-model derivation is enforced. A `complexContent` restriction or extension base must be a complex type (`src-ct.1`), so a `simpleType` or a built-in simple type such as `xs:string` is rejected (the ur-type `xs:anyType` is complex and stays valid). A `simpleContent` extension base must be a simple type or a complex type with simple content (`src-ct.2`), so a complex type with element-only, mixed, or empty content, including `xs:anyType`, is rejected. A locally-declared type takes precedence over the built-in reading, so a schema that targets the XSD namespace and extends its own complex types (the schema-for-schemas) is not rejected. invalid-schemas-accepted 104 → 99, no false positive (valid-schemas-rejected held at 0, corroborated by the differential: PureXML-stricter unchanged at 87 over 9618 schemas), no other bucket moved. diff --git a/Sources/Schema/SchemaAttributeUniqueness.swift b/Sources/Schema/SchemaAttributeUniqueness.swift index bf7642c..d16c64d 100644 --- a/Sources/Schema/SchemaAttributeUniqueness.swift +++ b/Sources/Schema/SchemaAttributeUniqueness.swift @@ -10,6 +10,32 @@ private struct ReachedAttribute { } extension PureXML.Schema.XSDParser { + /// XSD 1.0 `au-props-correct` / `src-attribute`: the {type definition} of an + /// attribute is a SIMPLE type. An `attribute` whose `type` resolves to a complex + /// type, or to the complex ur-type `xs:anyType`, is invalid; a built-in or user + /// simple type, an inline `simpleType`, or no type (`anySimpleType`) is valid. A + /// locally-declared type takes precedence over the built-in reading (a schema may + /// target the XSD namespace and define its own components there), so resolve + /// `types` first. Self-contained schema; a foreign or unresolved type is left + /// alone. Strictly zero false positive: an attribute can never carry a complex + /// type, so no valid schema is affected. + static func attributeTypeMustBeSimpleErrors(_ schema: XSDTree, _ context: PureXML.Schema.XSDContext, _ types: [String: PureXML.Schema.ElementType]) -> [String] { + guard !skipsCrossDocumentRules(schema, compositionLoaded: context.compositionLoaded) else { return [] } + let bindings = context.namespaceBindings + var errors: [String] = [] + for attribute in descendants(schema, named: "attribute") where attribute.name?.namespaceURI == xsdNamespace { + guard let typeName = AttrNode.attribute(attribute, "type") else { continue } + let namespace = AttrNode.referenceNamespace(typeName, bindings) + let local = AttrNode.stripPrefix(typeName) + if namespace == context.targetNamespace, case .complex? = types[local] { + errors.append("an attribute's type must be a simple type, not the complex type '\(local)'") + } else if namespace == xsdNamespace, types[local] == nil, local == "anyType" { + errors.append("an attribute's type must be a simple type, not 'anyType'") + } + } + return errors + } + /// Schema-validity findings for attribute-use uniqueness (XSD 1.0 /// `ct-props-correct.4` / `ag-props-correct.2`) and the single-ID rule /// (`ct-props-correct.5`): a complex type's or attribute group's complete diff --git a/Sources/Schema/SchemaCompileValidation.swift b/Sources/Schema/SchemaCompileValidation.swift index 7d554ed..45a09bc 100644 --- a/Sources/Schema/SchemaCompileValidation.swift +++ b/Sources/Schema/SchemaCompileValidation.swift @@ -262,7 +262,7 @@ extension PureXML.Validation { static func postCompileValidator() -> Validator { Validator.defaults( nonReference: [ - AnyValidation(attributeUsesValid), + AnyValidation(attributeUsesValid), AnyValidation(attributeTypesSimple), AnyValidation(idValueConstraintsValid), AnyValidation(userTypeValueConstraintsValid), AnyValidation(extensionAllGroupsValid), @@ -341,6 +341,16 @@ extension PureXML.Validation { } extension PureXML.Validation.SchemaCompile { + /// 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 + guard let namedTypes = document.namedTypes else { return [] } + return PureXML.Schema.SchemaLocatedFinding.unlocated( + PureXML.Schema.XSDParser.attributeTypeMustBeSimpleErrors(document.schema, document.context, namedTypes), + ) + } + } + /// Particle Valid (Restriction) for anonymous (inline) complex types, which the /// name-keyed `restrictionsAreSubsets` rule never sees. static var anonymousRestrictionsValid: PureXML.Validation.Validation { diff --git a/Tests/SchemaTopLevelAttributeTests.swift b/Tests/SchemaTopLevelAttributeTests.swift index 786fc4e..15dadf3 100644 --- a/Tests/SchemaTopLevelAttributeTests.swift +++ b/Tests/SchemaTopLevelAttributeTests.swift @@ -44,4 +44,18 @@ struct SchemaTopLevelAttributeTests { + "", )) } + + /// au-props-correct: an attribute's type must be a simple type. A complex type + /// (here one with simple content) is rejected; a built-in or user simple type, + /// an inline simpleType, or no type (anySimpleType) is valid. + @Test("an attribute's type must be a simple type") + func test_attributeTypeMustBeSimple() { + let complexT = "" + #expect(!compiles(complexT + "")) + #expect(!compiles("")) + let simpleT = "" + #expect(compiles(simpleT + "")) + #expect(compiles("")) + #expect(compiles("")) + } } diff --git a/Tests/ValidationConfigurationTests.swift b/Tests/ValidationConfigurationTests.swift index 676c02a..e4ba895 100644 --- a/Tests/ValidationConfigurationTests.swift +++ b/Tests/ValidationConfigurationTests.swift @@ -97,6 +97,7 @@ struct ValidationConfigurationTests { ]) #expect(PureXML.Validation.SchemaCompile.postCompileValidator().validationDescriptions == [ "Attribute uses are unique and declare at most one ID attribute", + "An attribute's type is a simple type", "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", diff --git a/Tests/XSTSSuiteTests.swift b/Tests/XSTSSuiteTests.swift index 549463b..c0c9808 100644 --- a/Tests/XSTSSuiteTests.swift +++ b/Tests/XSTSSuiteTests.swift @@ -151,9 +151,10 @@ struct XSTSSuiteTests { /// a local type takes precedence over the built-in reading so the schema-for- /// schemas, which targets the XSD namespace, is not rejected) 104 -> 99; and the /// element value-constraint content type (cos-valid-default: a default/fixed needs - /// simple or mixed content, not element-only) 99 -> 96. + /// simple or mixed content, not element-only) 99 -> 96; and an attribute's type + /// must be a simple type, never a complex type (au-props-correct) 96 -> 95. private let knownSchemaValidRejected = 0 - private let knownSchemaInvalidAccepted = 96 + private let knownSchemaInvalidAccepted = 95 private let knownInstanceValidRejected = 0 private let knownInstanceInvalidAccepted = 133