cmark_writer/ast/
node.rs

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