Skip to main content

xil_web/
html.rs

1//! Small HTML builders. Every value from disk or from a request goes through
2//! [`esc`] before it lands in markup.
3
4/// Escape text for an element body or a double-quoted attribute.
5pub fn esc(s: &str) -> String {
6    let mut out = String::with_capacity(s.len());
7    for c in s.chars() {
8        match c {
9            '&' => out.push_str("&"),
10            '<' => out.push_str("&lt;"),
11            '>' => out.push_str("&gt;"),
12            '"' => out.push_str("&quot;"),
13            '\'' => out.push_str("&#39;"),
14            _ => out.push(c),
15        }
16    }
17    out
18}
19
20/// `<option>`s whose value is also the label.
21pub fn options(values: &[String], selected: Option<&str>) -> String {
22    values
23        .iter()
24        .map(|v| {
25            let sel = if Some(v.as_str()) == selected {
26                " selected"
27            } else {
28                ""
29            };
30            format!("<option value=\"{0}\"{sel}>{0}</option>", esc(v))
31        })
32        .collect()
33}
34
35/// `<option>`s from `(label, value)` pairs.
36pub fn labelled_options(pairs: &[(String, String)], selected: Option<&str>) -> String {
37    pairs
38        .iter()
39        .map(|(label, value)| {
40            let sel = if Some(value.as_str()) == selected {
41                " selected"
42            } else {
43                ""
44            };
45            format!(
46                "<option value=\"{}\"{sel}>{}</option>",
47                esc(value),
48                esc(label)
49            )
50        })
51        .collect()
52}
53
54/// A labelled checkbox that posts `on` when ticked.
55pub fn checkbox(name: &str, label: &str, checked: bool) -> String {
56    format!(
57        "<label class=\"check\"><input type=\"checkbox\" name=\"{name}\"{}> {}</label>",
58        if checked { " checked" } else { "" },
59        esc(label)
60    )
61}
62
63/// A labelled text input.
64pub fn text_input(name: &str, label: &str, placeholder: &str) -> String {
65    format!(
66        "<label class=\"field\"><span>{}</span><input name=\"{name}\" placeholder=\"{}\"></label>",
67        esc(label),
68        esc(placeholder)
69    )
70}
71
72/// A labelled number input.
73pub fn number_input(name: &str, label: &str, value: i64) -> String {
74    format!(
75        "<label class=\"field\"><span>{}</span><input type=\"number\" min=\"0\" step=\"1\" name=\"{name}\" value=\"{value}\"></label>",
76        esc(label)
77    )
78}
79
80/// A status line.
81pub fn status(text: &str) -> String {
82    format!("<div class=\"status\">{}</div>", esc(text))
83}
84
85#[cfg(test)]
86mod tests {
87    use super::*;
88
89    #[test]
90    fn escapes_markup_and_quotes() {
91        assert_eq!(
92            esc("<a href=\"x\">'&'</a>"),
93            "&lt;a href=&quot;x&quot;&gt;&#39;&amp;&#39;&lt;/a&gt;"
94        );
95        assert_eq!(
96            options(&["a<b".into(), "c".into()], Some("c")),
97            "<option value=\"a&lt;b\">a&lt;b</option><option value=\"c\" selected>c</option>"
98        );
99    }
100}