Skip to main content

asciidoc_parser/blocks/
block.rs

1use std::slice::Iter;
2
3use crate::{
4    HasSpan, Parser, Span,
5    attributes::Attrlist,
6    blocks::{
7        AdmonitionBlock, Break, CompoundDelimitedBlock, ContentModel, IsBlock, ListBlock, ListItem,
8        ListItemMarker, MediaBlock, Preamble, QuoteBlock, RawDelimitedBlock, SectionBlock,
9        SimpleBlock, TableBlock, metadata::BlockMetadata, starts_with_admonition_label,
10    },
11    content::{Content, SubstitutionGroup},
12    document::{Attribute, RefType},
13    parser::{InlineSubstitutionRenderer, ReferenceResolver, ReferenceWarning},
14    span::MatchedItem,
15    strings::CowStr,
16    warnings::{MatchAndWarnings, Warning, WarningType},
17};
18
19/// **Block elements** form the main structure of an AsciiDoc document, starting
20/// with the document itself.
21///
22/// A block element (aka **block**) is a discrete, line-oriented chunk of
23/// content in an AsciiDoc document. Once parsed, that chunk of content becomes
24/// a block element in the parsed document model. Certain blocks may contain
25/// other blocks, so we say that blocks can be nested. The converter visits each
26/// block in turn, in document order, converting it to a corresponding chunk of
27/// output.
28///
29/// This enum represents all of the block types that are understood directly by
30/// this parser and also implements the [`IsBlock`] trait.
31#[derive(Clone, Eq, PartialEq)]
32#[allow(clippy::large_enum_variant)] // TEMPORARY: review later
33#[non_exhaustive]
34pub enum Block<'src> {
35    /// A block that’s treated as contiguous lines of paragraph text (and
36    /// subject to normal substitutions) (e.g., a paragraph block).
37    Simple(SimpleBlock<'src>),
38
39    /// A media block is used to represent an image, video, or audio block
40    /// macro.
41    Media(MediaBlock<'src>),
42
43    /// A section helps to partition the document into a content hierarchy.
44    /// May also be a part, chapter, or special section.
45    Section(SectionBlock<'src>),
46
47    /// A list contains a sequence of items prefixed with symbol, such as a disc
48    /// (aka bullet). Each individual item in the list is represented by a
49    /// [`ListItem`].
50    List(ListBlock<'src>),
51
52    /// A list item is a special kind of block that is a member of a
53    /// [`ListBlock`] and contains one or more blocks attached to it.
54    ListItem(ListItem<'src>),
55
56    /// A delimited block that contains verbatim, raw, or comment text. The
57    /// content between the matching delimiters is not parsed for block
58    /// syntax.
59    RawDelimited(RawDelimitedBlock<'src>),
60
61    /// A delimited block that can contain other blocks.
62    CompoundDelimited(CompoundDelimitedBlock<'src>),
63
64    /// An admonition draws attention to a statement by taking it out of the
65    /// content's flow and labeling it with a priority (e.g., a note or a
66    /// warning).
67    Admonition(AdmonitionBlock<'src>),
68
69    /// A blockquote: a quote, prose excerpt, or verse, optionally attributed to
70    /// a person and a source citation.
71    Quote(QuoteBlock<'src>),
72
73    /// A table block arranges content into a grid of rows and columns.
74    Table(TableBlock<'src>),
75
76    /// Content between the end of the document header and the first section
77    /// title in the document body is called the preamble.
78    Preamble(Preamble<'src>),
79
80    /// A thematic or page break.
81    Break(Break<'src>),
82
83    /// When an attribute is defined in the document body using an attribute
84    /// entry, that’s simply referred to as a document attribute.
85    DocumentAttribute(Attribute<'src>),
86}
87
88impl<'src> std::fmt::Debug for Block<'src> {
89    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
90        match self {
91            Block::Simple(block) => f.debug_tuple("Block::Simple").field(block).finish(),
92            Block::Media(block) => f.debug_tuple("Block::Media").field(block).finish(),
93            Block::Section(block) => f.debug_tuple("Block::Section").field(block).finish(),
94            Block::List(block) => f.debug_tuple("Block::List").field(block).finish(),
95            Block::ListItem(block) => f.debug_tuple("Block::ListItem").field(block).finish(),
96
97            Block::RawDelimited(block) => {
98                f.debug_tuple("Block::RawDelimited").field(block).finish()
99            }
100
101            Block::CompoundDelimited(block) => f
102                .debug_tuple("Block::CompoundDelimited")
103                .field(block)
104                .finish(),
105
106            Block::Admonition(block) => f.debug_tuple("Block::Admonition").field(block).finish(),
107            Block::Quote(block) => f.debug_tuple("Block::Quote").field(block).finish(),
108            Block::Table(block) => f.debug_tuple("Block::Table").field(block).finish(),
109            Block::Preamble(block) => f.debug_tuple("Block::Preamble").field(block).finish(),
110            Block::Break(break_) => f.debug_tuple("Block::Break").field(break_).finish(),
111
112            Block::DocumentAttribute(block) => f
113                .debug_tuple("Block::DocumentAttribute")
114                .field(block)
115                .finish(),
116        }
117    }
118}
119
120impl<'src> Block<'src> {
121    /// Parse a block of any type and return a `Block` that describes it.
122    ///
123    /// Consumes any blank lines before and after the block.
124    pub(crate) fn parse(
125        source: Span<'src>,
126        parser: &mut Parser,
127    ) -> MatchAndWarnings<'src, Option<MatchedItem<'src, Self>>> {
128        Self::parse_internal(source, parser, None, false)
129    }
130
131    /// Parse a block of any type and return a `Block` that describes it.
132    ///
133    /// Will terminate early when parsing certain block types within a list
134    /// context.
135    ///
136    /// Consumes any blank lines before and after the block.
137    ///
138    /// If `is_continuation` is true, this content was attached via a `+`
139    /// continuation marker and literal blocks should preserve their
140    /// indentation.
141    pub(crate) fn parse_for_list_item(
142        source: Span<'src>,
143        parser: &mut Parser,
144        parent_list_markers: &[ListItemMarker<'src>],
145        is_continuation: bool,
146    ) -> MatchAndWarnings<'src, Option<MatchedItem<'src, Self>>> {
147        Self::parse_internal(source, parser, Some(parent_list_markers), is_continuation)
148    }
149
150    /// Shared parser for [`Block::parse`] and [`Block::parse_for_list_item`].
151    fn parse_internal(
152        source: Span<'src>,
153        parser: &mut Parser,
154        parent_list_markers: Option<&[ListItemMarker<'src>]>,
155        is_continuation: bool,
156    ) -> MatchAndWarnings<'src, Option<MatchedItem<'src, Self>>> {
157        // Optimization: If the first line doesn't match any of the early indications
158        // for delimited blocks, titles, or attrlists, we can skip directly to treating
159        // this as a simple block. That saves quite a bit of parsing time.
160        let first_line = source.take_line().item.discard_whitespace();
161
162        // If it does contain any of those markers, we fall through to the more costly
163        // tests below which can more accurately classify the upcoming block.
164        if let Some(first_char) = first_line.chars().next()
165            && !matches!(
166                first_char,
167                '.' | '#'
168                    | '='
169                    | '/'
170                    | '-'
171                    | '+'
172                    | '*'
173                    | '_'
174                    | '['
175                    | ':'
176                    | '\''
177                    | '<'
178                    | '>'
179                    | '"'
180                    | '•'
181            )
182            && !first_line.contains("::")
183            && !first_line.contains(";;")
184            && !TableBlock::is_table_delimiter(&first_line)
185            && !ListItemMarker::starts_with_marker(first_line)
186            && !starts_with_admonition_label(first_line)
187            && parent_list_markers.is_none()
188            && let Some(MatchedItem {
189                item: simple_block,
190                after,
191            }) = SimpleBlock::parse_fast(source, parser)
192        {
193            let mut warnings = vec![];
194            let block = Self::Simple(simple_block);
195
196            Self::register_block_id(
197                block.id(),
198                block.title(),
199                block.span(),
200                parser,
201                &mut warnings,
202            );
203
204            return MatchAndWarnings {
205                item: Some(MatchedItem { item: block, after }),
206                warnings,
207            };
208        }
209
210        // Look for document attributes first since these don't support block metadata.
211        if first_line.starts_with(':')
212            && (first_line.ends_with(':') || first_line.contains(": "))
213            && let Some(attr) = Attribute::parse(source, parser)
214        {
215            let mut warnings: Vec<Warning<'src>> = vec![];
216            parser.set_attribute_from_body(&attr.item, &mut warnings);
217
218            return MatchAndWarnings {
219                item: Some(MatchedItem {
220                    item: Self::DocumentAttribute(attr.item),
221                    after: attr.after,
222                }),
223                warnings,
224            };
225        }
226
227        // Optimization not possible; start by looking for block metadata (title,
228        // attrlist, etc.).
229        let MatchAndWarnings {
230            item: mut metadata,
231            mut warnings,
232        } = BlockMetadata::parse(source, parser);
233
234        // The `[literal]` block style normally marks a literal *paragraph*,
235        // which is handled directly as a simple (literal) block below, bypassing
236        // the delimited-block parsers. The one exception is when it is set on an
237        // open block (`--`), where it masquerades as a verbatim literal block;
238        // that case must fall through to `RawDelimitedBlock`.
239        let is_literal = metadata.attrlist.as_ref().and_then(|a| a.block_style())
240            == Some("literal")
241            && metadata.block_start.take_normalized_line().item.data() != "--";
242
243        // A simple block may be parsed speculatively inside the `!is_literal`
244        // branch below (to detect the "metadata with no block" edge case). When
245        // that speculative parse succeeds it is reused as the final result rather
246        // than re-parsed, so that the captioning side effect of
247        // `SimpleBlock::parse` (which can consume a caption counter) happens at
248        // most once per block.
249        let mut simple_block_mi = None;
250
251        if !is_literal {
252            if let Some(mut adm_maw) = AdmonitionBlock::parse(&metadata, parser)
253                && let Some(adm) = adm_maw.item
254            {
255                if !adm_maw.warnings.is_empty() {
256                    warnings.append(&mut adm_maw.warnings);
257                }
258
259                let block = Self::Admonition(adm.item);
260
261                Self::register_block_id(
262                    block.id(),
263                    block.title(),
264                    block.span(),
265                    parser,
266                    &mut warnings,
267                );
268
269                return MatchAndWarnings {
270                    item: Some(MatchedItem {
271                        item: block,
272                        after: adm.after,
273                    }),
274                    warnings,
275                };
276            }
277
278            if let Some(mut quote_maw) = QuoteBlock::parse(&metadata, parser)
279                && let Some(quote) = quote_maw.item
280            {
281                if !quote_maw.warnings.is_empty() {
282                    warnings.append(&mut quote_maw.warnings);
283                }
284
285                let block = Self::Quote(quote.item);
286
287                Self::register_block_id(
288                    block.id(),
289                    block.title(),
290                    block.span(),
291                    parser,
292                    &mut warnings,
293                );
294
295                return MatchAndWarnings {
296                    item: Some(MatchedItem {
297                        item: block,
298                        after: quote.after,
299                    }),
300                    warnings,
301                };
302            }
303
304            if let Some(mut rdb_maw) = RawDelimitedBlock::parse(&metadata, parser)
305                && let Some(rdb) = rdb_maw.item
306            {
307                if !rdb_maw.warnings.is_empty() {
308                    warnings.append(&mut rdb_maw.warnings);
309                }
310
311                let block = Self::RawDelimited(rdb.item);
312
313                Self::register_block_id(
314                    block.id(),
315                    block.title(),
316                    block.span(),
317                    parser,
318                    &mut warnings,
319                );
320
321                return MatchAndWarnings {
322                    item: Some(MatchedItem {
323                        item: block,
324                        after: rdb.after,
325                    }),
326                    warnings,
327                };
328            }
329
330            if let Some(mut cdb_maw) = CompoundDelimitedBlock::parse(&metadata, parser)
331                && let Some(cdb) = cdb_maw.item
332            {
333                if !cdb_maw.warnings.is_empty() {
334                    warnings.append(&mut cdb_maw.warnings);
335                }
336
337                let block = Self::CompoundDelimited(cdb.item);
338
339                Self::register_block_id(
340                    block.id(),
341                    block.title(),
342                    block.span(),
343                    parser,
344                    &mut warnings,
345                );
346
347                return MatchAndWarnings {
348                    item: Some(MatchedItem {
349                        item: block,
350                        after: cdb.after,
351                    }),
352                    warnings,
353                };
354            }
355
356            if let Some(mut table_maw) = TableBlock::parse(&metadata, parser)
357                && let Some(table) = table_maw.item
358            {
359                if !table_maw.warnings.is_empty() {
360                    warnings.append(&mut table_maw.warnings);
361                }
362
363                let block = Self::Table(table.item);
364
365                Self::register_block_id(
366                    block.id(),
367                    block.title(),
368                    block.span(),
369                    parser,
370                    &mut warnings,
371                );
372
373                return MatchAndWarnings {
374                    item: Some(MatchedItem {
375                        item: block,
376                        after: table.after,
377                    }),
378                    warnings,
379                };
380            }
381
382            // Try to discern the block type by scanning the first line.
383            let line = metadata.block_start.take_normalized_line();
384
385            if line.item.starts_with("image::")
386                || line.item.starts_with("video::")
387                || line.item.starts_with("video::")
388            {
389                let mut media_block_maw = MediaBlock::parse(&metadata, parser);
390
391                if let Some(media_block) = media_block_maw.item {
392                    // Only propagate warnings from media block parsing if we think this
393                    // *is* a media block. Otherwise, there would likely be too many false
394                    // positives.
395                    if !media_block_maw.warnings.is_empty() {
396                        warnings.append(&mut media_block_maw.warnings);
397                    }
398
399                    let block = Self::Media(media_block.item);
400
401                    Self::register_block_id(
402                        block.id(),
403                        block.title(),
404                        block.span(),
405                        parser,
406                        &mut warnings,
407                    );
408
409                    return MatchAndWarnings {
410                        item: Some(MatchedItem {
411                            item: block,
412                            after: media_block.after,
413                        }),
414                        warnings,
415                    };
416                }
417
418                // This might be some other kind of block, so we don't
419                // automatically error out on a parse failure.
420            }
421
422            if (line.item.starts_with('=') || line.item.starts_with('#'))
423                && let Some(mi_section_block) =
424                    SectionBlock::parse(&metadata, parser, &mut warnings)
425            {
426                // A line starting with `=` or `#` might be some other kind of block, so we
427                // continue quietly if `SectionBlock` parser rejects this block.
428
429                return MatchAndWarnings {
430                    item: Some(MatchedItem {
431                        item: Self::Section(mi_section_block.item),
432                        after: mi_section_block.after,
433                    }),
434                    warnings,
435                };
436            }
437
438            if (line.item.starts_with('\'')
439                || line.item.starts_with('-')
440                || line.item.starts_with('*')
441                || line.item.starts_with('<'))
442                && let Some(mi_break) = Break::parse(&metadata, parser)
443            {
444                // Continue quietly if `Break` parser rejects this block.
445
446                return MatchAndWarnings {
447                    item: Some(MatchedItem {
448                        item: Self::Break(mi_break.item),
449                        after: mi_break.after,
450                    }),
451                    warnings,
452                };
453            }
454
455            // Only try to parse as a new list if we're NOT inside a list item context.
456            // If we are inside a list context, lists can only be created when the first
457            // line is a list item marker (handled above).
458            if parent_list_markers.is_none()
459                && let Some(mi_list) = ListBlock::parse(&metadata, parser, &mut warnings)
460            {
461                return MatchAndWarnings {
462                    item: Some(MatchedItem {
463                        item: Self::List(mi_list.item),
464                        after: mi_list.after,
465                    }),
466                    warnings,
467                };
468            }
469
470            // First, let's look for a fun edge case. Perhaps the text contains block
471            // metadata but no block immediately following. If we're not careful, we could
472            // spin in a loop (for example, `parse_blocks_until`) thinking there will be
473            // another block, but there isn't.
474
475            // The following check disables that spin loop.
476            simple_block_mi = if let Some(plm) = parent_list_markers {
477                SimpleBlock::parse_for_list_item(&metadata, parser, is_continuation, plm)
478            } else {
479                SimpleBlock::parse(&metadata, parser)
480            };
481
482            if simple_block_mi.is_none() && !metadata.is_empty() {
483                // We have a metadata with no block. Treat it as a simple block but issue a
484                // warning.
485
486                warnings.push(Warning {
487                    source: metadata.source,
488                    warning: WarningType::MissingBlockAfterTitleOrAttributeList,
489                });
490
491                // Remove the metadata content so that SimpleBlock will read the title/attrlist
492                // line(s) as regular content. The speculative parse failed, so the
493                // block is re-parsed below with this stripped metadata.
494                metadata.title_source = None;
495                metadata.title = None;
496                metadata.anchor = None;
497                metadata.attrlist = None;
498                metadata.block_start = metadata.source;
499            }
500        }
501
502        // If no other block kind matches, we can always use SimpleBlock. Reuse the
503        // speculative parse from the `!is_literal` branch when it succeeded;
504        // otherwise (a literal block, or metadata stripped above) parse now.
505        let simple_block_mi = match simple_block_mi {
506            Some(mi) => Some(mi),
507            None => {
508                if let Some(plm) = parent_list_markers {
509                    SimpleBlock::parse_for_list_item(&metadata, parser, is_continuation, plm)
510                } else {
511                    SimpleBlock::parse(&metadata, parser)
512                }
513            }
514        };
515
516        let mut result = MatchAndWarnings {
517            item: simple_block_mi.map(|mi| MatchedItem {
518                item: Self::Simple(mi.item),
519                after: mi.after,
520            }),
521            warnings,
522        };
523
524        if let Some(ref matched_item) = result.item {
525            Self::register_block_id(
526                matched_item.item.id(),
527                matched_item.item.title(),
528                matched_item.item.span(),
529                parser,
530                &mut result.warnings,
531            );
532        }
533
534        result
535    }
536
537    /// Register a block's ID with the catalog if the block has an ID.
538    ///
539    /// This should be called for all block types except `SectionBlock`,
540    /// which handles its own catalog registration.
541    fn register_block_id(
542        id: Option<&str>,
543        title: Option<&str>,
544        span: Span<'src>,
545        parser: &mut Parser,
546        warnings: &mut Vec<Warning<'src>>,
547    ) {
548        if let Some(id) = id
549            && let Err(_duplicate_error) = parser.register_ref(
550                id,
551                title, // Use block title as reftext if available
552                RefType::Anchor,
553            )
554        {
555            // If registration fails due to duplicate ID, issue a warning.
556            warnings.push(Warning {
557                source: span,
558                warning: WarningType::DuplicateId(id.to_string()),
559            });
560        }
561    }
562
563    /// Returns a reference to the inner [`ListItem`] if this is a
564    /// `Block::ListItem`, or `None` otherwise.
565    pub(crate) fn as_list_item(&self) -> Option<&ListItem<'src>> {
566        match self {
567            Self::ListItem(li) => Some(li),
568            _ => None,
569        }
570    }
571
572    /// Resolve any deferred cross-references in this block and its descendants,
573    /// using `resolver` to map targets to destinations and `renderer` to render
574    /// the resulting links. Unresolved targets are reported in `warnings`.
575    ///
576    /// This drives the recursion uniformly via the [`IsBlock::content_mut`] and
577    /// [`IsBlock::nested_blocks_mut`] accessors, so it needs no per-block-type
578    /// special casing.
579    pub(crate) fn resolve_references(
580        &mut self,
581        resolver: &dyn ReferenceResolver,
582        renderer: &dyn InlineSubstitutionRenderer,
583        warnings: &mut Vec<ReferenceWarning>,
584    ) {
585        if let Some(content) = self.content_mut() {
586            content.resolve_references(resolver, renderer, warnings);
587        }
588
589        // Tables hold their resolvable content in cells rather than in a single
590        // `content_mut()` value, so they are resolved explicitly here.
591        if let Self::Table(table) = self {
592            table.resolve_references(resolver, renderer, warnings);
593        }
594
595        // A Markdown-style blockquote holds its nested blocks in its own owned
596        // source, which the generic `nested_blocks_mut()` walk below does not
597        // reach, so they are resolved explicitly here.
598        if let Self::Quote(quote) = self {
599            quote.resolve_references(resolver, renderer, warnings);
600        }
601
602        for child in self.nested_blocks_mut() {
603            child.resolve_references(resolver, renderer, warnings);
604        }
605    }
606}
607
608impl<'src> IsBlock<'src> for Block<'src> {
609    fn content_model(&self) -> ContentModel {
610        match self {
611            Self::Simple(_) => ContentModel::Simple,
612            Self::Media(b) => b.content_model(),
613            Self::Section(_) => ContentModel::Compound,
614            Self::List(b) => b.content_model(),
615            Self::ListItem(b) => b.content_model(),
616            Self::RawDelimited(b) => b.content_model(),
617            Self::CompoundDelimited(b) => b.content_model(),
618            Self::Admonition(b) => b.content_model(),
619            Self::Quote(b) => b.content_model(),
620            Self::Table(b) => b.content_model(),
621            Self::Preamble(b) => b.content_model(),
622            Self::Break(b) => b.content_model(),
623            Self::DocumentAttribute(b) => b.content_model(),
624        }
625    }
626
627    fn declared_style(&'src self) -> Option<&'src str> {
628        match self {
629            Self::Simple(b) => b.declared_style(),
630            Self::Media(b) => b.declared_style(),
631            Self::Section(b) => b.declared_style(),
632            Self::List(b) => b.declared_style(),
633            Self::ListItem(b) => b.declared_style(),
634            Self::RawDelimited(b) => b.declared_style(),
635            Self::CompoundDelimited(b) => b.declared_style(),
636            Self::Admonition(b) => b.declared_style(),
637            Self::Quote(b) => b.declared_style(),
638            Self::Table(b) => b.declared_style(),
639            Self::Preamble(b) => b.declared_style(),
640            Self::Break(b) => b.declared_style(),
641            Self::DocumentAttribute(b) => b.declared_style(),
642        }
643    }
644
645    fn rendered_content(&'src self) -> Option<&'src str> {
646        match self {
647            Self::Simple(b) => b.rendered_content(),
648            Self::Media(b) => b.rendered_content(),
649            Self::Section(b) => b.rendered_content(),
650            Self::List(b) => b.rendered_content(),
651            Self::ListItem(b) => b.rendered_content(),
652            Self::RawDelimited(b) => b.rendered_content(),
653            Self::CompoundDelimited(b) => b.rendered_content(),
654            Self::Admonition(b) => b.rendered_content(),
655            Self::Quote(b) => b.rendered_content(),
656            Self::Table(b) => b.rendered_content(),
657            Self::Preamble(b) => b.rendered_content(),
658            Self::Break(b) => b.rendered_content(),
659            Self::DocumentAttribute(b) => b.rendered_content(),
660        }
661    }
662
663    fn raw_context(&self) -> CowStr<'src> {
664        match self {
665            Self::Simple(b) => b.raw_context(),
666            Self::Media(b) => b.raw_context(),
667            Self::Section(b) => b.raw_context(),
668            Self::List(b) => b.raw_context(),
669            Self::ListItem(b) => b.raw_context(),
670            Self::RawDelimited(b) => b.raw_context(),
671            Self::CompoundDelimited(b) => b.raw_context(),
672            Self::Admonition(b) => b.raw_context(),
673            Self::Quote(b) => b.raw_context(),
674            Self::Table(b) => b.raw_context(),
675            Self::Preamble(b) => b.raw_context(),
676            Self::Break(b) => b.raw_context(),
677            Self::DocumentAttribute(b) => b.raw_context(),
678        }
679    }
680
681    fn nested_blocks(&'src self) -> Iter<'src, Block<'src>> {
682        match self {
683            Self::Simple(b) => b.nested_blocks(),
684            Self::Media(b) => b.nested_blocks(),
685            Self::Section(b) => b.nested_blocks(),
686            Self::List(b) => b.nested_blocks(),
687            Self::ListItem(b) => b.nested_blocks(),
688            Self::RawDelimited(b) => b.nested_blocks(),
689            Self::CompoundDelimited(b) => b.nested_blocks(),
690            Self::Admonition(b) => b.nested_blocks(),
691            Self::Quote(b) => b.nested_blocks(),
692            Self::Table(b) => b.nested_blocks(),
693            Self::Preamble(b) => b.nested_blocks(),
694            Self::Break(b) => b.nested_blocks(),
695            Self::DocumentAttribute(b) => b.nested_blocks(),
696        }
697    }
698
699    fn nested_blocks_mut(&mut self) -> &mut [Block<'src>] {
700        match self {
701            Self::Simple(b) => b.nested_blocks_mut(),
702            Self::Media(b) => b.nested_blocks_mut(),
703            Self::Section(b) => b.nested_blocks_mut(),
704            Self::List(b) => b.nested_blocks_mut(),
705            Self::ListItem(b) => b.nested_blocks_mut(),
706            Self::RawDelimited(b) => b.nested_blocks_mut(),
707            Self::CompoundDelimited(b) => b.nested_blocks_mut(),
708            Self::Admonition(b) => b.nested_blocks_mut(),
709            Self::Quote(b) => b.nested_blocks_mut(),
710            Self::Table(b) => b.nested_blocks_mut(),
711            Self::Preamble(b) => b.nested_blocks_mut(),
712            Self::Break(b) => b.nested_blocks_mut(),
713            Self::DocumentAttribute(b) => b.nested_blocks_mut(),
714        }
715    }
716
717    fn content_mut(&mut self) -> Option<&mut Content<'src>> {
718        match self {
719            Self::Simple(b) => b.content_mut(),
720            Self::Media(b) => b.content_mut(),
721            Self::Section(b) => b.content_mut(),
722            Self::List(b) => b.content_mut(),
723            Self::ListItem(b) => b.content_mut(),
724            Self::RawDelimited(b) => b.content_mut(),
725            Self::CompoundDelimited(b) => b.content_mut(),
726            Self::Admonition(b) => b.content_mut(),
727            Self::Quote(b) => b.content_mut(),
728            Self::Table(b) => b.content_mut(),
729            Self::Preamble(b) => b.content_mut(),
730            Self::Break(b) => b.content_mut(),
731            Self::DocumentAttribute(b) => b.content_mut(),
732        }
733    }
734
735    fn title_source(&'src self) -> Option<Span<'src>> {
736        match self {
737            Self::Simple(b) => b.title_source(),
738            Self::Media(b) => b.title_source(),
739            Self::Section(b) => b.title_source(),
740            Self::List(b) => b.title_source(),
741            Self::ListItem(b) => b.title_source(),
742            Self::RawDelimited(b) => b.title_source(),
743            Self::CompoundDelimited(b) => b.title_source(),
744            Self::Admonition(b) => b.title_source(),
745            Self::Quote(b) => b.title_source(),
746            Self::Table(b) => b.title_source(),
747            Self::Preamble(b) => b.title_source(),
748            Self::Break(b) => b.title_source(),
749            Self::DocumentAttribute(b) => b.title_source(),
750        }
751    }
752
753    fn title(&self) -> Option<&str> {
754        match self {
755            Self::Simple(b) => b.title(),
756            Self::Media(b) => b.title(),
757            Self::Section(b) => b.title(),
758            Self::List(b) => b.title(),
759            Self::ListItem(b) => b.title(),
760            Self::RawDelimited(b) => b.title(),
761            Self::CompoundDelimited(b) => b.title(),
762            Self::Admonition(b) => b.title(),
763            Self::Quote(b) => b.title(),
764            Self::Table(b) => b.title(),
765            Self::Preamble(b) => b.title(),
766            Self::Break(b) => b.title(),
767            Self::DocumentAttribute(b) => b.title(),
768        }
769    }
770
771    fn caption(&self) -> Option<&str> {
772        match self {
773            Self::Simple(b) => b.caption(),
774            Self::Media(b) => b.caption(),
775            Self::Section(b) => b.caption(),
776            Self::List(b) => b.caption(),
777            Self::ListItem(b) => b.caption(),
778            Self::RawDelimited(b) => b.caption(),
779            Self::CompoundDelimited(b) => b.caption(),
780            Self::Admonition(b) => b.caption(),
781            Self::Quote(b) => b.caption(),
782            Self::Table(b) => b.caption(),
783            Self::Preamble(b) => b.caption(),
784            Self::Break(b) => b.caption(),
785            Self::DocumentAttribute(b) => b.caption(),
786        }
787    }
788
789    fn number(&self) -> Option<usize> {
790        match self {
791            Self::Simple(b) => b.number(),
792            Self::Media(b) => b.number(),
793            Self::Section(b) => b.number(),
794            Self::List(b) => b.number(),
795            Self::ListItem(b) => b.number(),
796            Self::RawDelimited(b) => b.number(),
797            Self::CompoundDelimited(b) => b.number(),
798            Self::Admonition(b) => b.number(),
799            Self::Quote(b) => b.number(),
800            Self::Table(b) => b.number(),
801            Self::Preamble(b) => b.number(),
802            Self::Break(b) => b.number(),
803            Self::DocumentAttribute(b) => b.number(),
804        }
805    }
806
807    fn anchor(&'src self) -> Option<Span<'src>> {
808        match self {
809            Self::Simple(b) => b.anchor(),
810            Self::Media(b) => b.anchor(),
811            Self::Section(b) => b.anchor(),
812            Self::List(b) => b.anchor(),
813            Self::ListItem(b) => b.anchor(),
814            Self::RawDelimited(b) => b.anchor(),
815            Self::CompoundDelimited(b) => b.anchor(),
816            Self::Admonition(b) => b.anchor(),
817            Self::Quote(b) => b.anchor(),
818            Self::Table(b) => b.anchor(),
819            Self::Preamble(b) => b.anchor(),
820            Self::Break(b) => b.anchor(),
821            Self::DocumentAttribute(b) => b.anchor(),
822        }
823    }
824
825    fn anchor_reftext(&'src self) -> Option<Span<'src>> {
826        match self {
827            Self::Simple(b) => b.anchor_reftext(),
828            Self::Media(b) => b.anchor_reftext(),
829            Self::Section(b) => b.anchor_reftext(),
830            Self::List(b) => b.anchor_reftext(),
831            Self::ListItem(b) => b.anchor_reftext(),
832            Self::RawDelimited(b) => b.anchor_reftext(),
833            Self::CompoundDelimited(b) => b.anchor_reftext(),
834            Self::Admonition(b) => b.anchor_reftext(),
835            Self::Quote(b) => b.anchor_reftext(),
836            Self::Table(b) => b.anchor_reftext(),
837            Self::Preamble(b) => b.anchor_reftext(),
838            Self::Break(b) => b.anchor_reftext(),
839            Self::DocumentAttribute(b) => b.anchor_reftext(),
840        }
841    }
842
843    fn attrlist(&'src self) -> Option<&'src Attrlist<'src>> {
844        match self {
845            Self::Simple(b) => b.attrlist(),
846            Self::Media(b) => b.attrlist(),
847            Self::Section(b) => b.attrlist(),
848            Self::List(b) => b.attrlist(),
849            Self::ListItem(b) => b.attrlist(),
850            Self::RawDelimited(b) => b.attrlist(),
851            Self::CompoundDelimited(b) => b.attrlist(),
852            Self::Admonition(b) => b.attrlist(),
853            Self::Quote(b) => b.attrlist(),
854            Self::Table(b) => b.attrlist(),
855            Self::Preamble(b) => b.attrlist(),
856            Self::Break(b) => b.attrlist(),
857            Self::DocumentAttribute(b) => b.attrlist(),
858        }
859    }
860
861    fn substitution_group(&self) -> SubstitutionGroup {
862        match self {
863            Self::Simple(b) => b.substitution_group(),
864            Self::Media(b) => b.substitution_group(),
865            Self::Section(b) => b.substitution_group(),
866            Self::List(b) => b.substitution_group(),
867            Self::ListItem(b) => b.substitution_group(),
868            Self::RawDelimited(b) => b.substitution_group(),
869            Self::CompoundDelimited(b) => b.substitution_group(),
870            Self::Admonition(b) => b.substitution_group(),
871            Self::Quote(b) => b.substitution_group(),
872            Self::Table(b) => b.substitution_group(),
873            Self::Preamble(b) => b.substitution_group(),
874            Self::Break(b) => b.substitution_group(),
875            Self::DocumentAttribute(b) => b.substitution_group(),
876        }
877    }
878}
879
880impl<'src> HasSpan<'src> for Block<'src> {
881    fn span(&self) -> Span<'src> {
882        match self {
883            Self::Simple(b) => b.span(),
884            Self::Media(b) => b.span(),
885            Self::Section(b) => b.span(),
886            Self::List(b) => b.span(),
887            Self::ListItem(b) => b.span(),
888            Self::RawDelimited(b) => b.span(),
889            Self::CompoundDelimited(b) => b.span(),
890            Self::Admonition(b) => b.span(),
891            Self::Quote(b) => b.span(),
892            Self::Table(b) => b.span(),
893            Self::Preamble(b) => b.span(),
894            Self::Break(b) => b.span(),
895            Self::DocumentAttribute(b) => b.span(),
896        }
897    }
898}