markdown.rs

  1use crate::html_element::HtmlElement;
  2use crate::markdown_writer::{HandleTag, HandlerOutcome, MarkdownWriter, StartTagOutcome};
  3
  4pub struct ParagraphHandler;
  5
  6impl HandleTag for ParagraphHandler {
  7    fn should_handle(&self, _tag: &str) -> bool {
  8        true
  9    }
 10
 11    fn handle_tag_start(
 12        &mut self,
 13        tag: &HtmlElement,
 14        writer: &mut MarkdownWriter,
 15    ) -> StartTagOutcome {
 16        if tag.is_inline() && writer.is_inside("p") {
 17            if let Some(parent) = writer.current_element_stack().iter().last() {
 18                if !parent.is_inline() {
 19                    if !(writer.markdown.ends_with(' ') || writer.markdown.ends_with('\n')) {
 20                        writer.push_str(" ");
 21                    }
 22                }
 23            }
 24        }
 25
 26        match tag.tag.as_str() {
 27            "p" => writer.push_blank_line(),
 28            _ => {}
 29        }
 30
 31        StartTagOutcome::Continue
 32    }
 33}
 34
 35pub struct HeadingHandler;
 36
 37impl HandleTag for HeadingHandler {
 38    fn should_handle(&self, tag: &str) -> bool {
 39        match tag {
 40            "h1" | "h2" | "h3" | "h4" | "h5" | "h6" => true,
 41            _ => false,
 42        }
 43    }
 44
 45    fn handle_tag_start(
 46        &mut self,
 47        tag: &HtmlElement,
 48        writer: &mut MarkdownWriter,
 49    ) -> StartTagOutcome {
 50        match tag.tag.as_str() {
 51            "h1" => writer.push_str("\n\n# "),
 52            "h2" => writer.push_str("\n\n## "),
 53            "h3" => writer.push_str("\n\n### "),
 54            "h4" => writer.push_str("\n\n#### "),
 55            "h5" => writer.push_str("\n\n##### "),
 56            "h6" => writer.push_str("\n\n###### "),
 57            _ => {}
 58        }
 59
 60        StartTagOutcome::Continue
 61    }
 62
 63    fn handle_tag_end(&mut self, tag: &HtmlElement, writer: &mut MarkdownWriter) {
 64        match tag.tag.as_str() {
 65            "h1" | "h2" | "h3" | "h4" | "h5" | "h6" => writer.push_blank_line(),
 66            _ => {}
 67        }
 68    }
 69}
 70
 71pub struct ListHandler;
 72
 73impl HandleTag for ListHandler {
 74    fn should_handle(&self, tag: &str) -> bool {
 75        match tag {
 76            "ul" | "ol" | "li" => true,
 77            _ => false,
 78        }
 79    }
 80
 81    fn handle_tag_start(
 82        &mut self,
 83        tag: &HtmlElement,
 84        writer: &mut MarkdownWriter,
 85    ) -> StartTagOutcome {
 86        match tag.tag.as_str() {
 87            "ul" | "ol" => writer.push_newline(),
 88            "li" => writer.push_str("- "),
 89            _ => {}
 90        }
 91
 92        StartTagOutcome::Continue
 93    }
 94
 95    fn handle_tag_end(&mut self, tag: &HtmlElement, writer: &mut MarkdownWriter) {
 96        match tag.tag.as_str() {
 97            "ul" | "ol" => writer.push_newline(),
 98            "li" => writer.push_newline(),
 99            _ => {}
100        }
101    }
102}
103
104pub struct TableHandler {
105    /// The number of columns in the current `<table>`.
106    current_table_columns: usize,
107    is_first_th: bool,
108    is_first_td: bool,
109}
110
111impl TableHandler {
112    pub fn new() -> Self {
113        Self {
114            current_table_columns: 0,
115            is_first_th: true,
116            is_first_td: true,
117        }
118    }
119}
120
121impl HandleTag for TableHandler {
122    fn should_handle(&self, tag: &str) -> bool {
123        match tag {
124            "table" | "thead" | "tbody" | "tr" | "th" | "td" => true,
125            _ => false,
126        }
127    }
128
129    fn handle_tag_start(
130        &mut self,
131        tag: &HtmlElement,
132        writer: &mut MarkdownWriter,
133    ) -> StartTagOutcome {
134        match tag.tag.as_str() {
135            "thead" => writer.push_blank_line(),
136            "tr" => writer.push_newline(),
137            "th" => {
138                self.current_table_columns += 1;
139                if self.is_first_th {
140                    self.is_first_th = false;
141                } else {
142                    writer.push_str(" ");
143                }
144                writer.push_str("| ");
145            }
146            "td" => {
147                if self.is_first_td {
148                    self.is_first_td = false;
149                } else {
150                    writer.push_str(" ");
151                }
152                writer.push_str("| ");
153            }
154            _ => {}
155        }
156
157        StartTagOutcome::Continue
158    }
159
160    fn handle_tag_end(&mut self, tag: &HtmlElement, writer: &mut MarkdownWriter) {
161        match tag.tag.as_str() {
162            "thead" => {
163                writer.push_newline();
164                for ix in 0..self.current_table_columns {
165                    if ix > 0 {
166                        writer.push_str(" ");
167                    }
168                    writer.push_str("| ---");
169                }
170                writer.push_str(" |");
171                self.is_first_th = true;
172            }
173            "tr" => {
174                writer.push_str(" |");
175                self.is_first_td = true;
176            }
177            "table" => {
178                self.current_table_columns = 0;
179            }
180            _ => {}
181        }
182    }
183}
184
185pub struct StyledTextHandler;
186
187impl HandleTag for StyledTextHandler {
188    fn should_handle(&self, tag: &str) -> bool {
189        match tag {
190            "strong" | "em" => true,
191            _ => false,
192        }
193    }
194
195    fn handle_tag_start(
196        &mut self,
197        tag: &HtmlElement,
198        writer: &mut MarkdownWriter,
199    ) -> StartTagOutcome {
200        match tag.tag.as_str() {
201            "strong" => writer.push_str("**"),
202            "em" => writer.push_str("_"),
203            _ => {}
204        }
205
206        StartTagOutcome::Continue
207    }
208
209    fn handle_tag_end(&mut self, tag: &HtmlElement, writer: &mut MarkdownWriter) {
210        match tag.tag.as_str() {
211            "strong" => writer.push_str("**"),
212            "em" => writer.push_str("_"),
213            _ => {}
214        }
215    }
216}
217
218pub struct CodeHandler;
219
220impl HandleTag for CodeHandler {
221    fn should_handle(&self, tag: &str) -> bool {
222        match tag {
223            "pre" | "code" => true,
224            _ => false,
225        }
226    }
227
228    fn handle_tag_start(
229        &mut self,
230        tag: &HtmlElement,
231        writer: &mut MarkdownWriter,
232    ) -> StartTagOutcome {
233        match tag.tag.as_str() {
234            "code" => {
235                if !writer.is_inside("pre") {
236                    writer.push_str("`");
237                }
238            }
239            "pre" => writer.push_str("\n\n```\n"),
240            _ => {}
241        }
242
243        StartTagOutcome::Continue
244    }
245
246    fn handle_tag_end(&mut self, tag: &HtmlElement, writer: &mut MarkdownWriter) {
247        match tag.tag.as_str() {
248            "code" => {
249                if !writer.is_inside("pre") {
250                    writer.push_str("`");
251                }
252            }
253            "pre" => writer.push_str("\n```\n"),
254            _ => {}
255        }
256    }
257
258    fn handle_text(&mut self, text: &str, writer: &mut MarkdownWriter) -> HandlerOutcome {
259        if writer.is_inside("pre") {
260            writer.push_str(&text);
261            return HandlerOutcome::Handled;
262        }
263
264        HandlerOutcome::NoOp
265    }
266}