Skip to main content

nargo_document/generator/
markdown.rs

1//! Markdown 渲染器模块
2//! 提供将 Markdown 文本渲染为 HTML 的功能
3
4use oak_core::{Builder, ParseSession};
5use oak_markdown::{
6    ast::{Block, Blockquote, CodeBlock, Heading, Html, Inline, List, ListItem, MarkdownRoot, Paragraph, Table, TableCell},
7    MarkdownBuilder, MarkdownLanguage,
8};
9
10/// Markdown 渲染器配置
11#[derive(Debug, Clone)]
12pub struct MarkdownRendererConfig {
13    /// 是否启用表格支持
14    pub enable_tables: bool,
15    /// 是否启用脚注支持
16    pub enable_footnotes: bool,
17    /// 是否启用删除线支持
18    pub enable_strikethrough: bool,
19    /// 是否启用任务列表支持
20    pub enable_tasklists: bool,
21    /// 是否启用智能标点
22    pub enable_smart_punctuation: bool,
23}
24
25impl Default for MarkdownRendererConfig {
26    fn default() -> Self {
27        Self { enable_tables: true, enable_footnotes: true, enable_strikethrough: true, enable_tasklists: true, enable_smart_punctuation: true }
28    }
29}
30
31/// Markdown 渲染器
32#[derive(Debug, Clone)]
33pub struct MarkdownRenderer {
34    /// 渲染器配置
35    config: MarkdownRendererConfig,
36    /// Markdown 语言配置
37    lang_config: MarkdownLanguage,
38}
39
40impl MarkdownRenderer {
41    /// 创建新的 Markdown 渲染器
42    pub fn new() -> Self {
43        Self { config: MarkdownRendererConfig::default(), lang_config: MarkdownLanguage::default() }
44    }
45
46    /// 创建带配置的 Markdown 渲染器
47    ///
48    /// # Arguments
49    ///
50    /// * `config` - 渲染器配置
51    pub fn with_config(config: MarkdownRendererConfig) -> Self {
52        let mut lang_config = MarkdownLanguage::default();
53        lang_config.allow_tables = config.enable_tables;
54        lang_config.allow_footnotes = config.enable_footnotes;
55        lang_config.allow_strikethrough = config.enable_strikethrough;
56        lang_config.allow_task_lists = config.enable_tasklists;
57
58        Self { config, lang_config }
59    }
60
61    /// 获取渲染器配置
62    pub fn config(&self) -> &MarkdownRendererConfig {
63        &self.config
64    }
65
66    /// 获取可变的渲染器配置
67    pub fn config_mut(&mut self) -> &mut MarkdownRendererConfig {
68        &mut self.config
69    }
70
71    /// 将 Markdown 文本渲染为 HTML
72    ///
73    /// # Arguments
74    ///
75    /// * `markdown` - Markdown 文本内容
76    ///
77    /// # Returns
78    ///
79    /// 渲染后的 HTML 字符串
80    pub fn render(&self, markdown: &str) -> Result<String, std::io::Error> {
81        Ok(self.render_fallback(markdown))
82    }
83
84    /// 渲染 Markdown AST 为 HTML
85    ///
86    /// # Arguments
87    ///
88    /// * `root` - Markdown AST 根节点
89    ///
90    /// # Returns
91    ///
92    /// 渲染后的 HTML 字符串
93    fn render_ast(&self, root: &MarkdownRoot) -> String {
94        let mut html = String::new();
95
96        for block in &root.blocks {
97            html.push_str(&self.render_block(block));
98        }
99
100        html
101    }
102
103    /// 渲染块级元素
104    ///
105    /// # Arguments
106    ///
107    /// * `block` - 块级元素
108    ///
109    /// # Returns
110    ///
111    /// 渲染后的 HTML 字符串
112    fn render_block(&self, block: &Block) -> String {
113        match block {
114            Block::Heading(heading) => self.render_heading(heading),
115            Block::Paragraph(paragraph) => self.render_paragraph(paragraph),
116            Block::CodeBlock(code_block) => self.render_code_block(code_block),
117            Block::List(list) => self.render_list(list),
118            Block::Blockquote(blockquote) => self.render_blockquote(blockquote),
119            Block::HorizontalRule(_) => "<hr />\n".to_string(),
120            Block::Table(table) => self.render_table(table),
121            Block::Html(html) => self.render_html(html),
122            Block::AbbreviationDefinition(_) => String::new(),
123        }
124    }
125
126    /// 渲染标题
127    ///
128    /// # Arguments
129    ///
130    /// * `heading` - 标题元素
131    ///
132    /// # Returns
133    ///
134    /// 渲染后的 HTML 字符串
135    fn render_heading(&self, heading: &Heading) -> String {
136        let tag = format!("h{}", heading.level);
137        let escaped_content = self.escape_html(&heading.content);
138        format!("<{}>{}</{}>\n", tag, escaped_content, tag)
139    }
140
141    /// 渲染段落
142    ///
143    /// # Arguments
144    ///
145    /// * `paragraph` - 段落元素
146    ///
147    /// # Returns
148    ///
149    /// 渲染后的 HTML 字符串
150    fn render_paragraph(&self, paragraph: &Paragraph) -> String {
151        let escaped_content = self.escape_html(&paragraph.content);
152        format!("<p>{}</p>\n", escaped_content)
153    }
154
155    /// 渲染代码块
156    ///
157    /// # Arguments
158    ///
159    /// * `code_block` - 代码块元素
160    ///
161    /// # Returns
162    ///
163    /// 渲染后的 HTML 字符串
164    fn render_code_block(&self, code_block: &CodeBlock) -> String {
165        let class = if let Some(lang) = &code_block.language { format!(" class=\"language-{}\"", self.escape_html(lang)) } else { String::new() };
166        let escaped_content = self.escape_html(&code_block.content);
167        format!("<pre><code{}>{}</code></pre>\n", class, escaped_content)
168    }
169
170    /// 渲染列表
171    ///
172    /// # Arguments
173    ///
174    /// * `list` - 列表元素
175    ///
176    /// # Returns
177    ///
178    /// 渲染后的 HTML 字符串
179    fn render_list(&self, list: &List) -> String {
180        let tag = if list.is_ordered { "ol" } else { "ul" };
181        let mut html = format!("<{}>\n", tag);
182
183        for item in &list.items {
184            html.push_str(&self.render_list_item(item));
185        }
186
187        html.push_str(&format!("</{}>\n", tag));
188        html
189    }
190
191    /// 渲染列表项
192    ///
193    /// # Arguments
194    ///
195    /// * `list_item` - 列表项元素
196    ///
197    /// # Returns
198    ///
199    /// 渲染后的 HTML 字符串
200    fn render_list_item(&self, list_item: &ListItem) -> String {
201        let mut html = String::from("<li>");
202
203        if list_item.is_task {
204            let checked = if list_item.is_checked.unwrap_or(false) { "checked" } else { "" };
205            html.push_str(&format!("<input type=\"checkbox\" disabled {} /> ", checked));
206        }
207
208        for block in &list_item.content {
209            html.push_str(&self.render_block(block));
210        }
211
212        html.push_str("</li>\n");
213        html
214    }
215
216    /// 渲染引用块
217    ///
218    /// # Arguments
219    ///
220    /// * `blockquote` - 引用块元素
221    ///
222    /// # Returns
223    ///
224    /// 渲染后的 HTML 字符串
225    fn render_blockquote(&self, blockquote: &Blockquote) -> String {
226        let mut html = String::from("<blockquote>\n");
227
228        for block in &blockquote.content {
229            html.push_str(&self.render_block(block));
230        }
231
232        html.push_str("</blockquote>\n");
233        html
234    }
235
236    /// 渲染表格
237    ///
238    /// # Arguments
239    ///
240    /// * `table` - 表格元素
241    ///
242    /// # Returns
243    ///
244    /// 渲染后的 HTML 字符串
245    fn render_table(&self, table: &Table) -> String {
246        let mut html = String::from("<table>\n");
247
248        html.push_str("<thead>\n<tr>\n");
249        for cell in &table.header.cells {
250            html.push_str(&self.render_table_cell(cell, "th"));
251        }
252        html.push_str("</tr>\n</thead>\n");
253
254        html.push_str("<tbody>\n");
255        for row in &table.rows {
256            html.push_str("<tr>\n");
257            for cell in &row.cells {
258                html.push_str(&self.render_table_cell(cell, "td"));
259            }
260            html.push_str("</tr>\n");
261        }
262        html.push_str("</tbody>\n");
263
264        html.push_str("</table>\n");
265        html
266    }
267
268    /// 渲染表格单元格
269    ///
270    /// # Arguments
271    ///
272    /// * `cell` - 表格单元格元素
273    /// * `tag` - 单元格标签 (th 或 td)
274    ///
275    /// # Returns
276    ///
277    /// 渲染后的 HTML 字符串
278    fn render_table_cell(&self, cell: &TableCell, tag: &str) -> String {
279        let escaped_content = self.escape_html(&cell.content);
280        format!("<{}>{}</{}>\n", tag, escaped_content, tag)
281    }
282
283    /// 渲染 HTML 块
284    ///
285    /// # Arguments
286    ///
287    /// * `html` - HTML 块元素
288    ///
289    /// # Returns
290    ///
291    /// 渲染后的 HTML 字符串
292    fn render_html(&self, html: &Html) -> String {
293        format!("{}\n", html.content)
294    }
295
296    /// 渲染内联元素
297    ///
298    /// # Arguments
299    ///
300    /// * `inline` - 内联元素
301    ///
302    /// # Returns
303    ///
304    /// 渲染后的 HTML 字符串
305    fn render_inline(&self, inline: &Inline) -> String {
306        match inline {
307            Inline::Text(text) => self.escape_html(text),
308            Inline::Bold(text) => format!("<strong>{}</strong>", self.escape_html(text)),
309            Inline::Italic(text) => format!("<em>{}</em>", self.escape_html(text)),
310            Inline::Code(text) => format!("<code>{}</code>", self.escape_html(text)),
311            Inline::Link { text, url, title } => {
312                let title_attr = if let Some(t) = title { format!(" title=\"{}\"", self.escape_html(t)) } else { String::new() };
313                format!("<a href=\"{}\"{}>{}</a>", self.escape_html(url), title_attr, self.escape_html(text))
314            }
315            Inline::Image { alt, url, title } => {
316                let title_attr = if let Some(t) = title { format!(" title=\"{}\"", self.escape_html(t)) } else { String::new() };
317                format!("<img src=\"{}\" alt=\"{}\"{} />", self.escape_html(url), self.escape_html(alt), title_attr)
318            }
319            Inline::Abbreviation { key, .. } => key.clone(),
320        }
321    }
322
323    /// 转义 HTML 特殊字符
324    ///
325    /// # Arguments
326    ///
327    /// * `text` - 原始文本
328    ///
329    /// # Returns
330    ///
331    /// 转义后的文本
332    fn escape_html(&self, text: &str) -> String {
333        text.replace('&', "&amp;").replace('<', "&lt;").replace('>', "&gt;").replace('"', "&quot;").replace('\'', "&#39;")
334    }
335
336    /// 备用渲染方法(使用 pulldown-cmark)
337    ///
338    /// # Arguments
339    ///
340    /// * `markdown` - Markdown 文本
341    ///
342    /// # Returns
343    ///
344    /// 渲染后的 HTML 字符串
345    fn render_fallback(&self, markdown: &str) -> String {
346        use pulldown_cmark::{html, Options, Parser};
347
348        let mut options = Options::empty();
349
350        if self.config.enable_tables {
351            options.insert(Options::ENABLE_TABLES);
352        }
353        if self.config.enable_footnotes {
354            options.insert(Options::ENABLE_FOOTNOTES);
355        }
356        if self.config.enable_strikethrough {
357            options.insert(Options::ENABLE_STRIKETHROUGH);
358        }
359        if self.config.enable_tasklists {
360            options.insert(Options::ENABLE_TASKLISTS);
361        }
362        if self.config.enable_smart_punctuation {
363            options.insert(Options::ENABLE_SMART_PUNCTUATION);
364        }
365
366        let parser = Parser::new_ext(markdown, options);
367        let mut html_output = String::new();
368        html::push_html(&mut html_output, parser);
369
370        html_output
371    }
372}
373
374impl Default for MarkdownRenderer {
375    fn default() -> Self {
376        Self::new()
377    }
378}