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

60 lines
2.8 KiB
Swift

/// A membership set for identity-constraint value tuples that detects an equal
/// tuple in (amortized) O(1) for the common case instead of scanning every
/// stored tuple, which made `unique`/`key`/`keyref` validation quadratic over a
/// wide list (one duplicate scan per target, each linear in the targets seen).
///
/// `FieldValue` equality is value-space aware (`3.0` equals `3`, `true` equals
/// `1`, a QName equals an equivalent prefix), so it cannot be hashed directly: a
/// consistent hash would have to canonicalize each value space, and the QName
/// case even depends on the other operand. Instead the set keys only the tuples
/// it can prove compare as raw strings, and keeps the exact pairwise comparison
/// for the rest, so behavior is identical to the former linear scan.
///
/// A field compares as a raw string when it is type-less (lexical equality) or
/// an atomic, lexically-compared, whitespace-preserving type (so `valueMatches`
/// reduces to `string == string`), and carries no colon that could trigger QName
/// resolution. Two such tuples are equal exactly when their raw strings match,
/// so they live in a `Set` keyed by those strings. Tuples with any value-space,
/// whitespace-collapsing, or QName-bearing field are kept verbatim and compared
/// with `==`; a raw-string query still scans them, and a value-space query scans
/// everything, so no equal pair is ever missed.
struct TupleSet {
private var rawKeys: Set<[String]> = []
private var unsafe: [[FieldValue?]] = []
/// Every inserted tuple, consulted only when a value-space query must be
/// compared against the raw-string-keyed tuples too.
private var all: [[FieldValue?]] = []
/// Whether a stored tuple is equal to `tuple` under `FieldValue` equality.
func contains(_ tuple: [FieldValue?]) -> Bool {
if let key = Self.rawKey(of: tuple) {
return rawKeys.contains(key) || unsafe.contains { $0 == tuple }
}
return all.contains { $0 == tuple }
}
mutating func insert(_ tuple: [FieldValue?]) {
if let key = Self.rawKey(of: tuple) {
rawKeys.insert(key)
} else {
unsafe.append(tuple)
}
all.append(tuple)
}
/// The raw-string key of a tuple every one of whose fields compares as a raw
/// string, or nil when any field needs the value-space comparison.
private static func rawKey(of tuple: [FieldValue?]) -> [String]? {
var key: [String] = []
key.reserveCapacity(tuple.count)
for field in tuple {
guard let field, !field.string.contains(":") else { return nil }
if let type = field.type, !type.comparesRawLexically {
return nil
}
key.append(field.string)
}
return key
}
}