diff --git a/CHANGELOG.md b/CHANGELOG.md index 7e8a125..0117eaa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 three encodings expressible exactly (so transcription-error-free): ISO-8859-15 (Latin-9, euro), ISO-8859-9 (Latin-5, Turkish), and ISO-8859-5 (Latin/Cyrillic). 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 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. ### Fixed diff --git a/README.md b/README.md index f7c953a..3d2e53a 100644 --- a/README.md +++ b/README.md @@ -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 **753 tests in 117 suites** (`swift test`). +currently runs **754 tests in 117 suites** (`swift test`). ### Shipped (libxml2-aligned surface) diff --git a/Sources/Parsing/ByteDecoder.swift b/Sources/Parsing/ByteDecoder.swift index 8a11e50..b4ae670 100644 --- a/Sources/Parsing/ByteDecoder.swift +++ b/Sources/Parsing/ByteDecoder.swift @@ -31,6 +31,7 @@ extension PureXML.Parsing { switch encoding { case .latin1: { Unicode.Scalar($0) } case .windows1252: windows1252Scalar + case .windows1254: SingleByte.windows1254 case .latinCyrillic: SingleByte.iso8859_5 case .latin5: SingleByte.iso8859_9 case .latin9: SingleByte.iso8859_15 @@ -98,6 +99,7 @@ extension PureXML.Parsing { "utf-8": .utf8, "utf8": .utf8, "us-ascii": .utf8, "ascii": .utf8, "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-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, diff --git a/Sources/Parsing/InputEncoding.swift b/Sources/Parsing/InputEncoding.swift index c7a78ee..055ddec 100644 --- a/Sources/Parsing/InputEncoding.swift +++ b/Sources/Parsing/InputEncoding.swift @@ -10,6 +10,7 @@ public extension PureXML.Parsing { case utf32LittleEndian case latin1 case windows1252 + case windows1254 // Turkish case latinCyrillic // ISO-8859-5 case latin5 // ISO-8859-9 (Turkish) case latin9 // ISO-8859-15 diff --git a/Sources/Parsing/SingleByteEncodings.swift b/Sources/Parsing/SingleByteEncodings.swift index c87a634..153544d 100644 --- a/Sources/Parsing/SingleByteEncodings.swift +++ b/Sources/Parsing/SingleByteEncodings.swift @@ -35,6 +35,20 @@ extension PureXML.Parsing.ByteDecoder { } } + /// Windows-1254 (Turkish): Windows-1252 with the same six Turkish letters + /// as ISO-8859-9 (the only differences fall in the Latin-1 upper half). + static func windows1254(_ byte: UInt8) -> Unicode.Scalar { + switch byte { + case 0xD0: "\u{011E}" // Ğ + case 0xDD: "\u{0130}" // İ + case 0xDE: "\u{015E}" // Ş + case 0xF0: "\u{011F}" // ğ + case 0xFD: "\u{0131}" // ı + case 0xFE: "\u{015F}" // ş + default: PureXML.Parsing.ByteDecoder.windows1252Scalar(byte) + } + } + /// ISO-8859-5 (Latin/Cyrillic): the Cyrillic alphabet laid out as a /// contiguous block from `0xB0`, with a handful of named exceptions. static func iso8859_5(_ byte: UInt8) -> Unicode.Scalar { diff --git a/Tests/EncodingTests.swift b/Tests/EncodingTests.swift index 0ae78bc..81303b6 100644 --- a/Tests/EncodingTests.swift +++ b/Tests/EncodingTests.swift @@ -107,6 +107,13 @@ struct EncodingTests { try #expect(decoded("ISO-8859-5", [0xF0]) == "\u{2116}") // № } + @Test("Decodes Windows-1254: CP1252 punctuation plus the Turkish letters") + func test_windows1254() throws { + try #expect(decoded("windows-1254", [0x80]) == "\u{20AC}") // € (from CP1252 high range) + try #expect(decoded("windows-1254", [0xDE]) == "\u{015E}") // Ş + try #expect(decoded("windows-1254", [0xE9]) == "\u{E9}") // é, unchanged + } + private func byteSource(_ bytes: [UInt8]) -> () -> UInt8? { var index = 0 return {