pub fn escape_html_content(output: &mut String, input: &str) {
let output = unsafe { output.as_mut_vec() };
let mut haystack = input.as_bytes();
while let Some(index) = memchr::memchr3(b'&', b'<', b'>', haystack) {
let Some((before, after)) = haystack.split_at_checked(index) else {
break;
};
output.extend_from_slice(before);
let Some((special_char, rest)) = after.split_first() else {
break;
};
match special_char {
b'&' => output.extend_from_slice(b"&"),
b'<' => output.extend_from_slice(b"<"),
b'>' => output.extend_from_slice(b">"),
_ => {}
}
haystack = rest;
}
output.extend_from_slice(haystack);
}
pub fn escape_double_quoted_html_attribute(output: &mut String, input: &str) {
let output = unsafe { output.as_mut_vec() };
for ch in input.bytes() {
match ch {
b'&' => output.extend_from_slice(b"&"),
b'"' => output.extend_from_slice(b"""),
_ => output.push(ch),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_empty_input() {
let mut output = String::new();
escape_html_content(&mut output, "");
assert_eq!(output, "");
}
#[test]
fn test_no_special_characters() {
let mut output = String::new();
escape_html_content(&mut output, "Hello, World!");
assert_eq!(output, "Hello, World!");
}
#[test]
fn test_escape_ampersand() {
let mut output = String::new();
escape_html_content(&mut output, "Tom & Jerry");
assert_eq!(output, "Tom & Jerry");
}
#[test]
fn test_escape_less_than() {
let mut output = String::new();
escape_html_content(&mut output, "5 < 10");
assert_eq!(output, "5 < 10");
}
#[test]
fn test_escape_greater_than() {
let mut output = String::new();
escape_html_content(&mut output, "10 > 5");
assert_eq!(output, "10 > 5");
}
#[test]
fn test_all_special_characters() {
let mut output = String::new();
escape_html_content(&mut output, "<tag>&content</tag>");
assert_eq!(output, "<tag>&content</tag>");
}
#[test]
fn test_consecutive_special_characters() {
let mut output = String::new();
escape_html_content(&mut output, "&<>");
assert_eq!(output, "&<>");
}
#[test]
fn test_special_at_start() {
let mut output = String::new();
escape_html_content(&mut output, "<html>");
assert_eq!(output, "<html>");
}
#[test]
fn test_special_at_end() {
let mut output = String::new();
escape_html_content(&mut output, "test&");
assert_eq!(output, "test&");
}
#[test]
fn test_only_special_characters() {
let mut output = String::new();
escape_html_content(&mut output, "&&&<<<>>>");
assert_eq!(output, "&&&<<<>>>");
}
#[test]
fn test_utf8_with_special_characters() {
let mut output = String::new();
escape_html_content(&mut output, "Hello 世界 & <test>");
assert_eq!(output, "Hello 世界 & <test>");
}
#[test]
fn test_appends_to_existing_output() {
let mut output = "prefix: ".to_string();
escape_html_content(&mut output, "<tag>");
assert_eq!(output, "prefix: <tag>");
}
#[test]
fn test_long_text_with_few_escapes() {
let mut output = String::new();
let input = "This is a very long string with only one special character at the end: <";
escape_html_content(&mut output, input);
assert_eq!(
output,
"This is a very long string with only one special character at the end: <"
);
}
#[test]
fn test_alternating_pattern() {
let mut output = String::new();
escape_html_content(&mut output, "a<b>c&d<e>f&");
assert_eq!(output, "a<b>c&d<e>f&");
}
#[test]
fn test_single_byte_inputs() {
let mut output = String::new();
escape_html_content(&mut output, "&");
assert_eq!(output, "&");
output.clear();
escape_html_content(&mut output, "<");
assert_eq!(output, "<");
output.clear();
escape_html_content(&mut output, ">");
assert_eq!(output, ">");
output.clear();
escape_html_content(&mut output, "a");
assert_eq!(output, "a");
}
}