1// Copyright 2015 Google Inc. All rights reserved.
2//
3// Permission is hereby granted, free of charge, to any person obtaining a copy
4// of this software and associated documentation files (the "Software"), to deal
5// in the Software without restriction, including without limitation the rights
6// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7// copies of the Software, and to permit persons to whom the Software is
8// furnished to do so, subject to the following conditions:
9//
10// The above copyright notice and this permission notice shall be included in
11// all copies or substantial portions of the Software.
12//
13// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19// THE SOFTWARE.
20
21//! HTML renderer that takes an iterator of events as input.
22
23use std::collections::HashMap;
24use std::io::{self, Write};
25
26use crate::escape::{escape_href, escape_html, StrWrite, WriteWrapper};
27use crate::strings::CowStr;
28use crate::Event::*;
29use crate::{Alignment, CodeBlockKind, Event, LinkType, Tag};
30
31enum TableState {
32 Head,
33 Body,
34}
35
36struct HtmlWriter<'a, I, W> {
37 /// Iterator supplying events.
38 iter: I,
39
40 /// Writer to write to.
41 writer: W,
42
43 /// Whether or not the last write wrote a newline.
44 end_newline: bool,
45
46 table_state: TableState,
47 table_alignments: Vec<Alignment>,
48 table_cell_index: usize,
49 numbers: HashMap<CowStr<'a>, usize>,
50}
51
52impl<'a, I, W> HtmlWriter<'a, I, W>
53where
54 I: Iterator<Item = Event<'a>>,
55 W: StrWrite,
56{
57 fn new(iter: I, writer: W) -> Self {
58 Self {
59 iter,
60 writer,
61 end_newline: true,
62 table_state: TableState::Head,
63 table_alignments: vec![],
64 table_cell_index: 0,
65 numbers: HashMap::new(),
66 }
67 }
68
69 /// Writes a new line.
70 fn write_newline(&mut self) -> io::Result<()> {
71 self.end_newline = true;
72 self.writer.write_str("\n")
73 }
74
75 /// Writes a buffer, and tracks whether or not a newline was written.
76 #[inline]
77 fn write(&mut self, s: &str) -> io::Result<()> {
78 self.writer.write_str(s)?;
79
80 if !s.is_empty() {
81 self.end_newline = s.ends_with('\n');
82 }
83 Ok(())
84 }
85
86 fn run(mut self) -> io::Result<()> {
87 while let Some(event) = self.iter.next() {
88 match event {
89 Start(tag) => {
90 self.start_tag(tag)?;
91 }
92 End(tag) => {
93 self.end_tag(tag)?;
94 }
95 Text(text) => {
96 escape_html(&mut self.writer, &text)?;
97 self.end_newline = text.ends_with('\n');
98 }
99 Code(text) => {
100 self.write("<code>")?;
101 escape_html(&mut self.writer, &text)?;
102 self.write("</code>")?;
103 }
104 Html(html) => {
105 self.write(&html)?;
106 }
107 SoftBreak => {
108 self.write_newline()?;
109 }
110 HardBreak => {
111 self.write("<br />\n")?;
112 }
113 Rule => {
114 if self.end_newline {
115 self.write("<hr />\n")?;
116 } else {
117 self.write("\n<hr />\n")?;
118 }
119 }
120 FootnoteReference(name) => {
121 let len = self.numbers.len() + 1;
122 self.write("<sup class=\"footnote-reference\"><a href=\"#")?;
123 escape_html(&mut self.writer, &name)?;
124 self.write("\">")?;
125 let number = *self.numbers.entry(name).or_insert(len);
126 write!(&mut self.writer, "{}", number)?;
127 self.write("</a></sup>")?;
128 }
129 TaskListMarker(true) => {
130 self.write("<input disabled=\"\" type=\"checkbox\" checked=\"\"/>\n")?;
131 }
132 TaskListMarker(false) => {
133 self.write("<input disabled=\"\" type=\"checkbox\"/>\n")?;
134 }
135 }
136 }
137 Ok(())
138 }
139
140 /// Writes the start of an HTML tag.
141 fn start_tag(&mut self, tag: Tag<'a>) -> io::Result<()> {
142 match tag {
143 Tag::Paragraph => {
144 if self.end_newline {
145 self.write("<p>")
146 } else {
147 self.write("\n<p>")
148 }
149 }
150 Tag::Heading(level, id, classes) => {
151 if self.end_newline {
152 self.end_newline = false;
153 self.write("<")?;
154 } else {
155 self.write("\n<")?;
156 }
157 write!(&mut self.writer, "{}", level)?;
158 if let Some(id) = id {
159 self.write(" id=\"")?;
160 escape_html(&mut self.writer, id)?;
161 self.write("\"")?;
162 }
163 let mut classes = classes.iter();
164 if let Some(class) = classes.next() {
165 self.write(" class=\"")?;
166 escape_html(&mut self.writer, class)?;
167 for class in classes {
168 self.write(" ")?;
169 escape_html(&mut self.writer, class)?;
170 }
171 self.write("\"")?;
172 }
173 self.write(">")
174 }
175 Tag::Table(alignments) => {
176 self.table_alignments = alignments;
177 self.write("<table>")
178 }
179 Tag::TableHead => {
180 self.table_state = TableState::Head;
181 self.table_cell_index = 0;
182 self.write("<thead><tr>")
183 }
184 Tag::TableRow => {
185 self.table_cell_index = 0;
186 self.write("<tr>")
187 }
188 Tag::TableCell => {
189 match self.table_state {
190 TableState::Head => {
191 self.write("<th")?;
192 }
193 TableState::Body => {
194 self.write("<td")?;
195 }
196 }
197 match self.table_alignments.get(self.table_cell_index) {
198 Some(&Alignment::Left) => self.write(" style=\"text-align: left\">"),
199 Some(&Alignment::Center) => self.write(" style=\"text-align: center\">"),
200 Some(&Alignment::Right) => self.write(" style=\"text-align: right\">"),
201 _ => self.write(">"),
202 }
203 }
204 Tag::BlockQuote => {
205 if self.end_newline {
206 self.write("<blockquote>\n")
207 } else {
208 self.write("\n<blockquote>\n")
209 }
210 }
211 Tag::CodeBlock(info) => {
212 if !self.end_newline {
213 self.write_newline()?;
214 }
215 match info {
216 CodeBlockKind::Fenced(info) => {
217 let lang = info.split(' ').next().unwrap();
218 if lang.is_empty() {
219 self.write("<pre><code>")
220 } else {
221 self.write("<pre><code class=\"language-")?;
222 escape_html(&mut self.writer, lang)?;
223 self.write("\">")
224 }
225 }
226 CodeBlockKind::Indented => self.write("<pre><code>"),
227 }
228 }
229 Tag::List(Some(1)) => {
230 if self.end_newline {
231 self.write("<ol>\n")
232 } else {
233 self.write("\n<ol>\n")
234 }
235 }
236 Tag::List(Some(start)) => {
237 if self.end_newline {
238 self.write("<ol start=\"")?;
239 } else {
240 self.write("\n<ol start=\"")?;
241 }
242 write!(&mut self.writer, "{}", start)?;
243 self.write("\">\n")
244 }
245 Tag::List(None) => {
246 if self.end_newline {
247 self.write("<ul>\n")
248 } else {
249 self.write("\n<ul>\n")
250 }
251 }
252 Tag::Item => {
253 if self.end_newline {
254 self.write("<li>")
255 } else {
256 self.write("\n<li>")
257 }
258 }
259 Tag::Emphasis => self.write("<em>"),
260 Tag::Strong => self.write("<strong>"),
261 Tag::Strikethrough => self.write("<del>"),
262 Tag::Link(LinkType::Email, dest, title) => {
263 self.write("<a href=\"mailto:")?;
264 escape_href(&mut self.writer, &dest)?;
265 if !title.is_empty() {
266 self.write("\" title=\"")?;
267 escape_html(&mut self.writer, &title)?;
268 }
269 self.write("\">")
270 }
271 Tag::Link(_link_type, dest, title) => {
272 self.write("<a href=\"")?;
273 escape_href(&mut self.writer, &dest)?;
274 if !title.is_empty() {
275 self.write("\" title=\"")?;
276 escape_html(&mut self.writer, &title)?;
277 }
278 self.write("\">")
279 }
280 Tag::Image(_link_type, dest, title) => {
281 self.write("<img src=\"")?;
282 escape_href(&mut self.writer, &dest)?;
283 self.write("\" alt=\"")?;
284 self.raw_text()?;
285 if !title.is_empty() {
286 self.write("\" title=\"")?;
287 escape_html(&mut self.writer, &title)?;
288 }
289 self.write("\" />")
290 }
291 Tag::FootnoteDefinition(name) => {
292 if self.end_newline {
293 self.write("<div class=\"footnote-definition\" id=\"")?;
294 } else {
295 self.write("\n<div class=\"footnote-definition\" id=\"")?;
296 }
297 escape_html(&mut self.writer, &*name)?;
298 self.write("\"><sup class=\"footnote-definition-label\">")?;
299 let len = self.numbers.len() + 1;
300 let number = *self.numbers.entry(name).or_insert(len);
301 write!(&mut self.writer, "{}", number)?;
302 self.write("</sup>")
303 }
304 }
305 }
306
307 fn end_tag(&mut self, tag: Tag) -> io::Result<()> {
308 match tag {
309 Tag::Paragraph => {
310 self.write("</p>\n")?;
311 }
312 Tag::Heading(level, _id, _classes) => {
313 self.write("</")?;
314 write!(&mut self.writer, "{}", level)?;
315 self.write(">\n")?;
316 }
317 Tag::Table(_) => {
318 self.write("</tbody></table>\n")?;
319 }
320 Tag::TableHead => {
321 self.write("</tr></thead><tbody>\n")?;
322 self.table_state = TableState::Body;
323 }
324 Tag::TableRow => {
325 self.write("</tr>\n")?;
326 }
327 Tag::TableCell => {
328 match self.table_state {
329 TableState::Head => {
330 self.write("</th>")?;
331 }
332 TableState::Body => {
333 self.write("</td>")?;
334 }
335 }
336 self.table_cell_index += 1;
337 }
338 Tag::BlockQuote => {
339 self.write("</blockquote>\n")?;
340 }
341 Tag::CodeBlock(_) => {
342 self.write("</code></pre>\n")?;
343 }
344 Tag::List(Some(_)) => {
345 self.write("</ol>\n")?;
346 }
347 Tag::List(None) => {
348 self.write("</ul>\n")?;
349 }
350 Tag::Item => {
351 self.write("</li>\n")?;
352 }
353 Tag::Emphasis => {
354 self.write("</em>")?;
355 }
356 Tag::Strong => {
357 self.write("</strong>")?;
358 }
359 Tag::Strikethrough => {
360 self.write("</del>")?;
361 }
362 Tag::Link(_, _, _) => {
363 self.write("</a>")?;
364 }
365 Tag::Image(_, _, _) => (), // shouldn't happen, handled in start
366 Tag::FootnoteDefinition(_) => {
367 self.write("</div>\n")?;
368 }
369 }
370 Ok(())
371 }
372
373 // run raw text, consuming end tag
374 fn raw_text(&mut self) -> io::Result<()> {
375 let mut nest = 0;
376 while let Some(event) = self.iter.next() {
377 match event {
378 Start(_) => nest += 1,
379 End(_) => {
380 if nest == 0 {
381 break;
382 }
383 nest -= 1;
384 }
385 Html(text) | Code(text) | Text(text) => {
386 escape_html(&mut self.writer, &text)?;
387 self.end_newline = text.ends_with('\n');
388 }
389 SoftBreak | HardBreak | Rule => {
390 self.write(" ")?;
391 }
392 FootnoteReference(name) => {
393 let len = self.numbers.len() + 1;
394 let number = *self.numbers.entry(name).or_insert(len);
395 write!(&mut self.writer, "[{}]", number)?;
396 }
397 TaskListMarker(true) => self.write("[x]")?,
398 TaskListMarker(false) => self.write("[ ]")?,
399 }
400 }
401 Ok(())
402 }
403}
404
405/// Iterate over an `Iterator` of `Event`s, generate HTML for each `Event`, and
406/// push it to a `String`.
407///
408/// # Examples
409///
410/// ```
411/// use pulldown_cmark::{html, Parser};
412///
413/// let markdown_str = r#"
414/// hello
415/// =====
416///
417/// * alpha
418/// * beta
419/// "#;
420/// let parser = Parser::new(markdown_str);
421///
422/// let mut html_buf = String::new();
423/// html::push_html(&mut html_buf, parser);
424///
425/// assert_eq!(html_buf, r#"<h1>hello</h1>
426/// <ul>
427/// <li>alpha</li>
428/// <li>beta</li>
429/// </ul>
430/// "#);
431/// ```
432pub fn push_html<'a, I>(s: &mut String, iter: I)
433where
434 I: Iterator<Item = Event<'a>>,
435{
436 HtmlWriter::new(iter, writer:s).run().unwrap();
437}
438
439/// Iterate over an `Iterator` of `Event`s, generate HTML for each `Event`, and
440/// write it out to a writable stream.
441///
442/// **Note**: using this function with an unbuffered writer like a file or socket
443/// will result in poor performance. Wrap these in a
444/// [`BufWriter`](https://doc.rust-lang.org/std/io/struct.BufWriter.html) to
445/// prevent unnecessary slowdowns.
446///
447/// # Examples
448///
449/// ```
450/// use pulldown_cmark::{html, Parser};
451/// use std::io::Cursor;
452///
453/// let markdown_str = r#"
454/// hello
455/// =====
456///
457/// * alpha
458/// * beta
459/// "#;
460/// let mut bytes = Vec::new();
461/// let parser = Parser::new(markdown_str);
462///
463/// html::write_html(Cursor::new(&mut bytes), parser);
464///
465/// assert_eq!(&String::from_utf8_lossy(&bytes)[..], r#"<h1>hello</h1>
466/// <ul>
467/// <li>alpha</li>
468/// <li>beta</li>
469/// </ul>
470/// "#);
471/// ```
472pub fn write_html<'a, I, W>(writer: W, iter: I) -> io::Result<()>
473where
474 I: Iterator<Item = Event<'a>>,
475 W: Write,
476{
477 HtmlWriter::new(iter, writer:WriteWrapper(writer)).run()
478}
479