PureXML/Sources/Schema/SchemaWildcardNamespace.swift
Mihaela Mihaljevic 380160cc2b refactor(validation): decompose XSD instance validation into composable rules
The two monolithic XSD content/identity rules only satisfied the OpenAPIKit
validation idiom at the wrong granularity: no per-constraint removability,
no isolable failing/near-miss tests. Split them into 32 named rules, each
finding tagged at its origin and cached per XSDContext so the walk still
runs once per document (the #101 decision against duplicating XSD's
recursive type resolution still holds). Wired the streaming path's ID/IDREF
and identity checks through the same framework instead of calling the
validators imperatively, and added the unwrapAndLookup combinator the rule
already named as required.

Also includes the mandated repo-wide swiftformat/swiftlint pass.
2026-07-12 14:12:17 +02:00

38 lines
1.8 KiB
Swift

extension PureXML.Schema.XSDParser {
/// Validity of an `any`/`anyAttribute` `namespace` constraint (XSD 1.0 Structures):
///
/// namespace ::= ('##any' | '##other')
/// | List of (anyURI | '##targetNamespace' | '##local')
///
/// So `##any` and `##other` stand alone, and a list admits only namespace URIs
/// and the `##targetNamespace`/`##local` tokens. A misspelled token (`##all`,
/// `##target`) or `##any`/`##other` placed inside a list (`##any ##other`) was
/// accepted, because the wildcard parser mapped any unrecognised token to a
/// literal URI; it is now rejected at compile time.
static func wildcardNamespaceErrors(_ node: XSDTree) -> [String] {
// The wildcard's own constraint is the unprefixed `namespace`; a prefixed
// (foreign) attribute that merely shares the local name is the author's own
// and is not checked, as `attributeApplicabilityErrors` also requires.
guard let raw = node.attributes.first(where: { $0.name.prefix == nil && $0.name.localName == "namespace" })?.value
else { return [] }
return validWildcardNamespace(raw)
? []
: ["the wildcard namespace '\(raw)' is not a valid namespace constraint"]
}
private static func validWildcardNamespace(_ raw: String) -> Bool {
let tokens = raw.split(whereSeparator: \.isWhitespace).map(String.init)
// `##any`/`##other` are valid only as the whole value, never as a list item.
if tokens == ["##any"] || tokens == ["##other"] {
return true
}
return tokens.allSatisfy { token in
switch token {
case "##targetNamespace", "##local": true
case "##any", "##other": false
default: !token.hasPrefix("##")
}
}
}
}