use regex::Regex;
use liquid_value::Value;
use super::check_args_len;
use compiler::FilterResult;
fn nr_escaped(text: &str) -> usize {
for prefix in &["lt;", "gt;", "#39;", "quot;", "amp;"] {
if text.starts_with(prefix) {
return prefix.len();
}
}
0
}
fn _escape(input: &Value, args: &[Value], once_p: bool) -> FilterResult {
check_args_len(args, 0, 0)?;
let s = input.to_str();
let mut result = String::new();
let mut last = 0;
let mut skip = 0;
for (i, c) in s.char_indices() {
if skip > 0 {
skip -= 1;
continue;
}
match c as char {
'<' | '>' | '\'' | '"' | '&' => {
result.push_str(&s[last..i]);
last = i + 1;
let escaped = match c as char {
'<' => "<",
'>' => ">",
'\'' => "'",
'"' => """,
'&' => {
if once_p {
skip = nr_escaped(&s[last..]);
}
if skip == 0 {
"&"
} else {
"&"
}
}
_ => unreachable!(),
};
result.push_str(escaped);
}
_ => {}
}
}
if last < s.len() {
result.push_str(&s[last..]);
}
Ok(Value::scalar(result))
}
pub fn escape(input: &Value, args: &[Value]) -> FilterResult {
_escape(input, args, false)
}
pub fn escape_once(input: &Value, args: &[Value]) -> FilterResult {
_escape(input, args, true)
}
pub fn strip_html(input: &Value, args: &[Value]) -> FilterResult {
lazy_static! {
static ref MATCHERS: [Regex; 4] = [Regex::new(r"(?is)<script.*?</script>").unwrap(),
Regex::new(r"(?is)<style.*?</style>").unwrap(),
Regex::new(r"(?is)<!--.*?-->").unwrap(),
Regex::new(r"(?is)<.*?>").unwrap()];
}
check_args_len(args, 0, 0)?;
let input = input.to_str().into_owned();
let result = MATCHERS.iter().fold(input, |acc, matcher| {
matcher.replace_all(&acc, "").into_owned()
});
Ok(Value::scalar(result))
}
pub fn newline_to_br(input: &Value, args: &[Value]) -> FilterResult {
check_args_len(args, 0, 0)?;
let input = input.to_str();
Ok(Value::scalar(input.replace("\n", "<br />\n")))
}
#[cfg(test)]
mod tests {
use super::*;
macro_rules! unit {
($a:ident, $b:expr) => {{
unit!($a, $b, &[])
}};
($a:ident, $b:expr, $c:expr) => {{
$a(&$b, $c).unwrap()
}};
}
macro_rules! failed {
($a:ident, $b:expr) => {{
failed!($a, $b, &[])
}};
($a:ident, $b:expr, $c:expr) => {{
$a(&$b, $c).unwrap_err()
}};
}
macro_rules! tos {
($a:expr) => {{
Value::scalar($a.to_owned())
}};
}
#[test]
fn unit_escape() {
assert_eq!(
unit!(escape, tos!("Have you read 'James & the Giant Peach'?")),
tos!("Have you read 'James & the Giant Peach'?")
);
assert_eq!(
unit!(escape, tos!("Tetsuro Takara")),
tos!("Tetsuro Takara")
);
}
#[test]
fn unit_escape_non_ascii() {
assert_eq!(
unit!(escape, tos!("word¹ <br> word¹")),
tos!("word¹ <br> word¹")
);
}
#[test]
fn unit_escape_once() {
assert_eq!(
unit!(escape_once, tos!("1 < 2 & 3")),
tos!("1 < 2 & 3")
);
assert_eq!(
unit!(escape_once, tos!("1 < 2 & 3")),
tos!("1 < 2 & 3")
);
assert_eq!(
unit!(escape_once, tos!("<>&'"&xyz;")),
tos!("<>&'"&xyz;")
);
}
#[test]
fn unit_strip_html() {
assert_eq!(
unit!(
strip_html,
tos!("<script type=\"text/javascript\">alert('Hi!');</script>"),
&[]
),
tos!("")
);
assert_eq!(
unit!(
strip_html,
tos!("<SCRIPT type=\"text/javascript\">alert('Hi!');</SCRIPT>"),
&[]
),
tos!("")
);
assert_eq!(unit!(strip_html, tos!("<p>test</p>"), &[]), tos!("test"));
assert_eq!(
unit!(strip_html, tos!("<p id='xxx'>test</p>"), &[]),
tos!("test")
);
assert_eq!(
unit!(
strip_html,
tos!("<style type=\"text/css\">cool style</style>"),
&[]
),
tos!("")
);
assert_eq!(
unit!(strip_html, tos!("<p\nclass='loooong'>test</p>"), &[]),
tos!("test")
);
assert_eq!(
unit!(strip_html, tos!("<!--\n\tcomment\n-->test"), &[]),
tos!("test")
);
assert_eq!(unit!(strip_html, tos!(""), &[]), tos!(""));
}
#[test]
fn unit_newline_to_br() {
let input = &tos!("a\nb");
let args = &[];
let desired_result = tos!("a<br />\nb");
assert_eq!(unit!(newline_to_br, input, args), desired_result);
}
#[test]
fn unit_newline_to_br_hello_world() {
let input = &tos!("\nHello\nWorld\n");
let args = &[];
let desired_result = tos!("<br />\nHello<br />\nWorld<br />\n");
assert_eq!(unit!(newline_to_br, input, args), desired_result);
}
#[test]
fn unit_newline_to_br_one_argument() {
let input = &tos!("a\nb");
let args = &[Value::scalar(0f64)];
failed!(newline_to_br, input, args);
}
}