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