use alloc::string::{String, ToString as _};
use alloc::vec::Vec;
use crate::{DecodeError, EncodeError, Encoding, Table};
const REPLACEMENT: char = '\u{FFFD}';
fn lookup2(table: &Table, lead: u8, trail: u8) -> Option<&'static str> {
table
.dec2
.iter()
.find(|(l, t, _)| *l == lead && *t == trail)
.map(|(_, _, text)| *text)
}
fn decode_inner(encoding: Encoding, bytes: &[u8], fatal: bool) -> Result<String, DecodeError> {
let table = encoding.table();
let mut out = String::with_capacity(bytes.len());
let mut i = 0;
while i < bytes.len() {
if !table.dec2.is_empty() && i + 1 < bytes.len() {
if let Some(text) = lookup2(table, bytes[i], bytes[i + 1]) {
out.push_str(text);
i += 2;
continue;
}
}
let text = table.dec1[bytes[i] as usize];
if text.is_empty() {
if fatal {
return Err(DecodeError {
encoding,
byte: bytes[i],
index: i,
});
}
out.push(REPLACEMENT);
} else {
out.push_str(text);
}
i += 1;
}
Ok(out)
}
pub(crate) fn decode(encoding: Encoding, bytes: &[u8]) -> String {
decode_inner(encoding, bytes, false).expect("the error branch needs fatal")
}
pub(crate) fn decode_strict(encoding: Encoding, bytes: &[u8]) -> Result<String, DecodeError> {
decode_inner(encoding, bytes, true)
}
fn encode_prefix(table: &Table, rest: &str) -> Option<(&'static [u8], usize)> {
for (text, code) in table.enc_seq {
if rest.starts_with(text) {
return Some((code, text.len()));
}
}
let c = rest.chars().next()?;
table
.enc1
.binary_search_by_key(&c, |(k, _)| *k)
.ok()
.map(|i| (table.enc1[i].1, c.len_utf8()))
}
pub(crate) fn encode(encoding: Encoding, text: &str) -> Result<Vec<u8>, EncodeError> {
let table = encoding.table();
let mut out = Vec::with_capacity(text.len());
let mut i = 0;
while i < text.len() {
match encode_prefix(table, &text[i..]) {
Some((code, consumed)) => {
out.extend_from_slice(code);
i += consumed;
}
None => {
let code_point = text[i..]
.chars()
.next()
.expect("index is on a char boundary");
return Err(EncodeError {
encoding,
code_point,
index: i,
});
}
}
}
Ok(out)
}
pub(crate) fn encode_html(encoding: Encoding, text: &str) -> Vec<u8> {
let table = encoding.table();
let mut out = Vec::with_capacity(text.len());
let mut i = 0;
while i < text.len() {
match encode_prefix(table, &text[i..]) {
Some((code, consumed)) => {
out.extend_from_slice(code);
i += consumed;
}
None => {
let c = text[i..]
.chars()
.next()
.expect("index is on a char boundary");
out.extend_from_slice(b"&#");
out.extend_from_slice((c as u32).to_string().as_bytes());
out.push(b';');
i += c.len_utf8();
}
}
}
out
}