PureXML/Sources/Pattern/PatternStream.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

57 lines
2.0 KiB
Swift

public extension PureXML.Pattern {
/// Streams `xml` through the pull parser and returns the paths of every node
/// that matches `pattern`, in document order, without building a tree. Each
/// path is rendered root-first as `/a/b/c`, and an attribute match as
/// `/a/b/@id`. The match is decided from the open-element stack alone, so the
/// document is never held whole.
static func matches(
_ pattern: String,
in xml: String,
limits: PureXML.Parsing.Limits = .default,
) throws -> [String] {
let matcher = try Matcher(pattern)
return try matcher.matches(in: xml, limits: limits)
}
}
public extension PureXML.Pattern.Matcher {
/// Streams `xml` and returns the matching node paths in document order.
func matches(
in xml: String,
limits: PureXML.Parsing.Limits = .default,
) throws -> [String] {
var reader = PureXML.Parsing.EventReader(xml, limits: limits)
var stack: [PureXML.Model.QualifiedName] = []
var results: [String] = []
while let event = try reader.next() {
switch event {
case let .startElement(name, attributes):
stack.append(name)
collect(stack: stack, attributes: attributes, into: &results)
case .endElement:
if !stack.isEmpty {
stack.removeLast()
}
default:
break
}
}
return results
}
private func collect(
stack: [PureXML.Model.QualifiedName],
attributes: [PureXML.Model.Attribute],
into results: inout [String],
) {
let base = "/" + stack.map(\.description).joined(separator: "/")
if matchesAttributes {
for attribute in matchingAttributes(path: stack, attributes: attributes) {
results.append(base + "/@" + attribute.name.description)
}
} else if matchesElement(path: stack) {
results.append(base)
}
}
}