typlite/writer/
text.rs

1//! Text writer implementation - produces plain text output
2
3use cmark_writer::ast::Node;
4use ecow::EcoString;
5
6use crate::Result;
7use crate::common::{ExternalFrameNode, FigureNode, FormatWriter};
8
9/// Text writer implementation
10#[derive(Default)]
11pub struct TextWriter {}
12
13impl TextWriter {
14    pub fn new() -> Self {
15        Self {}
16    }
17
18    fn write_node(node: &Node, output: &mut EcoString) -> Result<()> {
19        match node {
20            Node::Document(blocks) => {
21                for block in blocks {
22                    Self::write_node(block, output)?;
23                    output.push_str("\n");
24                }
25            }
26            Node::Paragraph(inlines) => {
27                for inline in inlines {
28                    Self::write_node(inline, output)?;
29                }
30                output.push_str("\n");
31            }
32            Node::Heading {
33                level: _,
34                content,
35                heading_type: _,
36            } => {
37                for inline in content {
38                    Self::write_node(inline, output)?;
39                }
40                output.push_str("\n");
41            }
42            Node::BlockQuote(content) => {
43                for block in content {
44                    Self::write_node(block, output)?;
45                }
46            }
47            Node::CodeBlock {
48                language: _,
49                content,
50                block_type: _,
51            } => {
52                output.push_str(content);
53                output.push_str("\n\n");
54            }
55            Node::OrderedList { start: _, items } => {
56                for item in items.iter() {
57                    match item {
58                        cmark_writer::ast::ListItem::Ordered { content, .. }
59                        | cmark_writer::ast::ListItem::Unordered { content } => {
60                            for block in content {
61                                Self::write_node(block, output)?;
62                            }
63                        }
64                        _ => {}
65                    }
66                }
67            }
68            Node::UnorderedList(items) => {
69                for item in items {
70                    match item {
71                        cmark_writer::ast::ListItem::Ordered { content, .. }
72                        | cmark_writer::ast::ListItem::Unordered { content } => {
73                            for block in content {
74                                Self::write_node(block, output)?;
75                            }
76                        }
77                        _ => {}
78                    }
79                }
80            }
81            Node::Table {
82                headers,
83                rows,
84                alignments: _,
85            } => {
86                // Write headers
87                for header in headers {
88                    Self::write_node(header, output)?;
89                    output.push(' ');
90                }
91                output.push_str("\n");
92
93                // Write rows
94                for row in rows {
95                    for cell in row {
96                        Self::write_node(cell, output)?;
97                        output.push(' ');
98                    }
99                    output.push_str("\n");
100                }
101                output.push_str("\n");
102            }
103            Node::Text(text) => {
104                output.push_str(text);
105            }
106            Node::Emphasis(content) | Node::Strong(content) | Node::Strikethrough(content) => {
107                for inline in content {
108                    Self::write_node(inline, output)?;
109                }
110            }
111            Node::Link {
112                url: _,
113                title: _,
114                content,
115            } => {
116                for inline in content {
117                    Self::write_node(inline, output)?;
118                }
119            }
120            Node::Image {
121                url: _,
122                title: _,
123                alt,
124            } => {
125                if !alt.is_empty() {
126                    for inline in alt {
127                        Self::write_node(inline, output)?;
128                    }
129                }
130            }
131            Node::InlineCode(code) => {
132                output.push_str(code);
133            }
134            Node::HardBreak => {
135                output.push_str("\n");
136            }
137            Node::SoftBreak => {
138                output.push(' ');
139            }
140            Node::ThematicBreak => {
141                output.push_str("\n");
142            }
143            Node::HtmlElement(element) => {
144                for child in &element.children {
145                    Self::write_node(child, output)?;
146                }
147            }
148            node if node.is_custom_type::<FigureNode>() => {
149                if let Some(figure_node) = node.as_custom_type::<FigureNode>() {
150                    Self::write_node(&figure_node.body, output)?;
151                    if !figure_node.caption.is_empty() {
152                        output.push_str("\n");
153                        output.push_str(&figure_node.caption);
154                    }
155                }
156            }
157            node if node.is_custom_type::<ExternalFrameNode>() => {
158                if let Some(external_frame) = node.as_custom_type::<ExternalFrameNode>()
159                    && !external_frame.alt_text.is_empty()
160                {
161                    output.push_str(&external_frame.alt_text);
162                }
163            }
164            node if node.is_custom_type::<crate::common::HighlightNode>() => {
165                if let Some(highlight) = node.as_custom_type::<crate::common::HighlightNode>() {
166                    for child in &highlight.content {
167                        Self::write_node(child, output)?;
168                    }
169                }
170            }
171            _ => {}
172        }
173        Ok(())
174    }
175}
176
177impl FormatWriter for TextWriter {
178    fn write_eco(&mut self, document: &Node, output: &mut EcoString) -> Result<()> {
179        Self::write_node(document, output)
180    }
181
182    fn write_vec(&mut self, document: &Node) -> Result<Vec<u8>> {
183        let mut output = EcoString::new();
184        Self::write_node(document, &mut output)?;
185        Ok(output.as_str().as_bytes().to_vec())
186    }
187}