1#[must_use]
7pub fn html_escape(s: &str) -> String {
8 s.replace('&', "&")
9 .replace('<', "<")
10 .replace('>', ">")
11 .replace('"', """)
12 .replace('\'', "'")
13}
14
15#[must_use]
20pub fn slugify(text: &str) -> String {
21 text.to_lowercase()
22 .chars()
23 .map(|c| {
24 if c.is_alphanumeric() {
25 c
26 } else if c.is_whitespace() || c == '-' || c == '_' {
27 '-'
28 } else {
29 '\0'
30 }
31 })
32 .filter(|c| *c != '\0')
33 .collect::<String>()
34 .split('-')
35 .filter(|s| !s.is_empty())
36 .collect::<Vec<_>>()
37 .join("-")
38}
39
40#[must_use]
45pub fn strip_html(html: &str) -> String {
46 let mut result = String::with_capacity(html.len());
47 let mut in_tag = false;
48 let mut in_script = false;
49 let mut in_style = false;
50
51 let html_lower = html.to_lowercase();
52 let chars: Vec<char> = html.chars().collect();
53 let chars_lower: Vec<char> = html_lower.chars().collect();
54
55 let mut i = 0;
56 while i < chars.len() {
57 let c = chars[i];
58
59 if i + 7 < chars.len() {
61 let next_7: String = chars_lower[i..i + 7].iter().collect();
62 if next_7 == "<script" {
63 in_script = true;
64 } else if next_7 == "</scrip" {
65 in_script = false;
66 }
67 }
68
69 if i + 6 < chars.len() {
70 let next_6: String = chars_lower[i..i + 6].iter().collect();
71 if next_6 == "<style" {
72 in_style = true;
73 } else if next_6 == "</styl" {
74 in_style = false;
75 }
76 }
77
78 if c == '<' {
79 in_tag = true;
80 } else if c == '>' {
81 in_tag = false;
82 } else if !in_tag && !in_script && !in_style {
83 result.push(c);
84 }
85
86 i += 1;
87 }
88
89 result = result
91 .replace(" ", " ")
92 .replace("&", "&")
93 .replace("<", "<")
94 .replace(">", ">")
95 .replace(""", "\"")
96 .replace("'", "'");
97
98 let mut collapsed = String::with_capacity(result.len());
100 let mut prev_space = false;
101 for c in result.chars() {
102 if c.is_whitespace() {
103 if !prev_space {
104 collapsed.push(' ');
105 prev_space = true;
106 }
107 } else {
108 collapsed.push(c);
109 prev_space = false;
110 }
111 }
112
113 collapsed.trim().to_string()
114}
115
116#[cfg(test)]
117mod tests {
118 use super::*;
119
120 #[test]
121 fn test_html_escape() {
122 assert_eq!(html_escape("a & b"), "a & b");
123 assert_eq!(html_escape("<div>"), "<div>");
124 assert_eq!(html_escape(r#"a "b" c"#), "a "b" c");
125 assert_eq!(html_escape("it's"), "it's");
126 }
127
128 #[test]
129 fn test_slugify() {
130 assert_eq!(slugify("Hello World"), "hello-world");
131 assert_eq!(slugify("foo_bar"), "foo-bar");
132 assert_eq!(slugify(" multiple spaces "), "multiple-spaces");
133 assert_eq!(slugify("special!@#chars"), "specialchars");
134 }
135
136 #[test]
137 fn test_strip_html() {
138 assert_eq!(strip_html("<p>Hello</p>"), "Hello");
139 assert_eq!(strip_html("<script>alert(1)</script>"), "");
140 assert_eq!(
141 strip_html("<b>bold</b> and <i>italic</i>"),
142 "bold and italic"
143 );
144 assert_eq!(strip_html("a & b"), "a & b");
145 assert_eq!(
146 strip_html(" multiple <br> spaces "),
147 "multiple spaces"
148 );
149 }
150}