PureXML/Sources/Parsing/InputEncoding.swift
Mihaela Mihaljevic 8d30010e0d docs: align stale figures with code and the enforced XSTS gate
- README, roadmap: XSTS status quad and gates table now read the enforced
  0/43/0/31 (Tests/XSTSSuiteTests.swift); test count 986/169 to 1448/258;
  encoding list and the remaining-work section reflect shipped reality;
  check-all expansion lists the validation and changelog gates
- GEMINI.md, onboarding-antigravity.md: valid-schemas-rejected 1 to 0 and the
  pinned baselines updated to current
- SECURITY.md: complete the reporting address
- Parsing.InputEncoding: doc comment lists the full shipped encoding set
- Schema.XSDParserUnion: guard the localName interpolation with ?? "" to match
  the same line and silence an optional-interpolation warning (internal dedup
  key, behavior preserving)
- CHANGELOG: Unreleased entries for the above
2026-06-19 23:32:04 +02:00

91 lines
3.4 KiB
Swift

public extension PureXML.Parsing {
/// A character encoding PureXML can decode from raw bytes. Covers the Unicode
/// transformation formats, the ISO-8859 single-byte family, the Windows code
/// pages (1250-1258), KOI8-R/U, and the CJK multi-byte encodings (Shift-JIS,
/// EUC-JP/KR/TW, GBK, GB18030, Big5, ISO-2022-JP), for libxml2 encoding parity.
enum InputEncoding: String, Equatable, Sendable {
case utf8
case utf16BigEndian
case utf16LittleEndian
case utf32BigEndian
case utf32LittleEndian
case latin1
case windows1252
case windows1254 // Turkish
case latin2 // ISO-8859-2
case latin3 // ISO-8859-3
case latin4 // ISO-8859-4
case greek // ISO-8859-7
case latin7 // ISO-8859-13
case arabic // ISO-8859-6
case hebrew // ISO-8859-8
case latin6 // ISO-8859-10
case latin8 // ISO-8859-14
case latin10 // ISO-8859-16
case thai // ISO-8859-11
case windows1250
case windows1251
case windows1253
case windows1257
case windows1255
case windows1256
case windows1258
case koi8r
case koi8u
case shiftJIS
case eucJP
case eucKR
case gbk
case gb18030
case big5
case iso2022jp
case eucTW
case latinCyrillic // ISO-8859-5
case latin5 // ISO-8859-9 (Turkish)
case latin9 // ISO-8859-15
/// Detects the encoding from the leading bytes, following the XML sniff
/// order: a byte-order mark first, then the `<?` byte pattern of the XML
/// declaration, defaulting to UTF-8. Single-byte encodings are not
/// detectable from bytes alone; they come from the declared encoding name.
public static func detect(_ bytes: [UInt8]) -> InputEncoding {
detectWithBOM(bytes).encoding
}
/// Detects the encoding and the length of any byte-order mark to strip.
static func detectWithBOM(_ bytes: [UInt8]) -> (encoding: InputEncoding, bomLength: Int) {
if bytes.starts(with: [0xEF, 0xBB, 0xBF]) {
return (.utf8, 3)
}
// UTF-32 marks are four bytes and must be checked before UTF-16, since
// the UTF-32LE mark begins with the UTF-16LE mark.
if bytes.starts(with: [0x00, 0x00, 0xFE, 0xFF]) {
return (.utf32BigEndian, 4)
}
if bytes.starts(with: [0xFF, 0xFE, 0x00, 0x00]) {
return (.utf32LittleEndian, 4)
}
if bytes.starts(with: [0xFE, 0xFF]) {
return (.utf16BigEndian, 2)
}
if bytes.starts(with: [0xFF, 0xFE]) {
return (.utf16LittleEndian, 2)
}
// No BOM: sniff the "<?" byte pattern of an XML declaration.
if bytes.starts(with: [0x00, 0x00, 0x00, 0x3C]) {
return (.utf32BigEndian, 0)
}
if bytes.starts(with: [0x3C, 0x00, 0x00, 0x00]) {
return (.utf32LittleEndian, 0)
}
if bytes.starts(with: [0x00, 0x3C, 0x00, 0x3F]) {
return (.utf16BigEndian, 0)
}
if bytes.starts(with: [0x3C, 0x00, 0x3F, 0x00]) {
return (.utf16LittleEndian, 0)
}
return (.utf8, 0)
}
}
}