use crate::directive::{DirectiveError, DirectiveImplementation, Processed}; use crate::page::PageMeta; use crate::site::Site; use crate::wikitext::ParsedDirective; use html_page::{Element, Tag, Visitor}; use std::cmp::Ordering; #[derive(Debug, Default)] struct Headings { headings: Vec<(usize, Element)>, } impl Visitor for Headings { fn visit_text(&mut self, _: &str) {} fn visit_element(&mut self, e: &Element) { match e.tag() { Tag::H1 => self.headings.push((1, e.clone())), Tag::H2 => self.headings.push((2, e.clone())), Tag::H3 => self.headings.push((3, e.clone())), Tag::H4 => self.headings.push((4, e.clone())), Tag::H5 => self.headings.push((5, e.clone())), Tag::H6 => self.headings.push((6, e.clone())), _ => (), } } } #[derive(Debug, Default)] struct Text { text: String, } impl Visitor for Text { fn visit_text(&mut self, s: &str) { self.text.push_str(s); } fn visit_element(&mut self, _: &Element) {} } #[derive(Debug, Default, Eq, PartialEq)] pub struct Toc { levels: String, } impl DirectiveImplementation for Toc { const REQUIRED: &'static [&'static str] = &[]; const ALLOWED: &'static [&'static str] = &["levels"]; const ALLOW_ANY_UNNAMED: bool = true; fn from_parsed(p: &ParsedDirective) -> Self { let args = p.args(); let levels = args.get("levels").unwrap_or(&"9"); Self::new(levels.to_string()) } fn process(&self, _site: &Site, _meta: &mut PageMeta) -> Result { let levels: usize = self .levels .parse() .map_err(|e| DirectiveError::LevelsParse(self.levels.clone(), e))?; Ok(Processed::Toc(levels)) } } impl Toc { fn new(levels: String) -> Self { Self { levels } } pub fn post_process(html: &Element, levels: usize) -> String { let mut headings = Headings::default(); headings.visit(html); let mut stack = vec![]; let mut prev_level: usize = 0; for (level, heading) in headings .headings .iter() .filter(|(level, _)| *level < levels) { match level.cmp(&prev_level) { Ordering::Greater => { let list = Element::new(Tag::Ul); stack.push(list); } Ordering::Less => { assert!(!stack.is_empty()); let ending = stack.pop().unwrap(); assert!(!stack.is_empty()); let parent = stack.last_mut().unwrap(); parent.push_child(&ending); } Ordering::Equal => (), } if let Some(ol) = stack.last_mut() { let mut text = Text::default(); text.visit(heading); let li = Element::new(Tag::Li).with_text(&text.text); ol.push_child(&li); } prev_level = *level; } while stack.len() > 1 { assert!(!stack.is_empty()); let ending = stack.pop().unwrap(); assert!(!stack.is_empty()); let parent = stack.last_mut().unwrap(); parent.push_child(&ending); } let mut toc = Element::new(Tag::Div).with_child(Element::new(Tag::H1).with_text("Contents")); if let Some(e) = stack.get(0) { toc.push_child(e); } format!("{}", toc) } }