use encoding_rs::Encoding;
pub const DEFAULT: &Encoding = encoding_rs::UTF_8;
pub fn resolve_cpg(label: &str) -> Option<&'static Encoding> {
let trimmed = label.trim();
if trimmed.is_empty() {
return None;
}
let upper = trimmed.to_ascii_uppercase();
let mut number_part = upper.as_str();
number_part = number_part.strip_prefix("LDID/").unwrap_or(number_part);
number_part = number_part.strip_prefix("CP").unwrap_or(number_part);
if !number_part.is_empty() && number_part.bytes().all(|b| b.is_ascii_digit()) {
return number_part
.parse::<u32>()
.ok()
.and_then(label_for_codepage)
.and_then(|label| Encoding::for_label(label.as_bytes()));
}
Encoding::for_label(trimmed.as_bytes())
}
pub fn resolve_ldid(code_page: u8) -> Option<&'static Encoding> {
let label = match code_page {
0x03 | 0x57 | 0x58 | 0x59 => "windows-1252", 0x65 => "ibm866", 0x78 => "big5", 0x79 => "euc-kr", 0x7a => "gbk", 0x7b => "shift_jis", 0x7c => "windows-874", 0x7d => "windows-1255", 0x7e => "windows-1256", 0xc8 => "windows-1250", 0xc9 => "windows-1251", 0xca => "windows-1254", 0xcb => "windows-1253", 0xcc => "windows-1257", _ => return None,
};
Encoding::for_label(label.as_bytes())
}
fn label_for_codepage(cp: u32) -> Option<String> {
let label = match cp {
65001 => "utf-8",
1200 => "utf-16le",
1201 => "utf-16be",
20127 => "ascii",
874 => "windows-874",
932 => "shift_jis",
936 => "gbk",
949 => "euc-kr",
950 => "big5",
866 => "ibm866",
1250..=1258 => return Some(format!("windows-{cp}")),
28591..=28599 => return Some(format!("iso-8859-{}", cp - 28590)),
28603 => "iso-8859-13",
28605 => "iso-8859-15",
_ => return None,
};
Some(label.to_string())
}
pub fn decode(bytes: &[u8], encoding: &'static Encoding) -> String {
encoding.decode_without_bom_handling(bytes).0.into_owned()
}
#[cfg(test)]
#[allow(clippy::unwrap_used, clippy::expect_used)]
mod tests {
use super::*;
#[test]
fn cpg_textual_labels() {
assert_eq!(resolve_cpg("UTF-8").unwrap(), encoding_rs::UTF_8);
assert_eq!(resolve_cpg("utf8").unwrap(), encoding_rs::UTF_8);
assert_eq!(
resolve_cpg("ISO-8859-1").unwrap(),
encoding_rs::WINDOWS_1252
);
assert_eq!(
resolve_cpg("windows-1251").unwrap(),
encoding_rs::WINDOWS_1251
);
assert_eq!(resolve_cpg("GBK").unwrap(), encoding_rs::GBK);
}
#[test]
fn cpg_numeric_codepages() {
assert_eq!(resolve_cpg("65001").unwrap(), encoding_rs::UTF_8);
assert_eq!(resolve_cpg("1252").unwrap(), encoding_rs::WINDOWS_1252);
assert_eq!(resolve_cpg("CP1252").unwrap(), encoding_rs::WINDOWS_1252);
assert_eq!(resolve_cpg("936").unwrap(), encoding_rs::GBK);
assert_eq!(resolve_cpg("874").unwrap(), encoding_rs::WINDOWS_874);
}
#[test]
fn cpg_empty_or_unknown() {
assert!(resolve_cpg("").is_none());
assert!(resolve_cpg(" ").is_none());
assert!(resolve_cpg("not-an-encoding").is_none());
}
#[test]
fn ldid_bytes() {
assert_eq!(resolve_ldid(0x57).unwrap(), encoding_rs::WINDOWS_1252);
assert_eq!(resolve_ldid(0xc9).unwrap(), encoding_rs::WINDOWS_1251);
assert_eq!(resolve_ldid(0x7c).unwrap(), encoding_rs::WINDOWS_874);
assert!(resolve_ldid(0x00).is_none());
}
#[test]
fn decode_windows1252() {
let s = decode(&[0x43, 0x61, 0x66, 0xe9], encoding_rs::WINDOWS_1252);
assert_eq!(s, "Café");
}
#[test]
fn decode_utf8_passthrough() {
let s = decode(&[0xe0, 0xba, 0x81], encoding_rs::UTF_8);
assert_eq!(s, "ກ");
}
}