use crate::{
CharsetDecodeError,
CharsetDecodeErrorKind,
Coder,
CoderProgress,
CoderStatus,
};
use super::{
charset_codec::CharsetCodec,
decode_status::DecodeStatus,
malformed_action::MalformedAction,
};
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct CharsetDecoder<C>
where
C: CharsetCodec,
{
codec: C,
malformed_action: MalformedAction,
replacement: char,
}
impl<C> CharsetDecoder<C>
where
C: CharsetCodec,
{
pub const DEFAULT_REPLACEMENT: char = '\u{fffd}';
#[must_use]
#[inline]
pub const fn new(codec: C) -> Self {
Self {
codec,
malformed_action: MalformedAction::Replace,
replacement: Self::DEFAULT_REPLACEMENT,
}
}
#[inline]
pub fn with_replacement(mut self, replacement: char) -> Self {
self.replacement = replacement;
self
}
#[must_use]
#[inline]
pub const fn codec(&self) -> &C {
&self.codec
}
#[must_use]
#[inline]
pub fn codec_mut(&mut self) -> &mut C {
&mut self.codec
}
#[must_use]
#[inline]
pub const fn malformed_action(&self) -> MalformedAction {
self.malformed_action
}
#[inline]
pub fn set_malformed_action(&mut self, action: MalformedAction) {
self.malformed_action = action;
}
#[must_use]
#[inline]
pub const fn replacement(&self) -> char {
self.replacement
}
#[inline]
pub fn set_replacement(&mut self, replacement: char) {
self.replacement = replacement;
}
}
impl<C> Coder<C::Unit, char> for CharsetDecoder<C>
where
C: CharsetCodec,
{
type Error = CharsetDecodeError;
#[inline]
fn max_output_len(&self, input_len: usize) -> Option<usize> {
Some(input_len)
}
fn convert(
&mut self,
input: &[C::Unit],
input_index: usize,
output: &mut [char],
output_index: usize,
) -> Result<CoderProgress, Self::Error> {
if input_index > input.len() {
let kind = CharsetDecodeErrorKind::MalformedSequence { value: None };
return Err(CharsetDecodeError::new(self.codec.charset(), kind, input_index));
}
if output_index > output.len() {
let status = CoderStatus::NeedOutput {
output_index,
required: 1,
available: 0,
};
return Ok(CoderProgress::new(status, 0, 0));
}
let mut input_cursor = input_index;
let mut output_cursor = output_index;
while input_cursor < input.len() {
if output_cursor == output.len() {
let status = CoderStatus::NeedOutput {
output_index: output_cursor,
required: 1,
available: 0,
};
return Ok(CoderProgress::new(
status,
input_cursor - input_index,
output_cursor - output_index,
));
}
match self.codec.decode_one(input, input_cursor) {
Ok(DecodeStatus::Complete { value, consumed }) => {
output[output_cursor] = value;
input_cursor += consumed;
output_cursor += 1;
}
Ok(DecodeStatus::NeedMore { required, available }) => {
let needed = required.saturating_sub(input_cursor);
let status = CoderStatus::NeedInput {
input_index: input_cursor,
required: needed,
available,
};
return Ok(CoderProgress::new(
status,
input_cursor - input_index,
output_cursor - output_index,
));
}
Err(error)
if matches!(
error.kind(),
CharsetDecodeErrorKind::MalformedSequence { .. }
| CharsetDecodeErrorKind::InvalidCodePoint { .. }
) =>
{
let skip = malformed_skip(input_cursor, input.len(), error.index());
match self.malformed_action {
MalformedAction::Report => return Err(error),
MalformedAction::Ignore => {
input_cursor += skip;
}
MalformedAction::Replace => {
output[output_cursor] = self.replacement;
input_cursor += skip;
output_cursor += 1;
}
}
}
Err(error) => return Err(error),
}
}
Ok(CoderProgress::complete(
input_cursor - input_index,
output_cursor - output_index,
))
}
}
#[inline]
fn malformed_skip(input_index: usize, input_len: usize, error_index: usize) -> usize {
let available = input_len.saturating_sub(input_index);
let end = error_index.saturating_add(1).min(input_len);
end.saturating_sub(input_index).max(1).min(available)
}