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)] 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(" € ")),
238 CompilableTextPart::new_compilable(
239 String::from("**again"),
240 ModifiersBucket::None
241 ),
242 CompilableTextPart::new_fixed(String::from(" €")),
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>! € <strong>again €</strong></p>"
292 )
293 }
294
295
296}