PureXML/Sources/Parsing/ByteDecoder.swift
Mihaela Mihaljevic e4b2e0127d feat(parser): accept raw bytes with UTF-8/UTF-16 encoding detection
Add PureXML.parse(bytes:) and events(bytes:) for raw [UInt8] input. The encoding
is detected following the XML sniff order (BOM first, then the <? byte pattern,
defaulting to UTF-8) and decoded with the Swift standard library only, so the
package stays Foundation-free and portable. UTF-8 and UTF-16 (both byte orders,
with or without a BOM) are supported, the encoding set the XML spec requires.
Malformed input (odd-length UTF-16) raises ParseError.malformedEncoding.

PureXML.Parsing.InputEncoding.detect(_:) exposes the detector. The lint rule
optional_data_string_conversion is disabled because its suggested fix is a
Foundation API this package cannot use. Reimplemented from the documented XML
byte-detection algorithm; no upstream code copied.
2026-06-08 00:52:56 +02:00

36 lines
1.6 KiB
Swift

extension PureXML.Parsing {
/// Decodes raw bytes into a string using the detected encoding, stripping any
/// byte-order mark. UTF-8 and UTF-16 (both byte orders) are supported, which
/// is the encoding set the XML specification requires of every processor.
/// Uses only the Swift standard library (no Foundation), so it stays portable.
enum ByteDecoder {
static func decode(_ bytes: [UInt8]) throws -> String {
let (encoding, bomLength) = InputEncoding.detectWithBOM(bytes)
let body = bytes.dropFirst(bomLength)
switch encoding {
case .utf8:
return String(decoding: body, as: UTF8.self)
case .utf16BigEndian:
return try decodeUTF16(body, bigEndian: true)
case .utf16LittleEndian:
return try decodeUTF16(body, bigEndian: false)
}
}
private static func decodeUTF16(_ bytes: ArraySlice<UInt8>, bigEndian: Bool) throws -> String {
guard bytes.count.isMultiple(of: 2) else {
throw PureXML.Parsing.ParseError.malformedEncoding
}
var units: [UInt16] = []
units.reserveCapacity(bytes.count / 2)
var iterator = bytes.makeIterator()
while let first = iterator.next(), let second = iterator.next() {
let high = UInt16(first)
let low = UInt16(second)
units.append(bigEndian ? (high << 8 | low) : (low << 8 | high))
}
return String(decoding: units, as: UTF16.self)
}
}
}