oxixml_encoding/label.rs
1//! The [`Encoding`] enum and IANA/WHATWG label resolution.
2
3use crate::decoder::Decoder;
4
5/// A character encoding recognized by this crate.
6///
7/// The original core set is exactly what an XML processor needs to deal with in practice:
8/// `UTF-8` and `UTF-16` (in both byte orders) are the two encodings XML 1.0 requires every
9/// conformant processor to support; [`Encoding::Latin1`] and [`Encoding::Windows1252`] cover the
10/// overwhelming majority of legacy (`encoding="..."`) documents seen in the wild, and
11/// [`Encoding::Ascii`] is useful as a strict validation target.
12///
13/// Beyond that core, this crate also implements the rest of the
14/// [WHATWG Encoding Standard](https://encoding.spec.whatwg.org/)'s legacy single-byte encodings
15/// (the full `ISO-8859-*`, `windows-125x`, `KOI8-*`, `IBM866`, `macintosh` and `x-mac-cyrillic`
16/// families) and its three legacy Japanese multi-byte encodings (Shift_JIS, EUC-JP, and
17/// ISO-2022-JP), prioritized for COOLJAPAN. See [`Encoding::ALL`] for the complete list.
18///
19/// Unlike the WHATWG Encoding Standard, which folds `ISO-8859-1` into `windows-1252` for web
20/// compatibility, this crate keeps them distinct: XML tooling (libxml2, Xerces, .NET, the JDK,
21/// ...) has always treated `ISO-8859-1` as the "pure" Latin-1 repertoire (bytes `0x00..=0xFF` map
22/// to `U+0000..=U+00FF` unchanged) and `windows-1252` as the Microsoft superset that additionally
23/// assigns the `0x80..=0x9F` range to punctuation such as smart quotes and the euro sign. The
24/// same reasoning applies to [`Encoding::Iso8859_9`] versus [`Encoding::Windows1254`]: the WHATWG
25/// standard folds the `"iso-8859-9"`/`"latin5"` labels into windows-1254 too, but this crate keeps
26/// pure ISO-8859-9 (ISO/IEC 8859-9:1999, a.k.a. Latin-5) as its own encoding for the same reason.
27#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
28pub enum Encoding {
29 /// UTF-8: the ASCII-compatible variable-width encoding of Unicode, and the default assumed
30 /// by XML 1.0 in the absence of a byte-order mark or `encoding` declaration.
31 Utf8,
32 /// UTF-16, little-endian byte order.
33 Utf16Le,
34 /// UTF-16, big-endian byte order.
35 Utf16Be,
36 /// ISO-8859-1 (Latin-1): each byte `b` decodes to the scalar value `U+00xx` with `xx == b`.
37 Latin1,
38 /// windows-1252 (a.k.a. `cp1252`): ISO-8859-1 with the C1 control range `0x80..=0x9F`
39 /// reassigned to printable characters (the five positions Microsoft left unassigned keep
40 /// their Latin-1 fallback, per the [WHATWG index table][idx]).
41 ///
42 /// [idx]: https://encoding.spec.whatwg.org/index-windows-1252.txt
43 Windows1252,
44 /// US-ASCII: the 7-bit repertoire `U+0000..=U+007F`. Bytes `0x80..=0xFF` are not valid.
45 Ascii,
46
47 // --- ISO-8859 family (beyond -1, above) -------------------------------------------------
48 /// ISO-8859-2 (Latin-2): Central/Eastern European.
49 Iso8859_2,
50 /// ISO-8859-3 (Latin-3): South European (Maltese, Esperanto).
51 Iso8859_3,
52 /// ISO-8859-4 (Latin-4): North European (Baltic languages, pre-ISO-8859-13).
53 Iso8859_4,
54 /// ISO-8859-5: Cyrillic.
55 Iso8859_5,
56 /// ISO-8859-6: Arabic.
57 Iso8859_6,
58 /// ISO-8859-7: Greek.
59 Iso8859_7,
60 /// ISO-8859-8: Hebrew (visual order; this crate makes no distinction from the "logical
61 /// order" `ISO-8859-8-I` variant, since directionality is outside this crate's scope and
62 /// both share the same byte/code-point repertoire).
63 Iso8859_8,
64 /// ISO-8859-9 (Latin-5): identical to ISO-8859-1 except six positions replaced with the
65 /// Turkish letters `Ğ`/`ğ`, `İ`/`ı`, `Ş`/`ş`. See the type-level docs for why this is kept
66 /// distinct from [`Encoding::Windows1254`].
67 Iso8859_9,
68 /// ISO-8859-10 (Latin-6): Nordic (an alternative to -4 with full Icelandic coverage).
69 Iso8859_10,
70 /// ISO-8859-13 (Latin-7): Baltic.
71 Iso8859_13,
72 /// ISO-8859-14 (Latin-8): Celtic.
73 Iso8859_14,
74 /// ISO-8859-15 (Latin-9): Latin-1 with the euro sign and a handful of French/Finnish letters.
75 Iso8859_15,
76 /// ISO-8859-16 (Latin-10): South-Eastern European.
77 Iso8859_16,
78
79 // --- windows-125x family (beyond -1252, above) ------------------------------------------
80 /// windows-1250: Central/Eastern European.
81 Windows1250,
82 /// windows-1251: Cyrillic.
83 Windows1251,
84 /// windows-1253: Greek.
85 Windows1253,
86 /// windows-1254 (Turkish): the Microsoft superset of [`Encoding::Iso8859_9`].
87 Windows1254,
88 /// windows-1255: Hebrew.
89 Windows1255,
90 /// windows-1256: Arabic.
91 Windows1256,
92 /// windows-1257: Baltic.
93 Windows1257,
94 /// windows-1258: Vietnamese.
95 Windows1258,
96
97 // --- Other single-byte encodings ---------------------------------------------------------
98 /// KOI8-R: Russian Cyrillic.
99 Koi8R,
100 /// KOI8-U: Ukrainian Cyrillic (a KOI8-R variant with a handful of Ukrainian letters).
101 Koi8U,
102 /// IBM866 (code page 866): Cyrillic, MS-DOS.
103 Ibm866,
104 /// macintosh (a.k.a. "Mac OS Roman"): the classic Mac OS Western European encoding.
105 Macintosh,
106 /// x-mac-cyrillic (a.k.a. "Mac OS Cyrillic"): the classic Mac OS Cyrillic encoding.
107 XMacCyrillic,
108
109 // --- Legacy multi-byte Japanese encodings ------------------------------------------------
110 /// Shift_JIS (a.k.a. `MS932`/`Windows-31J`): the JIS X 0201 + JIS X 0208 encoding most
111 /// commonly used on Japanese Windows and in Japanese web content.
112 ShiftJis,
113 /// EUC-JP: the Unix-family Japanese encoding covering JIS X 0201, JIS X 0208, and (via its
114 /// `0x8F`-prefixed `SS3` sequences) JIS X 0212.
115 EucJp,
116 /// ISO-2022-JP: the 7-bit, escape-sequence-switched Japanese encoding historically used for
117 /// Japanese email (`Content-Transfer-Encoding: 7bit`-safe).
118 Iso2022Jp,
119}
120
121impl Encoding {
122 /// Every encoding this crate supports, in declaration order. Useful for exhaustively testing
123 /// or listing supported encodings without having to enumerate the (growing) variant list by
124 /// hand.
125 ///
126 /// # Examples
127 ///
128 /// ```
129 /// use oxixml_encoding::Encoding;
130 ///
131 /// assert!(Encoding::ALL.contains(&Encoding::Utf8));
132 /// assert!(Encoding::ALL.contains(&Encoding::ShiftJis));
133 /// // Every entry's canonical name resolves back to itself.
134 /// for &encoding in Encoding::ALL {
135 /// assert_eq!(Encoding::for_label(encoding.name()), Some(encoding));
136 /// }
137 /// ```
138 pub const ALL: &'static [Encoding] = &[
139 Encoding::Utf8,
140 Encoding::Utf16Le,
141 Encoding::Utf16Be,
142 Encoding::Latin1,
143 Encoding::Windows1252,
144 Encoding::Ascii,
145 Encoding::Iso8859_2,
146 Encoding::Iso8859_3,
147 Encoding::Iso8859_4,
148 Encoding::Iso8859_5,
149 Encoding::Iso8859_6,
150 Encoding::Iso8859_7,
151 Encoding::Iso8859_8,
152 Encoding::Iso8859_9,
153 Encoding::Iso8859_10,
154 Encoding::Iso8859_13,
155 Encoding::Iso8859_14,
156 Encoding::Iso8859_15,
157 Encoding::Iso8859_16,
158 Encoding::Windows1250,
159 Encoding::Windows1251,
160 Encoding::Windows1253,
161 Encoding::Windows1254,
162 Encoding::Windows1255,
163 Encoding::Windows1256,
164 Encoding::Windows1257,
165 Encoding::Windows1258,
166 Encoding::Koi8R,
167 Encoding::Koi8U,
168 Encoding::Ibm866,
169 Encoding::Macintosh,
170 Encoding::XMacCyrillic,
171 Encoding::ShiftJis,
172 Encoding::EucJp,
173 Encoding::Iso2022Jp,
174 ];
175
176 /// Resolve an IANA/WHATWG charset label to an [`Encoding`], case-insensitively and after
177 /// trimming ASCII whitespace, mirroring the label tables in the
178 /// [WHATWG Encoding Standard](https://encoding.spec.whatwg.org/#names-and-labels) — with the
179 /// [`Encoding::Iso8859_9`]-versus-[`Encoding::Windows1254`] exception documented on
180 /// [`Encoding`] itself.
181 ///
182 /// Returns [`None`] for labels this crate does not resolve to a supported encoding (for
183 /// example `"utf-32"`, `"gb18030"` or `"ebcdic-cp-us"`).
184 ///
185 /// # Examples
186 ///
187 /// ```
188 /// use oxixml_encoding::Encoding;
189 ///
190 /// assert_eq!(Encoding::for_label("UTF-8"), Some(Encoding::Utf8));
191 /// assert_eq!(Encoding::for_label(" utf8 \t"), Some(Encoding::Utf8));
192 /// assert_eq!(Encoding::for_label("Windows-1252"), Some(Encoding::Windows1252));
193 /// assert_eq!(Encoding::for_label("iso-8859-1"), Some(Encoding::Latin1));
194 /// assert_eq!(Encoding::for_label("Shift_JIS"), Some(Encoding::ShiftJis));
195 /// assert_eq!(Encoding::for_label("EUC-JP"), Some(Encoding::EucJp));
196 /// assert_eq!(Encoding::for_label("gb18030"), None);
197 /// ```
198 #[must_use]
199 pub fn for_label(label: &str) -> Option<Encoding> {
200 let trimmed = label.trim_matches(is_whatwg_ascii_whitespace);
201 if trimmed.is_empty() {
202 return None;
203 }
204 // WHATWG label matching is defined over the ASCII-lowercased label; avoid allocating in
205 // the common case where the caller already passed a canonical lowercase label.
206 if trimmed.bytes().all(|b| !b.is_ascii_uppercase()) {
207 return encoding_for_lowercase_label(trimmed);
208 }
209 let lowered = trimmed.to_ascii_lowercase();
210 encoding_for_lowercase_label(&lowered)
211 }
212
213 /// The canonical name of this encoding, as it should appear in generated `encoding="..."`
214 /// declarations or diagnostics.
215 ///
216 /// # Examples
217 ///
218 /// ```
219 /// use oxixml_encoding::Encoding;
220 ///
221 /// assert_eq!(Encoding::Utf8.name(), "UTF-8");
222 /// assert_eq!(Encoding::Windows1252.name(), "windows-1252");
223 /// assert_eq!(Encoding::ShiftJis.name(), "Shift_JIS");
224 /// ```
225 #[must_use]
226 pub const fn name(&self) -> &'static str {
227 match self {
228 Encoding::Utf8 => "UTF-8",
229 Encoding::Utf16Le => "UTF-16LE",
230 Encoding::Utf16Be => "UTF-16BE",
231 Encoding::Latin1 => "ISO-8859-1",
232 Encoding::Windows1252 => "windows-1252",
233 Encoding::Ascii => "US-ASCII",
234 Encoding::Iso8859_2 => "ISO-8859-2",
235 Encoding::Iso8859_3 => "ISO-8859-3",
236 Encoding::Iso8859_4 => "ISO-8859-4",
237 Encoding::Iso8859_5 => "ISO-8859-5",
238 Encoding::Iso8859_6 => "ISO-8859-6",
239 Encoding::Iso8859_7 => "ISO-8859-7",
240 Encoding::Iso8859_8 => "ISO-8859-8",
241 Encoding::Iso8859_9 => "ISO-8859-9",
242 Encoding::Iso8859_10 => "ISO-8859-10",
243 Encoding::Iso8859_13 => "ISO-8859-13",
244 Encoding::Iso8859_14 => "ISO-8859-14",
245 Encoding::Iso8859_15 => "ISO-8859-15",
246 Encoding::Iso8859_16 => "ISO-8859-16",
247 Encoding::Windows1250 => "windows-1250",
248 Encoding::Windows1251 => "windows-1251",
249 Encoding::Windows1253 => "windows-1253",
250 Encoding::Windows1254 => "windows-1254",
251 Encoding::Windows1255 => "windows-1255",
252 Encoding::Windows1256 => "windows-1256",
253 Encoding::Windows1257 => "windows-1257",
254 Encoding::Windows1258 => "windows-1258",
255 Encoding::Koi8R => "KOI8-R",
256 Encoding::Koi8U => "KOI8-U",
257 Encoding::Ibm866 => "IBM866",
258 Encoding::Macintosh => "macintosh",
259 Encoding::XMacCyrillic => "x-mac-cyrillic",
260 Encoding::ShiftJis => "Shift_JIS",
261 Encoding::EucJp => "EUC-JP",
262 Encoding::Iso2022Jp => "ISO-2022-JP",
263 }
264 }
265
266 /// Whether this encoding is ASCII-compatible in the sense relevant to XML prologue
267 /// scanning: every byte in `0x00..=0x7F` decodes to the identically-valued scalar, so an
268 /// ASCII-only substring (such as an `<?xml ... ?>` declaration) can be located by scanning
269 /// raw bytes without first performing a full decode.
270 ///
271 /// This holds for every single-byte encoding this crate supports (all of them agree with
272 /// ASCII below `0x80`, by construction — see [`Encoding::ALL`]), not only the original four.
273 /// It does *not* hold for the three multi-byte Japanese encodings: even though each of them
274 /// also encodes `0x00..=0x7F` as ASCII in their respective *initial* shift state (which is
275 /// enough to make [`crate::sniff_xml_encoding`]'s prologue scan work, since an `<?xml ...?>`
276 /// declaration is required to be the first thing in the entity and is itself pure ASCII), a
277 /// non-initial byte elsewhere in the document is not reliably ASCII-interpretable without
278 /// tracking shift state, so this method intentionally answers `false` for them.
279 #[must_use]
280 pub const fn is_ascii_compatible_single_byte(&self) -> bool {
281 matches!(
282 self,
283 Encoding::Utf8
284 | Encoding::Latin1
285 | Encoding::Windows1252
286 | Encoding::Ascii
287 | Encoding::Iso8859_2
288 | Encoding::Iso8859_3
289 | Encoding::Iso8859_4
290 | Encoding::Iso8859_5
291 | Encoding::Iso8859_6
292 | Encoding::Iso8859_7
293 | Encoding::Iso8859_8
294 | Encoding::Iso8859_9
295 | Encoding::Iso8859_10
296 | Encoding::Iso8859_13
297 | Encoding::Iso8859_14
298 | Encoding::Iso8859_15
299 | Encoding::Iso8859_16
300 | Encoding::Windows1250
301 | Encoding::Windows1251
302 | Encoding::Windows1253
303 | Encoding::Windows1254
304 | Encoding::Windows1255
305 | Encoding::Windows1256
306 | Encoding::Windows1257
307 | Encoding::Windows1258
308 | Encoding::Koi8R
309 | Encoding::Koi8U
310 | Encoding::Ibm866
311 | Encoding::Macintosh
312 | Encoding::XMacCyrillic
313 )
314 }
315
316 /// Whether this encoding is one of the 26 legacy single-byte encodings backed by a generated
317 /// 128-entry upper-half table in `crate::tables::legacy_single_byte` (every [`Encoding`]
318 /// variant except UTF-8/UTF-16LE/UTF-16BE, the original [`Encoding::Latin1`]/
319 /// [`Encoding::Windows1252`]/[`Encoding::Ascii`] trio, and the three multi-byte Japanese
320 /// encodings).
321 #[must_use]
322 pub(crate) const fn is_legacy_single_byte_table(&self) -> bool {
323 matches!(
324 self,
325 Encoding::Iso8859_2
326 | Encoding::Iso8859_3
327 | Encoding::Iso8859_4
328 | Encoding::Iso8859_5
329 | Encoding::Iso8859_6
330 | Encoding::Iso8859_7
331 | Encoding::Iso8859_8
332 | Encoding::Iso8859_9
333 | Encoding::Iso8859_10
334 | Encoding::Iso8859_13
335 | Encoding::Iso8859_14
336 | Encoding::Iso8859_15
337 | Encoding::Iso8859_16
338 | Encoding::Windows1250
339 | Encoding::Windows1251
340 | Encoding::Windows1253
341 | Encoding::Windows1254
342 | Encoding::Windows1255
343 | Encoding::Windows1256
344 | Encoding::Windows1257
345 | Encoding::Windows1258
346 | Encoding::Koi8R
347 | Encoding::Koi8U
348 | Encoding::Ibm866
349 | Encoding::Macintosh
350 | Encoding::XMacCyrillic
351 )
352 }
353
354 /// Create a new incremental [`Decoder`] for this encoding.
355 ///
356 /// # Examples
357 ///
358 /// ```
359 /// use oxixml_encoding::Encoding;
360 ///
361 /// let mut decoder = Encoding::Utf8.new_decoder();
362 /// let mut out = String::new();
363 /// decoder.decode_to_string(b"hello", &mut out, true).expect("valid utf-8");
364 /// assert_eq!(out, "hello");
365 /// ```
366 #[must_use]
367 pub const fn new_decoder(self) -> Decoder {
368 Decoder::new(self)
369 }
370}
371
372impl std::fmt::Display for Encoding {
373 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
374 f.write_str(self.name())
375 }
376}
377
378/// WHATWG's definition of "ASCII whitespace" used when trimming labels: tab, line feed, form
379/// feed, carriage return and space.
380const fn is_whatwg_ascii_whitespace(c: char) -> bool {
381 matches!(c, '\t' | '\n' | '\x0C' | '\r' | ' ')
382}
383
384/// Resolve an already-trimmed, already-lowercased label. Kept as a `match` over `&str` (rather
385/// than a hash map) since the label set is small, fixed at compile time, and this keeps the
386/// crate free of any external dependency.
387///
388/// Label lists are transcribed from the WHATWG Encoding Standard
389/// ([`§4.2 "get an encoding"`](https://encoding.spec.whatwg.org/#names-and-labels), data dated
390/// 2024-09-18), with one deliberate deviation: the `"iso-8859-9"`/`"latin5"`-family labels
391/// resolve to [`Encoding::Iso8859_9`] here rather than [`Encoding::Windows1254`] (see the
392/// type-level docs on [`Encoding`]).
393fn encoding_for_lowercase_label(label: &str) -> Option<Encoding> {
394 match label {
395 // --- UTF-8 --------------------------------------------------------------------------
396 "unicode-1-1-utf-8" | "unicode11utf8" | "unicode20utf8" | "utf-8" | "utf8"
397 | "x-unicode20utf8" => Some(Encoding::Utf8),
398
399 // --- UTF-16, unspecified/little-endian ----------------------------------------------
400 // Per the WHATWG Encoding Standard, a bare "utf-16" label (no endianness suffix) is
401 // resolved as UTF-16LE for compatibility with the (much more common) little-endian
402 // content that historically omitted the suffix.
403 "utf-16" | "utf-16le" | "utf16" | "utf16le" | "ucs-2" | "unicode" | "iso-10646-ucs-2"
404 | "csunicode" => Some(Encoding::Utf16Le),
405
406 // --- UTF-16, big-endian ----------------------------------------------------------------
407 "utf-16be" | "utf16be" | "unicodefffe" => Some(Encoding::Utf16Be),
408
409 // --- ISO-8859-1 / Latin-1 --------------------------------------------------------------
410 "iso-8859-1" | "iso-ir-100" | "iso8859-1" | "iso88591" | "iso_8859-1"
411 | "iso_8859-1:1987" | "l1" | "latin1" | "csisolatin1" | "8859-1" | "88591" => {
412 Some(Encoding::Latin1)
413 }
414
415 // --- windows-1252 ------------------------------------------------------------------------
416 "windows-1252" | "cp1252" | "x-cp1252" => Some(Encoding::Windows1252),
417
418 // --- US-ASCII --------------------------------------------------------------------------
419 "us-ascii" | "ascii" | "ansi_x3.4-1968" | "ansi_x3.4-1986" | "iso646-us" | "iso-ir-6"
420 | "us" | "ibm367" | "cp367" | "csascii" => Some(Encoding::Ascii),
421
422 // --- ISO-8859-2 --------------------------------------------------------------------------
423 "csisolatin2" | "iso-8859-2" | "iso-ir-101" | "iso8859-2" | "iso88592" | "iso_8859-2"
424 | "iso_8859-2:1987" | "l2" | "latin2" => Some(Encoding::Iso8859_2),
425
426 // --- ISO-8859-3 --------------------------------------------------------------------------
427 "csisolatin3" | "iso-8859-3" | "iso-ir-109" | "iso8859-3" | "iso88593" | "iso_8859-3"
428 | "iso_8859-3:1988" | "l3" | "latin3" => Some(Encoding::Iso8859_3),
429
430 // --- ISO-8859-4 --------------------------------------------------------------------------
431 "csisolatin4" | "iso-8859-4" | "iso-ir-110" | "iso8859-4" | "iso88594" | "iso_8859-4"
432 | "iso_8859-4:1988" | "l4" | "latin4" => Some(Encoding::Iso8859_4),
433
434 // --- ISO-8859-5 --------------------------------------------------------------------------
435 "csisolatincyrillic" | "cyrillic" | "iso-8859-5" | "iso-ir-144" | "iso8859-5"
436 | "iso88595" | "iso_8859-5" | "iso_8859-5:1988" => Some(Encoding::Iso8859_5),
437
438 // --- ISO-8859-6 --------------------------------------------------------------------------
439 "arabic" | "asmo-708" | "csiso88596e" | "csiso88596i" | "csisolatinarabic" | "ecma-114"
440 | "iso-8859-6" | "iso-8859-6-e" | "iso-8859-6-i" | "iso-ir-127" | "iso8859-6"
441 | "iso88596" | "iso_8859-6" | "iso_8859-6:1987" => Some(Encoding::Iso8859_6),
442
443 // --- ISO-8859-7 --------------------------------------------------------------------------
444 "csisolatingreek" | "ecma-118" | "elot_928" | "greek" | "greek8" | "iso-8859-7"
445 | "iso-ir-126" | "iso8859-7" | "iso88597" | "iso_8859-7" | "iso_8859-7:1987"
446 | "sun_eu_greek" => Some(Encoding::Iso8859_7),
447
448 // --- ISO-8859-8 (and ISO-8859-8-I: same repertoire, direction only, out of scope) -------
449 "csiso88598e" | "csisolatinhebrew" | "hebrew" | "iso-8859-8" | "iso-8859-8-e"
450 | "iso-ir-138" | "iso8859-8" | "iso88598" | "iso_8859-8" | "iso_8859-8:1988" | "visual"
451 | "csiso88598i" | "iso-8859-8-i" | "logical" => Some(Encoding::Iso8859_8),
452
453 // --- ISO-8859-9 (kept distinct from windows-1254; see `Encoding` docs) -----------------
454 "csisolatin5" | "iso-8859-9" | "iso-ir-148" | "iso8859-9" | "iso88599" | "iso_8859-9"
455 | "iso_8859-9:1989" | "l5" | "latin5" => Some(Encoding::Iso8859_9),
456
457 // --- ISO-8859-10 -------------------------------------------------------------------------
458 "csisolatin6" | "iso-8859-10" | "iso-ir-157" | "iso8859-10" | "iso885910" | "l6"
459 | "latin6" => Some(Encoding::Iso8859_10),
460
461 // --- ISO-8859-13 -------------------------------------------------------------------------
462 "iso-8859-13" | "iso8859-13" | "iso885913" => Some(Encoding::Iso8859_13),
463
464 // --- ISO-8859-14 -------------------------------------------------------------------------
465 "iso-8859-14" | "iso8859-14" | "iso885914" => Some(Encoding::Iso8859_14),
466
467 // --- ISO-8859-15 -------------------------------------------------------------------------
468 "csisolatin9" | "iso-8859-15" | "iso8859-15" | "iso885915" | "iso_8859-15" | "l9" => {
469 Some(Encoding::Iso8859_15)
470 }
471
472 // --- ISO-8859-16 -------------------------------------------------------------------------
473 "iso-8859-16" => Some(Encoding::Iso8859_16),
474
475 // --- windows-1250 ------------------------------------------------------------------------
476 "cp1250" | "windows-1250" | "x-cp1250" => Some(Encoding::Windows1250),
477
478 // --- windows-1251 ------------------------------------------------------------------------
479 "cp1251" | "windows-1251" | "x-cp1251" => Some(Encoding::Windows1251),
480
481 // --- windows-1253 ------------------------------------------------------------------------
482 "cp1253" | "windows-1253" | "x-cp1253" => Some(Encoding::Windows1253),
483
484 // --- windows-1254 ------------------------------------------------------------------------
485 "cp1254" | "windows-1254" | "x-cp1254" => Some(Encoding::Windows1254),
486
487 // --- windows-1255 ------------------------------------------------------------------------
488 "cp1255" | "windows-1255" | "x-cp1255" => Some(Encoding::Windows1255),
489
490 // --- windows-1256 ------------------------------------------------------------------------
491 "cp1256" | "windows-1256" | "x-cp1256" => Some(Encoding::Windows1256),
492
493 // --- windows-1257 ------------------------------------------------------------------------
494 "cp1257" | "windows-1257" | "x-cp1257" => Some(Encoding::Windows1257),
495
496 // --- windows-1258 ------------------------------------------------------------------------
497 "cp1258" | "windows-1258" | "x-cp1258" => Some(Encoding::Windows1258),
498
499 // --- KOI8-R ------------------------------------------------------------------------------
500 "cskoi8r" | "koi" | "koi8" | "koi8-r" | "koi8_r" => Some(Encoding::Koi8R),
501
502 // --- KOI8-U ------------------------------------------------------------------------------
503 "koi8-ru" | "koi8-u" => Some(Encoding::Koi8U),
504
505 // --- IBM866 ------------------------------------------------------------------------------
506 "866" | "cp866" | "csibm866" | "ibm866" => Some(Encoding::Ibm866),
507
508 // --- macintosh ---------------------------------------------------------------------------
509 "csmacintosh" | "mac" | "macintosh" | "x-mac-roman" => Some(Encoding::Macintosh),
510
511 // --- x-mac-cyrillic ----------------------------------------------------------------------
512 "x-mac-cyrillic" | "x-mac-ukrainian" => Some(Encoding::XMacCyrillic),
513
514 // --- EUC-JP ------------------------------------------------------------------------------
515 "cseucpkdfmtjapanese" | "euc-jp" | "x-euc-jp" => Some(Encoding::EucJp),
516
517 // --- ISO-2022-JP -------------------------------------------------------------------------
518 "csiso2022jp" | "iso-2022-jp" => Some(Encoding::Iso2022Jp),
519
520 // --- Shift_JIS ---------------------------------------------------------------------------
521 "csshiftjis" | "ms932" | "ms_kanji" | "shift-jis" | "shift_jis" | "sjis"
522 | "windows-31j" | "x-sjis" => Some(Encoding::ShiftJis),
523
524 _ => None,
525 }
526}
527
528#[cfg(test)]
529mod tests {
530 use super::*;
531
532 #[test]
533 fn for_label_is_case_insensitive() {
534 assert_eq!(Encoding::for_label("UTF-8"), Some(Encoding::Utf8));
535 assert_eq!(Encoding::for_label("Utf-8"), Some(Encoding::Utf8));
536 assert_eq!(Encoding::for_label("uTf8"), Some(Encoding::Utf8));
537 assert_eq!(
538 Encoding::for_label("WINDOWS-1252"),
539 Some(Encoding::Windows1252)
540 );
541 assert_eq!(Encoding::for_label("Iso-8859-1"), Some(Encoding::Latin1));
542 assert_eq!(Encoding::for_label("US-ASCII"), Some(Encoding::Ascii));
543 assert_eq!(Encoding::for_label("Shift_JIS"), Some(Encoding::ShiftJis));
544 assert_eq!(Encoding::for_label("SHIFT_JIS"), Some(Encoding::ShiftJis));
545 }
546
547 #[test]
548 fn for_label_trims_whatwg_whitespace() {
549 assert_eq!(Encoding::for_label(" utf-8\t\n"), Some(Encoding::Utf8));
550 assert_eq!(Encoding::for_label("\r\nlatin1 "), Some(Encoding::Latin1));
551 assert_eq!(
552 Encoding::for_label(" shift_jis \n"),
553 Some(Encoding::ShiftJis)
554 );
555 }
556
557 #[test]
558 fn for_label_unknown_returns_none() {
559 assert_eq!(Encoding::for_label("utf-32"), None);
560 assert_eq!(Encoding::for_label(""), None);
561 assert_eq!(Encoding::for_label(" "), None);
562 // Deliberately unsupported (cut from this pass; see crate changelog/report).
563 assert_eq!(Encoding::for_label("gbk"), None);
564 assert_eq!(Encoding::for_label("gb18030"), None);
565 assert_eq!(Encoding::for_label("big5"), None);
566 assert_eq!(Encoding::for_label("euc-kr"), None);
567 assert_eq!(Encoding::for_label("windows-874"), None);
568 assert_eq!(Encoding::for_label("iso-8859-11"), None);
569 }
570
571 #[test]
572 fn for_label_utf16_variants() {
573 assert_eq!(Encoding::for_label("utf-16"), Some(Encoding::Utf16Le));
574 assert_eq!(Encoding::for_label("utf-16le"), Some(Encoding::Utf16Le));
575 assert_eq!(Encoding::for_label("utf-16be"), Some(Encoding::Utf16Be));
576 assert_eq!(Encoding::for_label("unicodeFFFE"), Some(Encoding::Utf16Be));
577 }
578
579 #[test]
580 fn for_label_iso_8859_9_is_kept_distinct_from_windows_1254() {
581 assert_eq!(Encoding::for_label("iso-8859-9"), Some(Encoding::Iso8859_9));
582 assert_eq!(Encoding::for_label("latin5"), Some(Encoding::Iso8859_9));
583 assert_eq!(
584 Encoding::for_label("windows-1254"),
585 Some(Encoding::Windows1254)
586 );
587 assert_eq!(Encoding::for_label("cp1254"), Some(Encoding::Windows1254));
588 assert_ne!(Encoding::Iso8859_9, Encoding::Windows1254);
589 }
590
591 #[test]
592 fn for_label_japanese_multi_byte_labels() {
593 assert_eq!(Encoding::for_label("shift_jis"), Some(Encoding::ShiftJis));
594 assert_eq!(Encoding::for_label("sjis"), Some(Encoding::ShiftJis));
595 assert_eq!(Encoding::for_label("ms932"), Some(Encoding::ShiftJis));
596 assert_eq!(Encoding::for_label("windows-31j"), Some(Encoding::ShiftJis));
597 assert_eq!(Encoding::for_label("euc-jp"), Some(Encoding::EucJp));
598 assert_eq!(Encoding::for_label("x-euc-jp"), Some(Encoding::EucJp));
599 assert_eq!(
600 Encoding::for_label("iso-2022-jp"),
601 Some(Encoding::Iso2022Jp)
602 );
603 assert_eq!(
604 Encoding::for_label("csiso2022jp"),
605 Some(Encoding::Iso2022Jp)
606 );
607 }
608
609 #[test]
610 fn name_round_trips_through_for_label_for_every_supported_encoding() {
611 for &enc in Encoding::ALL {
612 assert_eq!(
613 Encoding::for_label(enc.name()),
614 Some(enc),
615 "name = {}",
616 enc.name()
617 );
618 // Case-insensitivity holds for every encoding's canonical name too.
619 assert_eq!(
620 Encoding::for_label(&enc.name().to_lowercase()),
621 Some(enc),
622 "lowercased name = {}",
623 enc.name()
624 );
625 assert_eq!(
626 Encoding::for_label(&enc.name().to_uppercase()),
627 Some(enc),
628 "uppercased name = {}",
629 enc.name()
630 );
631 }
632 }
633
634 #[test]
635 fn all_is_free_of_duplicates() {
636 for (i, &a) in Encoding::ALL.iter().enumerate() {
637 for &b in &Encoding::ALL[i + 1..] {
638 assert_ne!(a, b, "Encoding::ALL contains a duplicate: {a}");
639 }
640 }
641 }
642
643 #[test]
644 fn display_matches_name() {
645 assert_eq!(Encoding::Utf8.to_string(), "UTF-8");
646 assert_eq!(Encoding::Windows1252.to_string(), "windows-1252");
647 assert_eq!(Encoding::ShiftJis.to_string(), "Shift_JIS");
648 }
649
650 #[test]
651 fn is_ascii_compatible_single_byte_matches_expectation() {
652 assert!(Encoding::Utf8.is_ascii_compatible_single_byte());
653 assert!(Encoding::Latin1.is_ascii_compatible_single_byte());
654 assert!(Encoding::Windows1252.is_ascii_compatible_single_byte());
655 assert!(Encoding::Ascii.is_ascii_compatible_single_byte());
656 assert!(Encoding::Iso8859_2.is_ascii_compatible_single_byte());
657 assert!(Encoding::Windows1251.is_ascii_compatible_single_byte());
658 assert!(Encoding::Koi8R.is_ascii_compatible_single_byte());
659 assert!(!Encoding::Utf16Le.is_ascii_compatible_single_byte());
660 assert!(!Encoding::Utf16Be.is_ascii_compatible_single_byte());
661 assert!(!Encoding::ShiftJis.is_ascii_compatible_single_byte());
662 assert!(!Encoding::EucJp.is_ascii_compatible_single_byte());
663 assert!(!Encoding::Iso2022Jp.is_ascii_compatible_single_byte());
664 }
665
666 #[test]
667 fn is_legacy_single_byte_table_matches_expectation() {
668 assert!(Encoding::Iso8859_2.is_legacy_single_byte_table());
669 assert!(Encoding::Windows1250.is_legacy_single_byte_table());
670 assert!(Encoding::Koi8R.is_legacy_single_byte_table());
671 assert!(Encoding::Macintosh.is_legacy_single_byte_table());
672 assert!(!Encoding::Latin1.is_legacy_single_byte_table());
673 assert!(!Encoding::Windows1252.is_legacy_single_byte_table());
674 assert!(!Encoding::Ascii.is_legacy_single_byte_table());
675 assert!(!Encoding::Utf8.is_legacy_single_byte_table());
676 assert!(!Encoding::ShiftJis.is_legacy_single_byte_table());
677 assert!(!Encoding::EucJp.is_legacy_single_byte_table());
678 assert!(!Encoding::Iso2022Jp.is_legacy_single_byte_table());
679 }
680
681 #[test]
682 fn all_contains_every_variant_exactly_once_via_new_decoder_smoke_test() {
683 // Not exhaustive by construction (nothing enforces that `ALL` is kept in sync with the
684 // enum at compile time), but constructing a decoder for every entry at least catches an
685 // entry that was added to `ALL` without the corresponding `Decoder::new` support (which
686 // would panic here rather than silently mis-decode).
687 for &encoding in Encoding::ALL {
688 let _ = encoding.new_decoder();
689 }
690 }
691}