Skip to main content

core_markdown/
renderer.rs

1// Released under the MIT License.
2// Copyright, 2026, by Samuel Williams.
3
4//! Traits for rendering Markdown AST nodes.
5mod html_renderer;
6mod markdown_renderer;
7
8pub use html_renderer::HtmlRenderer;
9pub use markdown_renderer::MarkdownRenderer;
10
11use crate::mdast::Node;
12use alloc::string::String;
13
14/// A renderer for an AST node tree.
15///
16/// Implement [`render_node`][Self::render_node] to decide how each node is
17/// emitted. Call [`render_children`][Self::render_children] for transparent
18/// containers or when a node's children should be rendered recursively.
19/// Renderers choose their output format and escaping rules.
20///
21/// This is separate from [`crate::to_html`], which parses Markdown source and
22/// renders the parser's events with the built-in HTML compiler.
23///
24/// # Example
25///
26/// ```
27/// use core_markdown::{mdast::Node, Renderer};
28///
29/// struct PlainText;
30///
31/// impl Renderer for PlainText {
32///     fn render_node(&mut self, node: &Node) -> String {
33///         match node {
34///             Node::Text(text) => text.value.clone(),
35///             Node::InlineCode(code) => code.value.clone(),
36///             Node::Code(code) => code.value.clone(),
37///             Node::Break(_) => "\n".into(),
38///             Node::Image(image) => image.alt.clone(),
39///             Node::ImageReference(image) => image.alt.clone(),
40///             Node::Definition(_) | Node::FootnoteReference(_) | Node::ThematicBreak(_) => {
41///                 String::new()
42///             }
43///             _ => self.render_children(node),
44///         }
45///     }
46/// }
47///
48/// let node = Node::Fragment(core_markdown::mdast::Fragment {
49///     children: vec![Node::Text(core_markdown::mdast::Text {
50///         value: "hello".into(),
51///         position: None,
52///     })],
53/// });
54/// let mut renderer = PlainText;
55/// let plain_text = node.render_with(&mut renderer);
56/// assert_eq!(plain_text, "hello");
57/// ```
58pub trait Renderer {
59    /// Render one AST node into this renderer's output.
60    fn render_node(&mut self, node: &Node) -> String;
61
62    /// Render a node and its descendants.
63    fn render(&mut self, node: &Node) -> String {
64        self.render_node(node)
65    }
66
67    /// Render the direct children of a parent node in order.
68    fn render_children(&mut self, node: &Node) -> String {
69        let mut output = String::new();
70
71        if let Some(children) = node.children() {
72            for child in children {
73                output.push_str(&self.render(child));
74            }
75        }
76
77        output
78    }
79}
80
81#[cfg(test)]
82mod tests {
83    use super::Renderer;
84    use crate::mdast::{Node, Root, Text};
85    use alloc::string::String;
86    use alloc::vec;
87
88    struct PlainTextRenderer;
89
90    impl Renderer for PlainTextRenderer {
91        fn render_node(&mut self, node: &Node) -> String {
92            match node {
93                Node::Text(text) => text.value.clone(),
94                _ => self.render_children(node),
95            }
96        }
97    }
98
99    #[test]
100    fn renders_nodes_and_their_children_by_default() {
101        let node = Node::Root(Root {
102            position: None,
103            children: vec![
104                Node::Text(Text {
105                    value: "one".into(),
106                    position: None,
107                }),
108                Node::Text(Text {
109                    value: "two".into(),
110                    position: None,
111                }),
112            ],
113        });
114        let mut renderer = PlainTextRenderer;
115
116        assert_eq!(renderer.render(&node), "onetwo");
117        assert_eq!(
118            renderer.render_children(&Node::ThematicBreak(crate::mdast::ThematicBreak {
119                position: None,
120            })),
121            ""
122        );
123    }
124}