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}