1use std::slice::Iter;
2
3use crate::{
4 HasSpan, Parser, Span,
5 attributes::Attrlist,
6 blocks::{
7 Break, CompoundDelimitedBlock, ContentModel, IsBlock, MediaBlock, Preamble,
8 RawDelimitedBlock, SectionBlock, SimpleBlock, metadata::BlockMetadata,
9 },
10 content::SubstitutionGroup,
11 document::{Attribute, RefType},
12 span::MatchedItem,
13 strings::CowStr,
14 warnings::{MatchAndWarnings, Warning, WarningType},
15};
16
17#[derive(Clone, Eq, PartialEq)]
30#[allow(clippy::large_enum_variant)] #[non_exhaustive]
32pub enum Block<'src> {
33 Simple(SimpleBlock<'src>),
36
37 Media(MediaBlock<'src>),
40
41 Section(SectionBlock<'src>),
44
45 RawDelimited(RawDelimitedBlock<'src>),
49
50 CompoundDelimited(CompoundDelimitedBlock<'src>),
52
53 Preamble(Preamble<'src>),
56
57 Break(Break<'src>),
59
60 DocumentAttribute(Attribute<'src>),
63}
64
65impl<'src> std::fmt::Debug for Block<'src> {
66 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
67 match self {
68 Block::Simple(block) => f.debug_tuple("Block::Simple").field(block).finish(),
69 Block::Media(block) => f.debug_tuple("Block::Media").field(block).finish(),
70 Block::Section(block) => f.debug_tuple("Block::Section").field(block).finish(),
71
72 Block::RawDelimited(block) => {
73 f.debug_tuple("Block::RawDelimited").field(block).finish()
74 }
75
76 Block::CompoundDelimited(block) => f
77 .debug_tuple("Block::CompoundDelimited")
78 .field(block)
79 .finish(),
80
81 Block::Preamble(block) => f.debug_tuple("Block::Preamble").field(block).finish(),
82 Block::Break(break_) => f.debug_tuple("Block::Break").field(break_).finish(),
83
84 Block::DocumentAttribute(block) => f
85 .debug_tuple("Block::DocumentAttribute")
86 .field(block)
87 .finish(),
88 }
89 }
90}
91
92impl<'src> Block<'src> {
93 pub(crate) fn parse(
97 source: Span<'src>,
98 parser: &mut Parser,
99 ) -> MatchAndWarnings<'src, Option<MatchedItem<'src, Self>>> {
100 let first_line = source.take_line();
104
105 if let Some(first_char) = source.chars().next()
108 && !matches!(
109 first_char,
110 '.' | '#' | '=' | '/' | '-' | '+' | '*' | '_' | '[' | ':' | '\'' | '<'
111 )
112 && !first_line.item.contains("::")
113 && let Some(MatchedItem {
114 item: simple_block,
115 after,
116 }) = SimpleBlock::parse_fast(source, parser)
117 {
118 let mut warnings = vec![];
119 let block = Self::Simple(simple_block);
120
121 Self::register_block_id(
122 block.id(),
123 block.title(),
124 block.span(),
125 parser,
126 &mut warnings,
127 );
128
129 return MatchAndWarnings {
130 item: Some(MatchedItem { item: block, after }),
131 warnings,
132 };
133 }
134
135 if first_line.item.starts_with(':')
137 && (first_line.item.ends_with(':') || first_line.item.contains(": "))
138 && let Some(attr) = Attribute::parse(source, parser)
139 {
140 let mut warnings: Vec<Warning<'src>> = vec![];
141 parser.set_attribute_from_body(&attr.item, &mut warnings);
142
143 return MatchAndWarnings {
144 item: Some(MatchedItem {
145 item: Self::DocumentAttribute(attr.item),
146 after: attr.after,
147 }),
148 warnings,
149 };
150 }
151
152 let MatchAndWarnings {
155 item: mut metadata,
156 mut warnings,
157 } = BlockMetadata::parse(source, parser);
158
159 if let Some(mut rdb_maw) = RawDelimitedBlock::parse(&metadata, parser)
160 && let Some(rdb) = rdb_maw.item
161 {
162 if !rdb_maw.warnings.is_empty() {
163 warnings.append(&mut rdb_maw.warnings);
164 }
165
166 let block = Self::RawDelimited(rdb.item);
167
168 Self::register_block_id(
169 block.id(),
170 block.title(),
171 block.span(),
172 parser,
173 &mut warnings,
174 );
175
176 return MatchAndWarnings {
177 item: Some(MatchedItem {
178 item: block,
179 after: rdb.after,
180 }),
181 warnings,
182 };
183 }
184
185 if let Some(mut cdb_maw) = CompoundDelimitedBlock::parse(&metadata, parser)
186 && let Some(cdb) = cdb_maw.item
187 {
188 if !cdb_maw.warnings.is_empty() {
189 warnings.append(&mut cdb_maw.warnings);
190 }
191
192 let block = Self::CompoundDelimited(cdb.item);
193
194 Self::register_block_id(
195 block.id(),
196 block.title(),
197 block.span(),
198 parser,
199 &mut warnings,
200 );
201
202 return MatchAndWarnings {
203 item: Some(MatchedItem {
204 item: block,
205 after: cdb.after,
206 }),
207 warnings,
208 };
209 }
210
211 let line = metadata.block_start.take_normalized_line();
213
214 if line.item.starts_with("image::")
215 || line.item.starts_with("video::")
216 || line.item.starts_with("video::")
217 {
218 let mut media_block_maw = MediaBlock::parse(&metadata, parser);
219
220 if let Some(media_block) = media_block_maw.item {
221 if !media_block_maw.warnings.is_empty() {
225 warnings.append(&mut media_block_maw.warnings);
226 }
227
228 let block = Self::Media(media_block.item);
229
230 Self::register_block_id(
231 block.id(),
232 block.title(),
233 block.span(),
234 parser,
235 &mut warnings,
236 );
237
238 return MatchAndWarnings {
239 item: Some(MatchedItem {
240 item: block,
241 after: media_block.after,
242 }),
243 warnings,
244 };
245 }
246
247 }
250
251 if (line.item.starts_with('=') || line.item.starts_with('#'))
252 && let Some(mi_section_block) = SectionBlock::parse(&metadata, parser, &mut warnings)
253 {
254 return MatchAndWarnings {
258 item: Some(MatchedItem {
259 item: Self::Section(mi_section_block.item),
260 after: mi_section_block.after,
261 }),
262 warnings,
263 };
264 }
265
266 if (line.item.starts_with('\'')
267 || line.item.starts_with('-')
268 || line.item.starts_with('*')
269 || line.item.starts_with('<'))
270 && let Some(mi_break) = Break::parse(&metadata, parser)
271 {
272 return MatchAndWarnings {
275 item: Some(MatchedItem {
276 item: Self::Break(mi_break.item),
277 after: mi_break.after,
278 }),
279 warnings,
280 };
281 }
282
283 let simple_block_mi = SimpleBlock::parse(&metadata, parser);
290
291 if simple_block_mi.is_none() && !metadata.is_empty() {
292 warnings.push(Warning {
296 source: metadata.source,
297 warning: WarningType::MissingBlockAfterTitleOrAttributeList,
298 });
299
300 metadata.title_source = None;
303 metadata.title = None;
304 metadata.anchor = None;
305 metadata.attrlist = None;
306 metadata.block_start = metadata.source;
307 }
308
309 let mut result = MatchAndWarnings {
311 item: SimpleBlock::parse(&metadata, parser).map(|mi| MatchedItem {
312 item: Self::Simple(mi.item),
313 after: mi.after,
314 }),
315 warnings,
316 };
317
318 if let Some(ref matched_item) = result.item {
319 Self::register_block_id(
320 matched_item.item.id(),
321 matched_item.item.title(),
322 matched_item.item.span(),
323 parser,
324 &mut result.warnings,
325 );
326 }
327
328 result
329 }
330
331 fn register_block_id(
336 id: Option<&str>,
337 title: Option<&str>,
338 span: Span<'src>,
339 parser: &mut Parser,
340 warnings: &mut Vec<Warning<'src>>,
341 ) {
342 if let Some(id) = id
343 && let Some(catalog) = parser.catalog_mut()
344 && let Err(_duplicate_error) = catalog.register_ref(
345 id,
346 title, RefType::Anchor,
348 )
349 {
350 warnings.push(Warning {
352 source: span,
353 warning: WarningType::DuplicateId(id.to_string()),
354 });
355 }
356 }
357}
358
359impl<'src> IsBlock<'src> for Block<'src> {
360 fn content_model(&self) -> ContentModel {
361 match self {
362 Self::Simple(_) => ContentModel::Simple,
363 Self::Media(b) => b.content_model(),
364 Self::Section(_) => ContentModel::Compound,
365 Self::RawDelimited(b) => b.content_model(),
366 Self::CompoundDelimited(b) => b.content_model(),
367 Self::Preamble(b) => b.content_model(),
368 Self::Break(b) => b.content_model(),
369 Self::DocumentAttribute(b) => b.content_model(),
370 }
371 }
372
373 fn raw_context(&self) -> CowStr<'src> {
374 match self {
375 Self::Simple(b) => b.raw_context(),
376 Self::Media(b) => b.raw_context(),
377 Self::Section(b) => b.raw_context(),
378 Self::RawDelimited(b) => b.raw_context(),
379 Self::CompoundDelimited(b) => b.raw_context(),
380 Self::Preamble(b) => b.raw_context(),
381 Self::Break(b) => b.raw_context(),
382 Self::DocumentAttribute(b) => b.raw_context(),
383 }
384 }
385
386 fn nested_blocks(&'src self) -> Iter<'src, Block<'src>> {
387 match self {
388 Self::Simple(b) => b.nested_blocks(),
389 Self::Media(b) => b.nested_blocks(),
390 Self::Section(b) => b.nested_blocks(),
391 Self::RawDelimited(b) => b.nested_blocks(),
392 Self::CompoundDelimited(b) => b.nested_blocks(),
393 Self::Preamble(b) => b.nested_blocks(),
394 Self::Break(b) => b.nested_blocks(),
395 Self::DocumentAttribute(b) => b.nested_blocks(),
396 }
397 }
398
399 fn title_source(&'src self) -> Option<Span<'src>> {
400 match self {
401 Self::Simple(b) => b.title_source(),
402 Self::Media(b) => b.title_source(),
403 Self::Section(b) => b.title_source(),
404 Self::RawDelimited(b) => b.title_source(),
405 Self::CompoundDelimited(b) => b.title_source(),
406 Self::Preamble(b) => b.title_source(),
407 Self::Break(b) => b.title_source(),
408 Self::DocumentAttribute(b) => b.title_source(),
409 }
410 }
411
412 fn title(&self) -> Option<&str> {
413 match self {
414 Self::Simple(b) => b.title(),
415 Self::Media(b) => b.title(),
416 Self::Section(b) => b.title(),
417 Self::RawDelimited(b) => b.title(),
418 Self::CompoundDelimited(b) => b.title(),
419 Self::Preamble(b) => b.title(),
420 Self::Break(b) => b.title(),
421 Self::DocumentAttribute(b) => b.title(),
422 }
423 }
424
425 fn anchor(&'src self) -> Option<Span<'src>> {
426 match self {
427 Self::Simple(b) => b.anchor(),
428 Self::Media(b) => b.anchor(),
429 Self::Section(b) => b.anchor(),
430 Self::RawDelimited(b) => b.anchor(),
431 Self::CompoundDelimited(b) => b.anchor(),
432 Self::Preamble(b) => b.anchor(),
433 Self::Break(b) => b.anchor(),
434 Self::DocumentAttribute(b) => b.anchor(),
435 }
436 }
437
438 fn anchor_reftext(&'src self) -> Option<Span<'src>> {
439 match self {
440 Self::Simple(b) => b.anchor_reftext(),
441 Self::Media(b) => b.anchor_reftext(),
442 Self::Section(b) => b.anchor_reftext(),
443 Self::RawDelimited(b) => b.anchor_reftext(),
444 Self::CompoundDelimited(b) => b.anchor_reftext(),
445 Self::Preamble(b) => b.anchor_reftext(),
446 Self::Break(b) => b.anchor_reftext(),
447 Self::DocumentAttribute(b) => b.anchor_reftext(),
448 }
449 }
450
451 fn attrlist(&'src self) -> Option<&'src Attrlist<'src>> {
452 match self {
453 Self::Simple(b) => b.attrlist(),
454 Self::Media(b) => b.attrlist(),
455 Self::Section(b) => b.attrlist(),
456 Self::RawDelimited(b) => b.attrlist(),
457 Self::CompoundDelimited(b) => b.attrlist(),
458 Self::Preamble(b) => b.attrlist(),
459 Self::Break(b) => b.attrlist(),
460 Self::DocumentAttribute(b) => b.attrlist(),
461 }
462 }
463
464 fn substitution_group(&self) -> SubstitutionGroup {
465 match self {
466 Self::Simple(b) => b.substitution_group(),
467 Self::Media(b) => b.substitution_group(),
468 Self::Section(b) => b.substitution_group(),
469 Self::RawDelimited(b) => b.substitution_group(),
470 Self::CompoundDelimited(b) => b.substitution_group(),
471 Self::Preamble(b) => b.substitution_group(),
472 Self::Break(b) => b.substitution_group(),
473 Self::DocumentAttribute(b) => b.substitution_group(),
474 }
475 }
476}
477
478impl<'src> HasSpan<'src> for Block<'src> {
479 fn span(&self) -> Span<'src> {
480 match self {
481 Self::Simple(b) => b.span(),
482 Self::Media(b) => b.span(),
483 Self::Section(b) => b.span(),
484 Self::RawDelimited(b) => b.span(),
485 Self::CompoundDelimited(b) => b.span(),
486 Self::Preamble(b) => b.span(),
487 Self::Break(b) => b.span(),
488 Self::DocumentAttribute(b) => b.span(),
489 }
490 }
491}