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};
9
10#[derive(Clone, Debug, Eq, PartialEq)]
13pub struct SimpleBlock<'src> {
14 content: Content<'src>,
15 source: Span<'src>,
16 title_source: Option<Span<'src>>,
17 title: Option<String>,
18 anchor: Option<Span<'src>>,
19 anchor_reftext: Option<Span<'src>>,
20 attrlist: Option<Attrlist<'src>>,
21}
22
23impl<'src> SimpleBlock<'src> {
24 pub(crate) fn parse(
25 metadata: &BlockMetadata<'src>,
26 parser: &mut Parser,
27 ) -> Option<MatchedItem<'src, Self>> {
28 let source = metadata.block_start.take_non_empty_lines()?;
29
30 let mut content: Content<'src> = source.item.into();
31
32 SubstitutionGroup::Normal
33 .override_via_attrlist(metadata.attrlist.as_ref())
34 .apply(&mut content, parser, metadata.attrlist.as_ref());
35
36 Some(MatchedItem {
37 item: Self {
38 content,
39 source: metadata
40 .source
41 .trim_remainder(source.after)
42 .trim_trailing_whitespace(),
43 title_source: metadata.title_source,
44 title: metadata.title.clone(),
45 anchor: metadata.anchor,
46 anchor_reftext: metadata.anchor_reftext,
47 attrlist: metadata.attrlist.clone(),
48 },
49 after: source.after.discard_empty_lines(),
50 })
51 }
52
53 pub(crate) fn parse_fast(
54 source: Span<'src>,
55 parser: &mut Parser,
56 ) -> Option<MatchedItem<'src, Self>> {
57 let source = source.take_non_empty_lines()?;
58
59 let mut content: Content<'src> = source.item.into();
60 SubstitutionGroup::Normal.apply(&mut content, parser, None);
61
62 Some(MatchedItem {
63 item: Self {
64 content,
65 source: source.item,
66 title_source: None,
67 title: None,
68 anchor: None,
69 anchor_reftext: None,
70 attrlist: None,
71 },
72 after: source.after.discard_empty_lines(),
73 })
74 }
75
76 pub fn content(&self) -> &Content<'src> {
78 &self.content
79 }
80}
81
82impl<'src> IsBlock<'src> for SimpleBlock<'src> {
83 fn content_model(&self) -> ContentModel {
84 ContentModel::Simple
85 }
86
87 fn raw_context(&self) -> CowStr<'src> {
88 "paragraph".into()
89 }
90
91 fn title_source(&'src self) -> Option<Span<'src>> {
92 self.title_source
93 }
94
95 fn title(&self) -> Option<&str> {
96 self.title.as_deref()
97 }
98
99 fn anchor(&'src self) -> Option<Span<'src>> {
100 self.anchor
101 }
102
103 fn anchor_reftext(&'src self) -> Option<Span<'src>> {
104 self.anchor_reftext
105 }
106
107 fn attrlist(&'src self) -> Option<&'src Attrlist<'src>> {
108 self.attrlist.as_ref()
109 }
110}
111
112impl<'src> HasSpan<'src> for SimpleBlock<'src> {
113 fn span(&self) -> Span<'src> {
114 self.source
115 }
116}
117
118#[cfg(test)]
119mod tests {
120 #![allow(clippy::unwrap_used)]
121
122 use std::ops::Deref;
123
124 use pretty_assertions_sorted::assert_eq;
125
126 use crate::{
127 Parser,
128 blocks::{ContentModel, IsBlock, metadata::BlockMetadata},
129 content::SubstitutionGroup,
130 tests::prelude::*,
131 };
132
133 #[test]
134 fn impl_clone() {
135 let mut parser = Parser::default();
137
138 let b1 =
139 crate::blocks::SimpleBlock::parse(&BlockMetadata::new("abc"), &mut parser).unwrap();
140
141 let b2 = b1.item.clone();
142 assert_eq!(b1.item, b2);
143 }
144
145 #[test]
146 fn empty_source() {
147 let mut parser = Parser::default();
148 assert!(crate::blocks::SimpleBlock::parse(&BlockMetadata::new(""), &mut parser).is_none());
149 }
150
151 #[test]
152 fn only_spaces() {
153 let mut parser = Parser::default();
154 assert!(
155 crate::blocks::SimpleBlock::parse(&BlockMetadata::new(" "), &mut parser).is_none()
156 );
157 }
158
159 #[test]
160 fn single_line() {
161 let mut parser = Parser::default();
162 let mi =
163 crate::blocks::SimpleBlock::parse(&BlockMetadata::new("abc"), &mut parser).unwrap();
164
165 assert_eq!(
166 mi.item,
167 SimpleBlock {
168 content: Content {
169 original: Span {
170 data: "abc",
171 line: 1,
172 col: 1,
173 offset: 0,
174 },
175 rendered: "abc",
176 },
177 source: Span {
178 data: "abc",
179 line: 1,
180 col: 1,
181 offset: 0,
182 },
183 title_source: None,
184 title: None,
185 anchor: None,
186 anchor_reftext: None,
187 attrlist: None,
188 },
189 );
190
191 assert_eq!(mi.item.content_model(), ContentModel::Simple);
192 assert_eq!(mi.item.raw_context().deref(), "paragraph");
193 assert_eq!(mi.item.resolved_context().deref(), "paragraph");
194 assert!(mi.item.declared_style().is_none());
195 assert!(mi.item.id().is_none());
196 assert!(mi.item.roles().is_empty());
197 assert!(mi.item.options().is_empty());
198 assert!(mi.item.title_source().is_none());
199 assert!(mi.item.title().is_none());
200 assert!(mi.item.anchor().is_none());
201 assert!(mi.item.anchor_reftext().is_none());
202 assert!(mi.item.attrlist().is_none());
203 assert_eq!(mi.item.substitution_group(), SubstitutionGroup::Normal);
204
205 assert_eq!(
206 mi.after,
207 Span {
208 data: "",
209 line: 1,
210 col: 4,
211 offset: 3
212 }
213 );
214 }
215
216 #[test]
217 fn multiple_lines() {
218 let mut parser = Parser::default();
219 let mi = crate::blocks::SimpleBlock::parse(&BlockMetadata::new("abc\ndef"), &mut parser)
220 .unwrap();
221
222 assert_eq!(
223 mi.item,
224 SimpleBlock {
225 content: Content {
226 original: Span {
227 data: "abc\ndef",
228 line: 1,
229 col: 1,
230 offset: 0,
231 },
232 rendered: "abc\ndef",
233 },
234 source: Span {
235 data: "abc\ndef",
236 line: 1,
237 col: 1,
238 offset: 0,
239 },
240 title_source: None,
241 title: None,
242 anchor: None,
243 anchor_reftext: None,
244 attrlist: None,
245 }
246 );
247
248 assert_eq!(
249 mi.after,
250 Span {
251 data: "",
252 line: 2,
253 col: 4,
254 offset: 7
255 }
256 );
257 }
258
259 #[test]
260 fn consumes_blank_lines_after() {
261 let mut parser = Parser::default();
262 let mi = crate::blocks::SimpleBlock::parse(&BlockMetadata::new("abc\n\ndef"), &mut parser)
263 .unwrap();
264
265 assert_eq!(
266 mi.item,
267 SimpleBlock {
268 content: Content {
269 original: Span {
270 data: "abc",
271 line: 1,
272 col: 1,
273 offset: 0,
274 },
275 rendered: "abc",
276 },
277 source: Span {
278 data: "abc",
279 line: 1,
280 col: 1,
281 offset: 0,
282 },
283 title_source: None,
284 title: None,
285 anchor: None,
286 anchor_reftext: None,
287 attrlist: None,
288 }
289 );
290
291 assert_eq!(
292 mi.after,
293 Span {
294 data: "def",
295 line: 3,
296 col: 1,
297 offset: 5
298 }
299 );
300 }
301
302 #[test]
303 fn overrides_sub_group_via_subs_attribute() {
304 let mut parser = Parser::default();
305 let mi = crate::blocks::SimpleBlock::parse(
306 &BlockMetadata::new("[subs=quotes]\na<b>c *bold*\n\ndef"),
307 &mut parser,
308 )
309 .unwrap();
310
311 assert_eq!(
312 mi.item,
313 SimpleBlock {
314 content: Content {
315 original: Span {
316 data: "a<b>c *bold*",
317 line: 2,
318 col: 1,
319 offset: 14,
320 },
321 rendered: "a<b>c <strong>bold</strong>",
322 },
323 source: Span {
324 data: "[subs=quotes]\na<b>c *bold*",
325 line: 1,
326 col: 1,
327 offset: 0,
328 },
329 title_source: None,
330 title: None,
331 anchor: None,
332 anchor_reftext: None,
333 attrlist: Some(Attrlist {
334 attributes: &[ElementAttribute {
335 name: Some("subs"),
336 value: "quotes",
337 shorthand_items: &[],
338 },],
339 anchor: None,
340 source: Span {
341 data: "subs=quotes",
342 line: 1,
343 col: 2,
344 offset: 1,
345 },
346 },),
347 }
348 );
349
350 assert_eq!(
351 mi.after,
352 Span {
353 data: "def",
354 line: 4,
355 col: 1,
356 offset: 28
357 }
358 );
359 }
360}