Skip to main content

sciforge_parser/markdown/
writer.rs

1pub 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("&lt;"),
6            '>' => out.push_str("&gt;"),
7            '"' => out.push_str("&quot;"),
8            '\'' => out.push_str("&#39;"),
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}