use std::fmt;
use crate::engine::{encode_loop, is_unicode_noncharacter};
pub fn for_css_string(input: &str) -> String {
let mut out = String::with_capacity(input.len());
write_css_string(&mut out, input).expect("writing to string cannot fail");
out
}
pub fn write_css_string<W: fmt::Write>(out: &mut W, input: &str) -> fmt::Result {
encode_loop(out, input, needs_css_string_encoding, write_css_encoded)
}
fn needs_css_string_encoding(c: char) -> bool {
needs_css_common_encoding(c) || matches!(c, '(' | ')')
}
pub fn for_css_url(input: &str) -> String {
let mut out = String::with_capacity(input.len());
write_css_url(&mut out, input).expect("writing to string cannot fail");
out
}
pub fn write_css_url<W: fmt::Write>(out: &mut W, input: &str) -> fmt::Result {
encode_loop(out, input, needs_css_url_encoding, write_css_encoded)
}
fn needs_css_url_encoding(c: char) -> bool {
needs_css_common_encoding(c)
}
fn needs_css_common_encoding(c: char) -> bool {
let cp = c as u32;
cp <= 0x1F
|| matches!(c, '"' | '\'' | '\\' | '<' | '&' | '/' | '>')
|| (0x7F..=0x9F).contains(&cp) || cp == 0x2028
|| cp == 0x2029
|| is_unicode_noncharacter(cp)
}
fn write_css_encoded<W: fmt::Write>(out: &mut W, c: char, next: Option<char>) -> fmt::Result {
let cp = c as u32;
if is_unicode_noncharacter(cp) {
return out.write_char('_');
}
write!(out, "\\{:x}", cp)?;
if needs_css_separator(next) {
out.write_char(' ')?;
}
Ok(())
}
fn needs_css_separator(next: Option<char>) -> bool {
match next {
Some(c) => c.is_ascii_hexdigit() || matches!(c, ' ' | '\t' | '\n' | '\x0C' | '\r'),
None => false,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn css_string_no_encoding_needed() {
assert_eq!(for_css_string("hello"), "hello");
assert_eq!(for_css_string(""), "");
}
#[test]
fn css_string_encodes_double_quote() {
assert_eq!(for_css_string(r#"a"b"#), r"a\22 b");
assert_eq!(for_css_string(r#"a""#), r"a\22");
}
#[test]
fn css_string_encodes_single_quote() {
assert_eq!(for_css_string("a'z"), r"a\27z");
assert_eq!(for_css_string("a'1"), r"a\27 1");
}
#[test]
fn css_string_encodes_backslash() {
assert_eq!(for_css_string(r"a\b"), r"a\5c b");
}
#[test]
fn css_string_encodes_angle_brackets() {
assert_eq!(for_css_string("<x>"), r"\3cx\3e");
}
#[test]
fn css_string_encodes_ampersand() {
assert_eq!(for_css_string("a&b"), r"a\26 b");
}
#[test]
fn css_string_encodes_parens() {
assert_eq!(for_css_string("a(b)"), r"a\28 b\29");
}
#[test]
fn css_string_encodes_slash() {
assert_eq!(for_css_string("a/b"), r"a\2f b");
}
#[test]
fn css_string_encodes_control_chars() {
assert_eq!(for_css_string("\x00"), r"\0");
assert_eq!(for_css_string("\x01x"), r"\1x");
assert_eq!(for_css_string("\x1F"), r"\1f");
}
#[test]
fn css_string_encodes_del() {
assert_eq!(for_css_string("\x7F"), r"\7f");
}
#[test]
fn css_string_encodes_c1_controls() {
assert_eq!(for_css_string("\u{0080}"), r"\80");
assert_eq!(for_css_string("\u{0085}"), r"\85");
assert_eq!(for_css_string("\u{009F}"), r"\9f");
assert_eq!(for_css_string("\u{0080}a"), r"\80 a");
assert_eq!(for_css_string("\u{0080}z"), r"\80z");
}
#[test]
fn css_string_encodes_line_separators() {
assert_eq!(for_css_string("\u{2028}"), r"\2028");
assert_eq!(for_css_string("\u{2029}"), r"\2029");
}
#[test]
fn css_string_replaces_nonchars_with_underscore() {
assert_eq!(for_css_string("\u{FDD0}"), "_");
assert_eq!(for_css_string("\u{FFFE}"), "_");
assert_eq!(for_css_string("\u{FFFF}"), "_");
}
#[test]
fn css_string_separator_before_whitespace() {
assert_eq!(for_css_string("' "), r"\27 ");
}
#[test]
fn css_string_preserves_non_ascii() {
assert_eq!(for_css_string("café"), "café");
}
#[test]
fn css_string_writer_variant() {
let mut out = String::new();
write_css_string(&mut out, "a'b").unwrap();
assert_eq!(out, r"a\27 b");
}
#[test]
fn css_url_does_not_encode_parens() {
assert_eq!(for_css_url("a(b)c"), "a(b)c");
}
#[test]
fn css_url_encodes_quotes() {
assert_eq!(for_css_url("a'b"), r"a\27 b");
}
#[test]
fn css_url_encodes_backslash() {
assert_eq!(for_css_url(r"a\b"), r"a\5c b");
}
#[test]
fn css_url_encodes_c1_controls() {
assert_eq!(for_css_url("\u{0080}"), r"\80");
assert_eq!(for_css_url("\u{0085}"), r"\85");
assert_eq!(for_css_url("\u{009F}"), r"\9f");
}
}