use super::page::Page; use regex::{Captures, Regex}; use serde::{Deserialize, Serialize}; use std::collections::HashMap; pub const CSS_LINK_FRAGMENT: &'static str = "<link rel='stylesheet' href='{url}'>"; pub const SCRIPT_FRAGMENT: &'static str = "<script src='{url}'></script>"; pub const FAVICON_LINK_FRAGMENT: &'static str = "<link rel='icon' type='image/*' href='{url}'/>"; pub const IMAGE_LINKS_FRAGMENT: &'static str = "<meta name='image' content='{url}'/> <meta property='og:image' content='{url}'/> <meta property='twitter:image' content='{url}'/>"; const HTML_DOC_TEMPLATE: &'static str = " <html lang='{lang}' prefix='og: https://ogp.me/ns#'> <head> <meta charset='UTF-8'> <meta http-equiv='X-UA-Compatible' content='IE=edge'> <meta name='viewport' content='width=device-width, initial-scale=1.0'> <meta name='description' content='{description}'> <meta name='author' content='{author}'> <meta property='og:title' content='{title}'/> <meta property='og:description' content='{description}'/> {image} {favicon} <title>{title}</title> {css} </head> <body> {body} </body> {js} </html> "; pub fn replace_placeholders(template: &str, map: HashMap<String, String>) -> String { let re = Regex::new(r#"\{[a-z]+\}"#).unwrap(); let def = String::new(); re.replace_all(template, |captures: &Captures| { let placeholder = captures.iter().next().unwrap().unwrap().as_str(); let placeholder = placeholder[1..placeholder.len() - 1].to_owned(); // strip the brackets match map.get(&placeholder) { Some(s) => s, None => &def, } }) .to_string() } #[derive(Debug, Serialize, Deserialize, Clone)] pub struct HtmlDoc(String); impl HtmlDoc { pub fn from_page(page: &Page) -> Self { HtmlDoc(replace_placeholders(HTML_DOC_TEMPLATE, page.to_map())) } pub fn new() -> Self { HtmlDoc(String::new()) } } impl std::fmt::Display for HtmlDoc { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!(f, "{}", self.0) } } #[derive(Debug, Serialize, Deserialize, Clone)] pub struct HtmlAttributes(pub HashMap<String, String>); impl HtmlAttributes { pub fn new() -> Self { HtmlAttributes(HashMap::new()) } pub fn get<S: Into<String>>(&self, key: S) -> Option<&String> { self.0.get(&key.into()) } } impl std::fmt::Display for HtmlAttributes { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!( f, "{}", self.0 .iter() .map(|(key, value)| format!(" {}=\"{}\"", key, value)) .collect::<Vec<String>>() .join("") ) } } #[derive(Debug, Serialize, Deserialize, Clone)] pub struct HtmlElement { pub tag: String, pub text: Option<String>, pub contents: Option<Vec<HtmlElement>>, #[serde(default = "HtmlAttributes::new")] pub attrs: HtmlAttributes, } impl HtmlElement { pub fn set_contents(&mut self, contents: Vec<HtmlElement>) { self.contents = Some(contents); } } impl std::fmt::Display for HtmlElement { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { let body = match &self.contents { Some(contents) => contents .iter() .map(|el| el.to_string()) .collect::<Vec<String>>() .join(""), None => match &self.text { Some(text) => text.to_owned(), None => String::new(), }, }; write!(f, "<{}{}>{}</{}>", self.tag, self.attrs, body, self.tag) } } #[cfg(test)] mod test_html_elements { use super::*; #[test] fn text_html_element_to_string() { let html_el = HtmlElement { tag: String::from("p"), text: Some(String::from("Hello")), contents: None, attrs: HtmlAttributes::new(), }; assert_eq!(html_el.to_string(), "<p>Hello</p>") } #[test] fn html_el_with_attrs_to_string() { let mut attrs = HtmlAttributes::new(); attrs.0.insert(String::from("id"), String::from("some-id")); let html_el = HtmlElement { tag: String::from("p"), text: Some(String::from("Hello")), contents: None, attrs: attrs.clone(), }; assert_eq!(html_el.to_string(), "<p id=\"some-id\">Hello</p>"); } #[test] fn complex_html_el_to_string() { let html_el = HtmlElement { tag: String::from("p"), text: None, attrs: HtmlAttributes::new(), contents: Some(vec![ HtmlElement { tag: String::from("span"), text: Some(String::from("Hello ")), contents: None, attrs: HtmlAttributes::new(), }, HtmlElement { tag: String::from("b"), text: Some(String::from("World")), contents: None, attrs: HtmlAttributes::new(), }, ]), }; assert_eq!( html_el.to_string(), "<p><span>Hello </span><b>World</b></p>" ) } }