asciidoc_parser/blocks/
raw_delimited.rs

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