Skip to main content

asciidoc_parser/blocks/
block.rs

1use crate::{
2    HasSpan, Parser, Span,
3    attributes::Attrlist,
4    blocks::{
5        AdmonitionBlock, Break, CompoundDelimitedBlock, ContentModel, IsBlock, ListBlock, ListItem,
6        ListItemMarker, MediaBlock, Preamble, QuoteBlock, RawDelimitedBlock, SectionBlock,
7        SimpleBlock, TableBlock, TocBlock, media::TargetResolution, metadata::BlockMetadata,
8        starts_with_admonition_label,
9    },
10    content::{Content, SubstitutionGroup, substitute_attributes_in_reftext},
11    document::{Attribute, InterpretedValue, RefType},
12    parser::{InlineSubstitutionRenderer, ReferenceResolver, ReferenceWarnings, XrefSignifier},
13    span::MatchedItem,
14    strings::CowStr,
15    warnings::{MatchAndWarnings, Warning, WarningType},
16};
17
18/// **Block elements** form the main structure of an AsciiDoc document, starting
19/// with the document itself.
20///
21/// A block element (aka **block**) is a discrete, line-oriented chunk of
22/// content in an AsciiDoc document. Once parsed, that chunk of content becomes
23/// a block element in the parsed document model. Certain blocks may contain
24/// other blocks, so we say that blocks can be nested. The converter visits each
25/// block in turn, in document order, converting it to a corresponding chunk of
26/// output.
27///
28/// This enum represents all of the block types that are understood directly by
29/// this parser and also implements the [`IsBlock`] trait.
30#[derive(Clone, Eq, Hash, PartialEq)]
31#[non_exhaustive]
32pub enum Block<'src> {
33    /// A block that’s treated as contiguous lines of paragraph text (and
34    /// subject to normal substitutions) (e.g., a paragraph block).
35    Simple(SimpleBlock<'src>),
36
37    /// A media block is used to represent an image, video, or audio block
38    /// macro.
39    Media(MediaBlock<'src>),
40
41    /// A section helps to partition the document into a content hierarchy.
42    /// May also be a part, chapter, or special section.
43    Section(SectionBlock<'src>),
44
45    /// A list contains a sequence of items prefixed with symbol, such as a disc
46    /// (aka bullet). Each individual item in the list is represented by a
47    /// [`ListItem`].
48    List(ListBlock<'src>),
49
50    /// A list item is a special kind of block that is a member of a
51    /// [`ListBlock`] and contains one or more blocks attached to it.
52    ListItem(ListItem<'src>),
53
54    /// A delimited block that contains verbatim, raw, or comment text. The
55    /// content between the matching delimiters is not parsed for block
56    /// syntax.
57    RawDelimited(RawDelimitedBlock<'src>),
58
59    /// A delimited block that can contain other blocks.
60    CompoundDelimited(CompoundDelimitedBlock<'src>),
61
62    /// An admonition draws attention to a statement by taking it out of the
63    /// content's flow and labeling it with a priority (e.g., a note or a
64    /// warning).
65    Admonition(AdmonitionBlock<'src>),
66
67    /// A blockquote: a quote, prose excerpt, or verse, optionally attributed to
68    /// a person and a source citation.
69    Quote(QuoteBlock<'src>),
70
71    /// A table block arranges content into a grid of rows and columns.
72    Table(TableBlock<'src>),
73
74    /// Content between the end of the document header and the first section
75    /// title in the document body is called the preamble.
76    Preamble(Preamble<'src>),
77
78    /// A thematic or page break.
79    Break(Break<'src>),
80
81    /// The `toc::[]` block macro, marking where a table of contents should be
82    /// rendered under `toc-placement: macro`.
83    Toc(TocBlock<'src>),
84
85    /// When an attribute is defined in the document body using an attribute
86    /// entry, that’s simply referred to as a document attribute.
87    DocumentAttribute(Attribute<'src>),
88}
89
90impl<'src> std::fmt::Debug for Block<'src> {
91    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
92        match self {
93            Block::Simple(block) => f.debug_tuple("Block::Simple").field(block).finish(),
94            Block::Media(block) => f.debug_tuple("Block::Media").field(block).finish(),
95            Block::Section(block) => f.debug_tuple("Block::Section").field(block).finish(),
96            Block::List(block) => f.debug_tuple("Block::List").field(block).finish(),
97            Block::ListItem(block) => f.debug_tuple("Block::ListItem").field(block).finish(),
98
99            Block::RawDelimited(block) => {
100                f.debug_tuple("Block::RawDelimited").field(block).finish()
101            }
102
103            Block::CompoundDelimited(block) => f
104                .debug_tuple("Block::CompoundDelimited")
105                .field(block)
106                .finish(),
107
108            Block::Admonition(block) => f.debug_tuple("Block::Admonition").field(block).finish(),
109            Block::Quote(block) => f.debug_tuple("Block::Quote").field(block).finish(),
110            Block::Table(block) => f.debug_tuple("Block::Table").field(block).finish(),
111            Block::Preamble(block) => f.debug_tuple("Block::Preamble").field(block).finish(),
112            Block::Break(break_) => f.debug_tuple("Block::Break").field(break_).finish(),
113            Block::Toc(block) => f.debug_tuple("Block::Toc").field(block).finish(),
114
115            Block::DocumentAttribute(block) => f
116                .debug_tuple("Block::DocumentAttribute")
117                .field(block)
118                .finish(),
119        }
120    }
121}
122
123/// Outcome of attempting to parse a single [`Block`].
124///
125/// Most blocks parse to [`Parsed`](Self::Parsed). [`Dropped`](Self::Dropped)
126/// covers input that was consumed but yields no block – a drop-line block
127/// macro, or block metadata (such as a lone empty `[[]]` anchor) that decorates
128/// no block – which the parser must distinguish both from a successful parse
129/// and from "no block matched" (so the block-collection loops advance past the
130/// consumed source rather than spinning or mis-parsing it).
131// `Parsed` embeds a `Block`, which is itself a large enum (see the matching
132// allow on `Block`). This outcome is short-lived and returned by value on the
133// hot parse path, so boxing it would just trade the size for an allocation.
134#[allow(clippy::large_enum_variant)]
135pub(crate) enum BlockParseOutcome<'src> {
136    /// A block was parsed.
137    Parsed(MatchedItem<'src, Block<'src>>),
138
139    /// The input was consumed but yielded no block, so parsing must resume at
140    /// the contained span (where the consumed input ends) rather than treat the
141    /// source as unmatched. Two cases produce this:
142    ///
143    /// * A block macro whose target referenced a missing attribute under
144    ///   `attribute-missing=drop-line`, which Asciidoctor discards entirely.
145    ///
146    /// * Block metadata that named nothing and decorates no block – notably a
147    ///   lone empty `[[]]` anchor at the end of a block scope – which is
148    ///   dropped rather than rendered.
149    Dropped(Span<'src>),
150
151    /// No block matched. This happens only for empty or all-blank input.
152    NoMatch,
153}
154
155impl<'src> Block<'src> {
156    /// Parse a block of any type and return a `Block` that describes it.
157    ///
158    /// Consumes any blank lines before and after the block.
159    ///
160    /// This is a test-only convenience wrapper over
161    /// [`parse_with_outcome`](Self::parse_with_outcome) that flattens the
162    /// drop-line outcome to an `Option`; production code uses
163    /// `parse_with_outcome` so it can react to a dropped block.
164    #[cfg(test)]
165    pub(crate) fn parse(
166        source: Span<'src>,
167        parser: &mut Parser,
168    ) -> MatchAndWarnings<'src, Option<MatchedItem<'src, Self>>> {
169        let MatchAndWarnings { item, warnings } = Self::parse_internal(source, parser, None, false);
170
171        MatchAndWarnings {
172            item: match item {
173                BlockParseOutcome::Parsed(mi) => Some(mi),
174                BlockParseOutcome::Dropped(_) | BlockParseOutcome::NoMatch => None,
175            },
176            warnings,
177        }
178    }
179
180    /// Parse a block of any type, returning the full [`BlockParseOutcome`] so a
181    /// block-collection loop can advance past a block that was dropped at parse
182    /// time (`attribute-missing=drop-line`). Consumes any blank lines before
183    /// and after the block.
184    ///
185    /// This is the entry point used by production block-collection loops.
186    pub(crate) fn parse_with_outcome(
187        source: Span<'src>,
188        parser: &mut Parser,
189    ) -> MatchAndWarnings<'src, BlockParseOutcome<'src>> {
190        Self::parse_internal(source, parser, None, false)
191    }
192
193    /// Parse a block of any type and return a `Block` that describes it.
194    ///
195    /// Will terminate early when parsing certain block types within a list
196    /// context.
197    ///
198    /// Consumes any blank lines before and after the block.
199    ///
200    /// If `is_continuation` is true, this content was attached via a `+`
201    /// continuation marker and literal blocks should preserve their
202    /// indentation.
203    pub(crate) fn parse_for_list_item(
204        source: Span<'src>,
205        parser: &mut Parser,
206        parent_list_markers: &[ListItemMarker<'src>],
207        is_continuation: bool,
208    ) -> MatchAndWarnings<'src, BlockParseOutcome<'src>> {
209        Self::parse_internal(source, parser, Some(parent_list_markers), is_continuation)
210    }
211
212    /// Shared parser for [`parse_with_outcome`](Self::parse_with_outcome) and
213    /// [`parse_for_list_item`](Self::parse_for_list_item).
214    fn parse_internal(
215        source: Span<'src>,
216        parser: &mut Parser,
217        parent_list_markers: Option<&[ListItemMarker<'src>]>,
218        is_continuation: bool,
219    ) -> MatchAndWarnings<'src, BlockParseOutcome<'src>> {
220        // Optimization: If the first line doesn't match any of the early indications
221        // for delimited blocks, titles, or attrlists, we can skip directly to treating
222        // this as a simple block. That saves quite a bit of parsing time.
223        let first_line = source.take_line().item.discard_whitespace();
224
225        // If it does contain any of those markers, we fall through to the more costly
226        // tests below which can more accurately classify the upcoming block.
227        if let Some(first_char) = first_line.chars().next()
228            && !matches!(
229                first_char,
230                '.' | '#'
231                    | '='
232                    | '/'
233                    | '-'
234                    | '+'
235                    | '*'
236                    | '_'
237                    | '`'
238                    | '['
239                    | ':'
240                    | '\''
241                    | '<'
242                    | '>'
243                    | '"'
244                    | '•'
245            )
246            && !first_line.contains("::")
247            && !first_line.contains(";;")
248            && !TableBlock::is_table_delimiter(&first_line)
249            && !ListItemMarker::starts_with_marker(first_line)
250            && !starts_with_admonition_label(first_line)
251            && parent_list_markers.is_none()
252            && parser.pending_block_title.is_none()
253            && let Some(MatchedItem {
254                item: simple_block,
255                after,
256            }) = SimpleBlock::parse_fast(source, parser)
257        {
258            let mut warnings = vec![];
259            let block = Self::Simple(simple_block);
260
261            // This fast path only handles a metadata-free simple block, so there
262            // is no `[[id,reftext]]` anchor reftext to resolve.
263            Self::register_block_id(
264                block.id(),
265                Self::block_reftext(&block, None).as_deref(),
266                Self::block_signifier(&block, parser),
267                block.span(),
268                parser,
269                &mut warnings,
270            );
271
272            return MatchAndWarnings {
273                item: BlockParseOutcome::Parsed(MatchedItem { item: block, after }),
274                warnings,
275            };
276        }
277
278        // Look for document attributes first since these don't support block metadata.
279        if first_line.starts_with(':')
280            && (first_line.ends_with(':') || first_line.contains(": "))
281            && let Some(attr) = Attribute::parse(source, parser)
282        {
283            let mut warnings: Vec<Warning<'src>> = vec![];
284            parser.set_attribute_from_body(&attr.item, &mut warnings);
285
286            return MatchAndWarnings {
287                item: BlockParseOutcome::Parsed(MatchedItem {
288                    item: Self::DocumentAttribute(attr.item),
289                    after: attr.after,
290                }),
291                warnings,
292            };
293        }
294
295        // Optimization not possible; start by looking for block metadata (title,
296        // attrlist, etc.).
297        let MatchAndWarnings {
298            item: mut metadata,
299            mut warnings,
300        } = BlockMetadata::parse(source, parser);
301
302        // A block title stashed by an enclosing section heading (see
303        // `SectionBlock::parse`) is claimed by the next block parsed – this
304        // one. A title of the block's own wins, discarding the carried title.
305        // The carried title has no source line adjacent to this block, so
306        // `title_source` stays `None` (the same shape as a `title=` attribute).
307        if let Some(pending_title) = parser.pending_block_title.take()
308            && metadata.title.is_none()
309        {
310            // The carried title arrives as an owned snapshot; rebuild it as a
311            // `Content` anchored at the block's start, restoring any deferred
312            // cross-references so the title pass can still resolve them.
313            metadata.title = Some(crate::content::Content::from_owned_title(
314                metadata.block_start,
315                pending_title,
316            ));
317        }
318
319        // Tolerate a blank line between a block's metadata (title, anchor, or
320        // attribute list) and the block it decorates. Asciidoctor's
321        // `parse_block_metadata_lines` skips blank lines after each metadata
322        // line, so metadata separated from its block by one or more blank lines
323        // still attaches to that block rather than dangling as a spurious
324        // `MissingBlockAfterTitleOrAttributeList`. Advancing `block_start` past
325        // the gap lets the block-type dispatch below see the content directly.
326        //
327        // This applies at the block level only. Inside a list item,
328        // blank-separated metadata follows the list-continuation rules handled
329        // in `ListItem::parse` (where such metadata is discarded), so leave
330        // `block_start` pointing at the blank line for those callers. Likewise,
331        // if only blank lines follow (no block content), leave it untouched so
332        // the genuinely-dangling-metadata warning still fires.
333        if parent_list_markers.is_none() && !metadata.is_empty() {
334            let after_blanks = metadata.block_start.discard_empty_lines();
335            if after_blanks != metadata.block_start && !after_blanks.is_empty() {
336                metadata.block_start = after_blanks;
337            }
338        }
339
340        // Resolve attribute references in a `[[id,reftext]]` anchor reftext now,
341        // while the parser still holds the attributes in effect where the anchor
342        // appears. A compound block's body (parsed below) can redefine those
343        // attributes, so deferring this to registration – after the body – would
344        // record the wrong value. The result is threaded into `block_reftext`.
345        let anchor_reftext = metadata
346            .anchor_reftext
347            .as_ref()
348            .map(|span| substitute_attributes_in_reftext(*span, parser));
349
350        // The `[literal]` block style normally marks a literal *paragraph*,
351        // which is handled directly as a simple (literal) block below, bypassing
352        // the delimited-block parsers. The exception is when `[literal]` is set
353        // on the delimiter line of a structural container, where it masquerades
354        // over that container (e.g. `[literal]` on a `----` listing, on a `....`
355        // literal, or on a `--` open block); those cases must fall through to the
356        // delimited-block parsers.
357        let is_literal =
358            metadata.attrlist.as_ref().and_then(|a| a.block_style()) == Some("literal") && {
359                let first_line = metadata.block_start.take_normalized_line().item;
360                !RawDelimitedBlock::is_valid_delimiter(&first_line)
361                    && !CompoundDelimitedBlock::is_valid_delimiter(&first_line)
362                    && !TableBlock::is_table_delimiter(&first_line)
363            };
364
365        // A simple block may be parsed speculatively inside the `!is_literal`
366        // branch below (to detect the "metadata with no block" edge case). When
367        // that speculative parse succeeds it is reused as the final result rather
368        // than re-parsed, so that the captioning side effect of
369        // `SimpleBlock::parse` (which can consume a caption counter) happens at
370        // most once per block.
371        let mut simple_block_mi = None;
372
373        if !is_literal {
374            if let Some(mut adm_maw) = AdmonitionBlock::parse(&metadata, parser)
375                && let Some(adm) = adm_maw.item
376            {
377                if !adm_maw.warnings.is_empty() {
378                    warnings.append(&mut adm_maw.warnings);
379                }
380
381                let block = Self::Admonition(adm.item);
382
383                Self::register_block_id(
384                    block.id(),
385                    Self::block_reftext(&block, anchor_reftext.as_deref()).as_deref(),
386                    Self::block_signifier(&block, parser),
387                    block.span(),
388                    parser,
389                    &mut warnings,
390                );
391
392                return MatchAndWarnings {
393                    item: BlockParseOutcome::Parsed(MatchedItem {
394                        item: block,
395                        after: adm.after,
396                    }),
397                    warnings,
398                };
399            }
400
401            if let Some(mut quote_maw) = QuoteBlock::parse(&metadata, parser)
402                && let Some(quote) = quote_maw.item
403            {
404                if !quote_maw.warnings.is_empty() {
405                    warnings.append(&mut quote_maw.warnings);
406                }
407
408                let block = Self::Quote(quote.item);
409
410                Self::register_block_id(
411                    block.id(),
412                    Self::block_reftext(&block, anchor_reftext.as_deref()).as_deref(),
413                    Self::block_signifier(&block, parser),
414                    block.span(),
415                    parser,
416                    &mut warnings,
417                );
418
419                return MatchAndWarnings {
420                    item: BlockParseOutcome::Parsed(MatchedItem {
421                        item: block,
422                        after: quote.after,
423                    }),
424                    warnings,
425                };
426            }
427
428            if let Some(mut rdb_maw) = RawDelimitedBlock::parse(&metadata, parser)
429                && let Some(rdb) = rdb_maw.item
430            {
431                if !rdb_maw.warnings.is_empty() {
432                    warnings.append(&mut rdb_maw.warnings);
433                }
434
435                let block = Self::RawDelimited(rdb.item);
436
437                Self::register_block_id(
438                    block.id(),
439                    Self::block_reftext(&block, anchor_reftext.as_deref()).as_deref(),
440                    Self::block_signifier(&block, parser),
441                    block.span(),
442                    parser,
443                    &mut warnings,
444                );
445
446                return MatchAndWarnings {
447                    item: BlockParseOutcome::Parsed(MatchedItem {
448                        item: block,
449                        after: rdb.after,
450                    }),
451                    warnings,
452                };
453            }
454
455            if let Some(mut cdb_maw) = CompoundDelimitedBlock::parse(&metadata, parser)
456                && let Some(cdb) = cdb_maw.item
457            {
458                if !cdb_maw.warnings.is_empty() {
459                    warnings.append(&mut cdb_maw.warnings);
460                }
461
462                let block = Self::CompoundDelimited(cdb.item);
463
464                Self::register_block_id(
465                    block.id(),
466                    Self::block_reftext(&block, anchor_reftext.as_deref()).as_deref(),
467                    Self::block_signifier(&block, parser),
468                    block.span(),
469                    parser,
470                    &mut warnings,
471                );
472
473                return MatchAndWarnings {
474                    item: BlockParseOutcome::Parsed(MatchedItem {
475                        item: block,
476                        after: cdb.after,
477                    }),
478                    warnings,
479                };
480            }
481
482            if let Some(mut table_maw) = TableBlock::parse(&metadata, parser)
483                && let Some(table) = table_maw.item
484            {
485                if !table_maw.warnings.is_empty() {
486                    warnings.append(&mut table_maw.warnings);
487                }
488
489                let block = Self::Table(table.item);
490
491                Self::register_block_id(
492                    block.id(),
493                    Self::block_reftext(&block, anchor_reftext.as_deref()).as_deref(),
494                    Self::block_signifier(&block, parser),
495                    block.span(),
496                    parser,
497                    &mut warnings,
498                );
499
500                return MatchAndWarnings {
501                    item: BlockParseOutcome::Parsed(MatchedItem {
502                        item: block,
503                        after: table.after,
504                    }),
505                    warnings,
506                };
507            }
508
509            // Try to discern the block type by scanning the first line.
510            let line = metadata.block_start.take_normalized_line();
511
512            if line.item.starts_with("image::")
513                || line.item.starts_with("video::")
514                || line.item.starts_with("audio::")
515            {
516                let mut media_block_maw = MediaBlock::parse(&metadata, parser);
517
518                if let Some(mut media_block) = media_block_maw.item {
519                    // Only propagate warnings from media block parsing if we think this
520                    // *is* a media block. Otherwise, there would likely be too many false
521                    // positives.
522                    if !media_block_maw.warnings.is_empty() {
523                        warnings.append(&mut media_block_maw.warnings);
524                    }
525
526                    // Resolve attribute references in the macro target. Under
527                    // `attribute-missing=drop-line`, a reference to a missing
528                    // attribute drops the entire block (Asciidoctor behavior).
529                    if media_block.item.resolve_target(parser) == TargetResolution::Drop {
530                        return MatchAndWarnings {
531                            item: BlockParseOutcome::Dropped(media_block.after),
532                            warnings,
533                        };
534                    }
535
536                    // Assign the caption only now that the block has survived
537                    // `resolve_target`, so a dropped image does not consume the
538                    // `figure-number` counter and leave a gap in the numbering.
539                    media_block.item.assign_caption(parser);
540
541                    let block = Self::Media(media_block.item);
542
543                    Self::register_block_id(
544                        block.id(),
545                        Self::block_reftext(&block, anchor_reftext.as_deref()).as_deref(),
546                        Self::block_signifier(&block, parser),
547                        block.span(),
548                        parser,
549                        &mut warnings,
550                    );
551
552                    return MatchAndWarnings {
553                        item: BlockParseOutcome::Parsed(MatchedItem {
554                            item: block,
555                            after: media_block.after,
556                        }),
557                        warnings,
558                    };
559                }
560
561                // This might be some other kind of block, so we don't
562                // automatically error out on a parse failure.
563            }
564
565            if line.item.starts_with("toc::") {
566                let mut toc_block_maw = TocBlock::parse(&metadata, parser);
567
568                if let Some(toc_block) = toc_block_maw.item {
569                    // Only propagate warnings from TOC block parsing if we think
570                    // this *is* a TOC block. Otherwise, there would likely be too
571                    // many false positives.
572                    if !toc_block_maw.warnings.is_empty() {
573                        warnings.append(&mut toc_block_maw.warnings);
574                    }
575
576                    let block = Self::Toc(toc_block.item);
577
578                    Self::register_block_id(
579                        block.id(),
580                        Self::block_reftext(&block, anchor_reftext.as_deref()).as_deref(),
581                        Self::block_signifier(&block, parser),
582                        block.span(),
583                        parser,
584                        &mut warnings,
585                    );
586
587                    return MatchAndWarnings {
588                        item: BlockParseOutcome::Parsed(MatchedItem {
589                            item: block,
590                            after: toc_block.after,
591                        }),
592                        warnings,
593                    };
594                }
595
596                // This might be some other kind of block, so we don't
597                // automatically error out on a parse failure.
598            }
599
600            if (line.item.starts_with('=') || line.item.starts_with('#'))
601                && let Some(mi_section_block) =
602                    SectionBlock::parse(&metadata, parser, &mut warnings)
603            {
604                // A line starting with `=` or `#` might be some other kind of block, so we
605                // continue quietly if `SectionBlock` parser rejects this block.
606
607                return MatchAndWarnings {
608                    item: BlockParseOutcome::Parsed(MatchedItem {
609                        item: Self::Section(mi_section_block.item),
610                        after: mi_section_block.after,
611                    }),
612                    warnings,
613                };
614            }
615
616            if (line.item.starts_with('\'')
617                || line.item.starts_with('-')
618                || line.item.starts_with('*')
619                || line.item.starts_with('_')
620                || line.item.starts_with('<'))
621                && let Some(mi_break) = Break::parse(&metadata, parser)
622            {
623                // Continue quietly if `Break` parser rejects this block.
624
625                return MatchAndWarnings {
626                    item: BlockParseOutcome::Parsed(MatchedItem {
627                        item: Self::Break(mi_break.item),
628                        after: mi_break.after,
629                    }),
630                    warnings,
631                };
632            }
633
634            // Only try to parse as a new list if we're NOT inside a list item context.
635            // If we are inside a list context, lists can only be created when the first
636            // line is a list item marker (handled above).
637            if parent_list_markers.is_none()
638                && let Some(mi_list) = ListBlock::parse(&metadata, parser, &mut warnings)
639            {
640                return MatchAndWarnings {
641                    item: BlockParseOutcome::Parsed(MatchedItem {
642                        item: Self::List(mi_list.item),
643                        after: mi_list.after,
644                    }),
645                    warnings,
646                };
647            }
648
649            // First, let's look for a fun edge case. Perhaps the text contains block
650            // metadata but no block immediately following. If we're not careful, we could
651            // spin in a loop (for example, `parse_blocks_until`) thinking there will be
652            // another block, but there isn't.
653
654            // The following check disables that spin loop.
655            simple_block_mi = if let Some(plm) = parent_list_markers {
656                SimpleBlock::parse_for_list_item(&metadata, parser, is_continuation, plm)
657            } else {
658                SimpleBlock::parse(&metadata, parser)
659            };
660
661            if simple_block_mi.is_none() {
662                if !metadata.is_empty() {
663                    // We have a metadata with no block. Treat it as a simple block but issue a
664                    // warning.
665
666                    warnings.push(Warning {
667                        source: metadata.source,
668                        warning: WarningType::MissingBlockAfterTitleOrAttributeList,
669                        origin: None,
670                    });
671
672                    // Remove the metadata content so that SimpleBlock will read the title/attrlist
673                    // line(s) as regular content. The speculative parse failed, so the
674                    // block is re-parsed below with this stripped metadata.
675                    metadata.title_source = None;
676                    metadata.title = None;
677                    metadata.anchor = None;
678                    metadata.attrlist = None;
679                    metadata.block_start = metadata.source;
680                } else if !metadata.source.data().is_empty() {
681                    // The metadata scan consumed one or more do-nothing lines
682                    // (e.g. a lone empty `[[]]` anchor) that produced no title,
683                    // anchor, or attribute list, and no block follows them. The
684                    // lines are still consumed, so report the source as dropped
685                    // (resuming at `block_start`) rather than falling through to
686                    // `NoMatch`: a non-blank source left unadvanced would spin
687                    // the block-collection loop. Genuinely empty/blank input
688                    // (nothing consumed) still reaches `NoMatch` below.
689                    return MatchAndWarnings {
690                        item: BlockParseOutcome::Dropped(metadata.block_start),
691                        warnings,
692                    };
693                }
694            }
695        }
696
697        // If no other block kind matches, we can always use SimpleBlock. Reuse the
698        // speculative parse from the `!is_literal` branch when it succeeded;
699        // otherwise (a literal block, or metadata stripped above) parse now.
700        let simple_block_mi = match simple_block_mi {
701            Some(mi) => Some(mi),
702            None => {
703                if let Some(plm) = parent_list_markers {
704                    SimpleBlock::parse_for_list_item(&metadata, parser, is_continuation, plm)
705                } else {
706                    SimpleBlock::parse(&metadata, parser)
707                }
708            }
709        };
710
711        let mut result = MatchAndWarnings {
712            item: match simple_block_mi {
713                Some(mi) => BlockParseOutcome::Parsed(MatchedItem {
714                    item: Self::Simple(mi.item),
715                    after: mi.after,
716                }),
717                None => BlockParseOutcome::NoMatch,
718            },
719            warnings,
720        };
721
722        if let BlockParseOutcome::Parsed(ref matched_item) = result.item {
723            Self::register_block_id(
724                matched_item.item.id(),
725                Self::block_reftext(&matched_item.item, anchor_reftext.as_deref()).as_deref(),
726                Self::block_signifier(&matched_item.item, parser),
727                matched_item.item.span(),
728                parser,
729                &mut result.warnings,
730            );
731        }
732
733        result
734    }
735
736    /// Determine the [`XrefSignifier`] a cross-reference uses to build
737    /// `full`/`short` [`xrefstyle`](crate::parser::XrefStyle) text when this
738    /// block is the target.
739    ///
740    /// A signifier is produced only for an auto-numbered captioned block (e.g.
741    /// an image → "Figure 1", a titled table → "Table 1") that has no explicit
742    /// reftext. A block with an explicit `reftext` attribute or a
743    /// `[[id,reftext]]` anchor reftext uses that text verbatim, so it gets no
744    /// signifier; neither does an uncaptioned block or one whose caption was
745    /// overridden with `[caption=...]` (which is not numbered).
746    fn block_signifier<'a>(block: &'a Block<'a>, parser: &Parser) -> Option<XrefSignifier> {
747        // Only captioned blocks are eligible.
748        let caption = block.caption()?;
749
750        let has_explicit_reftext = block
751            .attrlist()
752            .and_then(|attrlist| attrlist.named_attribute("reftext"))
753            .is_some()
754            || block.anchor_reftext().is_some();
755        if has_explicit_reftext {
756            return None;
757        }
758
759        // Exclude explicit caption overrides, which are not numbered. This is
760        // *not* the same as `block.number().is_none()`: an auto-numbered block
761        // whose context counter holds a non-integer value (e.g. `:figure-number:
762        // A`, rendering "Figure B") also has no bare integer number, yet it is
763        // genuinely numbered and must keep its signifier ("Figure B").
764        if Self::has_caption_override(block, parser) {
765            return None;
766        }
767
768        // The caption prefix is "<label> <n>. "; the xrefstyle label is that
769        // prefix without its trailing ". " separator (e.g. "Figure 1").
770        let label = caption.strip_suffix(". ").unwrap_or(caption).to_string();
771        Some(XrefSignifier {
772            label,
773            emphasize: false,
774        })
775    }
776
777    /// Whether a captioned block's caption comes from an explicit override
778    /// rather than automatic numbering.
779    ///
780    /// An override is a `caption` attribute on the block (or, for an image, on
781    /// the image macro), or a non-empty document-wide `caption` attribute. This
782    /// mirrors the override detection in
783    /// [`caption::assign_block_caption`](crate::blocks::caption) and
784    /// [`MediaBlock::assign_caption`], so the two agree on which blocks are
785    /// numbered.
786    fn has_caption_override<'a>(block: &'a Block<'a>, parser: &Parser) -> bool {
787        let attribute_override = block
788            .attrlist()
789            .and_then(|attrlist| attrlist.named_attribute("caption"))
790            .is_some()
791            || matches!(block, Block::Media(media)
792                if media.macro_attrlist().named_attribute("caption").is_some());
793
794        attribute_override
795            || matches!(
796                parser.attribute_value("caption"),
797                InterpretedValue::Value(value) if !value.is_empty(),
798            )
799    }
800
801    /// Determine the reftext (a.k.a. xreflabel) used as the link text when a
802    /// block is the target of a cross reference. Asciidoctor's precedence is:
803    /// an explicit `reftext` attribute, then the reftext supplied with a
804    /// block anchor (`[[id,reftext]]`), and finally the block title.
805    ///
806    /// `anchor_reftext` is the block's `[[id,reftext]]` anchor reftext with its
807    /// attribute references already resolved (by the caller, against the
808    /// attributes in effect where the anchor appears – captured before the
809    /// block's body is parsed, since a compound block's body may itself
810    /// redefine those attributes). This matches how the anchor ID and a
811    /// `reftext=` attribute (both substituted when the attribute list is
812    /// parsed) are handled; the `reftext=` and title branches are already
813    /// substituted.
814    fn block_reftext<'a>(block: &'a Block<'a>, anchor_reftext: Option<&str>) -> Option<CowStr<'a>> {
815        if let Some(attr) = block
816            .attrlist()
817            .and_then(|attrlist| attrlist.named_attribute("reftext"))
818        {
819            return Some(CowStr::from(attr.value()));
820        }
821
822        if let Some(anchor_reftext) = anchor_reftext {
823            return Some(CowStr::from(anchor_reftext.to_string()));
824        }
825
826        block.title().map(CowStr::from)
827    }
828
829    /// Register a block's ID with the catalog if the block has an ID.
830    ///
831    /// This should be called for all block types except `SectionBlock`,
832    /// which handles its own catalog registration.
833    fn register_block_id(
834        id: Option<&str>,
835        reftext: Option<&str>,
836        signifier: Option<XrefSignifier>,
837        span: Span<'src>,
838        parser: &mut Parser,
839        warnings: &mut Vec<Warning<'src>>,
840    ) {
841        if let Some(id) = id {
842            match parser.register_ref(id, reftext, RefType::Anchor) {
843                Ok(()) => {
844                    if let Some(signifier) = signifier {
845                        parser.set_ref_signifier(id, signifier);
846                    }
847                }
848                Err(_duplicate_error) => {
849                    // If registration fails due to duplicate ID, issue a warning.
850                    warnings.push(Warning {
851                        source: span,
852                        warning: WarningType::DuplicateId(id.to_string()),
853                        origin: None,
854                    });
855                }
856            }
857        }
858    }
859
860    /// Returns a reference to the inner [`ListItem`] if this is a
861    /// `Block::ListItem`, or `None` otherwise.
862    pub(crate) fn as_list_item(&self) -> Option<&ListItem<'src>> {
863        match self {
864            Self::ListItem(li) => Some(li),
865            _ => None,
866        }
867    }
868
869    /// Resolve any deferred cross-references in this block and its descendants,
870    /// using `resolver` to map targets to destinations and `renderer` to render
871    /// the resulting links. Unresolved targets are reported in `warnings`.
872    ///
873    /// This drives the recursion uniformly via the [`IsBlock::content_mut`] and
874    /// [`IsBlock::child_blocks_mut`] accessors, so it needs no per-block-type
875    /// special casing.
876    pub(crate) fn resolve_references(
877        &mut self,
878        resolver: &dyn ReferenceResolver,
879        renderer: &dyn InlineSubstitutionRenderer,
880        warnings: &mut ReferenceWarnings<'src>,
881    ) {
882        // A section is not resolved here: its resolvable content is its
883        // heading, which `content_mut` deliberately does not expose (see
884        // `SectionBlock`). Headings are resolved by the document-order title
885        // pass (`title_refs::resolve_title_references`), which coordinates
886        // cross-references *between* titles (forward and circular) – something
887        // per-content resolution cannot see.
888        if let Some(content) = self.content_mut() {
889            content.resolve_references(resolver, renderer, warnings);
890        }
891
892        // Tables hold their resolvable content in cells rather than in a single
893        // `content_mut()` value, so they are resolved explicitly here.
894        if let Self::Table(table) = self {
895            table.resolve_references(resolver, renderer, warnings);
896        }
897
898        // A Markdown-style blockquote holds its nested blocks in its own owned
899        // source, which the generic `child_blocks_mut()` walk below does not
900        // reach, so they are resolved explicitly here.
901        if let Self::Quote(quote) = self {
902            quote.resolve_references(resolver, renderer, warnings);
903        }
904
905        for child in self.child_blocks_mut() {
906            child.resolve_references(resolver, renderer, warnings);
907        }
908    }
909
910    /// Returns this block's *block title* (`.Title`) as a mutable [`Content`],
911    /// when the block has one.
912    ///
913    /// This is the decorative title carried above a block, distinct from a
914    /// section's heading. Used only by the document-order title resolution
915    /// pass, which reads a title's deferred cross-references and installs the
916    /// re-rendered title once they are resolved. Blocks that never carry a
917    /// title return `None`.
918    pub(crate) fn block_title_content_mut(&mut self) -> Option<&mut Content<'src>> {
919        match self {
920            Self::Simple(b) => b.title_content_mut(),
921            Self::Media(b) => b.title_content_mut(),
922            Self::List(b) => b.title_content_mut(),
923            Self::RawDelimited(b) => b.title_content_mut(),
924            Self::CompoundDelimited(b) => b.title_content_mut(),
925            Self::Admonition(b) => b.title_content_mut(),
926            Self::Quote(b) => b.title_content_mut(),
927            Self::Table(b) => b.title_content_mut(),
928            Self::Break(b) => b.title_content_mut(),
929            Self::Toc(b) => b.title_content_mut(),
930            _ => None,
931        }
932    }
933}
934
935impl<'src> IsBlock<'src> for Block<'src> {
936    fn content_model(&self) -> ContentModel {
937        match self {
938            Self::Simple(_) => ContentModel::Simple,
939            Self::Media(b) => b.content_model(),
940            Self::Section(_) => ContentModel::Compound,
941            Self::List(b) => b.content_model(),
942            Self::ListItem(b) => b.content_model(),
943            Self::RawDelimited(b) => b.content_model(),
944            Self::CompoundDelimited(b) => b.content_model(),
945            Self::Admonition(b) => b.content_model(),
946            Self::Quote(b) => b.content_model(),
947            Self::Table(b) => b.content_model(),
948            Self::Preamble(b) => b.content_model(),
949            Self::Break(b) => b.content_model(),
950            Self::Toc(b) => b.content_model(),
951            Self::DocumentAttribute(b) => b.content_model(),
952        }
953    }
954
955    fn declared_style(&'src self) -> Option<&'src str> {
956        match self {
957            Self::Simple(b) => b.declared_style(),
958            Self::Media(b) => b.declared_style(),
959            Self::Section(b) => b.declared_style(),
960            Self::List(b) => b.declared_style(),
961            Self::ListItem(b) => b.declared_style(),
962            Self::RawDelimited(b) => b.declared_style(),
963            Self::CompoundDelimited(b) => b.declared_style(),
964            Self::Admonition(b) => b.declared_style(),
965            Self::Quote(b) => b.declared_style(),
966            Self::Table(b) => b.declared_style(),
967            Self::Preamble(b) => b.declared_style(),
968            Self::Break(b) => b.declared_style(),
969            Self::Toc(b) => b.declared_style(),
970            Self::DocumentAttribute(b) => b.declared_style(),
971        }
972    }
973
974    fn rendered_content(&'src self) -> Option<&'src str> {
975        match self {
976            Self::Simple(b) => b.rendered_content(),
977            Self::Media(b) => b.rendered_content(),
978            Self::Section(b) => b.rendered_content(),
979            Self::List(b) => b.rendered_content(),
980            Self::ListItem(b) => b.rendered_content(),
981            Self::RawDelimited(b) => b.rendered_content(),
982            Self::CompoundDelimited(b) => b.rendered_content(),
983            Self::Admonition(b) => b.rendered_content(),
984            Self::Quote(b) => b.rendered_content(),
985            Self::Table(b) => b.rendered_content(),
986            Self::Preamble(b) => b.rendered_content(),
987            Self::Break(b) => b.rendered_content(),
988            Self::Toc(b) => b.rendered_content(),
989            Self::DocumentAttribute(b) => b.rendered_content(),
990        }
991    }
992
993    fn raw_context(&self) -> CowStr<'src> {
994        match self {
995            Self::Simple(b) => b.raw_context(),
996            Self::Media(b) => b.raw_context(),
997            Self::Section(b) => b.raw_context(),
998            Self::List(b) => b.raw_context(),
999            Self::ListItem(b) => b.raw_context(),
1000            Self::RawDelimited(b) => b.raw_context(),
1001            Self::CompoundDelimited(b) => b.raw_context(),
1002            Self::Admonition(b) => b.raw_context(),
1003            Self::Quote(b) => b.raw_context(),
1004            Self::Table(b) => b.raw_context(),
1005            Self::Preamble(b) => b.raw_context(),
1006            Self::Break(b) => b.raw_context(),
1007            Self::Toc(b) => b.raw_context(),
1008            Self::DocumentAttribute(b) => b.raw_context(),
1009        }
1010    }
1011
1012    fn child_blocks_mut(&mut self) -> &mut [Block<'src>] {
1013        match self {
1014            Self::Simple(b) => b.child_blocks_mut(),
1015            Self::Media(b) => b.child_blocks_mut(),
1016            Self::Section(b) => b.child_blocks_mut(),
1017            Self::List(b) => b.child_blocks_mut(),
1018            Self::ListItem(b) => b.child_blocks_mut(),
1019            Self::RawDelimited(b) => b.child_blocks_mut(),
1020            Self::CompoundDelimited(b) => b.child_blocks_mut(),
1021            Self::Admonition(b) => b.child_blocks_mut(),
1022            Self::Quote(b) => b.child_blocks_mut(),
1023            Self::Table(b) => b.child_blocks_mut(),
1024            Self::Preamble(b) => b.child_blocks_mut(),
1025            Self::Break(b) => b.child_blocks_mut(),
1026            Self::Toc(b) => b.child_blocks_mut(),
1027            Self::DocumentAttribute(b) => b.child_blocks_mut(),
1028        }
1029    }
1030
1031    fn content_mut(&mut self) -> Option<&mut Content<'src>> {
1032        match self {
1033            Self::Simple(b) => b.content_mut(),
1034            Self::Media(b) => b.content_mut(),
1035            Self::Section(b) => b.content_mut(),
1036            Self::List(b) => b.content_mut(),
1037            Self::ListItem(b) => b.content_mut(),
1038            Self::RawDelimited(b) => b.content_mut(),
1039            Self::CompoundDelimited(b) => b.content_mut(),
1040            Self::Admonition(b) => b.content_mut(),
1041            Self::Quote(b) => b.content_mut(),
1042            Self::Table(b) => b.content_mut(),
1043            Self::Preamble(b) => b.content_mut(),
1044            Self::Break(b) => b.content_mut(),
1045            Self::Toc(b) => b.content_mut(),
1046            Self::DocumentAttribute(b) => b.content_mut(),
1047        }
1048    }
1049
1050    fn title_source(&'src self) -> Option<Span<'src>> {
1051        match self {
1052            Self::Simple(b) => b.title_source(),
1053            Self::Media(b) => b.title_source(),
1054            Self::Section(b) => b.title_source(),
1055            Self::List(b) => b.title_source(),
1056            Self::ListItem(b) => b.title_source(),
1057            Self::RawDelimited(b) => b.title_source(),
1058            Self::CompoundDelimited(b) => b.title_source(),
1059            Self::Admonition(b) => b.title_source(),
1060            Self::Quote(b) => b.title_source(),
1061            Self::Table(b) => b.title_source(),
1062            Self::Preamble(b) => b.title_source(),
1063            Self::Break(b) => b.title_source(),
1064            Self::Toc(b) => b.title_source(),
1065            Self::DocumentAttribute(b) => b.title_source(),
1066        }
1067    }
1068
1069    fn title(&self) -> Option<&str> {
1070        match self {
1071            Self::Simple(b) => b.title(),
1072            Self::Media(b) => b.title(),
1073            Self::Section(b) => b.title(),
1074            Self::List(b) => b.title(),
1075            Self::ListItem(b) => b.title(),
1076            Self::RawDelimited(b) => b.title(),
1077            Self::CompoundDelimited(b) => b.title(),
1078            Self::Admonition(b) => b.title(),
1079            Self::Quote(b) => b.title(),
1080            Self::Table(b) => b.title(),
1081            Self::Preamble(b) => b.title(),
1082            Self::Break(b) => b.title(),
1083            Self::Toc(b) => b.title(),
1084            Self::DocumentAttribute(b) => b.title(),
1085        }
1086    }
1087
1088    fn caption(&self) -> Option<&str> {
1089        match self {
1090            Self::Simple(b) => b.caption(),
1091            Self::Media(b) => b.caption(),
1092            Self::Section(b) => b.caption(),
1093            Self::List(b) => b.caption(),
1094            Self::ListItem(b) => b.caption(),
1095            Self::RawDelimited(b) => b.caption(),
1096            Self::CompoundDelimited(b) => b.caption(),
1097            Self::Admonition(b) => b.caption(),
1098            Self::Quote(b) => b.caption(),
1099            Self::Table(b) => b.caption(),
1100            Self::Preamble(b) => b.caption(),
1101            Self::Break(b) => b.caption(),
1102            Self::Toc(b) => b.caption(),
1103            Self::DocumentAttribute(b) => b.caption(),
1104        }
1105    }
1106
1107    fn number(&self) -> Option<usize> {
1108        match self {
1109            Self::Simple(b) => b.number(),
1110            Self::Media(b) => b.number(),
1111            Self::Section(b) => b.number(),
1112            Self::List(b) => b.number(),
1113            Self::ListItem(b) => b.number(),
1114            Self::RawDelimited(b) => b.number(),
1115            Self::CompoundDelimited(b) => b.number(),
1116            Self::Admonition(b) => b.number(),
1117            Self::Quote(b) => b.number(),
1118            Self::Table(b) => b.number(),
1119            Self::Preamble(b) => b.number(),
1120            Self::Break(b) => b.number(),
1121            Self::Toc(b) => b.number(),
1122            Self::DocumentAttribute(b) => b.number(),
1123        }
1124    }
1125
1126    fn id(&'src self) -> Option<&'src str> {
1127        // Three variants override the trait default:
1128        //
1129        // * A `MediaBlock` additionally recognizes a named `id=` _inside_ its macro
1130        //   attribute list (e.g. `image::sunset.jpg[id=sunset-img]`).
1131        //
1132        // * A `TocBlock` likewise recognizes a named `id=` _inside_ its macro attribute
1133        //   list (e.g. `toc::[id=contents]`).
1134        //
1135        // * A `SectionBlock` falls back to its auto-generated (`_slug`) ID when no
1136        //   explicit ID was supplied, so `block.id()` yields the same ID the section is
1137        //   registered and cross-referenced under. Delegating here (rather than
1138        //   applying the trait default) avoids the footgun of `block.id()` silently
1139        //   returning `None` for a section that plainly has an ID.
1140        //
1141        // Every other variant keeps the trait default (explicit anchor or block
1142        // attribute list only).
1143        match self {
1144            Self::Media(b) => b.id(),
1145            Self::Section(b) => b.id(),
1146            Self::Toc(b) => b.id(),
1147            _ => self
1148                .anchor()
1149                .map(|a| a.data())
1150                .or_else(|| self.attrlist().and_then(|attrlist| attrlist.id())),
1151        }
1152    }
1153
1154    fn anchor(&'src self) -> Option<Span<'src>> {
1155        match self {
1156            Self::Simple(b) => b.anchor(),
1157            Self::Media(b) => b.anchor(),
1158            Self::Section(b) => b.anchor(),
1159            Self::List(b) => b.anchor(),
1160            Self::ListItem(b) => b.anchor(),
1161            Self::RawDelimited(b) => b.anchor(),
1162            Self::CompoundDelimited(b) => b.anchor(),
1163            Self::Admonition(b) => b.anchor(),
1164            Self::Quote(b) => b.anchor(),
1165            Self::Table(b) => b.anchor(),
1166            Self::Preamble(b) => b.anchor(),
1167            Self::Break(b) => b.anchor(),
1168            Self::Toc(b) => b.anchor(),
1169            Self::DocumentAttribute(b) => b.anchor(),
1170        }
1171    }
1172
1173    fn anchor_reftext(&'src self) -> Option<Span<'src>> {
1174        match self {
1175            Self::Simple(b) => b.anchor_reftext(),
1176            Self::Media(b) => b.anchor_reftext(),
1177            Self::Section(b) => b.anchor_reftext(),
1178            Self::List(b) => b.anchor_reftext(),
1179            Self::ListItem(b) => b.anchor_reftext(),
1180            Self::RawDelimited(b) => b.anchor_reftext(),
1181            Self::CompoundDelimited(b) => b.anchor_reftext(),
1182            Self::Admonition(b) => b.anchor_reftext(),
1183            Self::Quote(b) => b.anchor_reftext(),
1184            Self::Table(b) => b.anchor_reftext(),
1185            Self::Preamble(b) => b.anchor_reftext(),
1186            Self::Break(b) => b.anchor_reftext(),
1187            Self::Toc(b) => b.anchor_reftext(),
1188            Self::DocumentAttribute(b) => b.anchor_reftext(),
1189        }
1190    }
1191
1192    fn attrlist(&'src self) -> Option<&'src Attrlist<'src>> {
1193        match self {
1194            Self::Simple(b) => b.attrlist(),
1195            Self::Media(b) => b.attrlist(),
1196            Self::Section(b) => b.attrlist(),
1197            Self::List(b) => b.attrlist(),
1198            Self::ListItem(b) => b.attrlist(),
1199            Self::RawDelimited(b) => b.attrlist(),
1200            Self::CompoundDelimited(b) => b.attrlist(),
1201            Self::Admonition(b) => b.attrlist(),
1202            Self::Quote(b) => b.attrlist(),
1203            Self::Table(b) => b.attrlist(),
1204            Self::Preamble(b) => b.attrlist(),
1205            Self::Break(b) => b.attrlist(),
1206            Self::Toc(b) => b.attrlist(),
1207            Self::DocumentAttribute(b) => b.attrlist(),
1208        }
1209    }
1210
1211    fn substitution_group(&self) -> SubstitutionGroup {
1212        match self {
1213            Self::Simple(b) => b.substitution_group(),
1214            Self::Media(b) => b.substitution_group(),
1215            Self::Section(b) => b.substitution_group(),
1216            Self::List(b) => b.substitution_group(),
1217            Self::ListItem(b) => b.substitution_group(),
1218            Self::RawDelimited(b) => b.substitution_group(),
1219            Self::CompoundDelimited(b) => b.substitution_group(),
1220            Self::Admonition(b) => b.substitution_group(),
1221            Self::Quote(b) => b.substitution_group(),
1222            Self::Table(b) => b.substitution_group(),
1223            Self::Preamble(b) => b.substitution_group(),
1224            Self::Break(b) => b.substitution_group(),
1225            Self::Toc(b) => b.substitution_group(),
1226            Self::DocumentAttribute(b) => b.substitution_group(),
1227        }
1228    }
1229}
1230
1231impl<'src> HasSpan<'src> for Block<'src> {
1232    fn span(&self) -> Span<'src> {
1233        match self {
1234            Self::Simple(b) => b.span(),
1235            Self::Media(b) => b.span(),
1236            Self::Section(b) => b.span(),
1237            Self::List(b) => b.span(),
1238            Self::ListItem(b) => b.span(),
1239            Self::RawDelimited(b) => b.span(),
1240            Self::CompoundDelimited(b) => b.span(),
1241            Self::Admonition(b) => b.span(),
1242            Self::Quote(b) => b.span(),
1243            Self::Table(b) => b.span(),
1244            Self::Preamble(b) => b.span(),
1245            Self::Break(b) => b.span(),
1246            Self::Toc(b) => b.span(),
1247            Self::DocumentAttribute(b) => b.span(),
1248        }
1249    }
1250}