use std::collections::HashMap;
use std::io::{self, Write};
use crate::parse::{LinkType, Event, Tag, Alignment};
use crate::parse::Event::*;
use crate::strings::CowStr;
use crate::escape::{escape_html, escape_href};
enum TableState {
Head,
Body,
}
struct HtmlWriter<'a, I, W>
where
W: Write,
{
iter: I,
writer: W,
end_newline: bool,
table_state: TableState,
table_alignments: Vec<Alignment>,
table_cell_index: usize,
numbers: HashMap<CowStr<'a>, usize>,
}
impl<'a, I, W> HtmlWriter<'a, I, W>
where
I: Iterator<Item = Event<'a>>,
W: Write,
{
fn write_newline(&mut self) -> io::Result<()> {
self.end_newline = true;
self.writer.write_all(&[b'\n'])
}
fn write(&mut self, bytes: &[u8], write_newline: bool) -> io::Result<()> {
self.writer.write_all(bytes)?;
if write_newline {
self.write_newline()
} else {
if !bytes.is_empty() {
self.end_newline = bytes[bytes.len() - 1] == b'\n';
}
Ok(())
}
}
fn fresh_line(&mut self) -> io::Result<()> {
if !self.end_newline {
self.write_newline()
} else {
Ok(())
}
}
pub 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(b"<code>", false)?;
escape_html(&mut self.writer, &text)?;
self.write(b"</code>", false)?;
self.end_newline = false;
}
Html(html) | InlineHtml(html) => {
self.write(html.as_bytes(), false)?;
}
SoftBreak => {
self.write_newline()?;
}
HardBreak => {
self.write(b"<br />", true)?;
}
FootnoteReference(name) => {
let len = self.numbers.len() + 1;
self.write(b"<sup class=\"footnote-reference\"><a href=\"#", false)?;
escape_html(&mut self.writer, &name)?;
self.write(b"\">", false)?;
let number = *self.numbers.entry(name).or_insert(len);
self.write(format!("{}", number).as_bytes(), false)?;
self.write(b"</a></sup>", false)?;
}
TaskListMarker(is_checked) => {
self.write(b"<input disabled=\"\" type=\"checkbox\"", false)?;
if is_checked {
self.write(b" checked=\"\"", false)?;
}
self.write(b"/>", true)?;
}
}
}
Ok(())
}
fn start_tag(&mut self, tag: Tag<'a>) -> io::Result<()> {
match tag {
Tag::Paragraph => {
self.fresh_line()?;
self.write(b"<p>", false)
}
Tag::Rule => {
self.fresh_line()?;
self.write(b"<hr />", true)
}
Tag::Header(level) => {
self.fresh_line()?;
self.write(b"<h", false)?;
self.write(&[(b'0' + level as u8)], false)?;
self.write(b">", false)
}
Tag::Table(alignments) => {
self.table_alignments = alignments;
self.write(b"<table>", false)
}
Tag::TableHead => {
self.table_state = TableState::Head;
self.table_cell_index = 0;
self.write(b"<thead><tr>", false)
}
Tag::TableRow => {
self.table_cell_index = 0;
self.write(b"<tr>", false)
}
Tag::TableCell => {
match self.table_state {
TableState::Head => {
self.write(b"<th", false)?;
}
TableState::Body => {
self.write(b"<td", false)?;
}
}
match self.table_alignments.get(self.table_cell_index) {
Some(&Alignment::Left) => {
self.write(b" align=\"left\"", false)?;
}
Some(&Alignment::Center) => {
self.write(b" align=\"center\"", false)?;
}
Some(&Alignment::Right) => {
self.write(b" align=\"right\"", false)?;
}
_ => (),
}
self.write(b">", false)
}
Tag::BlockQuote => {
self.fresh_line()?;
self.write(b"<blockquote>", true)
}
Tag::CodeBlock(info) => {
self.fresh_line()?;
let lang = info.split(' ').next().unwrap();
if lang.is_empty() {
self.write(b"<pre><code>", false)
} else {
self.write(b"<pre><code class=\"language-", false)?;
escape_html(&mut self.writer, lang)?;
self.write(b"\">", false)
}
}
Tag::List(Some(1)) => {
self.fresh_line()?;
self.write(b"<ol>", true)
}
Tag::List(Some(start)) => {
self.fresh_line()?;
self.write(b"<ol start=\"", false)?;
self.write(format!("{}", start).as_bytes(), false)?;
self.write(b"\">", true)
}
Tag::List(None) => {
self.fresh_line()?;
self.write(b"<ul>", true)
}
Tag::Item => {
self.fresh_line()?;
self.write(b"<li>", false)
}
Tag::Emphasis => self.write(b"<em>", false),
Tag::Strong => self.write(b"<strong>", false),
Tag::Strikethrough => self.write(b"<del>", false),
Tag::Link(LinkType::Email, dest, title) => {
self.write(b"<a href=\"mailto:", false)?;
escape_href(&mut self.writer, &dest)?;
if !title.is_empty() {
self.write(b"\" title=\"", false)?;
escape_html(&mut self.writer, &title)?;
}
self.write(b"\">", false)
}
Tag::Link(_link_type, dest, title) => {
self.write(b"<a href=\"", false)?;
escape_href(&mut self.writer, &dest)?;
if !title.is_empty() {
self.write(b"\" title=\"", false)?;
escape_html(&mut self.writer, &title)?;
}
self.write(b"\">", false)
}
Tag::Image(_link_type, dest, title) => {
self.write(b"<img src=\"", false)?;
escape_href(&mut self.writer, &dest)?;
self.write(b"\" alt=\"", false)?;
self.raw_text()?;
if !title.is_empty() {
self.write(b"\" title=\"", false)?;
escape_html(&mut self.writer, &title)?;
}
self.write(b"\" />", false)
}
Tag::FootnoteDefinition(name) => {
self.fresh_line()?;
let len = self.numbers.len() + 1;
self.write(b"<div class=\"footnote-definition\" id=\"", false)?;
escape_html(&mut self.writer, &*name)?;
self.write(b"\"><sup class=\"footnote-definition-label\">", false)?;
let number = *self.numbers.entry(name).or_insert(len);
self.write(&*format!("{}", number).as_bytes(), false)?;
self.write(b"</sup>", false)
}
Tag::HtmlBlock => Ok(())
}
}
fn end_tag(&mut self, tag: Tag) -> io::Result<()> {
match tag {
Tag::Paragraph => {
self.write(b"</p>", true)?;
}
Tag::Rule => (),
Tag::Header(level) => {
self.write(b"</h", false)?;
self.write(&[(b'0' + level as u8)], false)?;
self.write(b">", true)?;
}
Tag::Table(_) => {
self.write(b"</tbody></table>", true)?;
}
Tag::TableHead => {
self.write(b"</tr></thead><tbody>", true)?;
self.table_state = TableState::Body;
}
Tag::TableRow => {
self.write(b"</tr>", true)?;
}
Tag::TableCell => {
match self.table_state {
TableState::Head => {
self.write(b"</th>", false)?;
}
TableState::Body => {
self.write(b"</td>", false)?;
}
}
self.table_cell_index += 1;
}
Tag::BlockQuote => {
self.write(b"</blockquote>", true)?;
}
Tag::CodeBlock(_) => {
self.write(b"</code></pre>", true)?;
}
Tag::List(Some(_)) => {
self.write(b"</ol>", true)?;
}
Tag::List(None) => {
self.write(b"</ul>", true)?;
}
Tag::Item => {
self.write(b"</li>", true)?;
}
Tag::Emphasis => {
self.write(b"</em>", false)?;
}
Tag::Strong => {
self.write(b"</strong>", false)?;
}
Tag::Strikethrough => {
self.write(b"</del>", false)?;
}
Tag::Link(_, _, _) => {
self.write(b"</a>", false)?;
}
Tag::Image(_, _, _) => (),
Tag::FootnoteDefinition(_) => {
self.write(b"</div>", true)?;
}
Tag::HtmlBlock => {}
}
Ok(())
}
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(_) => (),
InlineHtml(text) | Code(text) | Text(text) => {
escape_html(&mut self.writer, &text)?;
self.end_newline = text.ends_with('\n');
}
SoftBreak | HardBreak => {
self.write(b" ", false)?;
}
FootnoteReference(name) => {
let len = self.numbers.len() + 1;
let number = *self.numbers.entry(name).or_insert(len);
self.write(&*format!("[{}]", number).as_bytes(), false)?;
}
TaskListMarker(true) => self.write(b"[x]", false)?,
TaskListMarker(false) => self.write(b"[ ]", false)?,
}
}
Ok(())
}
}
pub fn push_html<'a, I>(s: &mut String, iter: I)
where
I: Iterator<Item = Event<'a>>,
{
unsafe {
write_html(s.as_mut_vec(), iter).unwrap();
}
}
pub fn write_html<'a, I, W>(writer: W, iter: I) -> io::Result<()>
where
I: Iterator<Item = Event<'a>>,
W: Write,
{
let writer = HtmlWriter {
iter,
writer,
end_newline: true,
table_state: TableState::Head,
table_alignments: vec![],
table_cell_index: 0,
numbers: HashMap::new(),
};
writer.run()
}