use alloc::vec::Vec;
use crate::Encoding;
use crate::result::{CoderResult, EncoderResult};
use crate::sink::ByteSink;
use crate::variant::VariantEncoder;
pub const ENCODER_MIN_BUFFER: usize = 4;
const MAX_NUMERIC_REFERENCE: usize = 10;
#[derive(Debug, Clone)]
pub struct Encoder {
encoding: &'static Encoding,
variant: VariantEncoder,
pending: [u8; MAX_NUMERIC_REFERENCE],
pending_pos: u8,
pending_len: u8,
}
impl Encoder {
pub(crate) fn new(encoding: &'static Encoding) -> Encoder {
Encoder {
encoding,
variant: encoding.variant().new_encoder(),
pending: [0; MAX_NUMERIC_REFERENCE],
pending_pos: 0,
pending_len: 0,
}
}
pub fn encoding(&self) -> &'static Encoding {
self.encoding
}
pub fn max_buffer_length_from_utf8(&self, byte_length: usize) -> Option<usize> {
self.variant.max_buffer_length_from_utf8(byte_length)
}
pub fn max_buffer_length_from_utf8_without_replacement(
&self,
byte_length: usize,
) -> Option<usize> {
self.variant
.max_buffer_length_from_utf8_without_replacement(byte_length)
}
pub fn encode_from_utf8(
&mut self,
src: &str,
dst: &mut [u8],
last: bool,
) -> (EncoderResult, usize, usize) {
let mut sink = ByteSink::new(dst);
if !self.drain_pending(&mut sink) {
return (EncoderResult::OutputFull, 0, sink.written());
}
let (result, read) = self.variant.encode(src, &mut sink, last);
(result, read, sink.written())
}
pub fn encode_from_utf8_with_replacement(
&mut self,
src: &str,
dst: &mut [u8],
last: bool,
) -> (CoderResult, usize, usize, bool) {
let mut total_read = 0usize;
let mut total_written = 0usize;
let mut had_unmappable = false;
loop {
let (result, read, written) =
self.encode_from_utf8(&src[total_read..], &mut dst[total_written..], last);
total_read += read;
total_written += written;
match result {
EncoderResult::Unmappable(c) => {
had_unmappable = true;
self.queue_numeric_reference(c);
let mut sink = ByteSink::new(&mut dst[total_written..]);
let drained = self.drain_pending(&mut sink);
total_written += sink.written();
if !drained {
return (
CoderResult::OutputFull,
total_read,
total_written,
had_unmappable,
);
}
}
other => {
return (
other.as_coder_result(),
total_read,
total_written,
had_unmappable,
);
}
}
}
}
pub fn encode_from_str(&mut self, src: &str, dst: &mut Vec<u8>, last: bool) -> bool {
let mut buffer = [0u8; 1024];
let mut read = 0usize;
let mut had_unmappable = false;
loop {
let (result, n, written, unmappable) =
self.encode_from_utf8_with_replacement(&src[read..], &mut buffer, last);
read += n;
had_unmappable |= unmappable;
dst.extend_from_slice(&buffer[..written]);
if result == CoderResult::InputEmpty {
return had_unmappable;
}
}
}
pub fn encode_from_str_without_replacement(
&mut self,
src: &str,
dst: &mut Vec<u8>,
last: bool,
) -> Result<(), UnmappableError> {
let mut buffer = [0u8; 1024];
let mut read = 0usize;
loop {
let (result, n, written) = self.encode_from_utf8(&src[read..], &mut buffer, last);
read += n;
dst.extend_from_slice(&buffer[..written]);
match result {
EncoderResult::InputEmpty => return Ok(()),
EncoderResult::OutputFull => {}
EncoderResult::Unmappable(c) => {
return Err(UnmappableError {
character: c,
offset: read,
});
}
}
}
}
fn queue_numeric_reference(&mut self, c: char) {
debug_assert_eq!(self.pending_len, 0);
let mut digits = [0u8; 7];
let mut value = u32::from(c);
let mut n = 0;
loop {
digits[n] = b'0' + (value % 10) as u8;
value /= 10;
n += 1;
if value == 0 {
break;
}
}
let mut len = 0;
for (i, byte) in b"&#".iter().enumerate() {
self.pending[i] = *byte;
len += 1;
}
for i in (0..n).rev() {
self.pending[len] = digits[i];
len += 1;
}
self.pending[len] = b';';
len += 1;
self.pending_pos = 0;
self.pending_len = len as u8;
}
fn drain_pending(&mut self, sink: &mut ByteSink) -> bool {
if self.pending_pos == self.pending_len {
return true;
}
let (start, end) = (usize::from(self.pending_pos), usize::from(self.pending_len));
let written = sink.write_slice_partial(&self.pending[start..end]);
self.pending_pos += written as u8;
if self.pending_pos == self.pending_len {
self.pending_pos = 0;
self.pending_len = 0;
true
} else {
false
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct UnmappableError {
pub character: char,
pub offset: usize,
}
impl core::fmt::Display for UnmappableError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(
f,
"U+{:04X} cannot be represented in this encoding (at offset {})",
u32::from(self.character),
self.offset
)
}
}
#[cfg(feature = "std")]
impl std::error::Error for UnmappableError {}