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.
91 lines
3.4 KiB
Swift
91 lines
3.4 KiB
Swift
public extension PureXML.Parsing {
|
|
/// A push-style XML parser: feed input as it arrives with ``feed(_:)`` and end
|
|
/// the document with ``finish()``. It drives a ``SAXHandler``, holding only a
|
|
/// small buffer (the current incomplete token) plus the open-element stack, so
|
|
/// it never requires the whole document and resumes cleanly across chunk
|
|
/// boundaries (the Expat `XML_TOK_PARTIAL` model).
|
|
struct PushParser {
|
|
private var buffer: [Character] = []
|
|
private var namespaces = NamespaceContext()
|
|
private var open: [PureXML.Model.QualifiedName] = []
|
|
private let handler: SAXHandler
|
|
private var started = false
|
|
|
|
/// Creates a push parser delivering events to `sax`.
|
|
public init(sax: SAXHandler) {
|
|
handler = sax
|
|
}
|
|
|
|
/// Feeds the next chunk of input, delivering events for every token that is
|
|
/// now complete.
|
|
public mutating func feed(_ text: String) throws {
|
|
buffer += Array(text)
|
|
try drain(final: false)
|
|
}
|
|
|
|
/// Ends the document, flushing any trailing token and the document-end
|
|
/// callback.
|
|
public mutating func finish() throws {
|
|
ensureStarted()
|
|
try drain(final: true)
|
|
handler.endDocument?()
|
|
}
|
|
|
|
private mutating func ensureStarted() {
|
|
guard !started else { return }
|
|
started = true
|
|
handler.startDocument?()
|
|
}
|
|
|
|
private mutating func drain(final: Bool) throws {
|
|
ensureStarted()
|
|
while case let .token(token, consumed) = PushScanner.scan(buffer, final: final) {
|
|
buffer.removeFirst(consumed)
|
|
try deliver(token)
|
|
}
|
|
}
|
|
|
|
private mutating func deliver(_ token: PushToken) throws {
|
|
switch token {
|
|
case let .open(name, attributes, selfClosing):
|
|
try open(name, attributes, selfClosing: selfClosing)
|
|
case let .close(name):
|
|
close(name)
|
|
case let .text(value):
|
|
if !value.isEmpty {
|
|
handler.characters?(value)
|
|
}
|
|
case let .comment(value):
|
|
handler.comment?(value)
|
|
case let .cdata(value):
|
|
handler.cdata?(value)
|
|
case let .processingInstruction(target, data):
|
|
if target.lowercased() != "xml" {
|
|
handler.processingInstruction?(target, data)
|
|
}
|
|
case .ignorable:
|
|
break
|
|
}
|
|
}
|
|
|
|
private mutating func open(_ name: String, _ attributes: [PushAttribute], selfClosing: Bool) throws {
|
|
let rawName = PureXML.Model.QualifiedName(name)
|
|
let rawAttributes = attributes.map { PureXML.Model.Attribute(name: .init($0.name), value: $0.value) }
|
|
let resolved = try namespaces.enterElement(name: rawName, attributes: rawAttributes, at: .start)
|
|
handler.startElement?(resolved.name, resolved.attributes)
|
|
if selfClosing {
|
|
namespaces.leaveElement()
|
|
handler.endElement?(resolved.name)
|
|
} else {
|
|
open.append(resolved.name)
|
|
}
|
|
}
|
|
|
|
private mutating func close(_: String) {
|
|
guard let top = open.popLast() else { return }
|
|
namespaces.leaveElement()
|
|
handler.endElement?(top)
|
|
}
|
|
}
|
|
}
|