sciforge_parser/markdown/
writer.rs1pub fn push_escaped(out: &mut String, text: &str) {
2 for c in text.chars() {
3 match c {
4 '&' => out.push_str("&"),
5 '<' => out.push_str("<"),
6 '>' => out.push_str(">"),
7 '"' => out.push_str("""),
8 '\'' => out.push_str("'"),
9 _ => out.push(c),
10 }
11 }
12}
13
14pub fn render_inline(out: &mut String, text: &str) {
15 let mut rest = text;
16 loop {
17 match rest.find("**") {
18 None => {
19 push_escaped(out, rest);
20 break;
21 }
22 Some(pos) => {
23 push_escaped(out, &rest[..pos]);
24 rest = &rest[pos + 2..];
25 match rest.find("**") {
26 None => {
27 out.push_str("**");
28 push_escaped(out, rest);
29 break;
30 }
31 Some(end) => {
32 out.push_str("<strong>");
33 push_escaped(out, &rest[..end]);
34 out.push_str("</strong>");
35 rest = &rest[end + 2..];
36 }
37 }
38 }
39 }
40 }
41}
42
43pub fn render_md_html(src: &str) -> String {
44 let mut out = String::with_capacity(src.len() * 2);
45 let mut in_code = false;
46 let mut in_table = false;
47 let mut past_sep = false;
48
49 for line in src.lines() {
50 if in_code {
51 if line.starts_with("```") {
52 out.push_str("</code></pre>\n");
53 in_code = false;
54 } else {
55 push_escaped(&mut out, line);
56 out.push('\n');
57 }
58 continue;
59 }
60
61 if line.starts_with("```") {
62 if in_table {
63 out.push_str("</tbody></table>\n");
64 in_table = false;
65 }
66 out.push_str("<pre><code>");
67 in_code = true;
68 continue;
69 }
70
71 if line.starts_with('|') {
72 let inner = line.trim_start_matches('|').trim_end_matches('|');
73 let is_sep = !inner.is_empty()
74 && inner
75 .chars()
76 .all(|c| c == '-' || c == '|' || c == ' ' || c == ':');
77 if is_sep {
78 if in_table {
79 out.push_str("</thead><tbody>\n");
80 past_sep = true;
81 }
82 continue;
83 }
84 if !in_table {
85 out.push_str("<table>\n<thead>\n");
86 in_table = true;
87 past_sep = false;
88 }
89 let tag = if past_sep { "td" } else { "th" };
90 out.push_str("<tr>");
91 for cell in line.split('|') {
92 let cell = cell.trim();
93 if cell.is_empty() {
94 continue;
95 }
96 out.push('<');
97 out.push_str(tag);
98 out.push('>');
99 render_inline(&mut out, cell);
100 out.push_str("</");
101 out.push_str(tag);
102 out.push('>');
103 }
104 out.push_str("</tr>\n");
105 continue;
106 }
107
108 if in_table {
109 out.push_str("</tbody></table>\n");
110 in_table = false;
111 past_sep = false;
112 }
113
114 if let Some(rest) = line.strip_prefix("## ") {
115 out.push_str("<h3 style=\"color:#79c0ff;margin:1em 0 .4em\">");
116 push_escaped(&mut out, rest);
117 out.push_str("</h3>\n");
118 } else if let Some(rest) = line.strip_prefix("# ") {
119 out.push_str("<h2 style=\"color:#58a6ff;border-bottom:1px solid #21262d;padding-bottom:.3em;margin-bottom:.5em\">");
120 push_escaped(&mut out, rest);
121 out.push_str("</h2>\n");
122 } else if line == "---" {
123 out.push_str("<hr style=\"border:none;border-top:1px solid #30363d;margin:1em 0\">\n");
124 } else if line.is_empty() {
125 continue;
126 } else {
127 out.push_str("<p style=\"line-height:1.6;margin:.4em 0\">");
128 render_inline(&mut out, line);
129 out.push_str("</p>\n");
130 }
131 }
132
133 if in_table {
134 out.push_str("</tbody></table>\n");
135 }
136 if in_code {
137 out.push_str("</code></pre>\n");
138 }
139
140 out
141}
142
143pub fn write_md_heading(level: u8, text: &str) -> String {
144 let mut out = String::with_capacity(text.len() + 6);
145 for _ in 0..level {
146 out.push('#');
147 }
148 out.push(' ');
149 out.push_str(text);
150 out.push('\n');
151 out
152}
153
154pub fn write_md_table(headers: &[&str], rows: &[Vec<String>]) -> String {
155 let mut out = String::with_capacity(headers.len() * 20 + rows.len() * headers.len() * 15);
156 out.push('|');
157 for h in headers {
158 out.push(' ');
159 out.push_str(h);
160 out.push_str(" |");
161 }
162 out.push('\n');
163 out.push('|');
164 for _ in headers {
165 out.push_str("---|");
166 }
167 out.push('\n');
168 for row in rows {
169 out.push('|');
170 for cell in row {
171 out.push(' ');
172 out.push_str(cell);
173 out.push_str(" |");
174 }
175 out.push('\n');
176 }
177 out
178}
179
180pub fn write_md_code_block(content: &str) -> String {
181 let mut out = String::with_capacity(content.len() + 10);
182 out.push_str("```\n");
183 out.push_str(content);
184 if !content.ends_with('\n') {
185 out.push('\n');
186 }
187 out.push_str("```\n");
188 out
189}
190
191pub fn write_md_row(out: &mut String, label: &str, value: &str) {
192 out.push_str("| ");
193 out.push_str(label);
194 out.push_str(" | ");
195 out.push_str(value);
196 out.push_str(" |\n");
197}