markdown_it/parser/
renderer.rs1use std::collections::HashMap;
2use std::fmt::Debug;
3
4use crate::common::utils::escape_html;
5use crate::parser::extset::RenderExtSet;
6use crate::Node;
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 { continue; };
71
72 if name == "class" {
73 self.make_attr(name, &value.join(" "));
74 } else if name == "style" {
75 self.make_attr(name, &value.join(";"));
76 } else {
77 for v in value {
78 self.make_attr(name, v);
79 }
80 }
81 }
82 }
83}
84
85impl<const XHTML: bool> From<HTMLRenderer<XHTML>> for String {
86 fn from(f: HTMLRenderer<XHTML>) -> Self {
87 #[cold]
88 fn replace_null(input: String) -> String {
89 input.replace('\0', "\u{FFFD}")
90 }
91
92 if f.result.contains('\0') {
93 replace_null(f.result)
97 } else {
98 f.result
99 }
100 }
101}
102
103impl<const XHTML: bool> Renderer for HTMLRenderer<XHTML> {
104 fn open(&mut self, tag: &str, attrs: &[(&str, String)]) {
105 self.result.push('<');
106 self.result.push_str(tag);
107 self.make_attrs(attrs);
108 self.result.push('>');
109 }
110
111 fn close(&mut self, tag: &str) {
112 self.result.push('<');
113 self.result.push('/');
114 self.result.push_str(tag);
115 self.result.push('>');
116 }
117
118 fn self_close(&mut self, tag: &str, attrs: &[(&str, String)]) {
119 self.result.push('<');
120 self.result.push_str(tag);
121 self.make_attrs(attrs);
122 if XHTML {
123 self.result.push(' ');
124 self.result.push('/');
125 }
126 self.result.push('>');
127 }
128
129 fn contents(&mut self, nodes: &[Node]) {
130 for node in nodes.iter() {
131 self.render(node);
132 }
133 }
134
135 fn cr(&mut self) {
136 match self.result.as_bytes().last() {
138 Some(b'\n') | None => {}
139 Some(_) => self.result.push('\n')
140 }
141 }
142
143 fn text(&mut self, text: &str) {
144 self.result.push_str(&escape_html(text));
145 }
146
147 fn text_raw(&mut self, text: &str) {
148 self.result.push_str(text);
149 }
150
151 fn ext(&mut self) -> &mut RenderExtSet {
152 &mut self.ext
153 }
154}