Skip to main content

nmd_core/
content_bundle.rs

1use getset::{Getters, MutGetters, Setters};
2use rayon::{iter::{IntoParallelRefMutIterator, ParallelIterator}, slice::ParallelSliceMut};
3use serde::Serialize;
4use crate::{codex::Codex, compilable_text::CompilableText, compilation::{compilable::Compilable, compilation_configuration::{compilation_configuration_overlay::CompilationConfigurationOverLay, CompilationConfiguration}, compilation_error::CompilationError, compilation_outcome::CompilationOutcome}, dossier::document::{chapter::{heading::HeadingLevel, paragraph::Paragraph}, Chapter}, load_block::{LoadBlock, LoadBlockContent}, output_format::OutputFormat};
5
6
7#[derive(Debug, Getters, MutGetters, Setters, Serialize)]
8pub struct ContentBundle {
9    #[getset(get = "pub", get_mut = "pub", set = "pub")]
10    #[serde(skip)]      // TODO
11    preamble: Vec<Box<dyn Paragraph>>,
12
13    #[getset(get = "pub", get_mut = "pub", set = "pub")]
14    chapters: Vec<Chapter>,
15
16    compiled_content: Option<CompilableText>,
17}
18
19
20impl ContentBundle {
21
22    pub fn new(preamble: Vec<Box<dyn Paragraph>>, chapters: Vec<Chapter>,) -> Self {
23        Self {
24            preamble,
25            chapters,
26            compiled_content: None
27        }
28    }
29
30}
31
32impl From<Vec<LoadBlock>> for ContentBundle {
33    fn from(mut blocks: Vec<LoadBlock>) -> Self {
34        if !blocks.windows(2).all(|w| {
35
36            assert!(w[0].start() <= w[0].end());
37            assert!(w[1].start() <= w[1].end());
38
39            w[0].start() <= w[1].start()
40        }) {
41            
42            blocks.par_sort_by(|a, b| a.start().cmp(&b.start()));
43        }
44
45        let mut preamble: Vec<Box<dyn Paragraph>> = Vec::new();
46        let mut current_chapter: Option<Chapter> = None;
47        let mut chapters: Vec<Chapter> = Vec::new();
48        let mut last_heading_level: u32 = 0;
49
50        for block in blocks {
51
52            match Into::<LoadBlockContent>::into(block) {
53                LoadBlockContent::Paragraph(paragraph) => {
54
55                    if let Some(ref mut cc) = current_chapter {
56
57                        cc.paragraphs_mut().push(paragraph);
58
59                    } else {
60
61                        preamble.push(paragraph);
62                    }
63
64                },
65                LoadBlockContent::ChapterHeader(mut header) => {
66
67                    if let Some(cc) = current_chapter.take() {
68                        chapters.push(cc);
69                    }
70
71                    assert!(current_chapter.is_none());
72
73                    let level = match header.heading().level() {
74                        HeadingLevel::Minor => {
75                            
76                            let l;
77                            if last_heading_level < 1 {
78                                log::warn!("minor heading found, but last heading has level {}, so it is set as 1", last_heading_level);
79                                
80                                l = HeadingLevel::Explicit(1)
81                            
82                            } else {
83
84                                l = HeadingLevel::Explicit(last_heading_level - 1);
85                            }
86                            
87                            l
88                        },
89                        HeadingLevel::Major => {
90                            let l;
91                            if last_heading_level < 1 {
92                                log::warn!("major heading found, but last heading has level {}, so it is set as 1", last_heading_level);
93                                
94                                l = HeadingLevel::Explicit(1)
95                            
96                            } else {
97
98                                l = HeadingLevel::Explicit(last_heading_level + 1);
99                            }
100                            
101                            l
102                        },
103                        HeadingLevel::Same => {
104                            let l;
105                            if last_heading_level < 1 {
106                                log::warn!("same heading found, but last heading has level {}, so it is set as 1", last_heading_level);
107                                
108                                l = HeadingLevel::Explicit(1)
109                            
110                            } else {
111
112                                l = HeadingLevel::Explicit(last_heading_level);
113                            }
114                            
115                            l
116                        },
117                        HeadingLevel::Explicit(l) => HeadingLevel::Explicit(*l)
118                    };
119
120                    if let HeadingLevel::Explicit(l) = &level {
121                    
122                        last_heading_level = *l;
123                    
124                    } else {
125
126                        unreachable!("heading level must be made 'explicit' now");
127                    }
128
129                    header.heading_mut().set_level(level);
130
131                    current_chapter = Some(Chapter::new(header, Vec::new()));
132                },
133            }
134        }
135
136        if let Some(cc) = current_chapter.take() {
137            chapters.push(cc);
138        }
139
140        Self::new(preamble, chapters)
141    }
142}
143
144
145impl Compilable for ContentBundle {
146    fn standard_compile(&mut self, format: &OutputFormat, codex: &Codex, compilation_configuration: &CompilationConfiguration, compilation_configuration_overlay: CompilationConfigurationOverLay) -> Result<CompilationOutcome, CompilationError> {
147        
148        if compilation_configuration_overlay.document_name().is_none() {
149            return Err(CompilationError::DocumentNameNotFound)
150        }
151        
152        let parallelization = compilation_configuration.parallelization();
153
154        let mut preamble_outcomes: Vec<CompilationOutcome> = Vec::new();
155        let mut chapter_outcomes: Vec<CompilationOutcome> = Vec::new();
156
157        if parallelization {
158
159            let preamble_results: Vec<Result<CompilationOutcome, CompilationError>> = self.preamble.par_iter_mut()
160                .map(|paragraph| {
161
162                    paragraph.compile(format, codex, compilation_configuration, compilation_configuration_overlay.clone())
163                
164                }).collect();
165
166            let mut preamble_errors: Vec<CompilationError> = Vec::new();
167
168            preamble_results.into_iter().for_each(|result| {
169
170                match result {
171                    Ok(outcome) => preamble_outcomes.push(outcome),
172                    Err(err) => preamble_errors.push(err),
173                }
174            });
175
176            if !preamble_errors.is_empty() {
177                return Err(CompilationError::BucketOfErrors(preamble_errors))
178            }
179
180            let chapter_results: Vec<Result<CompilationOutcome, CompilationError>> = self.chapters.par_iter_mut()
181                .map(|chapter| {
182
183                    chapter.compile(format, codex, compilation_configuration, compilation_configuration_overlay.clone())
184                
185                }).collect();
186
187            let mut chapter_errors: Vec<CompilationError> = Vec::new();
188
189            chapter_results.into_iter().for_each(|result| {
190
191                match result {
192                    Ok(outcome) => chapter_outcomes.push(outcome),
193                    Err(err) => chapter_errors.push(err),
194                }
195            });
196
197            if !chapter_errors.is_empty() {
198                return Err(CompilationError::BucketOfErrors(chapter_errors))
199            }
200        
201        } else {
202
203            for paragraph in self.preamble.iter_mut() {
204
205                preamble_outcomes.push(paragraph.compile(format, codex, compilation_configuration, compilation_configuration_overlay.clone())?);
206            }
207            
208            for chapter in self.chapters.iter_mut() {
209                
210                chapter_outcomes.push(chapter.compile(format, codex, compilation_configuration, compilation_configuration_overlay.clone())?);
211            }
212        }
213
214        Ok(CompilationOutcome::from(codex.assembler().assemble_bundle(&preamble_outcomes, &chapter_outcomes, compilation_configuration_overlay.assembler_configuration())?))
215    }
216}
217
218
219
220#[cfg(test)]
221mod test {
222    use std::sync::Arc;
223
224    use crate::{assembler::html_assembler::HtmlAssembler, codex::{modifier::{base_modifier::BaseModifier, standard_text_modifier::StandardTextModifier, Modifier, ModifiersBucket}, Codex, ParagraphModifierOrderedMap, TextModifierOrderedMap}, compilable_text::{compilable_text_part::CompilableTextPart, CompilableText}, compilation::{compilable::Compilable, compilation_configuration::{compilation_configuration_overlay::CompilationConfigurationOverLay, CompilationConfiguration}, compilation_rule::{replacement_rule::{replacement_rule_part::{closure_replacement_rule_part::ClosureReplacementRuleReplacerPart, fixed_replacement_rule_part::FixedReplacementRuleReplacerPart}, ReplacementRule}, CompilationRule}}, output_format::OutputFormat};
225
226
227    #[test]
228    fn compile_fake_paragraph_with_bold_text() {
229
230        let mut compilable_text = CompilableText::new(
231            vec![
232                CompilableTextPart::new_fixed(String::from("<p>")),
233                CompilableTextPart::new_compilable(
234                    String::from("This is a **bold text**!"),
235                    ModifiersBucket::None
236                ),
237                CompilableTextPart::new_fixed(String::from(" &euro; ")),
238                CompilableTextPart::new_compilable(
239                    String::from("**again"),
240                    ModifiersBucket::None
241                ),
242                CompilableTextPart::new_fixed(String::from(" &euro;")),
243                CompilableTextPart::new_compilable(
244                    String::from("**"),
245                    ModifiersBucket::None
246                ),
247                CompilableTextPart::new_fixed(String::from("</p>")),
248            ],
249        );
250
251        let codex = Codex::new(
252            TextModifierOrderedMap::from([
253                (
254                    StandardTextModifier::BoldStarVersion.identifier(),
255                    (
256                        Box::new(Into::<BaseModifier>::into(StandardTextModifier::BoldStarVersion)) as Box<dyn Modifier>,
257                        Box::new(
258                            ReplacementRule::new(
259                                StandardTextModifier::BoldStarVersion.modifier_pattern(),
260                                vec![
261                                    Arc::new(FixedReplacementRuleReplacerPart::new(String::from("<strong>"))),
262                                    Arc::new(ClosureReplacementRuleReplacerPart::new(Arc::new(|captures, compilable, _, _, _| {
263                    
264                                        let capture1 = captures.get(1).unwrap();
265                                        
266                                        let slice = compilable.parts_slice(capture1.start(), capture1.end())?;
267                        
268                                        Ok(CompilableText::new(slice))
269                                    }))),
270                                    Arc::new(FixedReplacementRuleReplacerPart::new(String::from("</strong>"))),
271                                ]
272                            )
273                        ) as Box<dyn CompilationRule>
274                    ) as (Box<dyn Modifier>, Box<dyn CompilationRule>)
275                )
276            ]),
277            ParagraphModifierOrderedMap::new(),
278            None,
279            Box::new(HtmlAssembler::new())
280        );
281
282        compilable_text.compile(
283            &OutputFormat::Html,
284            &codex,
285            &CompilationConfiguration::default(),
286            CompilationConfigurationOverLay::default()
287        ).unwrap();
288        
289        assert_eq!(
290            compilable_text.content(),
291            "<p>This is a <strong>bold text</strong>! &euro; <strong>again &euro;</strong></p>"
292        )
293    }
294
295
296}