pub fn decode_latin1(bytes: &[u8]) -> String {
bytes.iter().map(|&b| b as char).collect()
}
pub fn decode_utf8_or_latin1(bytes: &[u8]) -> String {
match std::str::from_utf8(bytes) {
Ok(s) => s.to_string(),
Err(_) => decode_latin1(bytes),
}
}
pub fn encode_latin1(s: &str) -> Vec<u8> {
s.chars()
.map(|c| {
let cp = c as u32;
if cp <= 0xFF {
cp as u8
} else {
b'?'
}
})
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_encode_latin1_roundtrip() {
assert_eq!(encode_latin1("hello"), b"hello");
assert_eq!(encode_latin1("éüñ"), vec![0xE9, 0xFC, 0xF1]);
assert_eq!(encode_latin1("©®ö"), vec![0xA9, 0xAE, 0xF6]);
assert_eq!(
encode_latin1("Martín"),
vec![b'M', b'a', b'r', b't', 0xED, b'n']
);
}
#[test]
fn test_encode_latin1_substitutes_unrepresentable() {
assert_eq!(encode_latin1("Пример"), b"??????");
assert_eq!(encode_latin1("a😀b"), b"a?b");
}
#[test]
fn test_encode_decode_latin1_inverse() {
let s = "Àéîõü©";
assert_eq!(decode_latin1(&encode_latin1(s)), s);
}
#[test]
fn test_decode_latin1_ascii() {
assert_eq!(decode_latin1(b"hello"), "hello");
}
#[test]
fn test_decode_latin1_high_bytes() {
assert_eq!(decode_latin1(&[0xE9, 0xFC, 0xF1]), "éüñ");
}
#[test]
fn test_decode_latin1_full_range() {
assert_eq!(decode_latin1(&[0xA9, 0xAE, 0xF6]), "©®ö");
}
#[test]
fn test_decode_utf8_or_latin1_valid_utf8() {
assert_eq!(decode_utf8_or_latin1("café".as_bytes()), "café");
}
#[test]
fn test_decode_utf8_or_latin1_latin1_fallback() {
assert_eq!(decode_utf8_or_latin1(&[0x63, 0x61, 0x66, 0xE9]), "café");
}
#[test]
fn test_decode_utf8_or_latin1_pure_ascii() {
assert_eq!(decode_utf8_or_latin1(b"hello"), "hello");
}
#[test]
fn test_decode_utf8_or_latin1_empty() {
assert_eq!(decode_utf8_or_latin1(b""), "");
assert_eq!(decode_latin1(b""), "");
}
}