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;
24
25use crate::strings::CowStr;
26use crate::Event::*;
27use crate::{Alignment, BlockQuoteKind, CodeBlockKind, Event, LinkType, Tag, TagEnd};
28use pulldown_cmark_escape::{
29 escape_href, escape_html, escape_html_body_text, FmtWriter, IoWriter, StrWrite,
30};
31
32enum TableState {
33 Head,
34 Body,
35}
36
37struct HtmlWriter<'a, I, W> {
38 /// Iterator supplying events.
39 iter: I,
40
41 /// Writer to write to.
42 writer: W,
43
44 /// Whether or not the last write wrote a newline.
45 end_newline: bool,
46
47 /// Whether if inside a metadata block (text should not be written)
48 in_non_writing_block: bool,
49
50 table_state: TableState,
51 table_alignments: Vec<Alignment>,
52 table_cell_index: usize,
53 numbers: HashMap<CowStr<'a>, usize>,
54}
55
56impl<'a, I, W> HtmlWriter<'a, I, W>
57where
58 I: Iterator<Item = Event<'a>>,
59 W: StrWrite,
60{
61 fn new(iter: I, writer: W) -> Self {
62 Self {
63 iter,
64 writer,
65 end_newline: true,
66 in_non_writing_block: false,
67 table_state: TableState::Head,
68 table_alignments: vec![],
69 table_cell_index: 0,
70 numbers: HashMap::new(),
71 }
72 }
73
74 /// Writes a new line.
75 #[inline]
76 fn write_newline(&mut self) -> Result<(), W::Error> {
77 self.end_newline = true;
78 self.writer.write_str("\n")
79 }
80
81 /// Writes a buffer, and tracks whether or not a newline was written.
82 #[inline]
83 fn write(&mut self, s: &str) -> Result<(), W::Error> {
84 self.writer.write_str(s)?;
85
86 if !s.is_empty() {
87 self.end_newline = s.ends_with('\n');
88 }
89 Ok(())
90 }
91
92 fn run(mut self) -> Result<(), W::Error> {
93 while let Some(event) = self.iter.next() {
94 match event {
95 Start(tag) => {
96 self.start_tag(tag)?;
97 }
98 End(tag) => {
99 self.end_tag(tag)?;
100 }
101 Text(text) => {
102 if !self.in_non_writing_block {
103 escape_html_body_text(&mut self.writer, &text)?;
104 self.end_newline = text.ends_with('\n');
105 }
106 }
107 Code(text) => {
108 self.write("<code>")?;
109 escape_html_body_text(&mut self.writer, &text)?;
110 self.write("</code>")?;
111 }
112 InlineMath(text) => {
113 self.write(r#"<span class="math math-inline">"#)?;
114 escape_html(&mut self.writer, &text)?;
115 self.write("</span>")?;
116 }
117 DisplayMath(text) => {
118 self.write(r#"<span class="math math-display">"#)?;
119 escape_html(&mut self.writer, &text)?;
120 self.write("</span>")?;
121 }
122 Html(html) | InlineHtml(html) => {
123 self.write(&html)?;
124 }
125 SoftBreak => {
126 self.write_newline()?;
127 }
128 HardBreak => {
129 self.write("<br />\n")?;
130 }
131 Rule => {
132 if self.end_newline {
133 self.write("<hr />\n")?;
134 } else {
135 self.write("\n<hr />\n")?;
136 }
137 }
138 FootnoteReference(name) => {
139 let len = self.numbers.len() + 1;
140 self.write("<sup class=\"footnote-reference\"><a href=\"#")?;
141 escape_html(&mut self.writer, &name)?;
142 self.write("\">")?;
143 let number = *self.numbers.entry(name).or_insert(len);
144 write!(&mut self.writer, "{}", number)?;
145 self.write("</a></sup>")?;
146 }
147 TaskListMarker(true) => {
148 self.write("<input disabled=\"\" type=\"checkbox\" checked=\"\"/>\n")?;
149 }
150 TaskListMarker(false) => {
151 self.write("<input disabled=\"\" type=\"checkbox\"/>\n")?;
152 }
153 }
154 }
155 Ok(())
156 }
157
158 /// Writes the start of an HTML tag.
159 fn start_tag(&mut self, tag: Tag<'a>) -> Result<(), W::Error> {
160 match tag {
161 Tag::HtmlBlock => Ok(()),
162 Tag::Paragraph => {
163 if self.end_newline {
164 self.write("<p>")
165 } else {
166 self.write("\n<p>")
167 }
168 }
169 Tag::Heading {
170 level,
171 id,
172 classes,
173 attrs,
174 } => {
175 if self.end_newline {
176 self.write("<")?;
177 } else {
178 self.write("\n<")?;
179 }
180 write!(&mut self.writer, "{}", level)?;
181 if let Some(id) = id {
182 self.write(" id=\"")?;
183 escape_html(&mut self.writer, &id)?;
184 self.write("\"")?;
185 }
186 let mut classes = classes.iter();
187 if let Some(class) = classes.next() {
188 self.write(" class=\"")?;
189 escape_html(&mut self.writer, class)?;
190 for class in classes {
191 self.write(" ")?;
192 escape_html(&mut self.writer, class)?;
193 }
194 self.write("\"")?;
195 }
196 for (attr, value) in attrs {
197 self.write(" ")?;
198 escape_html(&mut self.writer, &attr)?;
199 if let Some(val) = value {
200 self.write("=\"")?;
201 escape_html(&mut self.writer, &val)?;
202 self.write("\"")?;
203 } else {
204 self.write("=\"\"")?;
205 }
206 }
207 self.write(">")
208 }
209 Tag::Table(alignments) => {
210 self.table_alignments = alignments;
211 self.write("<table>")
212 }
213 Tag::TableHead => {
214 self.table_state = TableState::Head;
215 self.table_cell_index = 0;
216 self.write("<thead><tr>")
217 }
218 Tag::TableRow => {
219 self.table_cell_index = 0;
220 self.write("<tr>")
221 }
222 Tag::TableCell => {
223 match self.table_state {
224 TableState::Head => {
225 self.write("<th")?;
226 }
227 TableState::Body => {
228 self.write("<td")?;
229 }
230 }
231 match self.table_alignments.get(self.table_cell_index) {
232 Some(&Alignment::Left) => self.write(" style=\"text-align: left\">"),
233 Some(&Alignment::Center) => self.write(" style=\"text-align: center\">"),
234 Some(&Alignment::Right) => self.write(" style=\"text-align: right\">"),
235 _ => self.write(">"),
236 }
237 }
238 Tag::BlockQuote(kind) => {
239 let class_str = match kind {
240 None => "",
241 Some(kind) => match kind {
242 BlockQuoteKind::Note => " class=\"markdown-alert-note\"",
243 BlockQuoteKind::Tip => " class=\"markdown-alert-tip\"",
244 BlockQuoteKind::Important => " class=\"markdown-alert-important\"",
245 BlockQuoteKind::Warning => " class=\"markdown-alert-warning\"",
246 BlockQuoteKind::Caution => " class=\"markdown-alert-caution\"",
247 },
248 };
249 if self.end_newline {
250 self.write(&format!("<blockquote{}>\n", class_str))
251 } else {
252 self.write(&format!("\n<blockquote{}>\n", class_str))
253 }
254 }
255 Tag::CodeBlock(info) => {
256 if !self.end_newline {
257 self.write_newline()?;
258 }
259 match info {
260 CodeBlockKind::Fenced(info) => {
261 let lang = info.split(' ').next().unwrap();
262 if lang.is_empty() {
263 self.write("<pre><code>")
264 } else {
265 self.write("<pre><code class=\"language-")?;
266 escape_html(&mut self.writer, lang)?;
267 self.write("\">")
268 }
269 }
270 CodeBlockKind::Indented => self.write("<pre><code>"),
271 }
272 }
273 Tag::List(Some(1)) => {
274 if self.end_newline {
275 self.write("<ol>\n")
276 } else {
277 self.write("\n<ol>\n")
278 }
279 }
280 Tag::List(Some(start)) => {
281 if self.end_newline {
282 self.write("<ol start=\"")?;
283 } else {
284 self.write("\n<ol start=\"")?;
285 }
286 write!(&mut self.writer, "{}", start)?;
287 self.write("\">\n")
288 }
289 Tag::List(None) => {
290 if self.end_newline {
291 self.write("<ul>\n")
292 } else {
293 self.write("\n<ul>\n")
294 }
295 }
296 Tag::Item => {
297 if self.end_newline {
298 self.write("<li>")
299 } else {
300 self.write("\n<li>")
301 }
302 }
303 Tag::Emphasis => self.write("<em>"),
304 Tag::Strong => self.write("<strong>"),
305 Tag::Strikethrough => self.write("<del>"),
306 Tag::Link {
307 link_type: LinkType::Email,
308 dest_url,
309 title,
310 id: _,
311 } => {
312 self.write("<a href=\"mailto:")?;
313 escape_href(&mut self.writer, &dest_url)?;
314 if !title.is_empty() {
315 self.write("\" title=\"")?;
316 escape_html(&mut self.writer, &title)?;
317 }
318 self.write("\">")
319 }
320 Tag::Link {
321 link_type: _,
322 dest_url,
323 title,
324 id: _,
325 } => {
326 self.write("<a href=\"")?;
327 escape_href(&mut self.writer, &dest_url)?;
328 if !title.is_empty() {
329 self.write("\" title=\"")?;
330 escape_html(&mut self.writer, &title)?;
331 }
332 self.write("\">")
333 }
334 Tag::Image {
335 link_type: _,
336 dest_url,
337 title,
338 id: _,
339 } => {
340 self.write("<img src=\"")?;
341 escape_href(&mut self.writer, &dest_url)?;
342 self.write("\" alt=\"")?;
343 self.raw_text()?;
344 if !title.is_empty() {
345 self.write("\" title=\"")?;
346 escape_html(&mut self.writer, &title)?;
347 }
348 self.write("\" />")
349 }
350 Tag::FootnoteDefinition(name) => {
351 if self.end_newline {
352 self.write("<div class=\"footnote-definition\" id=\"")?;
353 } else {
354 self.write("\n<div class=\"footnote-definition\" id=\"")?;
355 }
356 escape_html(&mut self.writer, &name)?;
357 self.write("\"><sup class=\"footnote-definition-label\">")?;
358 let len = self.numbers.len() + 1;
359 let number = *self.numbers.entry(name).or_insert(len);
360 write!(&mut self.writer, "{}", number)?;
361 self.write("</sup>")
362 }
363 Tag::MetadataBlock(_) => {
364 self.in_non_writing_block = true;
365 Ok(())
366 }
367 }
368 }
369
370 fn end_tag(&mut self, tag: TagEnd) -> Result<(), W::Error> {
371 match tag {
372 TagEnd::HtmlBlock => {}
373 TagEnd::Paragraph => {
374 self.write("</p>\n")?;
375 }
376 TagEnd::Heading(level) => {
377 self.write("</")?;
378 write!(&mut self.writer, "{}", level)?;
379 self.write(">\n")?;
380 }
381 TagEnd::Table => {
382 self.write("</tbody></table>\n")?;
383 }
384 TagEnd::TableHead => {
385 self.write("</tr></thead><tbody>\n")?;
386 self.table_state = TableState::Body;
387 }
388 TagEnd::TableRow => {
389 self.write("</tr>\n")?;
390 }
391 TagEnd::TableCell => {
392 match self.table_state {
393 TableState::Head => {
394 self.write("</th>")?;
395 }
396 TableState::Body => {
397 self.write("</td>")?;
398 }
399 }
400 self.table_cell_index += 1;
401 }
402 TagEnd::BlockQuote => {
403 self.write("</blockquote>\n")?;
404 }
405 TagEnd::CodeBlock => {
406 self.write("</code></pre>\n")?;
407 }
408 TagEnd::List(true) => {
409 self.write("</ol>\n")?;
410 }
411 TagEnd::List(false) => {
412 self.write("</ul>\n")?;
413 }
414 TagEnd::Item => {
415 self.write("</li>\n")?;
416 }
417 TagEnd::Emphasis => {
418 self.write("</em>")?;
419 }
420 TagEnd::Strong => {
421 self.write("</strong>")?;
422 }
423 TagEnd::Strikethrough => {
424 self.write("</del>")?;
425 }
426 TagEnd::Link => {
427 self.write("</a>")?;
428 }
429 TagEnd::Image => (), // shouldn't happen, handled in start
430 TagEnd::FootnoteDefinition => {
431 self.write("</div>\n")?;
432 }
433 TagEnd::MetadataBlock(_) => {
434 self.in_non_writing_block = false;
435 }
436 }
437 Ok(())
438 }
439
440 // run raw text, consuming end tag
441 fn raw_text(&mut self) -> Result<(), W::Error> {
442 let mut nest = 0;
443 while let Some(event) = self.iter.next() {
444 match event {
445 Start(_) => nest += 1,
446 End(_) => {
447 if nest == 0 {
448 break;
449 }
450 nest -= 1;
451 }
452 Html(_) => {}
453 InlineHtml(text) | Code(text) | Text(text) => {
454 // Don't use escape_html_body_text here.
455 // The output of this function is used in the `alt` attribute.
456 escape_html(&mut self.writer, &text)?;
457 self.end_newline = text.ends_with('\n');
458 }
459 InlineMath(text) => {
460 self.write("$")?;
461 escape_html(&mut self.writer, &text)?;
462 self.write("$")?;
463 }
464 DisplayMath(text) => {
465 self.write("$$")?;
466 escape_html(&mut self.writer, &text)?;
467 self.write("$$")?;
468 }
469 SoftBreak | HardBreak | Rule => {
470 self.write(" ")?;
471 }
472 FootnoteReference(name) => {
473 let len = self.numbers.len() + 1;
474 let number = *self.numbers.entry(name).or_insert(len);
475 write!(&mut self.writer, "[{}]", number)?;
476 }
477 TaskListMarker(true) => self.write("[x]")?,
478 TaskListMarker(false) => self.write("[ ]")?,
479 }
480 }
481 Ok(())
482 }
483}
484
485/// Iterate over an `Iterator` of `Event`s, generate HTML for each `Event`, and
486/// push it to a `String`.
487///
488/// # Examples
489///
490/// ```
491/// use pulldown_cmark::{html, Parser};
492///
493/// let markdown_str = r#"
494/// hello
495/// =====
496///
497/// * alpha
498/// * beta
499/// "#;
500/// let parser = Parser::new(markdown_str);
501///
502/// let mut html_buf = String::new();
503/// html::push_html(&mut html_buf, parser);
504///
505/// assert_eq!(html_buf, r#"<h1>hello</h1>
506/// <ul>
507/// <li>alpha</li>
508/// <li>beta</li>
509/// </ul>
510/// "#);
511/// ```
512pub fn push_html<'a, I>(s: &mut String, iter: I)
513where
514 I: Iterator<Item = Event<'a>>,
515{
516 write_html_fmt(writer:s, iter).unwrap()
517}
518
519/// Iterate over an `Iterator` of `Event`s, generate HTML for each `Event`, and
520/// write it out to an I/O stream.
521///
522/// **Note**: using this function with an unbuffered writer like a file or socket
523/// will result in poor performance. Wrap these in a
524/// [`BufWriter`](https://doc.rust-lang.org/std/io/struct.BufWriter.html) to
525/// prevent unnecessary slowdowns.
526///
527/// # Examples
528///
529/// ```
530/// use pulldown_cmark::{html, Parser};
531/// use std::io::Cursor;
532///
533/// let markdown_str = r#"
534/// hello
535/// =====
536///
537/// * alpha
538/// * beta
539/// "#;
540/// let mut bytes = Vec::new();
541/// let parser = Parser::new(markdown_str);
542///
543/// html::write_html_io(Cursor::new(&mut bytes), parser);
544///
545/// assert_eq!(&String::from_utf8_lossy(&bytes)[..], r#"<h1>hello</h1>
546/// <ul>
547/// <li>alpha</li>
548/// <li>beta</li>
549/// </ul>
550/// "#);
551/// ```
552pub fn write_html_io<'a, I, W>(writer: W, iter: I) -> std::io::Result<()>
553where
554 I: Iterator<Item = Event<'a>>,
555 W: std::io::Write,
556{
557 HtmlWriter::new(iter, writer:IoWriter(writer)).run()
558}
559
560/// Iterate over an `Iterator` of `Event`s, generate HTML for each `Event`, and
561/// write it into Unicode-accepting buffer or stream.
562///
563/// # Examples
564///
565/// ```
566/// use pulldown_cmark::{html, Parser};
567///
568/// let markdown_str = r#"
569/// hello
570/// =====
571///
572/// * alpha
573/// * beta
574/// "#;
575/// let mut buf = String::new();
576/// let parser = Parser::new(markdown_str);
577///
578/// html::write_html_fmt(&mut buf, parser);
579///
580/// assert_eq!(buf, r#"<h1>hello</h1>
581/// <ul>
582/// <li>alpha</li>
583/// <li>beta</li>
584/// </ul>
585/// "#);
586/// ```
587pub fn write_html_fmt<'a, I, W>(writer: W, iter: I) -> std::fmt::Result
588where
589 I: Iterator<Item = Event<'a>>,
590 W: std::fmt::Write,
591{
592 HtmlWriter::new(iter, writer:FmtWriter(writer)).run()
593}
594