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