pub fn strip_html(input: &str) -> String { let mut result = String::with_capacity(input.len()); let mut in_tag = false; let mut in_entity = false; let mut entity = String::new(); for c in input.chars() { match c { '<' => in_tag = true, '>' if in_tag => in_tag = false, '&' if !in_tag => { in_entity = true; entity.clear(); } ';' if in_entity => { in_entity = false; let decoded = match entity.as_str() { "amp" => "&", "lt" => "<", "gt" => ">", "quot" => "\"", "nbsp" => " ", _ => "", }; result.push_str(decoded); } c if !in_tag && !in_entity => result.push(c), c if in_entity => entity.push(c), _ => {} } } result } #[derive(Debug, Clone)] pub struct TocEntry { pub label: String, pub section: usize, pub children: Vec, } #[derive(Debug, Clone)] pub struct Section { pub title: String, pub content: String, pub pages: Vec, } #[derive(Debug, Clone)] pub struct Book { pub title: String, pub author: String, pub cover: Option>, pub sections: Vec
, pub toc: Vec, } #[cfg(test)] mod tests { use super::*; #[test] fn test_strip_html_plain_text() { assert_eq!(strip_html("Hello World"), "Hello World"); } #[test] fn test_strip_html_simple_tags() { assert_eq!(strip_html("

Hello

"), "Hello"); } #[test] fn test_strip_html_nested_tags() { assert_eq!( strip_html("

Hello World

"), "Hello World" ); } #[test] fn test_strip_html_html_entities() { assert_eq!(strip_html("Hello & World"), "Hello & World"); assert_eq!(strip_html("Hello World"), "Hello World"); } #[test] fn test_strip_html_empty() { assert_eq!(strip_html(""), ""); } }