nmd_core/
table_of_contents.rs

1pub mod content_tree;
2
3use getset::{CopyGetters, Getters, Setters};
4use serde::Serialize;
5use crate::{codex::Codex, compilable_text::{compilable_text_part::CompilableTextPart, CompilableText}, compilation::{compilable::Compilable, compilation_configuration::{compilation_configuration_overlay::CompilationConfigurationOverLay, CompilationConfiguration}, compilation_error::CompilationError, compilation_outcome::CompilationOutcome}, dossier::document::chapter::heading::HeadingLevel, output_format::OutputFormat};
6use super::dossier::document::chapter::heading::Heading;
7
8
9pub const TOC_INDENTATION: &str = r#"<span class="toc-item-indentation"></span>"#;
10
11
12
13#[derive(Debug, Clone, Getters, CopyGetters, Setters, Serialize)]
14pub struct TableOfContents {
15
16    #[getset(get = "pub", set = "pub")]
17    title: String,
18
19    #[getset(get_copy = "pub", set = "pub")]
20    page_numbers: bool,
21
22    #[getset(get_copy = "pub", set = "pub")]
23    plain: bool,
24
25    #[getset(get_copy = "pub", set = "pub")]
26    maximum_heading_level: usize,
27
28    #[getset(get = "pub", set = "pub")]
29    headings: Vec<Heading>,
30}
31
32impl TableOfContents {
33    pub fn new(title: String, page_numbers: bool, plain: bool, maximum_heading_level: usize, headings: Vec<Heading>) -> Self {
34        Self {
35            title,
36            page_numbers,
37            plain,
38            maximum_heading_level,
39            headings,
40        }
41    }
42
43    /// Return minimum header level (if exists)
44    fn min_headers_lv(headings: &Vec<Heading>) -> Result<u32, CompilationError> {
45        let mut m: u32 = 0;
46
47        for h in headings {
48
49            let level = if let HeadingLevel::Explicit(l) = h.level() {
50                *l
51            } else {
52
53                return Err(CompilationError::HeadingLevelNotInferable(h.title().to_string()))
54            };
55            
56            m = m.min(level);
57        }
58
59        Ok(m)
60    }
61}
62
63impl Compilable for TableOfContents {
64
65    fn standard_compile(&mut self, format: &OutputFormat, codex: &Codex, compilation_configuration: &CompilationConfiguration, compilation_configuration_overlay: CompilationConfigurationOverLay) -> Result<CompilationOutcome, CompilationError> {
66        
67        if self.headings().is_empty() {
68            
69            return Ok(CompilationOutcome::empty());
70        }
71
72        if self.page_numbers() {
73            log::error!("table of contents with page numbers not already usable...");
74
75            unimplemented!("table of contents with page numbers not already usable...");
76        }
77
78        let min_heading_lv = Self::min_headers_lv(self.headings())?;
79        
80        match format {
81            OutputFormat::Html => {
82                let mut outcome = CompilableText::new_empty();
83
84                let mut compiled_title = CompilableText::from(self.title.clone());
85
86                compiled_title.compile(format, codex, compilation_configuration, compilation_configuration_overlay.clone())?;
87
88                outcome.parts_mut().push(CompilableTextPart::new_fixed(String::from(r#"<section class="toc"><div class="toc-title">"#)));
89                outcome.parts_mut().append(compiled_title.parts_mut());
90                outcome.parts_mut().push(CompilableTextPart::new_fixed(String::from(r#"</div><ul class="toc-body">"#)));
91
92                let mut total_li = 0;
93
94                for heading in self.headings() {
95
96                    let heading_lv: u32 = if let HeadingLevel::Explicit(l) = heading.level() {
97
98                        *l
99                        
100                    } else {
101
102                        return Err(CompilationError::HeadingLevelNotInferable(heading.title().to_string()))
103                    };
104
105                    if heading_lv > self.maximum_heading_level() as u32 {
106                        continue;
107                    }
108
109                    outcome.parts_mut().push(CompilableTextPart::new_fixed(String::from(r#"<li class="toc-item">"#)));
110
111                    if !self.plain() {
112
113                        outcome.parts_mut().push(CompilableTextPart::new_fixed(TOC_INDENTATION.repeat((heading_lv - min_heading_lv) as usize)));
114                    }
115
116                    outcome.parts_mut().push(CompilableTextPart::new_fixed(r#"<span class="toc-item-bullet"></span><span class="toc-item-content">"#.to_string()));
117
118                    if let Some(id) = heading.resource_reference() {
119
120                        outcome.parts_mut().push(CompilableTextPart::new_fixed(format!(r#"<a href="{}" class="link">"#, id.build())));
121                    
122                    } else {
123                        log::warn!("heading '{}' does not have a valid id", heading.title())
124                    }
125
126                    let compilation_configuration_overlay = compilation_configuration_overlay.clone();
127
128                    let mut compiled_heading_title = CompilableText::from(heading.title().clone());
129                    
130                    compiled_heading_title.compile(format, codex, compilation_configuration, compilation_configuration_overlay.clone())?;
131
132                    outcome.parts_mut().append(compiled_heading_title.parts_mut());
133
134                    if let Some(_) = heading.resource_reference() {
135
136                        outcome.parts_mut().push(CompilableTextPart::new_fixed(String::from(r#"</a>"#)));
137                    }
138
139                    outcome.parts_mut().push(CompilableTextPart::new_fixed(String::from(r#"</span></li>"#)));
140
141                    total_li += 1;
142                        
143                }
144
145                outcome.parts_mut().push(CompilableTextPart::new_fixed(String::from(r#"</ul></section>"#)));
146
147                log::info!("compiled table of contents ({} lines, {} skipped)", total_li, self.headings().len() - total_li);
148
149                Ok(CompilationOutcome::from(&outcome))
150            },
151        }
152    }
153
154}