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}