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