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

54 lines
2.7 KiB
Swift

/// The overlap tests for a compositional-determinism decision set: a UPA violation
/// is two DISTINCT particles in one decision set that can match the same element
/// (same element name, or a wildcard admitting the other's name/namespace). Pure
/// functions over ``CompItem`` sets, factored out of ``CompWalk`` so the walk file
/// stays within its length budget; the walk accumulates decision sets and hands
/// each to ``overlap(_:)``.
enum CompConflict {
static func overlap(_ set: [CompItem]) -> String? {
var nameOwner: [PureXML.Model.QualifiedName: ObjectIdentifier] = [:]
var wildcards: [(card: PureXML.Schema.Wildcard, particle: ObjectIdentifier)] = []
for item in set {
switch item.label {
case let .name(qname):
if let owner = nameOwner[qname], owner != item.particle {
return qname.localName
}
if nameOwner[qname] == nil {
nameOwner[qname] = item.particle
}
case let .wildcard(card):
wildcards.append((card, item.particle))
}
}
for (index, wildcard) in wildcards.enumerated() {
// A wildcard conflicts with any name it admits, from a distinct particle,
// and with any other wildcard it intersects, from a distinct particle.
if nameOwner.contains(where: { wildcard.card.admits($0.key) && $0.value != wildcard.particle }) {
return "any"
}
for other in wildcards[(index + 1)...] where other.particle != wildcard.particle && wildcardsOverlap(wildcard.card, other.card) {
return "any"
}
}
return nil
}
static func wildcardsOverlap(_ lhs: PureXML.Schema.Wildcard, _ rhs: PureXML.Schema.Wildcard) -> Bool {
// Two wildcards overlap when some namespace name is admitted by both. Any
// two `not` forms always share infinitely many named namespaces; a `not`
// form overlaps a set when the set holds a named namespace it admits.
switch (lhs.namespace, rhs.namespace) {
case (.any, _), (_, .any): true
case let (.enumerated(lhsSet), .enumerated(rhsSet)): !lhsSet.isDisjoint(with: rhsSet)
case let (.enumerated(set), .notNamespace(name)), let (.notNamespace(name), .enumerated(set)):
set.contains { !$0.isEmpty && $0 != name }
case let (.enumerated(set), .notAbsent), let (.notAbsent, .enumerated(set)):
set.contains { !$0.isEmpty }
case (.notNamespace, .notNamespace), (.notNamespace, .notAbsent),
(.notAbsent, .notNamespace), (.notAbsent, .notAbsent):
true
}
}
}