The streaming decoder previously selected an encoding only from a byte-order mark or the UTF sniff, so its single-byte and CJK decode paths were latent. It now reads the encoding from the XML declaration (the libxml2 behavior) when there is no BOM, activating those paths over an incremental byte source. Add the streaming form of ISO-2022-JP, carrying the stateful mode across reads (extracted to its own file to stay under the type-body cap). A test asserts streaming agrees with whole-buffer decode for a single-byte (ISO-8859-1), a CJK (Shift_JIS), and an ISO-2022-JP document. Part of #108, #105
72 lines
2.7 KiB
Swift
72 lines
2.7 KiB
Swift
extension PureXML.Parsing.StreamingDecoder {
|
|
/// Streams one ISO-2022-JP character, carrying the active mode across calls.
|
|
/// Escape sequences switch mode and yield no character, so it loops until a
|
|
/// character is produced or the stream ends.
|
|
mutating func nextISO2022JP() -> Character? {
|
|
while true {
|
|
guard let byte = nextByte() else {
|
|
finished = true
|
|
return nil
|
|
}
|
|
if byte == 0x1B {
|
|
guard applyISO2022JPEscape() else { return Self.replacement }
|
|
continue
|
|
}
|
|
return decodeISO2022JPByte(byte)
|
|
}
|
|
}
|
|
|
|
private mutating func decodeISO2022JPByte(_ byte: UInt8) -> Character {
|
|
switch iso2022jpMode {
|
|
case .ascii:
|
|
return byte <= 0x7F ? Character(Unicode.Scalar(byte)) : Self.replacement
|
|
case .roman:
|
|
return romanScalar(byte)
|
|
case .katakana:
|
|
guard (0x21 ... 0x5F).contains(byte) else { return Self.replacement }
|
|
return Character(Unicode.Scalar(0xFF61 + UInt32(byte) - 0x21) ?? "\u{FFFD}")
|
|
case .jis0208:
|
|
return jis0208Character(byte)
|
|
}
|
|
}
|
|
|
|
private func romanScalar(_ byte: UInt8) -> Character {
|
|
switch byte {
|
|
case 0x5C: "\u{00A5}" // yen
|
|
case 0x7E: "\u{203E}" // overline
|
|
case 0 ... 0x7F: Character(Unicode.Scalar(byte))
|
|
default: Self.replacement
|
|
}
|
|
}
|
|
|
|
private mutating func jis0208Character(_ lead: UInt8) -> Character {
|
|
guard (0x21 ... 0x7E).contains(lead) else {
|
|
return lead == 0x0A || lead == 0x0D ? Character(Unicode.Scalar(lead)) : Self.replacement
|
|
}
|
|
guard let trail = nextByte() else {
|
|
finished = true
|
|
return Self.replacement
|
|
}
|
|
guard (0x21 ... 0x7E).contains(trail) else { return Self.replacement }
|
|
let pointer = (Int(lead) - 0x21) * 94 + Int(trail) - 0x21
|
|
let table = PureXML.Parsing.ByteDecoder.jis0208
|
|
guard pointer >= 0, pointer < table.count, table[pointer] != 0xFFFF else { return Self.replacement }
|
|
return Character(Unicode.Scalar(UInt32(table[pointer])) ?? "\u{FFFD}")
|
|
}
|
|
|
|
private mutating func applyISO2022JPEscape() -> Bool {
|
|
guard let first = nextByte(), let second = nextByte() else {
|
|
finished = true
|
|
return false
|
|
}
|
|
switch (first, second) {
|
|
case (0x28, 0x42): iso2022jpMode = .ascii
|
|
case (0x28, 0x4A): iso2022jpMode = .roman
|
|
case (0x28, 0x49): iso2022jpMode = .katakana
|
|
case (0x24, 0x40), (0x24, 0x42): iso2022jpMode = .jis0208
|
|
default: return false
|
|
}
|
|
return true
|
|
}
|
|
}
|