markdown_syntax/html/
mod.rs1mod blocks;
8mod escape;
9mod footnotes;
10mod inlines;
11mod refs;
12mod tables;
13
14use alloc::{string::String, vec::Vec};
15
16use crate::{ast::Document, diagnostic::Diagnostic, validate::validate_document};
17
18use self::footnotes::FootnoteContext;
19use self::refs::DefMap;
20
21#[derive(Clone, Copy, Debug, Eq, PartialEq)]
23#[non_exhaustive]
24pub enum SafeRawHtmlForm {
25 EscapeText,
28 OmitPlaceholder,
31}
32
33#[derive(Clone, Copy, Debug, Eq, PartialEq)]
36#[non_exhaustive]
37pub enum TasklistAttrOrder {
38 DisabledFirst,
40 CheckedFirst,
42}
43
44#[derive(Clone, Debug, Eq, PartialEq)]
47#[non_exhaustive]
48pub struct HtmlOptions {
49 pub allow_dangerous_html: bool,
51 pub allow_dangerous_protocol: bool,
53 pub allow_any_img_src: bool,
55 pub gfm_tagfilter: bool,
57 pub tasklist_checkable: bool,
59 pub safe_raw_html_form: SafeRawHtmlForm,
61 pub tasklist_attr_order: TasklistAttrOrder,
63}
64
65impl Default for HtmlOptions {
66 fn default() -> Self {
67 Self {
68 allow_dangerous_html: false,
69 allow_dangerous_protocol: false,
70 allow_any_img_src: false,
71 gfm_tagfilter: false,
72 tasklist_checkable: false,
73 safe_raw_html_form: SafeRawHtmlForm::EscapeText,
74 tasklist_attr_order: TasklistAttrOrder::DisabledFirst,
75 }
76 }
77}
78
79#[derive(Clone, Debug, Eq, PartialEq)]
81pub enum HtmlError {
82 InvalidDocument(Vec<Diagnostic>),
84}
85
86pub(crate) struct Ctx<'a> {
89 pub defs: &'a DefMap,
90 pub footnotes: &'a FootnoteContext,
91 pub allow_dangerous_html: bool,
92 pub allow_dangerous_protocol: bool,
93 pub allow_any_img_src: bool,
94 pub gfm_tagfilter: bool,
95 pub tasklist_checkable: bool,
96 pub safe_raw_html_form: SafeRawHtmlForm,
97 pub tasklist_attr_order: TasklistAttrOrder,
98}
99
100impl<'a> Ctx<'a> {
101 pub fn gfm_url_denylist(&self) -> bool {
108 matches!(self.safe_raw_html_form, SafeRawHtmlForm::OmitPlaceholder)
109 }
110}
111
112impl Document {
113 pub fn to_html(&self) -> Result<String, HtmlError> {
115 self.to_html_with(&HtmlOptions::default())
116 }
117
118 pub fn to_html_with(&self, options: &HtmlOptions) -> Result<String, HtmlError> {
120 let diagnostics = validate_document(self);
121 if !diagnostics.is_empty() {
122 return Err(HtmlError::InvalidDocument(diagnostics));
123 }
124
125 Ok(render_document(self, options))
126 }
127}
128
129fn render_document(doc: &Document, options: &HtmlOptions) -> String {
135 let defs = DefMap::build(&doc.children);
136 let footnote_ctx = footnotes::build(&doc.children);
137
138 let ctx = Ctx {
139 defs: &defs,
140 footnotes: &footnote_ctx,
141 allow_dangerous_html: options.allow_dangerous_html,
142 allow_dangerous_protocol: options.allow_dangerous_protocol,
143 allow_any_img_src: options.allow_any_img_src,
144 gfm_tagfilter: options.gfm_tagfilter,
145 tasklist_checkable: options.tasklist_checkable,
146 safe_raw_html_form: options.safe_raw_html_form,
147 tasklist_attr_order: options.tasklist_attr_order,
148 };
149
150 let mut out = blocks::render_blocks_joined(&doc.children, &ctx);
151
152 let section = footnotes::emit_footnote_section(&footnote_ctx, |body| {
153 blocks::render_blocks_joined(body, &ctx)
154 });
155 if !section.is_empty() {
156 if !out.is_empty() {
157 out.push('\n');
158 }
159 out.push_str(§ion);
160 }
161
162 out
163}