use crate::decoder::Decoder;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Encoding {
Utf8,
Utf16Le,
Utf16Be,
Latin1,
Windows1252,
Ascii,
Iso8859_2,
Iso8859_3,
Iso8859_4,
Iso8859_5,
Iso8859_6,
Iso8859_7,
Iso8859_8,
Iso8859_9,
Iso8859_10,
Iso8859_13,
Iso8859_14,
Iso8859_15,
Iso8859_16,
Windows1250,
Windows1251,
Windows1253,
Windows1254,
Windows1255,
Windows1256,
Windows1257,
Windows1258,
Koi8R,
Koi8U,
Ibm866,
Macintosh,
XMacCyrillic,
ShiftJis,
EucJp,
Iso2022Jp,
Gbk,
Gb18030,
Big5,
EucKr,
}
impl Encoding {
pub const ALL: &'static [Encoding] = &[
Encoding::Utf8,
Encoding::Utf16Le,
Encoding::Utf16Be,
Encoding::Latin1,
Encoding::Windows1252,
Encoding::Ascii,
Encoding::Iso8859_2,
Encoding::Iso8859_3,
Encoding::Iso8859_4,
Encoding::Iso8859_5,
Encoding::Iso8859_6,
Encoding::Iso8859_7,
Encoding::Iso8859_8,
Encoding::Iso8859_9,
Encoding::Iso8859_10,
Encoding::Iso8859_13,
Encoding::Iso8859_14,
Encoding::Iso8859_15,
Encoding::Iso8859_16,
Encoding::Windows1250,
Encoding::Windows1251,
Encoding::Windows1253,
Encoding::Windows1254,
Encoding::Windows1255,
Encoding::Windows1256,
Encoding::Windows1257,
Encoding::Windows1258,
Encoding::Koi8R,
Encoding::Koi8U,
Encoding::Ibm866,
Encoding::Macintosh,
Encoding::XMacCyrillic,
Encoding::ShiftJis,
Encoding::EucJp,
Encoding::Iso2022Jp,
Encoding::Gbk,
Encoding::Gb18030,
Encoding::Big5,
Encoding::EucKr,
];
#[must_use]
pub fn for_label(label: &str) -> Option<Encoding> {
let trimmed = label.trim_matches(is_whatwg_ascii_whitespace);
if trimmed.is_empty() {
return None;
}
if trimmed.bytes().all(|b| !b.is_ascii_uppercase()) {
return encoding_for_lowercase_label(trimmed);
}
let lowered = trimmed.to_ascii_lowercase();
encoding_for_lowercase_label(&lowered)
}
#[must_use]
pub const fn name(&self) -> &'static str {
match self {
Encoding::Utf8 => "UTF-8",
Encoding::Utf16Le => "UTF-16LE",
Encoding::Utf16Be => "UTF-16BE",
Encoding::Latin1 => "ISO-8859-1",
Encoding::Windows1252 => "windows-1252",
Encoding::Ascii => "US-ASCII",
Encoding::Iso8859_2 => "ISO-8859-2",
Encoding::Iso8859_3 => "ISO-8859-3",
Encoding::Iso8859_4 => "ISO-8859-4",
Encoding::Iso8859_5 => "ISO-8859-5",
Encoding::Iso8859_6 => "ISO-8859-6",
Encoding::Iso8859_7 => "ISO-8859-7",
Encoding::Iso8859_8 => "ISO-8859-8",
Encoding::Iso8859_9 => "ISO-8859-9",
Encoding::Iso8859_10 => "ISO-8859-10",
Encoding::Iso8859_13 => "ISO-8859-13",
Encoding::Iso8859_14 => "ISO-8859-14",
Encoding::Iso8859_15 => "ISO-8859-15",
Encoding::Iso8859_16 => "ISO-8859-16",
Encoding::Windows1250 => "windows-1250",
Encoding::Windows1251 => "windows-1251",
Encoding::Windows1253 => "windows-1253",
Encoding::Windows1254 => "windows-1254",
Encoding::Windows1255 => "windows-1255",
Encoding::Windows1256 => "windows-1256",
Encoding::Windows1257 => "windows-1257",
Encoding::Windows1258 => "windows-1258",
Encoding::Koi8R => "KOI8-R",
Encoding::Koi8U => "KOI8-U",
Encoding::Ibm866 => "IBM866",
Encoding::Macintosh => "macintosh",
Encoding::XMacCyrillic => "x-mac-cyrillic",
Encoding::ShiftJis => "Shift_JIS",
Encoding::EucJp => "EUC-JP",
Encoding::Iso2022Jp => "ISO-2022-JP",
Encoding::Gbk => "GBK",
Encoding::Gb18030 => "gb18030",
Encoding::Big5 => "Big5",
Encoding::EucKr => "EUC-KR",
}
}
#[must_use]
pub const fn is_ascii_compatible_single_byte(&self) -> bool {
matches!(
self,
Encoding::Utf8
| Encoding::Latin1
| Encoding::Windows1252
| Encoding::Ascii
| Encoding::Iso8859_2
| Encoding::Iso8859_3
| Encoding::Iso8859_4
| Encoding::Iso8859_5
| Encoding::Iso8859_6
| Encoding::Iso8859_7
| Encoding::Iso8859_8
| Encoding::Iso8859_9
| Encoding::Iso8859_10
| Encoding::Iso8859_13
| Encoding::Iso8859_14
| Encoding::Iso8859_15
| Encoding::Iso8859_16
| Encoding::Windows1250
| Encoding::Windows1251
| Encoding::Windows1253
| Encoding::Windows1254
| Encoding::Windows1255
| Encoding::Windows1256
| Encoding::Windows1257
| Encoding::Windows1258
| Encoding::Koi8R
| Encoding::Koi8U
| Encoding::Ibm866
| Encoding::Macintosh
| Encoding::XMacCyrillic
)
}
#[must_use]
pub(crate) const fn is_legacy_single_byte_table(&self) -> bool {
matches!(
self,
Encoding::Iso8859_2
| Encoding::Iso8859_3
| Encoding::Iso8859_4
| Encoding::Iso8859_5
| Encoding::Iso8859_6
| Encoding::Iso8859_7
| Encoding::Iso8859_8
| Encoding::Iso8859_9
| Encoding::Iso8859_10
| Encoding::Iso8859_13
| Encoding::Iso8859_14
| Encoding::Iso8859_15
| Encoding::Iso8859_16
| Encoding::Windows1250
| Encoding::Windows1251
| Encoding::Windows1253
| Encoding::Windows1254
| Encoding::Windows1255
| Encoding::Windows1256
| Encoding::Windows1257
| Encoding::Windows1258
| Encoding::Koi8R
| Encoding::Koi8U
| Encoding::Ibm866
| Encoding::Macintosh
| Encoding::XMacCyrillic
)
}
#[must_use]
pub const fn new_decoder(self) -> Decoder {
Decoder::new(self)
}
}
impl std::fmt::Display for Encoding {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(self.name())
}
}
const fn is_whatwg_ascii_whitespace(c: char) -> bool {
matches!(c, '\t' | '\n' | '\x0C' | '\r' | ' ')
}
fn encoding_for_lowercase_label(label: &str) -> Option<Encoding> {
match label {
"unicode-1-1-utf-8" | "unicode11utf8" | "unicode20utf8" | "utf-8" | "utf8"
| "x-unicode20utf8" => Some(Encoding::Utf8),
"utf-16" | "utf-16le" | "utf16" | "utf16le" | "ucs-2" | "unicode" | "iso-10646-ucs-2"
| "csunicode" => Some(Encoding::Utf16Le),
"utf-16be" | "utf16be" | "unicodefffe" => Some(Encoding::Utf16Be),
"iso-8859-1" | "iso-ir-100" | "iso8859-1" | "iso88591" | "iso_8859-1"
| "iso_8859-1:1987" | "l1" | "latin1" | "csisolatin1" | "8859-1" | "88591" => {
Some(Encoding::Latin1)
}
"windows-1252" | "cp1252" | "x-cp1252" => Some(Encoding::Windows1252),
"us-ascii" | "ascii" | "ansi_x3.4-1968" | "ansi_x3.4-1986" | "iso646-us" | "iso-ir-6"
| "us" | "ibm367" | "cp367" | "csascii" => Some(Encoding::Ascii),
"csisolatin2" | "iso-8859-2" | "iso-ir-101" | "iso8859-2" | "iso88592" | "iso_8859-2"
| "iso_8859-2:1987" | "l2" | "latin2" => Some(Encoding::Iso8859_2),
"csisolatin3" | "iso-8859-3" | "iso-ir-109" | "iso8859-3" | "iso88593" | "iso_8859-3"
| "iso_8859-3:1988" | "l3" | "latin3" => Some(Encoding::Iso8859_3),
"csisolatin4" | "iso-8859-4" | "iso-ir-110" | "iso8859-4" | "iso88594" | "iso_8859-4"
| "iso_8859-4:1988" | "l4" | "latin4" => Some(Encoding::Iso8859_4),
"csisolatincyrillic" | "cyrillic" | "iso-8859-5" | "iso-ir-144" | "iso8859-5"
| "iso88595" | "iso_8859-5" | "iso_8859-5:1988" => Some(Encoding::Iso8859_5),
"arabic" | "asmo-708" | "csiso88596e" | "csiso88596i" | "csisolatinarabic" | "ecma-114"
| "iso-8859-6" | "iso-8859-6-e" | "iso-8859-6-i" | "iso-ir-127" | "iso8859-6"
| "iso88596" | "iso_8859-6" | "iso_8859-6:1987" => Some(Encoding::Iso8859_6),
"csisolatingreek" | "ecma-118" | "elot_928" | "greek" | "greek8" | "iso-8859-7"
| "iso-ir-126" | "iso8859-7" | "iso88597" | "iso_8859-7" | "iso_8859-7:1987"
| "sun_eu_greek" => Some(Encoding::Iso8859_7),
"csiso88598e" | "csisolatinhebrew" | "hebrew" | "iso-8859-8" | "iso-8859-8-e"
| "iso-ir-138" | "iso8859-8" | "iso88598" | "iso_8859-8" | "iso_8859-8:1988" | "visual"
| "csiso88598i" | "iso-8859-8-i" | "logical" => Some(Encoding::Iso8859_8),
"csisolatin5" | "iso-8859-9" | "iso-ir-148" | "iso8859-9" | "iso88599" | "iso_8859-9"
| "iso_8859-9:1989" | "l5" | "latin5" => Some(Encoding::Iso8859_9),
"csisolatin6" | "iso-8859-10" | "iso-ir-157" | "iso8859-10" | "iso885910" | "l6"
| "latin6" => Some(Encoding::Iso8859_10),
"iso-8859-13" | "iso8859-13" | "iso885913" => Some(Encoding::Iso8859_13),
"iso-8859-14" | "iso8859-14" | "iso885914" => Some(Encoding::Iso8859_14),
"csisolatin9" | "iso-8859-15" | "iso8859-15" | "iso885915" | "iso_8859-15" | "l9" => {
Some(Encoding::Iso8859_15)
}
"iso-8859-16" => Some(Encoding::Iso8859_16),
"cp1250" | "windows-1250" | "x-cp1250" => Some(Encoding::Windows1250),
"cp1251" | "windows-1251" | "x-cp1251" => Some(Encoding::Windows1251),
"cp1253" | "windows-1253" | "x-cp1253" => Some(Encoding::Windows1253),
"cp1254" | "windows-1254" | "x-cp1254" => Some(Encoding::Windows1254),
"cp1255" | "windows-1255" | "x-cp1255" => Some(Encoding::Windows1255),
"cp1256" | "windows-1256" | "x-cp1256" => Some(Encoding::Windows1256),
"cp1257" | "windows-1257" | "x-cp1257" => Some(Encoding::Windows1257),
"cp1258" | "windows-1258" | "x-cp1258" => Some(Encoding::Windows1258),
"cskoi8r" | "koi" | "koi8" | "koi8-r" | "koi8_r" => Some(Encoding::Koi8R),
"koi8-ru" | "koi8-u" => Some(Encoding::Koi8U),
"866" | "cp866" | "csibm866" | "ibm866" => Some(Encoding::Ibm866),
"csmacintosh" | "mac" | "macintosh" | "x-mac-roman" => Some(Encoding::Macintosh),
"x-mac-cyrillic" | "x-mac-ukrainian" => Some(Encoding::XMacCyrillic),
"cseucpkdfmtjapanese" | "euc-jp" | "x-euc-jp" => Some(Encoding::EucJp),
"csiso2022jp" | "iso-2022-jp" => Some(Encoding::Iso2022Jp),
"csshiftjis" | "ms932" | "ms_kanji" | "shift-jis" | "shift_jis" | "sjis"
| "windows-31j" | "x-sjis" => Some(Encoding::ShiftJis),
"chinese" | "csgb2312" | "csiso58gb231280" | "gb2312" | "gb_2312" | "gb_2312-80"
| "gbk" | "iso-ir-58" | "x-gbk" => Some(Encoding::Gbk),
"gb18030" => Some(Encoding::Gb18030),
"big5" | "big5-hkscs" | "cn-big5" | "csbig5" | "x-x-big5" => Some(Encoding::Big5),
"cseuckr" | "csksc56011987" | "euc-kr" | "iso-ir-149" | "korean" | "ks_c_5601-1987"
| "ks_c_5601-1989" | "ksc5601" | "ksc_5601" | "windows-949" => Some(Encoding::EucKr),
_ => None,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn for_label_is_case_insensitive() {
assert_eq!(Encoding::for_label("UTF-8"), Some(Encoding::Utf8));
assert_eq!(Encoding::for_label("Utf-8"), Some(Encoding::Utf8));
assert_eq!(Encoding::for_label("uTf8"), Some(Encoding::Utf8));
assert_eq!(
Encoding::for_label("WINDOWS-1252"),
Some(Encoding::Windows1252)
);
assert_eq!(Encoding::for_label("Iso-8859-1"), Some(Encoding::Latin1));
assert_eq!(Encoding::for_label("US-ASCII"), Some(Encoding::Ascii));
assert_eq!(Encoding::for_label("Shift_JIS"), Some(Encoding::ShiftJis));
assert_eq!(Encoding::for_label("SHIFT_JIS"), Some(Encoding::ShiftJis));
}
#[test]
fn for_label_trims_whatwg_whitespace() {
assert_eq!(Encoding::for_label(" utf-8\t\n"), Some(Encoding::Utf8));
assert_eq!(Encoding::for_label("\r\nlatin1 "), Some(Encoding::Latin1));
assert_eq!(
Encoding::for_label(" shift_jis \n"),
Some(Encoding::ShiftJis)
);
}
#[test]
fn for_label_unknown_returns_none() {
assert_eq!(Encoding::for_label("utf-32"), None);
assert_eq!(Encoding::for_label(""), None);
assert_eq!(Encoding::for_label(" "), None);
assert_eq!(Encoding::for_label("windows-874"), None);
assert_eq!(Encoding::for_label("iso-8859-11"), None);
}
#[test]
fn for_label_cjk_multi_byte_labels() {
assert_eq!(Encoding::for_label("gbk"), Some(Encoding::Gbk));
assert_eq!(Encoding::for_label("GBK"), Some(Encoding::Gbk));
assert_eq!(Encoding::for_label("gb2312"), Some(Encoding::Gbk));
assert_eq!(Encoding::for_label("x-gbk"), Some(Encoding::Gbk));
assert_eq!(Encoding::for_label("chinese"), Some(Encoding::Gbk));
assert_eq!(Encoding::for_label("gb18030"), Some(Encoding::Gb18030));
assert_eq!(Encoding::for_label("GB18030"), Some(Encoding::Gb18030));
assert_eq!(Encoding::for_label("big5"), Some(Encoding::Big5));
assert_eq!(Encoding::for_label("Big5"), Some(Encoding::Big5));
assert_eq!(Encoding::for_label("big5-hkscs"), Some(Encoding::Big5));
assert_eq!(Encoding::for_label("cn-big5"), Some(Encoding::Big5));
assert_eq!(Encoding::for_label("euc-kr"), Some(Encoding::EucKr));
assert_eq!(Encoding::for_label("EUC-KR"), Some(Encoding::EucKr));
assert_eq!(Encoding::for_label("windows-949"), Some(Encoding::EucKr));
assert_eq!(Encoding::for_label("ks_c_5601-1987"), Some(Encoding::EucKr));
assert_eq!(Encoding::for_label("korean"), Some(Encoding::EucKr));
}
#[test]
fn for_label_utf16_variants() {
assert_eq!(Encoding::for_label("utf-16"), Some(Encoding::Utf16Le));
assert_eq!(Encoding::for_label("utf-16le"), Some(Encoding::Utf16Le));
assert_eq!(Encoding::for_label("utf-16be"), Some(Encoding::Utf16Be));
assert_eq!(Encoding::for_label("unicodeFFFE"), Some(Encoding::Utf16Be));
}
#[test]
fn for_label_iso_8859_9_is_kept_distinct_from_windows_1254() {
assert_eq!(Encoding::for_label("iso-8859-9"), Some(Encoding::Iso8859_9));
assert_eq!(Encoding::for_label("latin5"), Some(Encoding::Iso8859_9));
assert_eq!(
Encoding::for_label("windows-1254"),
Some(Encoding::Windows1254)
);
assert_eq!(Encoding::for_label("cp1254"), Some(Encoding::Windows1254));
assert_ne!(Encoding::Iso8859_9, Encoding::Windows1254);
}
#[test]
fn for_label_japanese_multi_byte_labels() {
assert_eq!(Encoding::for_label("shift_jis"), Some(Encoding::ShiftJis));
assert_eq!(Encoding::for_label("sjis"), Some(Encoding::ShiftJis));
assert_eq!(Encoding::for_label("ms932"), Some(Encoding::ShiftJis));
assert_eq!(Encoding::for_label("windows-31j"), Some(Encoding::ShiftJis));
assert_eq!(Encoding::for_label("euc-jp"), Some(Encoding::EucJp));
assert_eq!(Encoding::for_label("x-euc-jp"), Some(Encoding::EucJp));
assert_eq!(
Encoding::for_label("iso-2022-jp"),
Some(Encoding::Iso2022Jp)
);
assert_eq!(
Encoding::for_label("csiso2022jp"),
Some(Encoding::Iso2022Jp)
);
}
#[test]
fn name_round_trips_through_for_label_for_every_supported_encoding() {
for &enc in Encoding::ALL {
assert_eq!(
Encoding::for_label(enc.name()),
Some(enc),
"name = {}",
enc.name()
);
assert_eq!(
Encoding::for_label(&enc.name().to_lowercase()),
Some(enc),
"lowercased name = {}",
enc.name()
);
assert_eq!(
Encoding::for_label(&enc.name().to_uppercase()),
Some(enc),
"uppercased name = {}",
enc.name()
);
}
}
#[test]
fn all_is_free_of_duplicates() {
for (i, &a) in Encoding::ALL.iter().enumerate() {
for &b in &Encoding::ALL[i + 1..] {
assert_ne!(a, b, "Encoding::ALL contains a duplicate: {a}");
}
}
}
#[test]
fn display_matches_name() {
assert_eq!(Encoding::Utf8.to_string(), "UTF-8");
assert_eq!(Encoding::Windows1252.to_string(), "windows-1252");
assert_eq!(Encoding::ShiftJis.to_string(), "Shift_JIS");
}
#[test]
fn is_ascii_compatible_single_byte_matches_expectation() {
assert!(Encoding::Utf8.is_ascii_compatible_single_byte());
assert!(Encoding::Latin1.is_ascii_compatible_single_byte());
assert!(Encoding::Windows1252.is_ascii_compatible_single_byte());
assert!(Encoding::Ascii.is_ascii_compatible_single_byte());
assert!(Encoding::Iso8859_2.is_ascii_compatible_single_byte());
assert!(Encoding::Windows1251.is_ascii_compatible_single_byte());
assert!(Encoding::Koi8R.is_ascii_compatible_single_byte());
assert!(!Encoding::Utf16Le.is_ascii_compatible_single_byte());
assert!(!Encoding::Utf16Be.is_ascii_compatible_single_byte());
assert!(!Encoding::ShiftJis.is_ascii_compatible_single_byte());
assert!(!Encoding::EucJp.is_ascii_compatible_single_byte());
assert!(!Encoding::Iso2022Jp.is_ascii_compatible_single_byte());
assert!(!Encoding::Gbk.is_ascii_compatible_single_byte());
assert!(!Encoding::Gb18030.is_ascii_compatible_single_byte());
assert!(!Encoding::Big5.is_ascii_compatible_single_byte());
assert!(!Encoding::EucKr.is_ascii_compatible_single_byte());
}
#[test]
fn is_legacy_single_byte_table_matches_expectation() {
assert!(Encoding::Iso8859_2.is_legacy_single_byte_table());
assert!(Encoding::Windows1250.is_legacy_single_byte_table());
assert!(Encoding::Koi8R.is_legacy_single_byte_table());
assert!(Encoding::Macintosh.is_legacy_single_byte_table());
assert!(!Encoding::Latin1.is_legacy_single_byte_table());
assert!(!Encoding::Windows1252.is_legacy_single_byte_table());
assert!(!Encoding::Ascii.is_legacy_single_byte_table());
assert!(!Encoding::Utf8.is_legacy_single_byte_table());
assert!(!Encoding::ShiftJis.is_legacy_single_byte_table());
assert!(!Encoding::EucJp.is_legacy_single_byte_table());
assert!(!Encoding::Iso2022Jp.is_legacy_single_byte_table());
assert!(!Encoding::Gbk.is_legacy_single_byte_table());
assert!(!Encoding::Gb18030.is_legacy_single_byte_table());
assert!(!Encoding::Big5.is_legacy_single_byte_table());
assert!(!Encoding::EucKr.is_legacy_single_byte_table());
}
#[test]
fn all_contains_every_variant_exactly_once_via_new_decoder_smoke_test() {
for &encoding in Encoding::ALL {
let _ = encoding.new_decoder();
}
}
}