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.
108 lines
4.3 KiB
Swift
108 lines
4.3 KiB
Swift
extension PureXML.Parsing.ByteDecoder {
|
|
/// The ISO-2022-JP decoder (the WHATWG `iso-2022-jp` algorithm). Unlike the
|
|
/// other CJK encodings this one is stateful: escape sequences switch between
|
|
/// ASCII, JIS X 0201 Roman, JIS X 0201 katakana, and the JIS X 0208 plane,
|
|
/// which is resolved through the shared `jis0208` table (no new data).
|
|
enum ISO2022JP {
|
|
static func decode(_ bytes: ArraySlice<UInt8>) -> String {
|
|
var scalars = String.UnicodeScalarView()
|
|
var mode: ISO2022JPMode = .ascii
|
|
var lead: UInt8?
|
|
var index = bytes.startIndex
|
|
while index < bytes.endIndex {
|
|
let byte = bytes[index]
|
|
index += 1
|
|
if byte == 0x1B {
|
|
mode = applyEscape(bytes, &index, into: &scalars) ?? mode
|
|
lead = nil
|
|
continue
|
|
}
|
|
decodeByte(byte, mode: mode, lead: &lead, into: &scalars)
|
|
}
|
|
if lead != nil {
|
|
scalars.append("\u{FFFD}")
|
|
}
|
|
return String(scalars)
|
|
}
|
|
|
|
/// Reads the bytes after an escape and returns the new mode, or nil (with a
|
|
/// replacement emitted) for an unrecognized sequence.
|
|
private static func applyEscape(_ bytes: ArraySlice<UInt8>, _ index: inout Int, into scalars: inout String.UnicodeScalarView) -> ISO2022JPMode? {
|
|
guard index < bytes.endIndex else { scalars.append("\u{FFFD}")
|
|
return nil
|
|
}
|
|
let first = bytes[index]
|
|
index += 1
|
|
guard index < bytes.endIndex else { scalars.append("\u{FFFD}")
|
|
return nil
|
|
}
|
|
let second = bytes[index]
|
|
index += 1
|
|
switch (first, second) {
|
|
case (0x28, 0x42): return .ascii // ESC ( B
|
|
case (0x28, 0x4A): return .roman // ESC ( J
|
|
case (0x28, 0x49): return .katakana // ESC ( I
|
|
case (0x24, 0x40), (0x24, 0x42): return .jis0208 // ESC $ @ / ESC $ B
|
|
default:
|
|
scalars.append("\u{FFFD}")
|
|
return nil
|
|
}
|
|
}
|
|
|
|
private static func decodeByte(_ byte: UInt8, mode: ISO2022JPMode, lead: inout UInt8?, into scalars: inout String.UnicodeScalarView) {
|
|
switch mode {
|
|
case .ascii:
|
|
scalars.append(byte <= 0x7F ? Unicode.Scalar(byte) : "\u{FFFD}")
|
|
case .roman:
|
|
switch byte {
|
|
case 0x5C: scalars.append("\u{00A5}") // yen
|
|
case 0x7E: scalars.append("\u{203E}") // overline
|
|
case 0 ... 0x7F: scalars.append(Unicode.Scalar(byte))
|
|
default: scalars.append("\u{FFFD}")
|
|
}
|
|
case .katakana:
|
|
if (0x21 ... 0x5F).contains(byte) {
|
|
scalars.append(Unicode.Scalar(0xFF61 + UInt32(byte) - 0x21) ?? "\u{FFFD}")
|
|
} else {
|
|
scalars.append("\u{FFFD}")
|
|
}
|
|
case .jis0208:
|
|
decodeJIS0208(byte, lead: &lead, into: &scalars)
|
|
}
|
|
}
|
|
|
|
private static func decodeJIS0208(_ byte: UInt8, lead: inout UInt8?, into scalars: inout String.UnicodeScalarView) {
|
|
guard let first = lead else {
|
|
if (0x21 ... 0x7E).contains(byte) {
|
|
lead = byte
|
|
} else if byte == 0x0A || byte == 0x0D {
|
|
scalars.append(Unicode.Scalar(byte))
|
|
} else {
|
|
scalars.append("\u{FFFD}")
|
|
}
|
|
return
|
|
}
|
|
lead = nil
|
|
guard (0x21 ... 0x7E).contains(first), (0x21 ... 0x7E).contains(byte) else {
|
|
scalars.append("\u{FFFD}")
|
|
return
|
|
}
|
|
let pointer = (Int(first) - 0x21) * 94 + Int(byte) - 0x21
|
|
if pointer >= 0, pointer < jis0208.count, jis0208[pointer] != 0xFFFF {
|
|
scalars.append(Unicode.Scalar(UInt32(jis0208[pointer])) ?? "\u{FFFD}")
|
|
} else {
|
|
scalars.append("\u{FFFD}")
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// The active character set while decoding ISO-2022-JP, switched by escape
|
|
/// sequences. Held by the streaming decoder across reads, so it is module-visible.
|
|
enum ISO2022JPMode {
|
|
case ascii
|
|
case roman
|
|
case katakana
|
|
case jis0208
|
|
}
|