summaryrefslogtreecommitdiff
path: root/src/directive/toc.rs
blob: fa115b69ee85d97e684208f9941e3cdebfe69573 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
use crate::directive::{DirectiveError, DirectiveImplementation, Processed};
use crate::html::{Content, Element, ElementTag};
use crate::page::PageMeta;
use crate::site::Site;
use crate::wikitext::ParsedDirective;

#[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<Processed, DirectiveError> {
        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 headings: Vec<(usize, &[Content])> = html
            .children()
            .iter()
            .filter_map(|c| match c {
                Content::Elt(e) => Some(e),
                _ => None,
            })
            .filter_map(|e| match e.tag() {
                ElementTag::H1 => Some((1, e.children())),
                ElementTag::H2 => Some((2, e.children())),
                ElementTag::H3 => Some((3, e.children())),
                ElementTag::H4 => Some((4, e.children())),
                ElementTag::H5 => Some((5, e.children())),
                ElementTag::H6 => Some((6, e.children())),
                _ => None,
            })
            .collect();
        let mut html = String::new();
        let mut prev_level: usize = 0;
        for (level, text) in headings.iter() {
            if *level > levels {
                continue;
            } else if *level > prev_level {
                html.push_str("<ol>");
            } else if *level < prev_level {
                html.push_str("</ol>\n");
            }
            html.push_str("<li>");
            Self::stringify(&mut html, text);
            html.push_str("</li>\n");
            prev_level = *level;
        }
        for _ in 0..prev_level {
            html.push_str("</ol>\n");
        }
        html
    }

    fn stringify(buf: &mut String, bits: &[Content]) {
        for c in bits.iter() {
            match c {
                Content::Text(s) => buf.push_str(s),
                Content::Elt(e) => Self::stringify(buf, e.children()),
                Content::Html(h) => buf.push_str(h),
            }
        }
    }
}