PureXML/Sources/Schema/SchemaAllGroupPlacement.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

85 lines
4.5 KiB
Swift

private typealias AllNode = PureXML.Schema.XSDNode
extension PureXML.Schema.XSDParser {
/// XSD 1.0 `cos-all-limited.1.2`: an `all` group may only be the whole content
/// model of a complex type. A direct `all` nested in a `sequence`/`choice` is
/// already rejected by the child-content table; the remaining case is an `all`
/// reached through a `<group ref>` to a named group whose content is an `all`,
/// where that reference appears inside a compositor (so the `all` is not the
/// whole content). The reference is followed only when it resolves to this
/// schema's own target namespace.
static func allGroupReferencePlacementFindings(_ containers: [XSDTree], _ bindings: [String: String], _ target: String?) -> [PureXML.Schema.SchemaLocatedFinding] {
// A redefined group's effective content is the redefinition's, not the
// original loaded definition, so a redefined group name is exempt: its
// original `all` content is overridden (and the suite treats a reference to
// it inside a compositor as valid).
var redefined: Set<String> = []
for container in containers where AllNode.localName(container) == "redefine" {
for group in AllNode.children(container, named: "group") {
if let name = AllNode.attribute(group, "name") {
redefined.insert(name)
}
}
}
var allGroups: Set<String> = []
for container in containers {
// A named group defines a component in the target namespace of the
// schema document that holds it. A reference is only ever followed when
// it resolves to this schema's own `target` (see `allGroupReference`),
// so pooling a same-named all-group from an *imported* namespace would
// wrongly flag a local non-all group referenced inside a compositor.
// Record only all-groups defined in `target`: a `schema` container's
// own namespace is its `targetNamespace`; a `redefine` container's
// redefinitions belong to the including schema (this `target`).
let containerNamespace = AllNode.localName(container) == "redefine"
? target
: AllNode.attribute(container, "targetNamespace")
guard containerNamespace == target else { continue }
for group in descendants(container, named: "group") {
guard let name = AllNode.attribute(group, "name"), !redefined.contains(name) else { continue }
if AllNode.elementChildren(group).contains(where: { AllNode.localName($0) == "all" }) {
allGroups.insert(name)
}
}
}
guard !allGroups.isEmpty else { return [] }
var findings: [PureXML.Schema.SchemaLocatedFinding] = []
for container in containers {
collectAllGroupRefPlacement(container, allGroups, bindings, target, into: &findings)
}
return findings
}
private static func collectAllGroupRefPlacement(
_ node: XSDTree,
_ allGroups: Set<String>,
_ bindings: [String: String],
_ target: String?,
into findings: inout [PureXML.Schema.SchemaLocatedFinding],
) {
let kind = AllNode.localName(node)
let insideCompositor = kind == "sequence" || kind == "choice" || kind == "all"
for child in AllNode.elementChildren(node) {
if insideCompositor, let referenced = allGroupReference(child, allGroups, bindings, target) {
findings.append(PureXML.Schema.SchemaLocatedFinding(
reason: "the all-group '\(referenced)' may not be referenced inside a '\(kind ?? "")'; an all group must be the whole content model",
node: child,
))
}
collectAllGroupRefPlacement(child, allGroups, bindings, target, into: &findings)
}
}
/// The name of the all-group `child` references in this schema's target
/// namespace, or nil when `child` is not such a reference.
private static func allGroupReference(_ child: XSDTree, _ allGroups: Set<String>, _ bindings: [String: String], _ target: String?) -> String? {
guard AllNode.localName(child) == "group", let ref = AllNode.attribute(child, "ref"),
AllNode.referenceNamespace(ref, bindings) == target
else {
return nil
}
let name = AllNode.stripPrefix(ref)
return allGroups.contains(name) ? name : nil
}
}