diff --git a/CHANGELOG.md b/CHANGELOG.md index 2d7520b..220a7b0 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): an attribute use that references a global attribute carrying a `fixed` value constraint may not contradict it. A `fixed` on the reference whose value differs from the global's (compared in the attribute type's value space, so `1` and `01` are not wrongly flagged), or a `default` on a reference to a fixed global, is invalid (`au-props-correct.2`). A reference that omits its own value constraint inherits the global's fixed value and stays valid. Caught two invalid schemas: invalid-schemas-accepted 232 → 230, no other bucket moved. + - XSTS invalid-schemas-accepted (#145): a `complexContent` extension that adds element content may not extend a base whose whole content is a non-empty `all` group; the base's `all` would be nested in the sequence joining base and extension content (`cos-all-limited`). An empty-`all` base, or an attribute-only extension, stays valid. Caught one invalid schema: invalid-schemas-accepted 233 → 232, no other bucket moved. - XSTS invalid-schemas-accepted (#145): a `complexContent` extension that adds element content (a non-empty model group) may not extend a base whose content is simple (`cos-ct-extends.1.4.2.2`); a simple-content type is extended through `simpleContent`. An attribute-only `complexContent` extension of a simpleContent base stays valid (the `complexContent`-around-`simpleContent` idiom). Caught two invalid schemas: invalid-schemas-accepted 235 → 233, no other bucket moved. diff --git a/Sources/Schema/SchemaAttributeUniqueness.swift b/Sources/Schema/SchemaAttributeUniqueness.swift index 8377812..bf7642c 100644 --- a/Sources/Schema/SchemaAttributeUniqueness.swift +++ b/Sources/Schema/SchemaAttributeUniqueness.swift @@ -34,6 +34,41 @@ extension PureXML.Schema.XSDParser { let label = AttrNode.attribute(type, "name").map { "complex type '\($0)'" } ?? "an anonymous complex type" errors += attributeSetErrors(attributeDeclarations(under: attributeContainer(of: type), context), label) } + errors += attributeRefValueConstraintErrors(container, context) + } + return errors + } + + /// XSD 1.0 `au-props-correct.2`: when an attribute use references a global + /// attribute that has a `fixed` value constraint, the use may not contradict + /// it: a `fixed` whose value differs, or a `default`, on the reference is + /// invalid (the use either inherits the fixed value or restates the same one). + /// Compared in the attribute type's value space, so a value-equal pair written + /// in different lexical forms (`1` and `01`) is not wrongly flagged. + /// + /// The global declaration is looked up by local name (the only key the global + /// pool carries), so a reference is checked against it only once the resolved + /// declaration's own namespace matches the reference's resolved namespace. That + /// rejects a same-local-name collision across namespaces (an imported `imp:a` + /// versus a local `a`), where the local-name pool could otherwise surface the + /// wrong global and contradict a valid reference. + private static func attributeRefValueConstraintErrors(_ container: XSDTree, _ context: PureXML.Schema.XSDContext) -> [String] { + var errors: [String] = [] + for use in descendants(container, named: "attribute") { + guard let ref = AttrNode.attribute(use, "ref"), + let declaration = context.globalAttributes[AttrNode.stripPrefix(ref)], + let resolved = attributeUse(declaration, context), + resolved.name.namespaceURI == AttrNode.referenceNamespace(ref, context.namespaceBindings), + let globalFixed = AttrNode.attribute(declaration, "fixed") + else { continue } + if let useFixed = AttrNode.attribute(use, "fixed") { + let equal = resolved.type.valueMatches(useFixed, literal: globalFixed) || useFixed == globalFixed + if !equal { + errors.append("attribute reference to '\(AttrNode.stripPrefix(ref))' must use its fixed value '\(globalFixed)', not '\(useFixed)'") + } + } else if AttrNode.attribute(use, "default") != nil { + errors.append("attribute reference to '\(AttrNode.stripPrefix(ref))' has a fixed value and may not specify a default") + } } return errors } diff --git a/Tests/XSTSSuiteTests.swift b/Tests/XSTSSuiteTests.swift index fdbe55d..e94470c 100644 --- a/Tests/XSTSSuiteTests.swift +++ b/Tests/XSTSSuiteTests.swift @@ -87,8 +87,12 @@ struct XSTSSuiteTests { /// base whose whole content is an `all` group (the all would be nested in the /// joining sequence; cos-all-limited) then caught one more invalid schema: /// invalid-schemas accepted 233 → 232, no other bucket moved. + /// Rejecting an attribute use that references a global attribute with a `fixed` + /// value constraint but contradicts it (a value-space-differing `fixed`, or a + /// `default`; au-props-correct.2) then caught two more invalid schemas: + /// invalid-schemas accepted 232 → 230, no other bucket moved. private let knownSchemaValidRejected = 1 - private let knownSchemaInvalidAccepted = 232 + private let knownSchemaInvalidAccepted = 230 private let knownInstanceValidRejected = 3 private let knownInstanceInvalidAccepted = 132