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.
104 lines
5.0 KiB
Swift
104 lines
5.0 KiB
Swift
extension PureXML.XPath.AxisNavigation {
|
|
/// A document-order sort key. Comparing two nodes is O(1): the document each
|
|
/// belongs to (ranked by first appearance), the pre-order index of the node's
|
|
/// owning tree node, a band placing an element before its namespace nodes
|
|
/// before its attribute nodes before its children, and a tie-breaking
|
|
/// sub-index. This replaces the former per-node root path, an array as long as
|
|
/// the node's depth, which made ordering a deep document quadratic.
|
|
private struct OrderKey: Comparable {
|
|
var rootRank = 0
|
|
var index = 0
|
|
var band = 0
|
|
var sub = 0
|
|
|
|
static func < (lhs: OrderKey, rhs: OrderKey) -> Bool {
|
|
if lhs.rootRank != rhs.rootRank {
|
|
return lhs.rootRank < rhs.rootRank
|
|
}
|
|
if lhs.index != rhs.index {
|
|
return lhs.index < rhs.index
|
|
}
|
|
if lhs.band != rhs.band {
|
|
return lhs.band < rhs.band
|
|
}
|
|
return lhs.sub < rhs.sub
|
|
}
|
|
}
|
|
|
|
/// Sorts `nodes` into XPath document order using a per-root pre-order index
|
|
/// (built once and cached) rather than a per-node root path, so the sort is
|
|
/// O(n log n + document) instead of O(n log n x depth).
|
|
///
|
|
/// A node-set spanning several documents (an XSLT `document()` result) is
|
|
/// grouped by root, ranked by first appearance; only a node outside the first
|
|
/// node's document pays an O(depth) root walk, so the common single-document
|
|
/// case never walks a node's ancestors.
|
|
static func sortByDocumentOrder(_ nodes: [PureXML.XPath.Node], cache: PureXML.XPath.DocumentNavigationCache?) -> [PureXML.XPath.Node] {
|
|
guard nodes.count > 1, let primaryRoot = rootTree(of: nodes[0]) else { return nodes }
|
|
return decorate(nodes, primaryRoot: primaryRoot, cache: cache)
|
|
.sorted { $0.key < $1.key }
|
|
.map(\.node)
|
|
}
|
|
|
|
/// The single node first in document order, by the same keying as
|
|
/// ``sortByDocumentOrder(_:cache:)`` but in O(n) rather than a full sort.
|
|
static func firstByDocumentOrder(_ nodes: [PureXML.XPath.Node], cache: PureXML.XPath.DocumentNavigationCache?) -> PureXML.XPath.Node? {
|
|
guard nodes.count > 1, let primaryRoot = rootTree(of: nodes[0]) else { return nodes.first }
|
|
return decorate(nodes, primaryRoot: primaryRoot, cache: cache)
|
|
.min { $0.key < $1.key }?
|
|
.node
|
|
}
|
|
|
|
/// Pairs each node with its document-order key. The primary document's index
|
|
/// is built once; a foreign node falls back to its own document's index,
|
|
/// ranked after the primary by first appearance.
|
|
private static func decorate(
|
|
_ nodes: [PureXML.XPath.Node],
|
|
primaryRoot: PureXML.Model.TreeNode,
|
|
cache: PureXML.XPath.DocumentNavigationCache?,
|
|
) -> [(node: PureXML.XPath.Node, key: OrderKey)] {
|
|
let primaryID = ObjectIdentifier(primaryRoot)
|
|
var indexByRoot: [ObjectIdentifier: [PureXML.XPath.Node: Int]] = [primaryID: orderedDocument(rootedAt: primaryRoot, cache: cache).index]
|
|
var rootRank: [ObjectIdentifier: Int] = [primaryID: 0]
|
|
return nodes.map { node in
|
|
if var key = compactKey(node, in: indexByRoot[primaryID] ?? [:]) {
|
|
key.rootRank = 0
|
|
return (node, key)
|
|
}
|
|
guard let root = rootTree(of: node) else { return (node, OrderKey(rootRank: .max)) }
|
|
let rootID = ObjectIdentifier(root)
|
|
let rank: Int
|
|
if let existing = rootRank[rootID] {
|
|
rank = existing
|
|
} else {
|
|
rank = rootRank.count
|
|
rootRank[rootID] = rank
|
|
indexByRoot[rootID] = orderedDocument(rootedAt: root, cache: cache).index
|
|
}
|
|
var key = compactKey(node, in: indexByRoot[rootID] ?? [:]) ?? OrderKey()
|
|
key.rootRank = rank
|
|
return (node, key)
|
|
}
|
|
}
|
|
|
|
/// `node`'s order key within `index` (a pre-order numbering of one document's
|
|
/// tree nodes), or nil when `node` belongs to a different document. The band
|
|
/// orders an element before its namespace nodes before its attribute nodes
|
|
/// before its children (whose own index is greater), mirroring the bands of
|
|
/// the former path-key form.
|
|
private static func compactKey(_ node: PureXML.XPath.Node, in index: [PureXML.XPath.Node: Int]) -> OrderKey? {
|
|
switch node {
|
|
case .tree:
|
|
guard let position = index[node] else { return nil }
|
|
return OrderKey(index: position, band: 0)
|
|
case let .attribute(owner, attribute):
|
|
guard let position = index[.tree(owner)] else { return nil }
|
|
let attributeIndex = owner.attributes.firstIndex { $0.name == attribute.name } ?? 0
|
|
return OrderKey(index: position, band: 2, sub: attributeIndex)
|
|
case let .namespace(owner, prefix, _):
|
|
guard let position = index[.tree(owner)] else { return nil }
|
|
return OrderKey(index: position, band: 1, sub: prefix.hashValue)
|
|
}
|
|
}
|
|
}
|