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