Skip to main content

oxixml_encoding/decoder/
mod.rs

1//! Incremental, chunk-safe decoding from bytes to [`String`]s.
2
3mod single_byte;
4mod utf16;
5mod utf8;
6
7use crate::cjk::{big5, euc_kr, gb18030};
8use crate::jis::{euc_jp, iso2022jp, shift_jis};
9use crate::label::Encoding;
10
11/// An error reported by [`Decoder::decode_to_string`].
12///
13/// All positional information (`offset`, `length`) is expressed in bytes and is relative to the
14/// `input` slice passed to the specific [`decode_to_string`](Decoder::decode_to_string) call
15/// that returned this error — *not* to the logical start of the overall byte stream. A caller
16/// tracking a running total offset across chunks should add the sum of the lengths of all
17/// previously-consumed chunks to `offset` to obtain a stream-relative position.
18///
19/// When the malformed data spans a chunk boundary (part of it was buffered internally from a
20/// previous call), `offset` is clamped to `0`: the problem became visible only once the new
21/// bytes arrived, so `0` is the most useful position a caller can act on (for example, to slice
22/// `input[..0]` off before the error and report the rest as unconsumed).
23#[derive(Debug, Clone, Copy, PartialEq, Eq)]
24pub struct DecodeError {
25    /// Byte offset within the `input` slice of the call that failed.
26    pub offset: usize,
27    /// The number of bytes that make up the malformed/incomplete unit, where known. This is a
28    /// hint for error-recovery strategies (e.g. "skip `length` bytes and retry"); it may be `0`
29    /// when no specific byte range is at fault (e.g. an unpaired surrogate detected only once a
30    /// *following* code unit failed to complete the pair).
31    pub length: usize,
32    /// What kind of problem was found.
33    pub kind: DecodeErrorKind,
34}
35
36/// The kind of problem a [`DecodeError`] reports.
37#[derive(Debug, Clone, Copy, PartialEq, Eq)]
38pub enum DecodeErrorKind {
39    /// A byte sequence that can never be valid in this encoding (e.g. a UTF-8 continuation byte
40    /// in a position where a lead byte was expected, or an overlong encoding).
41    MalformedSequence,
42    /// The input ended in the middle of a multi-byte sequence and the caller signaled `last =
43    /// true`, so no further bytes will arrive to complete it.
44    TruncatedSequence,
45    /// A UTF-16 high surrogate was not followed by a low surrogate (or vice versa).
46    UnpairedSurrogate,
47    /// A byte outside the encoding's repertoire was found (e.g. `0x80..=0xFF` in
48    /// [`Encoding::Ascii`]).
49    DisallowedByte,
50}
51
52impl std::fmt::Display for DecodeError {
53    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
54        let what = match self.kind {
55            DecodeErrorKind::MalformedSequence => "malformed byte sequence",
56            DecodeErrorKind::TruncatedSequence => "truncated byte sequence at end of input",
57            DecodeErrorKind::UnpairedSurrogate => "unpaired UTF-16 surrogate",
58            DecodeErrorKind::DisallowedByte => "byte outside the encoding's repertoire",
59        };
60        write!(
61            f,
62            "{what} at offset {} (length {})",
63            self.offset, self.length
64        )
65    }
66}
67
68impl std::error::Error for DecodeError {}
69
70/// An incremental decoder for one [`Encoding`].
71///
72/// A `Decoder` is created via [`Encoding::new_decoder`] and is fed the byte stream in one or
73/// more calls to [`decode_to_string`](Decoder::decode_to_string) (or its lossy counterpart,
74/// [`decode_to_string_lossy`](Decoder::decode_to_string_lossy)). Chunks may be split at any
75/// byte boundary — including in the middle of a multi-byte sequence or a UTF-16 surrogate pair
76/// — and the decoder carries the necessary state between calls to produce output identical to
77/// decoding the concatenation of all chunks in one call.
78///
79/// The final chunk (which may be empty) must be passed with `last = true` so the decoder can
80/// detect a sequence left truncated at the true end of the stream.
81///
82/// # Examples
83///
84/// Feeding a UTF-8 string one byte at a time still reassembles multi-byte characters correctly:
85///
86/// ```
87/// use oxixml_encoding::Encoding;
88///
89/// let text = "a→b🎉c"; // mixes ASCII, a 3-byte BMP arrow, and a 4-byte astral emoji.
90/// let mut decoder = Encoding::Utf8.new_decoder();
91/// let mut out = String::new();
92/// let bytes = text.as_bytes();
93/// for i in 0..bytes.len() {
94///     let last = i + 1 == bytes.len();
95///     decoder.decode_to_string(&bytes[i..=i], &mut out, last).expect("valid utf-8");
96/// }
97/// assert_eq!(out, text);
98/// ```
99#[derive(Debug, Clone)]
100pub struct Decoder {
101    kind: DecoderKind,
102}
103
104#[derive(Debug, Clone)]
105enum DecoderKind {
106    Utf8(utf8::Utf8State),
107    Utf16Le(utf16::Utf16State),
108    Utf16Be(utf16::Utf16State),
109    Latin1,
110    Windows1252,
111    Ascii,
112    /// Any of the 26 legacy single-byte encodings backed by a generated 128-entry upper-half
113    /// table (see [`Encoding::is_legacy_single_byte_table`]). Stateless, like the three variants
114    /// above, so the specific [`Encoding`] is carried directly rather than via a per-encoding
115    /// unit-struct variant.
116    LegacySingleByte(Encoding),
117    ShiftJis(shift_jis::ShiftJisState),
118    EucJp(euc_jp::EucJpState),
119    Iso2022Jp(iso2022jp::Iso2022JpState),
120    /// GBK and gb18030 share this state and decode identically; see [`Encoding::Gbk`] and
121    /// [`Encoding::Gb18030`] for how the two differ (only in their encoder).
122    Gbk(gb18030::Gb18030State),
123    Gb18030(gb18030::Gb18030State),
124    Big5(big5::Big5State),
125    EucKr(euc_kr::EucKrState),
126}
127
128impl Decoder {
129    /// Create a new decoder for `encoding`. Prefer [`Encoding::new_decoder`], which forwards
130    /// here.
131    #[must_use]
132    pub(crate) const fn new(encoding: Encoding) -> Decoder {
133        let kind = match encoding {
134            Encoding::Utf8 => DecoderKind::Utf8(utf8::Utf8State::new()),
135            Encoding::Utf16Le => DecoderKind::Utf16Le(utf16::Utf16State::new()),
136            Encoding::Utf16Be => DecoderKind::Utf16Be(utf16::Utf16State::new()),
137            Encoding::Latin1 => DecoderKind::Latin1,
138            Encoding::Windows1252 => DecoderKind::Windows1252,
139            Encoding::Ascii => DecoderKind::Ascii,
140            Encoding::ShiftJis => DecoderKind::ShiftJis(shift_jis::ShiftJisState::new()),
141            Encoding::EucJp => DecoderKind::EucJp(euc_jp::EucJpState::new()),
142            Encoding::Iso2022Jp => DecoderKind::Iso2022Jp(iso2022jp::Iso2022JpState::new()),
143            Encoding::Gbk => DecoderKind::Gbk(gb18030::Gb18030State::new()),
144            Encoding::Gb18030 => DecoderKind::Gb18030(gb18030::Gb18030State::new()),
145            Encoding::Big5 => DecoderKind::Big5(big5::Big5State::new()),
146            Encoding::EucKr => DecoderKind::EucKr(euc_kr::EucKrState::new()),
147            // Every remaining variant is one of the 26 legacy single-byte encodings; verified in
148            // debug/test builds (this function must stay `const`, so a full `match` guard on
149            // `is_legacy_single_byte_table` is not usable here without also enumerating all 26
150            // variants a second time). `crate::tables::legacy_single_byte_table` enumerates
151            // every `Encoding` variant explicitly rather than through a wildcard, so a future
152            // variant left unhandled everywhere is still caught at compile time there, even if
153            // this debug-only assertion is never exercised by a test.
154            other => {
155                debug_assert!(
156                    other.is_legacy_single_byte_table(),
157                    "Decoder::new: unhandled Encoding variant"
158                );
159                DecoderKind::LegacySingleByte(other)
160            }
161        };
162        Decoder { kind }
163    }
164
165    /// The encoding this decoder was created for.
166    #[must_use]
167    pub const fn encoding(&self) -> Encoding {
168        match &self.kind {
169            DecoderKind::Utf8(_) => Encoding::Utf8,
170            DecoderKind::Utf16Le(_) => Encoding::Utf16Le,
171            DecoderKind::Utf16Be(_) => Encoding::Utf16Be,
172            DecoderKind::Latin1 => Encoding::Latin1,
173            DecoderKind::Windows1252 => Encoding::Windows1252,
174            DecoderKind::Ascii => Encoding::Ascii,
175            DecoderKind::LegacySingleByte(encoding) => *encoding,
176            DecoderKind::ShiftJis(_) => Encoding::ShiftJis,
177            DecoderKind::EucJp(_) => Encoding::EucJp,
178            DecoderKind::Iso2022Jp(_) => Encoding::Iso2022Jp,
179            DecoderKind::Gbk(_) => Encoding::Gbk,
180            DecoderKind::Gb18030(_) => Encoding::Gb18030,
181            DecoderKind::Big5(_) => Encoding::Big5,
182            DecoderKind::EucKr(_) => Encoding::EucKr,
183        }
184    }
185
186    /// Whether this decoder is currently holding onto part of an incomplete multi-byte sequence
187    /// (or an unpaired UTF-16 high surrogate) carried over from a previous
188    /// [`decode_to_string`](Decoder::decode_to_string) call.
189    ///
190    /// Useful for a caller that wants to detect a stream that ends mid-sequence *without*
191    /// making a final `last = true` call (for example, when a network connection drops).
192    #[must_use]
193    pub const fn has_pending_state(&self) -> bool {
194        match &self.kind {
195            DecoderKind::Utf8(state) => state.has_pending(),
196            DecoderKind::Utf16Le(state) | DecoderKind::Utf16Be(state) => state.has_pending(),
197            DecoderKind::Latin1
198            | DecoderKind::Windows1252
199            | DecoderKind::Ascii
200            | DecoderKind::LegacySingleByte(_) => false,
201            DecoderKind::ShiftJis(state) => state.has_pending(),
202            DecoderKind::EucJp(state) => state.has_pending(),
203            DecoderKind::Iso2022Jp(state) => state.has_pending(),
204            DecoderKind::Gbk(state) | DecoderKind::Gb18030(state) => state.has_pending(),
205            DecoderKind::Big5(state) => state.has_pending(),
206            DecoderKind::EucKr(state) => state.has_pending(),
207        }
208    }
209
210    /// Decode as much of `input` as possible, appending the result to `dst`.
211    ///
212    /// `input` need not end on a character boundary: an incomplete trailing sequence is saved
213    /// internally and completed by the start of the next call. Pass `last = true` for the final
214    /// chunk of the stream (an empty final chunk is fine) so that a sequence left dangling at
215    /// true end-of-input is reported as [`DecodeErrorKind::TruncatedSequence`] rather than
216    /// silently held onto forever.
217    ///
218    /// Returns the number of bytes of `input` incorporated into the decode (either emitted as
219    /// characters or buffered as a pending partial sequence). On success this is always
220    /// `input.len()`. On error, see [`DecodeError`] for how `offset`/`length` are interpreted;
221    /// any valid characters that appeared before the error have already been appended to `dst`,
222    /// and the decoder's carried state is reset to empty so the `Decoder` remains usable for a
223    /// fresh call (e.g. after the caller skips the offending bytes).
224    ///
225    /// # Errors
226    ///
227    /// Returns [`DecodeError`] if `input` (combined with any carried-over state) contains a
228    /// byte sequence that is not valid in this decoder's encoding, or ends in a sequence that
229    /// `last = true` asserts will never be completed.
230    pub fn decode_to_string(
231        &mut self,
232        input: &[u8],
233        dst: &mut String,
234        last: bool,
235    ) -> Result<usize, DecodeError> {
236        match &mut self.kind {
237            DecoderKind::Utf8(state) => utf8::decode(state, input, dst, last),
238            DecoderKind::Utf16Le(state) => utf16::decode(state, input, dst, last, false),
239            DecoderKind::Utf16Be(state) => utf16::decode(state, input, dst, last, true),
240            DecoderKind::Latin1 => single_byte::decode_latin1(input, dst),
241            DecoderKind::Windows1252 => single_byte::decode_windows1252(input, dst),
242            DecoderKind::Ascii => single_byte::decode_ascii(input, dst),
243            DecoderKind::LegacySingleByte(encoding) => {
244                single_byte::decode_legacy_single_byte(*encoding, input, dst)
245            }
246            DecoderKind::ShiftJis(state) => shift_jis::decode(state, input, dst, last),
247            DecoderKind::EucJp(state) => euc_jp::decode(state, input, dst, last),
248            DecoderKind::Iso2022Jp(state) => iso2022jp::decode(state, input, dst, last),
249            DecoderKind::Gbk(state) | DecoderKind::Gb18030(state) => {
250                gb18030::decode(state, input, dst, last)
251            }
252            DecoderKind::Big5(state) => big5::decode(state, input, dst, last),
253            DecoderKind::EucKr(state) => euc_kr::decode(state, input, dst, last),
254        }
255    }
256
257    /// Like [`decode_to_string`](Decoder::decode_to_string), but never fails: any malformed or
258    /// disallowed byte sequence is replaced by one `U+FFFD REPLACEMENT CHARACTER` per the
259    /// [Unicode "maximal subpart" recovery algorithm][u5-22], and decoding continues from the
260    /// byte immediately after the replaced sequence.
261    ///
262    /// Always consumes (and returns) the full length of `input`.
263    ///
264    /// [u5-22]: https://www.unicode.org/versions/Unicode15.0.0/ch03.pdf#page=22
265    pub fn decode_to_string_lossy(&mut self, input: &[u8], dst: &mut String, last: bool) -> usize {
266        match &mut self.kind {
267            DecoderKind::Utf8(state) => utf8::decode_lossy(state, input, dst, last),
268            DecoderKind::Utf16Le(state) => utf16::decode_lossy(state, input, dst, last, false),
269            DecoderKind::Utf16Be(state) => utf16::decode_lossy(state, input, dst, last, true),
270            DecoderKind::Latin1 => single_byte::decode_latin1_lossy(input, dst),
271            DecoderKind::Windows1252 => single_byte::decode_windows1252_lossy(input, dst),
272            DecoderKind::Ascii => single_byte::decode_ascii_lossy(input, dst),
273            DecoderKind::LegacySingleByte(encoding) => {
274                single_byte::decode_legacy_single_byte_lossy(*encoding, input, dst)
275            }
276            DecoderKind::ShiftJis(state) => shift_jis::decode_lossy(state, input, dst, last),
277            DecoderKind::EucJp(state) => euc_jp::decode_lossy(state, input, dst, last),
278            DecoderKind::Iso2022Jp(state) => iso2022jp::decode_lossy(state, input, dst, last),
279            DecoderKind::Gbk(state) | DecoderKind::Gb18030(state) => {
280                gb18030::decode_lossy(state, input, dst, last)
281            }
282            DecoderKind::Big5(state) => big5::decode_lossy(state, input, dst, last),
283            DecoderKind::EucKr(state) => euc_kr::decode_lossy(state, input, dst, last),
284        }
285    }
286}
287
288#[cfg(test)]
289mod tests {
290    use super::*;
291
292    #[test]
293    fn encoding_round_trips_for_every_variant() {
294        for &enc in Encoding::ALL {
295            assert_eq!(enc.new_decoder().encoding(), enc);
296        }
297    }
298
299    #[test]
300    fn fresh_decoder_has_no_pending_state() {
301        for &enc in Encoding::ALL {
302            assert!(!enc.new_decoder().has_pending_state(), "{enc}");
303        }
304    }
305
306    #[test]
307    fn shift_jis_euc_jp_iso2022jp_decoders_report_pending_state_mid_sequence()
308    -> Result<(), DecodeError> {
309        let mut decoder = Encoding::ShiftJis.new_decoder();
310        let mut out = String::new();
311        decoder.decode_to_string(&[0x82], &mut out, false)?;
312        assert!(decoder.has_pending_state());
313        decoder.decode_to_string(&[0xA0], &mut out, true)?;
314        assert!(!decoder.has_pending_state());
315
316        let mut decoder = Encoding::EucJp.new_decoder();
317        let mut out = String::new();
318        decoder.decode_to_string(&[0xA4], &mut out, false)?;
319        assert!(decoder.has_pending_state());
320        decoder.decode_to_string(&[0xA2], &mut out, true)?;
321        assert!(!decoder.has_pending_state());
322
323        let mut decoder = Encoding::Iso2022Jp.new_decoder();
324        let mut out = String::new();
325        decoder.decode_to_string(&[0x1B], &mut out, false)?;
326        assert!(decoder.has_pending_state());
327        decoder.decode_to_string(&[0x28, 0x42], &mut out, true)?;
328        assert!(!decoder.has_pending_state());
329        Ok(())
330    }
331
332    #[test]
333    fn cjk_decoders_report_pending_state_mid_sequence() -> Result<(), DecodeError> {
334        for (encoding, lead, trail) in [
335            (Encoding::Gbk, 0xD6u8, 0xD0u8),
336            (Encoding::Gb18030, 0xD6, 0xD0),
337            (Encoding::Big5, 0xA4, 0xA4),
338            (Encoding::EucKr, 0xC7, 0xD1),
339        ] {
340            let mut decoder = encoding.new_decoder();
341            let mut out = String::new();
342            decoder.decode_to_string(&[lead], &mut out, false)?;
343            assert!(decoder.has_pending_state(), "{encoding}");
344            decoder.decode_to_string(&[trail], &mut out, true)?;
345            assert!(!decoder.has_pending_state(), "{encoding}");
346        }
347        Ok(())
348    }
349
350    #[test]
351    fn legacy_single_byte_decoders_never_report_pending_state() {
352        for &enc in Encoding::ALL {
353            if !enc.is_legacy_single_byte_table() {
354                continue;
355            }
356            let mut decoder = enc.new_decoder();
357            let mut out = String::new();
358            let _ = decoder.decode_to_string(&[0x80], &mut out, false);
359            assert!(!decoder.has_pending_state(), "{enc}");
360        }
361    }
362
363    #[test]
364    fn utf8_decoder_reports_pending_state_mid_sequence() -> Result<(), DecodeError> {
365        let mut decoder = Encoding::Utf8.new_decoder();
366        let mut out = String::new();
367        // 0xE2 0x86 0x92 is '→'; feed only the first byte.
368        decoder.decode_to_string(&[0xE2], &mut out, false)?;
369        assert!(decoder.has_pending_state());
370        decoder.decode_to_string(&[0x86, 0x92], &mut out, true)?;
371        assert!(!decoder.has_pending_state());
372        assert_eq!(out, "→");
373        Ok(())
374    }
375
376    #[test]
377    fn decode_error_display_is_human_readable() {
378        let err = DecodeError {
379            offset: 3,
380            length: 1,
381            kind: DecodeErrorKind::DisallowedByte,
382        };
383        assert_eq!(
384            err.to_string(),
385            "byte outside the encoding's repertoire at offset 3 (length 1)"
386        );
387    }
388}