Skip to main content

quick_xml/
escape.rs

1//! Manage xml character escapes
2
3use memchr::{memchr, memchr2_iter, memchr3};
4use std::borrow::Cow;
5use std::fmt::{self, Write};
6use std::num::ParseIntError;
7use std::ops::Range;
8use std::slice::Iter;
9
10/// Error of parsing character reference (`&#<dec-number>;` or `&#x<hex-number>;`).
11#[derive(Clone, Debug, PartialEq)]
12pub enum ParseCharRefError {
13    /// Number contains sign character (`+` or `-`) which is not allowed.
14    UnexpectedSign,
15    /// Number cannot be parsed due to non-number characters or a numeric overflow.
16    InvalidNumber(ParseIntError),
17    /// Character reference represents not a valid unicode codepoint.
18    InvalidCodepoint(u32),
19    /// Character reference expanded to a not permitted character for an XML.
20    ///
21    /// Currently, only `0x0` character produces this error.
22    IllegalCharacter(u32),
23}
24
25impl std::fmt::Display for ParseCharRefError {
26    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
27        match self {
28            Self::UnexpectedSign => f.write_str("unexpected number sign"),
29            Self::InvalidNumber(e) => e.fmt(f),
30            Self::InvalidCodepoint(n) => write!(f, "`{}` is not a valid codepoint", n),
31            Self::IllegalCharacter(n) => write!(f, "0x{:x} character is not permitted in XML", n),
32        }
33    }
34}
35
36impl std::error::Error for ParseCharRefError {
37    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
38        match self {
39            Self::InvalidNumber(e) => Some(e),
40            _ => None,
41        }
42    }
43}
44
45/// Error for XML escape / unescape.
46#[derive(Clone, Debug, PartialEq)]
47pub enum EscapeError {
48    /// Referenced entity in unknown to the parser.
49    UnrecognizedEntity(Range<usize>, String),
50    /// Cannot find `;` after `&`
51    UnterminatedEntity(Range<usize>),
52    /// Attempt to parse character reference (`&#<dec-number>;` or `&#x<hex-number>;`)
53    /// was unsuccessful, not all characters are decimal or hexadecimal numbers.
54    InvalidCharRef(ParseCharRefError),
55    /// Expanded more than maximum possible entities during attribute normalization.
56    ///
57    /// Attribute normalization includes expanding of general entities (`&entity;`)
58    /// which replacement text also could contain entities, which is also must be expanded.
59    /// If more than 128 entities would be expanded, this error is returned.
60    TooManyNestedEntities,
61}
62
63impl std::fmt::Display for EscapeError {
64    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
65        match self {
66            Self::UnrecognizedEntity(rge, res) => {
67                write!(f, "at {:?}: unrecognized entity `{}`", rge, res)
68            }
69            Self::UnterminatedEntity(e) => write!(
70                f,
71                "Error while escaping character at range {:?}: Cannot find ';' after '&'",
72                e
73            ),
74            Self::InvalidCharRef(e) => {
75                write!(f, "invalid character reference: {}", e)
76            }
77            Self::TooManyNestedEntities => {
78                f.write_str("too many nested entities in an attribute value")
79            }
80        }
81    }
82}
83
84impl std::error::Error for EscapeError {
85    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
86        match self {
87            Self::InvalidCharRef(e) => Some(e),
88            _ => None,
89        }
90    }
91}
92
93/// Escapes an `&str` and replaces all xml special characters (`<`, `>`, `&`, `'`, `"`)
94/// with their corresponding xml escaped value. Also escapes `\r` which would
95/// otherwise be converted to `\n` by XML end-of-line normalization.
96///
97/// This function performs following replacements:
98///
99/// | Character | Replacement
100/// |-----------|------------
101/// | `<`       | `&lt;`
102/// | `>`       | `&gt;`
103/// | `&`       | `&amp;`
104/// | `'`       | `&apos;`
105/// | `"`       | `&quot;`
106/// | `\r`      | `&#13;`
107pub fn escape<'a>(raw: impl Into<Cow<'a, str>>) -> Cow<'a, str> {
108    _escape(raw, |ch| {
109        matches!(ch, b'<' | b'>' | b'&' | b'\'' | b'\"' | b'\r')
110    })
111}
112
113/// Escapes an `&str` and replaces xml special characters (`<`, `>`, `&`)
114/// with their corresponding xml escaped value. Also escapes `\r` which would
115/// otherwise be converted to `\n` by XML end-of-line normalization.
116///
117/// Should only be used for escaping text content. In XML text content, it is allowed
118/// (though not recommended) to leave the quote special characters `"` and `'` unescaped.
119///
120/// This function performs following replacements:
121///
122/// | Character | Replacement
123/// |-----------|------------
124/// | `<`       | `&lt;`
125/// | `>`       | `&gt;`
126/// | `&`       | `&amp;`
127/// | `\r`      | `&#13;`
128pub fn partial_escape<'a>(raw: impl Into<Cow<'a, str>>) -> Cow<'a, str> {
129    _escape(raw, |ch| matches!(ch, b'<' | b'>' | b'&' | b'\r'))
130}
131
132/// XML standard [requires] that only `<` and `&` was escaped in text content or
133/// attribute value. All other characters not necessary to be escaped, although
134/// for compatibility with SGML they also should be escaped. Practically, escaping
135/// only those characters is enough. Also escapes `\r` which would otherwise be
136/// converted to `\n` by XML end-of-line normalization.
137///
138/// This function performs following replacements:
139///
140/// | Character | Replacement
141/// |-----------|------------
142/// | `<`       | `&lt;`
143/// | `&`       | `&amp;`
144/// | `\r`      | `&#13;`
145///
146/// [requires]: https://www.w3.org/TR/xml11/#syntax
147pub fn minimal_escape<'a>(raw: impl Into<Cow<'a, str>>) -> Cow<'a, str> {
148    _escape(raw, |ch| matches!(ch, b'<' | b'&' | b'\r'))
149}
150
151/// Escapes a `&str` for use in an XML attribute value. In addition to the
152/// characters escaped by [`escape`], this also escapes `\n` and `\t` which
153/// would otherwise be normalized to spaces by XML attribute-value normalization.
154///
155/// This function performs following replacements:
156///
157/// | Character | Replacement
158/// |-----------|------------
159/// | `<`       | `&lt;`
160/// | `>`       | `&gt;`
161/// | `&`       | `&amp;`
162/// | `'`       | `&apos;`
163/// | `"`       | `&quot;`
164/// | `\r`      | `&#13;`
165/// | `\n`      | `&#10;`
166/// | `\t`      | `&#9;`
167pub(crate) fn escape_attribute<'a>(raw: impl Into<Cow<'a, str>>) -> Cow<'a, str> {
168    _escape(raw, |ch| {
169        matches!(
170            ch,
171            b'<' | b'>' | b'&' | b'\'' | b'\"' | b'\r' | b'\n' | b'\t'
172        )
173    })
174}
175
176pub(crate) fn escape_char<W>(writer: &mut W, value: &str, from: usize, to: usize) -> fmt::Result
177where
178    W: fmt::Write,
179{
180    writer.write_str(&value[from..to])?;
181    match value.as_bytes()[to] {
182        b'<' => writer.write_str("&lt;")?,
183        b'>' => writer.write_str("&gt;")?,
184        b'\'' => writer.write_str("&apos;")?,
185        b'&' => writer.write_str("&amp;")?,
186        b'"' => writer.write_str("&quot;")?,
187
188        // Whitespace characters: used as delimiters in xs:list elements,
189        // and subject to XML EOL / attribute-value normalization
190        b'\t' => writer.write_str("&#9;")?,
191        b'\n' => writer.write_str("&#10;")?,
192        b'\r' => writer.write_str("&#13;")?,
193        b' ' => writer.write_str("&#32;")?,
194        _ => unreachable!("Only '<', '>','\', '&', '\"', '\\t', '\\r', '\\n', and ' ' are escaped"),
195    }
196    Ok(())
197}
198
199/// Escapes an `&str` and replaces a subset of xml special characters (`<`, `>`,
200/// `&`, `'`, `"`) with their corresponding xml escaped value.
201fn _escape<'a, F: Fn(u8) -> bool>(raw: impl Into<Cow<'a, str>>, escape_chars: F) -> Cow<'a, str> {
202    let raw = raw.into();
203    let bytes = raw.as_bytes();
204    let mut escaped = None;
205    let mut iter = bytes.iter();
206    let mut pos = 0;
207    while let Some(i) = iter.position(|&b| escape_chars(b)) {
208        if escaped.is_none() {
209            escaped = Some(String::with_capacity(raw.len()));
210        }
211        let escaped = escaped.as_mut().expect("initialized");
212        let new_pos = pos + i;
213        // SAFETY: It should fail only on OOM
214        escape_char(escaped, &raw, pos, new_pos).unwrap();
215        pos = new_pos + 1;
216    }
217
218    if let Some(mut escaped) = escaped {
219        if let Some(raw) = raw.get(pos..) {
220            // SAFETY: It should fail only on OOM
221            escaped.write_str(raw).unwrap();
222        }
223        Cow::Owned(escaped)
224    } else {
225        raw
226    }
227}
228
229/// Unescape an `&str` and replaces all xml escaped characters (`&...;`) into
230/// their corresponding value.
231///
232/// If feature [`escape-html`] is enabled, then recognizes all [HTML5 escapes].
233///
234/// [`escape-html`]: ../index.html#escape-html
235/// [HTML5 escapes]: https://dev.w3.org/html5/html-author/charref
236pub fn unescape(raw: &str) -> Result<Cow<'_, str>, EscapeError> {
237    unescape_with(raw, resolve_predefined_entity)
238}
239
240/// Unescape an `&str` and replaces all xml escaped characters (`&...;`) into
241/// their corresponding value, using a resolver function for custom entities.
242///
243/// If feature [`escape-html`] is enabled, then recognizes all [HTML5 escapes].
244///
245/// Predefined entities will be resolved _after_ trying to resolve with `resolve_entity`,
246/// which allows you to override default behavior which required in some XML dialects.
247///
248/// Character references (`&#hh;`) cannot be overridden, they are resolved before
249/// calling `resolve_entity`.
250///
251/// Note, that entities will not be resolved recursively. In order to satisfy the
252/// XML [requirements] you should unescape nested entities by yourself.
253///
254/// # Example
255///
256/// ```
257/// use quick_xml::escape::resolve_xml_entity;
258/// # use quick_xml::escape::unescape_with;
259/// # use pretty_assertions::assert_eq;
260/// let override_named_entities = |entity: &str| match entity {
261///     // Override standard entities
262///     "lt" => Some("FOO"),
263///     "gt" => Some("BAR"),
264///     // Resolve custom entities
265///     "baz" => Some("&lt;"),
266///     // Delegate other entities to the default implementation
267///     _ => resolve_xml_entity(entity),
268/// };
269///
270/// assert_eq!(
271///     unescape_with("&amp;&lt;test&gt;&baz;", override_named_entities).unwrap(),
272///     "&FOOtestBAR&lt;"
273/// );
274/// ```
275///
276/// [`escape-html`]: ../index.html#escape-html
277/// [HTML5 escapes]: https://dev.w3.org/html5/html-author/charref
278/// [requirements]: https://www.w3.org/TR/xml11/#intern-replacement
279pub fn unescape_with<'input, 'entity, F>(
280    raw: &'input str,
281    mut resolve_entity: F,
282) -> Result<Cow<'input, str>, EscapeError>
283where
284    // the lifetime of the output comes from a capture or is `'static`
285    F: FnMut(&str) -> Option<&'entity str>,
286{
287    let bytes = raw.as_bytes();
288    let mut unescaped = None;
289    let mut last_end = 0;
290    let mut iter = memchr2_iter(b'&', b';', bytes);
291    while let Some(start) = iter.by_ref().find(|p| bytes[*p] == b'&') {
292        match iter.next() {
293            Some(end) if bytes[end] == b';' => {
294                // append valid data
295                if unescaped.is_none() {
296                    unescaped = Some(String::with_capacity(raw.len()));
297                }
298                let unescaped = unescaped.as_mut().expect("initialized");
299                unescaped.push_str(&raw[last_end..start]);
300
301                // search for character correctness
302                let pat = &raw[start + 1..end];
303                if let Some(entity) = pat.strip_prefix('#') {
304                    let codepoint = parse_number(entity).map_err(EscapeError::InvalidCharRef)?;
305                    unescaped.push_str(codepoint.encode_utf8(&mut [0u8; 4]));
306                } else if let Some(value) = resolve_entity(pat) {
307                    unescaped.push_str(value);
308                } else {
309                    return Err(EscapeError::UnrecognizedEntity(
310                        start + 1..end,
311                        pat.to_string(),
312                    ));
313                }
314
315                last_end = end + 1;
316            }
317            _ => return Err(EscapeError::UnterminatedEntity(start..raw.len())),
318        }
319    }
320
321    if let Some(mut unescaped) = unescaped {
322        if let Some(raw) = raw.get(last_end..) {
323            unescaped.push_str(raw);
324        }
325        Ok(Cow::Owned(unescaped))
326    } else {
327        Ok(Cow::Borrowed(raw))
328    }
329}
330
331////////////////////////////////////////////////////////////////////////////////////////////////////
332
333// TODO: It would be better to reuse buffer after decoding if possible
334pub(crate) fn normalize_xml11_eols<'input>(text: &'input str) -> Cow<'input, str> {
335    let bytes = text.as_bytes();
336
337    // The following sequences of UTF-8 encoded input should be translated into
338    // a single `\n` (U+000a) character to normalize EOLs:
339    //
340    // |UTF-8   |String|
341    // |--------|------|
342    // |0d 0a   |\r\n  |
343    // |0d c2 85|\r\x85|
344    // |0d      |\r    |
345    // |c2 85   |\x85  |
346    // |e2 80 a8|\u2028|
347    if let Some(i) = memchr3(b'\r', 0xC2, 0xE2, bytes) {
348        // We found a character that requires normalization, so create new normalized
349        // string, put the prefix as is and then put normalized character
350        let mut normalized = String::with_capacity(text.len());
351        // NOTE: unsafe { text.get_unchecked(0..i) } could be used because
352        // we are sure that index within string
353        normalized.push_str(&text[0..i]);
354
355        let mut pos = normalize_xml11_eol_step(&mut normalized, text, i, '\n');
356        while let Some(i) = memchr3(b'\r', 0xC2, 0xE2, &bytes[pos..]) {
357            let index = pos + i;
358            // NOTE: unsafe { text.get_unchecked(pos..index) } could be used because
359            // we are sure that index within string
360            normalized.push_str(&text[pos..index]);
361            pos = normalize_xml11_eol_step(&mut normalized, text, index, '\n');
362        }
363        if let Some(rest) = text.get(pos..) {
364            normalized.push_str(rest);
365        }
366        return normalized.into();
367    }
368    Cow::Borrowed(text)
369}
370
371/// All line breaks MUST have been normalized on input to #xA as described
372/// in [2.11 End-of-Line Handling][eof], so the rest of this algorithm operates
373/// on text normalized in this way.
374///
375/// To simplify the tasks of applications, the XML processor MUST behave
376/// as if it normalized all line breaks in external parsed entities
377/// (including the document entity) on input, before parsing, by translating
378/// all of the following to a single #xA character (_which attribute normalization
379/// routine will replace by #x20 character_):
380///
381/// 1. the two-character sequence #xD #xA
382/// 2. the two-character sequence #xD #x85
383/// 3. the single character #x85
384/// 4. the single character #x2028
385/// 5. any #xD character that is not immediately followed by #xA or #x85.
386///
387/// The characters #x85 and #x2028 cannot be reliably recognized and translated
388/// until an entity's encoding declaration (if present) has been read.
389/// Therefore, it is a fatal error to use them within the XML declaration or text declaration.
390///
391/// Note, that this function cannot be used to normalize HTML values. The text in HTML
392/// normally is not normalized in any way; normalization is performed only in limited
393/// contexts and [only for] `\r\n` and `\r`.
394///
395/// # Parameters
396///
397/// - `normalized`: the string with the result of normalization
398/// - `input`: UTF-8 bytes of the string to be normalized
399/// - `index`: a byte index into `input` of character which is processed right now.
400///   It always points to the first byte of character in UTF-8 encoding
401/// - `ch`: a character that should be put to the string instead of newline sequence
402///
403/// Returns the index of next unprocessed byte in the `input`.
404///
405/// [eof]: https://www.w3.org/TR/xml11/#sec-line-ends
406/// [only for]: https://html.spec.whatwg.org/#normalize-newlines
407fn normalize_xml11_eol_step(normalized: &mut String, text: &str, index: usize, ch: char) -> usize {
408    let input = text.as_bytes();
409    match input[index] {
410        b'\r' => {
411            if index + 1 < input.len() {
412                let next = input[index + 1];
413                if next == b'\n' {
414                    normalized.push(ch);
415                    return index + 2; // skip \r\n
416                }
417                if next == 0xC2 {
418                    // UTF-8 encoding of #x85 character is [c2 85]
419                    if index + 2 < input.len() && input[index + 2] == 0x85 {
420                        normalized.push(ch);
421                    } else {
422                        normalized.push(ch);
423                        // NOTE: unsafe { text.get_unchecked(index..index + 3) } could be used because
424                        // we are sure that index within string
425                        normalized.push_str(&text[index + 1..index + 3]);
426                    }
427                    return index + 3; // skip \r + UTF-8 encoding of character (c2 xx)
428                }
429            }
430            normalized.push(ch);
431            index + 1 // skip \r
432        }
433        b'\n' => {
434            normalized.push(ch);
435            index + 1 // skip \n
436        }
437        // Start of UTF-8 encoding of #x85 character (c2 85)
438        0xC2 => {
439            if index + 1 < input.len() && input[index + 1] == 0x85 {
440                normalized.push(ch);
441            } else {
442                // NOTE: unsafe { text.get_unchecked(index..index + 2) } could be used because
443                // we are sure that index within string
444                normalized.push_str(&text[index..index + 2]);
445            }
446            index + 2 // skip UTF-8 encoding of character (c2 xx)
447        }
448        // Start of UTF-8 encoding of #x2028 character (e2 80 a8)
449        0xE2 => {
450            if index + 2 < input.len() && input[index + 1] == 0x80 && input[index + 2] == 0xA8 {
451                normalized.push(ch);
452            } else {
453                // NOTE: unsafe { text.get_unchecked(index..index + 3) } could be used because
454                // we are sure that index within string
455                normalized.push_str(&text[index..index + 3]);
456            }
457            index + 3 // skip UTF-8 encoding of character (e2 xx xx)
458        }
459
460        x => unreachable!(
461            "at {}: expected ''\\n', '\\r', '\\xC2', or '\\xE2', found '{}' / {} / `0x{:X}`",
462            index, x as char, x, x
463        ),
464    }
465}
466
467////////////////////////////////////////////////////////////////////////////////////////////////////
468
469// TODO: It would be better to reuse buffer after decoding if possible
470pub(crate) fn normalize_xml10_eols<'input>(text: &'input str) -> Cow<'input, str> {
471    let bytes = text.as_bytes();
472
473    // The following sequences of UTF-8 encoded input should be translated into
474    // a single `\n` (U+000a) character to normalize EOLs:
475    //
476    // |UTF-8   |String|
477    // |--------|------|
478    // |0d 0a   |\r\n  |
479    // |0d      |\r    |
480    if let Some(i) = memchr(b'\r', bytes) {
481        // We found a character that requires normalization, so create new normalized
482        // string, put the prefix as is and then put normalized character
483        let mut normalized = String::with_capacity(text.len());
484        // NOTE: unsafe { text.get_unchecked(0..i) } could be used because
485        // we are sure that index within string
486        normalized.push_str(&text[0..i]);
487
488        let mut pos = normalize_xml10_eol_step(&mut normalized, text, i, '\n');
489        while let Some(i) = memchr(b'\r', &bytes[pos..]) {
490            let index = pos + i;
491            // NOTE: unsafe { text.get_unchecked(pos..index) } could be used because
492            // we are sure that index within string
493            normalized.push_str(&text[pos..index]);
494            pos = normalize_xml10_eol_step(&mut normalized, text, index, '\n');
495        }
496        if let Some(rest) = text.get(pos..) {
497            normalized.push_str(rest);
498        }
499        return normalized.into();
500    }
501    Cow::Borrowed(text)
502}
503
504/// The text in HTML normally is not normalized in any way; normalization is
505/// performed only in limited contexts and [only for] `\r\n` and `\r`.
506///
507/// # Parameters
508///
509/// - `normalized`: the string with the result of normalization
510/// - `input`: UTF-8 bytes of the string to be normalized
511/// - `index`: a byte index into `input` of character which is processed right now.
512///   It always points to the first byte of character in UTF-8 encoding
513/// - `ch`: a character that should be put to the string instead of newline sequence
514///
515/// [only for]: https://html.spec.whatwg.org/#normalize-newlines
516fn normalize_xml10_eol_step(normalized: &mut String, text: &str, index: usize, ch: char) -> usize {
517    let input = text.as_bytes();
518    match input[index] {
519        b'\r' => {
520            normalized.push(ch);
521            if index + 1 < input.len() && input[index + 1] == b'\n' {
522                return index + 2; // skip \r\n
523            }
524            index + 1 // skip \r
525        }
526        b'\n' => {
527            normalized.push(ch);
528            index + 1 // skip \n
529        }
530
531        x => unreachable!(
532            "at {}: expected ''\\n' or '\\r', found '{}' / {} / `0x{:X}`",
533            index, x as char, x, x
534        ),
535    }
536}
537
538////////////////////////////////////////////////////////////////////////////////////////////////////
539
540pub(crate) fn normalize_xml10_attribute_value<'input, 'entity, F>(
541    value: &'input str,
542    depth: usize,
543    resolve_entity: F,
544) -> Result<Cow<'input, str>, EscapeError>
545where
546    // the lifetime of the output comes from a capture or is `'static`
547    F: FnMut(&str) -> Option<&'entity str>,
548{
549    normalize_attribute_value(
550        value,
551        depth,
552        is_xml10_normalization_char,
553        normalize_xml10_eol_step,
554        resolve_entity,
555    )
556}
557
558const fn is_xml10_normalization_char(b: &u8) -> bool {
559    // The following sequences should be translated into a single `\n` (U+000a) character
560    // to normalize EOLs:
561    //
562    // |UTF-8   |String|
563    // |--------|------|
564    // |0d 0a   |\r\n  |
565    // |0d      |\r    |
566    matches!(*b, b'\t' | b'\r' | b'\n' | b'&')
567}
568
569////////////////////////////////////////////////////////////////////////////////////////////////////
570
571pub(crate) fn normalize_xml11_attribute_value<'input, 'entity, F>(
572    value: &'input str,
573    depth: usize,
574    resolve_entity: F,
575) -> Result<Cow<'input, str>, EscapeError>
576where
577    // the lifetime of the output comes from a capture or is `'static`
578    F: FnMut(&str) -> Option<&'entity str>,
579{
580    normalize_attribute_value(
581        value,
582        depth,
583        is_xml11_normalization_char,
584        normalize_xml11_eol_step,
585        resolve_entity,
586    )
587}
588
589const fn is_xml11_normalization_char(b: &u8) -> bool {
590    // The following sequences should be translated into a single `\n` (U+000a) character
591    // to normalize EOLs:
592    //
593    // |UTF-8   |String|
594    // |--------|------|
595    // |0d 0a   |\r\n  |
596    // |0d c2 85|\r\x85|
597    // |0d      |\r    |
598    // |c2 85   |\x85  |
599    // |e2 80 a8|\x2028|
600    matches!(*b, b'\t' | b'\r' | b'\n' | 0xC2 | 0xE2 | b'&')
601}
602
603////////////////////////////////////////////////////////////////////////////////////////////////////
604
605/// Returns the attribute value normalized as per [the XML specification],
606/// using a custom entity resolver.
607///
608/// Do not use this method with HTML attributes.
609///
610/// Escape sequences such as `&gt;` are replaced with their unescaped equivalents such as `>`
611/// and the characters `\t`, `\r`, `\n` are replaced with whitespace characters. A function
612/// for resolving entities can be provided as `resolve_entity`. Builtin entities will still
613/// take precedence.
614///
615/// This will allocate unless the raw attribute value does not require normalization.
616///
617/// # Parameters
618///
619/// - `value`: unnormalized attribute value
620/// - `depth`: maximum number of nested entities that can be expanded. If expansion
621///   chain will be more that this value, the function will return [`EscapeError::TooManyNestedEntities`]
622/// - `is_normalization_char`: a function to check if byte is the start byte of character
623///   that should be normalized (UTF-8 encoding is assumed)
624/// - `normalize_eol_step`: a function that performs EOL normalization of a character
625/// - `resolve_entity`: a function to resolve entity. This function could be called
626///   multiple times on the same input and can return different values in each case
627///   for the same input, although it is not recommended
628///
629/// # Lifetimes
630///
631/// - `'input`: lifetime of the unnormalized attribute. If normalization is not required,
632///   the input returned unchanged with the same lifetime
633/// - `'entity`: lifetime of all entities that is returned by the entity resolution routine
634///
635/// [the XML specification]: https://www.w3.org/TR/xml11/#AVNormalize
636pub fn normalize_attribute_value<'input, 'entity, C, E, F>(
637    value: &'input str,
638    depth: usize,
639    is_normalization_char: C,
640    normalize_eol_step: E,
641    mut resolve_entity: F,
642) -> Result<Cow<'input, str>, EscapeError>
643where
644    C: Fn(&u8) -> bool,
645    E: Fn(&mut String, &str, usize, char) -> usize,
646    // the lifetime of the output comes from a capture or is `'static`
647    F: FnMut(&str) -> Option<&'entity str>,
648{
649    let mut iter = value.as_bytes().iter();
650
651    // If we found the character that requires normalization, create a normalized
652    // version of the attribute, otherwise return the value unchanged
653    if let Some(i) = iter.position(&is_normalization_char) {
654        let mut normalized = String::with_capacity(value.len());
655        let pos = normalize_attr_step(
656            &mut normalized,
657            &mut iter,
658            value,
659            0,
660            i,
661            depth,
662            &is_normalization_char,
663            &normalize_eol_step,
664            &mut resolve_entity,
665        )?;
666
667        normalize_attr_steps(
668            &mut normalized,
669            &mut iter,
670            value,
671            pos,
672            depth,
673            &is_normalization_char,
674            &normalize_eol_step,
675            &mut resolve_entity,
676        )?;
677        return Ok(normalized.into());
678    }
679    Ok(Cow::Borrowed(value))
680}
681
682fn normalize_attr_steps<'entity, C, E, F>(
683    normalized: &mut String,
684    iter: &mut Iter<u8>,
685    input: &str,
686    mut pos: usize,
687    depth: usize,
688    is_normalization_char: &C,
689    normalize_eol_step: &E,
690    resolve_entity: &mut F,
691) -> Result<(), EscapeError>
692where
693    C: Fn(&u8) -> bool,
694    E: Fn(&mut String, &str, usize, char) -> usize,
695    // the lifetime of the output comes from a capture or is `'static`
696    F: FnMut(&str) -> Option<&'entity str>,
697{
698    while let Some(i) = iter.position(is_normalization_char) {
699        pos = normalize_attr_step(
700            normalized,
701            iter,
702            input,
703            pos,
704            pos + i,
705            depth,
706            is_normalization_char,
707            normalize_eol_step,
708            resolve_entity,
709        )?;
710    }
711    if let Some(rest) = input.get(pos..) {
712        normalized.push_str(rest);
713    }
714    Ok(())
715}
716
717/// Performs one step of the [normalization algorithm] (but with recursive part):
718///
719/// 1. For a character reference, append the referenced character
720///    to the normalized value.
721/// 2. For an entity reference, recursively apply this algorithm
722///    to the replacement text of the entity.
723/// 3. For a white space character (#x20, #xD, #xA, #x9), append
724///    a space character (#x20) to the normalized value.
725/// 4. For another character, append the character to the normalized value.
726///
727/// Because [according to the specification], XML parser should parse line-of-end
728/// normalized input, but quick-xml does not do that, this function also performs
729/// normalization of EOL characters. That should be done before expanding entities
730/// and character references, so cannot be processed later.
731///
732/// This function could be used also just to normalize line ends if the iterator
733/// won't be stop on `&` characters.
734///
735/// # Parameters
736///
737/// - `normalized`: Output of the algorithm. Normalized value will be placed here
738/// - `iter`: Iterator over bytes of `input`
739/// - `input`: Original non-normalized value
740/// - `last_pos`: Index of the last byte in `input` that was processed
741/// - `index`: Index of the byte in `input` that should be processed now
742/// - `seen_cr`: `\r\n` and `\r\x85` sequences should be normalized into one space
743///   so this parameter tracks if we seen the `\r` before processing the current byte
744/// - `depth`: Current recursion depth. Too deep recursion will interrupt the algorithm
745/// - `is_normalization_char`: a function to check if byte is the start byte of character
746///   that should be normalized (UTF-8 encoding is assumed)
747/// - `normalize_eol_step`: a function that performs EOL normalization of a character
748/// - `resolve_entity`: Resolver of entities. Returns `None` for unknown entities
749///
750/// # Lifetimes
751///
752/// - `'entity`: lifetime of all entities that is returned by the entity resolution routine
753///
754/// [normalization algorithm]: https://www.w3.org/TR/xml11/#AVNormalize
755/// [according to the specification]: https://www.w3.org/TR/xml11/#sec-line-ends
756fn normalize_attr_step<'entity, C, E, F>(
757    normalized: &mut String,
758    iter: &mut Iter<u8>,
759    input: &str,
760    last_pos: usize,
761    index: usize,
762    depth: usize,
763    is_normalization_char: &C,
764    normalize_eol_step: &E,
765    resolve_entity: &mut F,
766) -> Result<usize, EscapeError>
767where
768    C: Fn(&u8) -> bool,
769    E: Fn(&mut String, &str, usize, char) -> usize,
770    // the lifetime of the output comes from a capture or is `'static`
771    F: FnMut(&str) -> Option<&'entity str>,
772{
773    if depth == 0 {
774        return Err(EscapeError::TooManyNestedEntities);
775    }
776    // 4. For another character, append the character to the normalized value.
777    normalized.push_str(&input[last_pos..index]);
778
779    match input.as_bytes()[index] {
780        b'&' => {
781            let start = index + 1; // +1 - skip `&`
782            let end = start
783                + match iter.position(|&b| b == b';') {
784                    Some(end) => end,
785                    None => return Err(EscapeError::UnterminatedEntity(index..input.len())),
786                };
787
788            // Content between & and ; - &pat;
789            // Note, that this content have non-normalized EOLs as required by the specification,
790            // but because numbers in any case cannot have spaces inside, this is not the problem.
791            // Normalization of spaces in entity references and checking that they corresponds to
792            // [`Name`] production on conscience `resolve_entity`.
793            //
794            // [`Name`]: https://www.w3.org/TR/xml11/#NT-Name
795            let pat = &input[start..end];
796            // 1. For a character reference, append the referenced character
797            //    to the normalized value.
798            if let Some(entity) = pat.strip_prefix('#') {
799                let codepoint = parse_number(entity).map_err(EscapeError::InvalidCharRef)?;
800                normalized.push_str(codepoint.encode_utf8(&mut [0u8; 4]));
801            } else
802            // Special case: '&amp;' resolves to '&' and if follow this algorithm
803            // without special handling, we got unterminated entity error
804            if pat == "amp" {
805                normalized.push('&');
806            } else
807            // 2. For an entity reference, recursively apply this algorithm
808            //    to the replacement text of the entity.
809            if let Some(value) = resolve_entity(pat) {
810                normalize_attr_steps(
811                    normalized,
812                    &mut value.as_bytes().iter(),
813                    value,
814                    0,
815                    depth.saturating_sub(1),
816                    is_normalization_char,
817                    normalize_eol_step,
818                    resolve_entity,
819                )?;
820            } else {
821                return Err(EscapeError::UnrecognizedEntity(start..end, pat.to_string()));
822            }
823            Ok(end + 1) // +1 - skip `;`
824        }
825        // 3. For a white space character (#x20, #xD, #xA, #x9), append
826        //    a space character (#x20) to the normalized value.
827        // Space character (#x20) has no special meaning, so it is handled on step 4
828        b'\t' => {
829            normalized.push(' ');
830            Ok(index + 1) // +1 - skip \t
831        }
832        _ => {
833            let pos = normalize_eol_step(normalized, input, index, ' ');
834            // We should advance iterator because we may skip several characters
835            for _ in 0..pos - index - 1 {
836                iter.next();
837            }
838            Ok(pos)
839        }
840    }
841}
842
843////////////////////////////////////////////////////////////////////////////////////////////////////
844
845/// Resolves predefined XML entities or all HTML5 entities depending on the feature
846/// [`escape-html`](https://docs.rs/quick-xml/latest/quick_xml/#escape-html).
847///
848/// Behaves like [`resolve_xml_entity`] if feature is not enabled and as
849/// [`resolve_html5_entity`] if enabled.
850#[inline]
851pub const fn resolve_predefined_entity(entity: &str) -> Option<&'static str> {
852    #[cfg(not(feature = "escape-html"))]
853    {
854        resolve_xml_entity(entity)
855    }
856
857    #[cfg(feature = "escape-html")]
858    {
859        resolve_html5_entity(entity)
860    }
861}
862
863/// Resolves predefined XML entities. If specified entity is not a predefined XML
864/// entity, `None` is returned.
865///
866/// The complete list of predefined entities are defined in the [specification].
867///
868/// ```
869/// # use quick_xml::escape::resolve_xml_entity;
870/// # use pretty_assertions::assert_eq;
871/// assert_eq!(resolve_xml_entity("lt"), Some("<"));
872/// assert_eq!(resolve_xml_entity("gt"), Some(">"));
873/// assert_eq!(resolve_xml_entity("amp"), Some("&"));
874/// assert_eq!(resolve_xml_entity("apos"), Some("'"));
875/// assert_eq!(resolve_xml_entity("quot"), Some("\""));
876///
877/// assert_eq!(resolve_xml_entity("foo"), None);
878/// ```
879///
880/// [specification]: https://www.w3.org/TR/xml11/#sec-predefined-ent
881pub const fn resolve_xml_entity(entity: &str) -> Option<&'static str> {
882    // match over strings are not allowed in const functions
883    let s = match entity.as_bytes() {
884        b"lt" => "<",
885        b"gt" => ">",
886        b"amp" => "&",
887        b"apos" => "'",
888        b"quot" => "\"",
889        _ => return None,
890    };
891    Some(s)
892}
893
894/// Resolves all HTML5 entities. For complete list see <https://dev.w3.org/html5/html-author/charref>.
895#[cfg(feature = "escape-html")]
896pub const fn resolve_html5_entity(entity: &str) -> Option<&'static str> {
897    // imported from https://dev.w3.org/html5/html-author/charref
898    // match over strings are not allowed in const functions
899    //TODO: automate up-to-dating using https://html.spec.whatwg.org/entities.json
900    //TODO: building this function increases compilation time by 10+ seconds (or 5x times)
901    // Maybe this is because of very long match
902    // See https://github.com/tafia/quick-xml/issues/763
903    let s = match entity.as_bytes() {
904        b"Tab" => "\u{09}",
905        b"NewLine" => "\u{0A}",
906        b"excl" => "\u{21}",
907        b"quot" | b"QUOT" => "\u{22}",
908        b"num" => "\u{23}",
909        b"dollar" => "\u{24}",
910        b"percnt" => "\u{25}",
911        b"amp" | b"AMP" => "\u{26}",
912        b"apos" => "\u{27}",
913        b"lpar" => "\u{28}",
914        b"rpar" => "\u{29}",
915        b"ast" | b"midast" => "\u{2A}",
916        b"plus" => "\u{2B}",
917        b"comma" => "\u{2C}",
918        b"period" => "\u{2E}",
919        b"sol" => "\u{2F}",
920        b"colon" => "\u{3A}",
921        b"semi" => "\u{3B}",
922        b"lt" | b"LT" => "\u{3C}",
923        b"equals" => "\u{3D}",
924        b"gt" | b"GT" => "\u{3E}",
925        b"quest" => "\u{3F}",
926        b"commat" => "\u{40}",
927        b"lsqb" | b"lbrack" => "\u{5B}",
928        b"bsol" => "\u{5C}",
929        b"rsqb" | b"rbrack" => "\u{5D}",
930        b"Hat" => "\u{5E}",
931        b"lowbar" => "\u{5F}",
932        b"grave" | b"DiacriticalGrave" => "\u{60}",
933        b"lcub" | b"lbrace" => "\u{7B}",
934        b"verbar" | b"vert" | b"VerticalLine" => "\u{7C}",
935        b"rcub" | b"rbrace" => "\u{7D}",
936        b"nbsp" | b"NonBreakingSpace" => "\u{A0}",
937        b"iexcl" => "\u{A1}",
938        b"cent" => "\u{A2}",
939        b"pound" => "\u{A3}",
940        b"curren" => "\u{A4}",
941        b"yen" => "\u{A5}",
942        b"brvbar" => "\u{A6}",
943        b"sect" => "\u{A7}",
944        b"Dot" | b"die" | b"DoubleDot" | b"uml" => "\u{A8}",
945        b"copy" | b"COPY" => "\u{A9}",
946        b"ordf" => "\u{AA}",
947        b"laquo" => "\u{AB}",
948        b"not" => "\u{AC}",
949        b"shy" => "\u{AD}",
950        b"reg" | b"circledR" | b"REG" => "\u{AE}",
951        b"macr" | b"OverBar" | b"strns" => "\u{AF}",
952        b"deg" => "\u{B0}",
953        b"plusmn" | b"pm" | b"PlusMinus" => "\u{B1}",
954        b"sup2" => "\u{B2}",
955        b"sup3" => "\u{B3}",
956        b"acute" | b"DiacriticalAcute" => "\u{B4}",
957        b"micro" => "\u{B5}",
958        b"para" => "\u{B6}",
959        b"middot" | b"centerdot" | b"CenterDot" => "\u{B7}",
960        b"cedil" | b"Cedilla" => "\u{B8}",
961        b"sup1" => "\u{B9}",
962        b"ordm" => "\u{BA}",
963        b"raquo" => "\u{BB}",
964        b"frac14" => "\u{BC}",
965        b"frac12" | b"half" => "\u{BD}",
966        b"frac34" => "\u{BE}",
967        b"iquest" => "\u{BF}",
968        b"Agrave" => "\u{C0}",
969        b"Aacute" => "\u{C1}",
970        b"Acirc" => "\u{C2}",
971        b"Atilde" => "\u{C3}",
972        b"Auml" => "\u{C4}",
973        b"Aring" => "\u{C5}",
974        b"AElig" => "\u{C6}",
975        b"Ccedil" => "\u{C7}",
976        b"Egrave" => "\u{C8}",
977        b"Eacute" => "\u{C9}",
978        b"Ecirc" => "\u{CA}",
979        b"Euml" => "\u{CB}",
980        b"Igrave" => "\u{CC}",
981        b"Iacute" => "\u{CD}",
982        b"Icirc" => "\u{CE}",
983        b"Iuml" => "\u{CF}",
984        b"ETH" => "\u{D0}",
985        b"Ntilde" => "\u{D1}",
986        b"Ograve" => "\u{D2}",
987        b"Oacute" => "\u{D3}",
988        b"Ocirc" => "\u{D4}",
989        b"Otilde" => "\u{D5}",
990        b"Ouml" => "\u{D6}",
991        b"times" => "\u{D7}",
992        b"Oslash" => "\u{D8}",
993        b"Ugrave" => "\u{D9}",
994        b"Uacute" => "\u{DA}",
995        b"Ucirc" => "\u{DB}",
996        b"Uuml" => "\u{DC}",
997        b"Yacute" => "\u{DD}",
998        b"THORN" => "\u{DE}",
999        b"szlig" => "\u{DF}",
1000        b"agrave" => "\u{E0}",
1001        b"aacute" => "\u{E1}",
1002        b"acirc" => "\u{E2}",
1003        b"atilde" => "\u{E3}",
1004        b"auml" => "\u{E4}",
1005        b"aring" => "\u{E5}",
1006        b"aelig" => "\u{E6}",
1007        b"ccedil" => "\u{E7}",
1008        b"egrave" => "\u{E8}",
1009        b"eacute" => "\u{E9}",
1010        b"ecirc" => "\u{EA}",
1011        b"euml" => "\u{EB}",
1012        b"igrave" => "\u{EC}",
1013        b"iacute" => "\u{ED}",
1014        b"icirc" => "\u{EE}",
1015        b"iuml" => "\u{EF}",
1016        b"eth" => "\u{F0}",
1017        b"ntilde" => "\u{F1}",
1018        b"ograve" => "\u{F2}",
1019        b"oacute" => "\u{F3}",
1020        b"ocirc" => "\u{F4}",
1021        b"otilde" => "\u{F5}",
1022        b"ouml" => "\u{F6}",
1023        b"divide" | b"div" => "\u{F7}",
1024        b"oslash" => "\u{F8}",
1025        b"ugrave" => "\u{F9}",
1026        b"uacute" => "\u{FA}",
1027        b"ucirc" => "\u{FB}",
1028        b"uuml" => "\u{FC}",
1029        b"yacute" => "\u{FD}",
1030        b"thorn" => "\u{FE}",
1031        b"yuml" => "\u{FF}",
1032        b"Amacr" => "\u{10}",
1033        b"amacr" => "\u{10}",
1034        b"Abreve" => "\u{10}",
1035        b"abreve" => "\u{10}",
1036        b"Aogon" => "\u{10}",
1037        b"aogon" => "\u{10}",
1038        b"Cacute" => "\u{10}",
1039        b"cacute" => "\u{10}",
1040        b"Ccirc" => "\u{10}",
1041        b"ccirc" => "\u{10}",
1042        b"Cdot" => "\u{10}",
1043        b"cdot" => "\u{10}",
1044        b"Ccaron" => "\u{10}",
1045        b"ccaron" => "\u{10}",
1046        b"Dcaron" => "\u{10}",
1047        b"dcaron" => "\u{10}",
1048        b"Dstrok" => "\u{11}",
1049        b"dstrok" => "\u{11}",
1050        b"Emacr" => "\u{11}",
1051        b"emacr" => "\u{11}",
1052        b"Edot" => "\u{11}",
1053        b"edot" => "\u{11}",
1054        b"Eogon" => "\u{11}",
1055        b"eogon" => "\u{11}",
1056        b"Ecaron" => "\u{11}",
1057        b"ecaron" => "\u{11}",
1058        b"Gcirc" => "\u{11}",
1059        b"gcirc" => "\u{11}",
1060        b"Gbreve" => "\u{11}",
1061        b"gbreve" => "\u{11}",
1062        b"Gdot" => "\u{12}",
1063        b"gdot" => "\u{12}",
1064        b"Gcedil" => "\u{12}",
1065        b"Hcirc" => "\u{12}",
1066        b"hcirc" => "\u{12}",
1067        b"Hstrok" => "\u{12}",
1068        b"hstrok" => "\u{12}",
1069        b"Itilde" => "\u{12}",
1070        b"itilde" => "\u{12}",
1071        b"Imacr" => "\u{12}",
1072        b"imacr" => "\u{12}",
1073        b"Iogon" => "\u{12}",
1074        b"iogon" => "\u{12}",
1075        b"Idot" => "\u{13}",
1076        b"imath" | b"inodot" => "\u{13}",
1077        b"IJlig" => "\u{13}",
1078        b"ijlig" => "\u{13}",
1079        b"Jcirc" => "\u{13}",
1080        b"jcirc" => "\u{13}",
1081        b"Kcedil" => "\u{13}",
1082        b"kcedil" => "\u{13}",
1083        b"kgreen" => "\u{13}",
1084        b"Lacute" => "\u{13}",
1085        b"lacute" => "\u{13}",
1086        b"Lcedil" => "\u{13}",
1087        b"lcedil" => "\u{13}",
1088        b"Lcaron" => "\u{13}",
1089        b"lcaron" => "\u{13}",
1090        b"Lmidot" => "\u{13}",
1091        b"lmidot" => "\u{14}",
1092        b"Lstrok" => "\u{14}",
1093        b"lstrok" => "\u{14}",
1094        b"Nacute" => "\u{14}",
1095        b"nacute" => "\u{14}",
1096        b"Ncedil" => "\u{14}",
1097        b"ncedil" => "\u{14}",
1098        b"Ncaron" => "\u{14}",
1099        b"ncaron" => "\u{14}",
1100        b"napos" => "\u{14}",
1101        b"ENG" => "\u{14}",
1102        b"eng" => "\u{14}",
1103        b"Omacr" => "\u{14}",
1104        b"omacr" => "\u{14}",
1105        b"Odblac" => "\u{15}",
1106        b"odblac" => "\u{15}",
1107        b"OElig" => "\u{15}",
1108        b"oelig" => "\u{15}",
1109        b"Racute" => "\u{15}",
1110        b"racute" => "\u{15}",
1111        b"Rcedil" => "\u{15}",
1112        b"rcedil" => "\u{15}",
1113        b"Rcaron" => "\u{15}",
1114        b"rcaron" => "\u{15}",
1115        b"Sacute" => "\u{15}",
1116        b"sacute" => "\u{15}",
1117        b"Scirc" => "\u{15}",
1118        b"scirc" => "\u{15}",
1119        b"Scedil" => "\u{15}",
1120        b"scedil" => "\u{15}",
1121        b"Scaron" => "\u{16}",
1122        b"scaron" => "\u{16}",
1123        b"Tcedil" => "\u{16}",
1124        b"tcedil" => "\u{16}",
1125        b"Tcaron" => "\u{16}",
1126        b"tcaron" => "\u{16}",
1127        b"Tstrok" => "\u{16}",
1128        b"tstrok" => "\u{16}",
1129        b"Utilde" => "\u{16}",
1130        b"utilde" => "\u{16}",
1131        b"Umacr" => "\u{16}",
1132        b"umacr" => "\u{16}",
1133        b"Ubreve" => "\u{16}",
1134        b"ubreve" => "\u{16}",
1135        b"Uring" => "\u{16}",
1136        b"uring" => "\u{16}",
1137        b"Udblac" => "\u{17}",
1138        b"udblac" => "\u{17}",
1139        b"Uogon" => "\u{17}",
1140        b"uogon" => "\u{17}",
1141        b"Wcirc" => "\u{17}",
1142        b"wcirc" => "\u{17}",
1143        b"Ycirc" => "\u{17}",
1144        b"ycirc" => "\u{17}",
1145        b"Yuml" => "\u{17}",
1146        b"Zacute" => "\u{17}",
1147        b"zacute" => "\u{17}",
1148        b"Zdot" => "\u{17}",
1149        b"zdot" => "\u{17}",
1150        b"Zcaron" => "\u{17}",
1151        b"zcaron" => "\u{17}",
1152        b"fnof" => "\u{19}",
1153        b"imped" => "\u{1B}",
1154        b"gacute" => "\u{1F}",
1155        b"jmath" => "\u{23}",
1156        b"circ" => "\u{2C}",
1157        b"caron" | b"Hacek" => "\u{2C}",
1158        b"breve" | b"Breve" => "\u{2D}",
1159        b"dot" | b"DiacriticalDot" => "\u{2D}",
1160        b"ring" => "\u{2D}",
1161        b"ogon" => "\u{2D}",
1162        b"tilde" | b"DiacriticalTilde" => "\u{2D}",
1163        b"dblac" | b"DiacriticalDoubleAcute" => "\u{2D}",
1164        b"DownBreve" => "\u{31}",
1165        b"UnderBar" => "\u{33}",
1166        b"Alpha" => "\u{39}",
1167        b"Beta" => "\u{39}",
1168        b"Gamma" => "\u{39}",
1169        b"Delta" => "\u{39}",
1170        b"Epsilon" => "\u{39}",
1171        b"Zeta" => "\u{39}",
1172        b"Eta" => "\u{39}",
1173        b"Theta" => "\u{39}",
1174        b"Iota" => "\u{39}",
1175        b"Kappa" => "\u{39}",
1176        b"Lambda" => "\u{39}",
1177        b"Mu" => "\u{39}",
1178        b"Nu" => "\u{39}",
1179        b"Xi" => "\u{39}",
1180        b"Omicron" => "\u{39}",
1181        b"Pi" => "\u{3A}",
1182        b"Rho" => "\u{3A}",
1183        b"Sigma" => "\u{3A}",
1184        b"Tau" => "\u{3A}",
1185        b"Upsilon" => "\u{3A}",
1186        b"Phi" => "\u{3A}",
1187        b"Chi" => "\u{3A}",
1188        b"Psi" => "\u{3A}",
1189        b"Omega" => "\u{3A}",
1190        b"alpha" => "\u{3B}",
1191        b"beta" => "\u{3B}",
1192        b"gamma" => "\u{3B}",
1193        b"delta" => "\u{3B}",
1194        b"epsiv" | b"varepsilon" | b"epsilon" => "\u{3B}",
1195        b"zeta" => "\u{3B}",
1196        b"eta" => "\u{3B}",
1197        b"theta" => "\u{3B}",
1198        b"iota" => "\u{3B}",
1199        b"kappa" => "\u{3B}",
1200        b"lambda" => "\u{3B}",
1201        b"mu" => "\u{3B}",
1202        b"nu" => "\u{3B}",
1203        b"xi" => "\u{3B}",
1204        b"omicron" => "\u{3B}",
1205        b"pi" => "\u{3C}",
1206        b"rho" => "\u{3C}",
1207        b"sigmav" | b"varsigma" | b"sigmaf" => "\u{3C}",
1208        b"sigma" => "\u{3C}",
1209        b"tau" => "\u{3C}",
1210        b"upsi" | b"upsilon" => "\u{3C}",
1211        b"phi" | b"phiv" | b"varphi" => "\u{3C}",
1212        b"chi" => "\u{3C}",
1213        b"psi" => "\u{3C}",
1214        b"omega" => "\u{3C}",
1215        b"thetav" | b"vartheta" | b"thetasym" => "\u{3D}",
1216        b"Upsi" | b"upsih" => "\u{3D}",
1217        b"straightphi" => "\u{3D}",
1218        b"piv" | b"varpi" => "\u{3D}",
1219        b"Gammad" => "\u{3D}",
1220        b"gammad" | b"digamma" => "\u{3D}",
1221        b"kappav" | b"varkappa" => "\u{3F}",
1222        b"rhov" | b"varrho" => "\u{3F}",
1223        b"epsi" | b"straightepsilon" => "\u{3F}",
1224        b"bepsi" | b"backepsilon" => "\u{3F}",
1225        b"IOcy" => "\u{40}",
1226        b"DJcy" => "\u{40}",
1227        b"GJcy" => "\u{40}",
1228        b"Jukcy" => "\u{40}",
1229        b"DScy" => "\u{40}",
1230        b"Iukcy" => "\u{40}",
1231        b"YIcy" => "\u{40}",
1232        b"Jsercy" => "\u{40}",
1233        b"LJcy" => "\u{40}",
1234        b"NJcy" => "\u{40}",
1235        b"TSHcy" => "\u{40}",
1236        b"KJcy" => "\u{40}",
1237        b"Ubrcy" => "\u{40}",
1238        b"DZcy" => "\u{40}",
1239        b"Acy" => "\u{41}",
1240        b"Bcy" => "\u{41}",
1241        b"Vcy" => "\u{41}",
1242        b"Gcy" => "\u{41}",
1243        b"Dcy" => "\u{41}",
1244        b"IEcy" => "\u{41}",
1245        b"ZHcy" => "\u{41}",
1246        b"Zcy" => "\u{41}",
1247        b"Icy" => "\u{41}",
1248        b"Jcy" => "\u{41}",
1249        b"Kcy" => "\u{41}",
1250        b"Lcy" => "\u{41}",
1251        b"Mcy" => "\u{41}",
1252        b"Ncy" => "\u{41}",
1253        b"Ocy" => "\u{41}",
1254        b"Pcy" => "\u{41}",
1255        b"Rcy" => "\u{42}",
1256        b"Scy" => "\u{42}",
1257        b"Tcy" => "\u{42}",
1258        b"Ucy" => "\u{42}",
1259        b"Fcy" => "\u{42}",
1260        b"KHcy" => "\u{42}",
1261        b"TScy" => "\u{42}",
1262        b"CHcy" => "\u{42}",
1263        b"SHcy" => "\u{42}",
1264        b"SHCHcy" => "\u{42}",
1265        b"HARDcy" => "\u{42}",
1266        b"Ycy" => "\u{42}",
1267        b"SOFTcy" => "\u{42}",
1268        b"Ecy" => "\u{42}",
1269        b"YUcy" => "\u{42}",
1270        b"YAcy" => "\u{42}",
1271        b"acy" => "\u{43}",
1272        b"bcy" => "\u{43}",
1273        b"vcy" => "\u{43}",
1274        b"gcy" => "\u{43}",
1275        b"dcy" => "\u{43}",
1276        b"iecy" => "\u{43}",
1277        b"zhcy" => "\u{43}",
1278        b"zcy" => "\u{43}",
1279        b"icy" => "\u{43}",
1280        b"jcy" => "\u{43}",
1281        b"kcy" => "\u{43}",
1282        b"lcy" => "\u{43}",
1283        b"mcy" => "\u{43}",
1284        b"ncy" => "\u{43}",
1285        b"ocy" => "\u{43}",
1286        b"pcy" => "\u{43}",
1287        b"rcy" => "\u{44}",
1288        b"scy" => "\u{44}",
1289        b"tcy" => "\u{44}",
1290        b"ucy" => "\u{44}",
1291        b"fcy" => "\u{44}",
1292        b"khcy" => "\u{44}",
1293        b"tscy" => "\u{44}",
1294        b"chcy" => "\u{44}",
1295        b"shcy" => "\u{44}",
1296        b"shchcy" => "\u{44}",
1297        b"hardcy" => "\u{44}",
1298        b"ycy" => "\u{44}",
1299        b"softcy" => "\u{44}",
1300        b"ecy" => "\u{44}",
1301        b"yucy" => "\u{44}",
1302        b"yacy" => "\u{44}",
1303        b"iocy" => "\u{45}",
1304        b"djcy" => "\u{45}",
1305        b"gjcy" => "\u{45}",
1306        b"jukcy" => "\u{45}",
1307        b"dscy" => "\u{45}",
1308        b"iukcy" => "\u{45}",
1309        b"yicy" => "\u{45}",
1310        b"jsercy" => "\u{45}",
1311        b"ljcy" => "\u{45}",
1312        b"njcy" => "\u{45}",
1313        b"tshcy" => "\u{45}",
1314        b"kjcy" => "\u{45}",
1315        b"ubrcy" => "\u{45}",
1316        b"dzcy" => "\u{45}",
1317        b"ensp" => "\u{2002}",
1318        b"emsp" => "\u{2003}",
1319        b"emsp13" => "\u{2004}",
1320        b"emsp14" => "\u{2005}",
1321        b"numsp" => "\u{2007}",
1322        b"puncsp" => "\u{2008}",
1323        b"thinsp" | b"ThinSpace" => "\u{2009}",
1324        b"hairsp" | b"VeryThinSpace" => "\u{200A}",
1325        b"ZeroWidthSpace"
1326        | b"NegativeVeryThinSpace"
1327        | b"NegativeThinSpace"
1328        | b"NegativeMediumSpace"
1329        | b"NegativeThickSpace" => "\u{200B}",
1330        b"zwnj" => "\u{200C}",
1331        b"zwj" => "\u{200D}",
1332        b"lrm" => "\u{200E}",
1333        b"rlm" => "\u{200F}",
1334        b"hyphen" | b"dash" => "\u{2010}",
1335        b"ndash" => "\u{2013}",
1336        b"mdash" => "\u{2014}",
1337        b"horbar" => "\u{2015}",
1338        b"Verbar" | b"Vert" => "\u{2016}",
1339        b"lsquo" | b"OpenCurlyQuote" => "\u{2018}",
1340        b"rsquo" | b"rsquor" | b"CloseCurlyQuote" => "\u{2019}",
1341        b"lsquor" | b"sbquo" => "\u{201A}",
1342        b"ldquo" | b"OpenCurlyDoubleQuote" => "\u{201C}",
1343        b"rdquo" | b"rdquor" | b"CloseCurlyDoubleQuote" => "\u{201D}",
1344        b"ldquor" | b"bdquo" => "\u{201E}",
1345        b"dagger" => "\u{2020}",
1346        b"Dagger" | b"ddagger" => "\u{2021}",
1347        b"bull" | b"bullet" => "\u{2022}",
1348        b"nldr" => "\u{2025}",
1349        b"hellip" | b"mldr" => "\u{2026}",
1350        b"permil" => "\u{2030}",
1351        b"pertenk" => "\u{2031}",
1352        b"prime" => "\u{2032}",
1353        b"Prime" => "\u{2033}",
1354        b"tprime" => "\u{2034}",
1355        b"bprime" | b"backprime" => "\u{2035}",
1356        b"lsaquo" => "\u{2039}",
1357        b"rsaquo" => "\u{203A}",
1358        b"oline" => "\u{203E}",
1359        b"caret" => "\u{2041}",
1360        b"hybull" => "\u{2043}",
1361        b"frasl" => "\u{2044}",
1362        b"bsemi" => "\u{204F}",
1363        b"qprime" => "\u{2057}",
1364        b"MediumSpace" => "\u{205F}",
1365        b"NoBreak" => "\u{2060}",
1366        b"ApplyFunction" | b"af" => "\u{2061}",
1367        b"InvisibleTimes" | b"it" => "\u{2062}",
1368        b"InvisibleComma" | b"ic" => "\u{2063}",
1369        b"euro" => "\u{20AC}",
1370        b"tdot" | b"TripleDot" => "\u{20DB}",
1371        b"DotDot" => "\u{20DC}",
1372        b"Copf" | b"complexes" => "\u{2102}",
1373        b"incare" => "\u{2105}",
1374        b"gscr" => "\u{210A}",
1375        b"hamilt" | b"HilbertSpace" | b"Hscr" => "\u{210B}",
1376        b"Hfr" | b"Poincareplane" => "\u{210C}",
1377        b"quaternions" | b"Hopf" => "\u{210D}",
1378        b"planckh" => "\u{210E}",
1379        b"planck" | b"hbar" | b"plankv" | b"hslash" => "\u{210F}",
1380        b"Iscr" | b"imagline" => "\u{2110}",
1381        b"image" | b"Im" | b"imagpart" | b"Ifr" => "\u{2111}",
1382        b"Lscr" | b"lagran" | b"Laplacetrf" => "\u{2112}",
1383        b"ell" => "\u{2113}",
1384        b"Nopf" | b"naturals" => "\u{2115}",
1385        b"numero" => "\u{2116}",
1386        b"copysr" => "\u{2117}",
1387        b"weierp" | b"wp" => "\u{2118}",
1388        b"Popf" | b"primes" => "\u{2119}",
1389        b"rationals" | b"Qopf" => "\u{211A}",
1390        b"Rscr" | b"realine" => "\u{211B}",
1391        b"real" | b"Re" | b"realpart" | b"Rfr" => "\u{211C}",
1392        b"reals" | b"Ropf" => "\u{211D}",
1393        b"rx" => "\u{211E}",
1394        b"trade" | b"TRADE" => "\u{2122}",
1395        b"integers" | b"Zopf" => "\u{2124}",
1396        b"ohm" => "\u{2126}",
1397        b"mho" => "\u{2127}",
1398        b"Zfr" | b"zeetrf" => "\u{2128}",
1399        b"iiota" => "\u{2129}",
1400        b"angst" => "\u{212B}",
1401        b"bernou" | b"Bernoullis" | b"Bscr" => "\u{212C}",
1402        b"Cfr" | b"Cayleys" => "\u{212D}",
1403        b"escr" => "\u{212F}",
1404        b"Escr" | b"expectation" => "\u{2130}",
1405        b"Fscr" | b"Fouriertrf" => "\u{2131}",
1406        b"phmmat" | b"Mellintrf" | b"Mscr" => "\u{2133}",
1407        b"order" | b"orderof" | b"oscr" => "\u{2134}",
1408        b"alefsym" | b"aleph" => "\u{2135}",
1409        b"beth" => "\u{2136}",
1410        b"gimel" => "\u{2137}",
1411        b"daleth" => "\u{2138}",
1412        b"CapitalDifferentialD" | b"DD" => "\u{2145}",
1413        b"DifferentialD" | b"dd" => "\u{2146}",
1414        b"ExponentialE" | b"exponentiale" | b"ee" => "\u{2147}",
1415        b"ImaginaryI" | b"ii" => "\u{2148}",
1416        b"frac13" => "\u{2153}",
1417        b"frac23" => "\u{2154}",
1418        b"frac15" => "\u{2155}",
1419        b"frac25" => "\u{2156}",
1420        b"frac35" => "\u{2157}",
1421        b"frac45" => "\u{2158}",
1422        b"frac16" => "\u{2159}",
1423        b"frac56" => "\u{215A}",
1424        b"frac18" => "\u{215B}",
1425        b"frac38" => "\u{215C}",
1426        b"frac58" => "\u{215D}",
1427        b"frac78" => "\u{215E}",
1428        b"larr" | b"leftarrow" | b"LeftArrow" | b"slarr" | b"ShortLeftArrow" => "\u{2190}",
1429        b"uarr" | b"uparrow" | b"UpArrow" | b"ShortUpArrow" => "\u{2191}",
1430        b"rarr" | b"rightarrow" | b"RightArrow" | b"srarr" | b"ShortRightArrow" => "\u{2192}",
1431        b"darr" | b"downarrow" | b"DownArrow" | b"ShortDownArrow" => "\u{2193}",
1432        b"harr" | b"leftrightarrow" | b"LeftRightArrow" => "\u{2194}",
1433        b"varr" | b"updownarrow" | b"UpDownArrow" => "\u{2195}",
1434        b"nwarr" | b"UpperLeftArrow" | b"nwarrow" => "\u{2196}",
1435        b"nearr" | b"UpperRightArrow" | b"nearrow" => "\u{2197}",
1436        b"searr" | b"searrow" | b"LowerRightArrow" => "\u{2198}",
1437        b"swarr" | b"swarrow" | b"LowerLeftArrow" => "\u{2199}",
1438        b"nlarr" | b"nleftarrow" => "\u{219A}",
1439        b"nrarr" | b"nrightarrow" => "\u{219B}",
1440        b"rarrw" | b"rightsquigarrow" => "\u{219D}",
1441        b"Larr" | b"twoheadleftarrow" => "\u{219E}",
1442        b"Uarr" => "\u{219F}",
1443        b"Rarr" | b"twoheadrightarrow" => "\u{21A0}",
1444        b"Darr" => "\u{21A1}",
1445        b"larrtl" | b"leftarrowtail" => "\u{21A2}",
1446        b"rarrtl" | b"rightarrowtail" => "\u{21A3}",
1447        b"LeftTeeArrow" | b"mapstoleft" => "\u{21A4}",
1448        b"UpTeeArrow" | b"mapstoup" => "\u{21A5}",
1449        b"map" | b"RightTeeArrow" | b"mapsto" => "\u{21A6}",
1450        b"DownTeeArrow" | b"mapstodown" => "\u{21A7}",
1451        b"larrhk" | b"hookleftarrow" => "\u{21A9}",
1452        b"rarrhk" | b"hookrightarrow" => "\u{21AA}",
1453        b"larrlp" | b"looparrowleft" => "\u{21AB}",
1454        b"rarrlp" | b"looparrowright" => "\u{21AC}",
1455        b"harrw" | b"leftrightsquigarrow" => "\u{21AD}",
1456        b"nharr" | b"nleftrightarrow" => "\u{21AE}",
1457        b"lsh" | b"Lsh" => "\u{21B0}",
1458        b"rsh" | b"Rsh" => "\u{21B1}",
1459        b"ldsh" => "\u{21B2}",
1460        b"rdsh" => "\u{21B3}",
1461        b"crarr" => "\u{21B5}",
1462        b"cularr" | b"curvearrowleft" => "\u{21B6}",
1463        b"curarr" | b"curvearrowright" => "\u{21B7}",
1464        b"olarr" | b"circlearrowleft" => "\u{21BA}",
1465        b"orarr" | b"circlearrowright" => "\u{21BB}",
1466        b"lharu" | b"LeftVector" | b"leftharpoonup" => "\u{21BC}",
1467        b"lhard" | b"leftharpoondown" | b"DownLeftVector" => "\u{21BD}",
1468        b"uharr" | b"upharpoonright" | b"RightUpVector" => "\u{21BE}",
1469        b"uharl" | b"upharpoonleft" | b"LeftUpVector" => "\u{21BF}",
1470        b"rharu" | b"RightVector" | b"rightharpoonup" => "\u{21C0}",
1471        b"rhard" | b"rightharpoondown" | b"DownRightVector" => "\u{21C1}",
1472        b"dharr" | b"RightDownVector" | b"downharpoonright" => "\u{21C2}",
1473        b"dharl" | b"LeftDownVector" | b"downharpoonleft" => "\u{21C3}",
1474        b"rlarr" | b"rightleftarrows" | b"RightArrowLeftArrow" => "\u{21C4}",
1475        b"udarr" | b"UpArrowDownArrow" => "\u{21C5}",
1476        b"lrarr" | b"leftrightarrows" | b"LeftArrowRightArrow" => "\u{21C6}",
1477        b"llarr" | b"leftleftarrows" => "\u{21C7}",
1478        b"uuarr" | b"upuparrows" => "\u{21C8}",
1479        b"rrarr" | b"rightrightarrows" => "\u{21C9}",
1480        b"ddarr" | b"downdownarrows" => "\u{21CA}",
1481        b"lrhar" | b"ReverseEquilibrium" | b"leftrightharpoons" => "\u{21CB}",
1482        b"rlhar" | b"rightleftharpoons" | b"Equilibrium" => "\u{21CC}",
1483        b"nlArr" | b"nLeftarrow" => "\u{21CD}",
1484        b"nhArr" | b"nLeftrightarrow" => "\u{21CE}",
1485        b"nrArr" | b"nRightarrow" => "\u{21CF}",
1486        b"lArr" | b"Leftarrow" | b"DoubleLeftArrow" => "\u{21D0}",
1487        b"uArr" | b"Uparrow" | b"DoubleUpArrow" => "\u{21D1}",
1488        b"rArr" | b"Rightarrow" | b"Implies" | b"DoubleRightArrow" => "\u{21D2}",
1489        b"dArr" | b"Downarrow" | b"DoubleDownArrow" => "\u{21D3}",
1490        b"hArr" | b"Leftrightarrow" | b"DoubleLeftRightArrow" | b"iff" => "\u{21D4}",
1491        b"vArr" | b"Updownarrow" | b"DoubleUpDownArrow" => "\u{21D5}",
1492        b"nwArr" => "\u{21D6}",
1493        b"neArr" => "\u{21D7}",
1494        b"seArr" => "\u{21D8}",
1495        b"swArr" => "\u{21D9}",
1496        b"lAarr" | b"Lleftarrow" => "\u{21DA}",
1497        b"rAarr" | b"Rrightarrow" => "\u{21DB}",
1498        b"zigrarr" => "\u{21DD}",
1499        b"larrb" | b"LeftArrowBar" => "\u{21E4}",
1500        b"rarrb" | b"RightArrowBar" => "\u{21E5}",
1501        b"duarr" | b"DownArrowUpArrow" => "\u{21F5}",
1502        b"loarr" => "\u{21FD}",
1503        b"roarr" => "\u{21FE}",
1504        b"hoarr" => "\u{21FF}",
1505        b"forall" | b"ForAll" => "\u{2200}",
1506        b"comp" | b"complement" => "\u{2201}",
1507        b"part" | b"PartialD" => "\u{2202}",
1508        b"exist" | b"Exists" => "\u{2203}",
1509        b"nexist" | b"NotExists" | b"nexists" => "\u{2204}",
1510        b"empty" | b"emptyset" | b"emptyv" | b"varnothing" => "\u{2205}",
1511        b"nabla" | b"Del" => "\u{2207}",
1512        b"isin" | b"isinv" | b"Element" | b"in" => "\u{2208}",
1513        b"notin" | b"NotElement" | b"notinva" => "\u{2209}",
1514        b"niv" | b"ReverseElement" | b"ni" | b"SuchThat" => "\u{220B}",
1515        b"notni" | b"notniva" | b"NotReverseElement" => "\u{220C}",
1516        b"prod" | b"Product" => "\u{220F}",
1517        b"coprod" | b"Coproduct" => "\u{2210}",
1518        b"sum" | b"Sum" => "\u{2211}",
1519        b"minus" => "\u{2212}",
1520        b"mnplus" | b"mp" | b"MinusPlus" => "\u{2213}",
1521        b"plusdo" | b"dotplus" => "\u{2214}",
1522        b"setmn" | b"setminus" | b"Backslash" | b"ssetmn" | b"smallsetminus" => "\u{2216}",
1523        b"lowast" => "\u{2217}",
1524        b"compfn" | b"SmallCircle" => "\u{2218}",
1525        b"radic" | b"Sqrt" => "\u{221A}",
1526        b"prop" | b"propto" | b"Proportional" | b"vprop" | b"varpropto" => "\u{221D}",
1527        b"infin" => "\u{221E}",
1528        b"angrt" => "\u{221F}",
1529        b"ang" | b"angle" => "\u{2220}",
1530        b"angmsd" | b"measuredangle" => "\u{2221}",
1531        b"angsph" => "\u{2222}",
1532        b"mid" | b"VerticalBar" | b"smid" | b"shortmid" => "\u{2223}",
1533        b"nmid" | b"NotVerticalBar" | b"nsmid" | b"nshortmid" => "\u{2224}",
1534        b"par" | b"parallel" | b"DoubleVerticalBar" | b"spar" | b"shortparallel" => "\u{2225}",
1535        b"npar" | b"nparallel" | b"NotDoubleVerticalBar" | b"nspar" | b"nshortparallel" => {
1536            "\u{2226}"
1537        }
1538        b"and" | b"wedge" => "\u{2227}",
1539        b"or" | b"vee" => "\u{2228}",
1540        b"cap" => "\u{2229}",
1541        b"cup" => "\u{222A}",
1542        b"int" | b"Integral" => "\u{222B}",
1543        b"Int" => "\u{222C}",
1544        b"tint" | b"iiint" => "\u{222D}",
1545        b"conint" | b"oint" | b"ContourIntegral" => "\u{222E}",
1546        b"Conint" | b"DoubleContourIntegral" => "\u{222F}",
1547        b"Cconint" => "\u{2230}",
1548        b"cwint" => "\u{2231}",
1549        b"cwconint" | b"ClockwiseContourIntegral" => "\u{2232}",
1550        b"awconint" | b"CounterClockwiseContourIntegral" => "\u{2233}",
1551        b"there4" | b"therefore" | b"Therefore" => "\u{2234}",
1552        b"becaus" | b"because" | b"Because" => "\u{2235}",
1553        b"ratio" => "\u{2236}",
1554        b"Colon" | b"Proportion" => "\u{2237}",
1555        b"minusd" | b"dotminus" => "\u{2238}",
1556        b"mDDot" => "\u{223A}",
1557        b"homtht" => "\u{223B}",
1558        b"sim" | b"Tilde" | b"thksim" | b"thicksim" => "\u{223C}",
1559        b"bsim" | b"backsim" => "\u{223D}",
1560        b"ac" | b"mstpos" => "\u{223E}",
1561        b"acd" => "\u{223F}",
1562        b"wreath" | b"VerticalTilde" | b"wr" => "\u{2240}",
1563        b"nsim" | b"NotTilde" => "\u{2241}",
1564        b"esim" | b"EqualTilde" | b"eqsim" => "\u{2242}",
1565        b"sime" | b"TildeEqual" | b"simeq" => "\u{2243}",
1566        b"nsime" | b"nsimeq" | b"NotTildeEqual" => "\u{2244}",
1567        b"cong" | b"TildeFullEqual" => "\u{2245}",
1568        b"simne" => "\u{2246}",
1569        b"ncong" | b"NotTildeFullEqual" => "\u{2247}",
1570        b"asymp" | b"ap" | b"TildeTilde" | b"approx" | b"thkap" | b"thickapprox" => "\u{2248}",
1571        b"nap" | b"NotTildeTilde" | b"napprox" => "\u{2249}",
1572        b"ape" | b"approxeq" => "\u{224A}",
1573        b"apid" => "\u{224B}",
1574        b"bcong" | b"backcong" => "\u{224C}",
1575        b"asympeq" | b"CupCap" => "\u{224D}",
1576        b"bump" | b"HumpDownHump" | b"Bumpeq" => "\u{224E}",
1577        b"bumpe" | b"HumpEqual" | b"bumpeq" => "\u{224F}",
1578        b"esdot" | b"DotEqual" | b"doteq" => "\u{2250}",
1579        b"eDot" | b"doteqdot" => "\u{2251}",
1580        b"efDot" | b"fallingdotseq" => "\u{2252}",
1581        b"erDot" | b"risingdotseq" => "\u{2253}",
1582        b"colone" | b"coloneq" | b"Assign" => "\u{2254}",
1583        b"ecolon" | b"eqcolon" => "\u{2255}",
1584        b"ecir" | b"eqcirc" => "\u{2256}",
1585        b"cire" | b"circeq" => "\u{2257}",
1586        b"wedgeq" => "\u{2259}",
1587        b"veeeq" => "\u{225A}",
1588        b"trie" | b"triangleq" => "\u{225C}",
1589        b"equest" | b"questeq" => "\u{225F}",
1590        b"ne" | b"NotEqual" => "\u{2260}",
1591        b"equiv" | b"Congruent" => "\u{2261}",
1592        b"nequiv" | b"NotCongruent" => "\u{2262}",
1593        b"le" | b"leq" => "\u{2264}",
1594        b"ge" | b"GreaterEqual" | b"geq" => "\u{2265}",
1595        b"lE" | b"LessFullEqual" | b"leqq" => "\u{2266}",
1596        b"gE" | b"GreaterFullEqual" | b"geqq" => "\u{2267}",
1597        b"lnE" | b"lneqq" => "\u{2268}",
1598        b"gnE" | b"gneqq" => "\u{2269}",
1599        b"Lt" | b"NestedLessLess" | b"ll" => "\u{226A}",
1600        b"Gt" | b"NestedGreaterGreater" | b"gg" => "\u{226B}",
1601        b"twixt" | b"between" => "\u{226C}",
1602        b"NotCupCap" => "\u{226D}",
1603        b"nlt" | b"NotLess" | b"nless" => "\u{226E}",
1604        b"ngt" | b"NotGreater" | b"ngtr" => "\u{226F}",
1605        b"nle" | b"NotLessEqual" | b"nleq" => "\u{2270}",
1606        b"nge" | b"NotGreaterEqual" | b"ngeq" => "\u{2271}",
1607        b"lsim" | b"LessTilde" | b"lesssim" => "\u{2272}",
1608        b"gsim" | b"gtrsim" | b"GreaterTilde" => "\u{2273}",
1609        b"nlsim" | b"NotLessTilde" => "\u{2274}",
1610        b"ngsim" | b"NotGreaterTilde" => "\u{2275}",
1611        b"lg" | b"lessgtr" | b"LessGreater" => "\u{2276}",
1612        b"gl" | b"gtrless" | b"GreaterLess" => "\u{2277}",
1613        b"ntlg" | b"NotLessGreater" => "\u{2278}",
1614        b"ntgl" | b"NotGreaterLess" => "\u{2279}",
1615        b"pr" | b"Precedes" | b"prec" => "\u{227A}",
1616        b"sc" | b"Succeeds" | b"succ" => "\u{227B}",
1617        b"prcue" | b"PrecedesSlantEqual" | b"preccurlyeq" => "\u{227C}",
1618        b"sccue" | b"SucceedsSlantEqual" | b"succcurlyeq" => "\u{227D}",
1619        b"prsim" | b"precsim" | b"PrecedesTilde" => "\u{227E}",
1620        b"scsim" | b"succsim" | b"SucceedsTilde" => "\u{227F}",
1621        b"npr" | b"nprec" | b"NotPrecedes" => "\u{2280}",
1622        b"nsc" | b"nsucc" | b"NotSucceeds" => "\u{2281}",
1623        b"sub" | b"subset" => "\u{2282}",
1624        b"sup" | b"supset" | b"Superset" => "\u{2283}",
1625        b"nsub" => "\u{2284}",
1626        b"nsup" => "\u{2285}",
1627        b"sube" | b"SubsetEqual" | b"subseteq" => "\u{2286}",
1628        b"supe" | b"supseteq" | b"SupersetEqual" => "\u{2287}",
1629        b"nsube" | b"nsubseteq" | b"NotSubsetEqual" => "\u{2288}",
1630        b"nsupe" | b"nsupseteq" | b"NotSupersetEqual" => "\u{2289}",
1631        b"subne" | b"subsetneq" => "\u{228A}",
1632        b"supne" | b"supsetneq" => "\u{228B}",
1633        b"cupdot" => "\u{228D}",
1634        b"uplus" | b"UnionPlus" => "\u{228E}",
1635        b"sqsub" | b"SquareSubset" | b"sqsubset" => "\u{228F}",
1636        b"sqsup" | b"SquareSuperset" | b"sqsupset" => "\u{2290}",
1637        b"sqsube" | b"SquareSubsetEqual" | b"sqsubseteq" => "\u{2291}",
1638        b"sqsupe" | b"SquareSupersetEqual" | b"sqsupseteq" => "\u{2292}",
1639        b"sqcap" | b"SquareIntersection" => "\u{2293}",
1640        b"sqcup" | b"SquareUnion" => "\u{2294}",
1641        b"oplus" | b"CirclePlus" => "\u{2295}",
1642        b"ominus" | b"CircleMinus" => "\u{2296}",
1643        b"otimes" | b"CircleTimes" => "\u{2297}",
1644        b"osol" => "\u{2298}",
1645        b"odot" | b"CircleDot" => "\u{2299}",
1646        b"ocir" | b"circledcirc" => "\u{229A}",
1647        b"oast" | b"circledast" => "\u{229B}",
1648        b"odash" | b"circleddash" => "\u{229D}",
1649        b"plusb" | b"boxplus" => "\u{229E}",
1650        b"minusb" | b"boxminus" => "\u{229F}",
1651        b"timesb" | b"boxtimes" => "\u{22A0}",
1652        b"sdotb" | b"dotsquare" => "\u{22A1}",
1653        b"vdash" | b"RightTee" => "\u{22A2}",
1654        b"dashv" | b"LeftTee" => "\u{22A3}",
1655        b"top" | b"DownTee" => "\u{22A4}",
1656        b"bottom" | b"bot" | b"perp" | b"UpTee" => "\u{22A5}",
1657        b"models" => "\u{22A7}",
1658        b"vDash" | b"DoubleRightTee" => "\u{22A8}",
1659        b"Vdash" => "\u{22A9}",
1660        b"Vvdash" => "\u{22AA}",
1661        b"VDash" => "\u{22AB}",
1662        b"nvdash" => "\u{22AC}",
1663        b"nvDash" => "\u{22AD}",
1664        b"nVdash" => "\u{22AE}",
1665        b"nVDash" => "\u{22AF}",
1666        b"prurel" => "\u{22B0}",
1667        b"vltri" | b"vartriangleleft" | b"LeftTriangle" => "\u{22B2}",
1668        b"vrtri" | b"vartriangleright" | b"RightTriangle" => "\u{22B3}",
1669        b"ltrie" | b"trianglelefteq" | b"LeftTriangleEqual" => "\u{22B4}",
1670        b"rtrie" | b"trianglerighteq" | b"RightTriangleEqual" => "\u{22B5}",
1671        b"origof" => "\u{22B6}",
1672        b"imof" => "\u{22B7}",
1673        b"mumap" | b"multimap" => "\u{22B8}",
1674        b"hercon" => "\u{22B9}",
1675        b"intcal" | b"intercal" => "\u{22BA}",
1676        b"veebar" => "\u{22BB}",
1677        b"barvee" => "\u{22BD}",
1678        b"angrtvb" => "\u{22BE}",
1679        b"lrtri" => "\u{22BF}",
1680        b"xwedge" | b"Wedge" | b"bigwedge" => "\u{22C0}",
1681        b"xvee" | b"Vee" | b"bigvee" => "\u{22C1}",
1682        b"xcap" | b"Intersection" | b"bigcap" => "\u{22C2}",
1683        b"xcup" | b"Union" | b"bigcup" => "\u{22C3}",
1684        b"diam" | b"diamond" | b"Diamond" => "\u{22C4}",
1685        b"sdot" => "\u{22C5}",
1686        b"sstarf" | b"Star" => "\u{22C6}",
1687        b"divonx" | b"divideontimes" => "\u{22C7}",
1688        b"bowtie" => "\u{22C8}",
1689        b"ltimes" => "\u{22C9}",
1690        b"rtimes" => "\u{22CA}",
1691        b"lthree" | b"leftthreetimes" => "\u{22CB}",
1692        b"rthree" | b"rightthreetimes" => "\u{22CC}",
1693        b"bsime" | b"backsimeq" => "\u{22CD}",
1694        b"cuvee" | b"curlyvee" => "\u{22CE}",
1695        b"cuwed" | b"curlywedge" => "\u{22CF}",
1696        b"Sub" | b"Subset" => "\u{22D0}",
1697        b"Sup" | b"Supset" => "\u{22D1}",
1698        b"Cap" => "\u{22D2}",
1699        b"Cup" => "\u{22D3}",
1700        b"fork" | b"pitchfork" => "\u{22D4}",
1701        b"epar" => "\u{22D5}",
1702        b"ltdot" | b"lessdot" => "\u{22D6}",
1703        b"gtdot" | b"gtrdot" => "\u{22D7}",
1704        b"Ll" => "\u{22D8}",
1705        b"Gg" | b"ggg" => "\u{22D9}",
1706        b"leg" | b"LessEqualGreater" | b"lesseqgtr" => "\u{22DA}",
1707        b"gel" | b"gtreqless" | b"GreaterEqualLess" => "\u{22DB}",
1708        b"cuepr" | b"curlyeqprec" => "\u{22DE}",
1709        b"cuesc" | b"curlyeqsucc" => "\u{22DF}",
1710        b"nprcue" | b"NotPrecedesSlantEqual" => "\u{22E0}",
1711        b"nsccue" | b"NotSucceedsSlantEqual" => "\u{22E1}",
1712        b"nsqsube" | b"NotSquareSubsetEqual" => "\u{22E2}",
1713        b"nsqsupe" | b"NotSquareSupersetEqual" => "\u{22E3}",
1714        b"lnsim" => "\u{22E6}",
1715        b"gnsim" => "\u{22E7}",
1716        b"prnsim" | b"precnsim" => "\u{22E8}",
1717        b"scnsim" | b"succnsim" => "\u{22E9}",
1718        b"nltri" | b"ntriangleleft" | b"NotLeftTriangle" => "\u{22EA}",
1719        b"nrtri" | b"ntriangleright" | b"NotRightTriangle" => "\u{22EB}",
1720        b"nltrie" | b"ntrianglelefteq" | b"NotLeftTriangleEqual" => "\u{22EC}",
1721        b"nrtrie" | b"ntrianglerighteq" | b"NotRightTriangleEqual" => "\u{22ED}",
1722        b"vellip" => "\u{22EE}",
1723        b"ctdot" => "\u{22EF}",
1724        b"utdot" => "\u{22F0}",
1725        b"dtdot" => "\u{22F1}",
1726        b"disin" => "\u{22F2}",
1727        b"isinsv" => "\u{22F3}",
1728        b"isins" => "\u{22F4}",
1729        b"isindot" => "\u{22F5}",
1730        b"notinvc" => "\u{22F6}",
1731        b"notinvb" => "\u{22F7}",
1732        b"isinE" => "\u{22F9}",
1733        b"nisd" => "\u{22FA}",
1734        b"xnis" => "\u{22FB}",
1735        b"nis" => "\u{22FC}",
1736        b"notnivc" => "\u{22FD}",
1737        b"notnivb" => "\u{22FE}",
1738        b"barwed" | b"barwedge" => "\u{2305}",
1739        b"Barwed" | b"doublebarwedge" => "\u{2306}",
1740        b"lceil" | b"LeftCeiling" => "\u{2308}",
1741        b"rceil" | b"RightCeiling" => "\u{2309}",
1742        b"lfloor" | b"LeftFloor" => "\u{230A}",
1743        b"rfloor" | b"RightFloor" => "\u{230B}",
1744        b"drcrop" => "\u{230C}",
1745        b"dlcrop" => "\u{230D}",
1746        b"urcrop" => "\u{230E}",
1747        b"ulcrop" => "\u{230F}",
1748        b"bnot" => "\u{2310}",
1749        b"profline" => "\u{2312}",
1750        b"profsurf" => "\u{2313}",
1751        b"telrec" => "\u{2315}",
1752        b"target" => "\u{2316}",
1753        b"ulcorn" | b"ulcorner" => "\u{231C}",
1754        b"urcorn" | b"urcorner" => "\u{231D}",
1755        b"dlcorn" | b"llcorner" => "\u{231E}",
1756        b"drcorn" | b"lrcorner" => "\u{231F}",
1757        b"frown" | b"sfrown" => "\u{2322}",
1758        b"smile" | b"ssmile" => "\u{2323}",
1759        b"cylcty" => "\u{232D}",
1760        b"profalar" => "\u{232E}",
1761        b"topbot" => "\u{2336}",
1762        b"ovbar" => "\u{233D}",
1763        b"solbar" => "\u{233F}",
1764        b"angzarr" => "\u{237C}",
1765        b"lmoust" | b"lmoustache" => "\u{23B0}",
1766        b"rmoust" | b"rmoustache" => "\u{23B1}",
1767        b"tbrk" | b"OverBracket" => "\u{23B4}",
1768        b"bbrk" | b"UnderBracket" => "\u{23B5}",
1769        b"bbrktbrk" => "\u{23B6}",
1770        b"OverParenthesis" => "\u{23DC}",
1771        b"UnderParenthesis" => "\u{23DD}",
1772        b"OverBrace" => "\u{23DE}",
1773        b"UnderBrace" => "\u{23DF}",
1774        b"trpezium" => "\u{23E2}",
1775        b"elinters" => "\u{23E7}",
1776        b"blank" => "\u{2423}",
1777        b"oS" | b"circledS" => "\u{24C8}",
1778        b"boxh" | b"HorizontalLine" => "\u{2500}",
1779        b"boxv" => "\u{2502}",
1780        b"boxdr" => "\u{250C}",
1781        b"boxdl" => "\u{2510}",
1782        b"boxur" => "\u{2514}",
1783        b"boxul" => "\u{2518}",
1784        b"boxvr" => "\u{251C}",
1785        b"boxvl" => "\u{2524}",
1786        b"boxhd" => "\u{252C}",
1787        b"boxhu" => "\u{2534}",
1788        b"boxvh" => "\u{253C}",
1789        b"boxH" => "\u{2550}",
1790        b"boxV" => "\u{2551}",
1791        b"boxdR" => "\u{2552}",
1792        b"boxDr" => "\u{2553}",
1793        b"boxDR" => "\u{2554}",
1794        b"boxdL" => "\u{2555}",
1795        b"boxDl" => "\u{2556}",
1796        b"boxDL" => "\u{2557}",
1797        b"boxuR" => "\u{2558}",
1798        b"boxUr" => "\u{2559}",
1799        b"boxUR" => "\u{255A}",
1800        b"boxuL" => "\u{255B}",
1801        b"boxUl" => "\u{255C}",
1802        b"boxUL" => "\u{255D}",
1803        b"boxvR" => "\u{255E}",
1804        b"boxVr" => "\u{255F}",
1805        b"boxVR" => "\u{2560}",
1806        b"boxvL" => "\u{2561}",
1807        b"boxVl" => "\u{2562}",
1808        b"boxVL" => "\u{2563}",
1809        b"boxHd" => "\u{2564}",
1810        b"boxhD" => "\u{2565}",
1811        b"boxHD" => "\u{2566}",
1812        b"boxHu" => "\u{2567}",
1813        b"boxhU" => "\u{2568}",
1814        b"boxHU" => "\u{2569}",
1815        b"boxvH" => "\u{256A}",
1816        b"boxVh" => "\u{256B}",
1817        b"boxVH" => "\u{256C}",
1818        b"uhblk" => "\u{2580}",
1819        b"lhblk" => "\u{2584}",
1820        b"block" => "\u{2588}",
1821        b"blk14" => "\u{2591}",
1822        b"blk12" => "\u{2592}",
1823        b"blk34" => "\u{2593}",
1824        b"squ" | b"square" | b"Square" => "\u{25A1}",
1825        b"squf" | b"squarf" | b"blacksquare" | b"FilledVerySmallSquare" => "\u{25AA}",
1826        b"EmptyVerySmallSquare" => "\u{25AB}",
1827        b"rect" => "\u{25AD}",
1828        b"marker" => "\u{25AE}",
1829        b"fltns" => "\u{25B1}",
1830        b"xutri" | b"bigtriangleup" => "\u{25B3}",
1831        b"utrif" | b"blacktriangle" => "\u{25B4}",
1832        b"utri" | b"triangle" => "\u{25B5}",
1833        b"rtrif" | b"blacktriangleright" => "\u{25B8}",
1834        b"rtri" | b"triangleright" => "\u{25B9}",
1835        b"xdtri" | b"bigtriangledown" => "\u{25BD}",
1836        b"dtrif" | b"blacktriangledown" => "\u{25BE}",
1837        b"dtri" | b"triangledown" => "\u{25BF}",
1838        b"ltrif" | b"blacktriangleleft" => "\u{25C2}",
1839        b"ltri" | b"triangleleft" => "\u{25C3}",
1840        b"loz" | b"lozenge" => "\u{25CA}",
1841        b"cir" => "\u{25CB}",
1842        b"tridot" => "\u{25EC}",
1843        b"xcirc" | b"bigcirc" => "\u{25EF}",
1844        b"ultri" => "\u{25F8}",
1845        b"urtri" => "\u{25F9}",
1846        b"lltri" => "\u{25FA}",
1847        b"EmptySmallSquare" => "\u{25FB}",
1848        b"FilledSmallSquare" => "\u{25FC}",
1849        b"starf" | b"bigstar" => "\u{2605}",
1850        b"star" => "\u{2606}",
1851        b"phone" => "\u{260E}",
1852        b"female" => "\u{2640}",
1853        b"male" => "\u{2642}",
1854        b"spades" | b"spadesuit" => "\u{2660}",
1855        b"clubs" | b"clubsuit" => "\u{2663}",
1856        b"hearts" | b"heartsuit" => "\u{2665}",
1857        b"diams" | b"diamondsuit" => "\u{2666}",
1858        b"sung" => "\u{266A}",
1859        b"flat" => "\u{266D}",
1860        b"natur" | b"natural" => "\u{266E}",
1861        b"sharp" => "\u{266F}",
1862        b"check" | b"checkmark" => "\u{2713}",
1863        b"cross" => "\u{2717}",
1864        b"malt" | b"maltese" => "\u{2720}",
1865        b"sext" => "\u{2736}",
1866        b"VerticalSeparator" => "\u{2758}",
1867        b"lbbrk" => "\u{2772}",
1868        b"rbbrk" => "\u{2773}",
1869        b"lobrk" | b"LeftDoubleBracket" => "\u{27E6}",
1870        b"robrk" | b"RightDoubleBracket" => "\u{27E7}",
1871        b"lang" | b"LeftAngleBracket" | b"langle" => "\u{27E8}",
1872        b"rang" | b"RightAngleBracket" | b"rangle" => "\u{27E9}",
1873        b"Lang" => "\u{27EA}",
1874        b"Rang" => "\u{27EB}",
1875        b"loang" => "\u{27EC}",
1876        b"roang" => "\u{27ED}",
1877        b"xlarr" | b"longleftarrow" | b"LongLeftArrow" => "\u{27F5}",
1878        b"xrarr" | b"longrightarrow" | b"LongRightArrow" => "\u{27F6}",
1879        b"xharr" | b"longleftrightarrow" | b"LongLeftRightArrow" => "\u{27F7}",
1880        b"xlArr" | b"Longleftarrow" | b"DoubleLongLeftArrow" => "\u{27F8}",
1881        b"xrArr" | b"Longrightarrow" | b"DoubleLongRightArrow" => "\u{27F9}",
1882        b"xhArr" | b"Longleftrightarrow" | b"DoubleLongLeftRightArrow" => "\u{27FA}",
1883        b"xmap" | b"longmapsto" => "\u{27FC}",
1884        b"dzigrarr" => "\u{27FF}",
1885        b"nvlArr" => "\u{2902}",
1886        b"nvrArr" => "\u{2903}",
1887        b"nvHarr" => "\u{2904}",
1888        b"Map" => "\u{2905}",
1889        b"lbarr" => "\u{290C}",
1890        b"rbarr" | b"bkarow" => "\u{290D}",
1891        b"lBarr" => "\u{290E}",
1892        b"rBarr" | b"dbkarow" => "\u{290F}",
1893        b"RBarr" | b"drbkarow" => "\u{2910}",
1894        b"DDotrahd" => "\u{2911}",
1895        b"UpArrowBar" => "\u{2912}",
1896        b"DownArrowBar" => "\u{2913}",
1897        b"Rarrtl" => "\u{2916}",
1898        b"latail" => "\u{2919}",
1899        b"ratail" => "\u{291A}",
1900        b"lAtail" => "\u{291B}",
1901        b"rAtail" => "\u{291C}",
1902        b"larrfs" => "\u{291D}",
1903        b"rarrfs" => "\u{291E}",
1904        b"larrbfs" => "\u{291F}",
1905        b"rarrbfs" => "\u{2920}",
1906        b"nwarhk" => "\u{2923}",
1907        b"nearhk" => "\u{2924}",
1908        b"searhk" | b"hksearow" => "\u{2925}",
1909        b"swarhk" | b"hkswarow" => "\u{2926}",
1910        b"nwnear" => "\u{2927}",
1911        b"nesear" | b"toea" => "\u{2928}",
1912        b"seswar" | b"tosa" => "\u{2929}",
1913        b"swnwar" => "\u{292A}",
1914        b"rarrc" => "\u{2933}",
1915        b"cudarrr" => "\u{2935}",
1916        b"ldca" => "\u{2936}",
1917        b"rdca" => "\u{2937}",
1918        b"cudarrl" => "\u{2938}",
1919        b"larrpl" => "\u{2939}",
1920        b"curarrm" => "\u{293C}",
1921        b"cularrp" => "\u{293D}",
1922        b"rarrpl" => "\u{2945}",
1923        b"harrcir" => "\u{2948}",
1924        b"Uarrocir" => "\u{2949}",
1925        b"lurdshar" => "\u{294A}",
1926        b"ldrushar" => "\u{294B}",
1927        b"LeftRightVector" => "\u{294E}",
1928        b"RightUpDownVector" => "\u{294F}",
1929        b"DownLeftRightVector" => "\u{2950}",
1930        b"LeftUpDownVector" => "\u{2951}",
1931        b"LeftVectorBar" => "\u{2952}",
1932        b"RightVectorBar" => "\u{2953}",
1933        b"RightUpVectorBar" => "\u{2954}",
1934        b"RightDownVectorBar" => "\u{2955}",
1935        b"DownLeftVectorBar" => "\u{2956}",
1936        b"DownRightVectorBar" => "\u{2957}",
1937        b"LeftUpVectorBar" => "\u{2958}",
1938        b"LeftDownVectorBar" => "\u{2959}",
1939        b"LeftTeeVector" => "\u{295A}",
1940        b"RightTeeVector" => "\u{295B}",
1941        b"RightUpTeeVector" => "\u{295C}",
1942        b"RightDownTeeVector" => "\u{295D}",
1943        b"DownLeftTeeVector" => "\u{295E}",
1944        b"DownRightTeeVector" => "\u{295F}",
1945        b"LeftUpTeeVector" => "\u{2960}",
1946        b"LeftDownTeeVector" => "\u{2961}",
1947        b"lHar" => "\u{2962}",
1948        b"uHar" => "\u{2963}",
1949        b"rHar" => "\u{2964}",
1950        b"dHar" => "\u{2965}",
1951        b"luruhar" => "\u{2966}",
1952        b"ldrdhar" => "\u{2967}",
1953        b"ruluhar" => "\u{2968}",
1954        b"rdldhar" => "\u{2969}",
1955        b"lharul" => "\u{296A}",
1956        b"llhard" => "\u{296B}",
1957        b"rharul" => "\u{296C}",
1958        b"lrhard" => "\u{296D}",
1959        b"udhar" | b"UpEquilibrium" => "\u{296E}",
1960        b"duhar" | b"ReverseUpEquilibrium" => "\u{296F}",
1961        b"RoundImplies" => "\u{2970}",
1962        b"erarr" => "\u{2971}",
1963        b"simrarr" => "\u{2972}",
1964        b"larrsim" => "\u{2973}",
1965        b"rarrsim" => "\u{2974}",
1966        b"rarrap" => "\u{2975}",
1967        b"ltlarr" => "\u{2976}",
1968        b"gtrarr" => "\u{2978}",
1969        b"subrarr" => "\u{2979}",
1970        b"suplarr" => "\u{297B}",
1971        b"lfisht" => "\u{297C}",
1972        b"rfisht" => "\u{297D}",
1973        b"ufisht" => "\u{297E}",
1974        b"dfisht" => "\u{297F}",
1975        b"lopar" => "\u{2985}",
1976        b"ropar" => "\u{2986}",
1977        b"lbrke" => "\u{298B}",
1978        b"rbrke" => "\u{298C}",
1979        b"lbrkslu" => "\u{298D}",
1980        b"rbrksld" => "\u{298E}",
1981        b"lbrksld" => "\u{298F}",
1982        b"rbrkslu" => "\u{2990}",
1983        b"langd" => "\u{2991}",
1984        b"rangd" => "\u{2992}",
1985        b"lparlt" => "\u{2993}",
1986        b"rpargt" => "\u{2994}",
1987        b"gtlPar" => "\u{2995}",
1988        b"ltrPar" => "\u{2996}",
1989        b"vzigzag" => "\u{299A}",
1990        b"vangrt" => "\u{299C}",
1991        b"angrtvbd" => "\u{299D}",
1992        b"ange" => "\u{29A4}",
1993        b"range" => "\u{29A5}",
1994        b"dwangle" => "\u{29A6}",
1995        b"uwangle" => "\u{29A7}",
1996        b"angmsdaa" => "\u{29A8}",
1997        b"angmsdab" => "\u{29A9}",
1998        b"angmsdac" => "\u{29AA}",
1999        b"angmsdad" => "\u{29AB}",
2000        b"angmsdae" => "\u{29AC}",
2001        b"angmsdaf" => "\u{29AD}",
2002        b"angmsdag" => "\u{29AE}",
2003        b"angmsdah" => "\u{29AF}",
2004        b"bemptyv" => "\u{29B0}",
2005        b"demptyv" => "\u{29B1}",
2006        b"cemptyv" => "\u{29B2}",
2007        b"raemptyv" => "\u{29B3}",
2008        b"laemptyv" => "\u{29B4}",
2009        b"ohbar" => "\u{29B5}",
2010        b"omid" => "\u{29B6}",
2011        b"opar" => "\u{29B7}",
2012        b"operp" => "\u{29B9}",
2013        b"olcross" => "\u{29BB}",
2014        b"odsold" => "\u{29BC}",
2015        b"olcir" => "\u{29BE}",
2016        b"ofcir" => "\u{29BF}",
2017        b"olt" => "\u{29C0}",
2018        b"ogt" => "\u{29C1}",
2019        b"cirscir" => "\u{29C2}",
2020        b"cirE" => "\u{29C3}",
2021        b"solb" => "\u{29C4}",
2022        b"bsolb" => "\u{29C5}",
2023        b"boxbox" => "\u{29C9}",
2024        b"trisb" => "\u{29CD}",
2025        b"rtriltri" => "\u{29CE}",
2026        b"LeftTriangleBar" => "\u{29CF}",
2027        b"RightTriangleBar" => "\u{29D0}",
2028        b"race" => "\u{29DA}",
2029        b"iinfin" => "\u{29DC}",
2030        b"infintie" => "\u{29DD}",
2031        b"nvinfin" => "\u{29DE}",
2032        b"eparsl" => "\u{29E3}",
2033        b"smeparsl" => "\u{29E4}",
2034        b"eqvparsl" => "\u{29E5}",
2035        b"lozf" | b"blacklozenge" => "\u{29EB}",
2036        b"RuleDelayed" => "\u{29F4}",
2037        b"dsol" => "\u{29F6}",
2038        b"xodot" | b"bigodot" => "\u{2A00}",
2039        b"xoplus" | b"bigoplus" => "\u{2A01}",
2040        b"xotime" | b"bigotimes" => "\u{2A02}",
2041        b"xuplus" | b"biguplus" => "\u{2A04}",
2042        b"xsqcup" | b"bigsqcup" => "\u{2A06}",
2043        b"qint" | b"iiiint" => "\u{2A0C}",
2044        b"fpartint" => "\u{2A0D}",
2045        b"cirfnint" => "\u{2A10}",
2046        b"awint" => "\u{2A11}",
2047        b"rppolint" => "\u{2A12}",
2048        b"scpolint" => "\u{2A13}",
2049        b"npolint" => "\u{2A14}",
2050        b"pointint" => "\u{2A15}",
2051        b"quatint" => "\u{2A16}",
2052        b"intlarhk" => "\u{2A17}",
2053        b"pluscir" => "\u{2A22}",
2054        b"plusacir" => "\u{2A23}",
2055        b"simplus" => "\u{2A24}",
2056        b"plusdu" => "\u{2A25}",
2057        b"plussim" => "\u{2A26}",
2058        b"plustwo" => "\u{2A27}",
2059        b"mcomma" => "\u{2A29}",
2060        b"minusdu" => "\u{2A2A}",
2061        b"loplus" => "\u{2A2D}",
2062        b"roplus" => "\u{2A2E}",
2063        b"Cross" => "\u{2A2F}",
2064        b"timesd" => "\u{2A30}",
2065        b"timesbar" => "\u{2A31}",
2066        b"smashp" => "\u{2A33}",
2067        b"lotimes" => "\u{2A34}",
2068        b"rotimes" => "\u{2A35}",
2069        b"otimesas" => "\u{2A36}",
2070        b"Otimes" => "\u{2A37}",
2071        b"odiv" => "\u{2A38}",
2072        b"triplus" => "\u{2A39}",
2073        b"triminus" => "\u{2A3A}",
2074        b"tritime" => "\u{2A3B}",
2075        b"iprod" | b"intprod" => "\u{2A3C}",
2076        b"amalg" => "\u{2A3F}",
2077        b"capdot" => "\u{2A40}",
2078        b"ncup" => "\u{2A42}",
2079        b"ncap" => "\u{2A43}",
2080        b"capand" => "\u{2A44}",
2081        b"cupor" => "\u{2A45}",
2082        b"cupcap" => "\u{2A46}",
2083        b"capcup" => "\u{2A47}",
2084        b"cupbrcap" => "\u{2A48}",
2085        b"capbrcup" => "\u{2A49}",
2086        b"cupcup" => "\u{2A4A}",
2087        b"capcap" => "\u{2A4B}",
2088        b"ccups" => "\u{2A4C}",
2089        b"ccaps" => "\u{2A4D}",
2090        b"ccupssm" => "\u{2A50}",
2091        b"And" => "\u{2A53}",
2092        b"Or" => "\u{2A54}",
2093        b"andand" => "\u{2A55}",
2094        b"oror" => "\u{2A56}",
2095        b"orslope" => "\u{2A57}",
2096        b"andslope" => "\u{2A58}",
2097        b"andv" => "\u{2A5A}",
2098        b"orv" => "\u{2A5B}",
2099        b"andd" => "\u{2A5C}",
2100        b"ord" => "\u{2A5D}",
2101        b"wedbar" => "\u{2A5F}",
2102        b"sdote" => "\u{2A66}",
2103        b"simdot" => "\u{2A6A}",
2104        b"congdot" => "\u{2A6D}",
2105        b"easter" => "\u{2A6E}",
2106        b"apacir" => "\u{2A6F}",
2107        b"apE" => "\u{2A70}",
2108        b"eplus" => "\u{2A71}",
2109        b"pluse" => "\u{2A72}",
2110        b"Esim" => "\u{2A73}",
2111        b"Colone" => "\u{2A74}",
2112        b"Equal" => "\u{2A75}",
2113        b"eDDot" | b"ddotseq" => "\u{2A77}",
2114        b"equivDD" => "\u{2A78}",
2115        b"ltcir" => "\u{2A79}",
2116        b"gtcir" => "\u{2A7A}",
2117        b"ltquest" => "\u{2A7B}",
2118        b"gtquest" => "\u{2A7C}",
2119        b"les" | b"LessSlantEqual" | b"leqslant" => "\u{2A7D}",
2120        b"ges" | b"GreaterSlantEqual" | b"geqslant" => "\u{2A7E}",
2121        b"lesdot" => "\u{2A7F}",
2122        b"gesdot" => "\u{2A80}",
2123        b"lesdoto" => "\u{2A81}",
2124        b"gesdoto" => "\u{2A82}",
2125        b"lesdotor" => "\u{2A83}",
2126        b"gesdotol" => "\u{2A84}",
2127        b"lap" | b"lessapprox" => "\u{2A85}",
2128        b"gap" | b"gtrapprox" => "\u{2A86}",
2129        b"lne" | b"lneq" => "\u{2A87}",
2130        b"gne" | b"gneq" => "\u{2A88}",
2131        b"lnap" | b"lnapprox" => "\u{2A89}",
2132        b"gnap" | b"gnapprox" => "\u{2A8A}",
2133        b"lEg" | b"lesseqqgtr" => "\u{2A8B}",
2134        b"gEl" | b"gtreqqless" => "\u{2A8C}",
2135        b"lsime" => "\u{2A8D}",
2136        b"gsime" => "\u{2A8E}",
2137        b"lsimg" => "\u{2A8F}",
2138        b"gsiml" => "\u{2A90}",
2139        b"lgE" => "\u{2A91}",
2140        b"glE" => "\u{2A92}",
2141        b"lesges" => "\u{2A93}",
2142        b"gesles" => "\u{2A94}",
2143        b"els" | b"eqslantless" => "\u{2A95}",
2144        b"egs" | b"eqslantgtr" => "\u{2A96}",
2145        b"elsdot" => "\u{2A97}",
2146        b"egsdot" => "\u{2A98}",
2147        b"el" => "\u{2A99}",
2148        b"eg" => "\u{2A9A}",
2149        b"siml" => "\u{2A9D}",
2150        b"simg" => "\u{2A9E}",
2151        b"simlE" => "\u{2A9F}",
2152        b"simgE" => "\u{2AA0}",
2153        b"LessLess" => "\u{2AA1}",
2154        b"GreaterGreater" => "\u{2AA2}",
2155        b"glj" => "\u{2AA4}",
2156        b"gla" => "\u{2AA5}",
2157        b"ltcc" => "\u{2AA6}",
2158        b"gtcc" => "\u{2AA7}",
2159        b"lescc" => "\u{2AA8}",
2160        b"gescc" => "\u{2AA9}",
2161        b"smt" => "\u{2AAA}",
2162        b"lat" => "\u{2AAB}",
2163        b"smte" => "\u{2AAC}",
2164        b"late" => "\u{2AAD}",
2165        b"bumpE" => "\u{2AAE}",
2166        b"pre" | b"preceq" | b"PrecedesEqual" => "\u{2AAF}",
2167        b"sce" | b"succeq" | b"SucceedsEqual" => "\u{2AB0}",
2168        b"prE" => "\u{2AB3}",
2169        b"scE" => "\u{2AB4}",
2170        b"prnE" | b"precneqq" => "\u{2AB5}",
2171        b"scnE" | b"succneqq" => "\u{2AB6}",
2172        b"prap" | b"precapprox" => "\u{2AB7}",
2173        b"scap" | b"succapprox" => "\u{2AB8}",
2174        b"prnap" | b"precnapprox" => "\u{2AB9}",
2175        b"scnap" | b"succnapprox" => "\u{2ABA}",
2176        b"Pr" => "\u{2ABB}",
2177        b"Sc" => "\u{2ABC}",
2178        b"subdot" => "\u{2ABD}",
2179        b"supdot" => "\u{2ABE}",
2180        b"subplus" => "\u{2ABF}",
2181        b"supplus" => "\u{2AC0}",
2182        b"submult" => "\u{2AC1}",
2183        b"supmult" => "\u{2AC2}",
2184        b"subedot" => "\u{2AC3}",
2185        b"supedot" => "\u{2AC4}",
2186        b"subE" | b"subseteqq" => "\u{2AC5}",
2187        b"supE" | b"supseteqq" => "\u{2AC6}",
2188        b"subsim" => "\u{2AC7}",
2189        b"supsim" => "\u{2AC8}",
2190        b"subnE" | b"subsetneqq" => "\u{2ACB}",
2191        b"supnE" | b"supsetneqq" => "\u{2ACC}",
2192        b"csub" => "\u{2ACF}",
2193        b"csup" => "\u{2AD0}",
2194        b"csube" => "\u{2AD1}",
2195        b"csupe" => "\u{2AD2}",
2196        b"subsup" => "\u{2AD3}",
2197        b"supsub" => "\u{2AD4}",
2198        b"subsub" => "\u{2AD5}",
2199        b"supsup" => "\u{2AD6}",
2200        b"suphsub" => "\u{2AD7}",
2201        b"supdsub" => "\u{2AD8}",
2202        b"forkv" => "\u{2AD9}",
2203        b"topfork" => "\u{2ADA}",
2204        b"mlcp" => "\u{2ADB}",
2205        b"Dashv" | b"DoubleLeftTee" => "\u{2AE4}",
2206        b"Vdashl" => "\u{2AE6}",
2207        b"Barv" => "\u{2AE7}",
2208        b"vBar" => "\u{2AE8}",
2209        b"vBarv" => "\u{2AE9}",
2210        b"Vbar" => "\u{2AEB}",
2211        b"Not" => "\u{2AEC}",
2212        b"bNot" => "\u{2AED}",
2213        b"rnmid" => "\u{2AEE}",
2214        b"cirmid" => "\u{2AEF}",
2215        b"midcir" => "\u{2AF0}",
2216        b"topcir" => "\u{2AF1}",
2217        b"nhpar" => "\u{2AF2}",
2218        b"parsim" => "\u{2AF3}",
2219        b"parsl" => "\u{2AFD}",
2220        b"fflig" => "\u{FB00}",
2221        b"filig" => "\u{FB01}",
2222        b"fllig" => "\u{FB02}",
2223        b"ffilig" => "\u{FB03}",
2224        b"ffllig" => "\u{FB04}",
2225        b"Ascr" => "\u{1D49}",
2226        b"Cscr" => "\u{1D49}",
2227        b"Dscr" => "\u{1D49}",
2228        b"Gscr" => "\u{1D4A}",
2229        b"Jscr" => "\u{1D4A}",
2230        b"Kscr" => "\u{1D4A}",
2231        b"Nscr" => "\u{1D4A}",
2232        b"Oscr" => "\u{1D4A}",
2233        b"Pscr" => "\u{1D4A}",
2234        b"Qscr" => "\u{1D4A}",
2235        b"Sscr" => "\u{1D4A}",
2236        b"Tscr" => "\u{1D4A}",
2237        b"Uscr" => "\u{1D4B}",
2238        b"Vscr" => "\u{1D4B}",
2239        b"Wscr" => "\u{1D4B}",
2240        b"Xscr" => "\u{1D4B}",
2241        b"Yscr" => "\u{1D4B}",
2242        b"Zscr" => "\u{1D4B}",
2243        b"ascr" => "\u{1D4B}",
2244        b"bscr" => "\u{1D4B}",
2245        b"cscr" => "\u{1D4B}",
2246        b"dscr" => "\u{1D4B}",
2247        b"fscr" => "\u{1D4B}",
2248        b"hscr" => "\u{1D4B}",
2249        b"iscr" => "\u{1D4B}",
2250        b"jscr" => "\u{1D4B}",
2251        b"kscr" => "\u{1D4C}",
2252        b"lscr" => "\u{1D4C}",
2253        b"mscr" => "\u{1D4C}",
2254        b"nscr" => "\u{1D4C}",
2255        b"pscr" => "\u{1D4C}",
2256        b"qscr" => "\u{1D4C}",
2257        b"rscr" => "\u{1D4C}",
2258        b"sscr" => "\u{1D4C}",
2259        b"tscr" => "\u{1D4C}",
2260        b"uscr" => "\u{1D4C}",
2261        b"vscr" => "\u{1D4C}",
2262        b"wscr" => "\u{1D4C}",
2263        b"xscr" => "\u{1D4C}",
2264        b"yscr" => "\u{1D4C}",
2265        b"zscr" => "\u{1D4C}",
2266        b"Afr" => "\u{1D50}",
2267        b"Bfr" => "\u{1D50}",
2268        b"Dfr" => "\u{1D50}",
2269        b"Efr" => "\u{1D50}",
2270        b"Ffr" => "\u{1D50}",
2271        b"Gfr" => "\u{1D50}",
2272        b"Jfr" => "\u{1D50}",
2273        b"Kfr" => "\u{1D50}",
2274        b"Lfr" => "\u{1D50}",
2275        b"Mfr" => "\u{1D51}",
2276        b"Nfr" => "\u{1D51}",
2277        b"Ofr" => "\u{1D51}",
2278        b"Pfr" => "\u{1D51}",
2279        b"Qfr" => "\u{1D51}",
2280        b"Sfr" => "\u{1D51}",
2281        b"Tfr" => "\u{1D51}",
2282        b"Ufr" => "\u{1D51}",
2283        b"Vfr" => "\u{1D51}",
2284        b"Wfr" => "\u{1D51}",
2285        b"Xfr" => "\u{1D51}",
2286        b"Yfr" => "\u{1D51}",
2287        b"afr" => "\u{1D51}",
2288        b"bfr" => "\u{1D51}",
2289        b"cfr" => "\u{1D52}",
2290        b"dfr" => "\u{1D52}",
2291        b"efr" => "\u{1D52}",
2292        b"ffr" => "\u{1D52}",
2293        b"gfr" => "\u{1D52}",
2294        b"hfr" => "\u{1D52}",
2295        b"ifr" => "\u{1D52}",
2296        b"jfr" => "\u{1D52}",
2297        b"kfr" => "\u{1D52}",
2298        b"lfr" => "\u{1D52}",
2299        b"mfr" => "\u{1D52}",
2300        b"nfr" => "\u{1D52}",
2301        b"ofr" => "\u{1D52}",
2302        b"pfr" => "\u{1D52}",
2303        b"qfr" => "\u{1D52}",
2304        b"rfr" => "\u{1D52}",
2305        b"sfr" => "\u{1D53}",
2306        b"tfr" => "\u{1D53}",
2307        b"ufr" => "\u{1D53}",
2308        b"vfr" => "\u{1D53}",
2309        b"wfr" => "\u{1D53}",
2310        b"xfr" => "\u{1D53}",
2311        b"yfr" => "\u{1D53}",
2312        b"zfr" => "\u{1D53}",
2313        b"Aopf" => "\u{1D53}",
2314        b"Bopf" => "\u{1D53}",
2315        b"Dopf" => "\u{1D53}",
2316        b"Eopf" => "\u{1D53}",
2317        b"Fopf" => "\u{1D53}",
2318        b"Gopf" => "\u{1D53}",
2319        b"Iopf" => "\u{1D54}",
2320        b"Jopf" => "\u{1D54}",
2321        b"Kopf" => "\u{1D54}",
2322        b"Lopf" => "\u{1D54}",
2323        b"Mopf" => "\u{1D54}",
2324        b"Oopf" => "\u{1D54}",
2325        b"Sopf" => "\u{1D54}",
2326        b"Topf" => "\u{1D54}",
2327        b"Uopf" => "\u{1D54}",
2328        b"Vopf" => "\u{1D54}",
2329        b"Wopf" => "\u{1D54}",
2330        b"Xopf" => "\u{1D54}",
2331        b"Yopf" => "\u{1D55}",
2332        b"aopf" => "\u{1D55}",
2333        b"bopf" => "\u{1D55}",
2334        b"copf" => "\u{1D55}",
2335        b"dopf" => "\u{1D55}",
2336        b"eopf" => "\u{1D55}",
2337        b"fopf" => "\u{1D55}",
2338        b"gopf" => "\u{1D55}",
2339        b"hopf" => "\u{1D55}",
2340        b"iopf" => "\u{1D55}",
2341        b"jopf" => "\u{1D55}",
2342        b"kopf" => "\u{1D55}",
2343        b"lopf" => "\u{1D55}",
2344        b"mopf" => "\u{1D55}",
2345        b"nopf" => "\u{1D55}",
2346        b"oopf" => "\u{1D56}",
2347        b"popf" => "\u{1D56}",
2348        b"qopf" => "\u{1D56}",
2349        b"ropf" => "\u{1D56}",
2350        b"sopf" => "\u{1D56}",
2351        b"topf" => "\u{1D56}",
2352        b"uopf" => "\u{1D56}",
2353        b"vopf" => "\u{1D56}",
2354        b"wopf" => "\u{1D56}",
2355        b"xopf" => "\u{1D56}",
2356        b"yopf" => "\u{1D56}",
2357        b"zopf" => "\u{1D56}",
2358        _ => return None,
2359    };
2360    Some(s)
2361}
2362
2363pub(crate) fn parse_number(num: &str) -> Result<char, ParseCharRefError> {
2364    let code = if let Some(hex) = num.strip_prefix('x') {
2365        from_str_radix(hex, 16)?
2366    } else {
2367        from_str_radix(num, 10)?
2368    };
2369    if code == 0 {
2370        return Err(ParseCharRefError::IllegalCharacter(code));
2371    }
2372    match std::char::from_u32(code) {
2373        Some(c) => Ok(c),
2374        None => Err(ParseCharRefError::InvalidCodepoint(code)),
2375    }
2376}
2377
2378#[inline]
2379fn from_str_radix(src: &str, radix: u32) -> Result<u32, ParseCharRefError> {
2380    match src.as_bytes().first().copied() {
2381        // We should not allow sign numbers, but u32::from_str_radix will accept `+`.
2382        // We also handle `-` to be consistent in returned errors
2383        Some(b'+') | Some(b'-') => Err(ParseCharRefError::UnexpectedSign),
2384        _ => u32::from_str_radix(src, radix).map_err(ParseCharRefError::InvalidNumber),
2385    }
2386}
2387
2388////////////////////////////////////////////////////////////////////////////////////////////////////
2389
2390#[cfg(test)]
2391mod normalization {
2392    use super::*;
2393
2394    mod eol {
2395        use super::*;
2396
2397        mod xml11 {
2398            use super::*;
2399            use pretty_assertions::assert_eq;
2400
2401            #[test]
2402            fn empty() {
2403                assert_eq!(normalize_xml11_eols(""), "");
2404            }
2405
2406            #[test]
2407            fn already_normalized() {
2408                assert_eq!(
2409                    normalize_xml11_eols("\nalready \n\n normalized\n"),
2410                    "\nalready \n\n normalized\n",
2411                );
2412            }
2413
2414            #[test]
2415            fn cr_lf() {
2416                assert_eq!(
2417                    normalize_xml11_eols("\r\nsome\r\n\r\ntext"),
2418                    "\nsome\n\ntext"
2419                );
2420            }
2421
2422            #[test]
2423            fn cr_u0085() {
2424                assert_eq!(
2425                    normalize_xml11_eols("\r\u{0085}some\r\u{0085}\r\u{0085}text"),
2426                    "\nsome\n\ntext",
2427                );
2428            }
2429
2430            #[test]
2431            fn u0085() {
2432                assert_eq!(
2433                    normalize_xml11_eols("\u{0085}some\u{0085}\u{0085}text"),
2434                    "\nsome\n\ntext",
2435                );
2436            }
2437
2438            #[test]
2439            fn u2028() {
2440                assert_eq!(
2441                    normalize_xml11_eols("\u{2028}some\u{2028}\u{2028}text"),
2442                    "\nsome\n\ntext",
2443                );
2444            }
2445
2446            #[test]
2447            fn mixed() {
2448                assert_eq!(
2449                    normalize_xml11_eols("\r\r\r\u{2028}\n\r\nsome\n\u{0085}\r\u{0085}text"),
2450                    "\n\n\n\n\n\nsome\n\n\ntext",
2451                );
2452            }
2453
2454            #[test]
2455            fn utf8_0xc2() {
2456                // All possible characters encoded in 2 bytes in UTF-8 which first byte is 0xC2 (0b11000010)
2457                // Second byte follows the pattern 10xxxxxx
2458                let first = std::str::from_utf8(&[0b11000010, 0b10000000])
2459                    .unwrap()
2460                    .chars()
2461                    .next()
2462                    .unwrap();
2463                let last = std::str::from_utf8(&[0b11000010, 0b10111111])
2464                    .unwrap()
2465                    .chars()
2466                    .next()
2467                    .unwrap();
2468                let mut utf8 = [0; 2];
2469                for ch in first..=last {
2470                    ch.encode_utf8(&mut utf8);
2471                    let description = format!("UTF-8 [{:02x} {:02x}] = `{}`", utf8[0], utf8[1], ch);
2472                    let input = std::str::from_utf8(&utf8).expect(&description);
2473
2474                    dbg!((input, &description));
2475                    if ch == '\u{0085}' {
2476                        assert_eq!(normalize_xml11_eols(input), "\n", "{}", description);
2477                    } else {
2478                        assert_eq!(normalize_xml11_eols(input), input, "{}", description);
2479                    }
2480                }
2481                assert_eq!((first..=last).count(), 64);
2482            }
2483
2484            #[test]
2485            fn utf8_0x0d_0xc2() {
2486                // All possible characters encoded in 2 bytes in UTF-8 which first byte is 0xC2 (0b11000010)
2487                // Second byte follows the pattern 10xxxxxx
2488                let first = std::str::from_utf8(&[0b11000010, 0b10000000])
2489                    .unwrap()
2490                    .chars()
2491                    .next()
2492                    .unwrap();
2493                let last = std::str::from_utf8(&[0b11000010, 0b10111111])
2494                    .unwrap()
2495                    .chars()
2496                    .next()
2497                    .unwrap();
2498                let mut utf8 = [b'\r', 0, 0];
2499                for ch in first..=last {
2500                    ch.encode_utf8(&mut utf8[1..]);
2501                    let description = format!(
2502                        "UTF-8 [{:02x} {:02x} {:02x}] = `{}`",
2503                        utf8[0], utf8[1], utf8[2], ch
2504                    );
2505                    let input = std::str::from_utf8(&utf8).expect(&description);
2506
2507                    dbg!((input, &description));
2508                    if ch == '\u{0085}' {
2509                        assert_eq!(normalize_xml11_eols(input), "\n", "{}", description);
2510                    } else {
2511                        // utf8 is copied, because [u8; 3] implements Copy
2512                        let mut expected = utf8;
2513                        expected[0] = b'\n';
2514                        let expected = std::str::from_utf8(&expected).expect(&description);
2515                        assert_eq!(normalize_xml11_eols(input), expected, "{}", description);
2516                    }
2517                }
2518                assert_eq!((first..=last).count(), 64);
2519            }
2520
2521            #[test]
2522            fn utf8_0xe2() {
2523                // All possible characters encoded in 3 bytes in UTF-8 which first byte is 0xE2 (0b11100010)
2524                // Second and third bytes follows the pattern 10xxxxxx
2525                let first = std::str::from_utf8(&[0b11100010, 0b10000000, 0b10000000])
2526                    .unwrap()
2527                    .chars()
2528                    .next()
2529                    .unwrap();
2530                let last = std::str::from_utf8(&[0b11100010, 0b10111111, 0b10111111])
2531                    .unwrap()
2532                    .chars()
2533                    .next()
2534                    .unwrap();
2535                let mut buf = [0; 3];
2536                for ch in first..=last {
2537                    let input = &*ch.encode_utf8(&mut buf);
2538                    let buf = input.as_bytes();
2539                    let description = format!(
2540                        "UTF-8 [{:02x} {:02x} {:02x}] = `{}`",
2541                        buf[0], buf[1], buf[2], ch
2542                    );
2543
2544                    dbg!((input, &description));
2545                    if ch == '\u{2028}' {
2546                        assert_eq!(normalize_xml11_eols(input), "\n", "{}", description);
2547                    } else {
2548                        assert_eq!(normalize_xml11_eols(input), input, "{}", description);
2549                    }
2550                }
2551                assert_eq!((first..=last).count(), 4096);
2552            }
2553        }
2554
2555        mod xml10 {
2556            use super::*;
2557            use pretty_assertions::assert_eq;
2558
2559            #[test]
2560            fn empty() {
2561                assert_eq!(normalize_xml10_eols(""), "");
2562            }
2563
2564            #[test]
2565            fn already_normalized() {
2566                assert_eq!(
2567                    normalize_xml10_eols("\nalready \n\n normalized\n"),
2568                    "\nalready \n\n normalized\n",
2569                );
2570            }
2571
2572            #[test]
2573            fn cr_lf() {
2574                assert_eq!(
2575                    normalize_xml10_eols("\r\nsome\r\n\r\ntext"),
2576                    "\nsome\n\ntext"
2577                );
2578            }
2579
2580            #[test]
2581            fn cr_u0085() {
2582                assert_eq!(
2583                    normalize_xml10_eols("\r\u{0085}some\r\u{0085}\r\u{0085}text"),
2584                    "\n\u{0085}some\n\u{0085}\n\u{0085}text",
2585                );
2586            }
2587
2588            #[test]
2589            fn u0085() {
2590                assert_eq!(
2591                    normalize_xml10_eols("\u{0085}some\u{0085}\u{0085}text"),
2592                    "\u{0085}some\u{0085}\u{0085}text",
2593                );
2594            }
2595
2596            #[test]
2597            fn u2028() {
2598                assert_eq!(
2599                    normalize_xml10_eols("\u{2028}some\u{2028}\u{2028}text"),
2600                    "\u{2028}some\u{2028}\u{2028}text",
2601                );
2602            }
2603
2604            #[test]
2605            fn mixed() {
2606                assert_eq!(
2607                    normalize_xml10_eols("\r\r\r\u{2028}\n\r\nsome\n\u{0085}\r\u{0085}text"),
2608                    "\n\n\n\u{2028}\n\nsome\n\u{0085}\n\u{0085}text",
2609                );
2610            }
2611        }
2612    }
2613
2614    mod attribute {
2615        use super::*;
2616        use pretty_assertions::assert_eq;
2617
2618        #[test]
2619        fn empty() {
2620            assert_eq!(
2621                normalize_xml10_attribute_value("", 5, |_| { None }),
2622                Ok("".into())
2623            );
2624            assert_eq!(
2625                normalize_xml11_attribute_value("", 5, |_| { None }),
2626                Ok("".into())
2627            );
2628        }
2629
2630        #[test]
2631        fn already_normalized() {
2632            assert_eq!(
2633                normalize_xml10_attribute_value("already normalized", 5, |_| { None }),
2634                Ok("already normalized".into())
2635            );
2636            assert_eq!(
2637                normalize_xml11_attribute_value("already normalized", 5, |_| { None }),
2638                Ok("already normalized".into())
2639            );
2640        }
2641
2642        #[test]
2643        fn only_spaces() {
2644            assert_eq!(
2645                normalize_xml10_attribute_value("   ", 5, |_| { None }),
2646                Ok("   ".into())
2647            );
2648            assert_eq!(
2649                normalize_xml11_attribute_value("   ", 5, |_| { None }),
2650                Ok("   ".into())
2651            );
2652
2653            assert_eq!(
2654                normalize_xml10_attribute_value("\t\t\t", 5, |_| { None }),
2655                Ok("   ".into())
2656            );
2657            assert_eq!(
2658                normalize_xml11_attribute_value("\t\t\t", 5, |_| { None }),
2659                Ok("   ".into())
2660            );
2661
2662            assert_eq!(
2663                normalize_xml10_attribute_value("\r\r\r", 5, |_| { None }),
2664                Ok("   ".into())
2665            );
2666            assert_eq!(
2667                normalize_xml11_attribute_value("\r\r\r", 5, |_| { None }),
2668                Ok("   ".into())
2669            );
2670
2671            assert_eq!(
2672                normalize_xml10_attribute_value("\n\n\n", 5, |_| { None }),
2673                Ok("   ".into())
2674            );
2675            assert_eq!(
2676                normalize_xml11_attribute_value("\n\n\n", 5, |_| { None }),
2677                Ok("   ".into())
2678            );
2679
2680            assert_eq!(
2681                normalize_xml10_attribute_value("\r\n\r\n\r\n", 5, |_| { None }),
2682                Ok("   ".into())
2683            );
2684            assert_eq!(
2685                normalize_xml11_attribute_value("\r\n\r\n\r\n", 5, |_| { None }),
2686                Ok("   ".into())
2687            );
2688
2689            assert_eq!(
2690                normalize_xml10_attribute_value("\t\t\n\n\r\r  ", 5, |_| None),
2691                Ok("        ".into())
2692            );
2693            assert_eq!(
2694                normalize_xml11_attribute_value("\t\t\n\n\r\r  ", 5, |_| None),
2695                Ok("        ".into())
2696            );
2697
2698            assert_eq!(
2699                normalize_xml10_attribute_value("\u{0085}\u{0085}\u{0085}", 5, |_| { None }),
2700                Ok("\u{0085}\u{0085}\u{0085}".into())
2701            );
2702            assert_eq!(
2703                normalize_xml11_attribute_value("\u{0085}\u{0085}\u{0085}", 5, |_| { None }),
2704                Ok("   ".into())
2705            );
2706
2707            assert_eq!(
2708                normalize_xml10_attribute_value("\r\u{0085}\r\u{0085}\r\u{0085}", 5, |_| { None }),
2709                Ok(" \u{0085} \u{0085} \u{0085}".into())
2710            );
2711            assert_eq!(
2712                normalize_xml11_attribute_value("\r\u{0085}\r\u{0085}\r\u{0085}", 5, |_| { None }),
2713                Ok("   ".into())
2714            );
2715
2716            assert_eq!(
2717                normalize_xml10_attribute_value("\u{2028}\u{2028}\u{2028}", 5, |_| { None }),
2718                Ok("\u{2028}\u{2028}\u{2028}".into())
2719            );
2720            assert_eq!(
2721                normalize_xml11_attribute_value("\u{2028}\u{2028}\u{2028}", 5, |_| { None }),
2722                Ok("   ".into())
2723            );
2724        }
2725
2726        #[test]
2727        fn mixed_content_normalization() {
2728            // Text with both whitespace and character references
2729            assert_eq!(
2730                normalize_xml10_attribute_value("hello\t&#32;\nworld", 5, |_| None),
2731                Ok("hello   world".into())
2732            );
2733            assert_eq!(
2734                normalize_xml11_attribute_value("hello\t&#32;\nworld", 5, |_| None),
2735                Ok("hello   world".into())
2736            );
2737
2738            // Whitespace around entities
2739            assert_eq!(
2740                normalize_xml10_attribute_value("text &entity; \n more", 5, |_| {
2741                    Some("replacement")
2742                }),
2743                Ok("text replacement   more".into())
2744            );
2745            assert_eq!(
2746                normalize_xml11_attribute_value("text &entity; \n more", 5, |_| {
2747                    Some("replacement")
2748                }),
2749                Ok("text replacement   more".into())
2750            );
2751
2752            // Complex mix of tabs, newlines, and character references
2753            // \t → space, &#65; → A, \r\n → space, &#66; → B, \t → space
2754            assert_eq!(
2755                normalize_xml10_attribute_value("\t&#65;\r\n&#66;\t", 5, |_| None),
2756                Ok(" A B ".into())
2757            );
2758            assert_eq!(
2759                normalize_xml11_attribute_value("\t&#65;\r\n&#66;\t", 5, |_| None),
2760                Ok(" A B ".into())
2761            );
2762        }
2763
2764        #[test]
2765        fn leading_trailing_whitespace() {
2766            // Leading whitespace preserved but normalized
2767            assert_eq!(
2768                normalize_xml10_attribute_value("  text", 5, |_| None),
2769                Ok("  text".into())
2770            );
2771            assert_eq!(
2772                normalize_xml11_attribute_value("  text", 5, |_| None),
2773                Ok("  text".into())
2774            );
2775
2776            assert_eq!(
2777                normalize_xml10_attribute_value("\t\ttext", 5, |_| None),
2778                Ok("  text".into())
2779            );
2780            assert_eq!(
2781                normalize_xml11_attribute_value("\t\ttext", 5, |_| None),
2782                Ok("  text".into())
2783            );
2784
2785            // Trailing whitespace preserved but normalized
2786            assert_eq!(
2787                normalize_xml10_attribute_value("text  ", 5, |_| None),
2788                Ok("text  ".into())
2789            );
2790            assert_eq!(
2791                normalize_xml11_attribute_value("text  ", 5, |_| None),
2792                Ok("text  ".into())
2793            );
2794
2795            assert_eq!(
2796                normalize_xml10_attribute_value("text\n\n", 5, |_| None),
2797                Ok("text  ".into())
2798            );
2799            assert_eq!(
2800                normalize_xml11_attribute_value("text\n\n", 5, |_| None),
2801                Ok("text  ".into())
2802            );
2803
2804            // Both leading and trailing
2805            assert_eq!(
2806                normalize_xml10_attribute_value("\n\ntext\n\n", 5, |_| None),
2807                Ok("  text  ".into())
2808            );
2809            assert_eq!(
2810                normalize_xml11_attribute_value("\n\ntext\n\n", 5, |_| None),
2811                Ok("  text  ".into())
2812            );
2813        }
2814
2815        #[test]
2816        fn characters() {
2817            assert_eq!(
2818                normalize_xml10_attribute_value("string with &#32; character", 5, |_| { None }),
2819                Ok("string with   character".into())
2820            );
2821            assert_eq!(
2822                normalize_xml10_attribute_value("string with &#x20; character", 5, |_| { None }),
2823                Ok("string with   character".into())
2824            );
2825
2826            assert_eq!(
2827                normalize_xml11_attribute_value("string with &#32; character", 5, |_| { None }),
2828                Ok("string with   character".into())
2829            );
2830            assert_eq!(
2831                normalize_xml11_attribute_value("string with &#x20; character", 5, |_| { None }),
2832                Ok("string with   character".into())
2833            );
2834        }
2835
2836        #[test]
2837        fn character_reference_edge_cases() {
2838            // Invalid hex character references
2839            assert!(matches!(
2840                normalize_xml10_attribute_value("&#xGG;", 5, |_| None),
2841                Err(EscapeError::InvalidCharRef(
2842                    ParseCharRefError::InvalidNumber(_)
2843                ))
2844            ));
2845            assert!(matches!(
2846                normalize_xml11_attribute_value("&#xGG;", 5, |_| None),
2847                Err(EscapeError::InvalidCharRef(
2848                    ParseCharRefError::InvalidNumber(_)
2849                ))
2850            ));
2851
2852            // Invalid decimal character references
2853            assert!(matches!(
2854                normalize_xml10_attribute_value("&#ABC;", 5, |_| None),
2855                Err(EscapeError::InvalidCharRef(
2856                    ParseCharRefError::InvalidNumber(_)
2857                ))
2858            ));
2859
2860            // Out-of-range Unicode (beyond U+10FFFF)
2861            assert_eq!(
2862                normalize_xml10_attribute_value("&#x110000;", 5, |_| None),
2863                Err(EscapeError::InvalidCharRef(
2864                    ParseCharRefError::InvalidCodepoint(0x110000)
2865                ))
2866            );
2867            assert_eq!(
2868                normalize_xml11_attribute_value("&#x110000;", 5, |_| None),
2869                Err(EscapeError::InvalidCharRef(
2870                    ParseCharRefError::InvalidCodepoint(0x110000)
2871                ))
2872            );
2873
2874            // Large decimal value that is not a valid Unicode codepoint
2875            assert_eq!(
2876                normalize_xml10_attribute_value("&#999999999;", 5, |_| None),
2877                Err(EscapeError::InvalidCharRef(
2878                    ParseCharRefError::InvalidCodepoint(999999999)
2879                ))
2880            );
2881
2882            // Non-whitespace character references
2883            assert_eq!(
2884                normalize_xml10_attribute_value("&#65;&#66;&#67;", 5, |_| None),
2885                Ok("ABC".into())
2886            );
2887            assert_eq!(
2888                normalize_xml11_attribute_value("&#65;&#66;&#67;", 5, |_| None),
2889                Ok("ABC".into())
2890            );
2891
2892            // Character references at boundaries
2893            assert_eq!(
2894                normalize_xml10_attribute_value("&#32;text", 5, |_| None),
2895                Ok(" text".into())
2896            );
2897            assert_eq!(
2898                normalize_xml10_attribute_value("text&#32;", 5, |_| None),
2899                Ok("text ".into())
2900            );
2901            assert_eq!(
2902                normalize_xml11_attribute_value("&#32;text", 5, |_| None),
2903                Ok(" text".into())
2904            );
2905            assert_eq!(
2906                normalize_xml11_attribute_value("text&#32;", 5, |_| None),
2907                Ok("text ".into())
2908            );
2909        }
2910
2911        #[test]
2912        fn entities() {
2913            assert_eq!(
2914                normalize_xml10_attribute_value("string with &entity; reference", 5, |_| {
2915                    Some("replacement")
2916                }),
2917                Ok("string with replacement reference".into())
2918            );
2919            assert_eq!(
2920                normalize_xml10_attribute_value("string with &entity-1; reference", 5, |entity| {
2921                    match entity {
2922                        "entity-1" => Some("recursive &entity-2;"),
2923                        "entity-2" => Some("entity&#32;2"),
2924                        _ => None,
2925                    }
2926                }),
2927                Ok("string with recursive entity 2 reference".into())
2928            );
2929            // Special case: '&' should not treated as unterminated reference, but everything '&...' should
2930            assert_eq!(
2931                normalize_xml10_attribute_value(
2932                    "string with &entity;amp; reference",
2933                    5,
2934                    |entity| {
2935                        match entity {
2936                            "entity" => Some("&amp;"),
2937                            "amp" => Some("&"),
2938                            _ => None,
2939                        }
2940                    }
2941                ),
2942                Ok("string with &amp; reference".into())
2943            );
2944
2945            assert_eq!(
2946                normalize_xml11_attribute_value("string with &entity; reference", 5, |_| {
2947                    Some("replacement")
2948                }),
2949                Ok("string with replacement reference".into())
2950            );
2951            assert_eq!(
2952                normalize_xml11_attribute_value("string with &entity-1; reference", 5, |entity| {
2953                    match entity {
2954                        "entity-1" => Some("recursive &entity-2;"),
2955                        "entity-2" => Some("entity&#32;2"),
2956                        _ => None,
2957                    }
2958                }),
2959                Ok("string with recursive entity 2 reference".into())
2960            );
2961            // Special case: '&' should not treated as unterminated reference, but everything '&...' should
2962            assert_eq!(
2963                normalize_xml11_attribute_value(
2964                    "string with &entity;amp; reference",
2965                    5,
2966                    |entity| {
2967                        match entity {
2968                            "entity" => Some("&amp;"),
2969                            "amp" => Some("&"),
2970                            _ => None,
2971                        }
2972                    }
2973                ),
2974                Ok("string with &amp; reference".into())
2975            );
2976        }
2977
2978        #[test]
2979        fn unknown_entity() {
2980            assert_eq!(
2981                normalize_xml10_attribute_value(
2982                    "string with unknown &entity; reference",
2983                    //                   ^     ^ = 21..27
2984                    5,
2985                    |_| None
2986                ),
2987                Err(EscapeError::UnrecognizedEntity(
2988                    21..27,
2989                    "entity".to_string(),
2990                ))
2991            );
2992
2993            assert_eq!(
2994                normalize_xml11_attribute_value(
2995                    "string with unknown &entity; reference",
2996                    //                   ^     ^ = 21..27
2997                    5,
2998                    |_| None
2999                ),
3000                Err(EscapeError::UnrecognizedEntity(
3001                    21..27,
3002                    "entity".to_string(),
3003                ))
3004            );
3005        }
3006
3007        #[test]
3008        fn predefined_entities() {
3009            // Test how predefined XML entities are handled
3010            assert_eq!(
3011                normalize_xml10_attribute_value(
3012                    "&lt;&gt;&quot;&apos;",
3013                    5,
3014                    resolve_predefined_entity
3015                ),
3016                Ok("<>\"'".into())
3017            );
3018            assert_eq!(
3019                normalize_xml11_attribute_value(
3020                    "&lt;&gt;&quot;&apos;",
3021                    5,
3022                    resolve_predefined_entity
3023                ),
3024                Ok("<>\"'".into())
3025            );
3026
3027            // &amp; followed by more entities
3028            assert_eq!(
3029                normalize_xml10_attribute_value("&amp;&lt;", 5, resolve_predefined_entity),
3030                Ok("&<".into())
3031            );
3032            assert_eq!(
3033                normalize_xml11_attribute_value("&amp;&lt;", 5, resolve_predefined_entity),
3034                Ok("&<".into())
3035            );
3036
3037            // Multiple &amp; in sequence
3038            assert_eq!(
3039                normalize_xml10_attribute_value("&amp;&amp;&amp;", 5, resolve_predefined_entity),
3040                Ok("&&&".into())
3041            );
3042        }
3043
3044        #[test]
3045        fn unclosed_entity() {
3046            // Text consists only of an unterminated entity reference - no name
3047            assert_eq!(
3048                normalize_xml10_attribute_value("& ", 5, |_| None),
3049                Err(EscapeError::UnterminatedEntity(0..2))
3050            );
3051            assert_eq!(
3052                normalize_xml11_attribute_value("& ", 5, |_| None),
3053                Err(EscapeError::UnterminatedEntity(0..2))
3054            );
3055
3056            // Text consists only of an unterminated character reference - no value
3057            assert_eq!(
3058                normalize_xml10_attribute_value("&# ", 5, |_| None),
3059                Err(EscapeError::UnterminatedEntity(0..3))
3060            );
3061            assert_eq!(
3062                normalize_xml11_attribute_value("&# ", 5, |_| None),
3063                Err(EscapeError::UnterminatedEntity(0..3))
3064            );
3065
3066            // Text consists only of an unterminated entity reference
3067            assert_eq!(
3068                normalize_xml10_attribute_value("&entity", 5, |_| Some("text")),
3069                Err(EscapeError::UnterminatedEntity(0..7))
3070            );
3071            assert_eq!(
3072                normalize_xml11_attribute_value("&entity", 5, |_| Some("text")),
3073                Err(EscapeError::UnterminatedEntity(0..7))
3074            );
3075
3076            // Unclosed entity reference within text
3077            assert_eq!(
3078                normalize_xml10_attribute_value(
3079                    "string with unclosed &entity reference",
3080                    //                    ^ = 21           ^ = 38
3081                    5,
3082                    |_| Some("replacement")
3083                ),
3084                Err(EscapeError::UnterminatedEntity(21..38))
3085            );
3086            assert_eq!(
3087                normalize_xml11_attribute_value(
3088                    "string with unclosed &entity reference",
3089                    //                    ^ = 21           ^ = 38
3090                    5,
3091                    |_| Some("replacement")
3092                ),
3093                Err(EscapeError::UnterminatedEntity(21..38))
3094            );
3095
3096            // Unclosed character reference within text
3097            assert_eq!(
3098                normalize_xml10_attribute_value(
3099                    "string with unclosed &#32 (character) reference",
3100                    //                    ^ = 21                    ^ = 47
3101                    5,
3102                    |_| None
3103                ),
3104                Err(EscapeError::UnterminatedEntity(21..47))
3105            );
3106            assert_eq!(
3107                normalize_xml11_attribute_value(
3108                    "string with unclosed &#32 (character) reference",
3109                    //                    ^ = 21                    ^ = 47
3110                    5,
3111                    |_| None
3112                ),
3113                Err(EscapeError::UnterminatedEntity(21..47))
3114            );
3115        }
3116
3117        #[test]
3118        fn malformed_entity() {
3119            // Empty entity name - treated as unrecognized entity with empty name
3120            assert_eq!(
3121                normalize_xml10_attribute_value("&;", 5, |_| None),
3122                Err(EscapeError::UnrecognizedEntity(1..1, "".to_string()))
3123            );
3124            assert_eq!(
3125                normalize_xml11_attribute_value("&;", 5, |_| None),
3126                Err(EscapeError::UnrecognizedEntity(1..1, "".to_string()))
3127            );
3128
3129            // Numeric entity name (should be treated as unknown entity)
3130            assert_eq!(
3131                normalize_xml10_attribute_value("&123;", 5, |_| None),
3132                Err(EscapeError::UnrecognizedEntity(1..4, "123".to_string()))
3133            );
3134            assert_eq!(
3135                normalize_xml11_attribute_value("&123;", 5, |_| None),
3136                Err(EscapeError::UnrecognizedEntity(1..4, "123".to_string()))
3137            );
3138
3139            // Empty character reference
3140            assert!(matches!(
3141                normalize_xml10_attribute_value("&#;", 5, |_| None),
3142                Err(EscapeError::InvalidCharRef(
3143                    ParseCharRefError::InvalidNumber(_)
3144                ))
3145            ));
3146            assert!(matches!(
3147                normalize_xml10_attribute_value("&#x;", 5, |_| None),
3148                Err(EscapeError::InvalidCharRef(
3149                    ParseCharRefError::InvalidNumber(_)
3150                ))
3151            ));
3152        }
3153
3154        #[test]
3155        fn recursive_entity() {
3156            assert_eq!(
3157                normalize_xml10_attribute_value("&entity; reference", 5, |_| Some(
3158                    "recursive &entity;"
3159                )),
3160                Err(EscapeError::TooManyNestedEntities),
3161            );
3162
3163            assert_eq!(
3164                normalize_xml11_attribute_value("&entity; reference", 5, |_| Some(
3165                    "recursive &entity;"
3166                )),
3167                Err(EscapeError::TooManyNestedEntities),
3168            );
3169        }
3170
3171        #[test]
3172        fn recursion_depth() {
3173            // Test at exactly 4 levels with limit of 5 (should work)
3174            // e1 → e2 → e3 → e4 → text (4 entity expansions)
3175            assert_eq!(
3176                normalize_xml10_attribute_value("&e1;", 5, |entity| {
3177                    match entity {
3178                        "e1" => Some("&e2;"),
3179                        "e2" => Some("&e3;"),
3180                        "e3" => Some("&e4;"),
3181                        "e4" => Some("text"),
3182                        _ => None,
3183                    }
3184                }),
3185                Ok("text".into())
3186            );
3187
3188            // Test at exactly 5 levels with limit of 5 (should work at boundary)
3189            // e1 → e2 → e3 → e4 → e5 → text (5 entity expansions)
3190            assert_eq!(
3191                normalize_xml10_attribute_value("&e1;", 5, |entity| {
3192                    match entity {
3193                        "e1" => Some("&e2;"),
3194                        "e2" => Some("&e3;"),
3195                        "e3" => Some("&e4;"),
3196                        "e4" => Some("&e5;"),
3197                        "e5" => Some("text"),
3198                        _ => None,
3199                    }
3200                }),
3201                Ok("text".into())
3202            );
3203
3204            // Test at exactly 6 levels with limit of 5 (should fail)
3205            // e1 → e2 → e3 → e4 → e5 → e6 → text (6 entity expansions exceeds limit)
3206            assert_eq!(
3207                normalize_xml10_attribute_value("&e1;", 5, |entity| {
3208                    match entity {
3209                        "e1" => Some("&e2;"),
3210                        "e2" => Some("&e3;"),
3211                        "e3" => Some("&e4;"),
3212                        "e4" => Some("&e5;"),
3213                        "e5" => Some("&e6;"),
3214                        "e6" => Some("text"),
3215                        _ => None,
3216                    }
3217                }),
3218                Err(EscapeError::TooManyNestedEntities)
3219            );
3220        }
3221    }
3222}