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}