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.
72 lines
3.3 KiB
Swift
72 lines
3.3 KiB
Swift
private typealias XSDNode = PureXML.Schema.XSDNode
|
|
|
|
extension PureXML.Schema.XSDParser {
|
|
/// The named `<xs:simpleType>` nodes keyed by name (first declaration wins, matching
|
|
/// the resolution loop, which overwrites a later same-name node's slot last).
|
|
static func simpleTypeNodesByName(_ named: [PureXML.Model.TreeNode]) -> [String: PureXML.Model.TreeNode] {
|
|
var byName: [String: PureXML.Model.TreeNode] = [:]
|
|
for node in named {
|
|
if let name = XSDNode.attribute(node, "name"), byName[name] == nil {
|
|
byName[name] = node
|
|
}
|
|
}
|
|
return byName
|
|
}
|
|
|
|
/// The named simple types in dependency order: each type after every named type it
|
|
/// derives from (its `restriction` base, `list` item, or `union` members, including
|
|
/// those reached through an inline anonymous base). Resolving in this order lets each
|
|
/// type be compiled ONCE with its base already in `context.simpleTypes`, where the
|
|
/// former fixpoint re-resolved every type N times (super-linear on a long restriction
|
|
/// chain). An iterative post-order DFS, so it does not recurse on the chain depth; a
|
|
/// name already on the current path is not re-entered, so a circular reference
|
|
/// (illegal, rejected elsewhere) terminates and its types still get an order.
|
|
static func simpleTypeResolutionOrder(
|
|
_ named: [PureXML.Model.TreeNode],
|
|
nodeByName: [String: PureXML.Model.TreeNode],
|
|
) -> [String] {
|
|
var order: [String] = []
|
|
var visited: Set<String> = []
|
|
var onPath: Set<String> = []
|
|
var work: [(name: String, exit: Bool)] = named
|
|
.compactMap { XSDNode.attribute($0, "name") }
|
|
.reversed()
|
|
.map { ($0, false) }
|
|
while let (name, exit) = work.popLast() {
|
|
if exit {
|
|
onPath.remove(name)
|
|
order.append(name)
|
|
continue
|
|
}
|
|
if visited.contains(name) {
|
|
continue
|
|
}
|
|
visited.insert(name)
|
|
onPath.insert(name)
|
|
work.append((name, true))
|
|
guard let node = nodeByName[name] else { continue }
|
|
for dependency in referencedSimpleTypeNames(in: node) {
|
|
guard nodeByName[dependency] != nil, !visited.contains(dependency), !onPath.contains(dependency) else { continue }
|
|
work.append((dependency, false))
|
|
}
|
|
}
|
|
return order
|
|
}
|
|
|
|
/// The type names a `<xs:simpleType>` node references through `base`, `itemType`, or
|
|
/// `memberTypes` (the last a space-separated list), anywhere in its subtree, by an
|
|
/// iterative walk. The caller keeps only those that name another type in the set.
|
|
private static func referencedSimpleTypeNames(in node: PureXML.Model.TreeNode) -> [String] {
|
|
var names: [String] = []
|
|
var stack = [node]
|
|
while let current = stack.popLast() {
|
|
for attribute in ["base", "itemType", "memberTypes"] {
|
|
guard let value = XSDNode.attribute(current, attribute) else { continue }
|
|
names += value.split(separator: " ").map { XSDNode.stripPrefix(String($0)) }
|
|
}
|
|
stack.append(contentsOf: XSDNode.elementChildren(current))
|
|
}
|
|
return names
|
|
}
|
|
}
|