use once_cell::sync::Lazy;
use regex::{
Captures,
Regex,
};
static UNESCAPED_HTML: Lazy<Regex> =
Lazy::new(|| Regex::new(r#"[&<>"']"#).expect("unable to build the pattern"));
pub fn escape<S>(value: S) -> String
where
S: AsRef<str>,
{
let value = value.as_ref();
if UNESCAPED_HTML.is_match(value) {
UNESCAPED_HTML
.replace_all(value, |cap: &Captures| {
let item = &cap[0];
match item {
"&" => "&",
"<" => "<",
">" => ">",
r#"""# => """,
"'" => "'",
_ => item,
}
.to_owned()
})
.to_string()
} else {
value.to_string()
}
}
pub trait Escape<S>
where
S: AsRef<str> + ?Sized,
{
fn escape(&self) -> String;
}
impl Escape<String> for String {
fn escape(&self) -> String {
escape(self)
}
}
impl Escape<str> for str {
fn escape(&self) -> String {
escape(self)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{
unescape,
Unescape,
};
#[test]
fn can_escape_values() {
assert_eq!(
escape(r#"&<>"'/&<>"'/"#),
"&<>"'/&<>"'/".to_string()
);
assert_eq!(escape(r#"`"#), "`".to_string());
assert_eq!(escape(r#"/"#), "/".to_string());
assert_eq!(
escape(unescape(
r#"&<>"'/&<>"'/"#
)),
"&<>"'/&<>"'/".to_string()
);
assert_eq!(
r#"&<>"'/&<>"'/"#.escape(),
"&<>"'/&<>"'/".to_string()
);
assert_eq!(r#"`"#.escape(), "`".to_string());
assert_eq!(r#"/"#.escape(), "/".to_string());
assert_eq!(
r#"&<>"'/&<>"'/"#
.unescape()
.escape(),
"&<>"'/&<>"'/".to_string()
);
}
#[test]
fn can_handle_strings_with_nothing_to_escape() {
assert_eq!(escape("abc"), "abc".to_string());
assert_eq!("abc".escape(), "abc".to_string());
}
}