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

42 lines
2.0 KiB
Swift

/// Caches, per parent element and child local name, the one-based position of
/// each same-name element child among its like-named siblings, used to locate an
/// identity-constraint error precisely (`item[3]` rather than `item`).
///
/// Without it, ``IdentityValidator/steps(from:to:)`` rescans a parent's whole
/// child list for every target it locates, so validating a wide list of like-
/// named elements is O(n) per target and quadratic overall. The first lookup
/// under a given parent and name builds the position table once; later lookups
/// are dictionary reads. A position is recorded only when more than one element
/// shares the name, matching the path grammar, which omits the predicate for a
/// unique child.
final class StepSiblingIndex {
typealias Tree = PureXML.Model.TreeNode
/// parent identity -> child local name -> child identity -> one-based
/// position among same-name element siblings (present only when several
/// siblings share the name).
private var positions: [ObjectIdentifier: [String: [ObjectIdentifier: Int]]] = [:]
/// The one-based position of `node` among `parent`'s element children named
/// `name`, or nil when it is the only such child (no predicate needed).
func position(of node: Tree, under parent: Tree, name: String) -> Int? {
let parentIdentity = ObjectIdentifier(parent)
if let table = positions[parentIdentity]?[name] {
return table[ObjectIdentifier(node)]
}
var table: [ObjectIdentifier: Int] = [:]
var ordinal = 0
for child in parent.children where child.kind == .element && (child.name?.description ?? "") == name {
ordinal += 1
table[ObjectIdentifier(child)] = ordinal
}
// The grammar predicates a step only when the name is ambiguous; a single
// same-name child keeps the bare `name`, so drop a lone entry.
if ordinal < 2 {
table = [:]
}
positions[parentIdentity, default: [:]][name] = table
return table[ObjectIdentifier(node)]
}
}