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.
80 lines
2.8 KiB
Swift
80 lines
2.8 KiB
Swift
extension PureXML.Parsing.XMLDeclaration {
|
|
/// A name/value pair from an XML declaration.
|
|
struct PseudoAttribute: Equatable {
|
|
let name: String
|
|
let value: String
|
|
}
|
|
|
|
/// Scans the `name="value"` pseudo-attributes of an XML declaration's text.
|
|
/// Each value must be quoted; anything malformed (a missing `=`, an unquoted
|
|
/// value, trailing junk) makes the whole scan fail so the declaration is
|
|
/// rejected rather than partly read.
|
|
struct PseudoAttributeScanner {
|
|
private let chars: [Character]
|
|
private var index = 0
|
|
|
|
init(_ text: String) {
|
|
chars = Array(text)
|
|
}
|
|
|
|
mutating func scan() -> [PseudoAttribute]? {
|
|
var pairs: [PseudoAttribute] = []
|
|
skipSpace()
|
|
while index < chars.count {
|
|
guard let name = scanName(), !name.isEmpty else { return nil }
|
|
skipSpace()
|
|
guard consume("=") else { return nil }
|
|
skipSpace()
|
|
guard let value = scanQuoted() else { return nil }
|
|
pairs.append(PseudoAttribute(name: name, value: value))
|
|
// Pseudo-attributes must be separated by whitespace.
|
|
let before = index
|
|
skipSpace()
|
|
if index == before, index < chars.count {
|
|
return nil
|
|
}
|
|
}
|
|
return pairs
|
|
}
|
|
|
|
private mutating func scanName() -> String? {
|
|
var name = ""
|
|
while index < chars.count, isNameCharacter(chars[index]) {
|
|
name.append(chars[index])
|
|
index += 1
|
|
}
|
|
return name.isEmpty ? nil : name
|
|
}
|
|
|
|
private mutating func scanQuoted() -> String? {
|
|
guard index < chars.count, chars[index] == "\"" || chars[index] == "'" else { return nil }
|
|
let quote = chars[index]
|
|
index += 1
|
|
var value = ""
|
|
while index < chars.count, chars[index] != quote {
|
|
value.append(chars[index])
|
|
index += 1
|
|
}
|
|
guard index < chars.count else { return nil }
|
|
index += 1
|
|
return value
|
|
}
|
|
|
|
private mutating func consume(_ character: Character) -> Bool {
|
|
guard index < chars.count, chars[index] == character else { return false }
|
|
index += 1
|
|
return true
|
|
}
|
|
|
|
private mutating func skipSpace() {
|
|
while index < chars.count, chars[index] == " " || chars[index] == "\t" || chars[index] == "\n" || chars[index] == "\r" {
|
|
index += 1
|
|
}
|
|
}
|
|
|
|
private func isNameCharacter(_ character: Character) -> Bool {
|
|
character.isLetter || character.isNumber || character == "-" || character == "_" || character == "."
|
|
}
|
|
}
|
|
}
|