// use std::{string::String, vec::Vec}; // #[cfg(all(feature = "std", not(feature = "hashbrown")))] // use std::collections::HashMap; // #[cfg(feature = "hashbrown")] // use hashbrown::HashMap; // use std::collections::HashMap; //#[cfg(feature = "std")] use markdown_weaver_escape::IoWriter; use markdown_weaver_escape::{ FmtWriter, StrWrite, escape_href, escape_html, escape_html_body_text, }; use markdown_weaver::{ Alignment, BlockQuoteKind, CodeBlockKind, CowStr, Event, Event::*, LinkType, Tag, TagEnd, }; pub enum TableState { Head, Body, } struct HtmlWriter<'a, I, W> { /// Iterator supplying events. iter: I, /// Writer to write to. writer: W, /// Whether or not the last write wrote a newline. end_newline: bool, /// Whether if inside a metadata block (text should not be written) in_non_writing_block: bool, table_state: TableState, table_alignments: Vec, table_cell_index: usize, numbers: HashMap, usize>, /// Buffered paragraph opening tag (without closing `>`) for dir attribute emission pending_paragraph_open: Option, } impl<'a, I, W> HtmlWriter<'a, I, W> where I: Iterator>, W: StrWrite, { fn new(iter: I, writer: W) -> Self { Self { iter, writer, end_newline: true, in_non_writing_block: false, table_state: TableState::Head, table_alignments: vec![], table_cell_index: 0, numbers: HashMap::new(), pending_paragraph_open: None, } } /// Writes a new line. #[inline] fn write_newline(&mut self) -> Result<(), W::Error> { self.end_newline = true; self.writer.write_str("\n") } /// Writes a buffer, and tracks whether or not a newline was written. #[inline] fn write(&mut self, s: &str) -> Result<(), W::Error> { self.writer.write_str(s)?; if !s.is_empty() { self.end_newline = s.ends_with('\n'); } Ok(()) } fn run(mut self) -> Result<(), W::Error> { while let Some(event) = self.iter.next() { match event { Start(tag) => { self.start_tag(tag)?; } End(tag) => { self.end_tag(tag)?; } Text(text) => { if !self.in_non_writing_block { // Flush pending paragraph with dir attribute if needed if let Some(opening) = self.pending_paragraph_open.take() { if let Some(dir) = crate::utils::detect_text_direction(&text) { self.write(&opening)?; self.write(" dir=\"")?; self.write(dir)?; self.write("\">")?; } else { self.write(&opening)?; self.write(">")?; } } escape_html_body_text(&mut self.writer, &text)?; self.end_newline = text.ends_with('\n'); } } Code(text) => { self.write("")?; escape_html_body_text(&mut self.writer, &text)?; self.write("")?; } InlineMath(text) => match crate::math::render_math(&text, false) { crate::math::MathResult::Success(mathml) => { self.write(r#""#)?; self.write(&mathml)?; self.write("")?; } crate::math::MathResult::Error { html, .. } => { self.write(&html)?; } }, DisplayMath(text) => match crate::math::render_math(&text, true) { crate::math::MathResult::Success(mathml) => { self.write(r#""#)?; self.write(&mathml)?; self.write("")?; } crate::math::MathResult::Error { html, .. } => { self.write(&html)?; } }, Html(html) | InlineHtml(html) => { self.write(&html)?; } SoftBreak => { self.write_newline()?; } HardBreak => { self.write("
\n")?; } Rule => { if self.end_newline { self.write("
\n")?; } else { self.write("\n
\n")?; } } FootnoteReference(name) => { let len = self.numbers.len() + 1; self.write("")?; let number = *self.numbers.entry(name).or_insert(len); write!(&mut self.writer, "{}", number)?; self.write("")?; } TaskListMarker(true) => { self.write("\n")?; } TaskListMarker(false) => { self.write("\n")?; } WeaverBlock(_text) => {} } } Ok(()) } /// Writes the start of an HTML tag. fn start_tag(&mut self, tag: Tag<'a>) -> Result<(), W::Error> { match tag { Tag::HtmlBlock => Ok(()), Tag::Paragraph(_) => { // Buffer paragraph opening for dir attribute detection let opening = if self.end_newline { String::from(" { if self.end_newline { self.write("<")?; } else { self.write("\n<")?; } write!(&mut self.writer, "{}", level)?; if let Some(id) = id { self.write(" id=\"")?; escape_html(&mut self.writer, &id)?; self.write("\"")?; } let mut classes = classes.iter(); if let Some(class) = classes.next() { self.write(" class=\"")?; escape_html(&mut self.writer, class)?; for class in classes { self.write(" ")?; escape_html(&mut self.writer, class)?; } self.write("\"")?; } for (attr, value) in attrs { self.write(" ")?; escape_html(&mut self.writer, &attr)?; if let Some(val) = value { self.write("=\"")?; escape_html(&mut self.writer, &val)?; self.write("\"")?; } else { self.write("=\"\"")?; } } self.write(">") } Tag::Table(alignments) => { self.table_alignments = alignments; self.write("") } Tag::TableHead => { self.table_state = TableState::Head; self.table_cell_index = 0; self.write("") } Tag::TableRow => { self.table_cell_index = 0; self.write("") } Tag::TableCell => { match self.table_state { TableState::Head => { self.write(" { self.write(" self.write(" style=\"text-align: left\">"), Some(&Alignment::Center) => self.write(" style=\"text-align: center\">"), Some(&Alignment::Right) => self.write(" style=\"text-align: right\">"), _ => self.write(">"), } } Tag::BlockQuote(kind) => { let class_str = match kind { None => "", Some(kind) => match kind { BlockQuoteKind::Note => " class=\"markdown-alert-note\"", BlockQuoteKind::Tip => " class=\"markdown-alert-tip\"", BlockQuoteKind::Important => " class=\"markdown-alert-important\"", BlockQuoteKind::Warning => " class=\"markdown-alert-warning\"", BlockQuoteKind::Caution => " class=\"markdown-alert-caution\"", }, }; if self.end_newline { self.write(&format!("\n", class_str)) } else { self.write(&format!("\n\n", class_str)) } } Tag::CodeBlock(info) => { if !self.end_newline { self.write_newline()?; } match info { CodeBlockKind::Fenced(info) => { let lang = info.split(' ').next().unwrap(); if lang.is_empty() { self.write("
")
                        } else {
                            self.write("
")
                        }
                    }
                    CodeBlockKind::Indented => self.write("
"),
                }
            }
            Tag::List(Some(1)) => {
                if self.end_newline {
                    self.write("
    \n") } else { self.write("\n
      \n") } } Tag::List(Some(start)) => { if self.end_newline { self.write("
        \n") } Tag::List(None) => { if self.end_newline { self.write("