cmark_writer/ast/
node.rs

1//! Node definitions for the CommonMark AST.
2
3use super::custom::CustomNode;
4use super::html::HtmlElement;
5use ecow::EcoString;
6use std::boxed::Box;
7
8/// Code block type according to CommonMark specification
9#[derive(Debug, Clone, PartialEq, Default)]
10pub enum CodeBlockType {
11    /// Indented code block - composed of one or more indented chunks, each preceded by four or more spaces
12    Indented,
13    /// Fenced code block - surrounded by backtick or tilde fences
14    #[default]
15    Fenced,
16}
17
18/// Heading type according to CommonMark specification
19#[derive(Debug, Clone, PartialEq, Default)]
20pub enum HeadingType {
21    /// ATX Type - Beginning with #
22    #[default]
23    Atx,
24    /// Setext Type - Underlined or overlined text
25    Setext,
26}
27
28/// Table column alignment options for GFM tables
29#[cfg(feature = "gfm")]
30#[derive(Debug, Clone, PartialEq, Default)]
31pub enum TableAlignment {
32    /// Left alignment (default)
33    #[default]
34    Left,
35    /// Center alignment
36    Center,
37    /// Right alignment
38    Right,
39    /// No specific alignment specified
40    None,
41}
42
43/// Task list item status for GFM task lists
44#[cfg(feature = "gfm")]
45#[derive(Debug, Clone, PartialEq)]
46pub enum TaskListStatus {
47    /// Checked/completed task
48    Checked,
49    /// Unchecked/incomplete task
50    Unchecked,
51}
52
53/// Main node type, representing an element in a CommonMark document
54#[derive(Debug, Clone, PartialEq)]
55pub enum Node {
56    /// Root document node, contains child nodes
57    Document(Vec<Node>),
58
59    // Leaf blocks
60    // Thematic breaks
61    /// Thematic break (horizontal rule)
62    ThematicBreak,
63
64    // ATX headings & Setext headings
65    /// Heading, contains level (1-6) and inline content
66    Heading {
67        /// Heading level, 1-6
68        level: u8,
69        /// Heading content, containing inline elements
70        content: Vec<Node>,
71        /// Heading type (ATX or Setext)
72        heading_type: HeadingType,
73    },
74
75    // Indented code blocks & Fenced code blocks
76    /// Code block, containing optional language identifier and content
77    CodeBlock {
78        /// Optional language identifier (None for indented code blocks, Some for fenced code blocks)
79        language: Option<EcoString>,
80        /// Code content
81        content: EcoString,
82        /// The type of code block (Indented or Fenced)
83        block_type: CodeBlockType,
84    },
85
86    // HTML blocks
87    /// HTML block
88    HtmlBlock(EcoString),
89
90    // Link reference definitions
91    /// Link reference definition
92    LinkReferenceDefinition {
93        /// Link label (used for reference)
94        label: EcoString,
95        /// Link destination URL
96        destination: EcoString,
97        /// Optional link title
98        title: Option<EcoString>,
99    },
100
101    // Paragraphs
102    /// Paragraph node, containing inline elements
103    Paragraph(Vec<Node>),
104
105    // Blank lines - typically handled during parsing, not represented in AST
106
107    // Container blocks
108    // Block quotes
109    /// Block quote, containing any block-level elements
110    BlockQuote(Vec<Node>),
111
112    // & List items and Lists
113    /// Ordered list, containing starting number and list items
114    OrderedList {
115        /// List starting number
116        start: u32,
117        /// List items
118        items: Vec<ListItem>,
119    },
120
121    /// Unordered list, containing list items
122    UnorderedList(Vec<ListItem>),
123
124    /// Table (extension to CommonMark)
125    Table {
126        /// Header cells
127        headers: Vec<Node>,
128        /// Column alignments for the table
129        #[cfg(feature = "gfm")]
130        alignments: Vec<TableAlignment>,
131        /// Table rows, each row containing multiple cells
132        rows: Vec<Vec<Node>>,
133    },
134
135    // Inlines
136    // Code spans
137    /// Inline code
138    InlineCode(EcoString),
139
140    // Emphasis and strong emphasis
141    /// Emphasis (italic)
142    Emphasis(Vec<Node>),
143
144    /// Strong emphasis (bold)
145    Strong(Vec<Node>),
146
147    /// Strikethrough (GFM extension)
148    Strikethrough(Vec<Node>),
149
150    // Links
151    /// Link
152    Link {
153        /// Link URL
154        url: EcoString,
155        /// Optional link title
156        title: Option<EcoString>,
157        /// Link text
158        content: Vec<Node>,
159    },
160
161    /// Reference link
162    ReferenceLink {
163        /// Link reference label
164        label: EcoString,
165        /// Link text content (optional, if empty it's a shortcut reference)
166        content: Vec<Node>,
167    },
168
169    // Images
170    /// Image
171    Image {
172        /// Image URL
173        url: EcoString,
174        /// Optional image title
175        title: Option<EcoString>,
176        /// Alternative text, containing inline elements
177        alt: Vec<Node>,
178    },
179
180    // Autolinks
181    /// Autolink (URI or email wrapped in < and >)
182    Autolink {
183        /// Link URL
184        url: EcoString,
185        /// Whether this is an email autolink
186        is_email: bool,
187    },
188
189    /// GFM Extended Autolink (without angle brackets, automatically detected)
190    ExtendedAutolink(EcoString),
191
192    // Raw HTML
193    /// HTML inline element
194    HtmlElement(HtmlElement),
195
196    // Hard line breaks
197    /// Hard break (two spaces followed by a line break, or backslash followed by a line break)
198    HardBreak,
199
200    // Soft line breaks
201    /// Soft break (single line break)
202    SoftBreak,
203
204    // Textual content
205    /// Plain text
206    Text(EcoString),
207
208    /// Custom node that allows users to implement their own writing behavior
209    Custom(Box<dyn CustomNode>),
210}
211
212impl Default for Node {
213    fn default() -> Self {
214        Node::Document(vec![])
215    }
216}
217
218/// List item type
219#[derive(Debug, Clone, PartialEq)]
220pub enum ListItem {
221    /// Unordered list item
222    Unordered {
223        /// List item content, containing one or more block-level elements
224        content: Vec<Node>,
225    },
226    /// Ordered list item
227    Ordered {
228        /// Optional item number for ordered lists, allowing manual numbering
229        number: Option<u32>,
230        /// List item content, containing one or more block-level elements
231        content: Vec<Node>,
232    },
233    /// Task list item (GFM extension)
234    #[cfg(feature = "gfm")]
235    Task {
236        /// Task completion status
237        status: TaskListStatus,
238        /// List item content, containing one or more block-level elements
239        content: Vec<Node>,
240    },
241}
242
243impl Node {
244    /// Check if a node is a block-level node
245    pub fn is_block(&self) -> bool {
246        matches!(
247            self,
248            Node::Document(_)
249                // Leaf blocks
250                | Node::ThematicBreak
251                | Node::Heading { .. }
252                | Node::CodeBlock { .. }
253                | Node::HtmlBlock(_)
254                | Node::LinkReferenceDefinition { .. }
255                | Node::Paragraph(_)
256                // Container blocks
257                | Node::BlockQuote(_)
258                | Node::OrderedList { .. }
259                | Node::UnorderedList(_)
260                | Node::Table { .. }
261
262                | Node::Custom(_)
263        )
264    }
265
266    /// Check if a node is an inline node
267    pub fn is_inline(&self) -> bool {
268        matches!(
269            self,
270            // Inlines
271            // Code spans
272            Node::InlineCode(_)
273                // Emphasis and strong emphasis
274                | Node::Emphasis(_)
275                | Node::Strong(_)
276                | Node::Strikethrough(_)
277                // Links
278                | Node::Link { .. }
279                | Node::ReferenceLink { .. }
280                // Images
281                | Node::Image { .. }
282                // Autolinks
283                | Node::Autolink { .. }
284                | Node::ExtendedAutolink(_)
285                // Raw HTML
286                | Node::HtmlElement(_)
287                // Hard line breaks
288                | Node::HardBreak
289                // Soft line breaks
290                | Node::SoftBreak
291                // Textual content
292                | Node::Text(_)
293
294                | Node::Custom(_)
295        )
296    }
297    /// Create a heading node
298    ///
299    /// # Arguments
300    /// * `level` - Heading level (1-6)
301    /// * `content` - Heading content
302    ///
303    /// # Returns
304    /// A new heading node, default ATX type
305    pub fn heading(level: u8, content: Vec<Node>) -> Self {
306        Node::Heading {
307            level,
308            content,
309            heading_type: HeadingType::default(),
310        }
311    }
312
313    /// Create a code block node
314    ///
315    /// # Arguments
316    /// * `language` - Optional language identifier
317    /// * `content` - Code content
318    ///
319    /// # Returns
320    /// A new code block node, default Fenced type
321    pub fn code_block(language: Option<EcoString>, content: EcoString) -> Self {
322        Node::CodeBlock {
323            language,
324            content,
325            block_type: CodeBlockType::default(),
326        }
327    }
328
329    /// Create a strikethrough node
330    ///
331    /// # Arguments
332    /// * `content` - Content to be struck through
333    ///
334    /// # Returns
335    /// A new strikethrough node
336    pub fn strikethrough(content: Vec<Node>) -> Self {
337        Node::Strikethrough(content)
338    }
339
340    /// Create a task list item
341    ///
342    /// # Arguments
343    /// * `status` - Task completion status
344    /// * `content` - Task content
345    ///
346    /// # Returns
347    /// A new task list item
348    #[cfg(feature = "gfm")]
349    pub fn task_list_item(status: TaskListStatus, content: Vec<Node>) -> Self {
350        Node::UnorderedList(vec![ListItem::Task { status, content }])
351    }
352
353    /// Create a table with alignment
354    ///
355    /// # Arguments
356    /// * `headers` - Table header cells
357    /// * `alignments` - Column alignments
358    /// * `rows` - Table rows
359    ///
360    /// # Returns
361    /// A new table node with alignment information
362    #[cfg(feature = "gfm")]
363    pub fn table_with_alignment(
364        headers: Vec<Node>,
365        alignments: Vec<TableAlignment>,
366        rows: Vec<Vec<Node>>,
367    ) -> Self {
368        Node::Table {
369            headers,
370            alignments,
371            rows,
372        }
373    }
374    /// Check if a custom node is of a specific type, and return a reference to that type
375    pub fn as_custom_type<T: CustomNode + 'static>(&self) -> Option<&T> {
376        if let Node::Custom(node) = self {
377            node.as_any().downcast_ref::<T>()
378        } else {
379            None
380        }
381    }
382
383    /// Check if a node is a custom node of a specific type
384    pub fn is_custom_type<T: CustomNode + 'static>(&self) -> bool {
385        self.as_custom_type::<T>().is_some()
386    }
387}