#[cfg(feature = "alloc")]
use alloc::vec::Vec;
use core::fmt;
use crate::Encoding;
use crate::options::{EncodeOptions, Tally, Unmappable};
use crate::result::EncoderResult;
use crate::sink::ByteSink;
use crate::variant::VariantEncoder;
pub const ENCODER_MIN_BUFFER: usize = 4;
const STAGING: usize = 16;
#[derive(Debug, Clone)]
pub struct Encoder {
encoding: &'static Encoding,
variant: VariantEncoder,
options: EncodeOptions,
pending: [u8; STAGING],
pending_pos: u8,
pending_len: u8,
escape_ambiguous: bool,
tally: Tally,
}
impl Encoder {
pub(crate) fn new(encoding: &'static Encoding, options: EncodeOptions) -> Encoder {
Encoder {
encoding,
variant: encoding.variant().new_encoder(),
options,
pending: [0; STAGING],
pending_pos: 0,
pending_len: 0,
escape_ambiguous: true,
tally: Tally::default(),
}
}
#[cfg(feature = "alloc")]
pub(crate) fn new_html_form(encoding: &'static Encoding) -> Encoder {
let mut encoder = Encoder::new(encoding, EncodeOptions::new().unmappable(Unmappable::Html));
encoder.escape_ambiguous = false;
encoder
}
pub fn encoding(&self) -> &'static Encoding {
self.encoding
}
pub fn tally(&self) -> Tally {
self.tally
}
pub fn max_buffer_length_from_utf8(&self, byte_length: usize) -> Option<usize> {
match self.options.unmappable {
Unmappable::Fail | Unmappable::Omit | Unmappable::Replace(_) => self
.variant
.max_buffer_length_from_utf8_without_replacement(byte_length),
Unmappable::Html | Unmappable::JsonEscape => {
self.variant.max_buffer_length_from_utf8(byte_length)
}
}
}
fn ambiguous(&self) -> Option<char> {
if !self.escape_ambiguous {
return None;
}
match self.options.unmappable {
Unmappable::Html => Some('&'),
Unmappable::JsonEscape => Some('\\'),
_ => None,
}
}
pub fn encode_from_utf8(
&mut self,
src: &str,
dst: &mut [u8],
last: bool,
) -> (EncoderResult, usize, usize) {
let mut sink = ByteSink::new(dst);
let mut read = 0usize;
loop {
if !self.drain_pending(&mut sink) {
return (EncoderResult::OutputFull, read, sink.written());
}
let rest = &src[read..];
let chunk = match self.ambiguous() {
Some(ambiguous) if rest.starts_with(ambiguous) => {
let escaped = if ambiguous == '&' { "&" } else { "\\\\" };
if let Err(bad) = self.stage(escaped) {
return (EncoderResult::Unmappable(bad), read, sink.written());
}
read += ambiguous.len_utf8();
continue;
}
Some(ambiguous) => rest.find(ambiguous).unwrap_or(rest.len()),
None => rest.len(),
};
let is_final = chunk == rest.len();
let (result, n) = self
.variant
.encode(&rest[..chunk], &mut sink, last && is_final);
read += n;
match result {
EncoderResult::InputEmpty if is_final => {
return (EncoderResult::InputEmpty, read, sink.written());
}
EncoderResult::InputEmpty => continue,
EncoderResult::OutputFull => {
return (EncoderResult::OutputFull, read, sink.written());
}
EncoderResult::Unmappable(c) => match self.handle(c) {
Ok(()) => continue,
Err(bad) => return (EncoderResult::Unmappable(bad), read, sink.written()),
},
}
}
}
fn handle(&mut self, c: char) -> Result<(), char> {
if self.options.unmappable == Unmappable::Fail && !self.will_transliterate(c) {
return Err(c);
}
self.tally.errors += 1;
#[cfg(feature = "translit")]
if self.options.transliterate {
if let Some(text) = crate::translit::ascii_fold(c) {
return self.stage(text);
}
if self.options.unmappable == Unmappable::Fail {
self.tally.errors -= 1;
return Err(c);
}
}
let mut buf = [0u8; STAGING];
match self.options.unmappable {
Unmappable::Fail => Err(c),
Unmappable::Omit => Ok(()),
Unmappable::Replace(replacement) => {
let mut small = [0u8; 4];
self.stage(replacement.encode_utf8(&mut small))
}
Unmappable::Html => self.stage(numeric_reference(c, &mut buf)),
Unmappable::JsonEscape => self.stage(json_escape(c, &mut buf)),
}
}
fn will_transliterate(&self, c: char) -> bool {
#[cfg(feature = "translit")]
{
self.options.transliterate && crate::translit::ascii_fold(c).is_some()
}
#[cfg(not(feature = "translit"))]
{
let _ = c;
false
}
}
fn stage(&mut self, text: &str) -> Result<(), char> {
debug_assert_eq!(self.pending_len, 0);
let mut buf = [0u8; STAGING];
let mut sink = ByteSink::new(&mut buf);
let (result, _) = self.variant.encode(text, &mut sink, false);
if let EncoderResult::Unmappable(c) = result {
return Err(c);
}
let written = sink.written();
self.pending[..written].copy_from_slice(&buf[..written]);
self.pending_pos = 0;
self.pending_len = written as u8;
Ok(())
}
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
}
}
#[cfg(feature = "alloc")]
pub fn encode_from_str(
&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 numeric_reference(c: char, buf: &mut [u8; STAGING]) -> &str {
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;
}
}
buf[0] = b'&';
buf[1] = b'#';
let mut len = 2;
for i in (0..n).rev() {
buf[len] = digits[i];
len += 1;
}
buf[len] = b';';
len += 1;
core::str::from_utf8(&buf[..len]).unwrap_or("�")
}
fn json_escape(c: char, buf: &mut [u8; STAGING]) -> &str {
fn unit(buf: &mut [u8], at: usize, unit: u16) {
const HEX: &[u8; 16] = b"0123456789abcdef";
buf[at] = b'\\';
buf[at + 1] = b'u';
for i in 0..4 {
buf[at + 2 + i] = HEX[((unit >> (12 - 4 * i)) & 0xF) as usize];
}
}
let mut units = [0u16; 2];
let units = c.encode_utf16(&mut units);
let mut len = 0;
for &u in units.iter() {
unit(buf, len, u);
len += 6;
}
core::str::from_utf8(&buf[..len]).unwrap_or("\\ufffd")
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct UnmappableError {
pub character: char,
pub offset: usize,
}
impl fmt::Display for UnmappableError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> 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 {}