// Copyright 2015 Google Inc. All rights reserved. // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. //! HTML renderer that takes an iterator of events as input. use std::collections::HashMap; use std::io::{self, Write}; use crate::escape::{escape_href, escape_html, StrWrite, WriteWrapper}; use crate::strings::CowStr; use crate::Event::*; use crate::{Alignment, CodeBlockKind, Event, LinkType, Tag}; 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, table_state: TableState, table_alignments: Vec, table_cell_index: usize, numbers: HashMap, usize>, } 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, table_state: TableState::Head, table_alignments: vec![], table_cell_index: 0, numbers: HashMap::new(), } } /// Writes a new line. fn write_newline(&mut self) -> io::Result<()> { 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) -> io::Result<()> { self.writer.write_str(s)?; if !s.is_empty() { self.end_newline = s.ends_with('\n'); } Ok(()) } fn run(mut self) -> io::Result<()> { while let Some(event) = self.iter.next() { match event { Start(tag) => { self.start_tag(tag)?; } End(tag) => { self.end_tag(tag)?; } Text(text) => { escape_html(&mut self.writer, &text)?; self.end_newline = text.ends_with('\n'); } Code(text) => { self.write("")?; escape_html(&mut self.writer, &text)?; self.write("")?; } Html(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")?; } } } Ok(()) } /// Writes the start of an HTML tag. fn start_tag(&mut self, tag: Tag<'a>) -> io::Result<()> { match tag { Tag::Paragraph => { if self.end_newline { self.write("

") } else { self.write("\n

") } } Tag::Heading(level, id, classes) => { if self.end_newline { self.end_newline = false; 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("\"")?; } 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 => { if self.end_newline { self.write("
\n") } else { self.write("\n
\n") } } 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("
\n")?; } Tag::TableHead => { self.write("\n")?; self.table_state = TableState::Body; } Tag::TableRow => { self.write("\n")?; } Tag::TableCell => { match self.table_state { TableState::Head => { self.write("")?; } TableState::Body => { self.write("")?; } } self.table_cell_index += 1; } Tag::BlockQuote => { self.write("\n")?; } Tag::CodeBlock(_) => { self.write("\n")?; } Tag::List(Some(_)) => { self.write("\n")?; } Tag::List(None) => { self.write("\n")?; } Tag::Item => { self.write("\n")?; } Tag::Emphasis => { self.write("")?; } Tag::Strong => { self.write("")?; } Tag::Strikethrough => { self.write("")?; } Tag::Link(_, _, _) => { self.write("")?; } Tag::Image(_, _, _) => (), // shouldn't happen, handled in start Tag::FootnoteDefinition(_) => { self.write("\n")?; } } Ok(()) } // run raw text, consuming end tag fn raw_text(&mut self) -> io::Result<()> { let mut nest = 0; while let Some(event) = self.iter.next() { match event { Start(_) => nest += 1, End(_) => { if nest == 0 { break; } nest -= 1; } Html(text) | Code(text) | Text(text) => { escape_html(&mut self.writer, &text)?; self.end_newline = text.ends_with('\n'); } SoftBreak | HardBreak | Rule => { self.write(" ")?; } FootnoteReference(name) => { let len = self.numbers.len() + 1; let number = *self.numbers.entry(name).or_insert(len); write!(&mut self.writer, "[{}]", number)?; } TaskListMarker(true) => self.write("[x]")?, TaskListMarker(false) => self.write("[ ]")?, } } Ok(()) } } /// Iterate over an `Iterator` of `Event`s, generate HTML for each `Event`, and /// push it to a `String`. /// /// # Examples /// /// ``` /// use pulldown_cmark::{html, Parser}; /// /// let markdown_str = r#" /// hello /// ===== /// /// * alpha /// * beta /// "#; /// let parser = Parser::new(markdown_str); /// /// let mut html_buf = String::new(); /// html::push_html(&mut html_buf, parser); /// /// assert_eq!(html_buf, r#"

hello

///
    ///
  • alpha
  • ///
  • beta
  • ///
/// "#); /// ``` pub fn push_html<'a, I>(s: &mut String, iter: I) where I: Iterator>, { HtmlWriter::new(iter, s).run().unwrap(); } /// Iterate over an `Iterator` of `Event`s, generate HTML for each `Event`, and /// write it out to a writable stream. /// /// **Note**: using this function with an unbuffered writer like a file or socket /// will result in poor performance. Wrap these in a /// [`BufWriter`](https://doc.rust-lang.org/std/io/struct.BufWriter.html) to /// prevent unnecessary slowdowns. /// /// # Examples /// /// ``` /// use pulldown_cmark::{html, Parser}; /// use std::io::Cursor; /// /// let markdown_str = r#" /// hello /// ===== /// /// * alpha /// * beta /// "#; /// let mut bytes = Vec::new(); /// let parser = Parser::new(markdown_str); /// /// html::write_html(Cursor::new(&mut bytes), parser); /// /// assert_eq!(&String::from_utf8_lossy(&bytes)[..], r#"

hello

///
    ///
  • alpha
  • ///
  • beta
  • ///
/// "#); /// ``` pub fn write_html<'a, I, W>(writer: W, iter: I) -> io::Result<()> where I: Iterator>, W: Write, { HtmlWriter::new(iter, WriteWrapper(writer)).run() }