note_mark/layer/
transformer.rs

1//! Transformer of Markdown tree to HTML tree.
2
3use std::borrow::Cow;
4
5use crate::model::{html::*, tree::*};
6
7/// The struct to transform Markdown tree to HTML tree.
8#[derive(Debug, Clone)]
9pub struct Transformer {
10    #[allow(dead_code)]
11    section: bool,
12}
13
14#[allow(clippy::derivable_impls)]
15impl Default for Transformer {
16    fn default() -> Self {
17        Self { section: false }
18    }
19}
20
21impl Transformer {
22    /// Create a new Transformer.
23    pub fn new() -> Self {
24        Self::default()
25    }
26
27    #[allow(dead_code)]
28    fn section(mut self, section: bool) -> Self {
29        self.section = section;
30        self
31    }
32}
33
34impl Transformer {
35    /// Transform Markdown tree to HTML tree.
36    pub fn transform<'a>(&self, tree: MarkdownTree<'a>) -> DocumentNode<'a> {
37        DocumentNode {
38            root: self.block_tree(tree.root),
39        }
40    }
41
42    fn block_tree<'a>(&self, tree: BlockTree<'a>) -> Vec<Node<'a>> {
43        tree.root
44            .into_iter()
45            .map(|item| self.block_item(item))
46            .collect()
47    }
48
49    fn block_item<'a>(&self, item: BlockItem<'a>) -> Node<'a> {
50        match item {
51            BlockItem::Paragraph(tree) => self.paragraph(tree),
52            BlockItem::Headline(level, tree) => self.headline(level, tree),
53            BlockItem::BulletList(tree) => self.bullet_list(tree),
54            BlockItem::OrderedList(tree) => self.ordered_list(tree),
55            BlockItem::BlockQuote(tree) => self.blockquote(tree),
56            BlockItem::Container(_, _) => todo!(),
57        }
58    }
59
60    fn paragraph<'a>(&self, tree: InlineTree<'a>) -> Node<'a> {
61        Node::Element(ElementNode {
62            tag: ElementTag::P,
63            children: self.inline_tree(tree),
64            ..Default::default()
65        })
66    }
67
68    fn headline<'a>(&self, level: u8, tree: InlineTree<'a>) -> Node<'a> {
69        Node::Element(ElementNode {
70            tag: ElementTag::headline(level).unwrap(),
71            children: self.inline_tree(tree),
72            ..Default::default()
73        })
74    }
75
76    fn bullet_list<'a>(&self, tree: ListTree<'a>) -> Node<'a> {
77        Node::Element(ElementNode {
78            tag: ElementTag::Ul,
79            children: self.list_tree(tree),
80            ..Default::default()
81        })
82    }
83
84    fn ordered_list<'a>(&self, tree: ListTree<'a>) -> Node<'a> {
85        Node::Element(ElementNode {
86            tag: ElementTag::Ol,
87            children: self.list_tree(tree),
88            ..Default::default()
89        })
90    }
91
92    fn blockquote<'a>(&self, tree: BlockTree<'a>) -> Node<'a> {
93        Node::Element(ElementNode {
94            tag: ElementTag::Blockquote,
95            children: self.block_tree(tree),
96            ..Default::default()
97        })
98    }
99
100    fn list_tree<'a>(&self, tree: ListTree<'a>) -> Vec<Node<'a>> {
101        tree.root
102            .into_iter()
103            .map(|item| {
104                let mut nodes = self.inline_tree(item.name);
105
106                item.children
107                    .into_iter()
108                    .map(|item| self.block_item(item))
109                    .for_each(|node| nodes.push(node));
110
111                Node::Element(ElementNode {
112                    tag: ElementTag::Li,
113                    children: nodes,
114                    ..Default::default()
115                })
116            })
117            .collect()
118    }
119
120    fn inline_tree<'a>(&self, tree: InlineTree<'a>) -> Vec<Node<'a>> {
121        tree.root
122            .into_iter()
123            .map(|item| self.inline_item(item))
124            .collect()
125    }
126
127    fn inline_item<'a>(&self, item: InlineItem<'a>) -> Node<'a> {
128        match item {
129            InlineItem::Text(text) => self.text(text),
130            InlineItem::Italic(tree) => self.italic(tree),
131            InlineItem::Strong(tree) => self.strong(tree),
132            InlineItem::Break => self.r#break(),
133        }
134    }
135
136    fn text<'a>(&self, text: Cow<'a, str>) -> Node<'a> {
137        Node::Text(TextNode { text })
138    }
139
140    fn italic<'a>(&self, tree: InlineTree<'a>) -> Node<'a> {
141        Node::Element(ElementNode {
142            tag: ElementTag::Em,
143            children: self.inline_tree(tree),
144            ..Default::default()
145        })
146    }
147
148    fn strong<'a>(&self, tree: InlineTree<'a>) -> Node<'a> {
149        Node::Element(ElementNode {
150            tag: ElementTag::Strong,
151            children: self.inline_tree(tree),
152            ..Default::default()
153        })
154    }
155
156    fn r#break<'a>(&self) -> Node<'a> {
157        Node::Element(ElementNode {
158            tag: ElementTag::Br,
159            ..Default::default()
160        })
161    }
162}
163
164#[cfg(test)]
165mod tests {
166    use super::*;
167
168    #[test]
169    fn test_transform() {
170        // # Hello
171        // World
172        //
173        // Hello2 *World2*
174        let tree = MarkdownTree {
175            root: BlockTree {
176                root: vec![
177                    BlockItem::Headline(
178                        1,
179                        InlineTree {
180                            root: vec![
181                                InlineItem::Text(Cow::Borrowed("Hello")),
182                                InlineItem::Break,
183                                InlineItem::Text(Cow::Borrowed("World")),
184                            ],
185                        },
186                    ),
187                    BlockItem::Paragraph(InlineTree {
188                        root: vec![InlineItem::Strong(InlineTree {
189                            root: vec![InlineItem::Text(Cow::Borrowed("Hello World2"))],
190                        })],
191                    }),
192                ],
193            },
194        };
195
196        let transformer = Transformer::new();
197        let document = transformer.transform(tree);
198
199        assert_eq!(
200            document,
201            DocumentNode {
202                root: vec![
203                    Node::Element(ElementNode {
204                        tag: ElementTag::H1,
205                        children: vec![
206                            Node::Text(TextNode {
207                                text: Cow::Borrowed("Hello")
208                            }),
209                            Node::Element(ElementNode {
210                                tag: ElementTag::Br,
211                                ..Default::default()
212                            }),
213                            Node::Text(TextNode {
214                                text: Cow::Borrowed("World")
215                            }),
216                        ],
217                        ..Default::default()
218                    }),
219                    Node::Element(ElementNode {
220                        tag: ElementTag::P,
221                        children: vec![Node::Element(ElementNode {
222                            tag: ElementTag::Strong,
223                            children: vec![Node::Text(TextNode {
224                                text: Cow::Borrowed("Hello World2")
225                            })],
226                            ..Default::default()
227                        }),],
228                        ..Default::default()
229                    }),
230                ]
231            }
232        );
233    }
234
235    #[test]
236    fn test_transform2() {
237        #[rustfmt::skip]
238        // - Hello
239        // - World
240        //   1. Change the **world**
241        //   1. OK
242        //     Good
243        // - Hello2
244        let tree = MarkdownTree {
245            root: BlockTree {
246                root: vec![BlockItem::BulletList(ListTree {
247                    root: vec![
248                        ListItem {
249                            name: InlineTree {
250                                root: vec![InlineItem::Text(Cow::Borrowed("Hello"))],
251                            },
252                            children: vec![],
253                        },
254                        ListItem {
255                            name: InlineTree {
256                                root: vec![InlineItem::Text(Cow::Borrowed("World"))],
257                            },
258                            children: vec![
259                                BlockItem::OrderedList(ListTree {
260                                    root: vec![
261                                        ListItem {
262                                            name: InlineTree {
263                                                root: vec![InlineItem::Text(Cow::Borrowed(
264                                                    "Change the ",
265                                                ))],
266                                            },
267                                            children: vec![],
268                                        },
269                                        ListItem {
270                                            name: InlineTree {
271                                                root: vec![InlineItem::Strong(InlineTree {
272                                                    root: vec![InlineItem::Text(Cow::Borrowed(
273                                                        "world",
274                                                    ))],
275                                                })],
276                                            },
277                                            children: vec![],
278                                        },
279                                        ListItem {
280                                            name: InlineTree {
281                                                root: vec![
282                                                    InlineItem::Text(Cow::Borrowed("OK")),
283                                                    InlineItem::Break,
284                                                    InlineItem::Text(Cow::Borrowed("Good")),
285                                                ],
286                                            },
287                                            children: vec![],
288                                        },
289                                    ],
290                                }),
291                                BlockItem::Paragraph(InlineTree {
292                                    root: vec![InlineItem::Text(Cow::Borrowed("OK"))],
293                                }),
294                            ],
295                        },
296                        ListItem {
297                            name: InlineTree {
298                                root: vec![InlineItem::Text(Cow::Borrowed("Hello2"))],
299                            },
300                            children: vec![],
301                        },
302                    ],
303                })],
304            },
305        };
306
307        let transformer = Transformer::new();
308        let document = transformer.transform(tree);
309
310        assert_eq!(
311            document,
312            DocumentNode {
313                root: vec![Node::Element(ElementNode {
314                    tag: ElementTag::Ul,
315                    children: vec![
316                        Node::Element(ElementNode {
317                            tag: ElementTag::Li,
318                            children: vec![Node::Text(TextNode {
319                                text: Cow::Borrowed("Hello")
320                            }),],
321                            ..Default::default()
322                        }),
323                        Node::Element(ElementNode {
324                            tag: ElementTag::Li,
325                            children: vec![
326                                Node::Text(TextNode {
327                                    text: Cow::Borrowed("World")
328                                }),
329                                Node::Element(ElementNode {
330                                    tag: ElementTag::Ol,
331                                    children: vec![
332                                        Node::Element(ElementNode {
333                                            tag: ElementTag::Li,
334                                            children: vec![Node::Text(TextNode {
335                                                text: Cow::Borrowed("Change the ")
336                                            }),],
337                                            ..Default::default()
338                                        }),
339                                        Node::Element(ElementNode {
340                                            tag: ElementTag::Li,
341                                            children: vec![Node::Element(ElementNode {
342                                                tag: ElementTag::Strong,
343                                                children: vec![Node::Text(TextNode {
344                                                    text: Cow::Borrowed("world")
345                                                }),],
346                                                ..Default::default()
347                                            }),],
348                                            ..Default::default()
349                                        }),
350                                        Node::Element(ElementNode {
351                                            tag: ElementTag::Li,
352                                            children: vec![
353                                                Node::Text(TextNode {
354                                                    text: Cow::Borrowed("OK")
355                                                }),
356                                                Node::Element(ElementNode {
357                                                    tag: ElementTag::Br,
358                                                    ..Default::default()
359                                                }),
360                                                Node::Text(TextNode {
361                                                    text: Cow::Borrowed("Good")
362                                                }),
363                                            ],
364                                            ..Default::default()
365                                        }),
366                                    ],
367                                    ..Default::default()
368                                }),
369                                Node::Element(ElementNode {
370                                    tag: ElementTag::P,
371                                    children: vec![Node::Text(TextNode {
372                                        text: Cow::Borrowed("OK")
373                                    }),],
374                                    ..Default::default()
375                                }),
376                            ],
377                            ..Default::default()
378                        }),
379                        Node::Element(ElementNode {
380                            tag: ElementTag::Li,
381                            children: vec![Node::Text(TextNode {
382                                text: Cow::Borrowed("Hello2")
383                            }),],
384                            ..Default::default()
385                        }),
386                    ],
387                    ..Default::default()
388                }),]
389            }
390        )
391    }
392}