1use crate::{
2 HasSpan, Parser, Span,
3 attributes::Attrlist,
4 blocks::{ContentModel, IsBlock, metadata::BlockMetadata},
5 content::{Content, SubstitutionGroup},
6 span::MatchedItem,
7 strings::CowStr,
8 warnings::{MatchAndWarnings, Warning, WarningType},
9};
10
11#[derive(Clone, Debug, Eq, PartialEq)]
29pub struct RawDelimitedBlock<'src> {
30 content: Content<'src>,
31 content_model: ContentModel,
32 context: CowStr<'src>,
33 source: Span<'src>,
34 title_source: Option<Span<'src>>,
35 title: Option<String>,
36 anchor: Option<Span<'src>>,
37 anchor_reftext: Option<Span<'src>>,
38 attrlist: Option<Attrlist<'src>>,
39 substitution_group: SubstitutionGroup,
40}
41
42impl<'src> RawDelimitedBlock<'src> {
43 pub(crate) fn is_valid_delimiter(line: &Span<'src>) -> bool {
44 let data = line.data();
45
46 if data.len() >= 4 {
51 if data.starts_with("////") {
52 data.split_at(4).1.chars().all(|c| c == '/')
53 } else if data.starts_with("----") {
54 data.split_at(4).1.chars().all(|c| c == '-')
55 } else if data.starts_with("....") {
56 data.split_at(4).1.chars().all(|c| c == '.')
57 } else if data.starts_with("++++") {
58 data.split_at(4).1.chars().all(|c| c == '+')
59 } else {
60 false
61 }
62 } else {
63 false
64 }
65 }
66
67 pub(crate) fn parse(
68 metadata: &BlockMetadata<'src>,
69 parser: &mut Parser,
70 ) -> Option<MatchAndWarnings<'src, Option<MatchedItem<'src, Self>>>> {
71 let delimiter = metadata.block_start.take_normalized_line();
72 let delimiter_data = delimiter.item.data();
73
74 let (content_model, context, mut substitution_group) = if delimiter_data == "--" {
79 open_block_verbatim_masquerade(metadata.attrlist.as_ref())?
82 } else {
83 if delimiter.item.len() < 4 {
84 return None;
85 }
86
87 let block_type = match delimiter_data
88 .split_at_checked(delimiter_data.len().min(4))?
89 .0
90 {
91 "////" => (ContentModel::Raw, "comment", SubstitutionGroup::None),
92 "----" => (
93 ContentModel::Verbatim,
94 "listing",
95 SubstitutionGroup::Verbatim,
96 ),
97 "...." => (
98 ContentModel::Verbatim,
99 "literal",
100 SubstitutionGroup::Verbatim,
101 ),
102 "++++" => (ContentModel::Raw, "pass", SubstitutionGroup::Pass),
103 _ => {
104 return None;
105 }
106 };
107
108 if !Self::is_valid_delimiter(&delimiter.item) {
112 return None;
113 }
114
115 block_type
116 };
117
118 let content_start = delimiter.after;
119 let mut next = content_start;
120
121 while !next.is_empty() {
122 let line = next.take_normalized_line();
123 if line.item.data() == delimiter.item.data() {
124 let content = content_start.trim_remainder(next).trim_trailing_line_end();
125
126 let mut content: Content<'src> = content.into();
127
128 substitution_group =
129 substitution_group.override_via_attrlist(metadata.attrlist.as_ref());
130
131 substitution_group.apply(&mut content, parser, metadata.attrlist.as_ref());
132
133 return Some(MatchAndWarnings {
134 item: Some(MatchedItem {
135 item: Self {
136 content,
137 content_model,
138 context: context.into(),
139 source: metadata
140 .source
141 .trim_remainder(line.after)
142 .trim_trailing_line_end(),
143 title_source: metadata.title_source,
144 title: metadata.title.clone(),
145 anchor: metadata.anchor,
146 anchor_reftext: metadata.anchor_reftext,
147 attrlist: metadata.attrlist.clone(),
148 substitution_group,
149 },
150 after: line.after,
151 }),
152 warnings: vec![],
153 });
154 }
155
156 next = line.after;
157 }
158
159 let content = content_start.trim_remainder(next).trim_trailing_line_end();
160
161 Some(MatchAndWarnings {
162 item: Some(MatchedItem {
163 item: Self {
164 content: content.into(),
165 content_model,
166 context: context.into(),
167 source: metadata
168 .source
169 .trim_remainder(next)
170 .trim_trailing_line_end(),
171 title_source: metadata.title_source,
172 title: metadata.title.clone(),
173 anchor: metadata.anchor,
174 anchor_reftext: metadata.anchor_reftext,
175 attrlist: metadata.attrlist.clone(),
176 substitution_group,
177 },
178 after: next,
179 }),
180 warnings: vec![Warning {
181 source: delimiter.item,
182 warning: WarningType::UnterminatedDelimitedBlock,
183 }],
184 })
185 }
186
187 pub fn content(&self) -> &Content<'src> {
189 &self.content
190 }
191}
192
193fn open_block_verbatim_masquerade(
203 attrlist: Option<&Attrlist<'_>>,
204) -> Option<(ContentModel, &'static str, SubstitutionGroup)> {
205 match attrlist?.block_style()? {
206 "source" | "listing" => Some((
207 ContentModel::Verbatim,
208 "listing",
209 SubstitutionGroup::Verbatim,
210 )),
211 "literal" => Some((
212 ContentModel::Verbatim,
213 "literal",
214 SubstitutionGroup::Verbatim,
215 )),
216 _ => None,
217 }
218}
219
220impl<'src> IsBlock<'src> for RawDelimitedBlock<'src> {
221 fn content_model(&self) -> ContentModel {
222 self.content_model
223 }
224
225 fn content_mut(&mut self) -> Option<&mut Content<'src>> {
226 Some(&mut self.content)
227 }
228
229 fn rendered_content(&self) -> Option<&str> {
230 Some(self.content.rendered())
231 }
232
233 fn raw_context(&self) -> CowStr<'src> {
234 self.context.clone()
235 }
236
237 fn title_source(&'src self) -> Option<Span<'src>> {
238 self.title_source
239 }
240
241 fn title(&self) -> Option<&str> {
242 self.title.as_deref()
243 }
244
245 fn anchor(&'src self) -> Option<Span<'src>> {
246 self.anchor
247 }
248
249 fn anchor_reftext(&'src self) -> Option<Span<'src>> {
250 self.anchor_reftext
251 }
252
253 fn attrlist(&'src self) -> Option<&'src Attrlist<'src>> {
254 self.attrlist.as_ref()
255 }
256
257 fn substitution_group(&'src self) -> SubstitutionGroup {
258 self.substitution_group.clone()
259 }
260}
261
262impl<'src> HasSpan<'src> for RawDelimitedBlock<'src> {
263 fn span(&self) -> Span<'src> {
264 self.source
265 }
266}
267
268#[cfg(test)]
269mod tests {
270 #![allow(clippy::unwrap_used)]
271
272 mod is_valid_delimiter {
273 use crate::blocks::RawDelimitedBlock;
274
275 #[test]
276 fn comment() {
277 assert!(RawDelimitedBlock::is_valid_delimiter(&crate::Span::new(
278 "////"
279 )));
280 assert!(RawDelimitedBlock::is_valid_delimiter(&crate::Span::new(
281 "/////"
282 )));
283 assert!(RawDelimitedBlock::is_valid_delimiter(&crate::Span::new(
284 "/////////"
285 )));
286
287 assert!(!RawDelimitedBlock::is_valid_delimiter(&crate::Span::new(
288 "///"
289 )));
290 assert!(!RawDelimitedBlock::is_valid_delimiter(&crate::Span::new(
291 "//-/"
292 )));
293 assert!(!RawDelimitedBlock::is_valid_delimiter(&crate::Span::new(
294 "////-"
295 )));
296 assert!(!RawDelimitedBlock::is_valid_delimiter(&crate::Span::new(
297 "//////////x"
298 )));
299 }
300
301 #[test]
302 fn example() {
303 assert!(!RawDelimitedBlock::is_valid_delimiter(&crate::Span::new(
304 "===="
305 )));
306 assert!(!RawDelimitedBlock::is_valid_delimiter(&crate::Span::new(
307 "====="
308 )));
309 assert!(!RawDelimitedBlock::is_valid_delimiter(&crate::Span::new(
310 "==="
311 )));
312 }
313
314 #[test]
315 fn listing() {
316 assert!(RawDelimitedBlock::is_valid_delimiter(&crate::Span::new(
317 "----"
318 )));
319 assert!(RawDelimitedBlock::is_valid_delimiter(&crate::Span::new(
320 "-----"
321 )));
322 assert!(RawDelimitedBlock::is_valid_delimiter(&crate::Span::new(
323 "---------"
324 )));
325
326 assert!(!RawDelimitedBlock::is_valid_delimiter(&crate::Span::new(
327 "---"
328 )));
329 assert!(!RawDelimitedBlock::is_valid_delimiter(&crate::Span::new(
330 "--/-"
331 )));
332 assert!(!RawDelimitedBlock::is_valid_delimiter(&crate::Span::new(
333 "----/"
334 )));
335 assert!(!RawDelimitedBlock::is_valid_delimiter(&crate::Span::new(
336 "----------x"
337 )));
338 }
339
340 #[test]
341 fn literal() {
342 assert!(RawDelimitedBlock::is_valid_delimiter(&crate::Span::new(
343 "...."
344 )));
345 assert!(RawDelimitedBlock::is_valid_delimiter(&crate::Span::new(
346 "....."
347 )));
348 assert!(RawDelimitedBlock::is_valid_delimiter(&crate::Span::new(
349 "........."
350 )));
351
352 assert!(!RawDelimitedBlock::is_valid_delimiter(&crate::Span::new(
353 "..."
354 )));
355 assert!(!RawDelimitedBlock::is_valid_delimiter(&crate::Span::new(
356 "../."
357 )));
358 assert!(!RawDelimitedBlock::is_valid_delimiter(&crate::Span::new(
359 "..../"
360 )));
361 assert!(!RawDelimitedBlock::is_valid_delimiter(&crate::Span::new(
362 "..........x"
363 )));
364 }
365
366 #[test]
367 fn sidebar() {
368 assert!(!RawDelimitedBlock::is_valid_delimiter(&crate::Span::new(
369 "****"
370 )));
371 assert!(!RawDelimitedBlock::is_valid_delimiter(&crate::Span::new(
372 "*****"
373 )));
374 assert!(!RawDelimitedBlock::is_valid_delimiter(&crate::Span::new(
375 "***"
376 )));
377 }
378
379 #[test]
380 fn table() {
381 assert!(!RawDelimitedBlock::is_valid_delimiter(&crate::Span::new(
382 "|==="
383 )));
384 assert!(!RawDelimitedBlock::is_valid_delimiter(&crate::Span::new(
385 ",==="
386 )));
387 assert!(!RawDelimitedBlock::is_valid_delimiter(&crate::Span::new(
388 ":==="
389 )));
390 assert!(!RawDelimitedBlock::is_valid_delimiter(&crate::Span::new(
391 "!==="
392 )));
393 }
394
395 #[test]
396 fn pass() {
397 assert!(RawDelimitedBlock::is_valid_delimiter(&crate::Span::new(
398 "++++"
399 )));
400 assert!(RawDelimitedBlock::is_valid_delimiter(&crate::Span::new(
401 "+++++"
402 )));
403 assert!(RawDelimitedBlock::is_valid_delimiter(&crate::Span::new(
404 "+++++++++"
405 )));
406
407 assert!(!RawDelimitedBlock::is_valid_delimiter(&crate::Span::new(
408 "+++"
409 )));
410 assert!(!RawDelimitedBlock::is_valid_delimiter(&crate::Span::new(
411 "++/+"
412 )));
413 assert!(!RawDelimitedBlock::is_valid_delimiter(&crate::Span::new(
414 "++++/"
415 )));
416 assert!(!RawDelimitedBlock::is_valid_delimiter(&crate::Span::new(
417 "++++++++++x"
418 )));
419 }
420
421 #[test]
422 fn quote() {
423 assert!(!RawDelimitedBlock::is_valid_delimiter(&crate::Span::new(
424 "____"
425 )));
426 assert!(!RawDelimitedBlock::is_valid_delimiter(&crate::Span::new(
427 "_____"
428 )));
429 assert!(!RawDelimitedBlock::is_valid_delimiter(&crate::Span::new(
430 "___"
431 )));
432 }
433 }
434
435 mod parse {
436 use crate::{blocks::metadata::BlockMetadata, tests::prelude::*};
437
438 #[test]
439 fn err_invalid_delimiter() {
440 let mut parser = Parser::default();
441 assert!(
442 crate::blocks::RawDelimitedBlock::parse(&BlockMetadata::new(""), &mut parser)
443 .is_none()
444 );
445
446 let mut parser = Parser::default();
447 assert!(
448 crate::blocks::RawDelimitedBlock::parse(&BlockMetadata::new("..."), &mut parser)
449 .is_none()
450 );
451
452 let mut parser = Parser::default();
453 assert!(
454 crate::blocks::RawDelimitedBlock::parse(&BlockMetadata::new("++++x"), &mut parser)
455 .is_none()
456 );
457
458 let mut parser = Parser::default();
459 assert!(
460 crate::blocks::RawDelimitedBlock::parse(&BlockMetadata::new("____x"), &mut parser)
461 .is_none()
462 );
463
464 let mut parser = Parser::default();
465 assert!(
466 crate::blocks::RawDelimitedBlock::parse(&BlockMetadata::new("====x"), &mut parser)
467 .is_none()
468 );
469
470 let mut parser = Parser::default();
471 assert!(
472 crate::blocks::RawDelimitedBlock::parse(&BlockMetadata::new("==\n=="), &mut parser)
473 .is_none()
474 );
475
476 let mut parser = Parser::default();
477 assert!(
478 crate::blocks::RawDelimitedBlock::parse(&BlockMetadata::new("===😀"), &mut parser)
479 .is_none()
480 );
481 }
482
483 #[test]
484 fn err_unterminated() {
485 let mut parser = Parser::default();
486
487 let maw = crate::blocks::RawDelimitedBlock::parse(
488 &BlockMetadata::new("....\nblah blah blah"),
489 &mut parser,
490 )
491 .unwrap();
492
493 assert_eq!(
494 maw.warnings,
495 vec![Warning {
496 source: Span {
497 data: "....",
498 line: 1,
499 col: 1,
500 offset: 0,
501 },
502 warning: WarningType::UnterminatedDelimitedBlock,
503 }]
504 );
505 }
506 }
507
508 mod comment {
509 use crate::{
510 blocks::{ContentModel, IsBlock, metadata::BlockMetadata},
511 tests::prelude::*,
512 };
513
514 #[test]
515 fn empty() {
516 let mut parser = Parser::default();
517 let maw = crate::blocks::RawDelimitedBlock::parse(
518 &BlockMetadata::new("////\n////"),
519 &mut parser,
520 )
521 .unwrap();
522
523 let mi = maw.item.unwrap().clone();
524
525 assert_eq!(
526 mi.item,
527 RawDelimitedBlock {
528 content: Content {
529 original: Span {
530 data: "",
531 line: 2,
532 col: 1,
533 offset: 5,
534 },
535 rendered: "",
536 },
537 content_model: ContentModel::Raw,
538 context: "comment",
539 source: Span {
540 data: "////\n////",
541 line: 1,
542 col: 1,
543 offset: 0,
544 },
545 title_source: None,
546 title: None,
547 anchor: None,
548 anchor_reftext: None,
549 attrlist: None,
550 substitution_group: SubstitutionGroup::None,
551 }
552 );
553
554 assert_eq!(mi.item.content_model(), ContentModel::Raw);
555 assert_eq!(mi.item.rendered_content().unwrap(), "");
556 assert_eq!(mi.item.raw_context().as_ref(), "comment");
557 assert_eq!(mi.item.resolved_context().as_ref(), "comment");
558 assert!(mi.item.declared_style().is_none());
559 assert!(mi.item.content().is_empty());
560 assert!(mi.item.id().is_none());
561 assert!(mi.item.roles().is_empty());
562 assert!(mi.item.options().is_empty());
563 assert!(mi.item.title_source().is_none());
564 assert!(mi.item.title().is_none());
565 assert!(mi.item.anchor().is_none());
566 assert!(mi.item.anchor_reftext().is_none());
567 assert!(mi.item.attrlist().is_none());
568 assert_eq!(mi.item.substitution_group(), SubstitutionGroup::None);
569 }
570
571 #[test]
572 fn multiple_lines() {
573 let mut parser = Parser::default();
574
575 let maw = crate::blocks::RawDelimitedBlock::parse(
576 &BlockMetadata::new("////\nline1 \nline2\n////"),
577 &mut parser,
578 )
579 .unwrap();
580
581 let mi = maw.item.unwrap().clone();
582
583 assert_eq!(
584 mi.item,
585 RawDelimitedBlock {
586 content: Content {
587 original: Span {
588 data: "line1 \nline2",
589 line: 2,
590 col: 1,
591 offset: 5,
592 },
593 rendered: "line1 \nline2",
594 },
595 content_model: ContentModel::Raw,
596 context: "comment",
597 source: Span {
598 data: "////\nline1 \nline2\n////",
599 line: 1,
600 col: 1,
601 offset: 0,
602 },
603 title_source: None,
604 title: None,
605 anchor: None,
606 anchor_reftext: None,
607 attrlist: None,
608 substitution_group: SubstitutionGroup::None,
609 }
610 );
611
612 assert_eq!(mi.item.content_model(), ContentModel::Raw);
613 assert_eq!(mi.item.rendered_content().unwrap(), "line1 \nline2");
614 assert_eq!(mi.item.raw_context().as_ref(), "comment");
615 assert_eq!(mi.item.resolved_context().as_ref(), "comment");
616 assert!(mi.item.declared_style().is_none());
617 assert!(mi.item.id().is_none());
618 assert!(mi.item.roles().is_empty());
619 assert!(mi.item.options().is_empty());
620 assert!(mi.item.title_source().is_none());
621 assert!(mi.item.title().is_none());
622 assert!(mi.item.anchor().is_none());
623 assert!(mi.item.anchor_reftext().is_none());
624 assert!(mi.item.attrlist().is_none());
625 assert_eq!(mi.item.substitution_group(), SubstitutionGroup::None);
626
627 assert_eq!(
628 mi.item.content(),
629 Content {
630 original: Span {
631 data: "line1 \nline2",
632 line: 2,
633 col: 1,
634 offset: 5,
635 },
636 rendered: "line1 \nline2",
637 }
638 );
639 }
640
641 #[test]
642 fn ignores_delimiter_prefix() {
643 let mut parser = Parser::default();
644
645 let maw = crate::blocks::RawDelimitedBlock::parse(
646 &BlockMetadata::new("////\nline1 \n/////\nline2\n////"),
647 &mut parser,
648 )
649 .unwrap();
650
651 let mi = maw.item.unwrap().clone();
652
653 assert_eq!(
654 mi.item,
655 RawDelimitedBlock {
656 content: Content {
657 original: Span {
658 data: "line1 \n/////\nline2",
659 line: 2,
660 col: 1,
661 offset: 5,
662 },
663 rendered: "line1 \n/////\nline2",
664 },
665 content_model: ContentModel::Raw,
666 context: "comment",
667 source: Span {
668 data: "////\nline1 \n/////\nline2\n////",
669 line: 1,
670 col: 1,
671 offset: 0,
672 },
673 title_source: None,
674 title: None,
675 anchor: None,
676 anchor_reftext: None,
677 attrlist: None,
678 substitution_group: SubstitutionGroup::None,
679 }
680 );
681
682 assert_eq!(mi.item.content_model(), ContentModel::Raw);
683 assert_eq!(mi.item.raw_context().as_ref(), "comment");
684 assert_eq!(mi.item.resolved_context().as_ref(), "comment");
685 assert!(mi.item.declared_style().is_none());
686 assert!(mi.item.id().is_none());
687 assert!(mi.item.roles().is_empty());
688 assert!(mi.item.options().is_empty());
689 assert!(mi.item.title_source().is_none());
690 assert!(mi.item.title().is_none());
691 assert!(mi.item.anchor().is_none());
692 assert!(mi.item.anchor_reftext().is_none());
693 assert!(mi.item.attrlist().is_none());
694 assert_eq!(mi.item.substitution_group(), SubstitutionGroup::None);
695
696 assert_eq!(
697 mi.item.content(),
698 Content {
699 original: Span {
700 data: "line1 \n/////\nline2",
701 line: 2,
702 col: 1,
703 offset: 5,
704 },
705 rendered: "line1 \n/////\nline2",
706 }
707 );
708 }
709
710 #[test]
711 fn no_panic_for_utf8_code_point_using_more_than_one_byte() {
712 let mut parser = Parser::default();
713 assert!(
714 crate::blocks::RawDelimitedBlock::parse(&BlockMetadata::new("///😀"), &mut parser)
715 .is_none()
716 );
717 }
718 }
719
720 mod example {
721 use crate::{blocks::metadata::BlockMetadata, tests::prelude::*};
722
723 #[test]
724 fn empty() {
725 let mut parser = Parser::default();
726 assert!(
727 crate::blocks::RawDelimitedBlock::parse(
728 &BlockMetadata::new("====\n===="),
729 &mut parser
730 )
731 .is_none()
732 );
733 }
734
735 #[test]
736 fn multiple_lines() {
737 let mut parser = Parser::default();
738 assert!(
739 crate::blocks::RawDelimitedBlock::parse(
740 &BlockMetadata::new("====\nline1 \nline2\n===="),
741 &mut parser
742 )
743 .is_none()
744 );
745 }
746 }
747
748 mod listing {
749 use crate::{
750 blocks::{ContentModel, metadata::BlockMetadata},
751 content::SubstitutionStep,
752 tests::prelude::*,
753 };
754
755 #[test]
756 fn empty() {
757 let mut parser = Parser::default();
758
759 let maw = crate::blocks::RawDelimitedBlock::parse(
760 &BlockMetadata::new("----\n----"),
761 &mut parser,
762 )
763 .unwrap();
764
765 let mi = maw.item.unwrap().clone();
766
767 assert_eq!(
768 mi.item,
769 RawDelimitedBlock {
770 content: Content {
771 original: Span {
772 data: "",
773 line: 2,
774 col: 1,
775 offset: 5,
776 },
777 rendered: "",
778 },
779 content_model: ContentModel::Verbatim,
780 context: "listing",
781 source: Span {
782 data: "----\n----",
783 line: 1,
784 col: 1,
785 offset: 0,
786 },
787 title_source: None,
788 title: None,
789 anchor: None,
790 anchor_reftext: None,
791 attrlist: None,
792 substitution_group: SubstitutionGroup::Verbatim,
793 }
794 );
795
796 assert_eq!(mi.item.content_model(), ContentModel::Verbatim);
797 assert_eq!(mi.item.raw_context().as_ref(), "listing");
798 assert_eq!(mi.item.resolved_context().as_ref(), "listing");
799 assert!(mi.item.declared_style().is_none());
800 assert!(mi.item.content().is_empty());
801 assert!(mi.item.id().is_none());
802 assert!(mi.item.roles().is_empty());
803 assert!(mi.item.options().is_empty());
804 assert!(mi.item.title_source().is_none());
805 assert!(mi.item.title().is_none());
806 assert!(mi.item.anchor().is_none());
807 assert!(mi.item.anchor_reftext().is_none());
808 assert!(mi.item.attrlist().is_none());
809 assert_eq!(mi.item.substitution_group(), SubstitutionGroup::Verbatim);
810 }
811
812 #[test]
813 fn multiple_lines() {
814 let mut parser = Parser::default();
815
816 let maw = crate::blocks::RawDelimitedBlock::parse(
817 &BlockMetadata::new("----\nline1 \nline2\n----"),
818 &mut parser,
819 )
820 .unwrap();
821
822 let mi = maw.item.unwrap().clone();
823
824 assert_eq!(
825 mi.item,
826 RawDelimitedBlock {
827 content: Content {
828 original: Span {
829 data: "line1 \nline2",
830 line: 2,
831 col: 1,
832 offset: 5,
833 },
834 rendered: "line1 \nline2",
835 },
836 content_model: ContentModel::Verbatim,
837 context: "listing",
838 source: Span {
839 data: "----\nline1 \nline2\n----",
840 line: 1,
841 col: 1,
842 offset: 0,
843 },
844 title_source: None,
845 title: None,
846 anchor: None,
847 anchor_reftext: None,
848 attrlist: None,
849 substitution_group: SubstitutionGroup::Verbatim,
850 }
851 );
852
853 assert_eq!(mi.item.content_model(), ContentModel::Verbatim);
854 assert_eq!(mi.item.raw_context().as_ref(), "listing");
855 assert_eq!(mi.item.resolved_context().as_ref(), "listing");
856 assert!(mi.item.declared_style().is_none());
857 assert!(mi.item.id().is_none());
858 assert!(mi.item.roles().is_empty());
859 assert!(mi.item.options().is_empty());
860 assert!(mi.item.title_source().is_none());
861 assert!(mi.item.title().is_none());
862 assert!(mi.item.anchor().is_none());
863 assert!(mi.item.anchor_reftext().is_none());
864 assert!(mi.item.attrlist().is_none());
865 assert_eq!(mi.item.substitution_group(), SubstitutionGroup::Verbatim);
866
867 assert_eq!(
868 mi.item.content(),
869 Content {
870 original: Span {
871 data: "line1 \nline2",
872 line: 2,
873 col: 1,
874 offset: 5,
875 },
876 rendered: "line1 \nline2",
877 }
878 );
879 }
880
881 #[test]
882 fn overrides_sub_group_via_subs_attribute() {
883 let mut parser = Parser::default();
884
885 let maw = crate::blocks::RawDelimitedBlock::parse(
886 &BlockMetadata::new("[subs=quotes]\n----\nline1 < *line2*\n----"),
887 &mut parser,
888 )
889 .unwrap();
890
891 let mi = maw.item.unwrap().clone();
892
893 assert_eq!(
894 mi.item,
895 RawDelimitedBlock {
896 content: Content {
897 original: Span {
898 data: "line1 < *line2*",
899 line: 3,
900 col: 1,
901 offset: 19,
902 },
903 rendered: "line1 < <strong>line2</strong>",
904 },
905 content_model: ContentModel::Verbatim,
906 context: "listing",
907 source: Span {
908 data: "[subs=quotes]\n----\nline1 < *line2*\n----",
909 line: 1,
910 col: 1,
911 offset: 0,
912 },
913 title_source: None,
914 title: None,
915 anchor: None,
916 anchor_reftext: None,
917 attrlist: Some(Attrlist {
918 attributes: &[ElementAttribute {
919 name: Some("subs"),
920 value: "quotes",
921 shorthand_items: &[],
922 },],
923 anchor: None,
924 source: Span {
925 data: "subs=quotes",
926 line: 1,
927 col: 2,
928 offset: 1,
929 },
930 },),
931 substitution_group: SubstitutionGroup::Custom(vec![SubstitutionStep::Quotes]),
932 }
933 );
934
935 assert_eq!(mi.item.content_model(), ContentModel::Verbatim);
936 assert_eq!(mi.item.raw_context().as_ref(), "listing");
937 assert_eq!(mi.item.resolved_context().as_ref(), "listing");
938 assert!(mi.item.declared_style().is_none());
939 assert!(mi.item.id().is_none());
940 assert!(mi.item.roles().is_empty());
941 assert!(mi.item.options().is_empty());
942 assert!(mi.item.title_source().is_none());
943 assert!(mi.item.title().is_none());
944 assert!(mi.item.anchor().is_none());
945 assert!(mi.item.anchor_reftext().is_none());
946
947 assert_eq!(
948 mi.item.attrlist().unwrap(),
949 Attrlist {
950 attributes: &[ElementAttribute {
951 name: Some("subs"),
952 value: "quotes",
953 shorthand_items: &[],
954 },],
955 anchor: None,
956 source: Span {
957 data: "subs=quotes",
958 line: 1,
959 col: 2,
960 offset: 1,
961 },
962 }
963 );
964
965 assert_eq!(
966 mi.item.substitution_group(),
967 SubstitutionGroup::Custom(vec![SubstitutionStep::Quotes])
968 );
969
970 assert_eq!(
971 mi.item.content(),
972 Content {
973 original: Span {
974 data: "line1 < *line2*",
975 line: 3,
976 col: 1,
977 offset: 19,
978 },
979 rendered: "line1 < <strong>line2</strong>",
980 }
981 );
982 }
983
984 #[test]
985 fn ignores_delimiter_prefix() {
986 let mut parser = Parser::default();
987
988 let maw = crate::blocks::RawDelimitedBlock::parse(
989 &BlockMetadata::new("----\nline1 \n-----\nline2\n----"),
990 &mut parser,
991 )
992 .unwrap();
993
994 let mi = maw.item.unwrap().clone();
995
996 assert_eq!(
997 mi.item,
998 RawDelimitedBlock {
999 content: Content {
1000 original: Span {
1001 data: "line1 \n-----\nline2",
1002 line: 2,
1003 col: 1,
1004 offset: 5,
1005 },
1006 rendered: "line1 \n-----\nline2",
1007 },
1008 content_model: ContentModel::Verbatim,
1009 context: "listing",
1010 source: Span {
1011 data: "----\nline1 \n-----\nline2\n----",
1012 line: 1,
1013 col: 1,
1014 offset: 0,
1015 },
1016 title_source: None,
1017 title: None,
1018 anchor: None,
1019 anchor_reftext: None,
1020 attrlist: None,
1021 substitution_group: SubstitutionGroup::Verbatim,
1022 }
1023 );
1024
1025 assert_eq!(mi.item.content_model(), ContentModel::Verbatim);
1026 assert_eq!(mi.item.raw_context().as_ref(), "listing");
1027 assert_eq!(mi.item.resolved_context().as_ref(), "listing");
1028 assert!(mi.item.declared_style().is_none());
1029 assert!(mi.item.id().is_none());
1030 assert!(mi.item.roles().is_empty());
1031 assert!(mi.item.options().is_empty());
1032 assert!(mi.item.title_source().is_none());
1033 assert!(mi.item.title().is_none());
1034 assert!(mi.item.anchor().is_none());
1035 assert!(mi.item.anchor_reftext().is_none());
1036 assert!(mi.item.attrlist().is_none());
1037 assert_eq!(mi.item.substitution_group(), SubstitutionGroup::Verbatim);
1038
1039 assert_eq!(
1040 mi.item.content(),
1041 Content {
1042 original: Span {
1043 data: "line1 \n-----\nline2",
1044 line: 2,
1045 col: 1,
1046 offset: 5,
1047 },
1048 rendered: "line1 \n-----\nline2",
1049 }
1050 );
1051
1052 assert_eq!(
1053 mi.item.content(),
1054 Content {
1055 original: Span {
1056 data: "line1 \n-----\nline2",
1057 line: 2,
1058 col: 1,
1059 offset: 5,
1060 },
1061 rendered: "line1 \n-----\nline2",
1062 }
1063 );
1064 }
1065 }
1066
1067 mod sidebar {
1068 use crate::{blocks::metadata::BlockMetadata, tests::prelude::*};
1069
1070 #[test]
1071 fn empty() {
1072 let mut parser = Parser::default();
1073 assert!(
1074 crate::blocks::RawDelimitedBlock::parse(
1075 &BlockMetadata::new("****\n****"),
1076 &mut parser
1077 )
1078 .is_none()
1079 );
1080 }
1081
1082 #[test]
1083 fn multiple_lines() {
1084 let mut parser = Parser::default();
1085 assert!(
1086 crate::blocks::RawDelimitedBlock::parse(
1087 &BlockMetadata::new("****\nline1 \nline2\n****"),
1088 &mut parser
1089 )
1090 .is_none()
1091 );
1092 }
1093 }
1094
1095 mod table {
1096 use crate::{blocks::metadata::BlockMetadata, tests::prelude::*};
1097
1098 #[test]
1099 fn empty() {
1100 let mut parser = Parser::default();
1101 assert!(
1102 crate::blocks::RawDelimitedBlock::parse(
1103 &BlockMetadata::new("|===\n|==="),
1104 &mut parser
1105 )
1106 .is_none()
1107 );
1108
1109 let mut parser = Parser::default();
1110 assert!(
1111 crate::blocks::RawDelimitedBlock::parse(
1112 &BlockMetadata::new(",===\n,==="),
1113 &mut parser
1114 )
1115 .is_none()
1116 );
1117
1118 let mut parser = Parser::default();
1119 assert!(
1120 crate::blocks::RawDelimitedBlock::parse(
1121 &BlockMetadata::new(":===\n:==="),
1122 &mut parser
1123 )
1124 .is_none()
1125 );
1126
1127 let mut parser = Parser::default();
1128 assert!(
1129 crate::blocks::RawDelimitedBlock::parse(
1130 &BlockMetadata::new("!===\n!==="),
1131 &mut parser
1132 )
1133 .is_none()
1134 );
1135 }
1136
1137 #[test]
1138 fn multiple_lines() {
1139 let mut parser = Parser::default();
1140 assert!(
1141 crate::blocks::RawDelimitedBlock::parse(
1142 &BlockMetadata::new("|===\nline1 \nline2\n|==="),
1143 &mut parser
1144 )
1145 .is_none()
1146 );
1147
1148 let mut parser = Parser::default();
1149 assert!(
1150 crate::blocks::RawDelimitedBlock::parse(
1151 &BlockMetadata::new(",===\nline1 \nline2\n,==="),
1152 &mut parser
1153 )
1154 .is_none()
1155 );
1156
1157 let mut parser = Parser::default();
1158 assert!(
1159 crate::blocks::RawDelimitedBlock::parse(
1160 &BlockMetadata::new(":===\nline1 \nline2\n:==="),
1161 &mut parser
1162 )
1163 .is_none()
1164 );
1165
1166 let mut parser = Parser::default();
1167 assert!(
1168 crate::blocks::RawDelimitedBlock::parse(
1169 &BlockMetadata::new("!===\nline1 \nline2\n!==="),
1170 &mut parser
1171 )
1172 .is_none()
1173 );
1174 }
1175 }
1176
1177 mod pass {
1178 use crate::{
1179 blocks::{ContentModel, metadata::BlockMetadata},
1180 tests::prelude::*,
1181 };
1182
1183 #[test]
1184 fn empty() {
1185 let mut parser = Parser::default();
1186 let maw = crate::blocks::RawDelimitedBlock::parse(
1187 &BlockMetadata::new("++++\n++++"),
1188 &mut parser,
1189 )
1190 .unwrap();
1191
1192 let mi = maw.item.unwrap().clone();
1193
1194 assert_eq!(
1195 mi.item,
1196 RawDelimitedBlock {
1197 content: Content {
1198 original: Span {
1199 data: "",
1200 line: 2,
1201 col: 1,
1202 offset: 5,
1203 },
1204 rendered: "",
1205 },
1206 content_model: ContentModel::Raw,
1207 context: "pass",
1208 source: Span {
1209 data: "++++\n++++",
1210 line: 1,
1211 col: 1,
1212 offset: 0,
1213 },
1214 title_source: None,
1215 title: None,
1216 anchor: None,
1217 anchor_reftext: None,
1218 attrlist: None,
1219 substitution_group: SubstitutionGroup::Pass,
1220 }
1221 );
1222
1223 assert_eq!(mi.item.content_model(), ContentModel::Raw);
1224 assert_eq!(mi.item.raw_context().as_ref(), "pass");
1225 assert_eq!(mi.item.resolved_context().as_ref(), "pass");
1226 assert!(mi.item.declared_style().is_none());
1227 assert!(mi.item.content().is_empty());
1228 assert!(mi.item.id().is_none());
1229 assert!(mi.item.roles().is_empty());
1230 assert!(mi.item.options().is_empty());
1231 assert!(mi.item.title_source().is_none());
1232 assert!(mi.item.title().is_none());
1233 assert!(mi.item.anchor().is_none());
1234 assert!(mi.item.anchor_reftext().is_none());
1235 assert!(mi.item.attrlist().is_none());
1236 assert_eq!(mi.item.substitution_group(), SubstitutionGroup::Pass);
1237 }
1238
1239 #[test]
1240 fn multiple_lines() {
1241 let mut parser = Parser::default();
1242
1243 let maw = crate::blocks::RawDelimitedBlock::parse(
1244 &BlockMetadata::new("++++\nline1 \nline2\n++++"),
1245 &mut parser,
1246 )
1247 .unwrap();
1248
1249 let mi = maw.item.unwrap().clone();
1250
1251 assert_eq!(
1252 mi.item,
1253 RawDelimitedBlock {
1254 content: Content {
1255 original: Span {
1256 data: "line1 \nline2",
1257 line: 2,
1258 col: 1,
1259 offset: 5,
1260 },
1261 rendered: "line1 \nline2",
1262 },
1263 content_model: ContentModel::Raw,
1264 context: "pass",
1265 source: Span {
1266 data: "++++\nline1 \nline2\n++++",
1267 line: 1,
1268 col: 1,
1269 offset: 0,
1270 },
1271 title_source: None,
1272 title: None,
1273 anchor: None,
1274 anchor_reftext: None,
1275 attrlist: None,
1276 substitution_group: SubstitutionGroup::Pass,
1277 }
1278 );
1279
1280 assert_eq!(mi.item.content_model(), ContentModel::Raw);
1281 assert_eq!(mi.item.raw_context().as_ref(), "pass");
1282 assert_eq!(mi.item.resolved_context().as_ref(), "pass");
1283 assert!(mi.item.declared_style().is_none());
1284 assert!(mi.item.id().is_none());
1285 assert!(mi.item.roles().is_empty());
1286 assert!(mi.item.options().is_empty());
1287 assert!(mi.item.title_source().is_none());
1288 assert!(mi.item.title().is_none());
1289 assert!(mi.item.anchor().is_none());
1290 assert!(mi.item.anchor_reftext().is_none());
1291 assert!(mi.item.attrlist().is_none());
1292 assert_eq!(mi.item.substitution_group(), SubstitutionGroup::Pass);
1293
1294 assert_eq!(
1295 mi.item.content(),
1296 Content {
1297 original: Span {
1298 data: "line1 \nline2",
1299 line: 2,
1300 col: 1,
1301 offset: 5,
1302 },
1303 rendered: "line1 \nline2",
1304 }
1305 );
1306 }
1307
1308 #[test]
1309 fn ignores_delimiter_prefix() {
1310 let mut parser = Parser::default();
1311
1312 let maw = crate::blocks::RawDelimitedBlock::parse(
1313 &BlockMetadata::new("++++\nline1 \n+++++\nline2\n++++"),
1314 &mut parser,
1315 )
1316 .unwrap();
1317
1318 let mi = maw.item.unwrap().clone();
1319
1320 assert_eq!(
1321 mi.item,
1322 RawDelimitedBlock {
1323 content: Content {
1324 original: Span {
1325 data: "line1 \n+++++\nline2",
1326 line: 2,
1327 col: 1,
1328 offset: 5,
1329 },
1330 rendered: "line1 \n+++++\nline2",
1331 },
1332 content_model: ContentModel::Raw,
1333 context: "pass",
1334 source: Span {
1335 data: "++++\nline1 \n+++++\nline2\n++++",
1336 line: 1,
1337 col: 1,
1338 offset: 0,
1339 },
1340 title_source: None,
1341 title: None,
1342 anchor: None,
1343 anchor_reftext: None,
1344 attrlist: None,
1345 substitution_group: SubstitutionGroup::Pass,
1346 }
1347 );
1348
1349 assert_eq!(mi.item.content_model(), ContentModel::Raw);
1350 assert_eq!(mi.item.raw_context().as_ref(), "pass");
1351 assert_eq!(mi.item.resolved_context().as_ref(), "pass");
1352 assert!(mi.item.declared_style().is_none());
1353 assert!(mi.item.id().is_none());
1354 assert!(mi.item.roles().is_empty());
1355 assert!(mi.item.options().is_empty());
1356 assert!(mi.item.title_source().is_none());
1357 assert!(mi.item.title().is_none());
1358 assert!(mi.item.anchor().is_none());
1359 assert!(mi.item.anchor_reftext().is_none());
1360 assert!(mi.item.attrlist().is_none());
1361 assert_eq!(mi.item.substitution_group(), SubstitutionGroup::Pass);
1362
1363 assert_eq!(
1364 mi.item.content(),
1365 Content {
1366 original: Span {
1367 data: "line1 \n+++++\nline2",
1368 line: 2,
1369 col: 1,
1370 offset: 5,
1371 },
1372 rendered: "line1 \n+++++\nline2",
1373 }
1374 );
1375 }
1376 }
1377
1378 mod quote {
1379 use crate::{blocks::metadata::BlockMetadata, tests::prelude::*};
1380
1381 #[test]
1382 fn empty() {
1383 let mut parser = Parser::default();
1384 assert!(
1385 crate::blocks::RawDelimitedBlock::parse(
1386 &BlockMetadata::new("____\n____"),
1387 &mut parser
1388 )
1389 .is_none()
1390 );
1391 }
1392
1393 #[test]
1394 fn multiple_lines() {
1395 let mut parser = Parser::default();
1396 assert!(
1397 crate::blocks::RawDelimitedBlock::parse(
1398 &BlockMetadata::new("____\nline1 \nline2\n____"),
1399 &mut parser
1400 )
1401 .is_none()
1402 );
1403 }
1404 }
1405}