1use crate::{
2 Parser,
3 attributes::Attrlist,
4 content::{Content, Passthroughs, SubstitutionStep},
5};
6
7#[derive(Clone, Debug, Eq, PartialEq)]
13pub enum SubstitutionGroup {
14 Normal,
18
19 Title,
22
23 Header,
33
34 Verbatim,
38
39 Pass,
47
48 None,
51
52 AttributeEntryValue,
56
57 Custom(Vec<SubstitutionStep>),
67}
68
69impl SubstitutionGroup {
70 pub(crate) fn from_custom_string(start_from: Option<&Self>, mut custom: &str) -> Option<Self> {
75 custom = custom.trim();
76
77 if custom == "none" {
78 return Some(Self::None);
79 }
80
81 if custom == "n" || custom == "normal" {
82 return Some(Self::Normal);
83 }
84
85 if custom == "v" || custom == "verbatim" {
86 return Some(Self::Verbatim);
87 }
88
89 let mut steps: Vec<SubstitutionStep> = vec![];
90
91 for (count, mut step) in custom.split(",").enumerate() {
92 step = step.trim();
93
94 if step == "n" || step == "normal" {
95 steps = vec![
96 SubstitutionStep::SpecialCharacters,
97 SubstitutionStep::Quotes,
98 SubstitutionStep::AttributeReferences,
99 SubstitutionStep::CharacterReplacements,
100 SubstitutionStep::Macros,
101 SubstitutionStep::PostReplacement,
102 ];
103 continue;
104 }
105
106 if step == "v" || step == "verbatim" {
107 steps = vec![
108 SubstitutionStep::SpecialCharacters,
109 SubstitutionStep::Callouts,
110 ];
111 continue;
112 }
113
114 let append = if step.starts_with('+') {
115 step = &step[1..];
116 true
117 } else {
118 false
119 };
120
121 let prepend = if !append && step.ends_with('+') {
122 step = &step[0..step.len() - 1];
123 true
124 } else {
125 false
126 };
127
128 let subtract = if !append && !prepend && step.starts_with('-') {
129 step = &step[1..];
130 true
131 } else {
132 false
133 };
134
135 if count == 0
136 && let Some(start_from) = start_from
137 && (append || prepend || subtract)
138 {
139 steps = start_from.steps().to_owned();
140 }
141
142 let step = match step {
143 "c" | "specialcharacters" | "specialchars" => SubstitutionStep::SpecialCharacters,
144 "q" | "quotes" => SubstitutionStep::Quotes,
145 "a" | "attributes" => SubstitutionStep::AttributeReferences,
146 "r" | "replacements" => SubstitutionStep::CharacterReplacements,
147 "m" | "macros" => SubstitutionStep::Macros,
148 "p" | "post_replacements" => SubstitutionStep::PostReplacement,
149 "callouts" => SubstitutionStep::Callouts,
150 _ => {
151 return None;
152 }
153 };
154
155 if prepend {
156 steps.insert(0, step);
157 } else if append {
158 steps.push(step);
159 } else if subtract {
160 steps.retain(|s| s != &step);
161 } else {
162 steps.push(step);
163 }
164 }
165
166 Some(Self::Custom(steps))
167 }
168
169 pub(crate) fn apply(
170 &self,
171 content: &mut Content<'_>,
172 parser: &Parser,
173 attrlist: Option<&Attrlist>,
174 ) {
175 let steps = self.steps();
176
177 let passthroughs: Option<Passthroughs> =
178 if steps.contains(&SubstitutionStep::Macros) || self == &Self::Header {
179 Some(Passthroughs::extract_from(content))
180 } else {
181 None
182 };
183
184 for step in steps {
185 step.apply(content, parser, attrlist);
186 }
187
188 if let Some(passthroughs) = passthroughs {
189 passthroughs.restore_to(content, parser);
190 }
191
192 content.finalize_deferred(&*parser.renderer);
197 }
198
199 pub(crate) fn override_via_attrlist(&self, attrlist: Option<&Attrlist>) -> Self {
200 let mut result = self.clone();
201
202 if let Some(attrlist) = attrlist {
203 if let Some(block_style) = attrlist.nth_attribute(1).and_then(|a| a.block_style()) {
204 result = match block_style {
205 "pass" => SubstitutionGroup::None,
207 _ => result,
208 };
209 }
210
211 if let Some(sub_group) = attrlist
212 .named_attribute("subs")
213 .map(|attr| attr.value())
214 .and_then(|s| Self::from_custom_string(Some(self), s))
215 {
216 result = sub_group;
217 }
218 }
219
220 result
221 }
222
223 fn steps(&self) -> &[SubstitutionStep] {
224 match self {
225 Self::Normal | Self::Title => &[
226 SubstitutionStep::SpecialCharacters,
227 SubstitutionStep::Quotes,
228 SubstitutionStep::AttributeReferences,
229 SubstitutionStep::CharacterReplacements,
230 SubstitutionStep::Macros,
231 SubstitutionStep::PostReplacement,
232 ],
233
234 Self::Header | Self::AttributeEntryValue => &[
235 SubstitutionStep::SpecialCharacters,
236 SubstitutionStep::AttributeReferences,
237 ],
238
239 Self::Verbatim => &[
240 SubstitutionStep::SpecialCharacters,
241 SubstitutionStep::Callouts,
242 ],
243
244 Self::Pass | Self::None => &[],
245
246 Self::Custom(steps) => steps,
247 }
248 }
249}
250
251#[cfg(test)]
252mod tests {
253 #![allow(clippy::unwrap_used)]
254
255 mod from_custom_string {
256 use crate::{
257 content::{Content, SubstitutionStep},
258 strings::CowStr,
259 tests::prelude::*,
260 };
261
262 #[test]
263 fn empty() {
264 assert_eq!(SubstitutionGroup::from_custom_string(None, ""), None);
265 }
266
267 #[test]
268 fn none() {
269 assert_eq!(
270 SubstitutionGroup::from_custom_string(None, "none"),
271 Some(SubstitutionGroup::None)
272 );
273
274 assert_eq!(SubstitutionGroup::from_custom_string(None, "nermal"), None);
275 }
276
277 #[test]
278 fn normal() {
279 assert_eq!(
280 SubstitutionGroup::from_custom_string(None, "n"),
281 Some(SubstitutionGroup::Normal)
282 );
283
284 assert_eq!(
285 SubstitutionGroup::from_custom_string(None, "normal"),
286 Some(SubstitutionGroup::Normal)
287 );
288
289 assert_eq!(SubstitutionGroup::from_custom_string(None, "nermal"), None);
290 }
291
292 #[test]
293 fn verbatim() {
294 assert_eq!(
295 SubstitutionGroup::from_custom_string(None, "v"),
296 Some(SubstitutionGroup::Verbatim)
297 );
298
299 assert_eq!(
300 SubstitutionGroup::from_custom_string(None, "verbatim"),
301 Some(SubstitutionGroup::Verbatim)
302 );
303
304 assert_eq!(
305 SubstitutionGroup::from_custom_string(None, "verboten"),
306 None
307 );
308 }
309
310 #[test]
311 fn special_chars() {
312 assert_eq!(
313 SubstitutionGroup::from_custom_string(None, "c"),
314 Some(SubstitutionGroup::Custom(vec![
315 SubstitutionStep::SpecialCharacters
316 ]))
317 );
318
319 assert_eq!(
320 SubstitutionGroup::from_custom_string(None, "specialchars"),
321 Some(SubstitutionGroup::Custom(vec![
322 SubstitutionStep::SpecialCharacters
323 ]))
324 );
325 }
326
327 #[test]
328 fn quotes() {
329 assert_eq!(
330 SubstitutionGroup::from_custom_string(None, "q"),
331 Some(SubstitutionGroup::Custom(vec![SubstitutionStep::Quotes]))
332 );
333
334 assert_eq!(
335 SubstitutionGroup::from_custom_string(None, "quotes"),
336 Some(SubstitutionGroup::Custom(vec![SubstitutionStep::Quotes]))
337 );
338 }
339
340 #[test]
341 fn attributes() {
342 assert_eq!(
343 SubstitutionGroup::from_custom_string(None, "a"),
344 Some(SubstitutionGroup::Custom(vec![
345 SubstitutionStep::AttributeReferences
346 ]))
347 );
348
349 assert_eq!(
350 SubstitutionGroup::from_custom_string(None, "attributes"),
351 Some(SubstitutionGroup::Custom(vec![
352 SubstitutionStep::AttributeReferences
353 ]))
354 );
355 }
356
357 #[test]
358 fn replacements() {
359 assert_eq!(
360 SubstitutionGroup::from_custom_string(None, "r"),
361 Some(SubstitutionGroup::Custom(vec![
362 SubstitutionStep::CharacterReplacements
363 ]))
364 );
365
366 assert_eq!(
367 SubstitutionGroup::from_custom_string(None, "replacements"),
368 Some(SubstitutionGroup::Custom(vec![
369 SubstitutionStep::CharacterReplacements
370 ]))
371 );
372 }
373
374 #[test]
375 fn macros() {
376 assert_eq!(
377 SubstitutionGroup::from_custom_string(None, "m"),
378 Some(SubstitutionGroup::Custom(vec![SubstitutionStep::Macros]))
379 );
380
381 assert_eq!(
382 SubstitutionGroup::from_custom_string(None, "macros"),
383 Some(SubstitutionGroup::Custom(vec![SubstitutionStep::Macros]))
384 );
385 }
386
387 #[test]
388 fn post_replacements() {
389 assert_eq!(
390 SubstitutionGroup::from_custom_string(None, "p"),
391 Some(SubstitutionGroup::Custom(vec![
392 SubstitutionStep::PostReplacement
393 ]))
394 );
395
396 assert_eq!(
397 SubstitutionGroup::from_custom_string(None, "post_replacements"),
398 Some(SubstitutionGroup::Custom(vec![
399 SubstitutionStep::PostReplacement
400 ]))
401 );
402 }
403
404 #[test]
405 fn multiple() {
406 assert_eq!(
407 SubstitutionGroup::from_custom_string(None, "q,a"),
408 Some(SubstitutionGroup::Custom(vec![
409 SubstitutionStep::Quotes,
410 SubstitutionStep::AttributeReferences
411 ]))
412 );
413
414 assert_eq!(
415 SubstitutionGroup::from_custom_string(None, "q, a"),
416 Some(SubstitutionGroup::Custom(vec![
417 SubstitutionStep::Quotes,
418 SubstitutionStep::AttributeReferences
419 ]))
420 );
421
422 assert_eq!(
423 SubstitutionGroup::from_custom_string(None, "quotes,attributes"),
424 Some(SubstitutionGroup::Custom(vec![
425 SubstitutionStep::Quotes,
426 SubstitutionStep::AttributeReferences
427 ]))
428 );
429
430 assert_eq!(
431 SubstitutionGroup::from_custom_string(None, "x,bogus,no such step"),
432 None
433 );
434 }
435
436 #[test]
437 fn subtraction() {
438 assert_eq!(
439 SubstitutionGroup::from_custom_string(None, "n,-r"),
440 Some(SubstitutionGroup::Custom(vec![
441 SubstitutionStep::SpecialCharacters,
442 SubstitutionStep::Quotes,
443 SubstitutionStep::AttributeReferences,
444 SubstitutionStep::Macros,
445 SubstitutionStep::PostReplacement,
446 ]))
447 );
448
449 assert_eq!(
450 SubstitutionGroup::from_custom_string(None, "n,-r,-r,-m"),
451 Some(SubstitutionGroup::Custom(vec![
452 SubstitutionStep::SpecialCharacters,
453 SubstitutionStep::Quotes,
454 SubstitutionStep::AttributeReferences,
455 SubstitutionStep::PostReplacement,
456 ]))
457 );
458
459 assert_eq!(
460 SubstitutionGroup::from_custom_string(None, "v,-r"),
461 Some(SubstitutionGroup::Custom(vec![
462 SubstitutionStep::SpecialCharacters,
463 SubstitutionStep::Callouts,
464 ]))
465 );
466
467 assert_eq!(
468 SubstitutionGroup::from_custom_string(None, "v,-c"),
469 Some(SubstitutionGroup::Custom(vec![SubstitutionStep::Callouts]))
470 );
471
472 assert_eq!(
473 SubstitutionGroup::from_custom_string(None, "v,-callouts"),
474 Some(SubstitutionGroup::Custom(vec![
475 SubstitutionStep::SpecialCharacters,
476 ]))
477 );
478 }
479
480 #[test]
481 fn addition() {
482 assert_eq!(
483 SubstitutionGroup::from_custom_string(None, "n,r"),
484 Some(SubstitutionGroup::Custom(vec![
485 SubstitutionStep::SpecialCharacters,
486 SubstitutionStep::Quotes,
487 SubstitutionStep::AttributeReferences,
488 SubstitutionStep::CharacterReplacements,
489 SubstitutionStep::Macros,
490 SubstitutionStep::PostReplacement,
491 SubstitutionStep::CharacterReplacements,
492 ]))
493 );
494
495 assert_eq!(
496 SubstitutionGroup::from_custom_string(None, "v,m"),
497 Some(SubstitutionGroup::Custom(vec![
498 SubstitutionStep::SpecialCharacters,
499 SubstitutionStep::Callouts,
500 SubstitutionStep::Macros,
501 ]))
502 );
503 }
504
505 #[test]
506 fn incremental() {
507 assert_eq!(
508 SubstitutionGroup::from_custom_string(None, "n,r"),
509 Some(SubstitutionGroup::Custom(vec![
510 SubstitutionStep::SpecialCharacters,
511 SubstitutionStep::Quotes,
512 SubstitutionStep::AttributeReferences,
513 SubstitutionStep::CharacterReplacements,
514 SubstitutionStep::Macros,
515 SubstitutionStep::PostReplacement,
516 SubstitutionStep::CharacterReplacements,
517 ]))
518 );
519
520 assert_eq!(
521 SubstitutionGroup::from_custom_string(None, "v,m"),
522 Some(SubstitutionGroup::Custom(vec![
523 SubstitutionStep::SpecialCharacters,
524 SubstitutionStep::Callouts,
525 SubstitutionStep::Macros,
526 ]))
527 );
528 }
529
530 #[test]
531 fn prepend() {
532 assert_eq!(
533 SubstitutionGroup::from_custom_string(
534 Some(&SubstitutionGroup::Verbatim),
535 "attributes+"
536 ),
537 Some(SubstitutionGroup::Custom(vec![
538 SubstitutionStep::AttributeReferences,
539 SubstitutionStep::SpecialCharacters,
540 SubstitutionStep::Callouts,
541 ]))
542 );
543
544 assert_eq!(
545 SubstitutionGroup::from_custom_string(None, "attributes+"),
546 Some(SubstitutionGroup::Custom(vec![
547 SubstitutionStep::AttributeReferences,
548 ]))
549 );
550 }
551
552 #[test]
553 fn append() {
554 assert_eq!(
555 SubstitutionGroup::from_custom_string(
556 Some(&SubstitutionGroup::Verbatim),
557 "+attributes"
558 ),
559 Some(SubstitutionGroup::Custom(vec![
560 SubstitutionStep::SpecialCharacters,
561 SubstitutionStep::Callouts,
562 SubstitutionStep::AttributeReferences,
563 ]))
564 );
565
566 assert_eq!(
567 SubstitutionGroup::from_custom_string(None, "attributes+"),
568 Some(SubstitutionGroup::Custom(vec![
569 SubstitutionStep::AttributeReferences,
570 ]))
571 );
572 }
573
574 #[test]
575 fn subtract() {
576 assert_eq!(
577 SubstitutionGroup::from_custom_string(
578 Some(&SubstitutionGroup::Normal),
579 "-attributes"
580 ),
581 Some(SubstitutionGroup::Custom(vec![
582 SubstitutionStep::SpecialCharacters,
583 SubstitutionStep::Quotes,
584 SubstitutionStep::CharacterReplacements,
585 SubstitutionStep::Macros,
586 SubstitutionStep::PostReplacement,
587 ]))
588 );
589
590 assert_eq!(
591 SubstitutionGroup::from_custom_string(None, "-attributes"),
592 Some(SubstitutionGroup::Custom(vec![]))
593 );
594 }
595
596 #[test]
597 fn custom_group_with_macros_preserves_passthroughs() {
598 let custom_group = SubstitutionGroup::from_custom_string(None, "q,m").unwrap();
599
600 let mut content = Content::from(crate::Span::new(
601 "Text with +++pass<through>+++ icon:github[] content.",
602 ));
603 let p = Parser::default();
604 custom_group.apply(&mut content, &p, None);
605
606 assert!(!content.is_empty());
607 assert_eq!(
608 content.rendered,
609 CowStr::Boxed(
610 "Text with pass<through> <span class=\"icon\">[github]</span> content."
611 .to_string()
612 .into_boxed_str()
613 )
614 );
615 }
616 }
617
618 mod normal {
619 use crate::{content::Content, strings::CowStr, tests::prelude::*};
620
621 #[test]
622 fn empty() {
623 let mut content = Content::from(crate::Span::default());
624 let p = Parser::default();
625 SubstitutionGroup::Normal.apply(&mut content, &p, None);
626 assert!(content.is_empty());
627 assert_eq!(content.rendered, CowStr::Borrowed(""));
628 }
629
630 #[test]
631 fn basic_non_empty_span() {
632 let mut content = Content::from(crate::Span::new("blah"));
633 let p = Parser::default();
634 SubstitutionGroup::Normal.apply(&mut content, &p, None);
635 assert!(!content.is_empty());
636 assert_eq!(content.rendered, CowStr::Borrowed("blah"));
637 }
638
639 #[test]
640 fn match_lt_and_gt() {
641 let mut content = Content::from(crate::Span::new("bl<ah>"));
642 let p = Parser::default();
643 SubstitutionGroup::Normal.apply(&mut content, &p, None);
644 assert!(!content.is_empty());
645 assert_eq!(
646 content.rendered,
647 CowStr::Boxed("bl<ah>".to_string().into_boxed_str())
648 );
649 }
650
651 #[test]
652 fn match_amp() {
653 let mut content = Content::from(crate::Span::new("bl<a&h>"));
654 let p = Parser::default();
655 SubstitutionGroup::Normal.apply(&mut content, &p, None);
656 assert!(!content.is_empty());
657 assert_eq!(
658 content.rendered,
659 CowStr::Boxed("bl<a&h>".to_string().into_boxed_str())
660 );
661 }
662
663 #[test]
664 fn strong_word() {
665 let mut content = Content::from(crate::Span::new("One *word* is strong."));
666 let p = Parser::default();
667 SubstitutionGroup::Normal.apply(&mut content, &p, None);
668 assert!(!content.is_empty());
669 assert_eq!(
670 content.rendered,
671 CowStr::Boxed(
672 "One <strong>word</strong> is strong."
673 .to_string()
674 .into_boxed_str()
675 )
676 );
677 }
678
679 #[test]
680 fn strong_word_with_special_chars() {
681 let mut content = Content::from(crate::Span::new("One *wo<r>d* is strong."));
682 let p = Parser::default();
683 SubstitutionGroup::Normal.apply(&mut content, &p, None);
684 assert!(!content.is_empty());
685 assert_eq!(
686 content.rendered,
687 CowStr::Boxed(
688 "One <strong>wo<r>d</strong> is strong."
689 .to_string()
690 .into_boxed_str()
691 )
692 );
693 }
694
695 #[test]
696 fn marked_string_with_id() {
697 let mut content = Content::from(crate::Span::new(r#"[#id]#a few words#"#));
698 let p = Parser::default();
699 SubstitutionGroup::Normal.apply(&mut content, &p, None);
700 assert!(!content.is_empty());
701 assert_eq!(
702 content.rendered,
703 CowStr::Boxed(r#"<span id="id">a few words</span>"#.to_string().into_boxed_str())
704 );
705 }
706 }
707
708 mod attribute_entry_value {
709 use crate::{
710 content::Content, parser::ModificationContext, strings::CowStr, tests::prelude::*,
711 };
712
713 #[test]
714 fn empty() {
715 let mut content = Content::from(crate::Span::default());
716 let p = Parser::default();
717 SubstitutionGroup::AttributeEntryValue.apply(&mut content, &p, None);
718 assert!(content.is_empty());
719 assert_eq!(content.rendered, CowStr::Borrowed(""));
720 }
721
722 #[test]
723 fn basic_non_empty_span() {
724 let mut content = Content::from(crate::Span::new("blah"));
725 let p = Parser::default();
726 SubstitutionGroup::AttributeEntryValue.apply(&mut content, &p, None);
727 assert!(!content.is_empty());
728 assert_eq!(content.rendered, CowStr::Borrowed("blah"));
729 }
730
731 #[test]
732 fn match_lt_and_gt() {
733 let mut content = Content::from(crate::Span::new("bl<ah>"));
734 let p = Parser::default();
735 SubstitutionGroup::Normal.apply(&mut content, &p, None);
736 assert!(!content.is_empty());
737 assert_eq!(
738 content.rendered,
739 CowStr::Boxed("bl<ah>".to_string().into_boxed_str())
740 );
741 }
742
743 #[test]
744 fn match_amp() {
745 let mut content = Content::from(crate::Span::new("bl<a&h>"));
746 let p = Parser::default();
747 SubstitutionGroup::AttributeEntryValue.apply(&mut content, &p, None);
748 assert!(!content.is_empty());
749 assert_eq!(
750 content.rendered,
751 CowStr::Boxed("bl<a&h>".to_string().into_boxed_str())
752 );
753 }
754
755 #[test]
756 fn ignores_strong_word() {
757 let mut content = Content::from(crate::Span::new("One *word* is strong."));
758 let p = Parser::default();
759 SubstitutionGroup::AttributeEntryValue.apply(&mut content, &p, None);
760 assert!(!content.is_empty());
761 assert_eq!(
762 content.rendered,
763 CowStr::Boxed("One *word* is strong.".to_string().into_boxed_str())
764 );
765 }
766
767 #[test]
768 fn special_chars_and_attributes() {
769 let mut content = Content::from(crate::Span::new("bl<ah> {color}"));
770
771 let p = Parser::default().with_intrinsic_attribute(
772 "color",
773 "red",
774 ModificationContext::Anywhere,
775 );
776
777 SubstitutionGroup::AttributeEntryValue.apply(&mut content, &p, None);
778 assert!(!content.is_empty());
779 assert_eq!(
780 content.rendered,
781 CowStr::Boxed("bl<ah> red".to_string().into_boxed_str())
782 );
783 }
784 }
785
786 mod header {
787 use crate::{content::Content, strings::CowStr, tests::prelude::*};
788
789 #[test]
790 fn empty() {
791 let mut content = Content::from(crate::Span::default());
792 let p = Parser::default();
793 SubstitutionGroup::Header.apply(&mut content, &p, None);
794 assert!(content.is_empty());
795 assert_eq!(content.rendered, CowStr::Borrowed(""));
796 }
797
798 #[test]
799 fn basic_non_empty_span() {
800 let mut content = Content::from(crate::Span::new("blah"));
801 let p = Parser::default();
802 SubstitutionGroup::Header.apply(&mut content, &p, None);
803 assert!(!content.is_empty());
804 assert_eq!(content.rendered, CowStr::Borrowed("blah"));
805 }
806
807 #[test]
808 fn match_lt_and_gt() {
809 let mut content = Content::from(crate::Span::new("bl<ah>"));
810 let p = Parser::default();
811 SubstitutionGroup::Header.apply(&mut content, &p, None);
812 assert!(!content.is_empty());
813 assert_eq!(
814 content.rendered,
815 CowStr::Boxed("bl<ah>".to_string().into_boxed_str())
816 );
817 }
818
819 #[test]
820 fn match_amp() {
821 let mut content = Content::from(crate::Span::new("bl<a&h>"));
822 let p = Parser::default();
823 SubstitutionGroup::Header.apply(&mut content, &p, None);
824 assert!(!content.is_empty());
825 assert_eq!(
826 content.rendered,
827 CowStr::Boxed("bl<a&h>".to_string().into_boxed_str())
828 );
829 }
830
831 #[test]
832 fn ignores_strong_word() {
833 let mut content = Content::from(crate::Span::new("One *word* is strong."));
834 let p = Parser::default();
835 SubstitutionGroup::Header.apply(&mut content, &p, None);
836 assert!(!content.is_empty());
837 assert_eq!(content.rendered, CowStr::Borrowed("One *word* is strong."));
838 }
839
840 #[test]
841 fn ignores_strong_word_with_special_chars() {
842 let mut content = Content::from(crate::Span::new("One *wo<r>d* is strong."));
843 let p = Parser::default();
844 SubstitutionGroup::Header.apply(&mut content, &p, None);
845 assert!(!content.is_empty());
846 assert_eq!(
847 content.rendered,
848 CowStr::Boxed("One *wo<r>d* is strong.".to_string().into_boxed_str())
849 );
850 }
851
852 #[test]
853 fn ignores_marked_string_with_id() {
854 let mut content = Content::from(crate::Span::new(r#"[#id]#a few words#"#));
855 let p = Parser::default();
856 SubstitutionGroup::Header.apply(&mut content, &p, None);
857 assert!(!content.is_empty());
858 assert_eq!(content.rendered, CowStr::Borrowed("[#id]#a few words#"));
859 }
860 }
861
862 mod title {
863 use crate::{content::Content, strings::CowStr, tests::prelude::*};
864
865 #[test]
866 fn empty() {
867 let mut content = Content::from(crate::Span::default());
868 let p = Parser::default();
869 SubstitutionGroup::Title.apply(&mut content, &p, None);
870 assert!(content.is_empty());
871 assert_eq!(content.rendered, CowStr::Borrowed(""));
872 }
873
874 #[test]
875 fn basic_non_empty_span() {
876 let mut content = Content::from(crate::Span::new("blah"));
877 let p = Parser::default();
878 SubstitutionGroup::Title.apply(&mut content, &p, None);
879 assert!(!content.is_empty());
880 assert_eq!(content.rendered, CowStr::Borrowed("blah"));
881 }
882
883 #[test]
884 fn match_lt_and_gt() {
885 let mut content = Content::from(crate::Span::new("bl<ah>"));
886 let p = Parser::default();
887 SubstitutionGroup::Title.apply(&mut content, &p, None);
888 assert!(!content.is_empty());
889 assert_eq!(
890 content.rendered,
891 CowStr::Boxed("bl<ah>".to_string().into_boxed_str())
892 );
893 }
894
895 #[test]
896 fn match_amp() {
897 let mut content = Content::from(crate::Span::new("bl<a&h>"));
898 let p = Parser::default();
899 SubstitutionGroup::Title.apply(&mut content, &p, None);
900 assert!(!content.is_empty());
901 assert_eq!(
902 content.rendered,
903 CowStr::Boxed("bl<a&h>".to_string().into_boxed_str())
904 );
905 }
906
907 #[test]
908 fn strong_word() {
909 let mut content = Content::from(crate::Span::new("One *word* is strong."));
910 let p = Parser::default();
911 SubstitutionGroup::Title.apply(&mut content, &p, None);
912 assert!(!content.is_empty());
913 assert_eq!(
914 content.rendered,
915 CowStr::Boxed(
916 "One <strong>word</strong> is strong."
917 .to_string()
918 .into_boxed_str()
919 )
920 );
921 }
922
923 #[test]
924 fn strong_word_with_special_chars() {
925 let mut content = Content::from(crate::Span::new("One *wo<r>d* is strong."));
926 let p = Parser::default();
927 SubstitutionGroup::Title.apply(&mut content, &p, None);
928 assert!(!content.is_empty());
929 assert_eq!(
930 content.rendered,
931 CowStr::Boxed(
932 "One <strong>wo<r>d</strong> is strong."
933 .to_string()
934 .into_boxed_str()
935 )
936 );
937 }
938
939 #[test]
940 fn marked_string_with_id() {
941 let mut content = Content::from(crate::Span::new(r#"[#id]#a few words#"#));
942 let p = Parser::default();
943 SubstitutionGroup::Title.apply(&mut content, &p, None);
944 assert!(!content.is_empty());
945 assert_eq!(
946 content.rendered,
947 CowStr::Boxed(r#"<span id="id">a few words</span>"#.to_string().into_boxed_str())
948 );
949 }
950
951 #[test]
952 fn title_behaves_same_as_normal() {
953 let test_input = "One *wo<r>d* is strong with [#id]#marked text#.";
954
955 let mut title_content = Content::from(crate::Span::new(test_input));
956 let mut normal_content = Content::from(crate::Span::new(test_input));
957 let p = Parser::default();
958
959 SubstitutionGroup::Title.apply(&mut title_content, &p, None);
960 SubstitutionGroup::Normal.apply(&mut normal_content, &p, None);
961
962 assert_eq!(title_content.rendered, normal_content.rendered);
964 }
965 }
966}