PureXML/Tests/OutputEncodingTests.swift
Mihaela Mihaljevic e62995a7a8 feat(emitting): output encoding for UTF and single-byte families
Add PureXML.Parsing.ByteEncoder, the inverse of ByteDecoder, and a
Serializer.serialize(_:encoding:) / PureXML.serialize(_:encoding:) that
emits bytes in a chosen encoding (the libxml2 save-with-encoding model).
Covers the Unicode transformation formats and the single-byte legacy
families, built by inverting the existing vendored to-Unicode tables, so
no new data is introduced. The declaration carries the canonical encoding
name, a UTF-16/32 stream is preceded by a byte-order mark, and any
unrepresentable scalar becomes a decimal numeric character reference, so
output round-trips. CJK output encodings remain (tracked on #106).

Part of #106, #105
2026-06-09 09:32:39 +02:00

55 lines
2.4 KiB
Swift
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

@testable import PureXML
import Testing
@Suite("Output encoding")
struct OutputEncodingTests {
private func rootText(_ node: PureXML.Model.Node) -> String? {
guard case let .document(children) = node else { return nil }
for child in children {
if case let .element(element) = child { return element.text }
}
return nil
}
private func serialized(_ xml: String, _ encoding: PureXML.Parsing.InputEncoding) throws -> [UInt8] {
try PureXML.serialize(PureXML.parse(xml), encoding: encoding)
}
@Test("ISO-8859-1 emits one byte per Latin-1 scalar and round-trips")
func test_latin1() throws {
let bytes = try serialized("<r>caf\u{E9}</r>", .latin1) // café
#expect(bytes.contains(0xE9)) // é is a single 0xE9 byte
#expect(!bytes.contains(0xC3)) // not the UTF-8 lead byte of é
try #expect(rootText(PureXML.parse(bytes: bytes)) == "caf\u{E9}")
}
@Test("ISO-8859-7 round-trips Greek and windows-1252 round-trips its punctuation")
func test_greekAndWindows() throws {
try #expect(rootText(PureXML.parse(bytes: serialized("<r>\u{0391}</r>", .greek))) == "\u{0391}") // Α
try #expect(rootText(PureXML.parse(bytes: serialized("<r>\u{20AC}\u{201C}</r>", .windows1252))) == "\u{20AC}\u{201C}")
}
@Test("An unrepresentable scalar becomes a numeric character reference")
func test_characterReferenceFallback() throws {
// U+4E2D (中) is not in Latin-1, so it is written as &#20013; (decimal).
let bytes = try serialized("<r>\u{4E2D}</r>", .latin1)
let text = String(decoding: bytes, as: UTF8.self)
#expect(text.contains("&#20013;"))
// The reference bytes are ASCII, so they re-parse to the original scalar.
try #expect(rootText(PureXML.parse(bytes: bytes)) == "\u{4E2D}")
}
@Test("The declaration carries the canonical encoding name")
func test_declarationName() throws {
let text = try String(decoding: serialized("<r>x</r>", .latin1), as: UTF8.self)
#expect(text.hasPrefix("<?xml version=\"1.0\" encoding=\"ISO-8859-1\"?>"))
}
@Test("UTF-16 output carries a byte-order mark and round-trips")
func test_utf16() throws {
let bytes = try serialized("<r>hi\u{4E2D}</r>", .utf16BigEndian)
#expect(Array(bytes.prefix(2)) == [0xFE, 0xFF]) // BOM
try #expect(rootText(PureXML.parse(bytes: bytes)) == "hi\u{4E2D}")
}
}