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}