1pub mod compilable_text_part;
2
3
4use compilable_text_part::{CompilableTextPart, CompilableTextPartType};
5use getset::{Getters, MutGetters, Setters};
6use serde::Serialize;
7use thiserror::Error;
8use crate::{codex::{modifier::{ModifierIdentifier, ModifiersBucket}, Codex}, compilation::{compilable::Compilable, compilation_configuration::{compilation_configuration_overlay::CompilationConfigurationOverLay, CompilationConfiguration}, compilation_error::CompilationError, compilation_outcome::CompilationOutcome, compilation_rule::CompilationRule}, output_format::OutputFormat, resource::bucket::Bucket, utility::nmd_unique_identifier::NmdUniqueIdentifier};
9
10
11#[derive(Debug, Clone)]
12pub enum PartsSliceElaborationPolicy {
13 DontTakeBorderFixedParts,
14 TakeLeftFixedParts,
15 TakeRightFixedParts,
16 TakeLeftAndRightFixedParts,
17}
18
19#[derive(Debug, Clone)]
20enum ElaborationPosition {
21 BeforeRange,
22 InRange,
23 AfterRange,
24}
25
26
27#[derive(Error, Debug)]
28pub enum CompilableError {
29 #[error("compilable content {0} has an overflow using {1} -> {2}")]
30 ContentOverflow(String, usize, usize),
31}
32
33
34#[derive(Debug, Clone, Getters, MutGetters, Setters, Serialize)]
35pub struct CompilableText {
36
37 #[getset(get = "pub", get_mut = "pub", set = "pub")]
38 parts: Vec<CompilableTextPart>,
39
40 #[getset(get = "pub", get_mut = "pub", set = "pub")]
41 nuid: Option<NmdUniqueIdentifier>,
42}
43
44
45
46impl From<CompilableTextPart> for CompilableText {
47 fn from(value: CompilableTextPart) -> Self {
48 Self::new(vec![value])
49 }
50}
51
52impl From<Vec<CompilableTextPart>> for CompilableText {
53 fn from(value: Vec<CompilableTextPart>) -> Self {
54 Self::new(value)
55 }
56}
57
58impl Into<Vec<CompilableTextPart>> for CompilableText {
59 fn into(self) -> Vec<CompilableTextPart> {
60 self.parts
61 }
62}
63
64impl Into<String> for CompilableText {
65 fn into(self) -> String {
66 self.content()
67 }
68}
69
70impl From<String> for CompilableText {
71 fn from(value: String) -> Self {
72 Self::from(CompilableTextPart::new_compilable(
73 value,
74 ModifiersBucket::None
75 ))
76 }
77}
78
79impl From<&str> for CompilableText {
80 fn from(value: &str) -> Self {
81 Self::from(CompilableTextPart::new_compilable(
82 value.to_string(),
83 ModifiersBucket::None
84 ))
85 }
86}
87
88impl CompilableText {
89
90 pub fn new_empty() -> Self {
91 Self {
92 parts: Vec::new(),
93 nuid: None,
94 }
95 }
96
97 pub fn new(parts: Vec<CompilableTextPart>) -> Self {
98
99 Self {
100 parts,
101 nuid: None,
102 }
103 }
104
105 pub fn new_with_nuid(parts: Vec<CompilableTextPart>, nuid: Option<NmdUniqueIdentifier>) -> Self {
106 Self {
107 parts,
108 nuid
109 }
110 }
111
112 pub fn compilable_content(&self) -> String {
114
115 self.compilable_content_with_ends_positions().0
116 }
117
118 pub fn compilable_content_with_ends_positions(&self) -> (String, Vec<usize>) {
119 let mut compilable_content = String::new();
120 let mut ends: Vec<usize> = Vec::new();
121 let mut last_end: usize = 0;
122
123 self.parts.iter().for_each(|part| {
124 match part.part_type() {
125 CompilableTextPartType::Fixed => (),
126 CompilableTextPartType::Compilable { incompatible_modifiers: _ } => {
127
128 let content = part.content();
129
130 ends.push(last_end + content.len());
131 last_end = *ends.last().unwrap();
132
133 compilable_content.push_str(&content);
134 },
135 }
136 });
137
138 (compilable_content, ends)
139 }
140
141 pub fn content(&self) -> String {
143
144 let mut content = String::new();
145
146 self.parts.iter().for_each(|part| content.push_str(part.content()));
147
148 content
149 }
150
151 pub fn parts_slice(&self, start: usize, end: usize) -> Result<Vec<CompilableTextPart>, CompilableError> {
153 self.parts_slice_with_explicit_policy(start, end, PartsSliceElaborationPolicy::TakeLeftAndRightFixedParts)
154 }
155
156 pub fn parts_slice_with_explicit_policy(&self, start: usize, end: usize, elaboration_policy: PartsSliceElaborationPolicy) -> Result<Vec<CompilableTextPart>, CompilableError> {
161
162 let (compilable_content, ends) = self.compilable_content_with_ends_positions();
163
164 if end > compilable_content.len() {
165 return Err(CompilableError::ContentOverflow(compilable_content, start, end))
166 }
167
168 let mut parts_slice: Vec<CompilableTextPart> = Vec::new();
169
170 let mut start_part_position_in_compilable_content: usize = 0;
171 let mut end_part_position_in_compilable_content: usize;
172
173 let mut elaboration_position = ElaborationPosition::BeforeRange;
174
175 let mut left_fixed_parts: Vec<&CompilableTextPart> = Vec::new();
176 let mut right_fixed_parts: Vec<&CompilableTextPart> = Vec::new();
177
178 let mut index: usize = 0;
179 let mut compilable_parts_index: usize = 0;
180
181 while index < self.parts.len() {
182
183 let part = &self.parts[index];
184
185 index += 1;
186
187 match part.part_type() {
188 CompilableTextPartType::Fixed => {
189
190 match elaboration_position {
191 ElaborationPosition::BeforeRange => left_fixed_parts.push(part),
192 ElaborationPosition::InRange => parts_slice.push(part.clone()),
193 ElaborationPosition::AfterRange => right_fixed_parts.push(part),
194 }
195 },
196 CompilableTextPartType::Compilable { incompatible_modifiers: _ } => {
197
198 end_part_position_in_compilable_content = ends[compilable_parts_index];
199
200 compilable_parts_index += 1;
201
202 if start_part_position_in_compilable_content == end {
203
204 elaboration_position = ElaborationPosition::AfterRange;
205 }
206
207 match elaboration_position {
208
209 ElaborationPosition::BeforeRange => {
210 if start_part_position_in_compilable_content <= start
211 && start < end_part_position_in_compilable_content { if start_part_position_in_compilable_content < start { left_fixed_parts.clear();
215 }
216
217 let part = CompilableTextPart::new(
218 compilable_content[start..end_part_position_in_compilable_content.min(end)].to_string(),
219 part.part_type().clone()
220 );
221
222 parts_slice.push(part);
223
224 if end < end_part_position_in_compilable_content { break;
226 }
227
228 elaboration_position = ElaborationPosition::InRange;
229
230 start_part_position_in_compilable_content = end_part_position_in_compilable_content.min(end);
231
232 if start_part_position_in_compilable_content < end_part_position_in_compilable_content {
233
234 index -= 1;
235 compilable_parts_index -= 1;
236
237 continue;
238 }
239
240 } else { left_fixed_parts.clear();
243 }
244 },
245
246 ElaborationPosition::InRange => {
247 if end <= end_part_position_in_compilable_content { let content = compilable_content[start_part_position_in_compilable_content..end].to_string();
250
251 if !content.is_empty() {
252 let part = CompilableTextPart::new(
254 content,
255 part.part_type().clone()
256 );
257
258 parts_slice.push(part);
259 }
260
261 if end < end_part_position_in_compilable_content {
262 break;
263 }
264
265 elaboration_position = ElaborationPosition::AfterRange;
266
267 } else {
268 let part = CompilableTextPart::new(
269 compilable_content[start_part_position_in_compilable_content..end_part_position_in_compilable_content].to_string(),
270 part.part_type().clone()
271 );
272
273 parts_slice.push(part);
274 }
275 },
276
277 ElaborationPosition::AfterRange => break,
278 }
279
280 if start_part_position_in_compilable_content == end {
281
282 elaboration_position = ElaborationPosition::AfterRange;
283 }
284
285 start_part_position_in_compilable_content = end_part_position_in_compilable_content;
286 },
287 }
288
289 }
290
291 match elaboration_policy {
292 PartsSliceElaborationPolicy::DontTakeBorderFixedParts => (),
293 PartsSliceElaborationPolicy::TakeLeftFixedParts => left_fixed_parts.into_iter().for_each(|p| parts_slice.insert(0, p.clone())),
294 PartsSliceElaborationPolicy::TakeRightFixedParts => right_fixed_parts.into_iter().for_each(|p| parts_slice.push(p.clone())),
295 PartsSliceElaborationPolicy::TakeLeftAndRightFixedParts => {
296
297 left_fixed_parts.into_iter().for_each(|p| parts_slice.insert(0, p.clone()));
298
299 right_fixed_parts.into_iter().for_each(|p| parts_slice.push(p.clone()));
300 },
301 }
302
303 Ok(parts_slice)
304 }
305}
306
307impl CompilableText {
308
309 fn compile_with_compilation_rule(&mut self, (rule_identifier, rule): (&ModifierIdentifier, &Box<dyn CompilationRule>), format: &OutputFormat, compilation_configuration: &CompilationConfiguration, compilation_configuration_overlay: CompilationConfigurationOverLay) -> Result<(), CompilationError> {
312
313 let parts = self.parts();
314
315 let mut compilable_content = String::new();
316 let mut compilable_content_end_parts_positions: Vec<usize> = Vec::new();
317
318 parts.iter()
319 .filter(|part| {
320 match &part.part_type() {
321 CompilableTextPartType::Fixed => false,
322 CompilableTextPartType::Compilable{ incompatible_modifiers } => {
323 if incompatible_modifiers.contains(&rule_identifier) {
324 return false
325 } else {
326 return true
327 }
328 },
329 }
330 })
331 .for_each(|part| {
332
333 compilable_content.push_str(part.content());
334
335 let last_pos = *compilable_content_end_parts_positions.last().unwrap_or(&0);
336
337 compilable_content_end_parts_positions.push(last_pos + part.content().len());
338 });
339
340 let matches = rule.find_iter(&compilable_content);
341
342 if matches.len() == 0 {
343 log::debug!("'{}' => no matches with {:?} -> {:?}", compilable_content, rule_identifier, rule.search_pattern());
344
345 return Ok(());
346 }
347
348 log::debug!("'{}' => there is a match with {:?} -> {:?}", compilable_content, rule_identifier, rule.search_pattern());
349
350 let mut compiled_parts: Vec<CompilableTextPart> = Vec::new(); let mut parts_index: usize = 0;
353 let mut compilable_parts_index: usize = 0;
354
355 let mut part_start_position_in_compilable_content: usize = 0;
357 let mut part_end_position_in_compilable_content: usize;
358
359 let mut match_index: usize = 0;
360
361 while parts_index < parts.len() { let match_start_end: Option<(usize, usize)>; if match_index < matches.len() {
366
367 let current_evaluated_match = matches[match_index];
368
369 match_index += 1;
370
371 match_start_end = Some((
372 current_evaluated_match.start(),
373 current_evaluated_match.end()
374 ));
375
376 } else {
377
378 match_start_end = None;
379 }
380
381 let mut match_found = false;
382
383 let mut matched_parts: Vec<CompilableTextPart> = Vec::new();
384
385 'parts_loop: while parts_index < parts.len() {
386
387 let part = &parts[parts_index];
388
389 parts_index += 1; match part.part_type() {
392 CompilableTextPartType::Fixed => {
393
394 if let Some((_start, _end)) = match_start_end {
395
396 if match_found { matched_parts.push(part.clone());
399
400 continue 'parts_loop;
401
402 } else {
403
404 compiled_parts.push(part.clone()); continue 'parts_loop;
407 }
408
409 } else {
410 compiled_parts.push(part.clone()); continue 'parts_loop;
413 }
414 },
415 CompilableTextPartType::Compilable{ incompatible_modifiers } => {
416
417 if incompatible_modifiers.contains(rule_identifier) {
418 compiled_parts.push(part.clone()); continue 'parts_loop;
421 }
422
423 part_end_position_in_compilable_content = compilable_content_end_parts_positions[compilable_parts_index];
424
425 compilable_parts_index += 1;
426
427 if let Some((match_start, match_end)) = match_start_end {
428
429 if !match_found && part_end_position_in_compilable_content <= match_start { let sub_part = &compilable_content[part_start_position_in_compilable_content..part_end_position_in_compilable_content];
432
433 compiled_parts.push(CompilableTextPart::new(
434 sub_part.to_string(),
435 CompilableTextPartType::Compilable{ incompatible_modifiers: incompatible_modifiers.clone() }
436 ));
437
438 } else {
439 if !match_found && part_start_position_in_compilable_content <= match_start
443 && match_start < part_end_position_in_compilable_content {
444
445 let pre_matched_part = &compilable_content[part_start_position_in_compilable_content..match_start];
447
448 if !pre_matched_part.is_empty() {
449 compiled_parts.push(CompilableTextPart::new(
450 pre_matched_part.to_string(),
451 CompilableTextPartType::Compilable{ incompatible_modifiers: incompatible_modifiers.clone() }
452 ));
453 }
454
455 part_start_position_in_compilable_content = match_start;
456
457 let matched_part = &compilable_content[part_start_position_in_compilable_content..part_end_position_in_compilable_content.min(match_end)];
459
460 matched_parts.push(CompilableTextPart::new(
461 matched_part.to_string(),
462 CompilableTextPartType::Compilable{ incompatible_modifiers: incompatible_modifiers.clone() }
463 ));
464 }
465
466 if match_end <= part_end_position_in_compilable_content { if match_found { let matched_part = &compilable_content[part_start_position_in_compilable_content..match_end];
471
472 matched_parts.push(CompilableTextPart::new(
473 matched_part.to_string(),
474 CompilableTextPartType::Compilable{ incompatible_modifiers: incompatible_modifiers.clone() }
475 ));
476 }
477
478 compiled_parts.append(
480 &mut rule.compile(
481 &CompilableText::from(matched_parts),
482 format,
483 compilation_configuration,
484 compilation_configuration_overlay.clone()
485 )?.parts_mut()
486 );
487
488 parts_index -= 1;
490 compilable_parts_index -= 1;
491
492 part_start_position_in_compilable_content = match_end;
493
494 break 'parts_loop;
495
496 } else {
497
498 if match_found { let matched_part = &compilable_content[part_start_position_in_compilable_content..part_end_position_in_compilable_content];
501
502 matched_parts.push(CompilableTextPart::new(
503 matched_part.to_string(),
504 CompilableTextPartType::Compilable{ incompatible_modifiers: incompatible_modifiers.clone() }
505 ));
506 }
507 }
508
509 match_found = true; }
511
512 } else {
513
514 let part = &compilable_content[part_start_position_in_compilable_content..part_end_position_in_compilable_content];
515
516 if !part.is_empty() {
517 compiled_parts.push(CompilableTextPart::new(
518 part.to_string(),
519 CompilableTextPartType::Compilable{ incompatible_modifiers: incompatible_modifiers.clone() }
520 ));
521 }
522 }
523
524 part_start_position_in_compilable_content = part_end_position_in_compilable_content;
526 }
527
528 }
529 }
530 }
531
532 self.set_parts(compiled_parts);
533
534 Ok(())
535 }
536}
537
538impl Compilable for CompilableText {
539
540 fn standard_compile(&mut self, format: &OutputFormat, codex: &Codex, compilation_configuration: &CompilationConfiguration, compilation_configuration_overlay: CompilationConfigurationOverLay) -> Result<CompilationOutcome, CompilationError> {
541
542 let excluded_modifiers = compilation_configuration_overlay.excluded_modifiers().clone();
543
544 log::debug!("start to compile content:\n{:?}\nexcluding: {:?}", self, excluded_modifiers);
545
546 if excluded_modifiers == Bucket::All {
547 log::debug!("compilation of content:\n{:?} is skipped because are excluded all modifiers", self);
548
549 return Ok(CompilationOutcome::from(self.content()))
550 }
551
552 for (codex_identifier, (text_modifier, text_rule)) in codex.text_modifiers() {
553
554 if excluded_modifiers.contains(codex_identifier) {
555
556 log::debug!("{:?} is skipped", text_modifier);
557 continue;
558 }
559
560 self.compile_with_compilation_rule((codex_identifier, text_rule), format, compilation_configuration, compilation_configuration_overlay.clone())?;
561 }
562
563 Ok(CompilationOutcome::from(self.content()))
564 }
565}
566
567
568#[cfg(test)]
569mod test {
570 use std::collections::HashSet;
571
572 use crate::{codex::{modifier::{standard_text_modifier::StandardTextModifier, ModifiersBucket}, Codex}, compilable_text::{compilable_text_part::{CompilableTextPart, CompilableTextPartType}, PartsSliceElaborationPolicy}, compilation::{compilation_configuration::{compilation_configuration_overlay::CompilationConfigurationOverLay, CompilationConfiguration}, compilable::Compilable}, output_format::OutputFormat};
573
574 use super::CompilableText;
575
576
577 #[test]
578 fn parts_between_positions_in_cfc() {
579 let compilable = CompilableText::new(vec![
580 CompilableTextPart::new(
581 String::from("this is a string with 35 characters"),
582 CompilableTextPartType::Compilable { incompatible_modifiers: ModifiersBucket::None }
583 ),
584 CompilableTextPart::new(
585 String::from("this is the fixed part"),
586 CompilableTextPartType::Fixed
587 ),
588 CompilableTextPart::new(
589 String::from("end of the content"),
590 CompilableTextPartType::Compilable { incompatible_modifiers: ModifiersBucket::None }
591 ),
592 ]);
593
594 let start1: usize = 5;
595 let start2: usize = 25;
596
597 let end1: usize = 16;
598 let end2: usize = 38;
599
600 let parts_slice = compilable.parts_slice(start1, end1).unwrap();
601
602 assert_eq!(parts_slice.len(), 1);
603 assert_eq!(parts_slice[0].content(), &String::from("is a string"));
604
605 let parts_slice = compilable.parts_slice(start2, end2).unwrap();
606
607 assert_eq!(parts_slice.len(), 3);
608 assert_eq!(parts_slice[0].content(), &String::from("characters"));
609 assert_eq!(parts_slice[1].content(), &String::from("this is the fixed part"));
610 assert_eq!(parts_slice[2].content(), &String::from("end"));
611 }
612
613 #[test]
614 fn parts_between_positions_in_cfcfc() {
615 let compilable = CompilableText::new(vec![
616 CompilableTextPart::new_compilable(String::from("c1"), ModifiersBucket::None),
617 CompilableTextPart::new_fixed(String::from("f1")),
618 CompilableTextPart::new_compilable(String::from("c2"), ModifiersBucket::None),
619 CompilableTextPart::new_fixed(String::from("f2")),
620 CompilableTextPart::new_compilable(String::from("c3"), ModifiersBucket::None),
621 ]);
622
623 let start: usize = 1;
624 let end: usize = 5;
625
626 let parts_slice = compilable.parts_slice(start, end).unwrap();
627
628 assert_eq!(parts_slice.len(), 5);
629 assert_eq!(parts_slice[0].content(), &String::from("1"));
630 assert_eq!(parts_slice[1].content(), &String::from("f1"));
631 assert_eq!(parts_slice[2].content(), &String::from("c2"));
632 assert_eq!(parts_slice[3].content(), &String::from("f2"));
633 assert_eq!(parts_slice[4].content(), &String::from("c"));
634
635 let compilable = CompilableText::new(vec![
636 CompilableTextPart::new_compilable(String::from("c1"), ModifiersBucket::None),
637 CompilableTextPart::new_fixed(String::from("f1")),
638 CompilableTextPart::new_compilable(String::from("c2"), ModifiersBucket::None),
639 CompilableTextPart::new_fixed(String::from("f2")),
640 CompilableTextPart::new_compilable(String::from("c3"), ModifiersBucket::None),
641 ]);
642
643 let start: usize = 1;
644 let end: usize = 4;
645
646 let parts_slice = compilable.parts_slice(start, end).unwrap();
647
648 assert_eq!(parts_slice.len(), 4);
649 assert_eq!(parts_slice[0].content(), &String::from("1"));
650 assert_eq!(parts_slice[1].content(), &String::from("f1"));
651 assert_eq!(parts_slice[2].content(), &String::from("c2"));
652 assert_eq!(parts_slice[3].content(), &String::from("f2"));
653 }
654
655 #[test]
656 fn parts_between_positions_in_cfcfc_with_explicit_policy() {
657 let compilable = CompilableText::new(vec![
658 CompilableTextPart::new_fixed(String::from("f-1")),
659 CompilableTextPart::new_compilable(String::from("c0"), ModifiersBucket::None),
660 CompilableTextPart::new_fixed(String::from("f0")),
661 CompilableTextPart::new_compilable(String::from("*"), ModifiersBucket::None),
662 CompilableTextPart::new_fixed(String::from("f1")),
663 CompilableTextPart::new_compilable(String::from("c2"), ModifiersBucket::None),
664 CompilableTextPart::new_fixed(String::from("f2")),
665 CompilableTextPart::new_compilable(String::from("*"), ModifiersBucket::None),
666 CompilableTextPart::new_fixed(String::from("f3")),
667 CompilableTextPart::new_compilable(String::from("c3"), ModifiersBucket::None),
668 CompilableTextPart::new_fixed(String::from("f4")),
669 ]);
670
671 let start: usize = 3;
672 let end: usize = 5;
673
674 let parts_slice = compilable.parts_slice_with_explicit_policy(start, end, PartsSliceElaborationPolicy::TakeLeftAndRightFixedParts).unwrap();
676
677 assert_eq!(parts_slice.len(), 3);
678 assert_eq!(parts_slice[0].content(), &String::from("f1"));
679 assert_eq!(parts_slice[1].content(), &String::from("c2"));
680 assert_eq!(parts_slice[2].content(), &String::from("f2"));
681
682 let parts_slice = compilable.parts_slice_with_explicit_policy(start, end, PartsSliceElaborationPolicy::TakeLeftFixedParts).unwrap();
684
685 assert_eq!(parts_slice.len(), 2);
686 assert_eq!(parts_slice[0].content(), &String::from("f1"));
687 assert_eq!(parts_slice[1].content(), &String::from("c2"));
688
689 let parts_slice = compilable.parts_slice_with_explicit_policy(start, end, PartsSliceElaborationPolicy::TakeRightFixedParts).unwrap();
691
692 assert_eq!(parts_slice.len(), 2);
693 assert_eq!(parts_slice[0].content(), &String::from("c2"));
694 assert_eq!(parts_slice[1].content(), &String::from("f2"));
695
696 let parts_slice = compilable.parts_slice_with_explicit_policy(start, end, PartsSliceElaborationPolicy::DontTakeBorderFixedParts).unwrap();
698
699 assert_eq!(parts_slice.len(), 1);
700 assert_eq!(parts_slice[0].content(), &String::from("c2"));
701 }
702
703 #[test]
704 fn compile_nested_modifiers() {
705
706 let mut codex = Codex::of_html();
707
708 codex.retain(HashSet::from([
709 StandardTextModifier::BoldStarVersion.identifier(),
710 StandardTextModifier::BoldUnderscoreVersion.identifier(),
711 StandardTextModifier::ItalicStarVersion.identifier(),
712 StandardTextModifier::ItalicUnderscoreVersion.identifier(),
713 StandardTextModifier::InlineCode.identifier(),
714 ]));
715
716 let compilation_configuration = CompilationConfiguration::default();
717
718 let content = "A piece of **bold text**, *italic text*, `a **(fake) bold text** which must be not parsed` and *nested **bold text***";
719
720 let mut outcome = CompilableText::from(content);
721
722 outcome.compile(&OutputFormat::Html, &codex, &compilation_configuration, CompilationConfigurationOverLay::default()).unwrap();
723
724 assert_eq!(outcome.content(), concat!(
725 "A piece of ",
726 r#"<strong class="bold">bold text</strong>, "#,
727 r#"<em class="italic">italic text</em>, "#,
728 r#"<code class="language-markup inline-code">a **(fake) bold text** which must be not parsed</code>"#,
729 r#" and "#,
730 r#"<em class="italic">nested <strong class="bold">bold text</strong></em>"#,
731 ));
732 }
733
734}
735
736
737
738
739
740
741