Skip to main content

whisker_css/data_type/
string.rs

1//! `<string>` — a quoted CSS string literal.
2//!
3//! Lynx reference: <https://lynxjs.org/api/css/data-type/string.html>
4//!
5//! Used by properties such as `content`, `font-family` (when the
6//! family name contains whitespace or punctuation), and the
7//! `url("...")` function. The string is double-quoted on
8//! serialization; backslashes and embedded double quotes are
9//! escaped per CSS rules.
10
11use core::fmt;
12
13use crate::to_css::ToCss;
14
15/// A CSS `<string>` value.
16#[derive(Clone, Debug, PartialEq, Eq, Hash)]
17pub struct CssString(pub String);
18
19impl CssString {
20    /// Construct from anything string-like.
21    pub fn new(s: impl Into<String>) -> Self {
22        Self(s.into())
23    }
24
25    /// Borrow the inner `&str`.
26    pub fn as_str(&self) -> &str {
27        &self.0
28    }
29}
30
31impl From<&str> for CssString {
32    fn from(s: &str) -> Self {
33        Self(s.to_string())
34    }
35}
36
37impl From<String> for CssString {
38    fn from(s: String) -> Self {
39        Self(s)
40    }
41}
42
43impl ToCss for CssString {
44    fn to_css(&self, dest: &mut dyn fmt::Write) -> fmt::Result {
45        dest.write_char('"')?;
46        for ch in self.0.chars() {
47            match ch {
48                '"' => dest.write_str("\\\"")?,
49                '\\' => dest.write_str("\\\\")?,
50                '\n' => dest.write_str("\\A ")?,
51                '\r' => dest.write_str("\\D ")?,
52                c => dest.write_char(c)?,
53            }
54        }
55        dest.write_char('"')
56    }
57}
58
59#[cfg(test)]
60mod tests {
61    use super::*;
62
63    #[test]
64    fn plain_string_is_quoted() {
65        assert_eq!(CssString::new("hello").to_css_string(), "\"hello\"");
66    }
67
68    #[test]
69    fn embedded_quote_is_escaped() {
70        assert_eq!(CssString::new("a\"b").to_css_string(), "\"a\\\"b\"");
71    }
72
73    #[test]
74    fn backslash_is_escaped() {
75        assert_eq!(CssString::new("a\\b").to_css_string(), "\"a\\\\b\"");
76    }
77
78    #[test]
79    fn newline_uses_css_escape() {
80        assert_eq!(CssString::new("a\nb").to_css_string(), "\"a\\A b\"");
81    }
82
83    #[test]
84    fn from_str_and_string() {
85        let from_str: CssString = "x".into();
86        let from_string: CssString = String::from("y").into();
87        assert_eq!(from_str.to_css_string(), "\"x\"");
88        assert_eq!(from_string.to_css_string(), "\"y\"");
89        assert_eq!(from_str.as_str(), "x");
90    }
91}