mod blocks;
mod escape;
mod footnotes;
mod inlines;
mod refs;
mod tables;
use alloc::{string::String, vec::Vec};
use crate::{ast::Document, diagnostic::Diagnostic, validate::validate_document};
use self::footnotes::FootnoteContext;
use self::refs::DefMap;
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[non_exhaustive]
pub enum SafeRawHtmlForm {
EscapeText,
OmitPlaceholder,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[non_exhaustive]
pub enum TasklistAttrOrder {
DisabledFirst,
CheckedFirst,
}
#[derive(Clone, Debug, Eq, PartialEq)]
#[non_exhaustive]
pub struct HtmlOptions {
pub allow_dangerous_html: bool,
pub allow_dangerous_protocol: bool,
pub allow_any_img_src: bool,
pub gfm_tagfilter: bool,
pub tasklist_checkable: bool,
pub safe_raw_html_form: SafeRawHtmlForm,
pub tasklist_attr_order: TasklistAttrOrder,
}
impl Default for HtmlOptions {
fn default() -> Self {
Self {
allow_dangerous_html: false,
allow_dangerous_protocol: false,
allow_any_img_src: false,
gfm_tagfilter: false,
tasklist_checkable: false,
safe_raw_html_form: SafeRawHtmlForm::EscapeText,
tasklist_attr_order: TasklistAttrOrder::DisabledFirst,
}
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum HtmlError {
InvalidDocument(Vec<Diagnostic>),
}
pub(crate) struct Ctx<'a> {
pub defs: &'a DefMap,
pub footnotes: &'a FootnoteContext,
pub allow_dangerous_html: bool,
pub allow_dangerous_protocol: bool,
pub allow_any_img_src: bool,
pub gfm_tagfilter: bool,
pub tasklist_checkable: bool,
pub safe_raw_html_form: SafeRawHtmlForm,
pub tasklist_attr_order: TasklistAttrOrder,
}
impl<'a> Ctx<'a> {
pub fn gfm_url_denylist(&self) -> bool {
matches!(self.safe_raw_html_form, SafeRawHtmlForm::OmitPlaceholder)
}
}
impl Document {
pub fn to_html(&self) -> Result<String, HtmlError> {
self.to_html_with(&HtmlOptions::default())
}
pub fn to_html_with(&self, options: &HtmlOptions) -> Result<String, HtmlError> {
let diagnostics = validate_document(self);
if !diagnostics.is_empty() {
return Err(HtmlError::InvalidDocument(diagnostics));
}
Ok(render_document(self, options))
}
}
fn render_document(doc: &Document, options: &HtmlOptions) -> String {
let defs = DefMap::build(&doc.children);
let footnote_ctx = footnotes::build(&doc.children);
let ctx = Ctx {
defs: &defs,
footnotes: &footnote_ctx,
allow_dangerous_html: options.allow_dangerous_html,
allow_dangerous_protocol: options.allow_dangerous_protocol,
allow_any_img_src: options.allow_any_img_src,
gfm_tagfilter: options.gfm_tagfilter,
tasklist_checkable: options.tasklist_checkable,
safe_raw_html_form: options.safe_raw_html_form,
tasklist_attr_order: options.tasklist_attr_order,
};
let mut out = blocks::render_blocks_joined(&doc.children, &ctx);
let section = footnotes::emit_footnote_section(&footnote_ctx, |body| {
blocks::render_blocks_joined(body, &ctx)
});
if !section.is_empty() {
if !out.is_empty() {
out.push('\n');
}
out.push_str(§ion);
}
out
}