markdown_that/parser/
renderer.rs1use std::collections::HashMap;
2use std::fmt::Debug;
3
4use crate::Node;
5use crate::common::utils::escape_html;
6use crate::parser::extset::RenderExtSet;
7
8pub trait Renderer {
13 fn open(&mut self, tag: &str, attrs: &[(&str, String)]);
15 fn close(&mut self, tag: &str);
17 fn self_close(&mut self, tag: &str, attrs: &[(&str, String)]);
19 fn contents(&mut self, nodes: &[Node]);
21 fn cr(&mut self);
23 fn text(&mut self, text: &str);
25 fn text_raw(&mut self, text: &str);
27 fn ext(&mut self) -> &mut RenderExtSet;
29}
30
31#[derive(Debug, Default)]
32pub(crate) struct HTMLRenderer<const XHTML: bool> {
34 result: String,
35 ext: RenderExtSet,
36}
37
38impl<const XHTML: bool> HTMLRenderer<XHTML> {
39 pub fn new() -> Self {
40 Self {
41 result: String::new(),
42 ext: RenderExtSet::new(),
43 }
44 }
45
46 pub fn render(&mut self, node: &Node) {
47 node.node_value.render(node, self);
48 }
49
50 fn make_attr(&mut self, name: &str, value: &str) {
51 self.result.push(' ');
52 self.result.push_str(&escape_html(name));
53 self.result.push('=');
54 self.result.push('"');
55 self.result.push_str(&escape_html(value));
56 self.result.push('"');
57 }
58
59 fn make_attrs(&mut self, attrs: &[(&str, String)]) {
60 let mut attr_hash = HashMap::new();
61 let mut attr_order = Vec::with_capacity(attrs.len());
62
63 for (name, value) in attrs {
64 let entry = attr_hash.entry(*name).or_insert(Vec::new());
65 entry.push(value.as_str());
66 attr_order.push(*name);
67 }
68
69 for name in attr_order {
70 let Some(value) = attr_hash.remove(name) else {
71 continue;
72 };
73
74 if name == "class" {
75 self.make_attr(name, &value.join(" "));
76 } else if name == "style" {
77 self.make_attr(name, &value.join(";"));
78 } else {
79 for v in value {
80 self.make_attr(name, v);
81 }
82 }
83 }
84 }
85}
86
87impl<const XHTML: bool> From<HTMLRenderer<XHTML>> for String {
88 fn from(f: HTMLRenderer<XHTML>) -> Self {
89 #[cold]
90 fn replace_null(input: String) -> String {
91 input.replace('\0', "\u{FFFD}")
92 }
93
94 if f.result.contains('\0') {
95 replace_null(f.result)
99 } else {
100 f.result
101 }
102 }
103}
104
105impl<const XHTML: bool> Renderer for HTMLRenderer<XHTML> {
106 fn open(&mut self, tag: &str, attrs: &[(&str, String)]) {
107 self.result.push('<');
108 self.result.push_str(tag);
109 self.make_attrs(attrs);
110 self.result.push('>');
111 }
112
113 fn close(&mut self, tag: &str) {
114 self.result.push('<');
115 self.result.push('/');
116 self.result.push_str(tag);
117 self.result.push('>');
118 }
119
120 fn self_close(&mut self, tag: &str, attrs: &[(&str, String)]) {
121 self.result.push('<');
122 self.result.push_str(tag);
123 self.make_attrs(attrs);
124 if XHTML {
125 self.result.push(' ');
126 self.result.push('/');
127 }
128 self.result.push('>');
129 }
130
131 fn contents(&mut self, nodes: &[Node]) {
132 for node in nodes.iter() {
133 self.render(node);
134 }
135 }
136
137 fn cr(&mut self) {
138 match self.result.as_bytes().last() {
140 Some(b'\n') | None => {}
141 Some(_) => self.result.push('\n'),
142 }
143 }
144
145 fn text(&mut self, text: &str) {
146 self.result.push_str(&escape_html(text));
147 }
148
149 fn text_raw(&mut self, text: &str) {
150 self.result.push_str(text);
151 }
152
153 fn ext(&mut self) -> &mut RenderExtSet {
154 &mut self.ext
155 }
156}