asciidoc_parser/blocks/
raw_delimited.rs1use 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 attrlist: Option<Attrlist<'src>>,
32 substitution_group: SubstitutionGroup,
33}
34
35impl<'src> RawDelimitedBlock<'src> {
36 pub(crate) fn is_valid_delimiter(line: &Span<'src>) -> bool {
37 let data = line.data();
38
39 if data.len() >= 4 {
44 if data.starts_with("////") {
45 data.split_at(4).1.chars().all(|c| c == '/')
46 } else if data.starts_with("----") {
47 data.split_at(4).1.chars().all(|c| c == '-')
48 } else if data.starts_with("....") {
49 data.split_at(4).1.chars().all(|c| c == '.')
50 } else if data.starts_with("++++") {
51 data.split_at(4).1.chars().all(|c| c == '+')
52 } else {
53 false
54 }
55 } else {
56 false
57 }
58 }
59
60 pub(crate) fn parse(
61 metadata: &BlockMetadata<'src>,
62 parser: &mut Parser,
63 ) -> Option<MatchAndWarnings<'src, Option<MatchedItem<'src, Self>>>> {
64 let delimiter = metadata.block_start.take_normalized_line();
65
66 if delimiter.item.len() < 4 {
67 return None;
68 }
69
70 let (content_model, context, mut substitution_group) =
71 match delimiter.item.data().split_at(4).0 {
72 "////" => (ContentModel::Raw, "comment", SubstitutionGroup::None),
73 "----" => (
74 ContentModel::Verbatim,
75 "listing",
76 SubstitutionGroup::Verbatim,
77 ),
78 "...." => (
79 ContentModel::Verbatim,
80 "literal",
81 SubstitutionGroup::Verbatim,
82 ),
83 "++++" => (ContentModel::Raw, "pass", SubstitutionGroup::Pass),
84 _ => return None,
85 };
86
87 if !Self::is_valid_delimiter(&delimiter.item) {
88 return None;
89 }
90
91 let content_start = delimiter.after;
92 let mut next = content_start;
93
94 while !next.is_empty() {
95 let line = next.take_normalized_line();
96 if line.item.data() == delimiter.item.data() {
97 let content = content_start.trim_remainder(next).trim_trailing_line_end();
98
99 let mut content: Content<'src> = content.into();
100
101 substitution_group =
102 substitution_group.override_via_attrlist(metadata.attrlist.as_ref());
103
104 substitution_group.apply(&mut content, parser, metadata.attrlist.as_ref());
105
106 return Some(MatchAndWarnings {
107 item: Some(MatchedItem {
108 item: Self {
109 content,
110 content_model,
111 context: context.into(),
112 source: metadata
113 .source
114 .trim_remainder(line.after)
115 .trim_trailing_line_end(),
116 title_source: metadata.title_source,
117 title: metadata.title.clone(),
118 anchor: metadata.anchor,
119 attrlist: metadata.attrlist.clone(),
120 substitution_group,
121 },
122 after: line.after,
123 }),
124 warnings: vec![],
125 });
126 }
127
128 next = line.after;
129 }
130
131 let content = content_start.trim_remainder(next).trim_trailing_line_end();
132
133 Some(MatchAndWarnings {
134 item: Some(MatchedItem {
135 item: Self {
136 content: content.into(),
137 content_model,
138 context: context.into(),
139 source: metadata
140 .source
141 .trim_remainder(next)
142 .trim_trailing_line_end(),
143 title_source: metadata.title_source,
144 title: metadata.title.clone(),
145 anchor: metadata.anchor,
146 attrlist: metadata.attrlist.clone(),
147 substitution_group,
148 },
149 after: next,
150 }),
151 warnings: vec![Warning {
152 source: delimiter.item,
153 warning: WarningType::UnterminatedDelimitedBlock,
154 }],
155 })
156 }
157
158 pub fn content(&self) -> &Content<'src> {
160 &self.content
161 }
162}
163
164impl<'src> IsBlock<'src> for RawDelimitedBlock<'src> {
165 fn content_model(&self) -> ContentModel {
166 self.content_model
167 }
168
169 fn raw_context(&self) -> CowStr<'src> {
170 self.context.clone()
171 }
172
173 fn title_source(&'src self) -> Option<Span<'src>> {
174 self.title_source
175 }
176
177 fn title(&self) -> Option<&str> {
178 self.title.as_deref()
179 }
180
181 fn anchor(&'src self) -> Option<Span<'src>> {
182 self.anchor
183 }
184
185 fn attrlist(&'src self) -> Option<&'src Attrlist<'src>> {
186 self.attrlist.as_ref()
187 }
188
189 fn substitution_group(&'src self) -> SubstitutionGroup {
190 self.substitution_group.clone()
191 }
192}
193
194impl<'src> HasSpan<'src> for RawDelimitedBlock<'src> {
195 fn span(&self) -> Span<'src> {
196 self.source
197 }
198}