asciidoc_parser/blocks/
is_block.rs

1use std::{fmt::Debug, slice::Iter};
2
3use crate::{
4    HasSpan, Span,
5    attributes::Attrlist,
6    blocks::{Block, is_built_in_context},
7    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>: HasSpan<'src> + Clone + Debug + Eq + PartialEq {
25    /// Returns the [`ContentModel`] for this block.
26    fn content_model(&self) -> ContentModel;
27
28    /// Returns the resolved context for this block.
29    ///
30    /// A block’s context is also sometimes referred to as a name, such as an
31    /// example block, a sidebar block, an admonition block, or a section.
32    ///
33    /// Every block has a context. The context is often implied by the syntax,
34    /// but can be declared explicitly in certain cases. The context is what
35    /// distinguishes one kind of block from another. You can think of the
36    /// context as the block’s type.
37    ///
38    /// For that reason, the context is not defined as an enumeration, but
39    /// rather as a string type that is optimized for the case where predefined
40    /// constants are viable.
41    ///
42    /// A block's context can be replaced by a block style that matches a
43    /// built-in context. Unlike [`raw_context()`], that transformation _is_
44    /// performed by this function.
45    ///
46    /// [`raw_context()`]: Self::raw_context
47    fn resolved_context(&'src self) -> CowStr<'src> {
48        if let Some(declared_style) = self.declared_style()
49            && is_built_in_context(declared_style)
50        {
51            return declared_style.into();
52        }
53
54        self.raw_context()
55    }
56
57    /// Returns the raw (uninterpreted) context for this block.
58    ///
59    /// A block’s context is also sometimes referred to as a name, such as an
60    /// example block, a sidebar block, an admonition block, or a section.
61    ///
62    /// Every block has a context. The context is often implied by the syntax,
63    /// but can be declared explicitly in certain cases. The context is what
64    /// distinguishes one kind of block from another. You can think of the
65    /// context as the block’s type.
66    ///
67    /// For that reason, the context is not defined as an enumeration, but
68    /// rather as a string type that is optimized for the case where predefined
69    /// constants are viable.
70    ///
71    /// A block's context can be replaced by a block style that matches a
72    /// built-in context. That transformation is only performed by
73    /// [`resolved_context()`], not this function.
74    ///
75    /// [`resolved_context()`]: Self::resolved_context
76    fn raw_context(&self) -> CowStr<'src>;
77
78    /// Returns the declared (uninterpreted) style for this block.
79    ///
80    /// Above some blocks, you may notice a name at the start of the block
81    /// attribute list (e.g., `[source]` or `[verse]`). The first positional
82    /// (unnamed) attribute in the block attribute list is used to declare the
83    /// block style.
84    ///
85    /// The declared block style is the value the author supplies.
86    ///
87    /// That value is then interpreted and resolved. That interpretation is not
88    /// performed by this function.
89    fn declared_style(&'src self) -> Option<&'src str> {
90        self.attrlist()
91            .and_then(|attrlist| attrlist.nth_attribute(1))
92            .and_then(|attr| attr.block_style())
93    }
94
95    /// Returns an iterator over the nested blocks contained within
96    /// this block.
97    ///
98    /// Many block types do not have nested blocks so the default implementation
99    /// returns an empty iterator.
100    fn nested_blocks(&'src self) -> Iter<'src, Block<'src>> {
101        const NO_BLOCKS: &[Block<'static>] = &[];
102        NO_BLOCKS.iter()
103    }
104
105    /// Returns the ID for this block, if present.
106    ///
107    /// You can assign an ID to a block using the shorthand syntax, the longhand
108    /// syntax, or a legacy block anchor.
109    ///
110    /// In the shorthand syntax, you prefix the name with a hash (`#`) in the
111    /// first position attribute:
112    ///
113    /// ```asciidoc
114    /// [#goals]
115    /// * Goal 1
116    /// * Goal 2
117    /// ```
118    ///
119    /// In the longhand syntax, you use a standard named attribute:
120    ///
121    /// ```asciidoc
122    /// [id=goals]
123    /// * Goal 1
124    /// * Goal 2
125    /// ```
126    ///
127    /// In the legacy block anchor syntax, you surround the name with double
128    /// square brackets:
129    ///
130    /// ```asciidoc
131    /// [[goals]]
132    /// * Goal 1
133    /// * Goal 2
134    /// ```
135    fn id(&'src self) -> Option<&'src str> {
136        self.attrlist().and_then(|attrlist| attrlist.id())
137    }
138
139    /// Returns any role attributes that were found.
140    ///
141    /// You can assign one or more roles to blocks and most inline elements
142    /// using the `role` attribute. The `role` attribute is a [named attribute].
143    /// Even though the attribute name is singular, it may contain multiple
144    /// (space-separated) roles. Roles may also be defined using a shorthand
145    /// (dot-prefixed) syntax.
146    ///
147    /// A role:
148    /// 1. adds additional semantics to an element
149    /// 2. can be used to apply additional styling to a group of elements (e.g.,
150    ///    via a CSS class selector)
151    /// 3. may activate additional behavior if recognized by the converter
152    ///
153    /// **TIP:** The `role` attribute in AsciiDoc always get mapped to the
154    /// `class` attribute in the HTML output. In other words, role names are
155    /// synonymous with HTML class names, thus allowing output elements to be
156    /// identified and styled in CSS using class selectors (e.g.,
157    /// `sidebarblock.role1`).
158    ///
159    /// [named attribute]: https://docs.asciidoctor.org/asciidoc/latest/attributes/positional-and-named-attributes/#named
160    fn roles(&'src self) -> Vec<&'src str> {
161        match self.attrlist() {
162            Some(attrlist) => attrlist.roles(),
163            None => vec![],
164        }
165    }
166
167    /// Returns any option attributes that were found.
168    ///
169    /// The `options` attribute (often abbreviated as `opts`) is a versatile
170    /// [named attribute] that can be assigned one or more values. It can be
171    /// defined globally as document attribute as well as a block attribute on
172    /// an individual block.
173    ///
174    /// There is no strict schema for options. Any options which are not
175    /// recognized are ignored.
176    ///
177    /// You can assign one or more options to a block using the shorthand or
178    /// formal syntax for the options attribute.
179    ///
180    /// # Shorthand options syntax for blocks
181    ///
182    /// To assign an option to a block, prefix the value with a percent sign
183    /// (`%`) in an attribute list. The percent sign implicitly sets the
184    /// `options` attribute.
185    ///
186    /// ## Example 1: Sidebar block with an option assigned using the shorthand dot
187    ///
188    /// ```asciidoc
189    /// [%option]
190    /// ****
191    /// This is a sidebar with an option assigned to it, named option.
192    /// ****
193    /// ```
194    ///
195    /// You can assign multiple options to a block by prest
196    /// fixing each value with
197    /// a percent sign (`%`).
198    ///
199    /// ## Example 2: Sidebar with two options assigned using the shorthand dot
200    /// ```asciidoc
201    /// [%option1%option2]
202    /// ****
203    /// This is a sidebar with two options assigned to it, named option1 and option2.
204    /// ****
205    /// ```
206    ///
207    /// # Formal options syntax for blocks
208    ///
209    /// Explicitly set `options` or `opts`, followed by the equals sign (`=`),
210    /// and then the value in an attribute list.
211    ///
212    /// ## Example 3. Sidebar block with an option assigned using the formal syntax
213    /// ```asciidoc
214    /// [opts=option]
215    /// ****
216    /// This is a sidebar with an option assigned to it, named option.
217    /// ****
218    /// ```
219    ///
220    /// Separate multiple option values with commas (`,`).
221    ///
222    /// ## Example 4. Sidebar with three options assigned using the formal syntax
223    /// ```asciidoc
224    /// [opts="option1,option2"]
225    /// ****
226    /// This is a sidebar with two options assigned to it, option1 and option2.
227    /// ****
228    /// ```
229    ///
230    /// [named attribute]: https://docs.asciidoctor.org/asciidoc/latest/attributes/positional-and-named-attributes/#named
231    fn options(&'src self) -> Vec<&'src str> {
232        match self.attrlist() {
233            Some(attrlist) => attrlist.options(),
234            None => vec![],
235        }
236    }
237
238    /// Returns `true` if this block has the named option.
239    ///
240    /// See [`options()`] for a description of option syntax.
241    ///
242    /// [`options()`]: Self::options
243    fn has_option<N: AsRef<str>>(&'src self, name: N) -> bool {
244        self.attrlist()
245            .is_some_and(|attrlist| attrlist.has_option(name))
246    }
247
248    /// Returns the source text for the title for this block, if present.
249    fn title_source(&'src self) -> Option<Span<'src>>;
250
251    /// Returns the rendered title for this block, if present.
252    fn title(&self) -> Option<&str>;
253
254    /// Returns the anchor for this bloc, if present.
255    fn anchor(&'src self) -> Option<Span<'src>>;
256
257    /// Returns the attribute list for this block, if present.
258    fn attrlist(&'src self) -> Option<&'src Attrlist<'src>>;
259
260    /// Returns the default substitution group that is applied unless you
261    /// customize the substitutions for a particular element.
262    fn substitution_group(&'src self) -> SubstitutionGroup {
263        SubstitutionGroup::Normal
264    }
265}
266
267/// The content model of a block determines what kind of content the block can
268/// have (if any) and how that content is processed.
269#[derive(Clone, Copy, Debug, Eq, PartialEq)]
270pub enum ContentModel {
271    /// A block that may only contain other blocks (e.g., a section)
272    Compound,
273
274    /// A block that's treated as contiguous lines of paragraph text (and
275    /// subject to normal substitutions) (e.g., a paragraph block)
276    Simple,
277
278    /// A block that holds verbatim text (displayed "as is") (and subject to
279    /// verbatim substitutions) (e.g., a listing block)
280    Verbatim,
281
282    /// A block that holds unprocessed content passed directly through to the
283    /// output with no substitutions applied (e.g., a passthrough block)
284    Raw,
285
286    /// A block that has no content (e.g., an image block)
287    Empty,
288
289    /// A special content model reserved for tables that enforces a fixed
290    /// structure
291    Table,
292}