html-escape 0.3.0

This library is for encoding/escaping special characters in HTML and decoding/unescaping HTML entities as well.
Documentation
use alloc::vec::Vec;
#[cfg(feature = "std")]
use std::io::{self, Write};

pub(crate) const HEX_DIGITS: [u8; 16] = *b"0123456789ABCDEF";

/// Find the next `byte` from `p`, or return the length of `text_bytes` if there is none.
#[inline]
pub(crate) fn find_byte(text_bytes: &[u8], mut p: usize, byte: u8) -> usize {
    const LO: u64 = 0x0101_0101_0101_0101;
    const HI: u64 = 0x8080_8080_8080_8080;

    // The target is often the current byte in dense text, so check it before the 8-byte loop.
    if p < text_bytes.len() && text_bytes[p] == byte {
        return p;
    }

    let pattern = LO * byte as u64;

    while p + 8 <= text_bytes.len() {
        let x = u64::from_ne_bytes(text_bytes[p..(p + 8)].try_into().unwrap()) ^ pattern;

        // This is non-zero only if a byte of `x` is zero, which means one of the 8 bytes is `byte`.
        if x.wrapping_sub(LO) & !x & HI != 0 {
            break;
        }

        p += 8;
    }

    while p < text_bytes.len() && text_bytes[p] != byte {
        p += 1;
    }

    p
}

/// Find the next `<` from `p`, or return the length of `text_bytes` if there is none.
#[inline]
pub(crate) fn find_less_than(text_bytes: &[u8], p: usize) -> usize {
    find_byte(text_bytes, p, b'<')
}

#[inline]
pub(crate) fn write_hex_to_vec(e: u8, output: &mut Vec<u8>) {
    output.reserve(6);

    let length = output.len();

    // SAFETY: 6 bytes have just been reserved, and the length is only updated after all of them are initialized.
    // Writing through a raw pointer never creates a reference to the uninitialized spare capacity.
    unsafe {
        let p = output.as_mut_ptr().add(length);

        p.write(b'&');
        p.add(1).write(b'#');
        p.add(2).write(b'x');
        p.add(3).write(HEX_DIGITS[(e >> 4) as usize]);
        p.add(4).write(HEX_DIGITS[(e & 0xF) as usize]);
        p.add(5).write(b';');

        output.set_len(length + 6);
    }
}

#[cfg(feature = "std")]
#[inline]
pub(crate) fn write_hex_to_writer<W: Write + ?Sized>(
    e: u8,
    output: &mut W,
) -> Result<(), io::Error> {
    let entity =
        [b'&', b'#', b'x', HEX_DIGITS[(e >> 4) as usize], HEX_DIGITS[(e & 0xF) as usize], b';'];

    output.write_all(&entity)
}

#[inline]
pub(crate) fn write_html_entity_to_vec(e: u8, output: &mut Vec<u8>) {
    match e {
        b'&' => output.extend_from_slice(b"&amp;"),
        b'<' => output.extend_from_slice(b"&lt;"),
        b'>' => output.extend_from_slice(b"&gt;"),
        b'"' => output.extend_from_slice(b"&quot;"),
        _ => write_hex_to_vec(e, output),
    }
}

#[cfg(feature = "std")]
#[inline]
pub(crate) fn write_html_entity_to_writer<W: Write + ?Sized>(
    e: u8,
    output: &mut W,
) -> Result<(), io::Error> {
    match e {
        b'&' => output.write_all(b"&amp;"),
        b'<' => output.write_all(b"&lt;"),
        b'>' => output.write_all(b"&gt;"),
        b'"' => output.write_all(b"&quot;"),
        _ => write_hex_to_writer(e, output),
    }
}

#[inline]
pub(crate) fn write_char_to_vec(c: char, output: &mut Vec<u8>) {
    match c.len_utf8() {
        1 => output.push(c as u8),
        _ => {
            let mut buffer = [0u8; 4];

            output.extend_from_slice(c.encode_utf8(&mut buffer).as_bytes());
        },
    }
}

#[cfg(feature = "std")]
#[inline]
pub(crate) fn write_char_to_writer<W: Write + ?Sized>(
    c: char,
    output: &mut W,
) -> Result<(), io::Error> {
    let mut buffer = [0u8; 4];
    let length = c.encode_utf8(&mut buffer).len();

    output.write_all(&buffer[..length])
}