Skip to main content

asciidoc_parser/blocks/
is_block.rs

1use std::{fmt::Debug, slice::Iter};
2
3use crate::{
4    Span,
5    attributes::Attrlist,
6    blocks::{Block, is_built_in_context},
7    content::{Content, SubstitutionGroup},
8    strings::CowStr,
9};
10
11/// **Block elements** form the main structure of an AsciiDoc document, starting
12/// with the document itself.
13///
14/// A block element (aka **block**) is a discrete, line-oriented chunk of
15/// content in an AsciiDoc document. Once parsed, that chunk of content becomes
16/// a block element in the parsed document model. Certain blocks may contain
17/// other blocks, so we say that blocks can be nested. The converter visits each
18/// block in turn, in document order, converting it to a corresponding chunk of
19/// output.
20///
21/// This trait implements many of the same core methods as the [`Block`] enum
22/// but provides a mechanism for third-party code to extend the behavior of
23/// blocks.
24pub trait IsBlock<'src>: Debug + Eq + PartialEq {
25    /// Returns the [`ContentModel`] for this block.
26    fn content_model(&self) -> ContentModel;
27
28    /// Returns the rendered content for this block, if any.
29    ///
30    /// Some blocks (especially compound blocks) do not directly contain
31    /// content. In such cases, this function will return `None`.
32    ///
33    /// This content will contain the text _after_ substitutions have been
34    /// applied.
35    fn rendered_content(&'src self) -> Option<&'src str> {
36        None
37    }
38
39    /// Returns the resolved context for this block.
40    ///
41    /// A block’s context is also sometimes referred to as a name, such as an
42    /// example block, a sidebar block, an admonition block, or a section.
43    ///
44    /// Every block has a context. The context is often implied by the syntax,
45    /// but can be declared explicitly in certain cases. The context is what
46    /// distinguishes one kind of block from another. You can think of the
47    /// context as the block’s type.
48    ///
49    /// For that reason, the context is not defined as an enumeration, but
50    /// rather as a string type that is optimized for the case where predefined
51    /// constants are viable.
52    ///
53    /// A block's context can be replaced by a block style that matches a
54    /// built-in context. Unlike [`raw_context()`], that transformation _is_
55    /// performed by this function.
56    ///
57    /// [`raw_context()`]: Self::raw_context
58    fn resolved_context(&'src self) -> CowStr<'src> {
59        if let Some(declared_style) = self.declared_style() {
60            if is_built_in_context(declared_style) {
61                return declared_style.into();
62            }
63
64            // The `source` style is not itself a context; it specializes the
65            // `listing` context (a source block is a listing block with syntax
66            // highlighting). A `source` style therefore resolves the context to
67            // `listing` — for example, `[source]` placed over a `....` literal
68            // block makes it a listing block.
69            if declared_style == "source" {
70                return "listing".into();
71            }
72        }
73
74        self.raw_context()
75    }
76
77    /// Returns the raw (uninterpreted) context for this block.
78    ///
79    /// A block’s context is also sometimes referred to as a name, such as an
80    /// example block, a sidebar block, an admonition block, or a section.
81    ///
82    /// Every block has a context. The context is often implied by the syntax,
83    /// but can be declared explicitly in certain cases. The context is what
84    /// distinguishes one kind of block from another. You can think of the
85    /// context as the block’s type.
86    ///
87    /// For that reason, the context is not defined as an enumeration, but
88    /// rather as a string type that is optimized for the case where predefined
89    /// constants are viable.
90    ///
91    /// A block's context can be replaced by a block style that matches a
92    /// built-in context. That transformation is only performed by
93    /// [`resolved_context()`], not this function.
94    ///
95    /// [`resolved_context()`]: Self::resolved_context
96    fn raw_context(&self) -> CowStr<'src>;
97
98    /// Returns the declared (uninterpreted) style for this block.
99    ///
100    /// Above some blocks, you may notice a name at the start of the block
101    /// attribute list (e.g., `[source]` or `[verse]`). The first positional
102    /// (unnamed) attribute in the block attribute list is used to declare the
103    /// block style.
104    ///
105    /// The declared block style is the value the author supplies.
106    ///
107    /// That value is then interpreted and resolved. That interpretation is not
108    /// performed by this function.
109    fn declared_style(&'src self) -> Option<&'src str> {
110        self.attrlist()
111            .and_then(|attrlist| attrlist.nth_attribute(1))
112            .and_then(|attr| attr.block_style())
113    }
114
115    /// Returns an iterator over the nested blocks contained within
116    /// this block.
117    ///
118    /// Many block types do not have nested blocks so the default implementation
119    /// returns an empty iterator.
120    fn nested_blocks(&'src self) -> Iter<'src, Block<'src>> {
121        const NO_BLOCKS: &[Block<'static>] = &[];
122        NO_BLOCKS.iter()
123    }
124
125    /// Returns a mutable slice of the nested blocks contained within this
126    /// block.
127    ///
128    /// This is the mutable counterpart of [`nested_blocks()`]. The default
129    /// returns an empty slice; container blocks override it to expose their
130    /// children for in-place passes such as cross-reference resolution.
131    ///
132    /// [`nested_blocks()`]: Self::nested_blocks
133    fn nested_blocks_mut(&mut self) -> &mut [Block<'src>] {
134        &mut []
135    }
136
137    /// Returns a mutable reference to this block's own resolvable content — its
138    /// body, section title, or description-list term — if any.
139    ///
140    /// The default returns `None`; content-bearing blocks override it. This is
141    /// used by in-place passes such as cross-reference resolution.
142    fn content_mut(&mut self) -> Option<&mut Content<'src>> {
143        None
144    }
145
146    /// Returns the ID for this block, if present.
147    ///
148    /// You can assign an ID to a block using the shorthand syntax, the longhand
149    /// syntax, or a legacy block anchor.
150    ///
151    /// In the shorthand syntax, you prefix the name with a hash (`#`) in the
152    /// first position attribute:
153    ///
154    /// ```asciidoc
155    /// [#goals]
156    /// * Goal 1
157    /// * Goal 2
158    /// ```
159    ///
160    /// In the longhand syntax, you use a standard named attribute:
161    ///
162    /// ```asciidoc
163    /// [id=goals]
164    /// * Goal 1
165    /// * Goal 2
166    /// ```
167    ///
168    /// In the legacy block anchor syntax, you surround the name with double
169    /// square brackets:
170    ///
171    /// ```asciidoc
172    /// [[goals]]
173    /// * Goal 1
174    /// * Goal 2
175    /// ```
176    fn id(&'src self) -> Option<&'src str> {
177        self.anchor()
178            .map(|a| a.data())
179            .or_else(|| self.attrlist().and_then(|attrlist| attrlist.id()))
180    }
181
182    /// Returns any role attributes that were found.
183    ///
184    /// You can assign one or more roles to blocks and most inline elements
185    /// using the `role` attribute. The `role` attribute is a [named attribute].
186    /// Even though the attribute name is singular, it may contain multiple
187    /// (space-separated) roles. Roles may also be defined using a shorthand
188    /// (dot-prefixed) syntax.
189    ///
190    /// A role:
191    /// 1. adds additional semantics to an element
192    /// 2. can be used to apply additional styling to a group of elements (e.g.,
193    ///    via a CSS class selector)
194    /// 3. may activate additional behavior if recognized by the converter
195    ///
196    /// **TIP:** The `role` attribute in AsciiDoc always get mapped to the
197    /// `class` attribute in the HTML output. In other words, role names are
198    /// synonymous with HTML class names, thus allowing output elements to be
199    /// identified and styled in CSS using class selectors (e.g.,
200    /// `sidebarblock.role1`).
201    ///
202    /// [named attribute]: https://docs.asciidoctor.org/asciidoc/latest/attributes/positional-and-named-attributes/#named
203    fn roles(&'src self) -> Vec<&'src str> {
204        match self.attrlist() {
205            Some(attrlist) => attrlist.roles(),
206            None => vec![],
207        }
208    }
209
210    /// Returns any option attributes that were found.
211    ///
212    /// The `options` attribute (often abbreviated as `opts`) is a versatile
213    /// [named attribute] that can be assigned one or more values. It can be
214    /// defined globally as document attribute as well as a block attribute on
215    /// an individual block.
216    ///
217    /// There is no strict schema for options. Any options which are not
218    /// recognized are ignored.
219    ///
220    /// You can assign one or more options to a block using the shorthand or
221    /// formal syntax for the options attribute.
222    ///
223    /// # Shorthand options syntax for blocks
224    ///
225    /// To assign an option to a block, prefix the value with a percent sign
226    /// (`%`) in an attribute list. The percent sign implicitly sets the
227    /// `options` attribute.
228    ///
229    /// ## Example 1: Sidebar block with an option assigned using the shorthand dot
230    ///
231    /// ```asciidoc
232    /// [%option]
233    /// ****
234    /// This is a sidebar with an option assigned to it, named option.
235    /// ****
236    /// ```
237    ///
238    /// You can assign multiple options to a block by prest
239    /// fixing each value with
240    /// a percent sign (`%`).
241    ///
242    /// ## Example 2: Sidebar with two options assigned using the shorthand dot
243    /// ```asciidoc
244    /// [%option1%option2]
245    /// ****
246    /// This is a sidebar with two options assigned to it, named option1 and option2.
247    /// ****
248    /// ```
249    ///
250    /// # Formal options syntax for blocks
251    ///
252    /// Explicitly set `options` or `opts`, followed by the equals sign (`=`),
253    /// and then the value in an attribute list.
254    ///
255    /// ## Example 3. Sidebar block with an option assigned using the formal syntax
256    /// ```asciidoc
257    /// [opts=option]
258    /// ****
259    /// This is a sidebar with an option assigned to it, named option.
260    /// ****
261    /// ```
262    ///
263    /// Separate multiple option values with commas (`,`).
264    ///
265    /// ## Example 4. Sidebar with three options assigned using the formal syntax
266    /// ```asciidoc
267    /// [opts="option1,option2"]
268    /// ****
269    /// This is a sidebar with two options assigned to it, option1 and option2.
270    /// ****
271    /// ```
272    ///
273    /// [named attribute]: https://docs.asciidoctor.org/asciidoc/latest/attributes/positional-and-named-attributes/#named
274    fn options(&'src self) -> Vec<&'src str> {
275        match self.attrlist() {
276            Some(attrlist) => attrlist.options(),
277            None => vec![],
278        }
279    }
280
281    /// Returns `true` if this block has the named option.
282    ///
283    /// See [`options()`] for a description of option syntax.
284    ///
285    /// [`options()`]: Self::options
286    fn has_option<N: AsRef<str>>(&'src self, name: N) -> bool {
287        self.attrlist()
288            .is_some_and(|attrlist| attrlist.has_option(name))
289    }
290
291    /// Returns the source text for the title for this block, if present.
292    fn title_source(&'src self) -> Option<Span<'src>>;
293
294    /// Returns the rendered title for this block, if present.
295    fn title(&self) -> Option<&str>;
296
297    /// Returns the caption prefix for this block, if it has one.
298    ///
299    /// A *captionable* block (e.g. an example block or a table) that has a
300    /// [title](Self::title) is given a caption: a label and an automatically
301    /// incremented number that a converter prepends to the title (e.g.
302    /// `"Example 1. "`, including the trailing separator and space). The prefix
303    /// combines a label drawn from a document attribute (e.g.
304    /// `example-caption`) with the block's [number](Self::number).
305    ///
306    /// The caption is absent when the block has no title, when its caption
307    /// attribute has been unset, or when an explicitly empty caption was
308    /// supplied. An explicit `caption` attribute overrides the prefix with a
309    /// verbatim, unnumbered label. The default implementation returns `None`.
310    fn caption(&self) -> Option<&str> {
311        None
312    }
313
314    /// Returns the automatically assigned number for this block, if it has one.
315    ///
316    /// Captionable blocks are numbered, per context, in document order as they
317    /// finish parsing (so a nested captioned block is numbered before its
318    /// container). The number is the bare counter value that appears in the
319    /// block's [caption](Self::caption) prefix. It is absent when the block is
320    /// not captioned, or when its caption comes from an explicit (unnumbered)
321    /// `caption` attribute. The default implementation returns `None`.
322    fn number(&self) -> Option<usize> {
323        None
324    }
325
326    /// Returns the anchor for this block, if present.
327    fn anchor(&'src self) -> Option<Span<'src>>;
328
329    /// Returns the reference text for this block's anchor, if present.
330    fn anchor_reftext(&'src self) -> Option<Span<'src>>;
331
332    /// Returns the attribute list for this block, if present.
333    fn attrlist(&'src self) -> Option<&'src Attrlist<'src>>;
334
335    /// Returns the default substitution group that is applied unless you
336    /// customize the substitutions for a particular element.
337    fn substitution_group(&'src self) -> SubstitutionGroup {
338        SubstitutionGroup::Normal
339    }
340}
341
342/// The content model of a block determines what kind of content the block can
343/// have (if any) and how that content is processed.
344#[derive(Clone, Copy, Eq, PartialEq)]
345pub enum ContentModel {
346    /// A block that may only contain other blocks (e.g., a section)
347    Compound,
348
349    /// A block that's treated as contiguous lines of paragraph text (and
350    /// subject to normal substitutions) (e.g., a paragraph block)
351    Simple,
352
353    /// A block that holds verbatim text (displayed "as is") (and subject to
354    /// verbatim substitutions) (e.g., a listing block)
355    Verbatim,
356
357    /// A block that holds unprocessed content passed directly through to the
358    /// output with no substitutions applied (e.g., a passthrough block)
359    Raw,
360
361    /// A block that has no content (e.g., an image block)
362    Empty,
363
364    /// A special content model reserved for tables that enforces a fixed
365    /// structure
366    Table,
367}
368
369impl std::fmt::Debug for ContentModel {
370    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
371        match self {
372            ContentModel::Compound => write!(f, "ContentModel::Compound"),
373            ContentModel::Simple => write!(f, "ContentModel::Simple"),
374            ContentModel::Verbatim => write!(f, "ContentModel::Verbatim"),
375            ContentModel::Raw => write!(f, "ContentModel::Raw"),
376            ContentModel::Empty => write!(f, "ContentModel::Empty"),
377            ContentModel::Table => write!(f, "ContentModel::Table"),
378        }
379    }
380}
381
382#[cfg(test)]
383mod tests {
384    #![allow(clippy::unwrap_used)]
385
386    mod content_model {
387        mod impl_debug {
388            use crate::blocks::ContentModel;
389
390            #[test]
391            fn compound() {
392                let content_model = ContentModel::Compound;
393                let debug_output = format!("{:?}", content_model);
394                assert_eq!(debug_output, "ContentModel::Compound");
395            }
396
397            #[test]
398            fn simple() {
399                let content_model = ContentModel::Simple;
400                let debug_output = format!("{:?}", content_model);
401                assert_eq!(debug_output, "ContentModel::Simple");
402            }
403
404            #[test]
405            fn verbatim() {
406                let content_model = ContentModel::Verbatim;
407                let debug_output = format!("{:?}", content_model);
408                assert_eq!(debug_output, "ContentModel::Verbatim");
409            }
410
411            #[test]
412            fn raw() {
413                let content_model = ContentModel::Raw;
414                let debug_output = format!("{:?}", content_model);
415                assert_eq!(debug_output, "ContentModel::Raw");
416            }
417
418            #[test]
419            fn empty() {
420                let content_model = ContentModel::Empty;
421                let debug_output = format!("{:?}", content_model);
422                assert_eq!(debug_output, "ContentModel::Empty");
423            }
424
425            #[test]
426            fn table() {
427                let content_model = ContentModel::Table;
428                let debug_output = format!("{:?}", content_model);
429                assert_eq!(debug_output, "ContentModel::Table");
430            }
431        }
432    }
433}