Merge #97: ISO-8859-2

This commit is contained in:
Mihaela Mihaljevic 2026-06-09 07:08:19 +02:00
commit cf8969d08c
7 changed files with 45 additions and 3 deletions

View File

@ -14,7 +14,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added
- Single-byte legacy encodings (#97, partial): the byte decoder gains a table-based single-byte path and four encodings expressible exactly (so transcription-error-free): ISO-8859-15 (Latin-9, euro), ISO-8859-9 (Latin-5, Turkish), ISO-8859-5 (Latin/Cyrillic), and Windows-1254 (Turkish, composed from Windows-1252). They are selected by the XML declaration's `encoding` name, for both the whole-buffer and streaming decoders. The remaining legacy encodings are data drop-ins into the same path.
- Single-byte legacy encodings (#97, partial): the byte decoder gains a table-based single-byte path and five legacy encodings: ISO-8859-15 (Latin-9, euro), ISO-8859-9 (Latin-5, Turkish), ISO-8859-5 (Latin/Cyrillic), Windows-1254 (Turkish, composed from Windows-1252), and ISO-8859-2 (Latin-2, vendored verbatim from the authoritative Unicode mapping file). They are selected by the XML declaration's `encoding` name, for both the whole-buffer and streaming decoders. The remaining legacy encodings are data drop-ins into the same path.
### Fixed

View File

@ -89,7 +89,7 @@ flowchart TB
PureXML is a working, dependency-free XML library today: parse, emit, validate,
query, and stream documents on macOS, Linux, Windows, and WASM. The test suite
currently runs **754 tests in 117 suites** (`swift test`).
currently runs **755 tests in 117 suites** (`swift test`).
### Shipped (libxml2-aligned surface)

View File

@ -32,6 +32,7 @@ extension PureXML.Parsing {
case .latin1: { Unicode.Scalar($0) }
case .windows1252: windows1252Scalar
case .windows1254: SingleByte.windows1254
case .latin2: SingleByte.iso8859_2
case .latinCyrillic: SingleByte.iso8859_5
case .latin5: SingleByte.iso8859_9
case .latin9: SingleByte.iso8859_15
@ -100,6 +101,7 @@ extension PureXML.Parsing {
"iso-8859-1": .latin1, "latin1": .latin1, "latin-1": .latin1, "l1": .latin1,
"windows-1252": .windows1252, "cp1252": .windows1252, "cp-1252": .windows1252,
"windows-1254": .windows1254, "cp1254": .windows1254,
"iso-8859-2": .latin2, "iso8859-2": .latin2, "latin2": .latin2, "latin-2": .latin2, "l2": .latin2,
"iso-8859-5": .latinCyrillic, "iso8859-5": .latinCyrillic, "cyrillic": .latinCyrillic,
"iso-8859-9": .latin5, "iso8859-9": .latin5, "latin5": .latin5, "latin-5": .latin5, "l5": .latin5,
"iso-8859-15": .latin9, "iso8859-15": .latin9, "latin9": .latin9, "latin-9": .latin9, "l9": .latin9,

View File

@ -11,6 +11,7 @@ public extension PureXML.Parsing {
case latin1
case windows1252
case windows1254 // Turkish
case latin2 // ISO-8859-2
case latinCyrillic // ISO-8859-5
case latin5 // ISO-8859-9 (Turkish)
case latin9 // ISO-8859-15

View File

@ -64,7 +64,20 @@ extension PureXML.Parsing.ByteDecoder {
}
}
private static func scalar(_ value: UInt32) -> Unicode.Scalar {
/// ISO-8859-2 (Latin-2, Central European). Vendored from the authoritative
/// Unicode mapping file.
static func iso8859_2(_ byte: UInt8) -> Unicode.Scalar {
upperHalf(byte, latin2Upper)
}
/// Maps a byte through a 96-entry upper-half table (`0xA0`-`0xFF`); bytes
/// below `0xA0` are ASCII or C1 controls (identity). Used by the encodings
/// whose upper half is vendored from the Unicode mapping files.
static func upperHalf(_ byte: UInt8, _ table: [UInt16]) -> Unicode.Scalar {
byte >= 0xA0 ? scalar(UInt32(table[Int(byte) - 0xA0])) : Unicode.Scalar(byte)
}
static func scalar(_ value: UInt32) -> Unicode.Scalar {
Unicode.Scalar(value) ?? "\u{FFFD}"
}
}

View File

@ -0,0 +1,18 @@
extension PureXML.Parsing.ByteDecoder.SingleByte {
/// The upper half (bytes 0xA0-0xFF) of ISO-8859-2 (Latin-2), vendored verbatim
/// from the Unicode mapping file `MAPPINGS/ISO8859/8859-2.TXT`.
static let latin2Upper: [UInt16] = [
0x00A0, 0x0104, 0x02D8, 0x0141, 0x00A4, 0x013D, 0x015A, 0x00A7,
0x00A8, 0x0160, 0x015E, 0x0164, 0x0179, 0x00AD, 0x017D, 0x017B,
0x00B0, 0x0105, 0x02DB, 0x0142, 0x00B4, 0x013E, 0x015B, 0x02C7,
0x00B8, 0x0161, 0x015F, 0x0165, 0x017A, 0x02DD, 0x017E, 0x017C,
0x0154, 0x00C1, 0x00C2, 0x0102, 0x00C4, 0x0139, 0x0106, 0x00C7,
0x010C, 0x00C9, 0x0118, 0x00CB, 0x011A, 0x00CD, 0x00CE, 0x010E,
0x0110, 0x0143, 0x0147, 0x00D3, 0x00D4, 0x0150, 0x00D6, 0x00D7,
0x0158, 0x016E, 0x00DA, 0x0170, 0x00DC, 0x00DD, 0x0162, 0x00DF,
0x0155, 0x00E1, 0x00E2, 0x0103, 0x00E4, 0x013A, 0x0107, 0x00E7,
0x010D, 0x00E9, 0x0119, 0x00EB, 0x011B, 0x00ED, 0x00EE, 0x010F,
0x0111, 0x0144, 0x0148, 0x00F3, 0x00F4, 0x0151, 0x00F6, 0x00F7,
0x0159, 0x016F, 0x00FA, 0x0171, 0x00FC, 0x00FD, 0x0163, 0x02D9,
]
}

View File

@ -100,6 +100,14 @@ struct EncodingTests {
try #expect(decoded("ISO-8859-9", [0xE9]) == "\u{E9}") // é, unchanged from Latin-1
}
@Test("Decodes ISO-8859-2 (Latin-2) from the vendored table")
func test_iso8859_2() throws {
try #expect(decoded("ISO-8859-2", [0xA1]) == "\u{0104}") // Ą
try #expect(decoded("ISO-8859-2", [0xE8]) == "\u{010D}") // č
try #expect(decoded("ISO-8859-2", [0xE9]) == "\u{E9}") // é
try #expect(decoded("ISO-8859-2", [0xFF]) == "\u{02D9}") // ˙
}
@Test("Decodes ISO-8859-5: the Cyrillic block")
func test_iso8859_5() throws {
try #expect(decoded("ISO-8859-5", [0xB0]) == "\u{0410}") // А