Coverage Report

Created: 2023-04-25 07:07

/rust/registry/src/index.crates.io-6f17d22bba15001f/pulldown-cmark-0.8.0/src/html.rs
Line
Count
Source (jump to first uncovered line)
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
23
use std::collections::HashMap;
24
use std::io::{self, Write};
25
26
use crate::escape::{escape_href, escape_html, StrWrite, WriteWrapper};
27
use crate::parse::Event::*;
28
use crate::parse::{Alignment, CodeBlockKind, Event, LinkType, Tag};
29
use crate::strings::CowStr;
30
31
enum TableState {
32
    Head,
33
    Body,
34
}
35
36
struct 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
52
impl<'a, I, W> HtmlWriter<'a, I, W>
53
where
54
    I: Iterator<Item = Event<'a>>,
55
    W: StrWrite,
56
{
57
0
    fn new(iter: I, writer: W) -> Self {
58
0
        Self {
59
0
            iter,
60
0
            writer,
61
0
            end_newline: true,
62
0
            table_state: TableState::Head,
63
0
            table_alignments: vec![],
64
0
            table_cell_index: 0,
65
0
            numbers: HashMap::new(),
66
0
        }
67
0
    }
68
69
    /// Writes a new line.
70
0
    fn write_newline(&mut self) -> io::Result<()> {
71
0
        self.end_newline = true;
72
0
        self.writer.write_str("\n")
73
0
    }
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
0
        self.writer.write_str(s)?;
79
80
0
        if !s.is_empty() {
81
0
            self.end_newline = s.ends_with('\n');
82
0
        }
83
0
        Ok(())
84
0
    }
85
86
0
    pub fn run(mut self) -> io::Result<()> {
87
0
        while let Some(event) = self.iter.next() {
88
0
            match event {
89
0
                Start(tag) => {
90
0
                    self.start_tag(tag)?;
91
                }
92
0
                End(tag) => {
93
0
                    self.end_tag(tag)?;
94
                }
95
0
                Text(text) => {
96
0
                    escape_html(&mut self.writer, &text)?;
97
0
                    self.end_newline = text.ends_with('\n');
98
                }
99
0
                Code(text) => {
100
0
                    self.write("<code>")?;
101
0
                    escape_html(&mut self.writer, &text)?;
102
0
                    self.write("</code>")?;
103
                }
104
0
                Html(html) => {
105
0
                    self.write(&html)?;
106
                }
107
                SoftBreak => {
108
0
                    self.write_newline()?;
109
                }
110
                HardBreak => {
111
0
                    self.write("<br />\n")?;
112
                }
113
                Rule => {
114
0
                    if self.end_newline {
115
0
                        self.write("<hr />\n")?;
116
                    } else {
117
0
                        self.write("\n<hr />\n")?;
118
                    }
119
                }
120
0
                FootnoteReference(name) => {
121
0
                    let len = self.numbers.len() + 1;
122
0
                    self.write("<sup class=\"footnote-reference\"><a href=\"#")?;
123
0
                    escape_html(&mut self.writer, &name)?;
124
0
                    self.write("\">")?;
125
0
                    let number = *self.numbers.entry(name).or_insert(len);
126
0
                    write!(&mut self.writer, "{}", number)?;
127
0
                    self.write("</a></sup>")?;
128
                }
129
                TaskListMarker(true) => {
130
0
                    self.write("<input disabled=\"\" type=\"checkbox\" checked=\"\"/>\n")?;
131
                }
132
                TaskListMarker(false) => {
133
0
                    self.write("<input disabled=\"\" type=\"checkbox\"/>\n")?;
134
                }
135
            }
136
        }
137
0
        Ok(())
138
0
    }
139
140
    /// Writes the start of an HTML tag.
141
    fn start_tag(&mut self, tag: Tag<'a>) -> io::Result<()> {
142
0
        match tag {
143
            Tag::Paragraph => {
144
0
                if self.end_newline {
145
0
                    self.write("<p>")
146
                } else {
147
0
                    self.write("\n<p>")
148
                }
149
            }
150
0
            Tag::Heading(level) => {
151
0
                if self.end_newline {
152
0
                    self.end_newline = false;
153
0
                    write!(&mut self.writer, "<h{}>", level)
154
                } else {
155
0
                    write!(&mut self.writer, "\n<h{}>", level)
156
                }
157
            }
158
0
            Tag::Table(alignments) => {
159
0
                self.table_alignments = alignments;
160
0
                self.write("<table>")
161
            }
162
            Tag::TableHead => {
163
0
                self.table_state = TableState::Head;
164
0
                self.table_cell_index = 0;
165
0
                self.write("<thead><tr>")
166
            }
167
            Tag::TableRow => {
168
0
                self.table_cell_index = 0;
169
0
                self.write("<tr>")
170
            }
171
            Tag::TableCell => {
172
0
                match self.table_state {
173
                    TableState::Head => {
174
0
                        self.write("<th")?;
175
                    }
176
                    TableState::Body => {
177
0
                        self.write("<td")?;
178
                    }
179
                }
180
0
                match self.table_alignments.get(self.table_cell_index) {
181
0
                    Some(&Alignment::Left) => self.write(" align=\"left\">"),
182
0
                    Some(&Alignment::Center) => self.write(" align=\"center\">"),
183
0
                    Some(&Alignment::Right) => self.write(" align=\"right\">"),
184
0
                    _ => self.write(">"),
185
                }
186
            }
187
            Tag::BlockQuote => {
188
0
                if self.end_newline {
189
0
                    self.write("<blockquote>\n")
190
                } else {
191
0
                    self.write("\n<blockquote>\n")
192
                }
193
            }
194
0
            Tag::CodeBlock(info) => {
195
0
                if !self.end_newline {
196
0
                    self.write_newline()?;
197
0
                }
198
0
                match info {
199
0
                    CodeBlockKind::Fenced(info) => {
200
0
                        let lang = info.split(' ').next().unwrap();
201
0
                        if lang.is_empty() {
202
0
                            self.write("<pre><code>")
203
                        } else {
204
0
                            self.write("<pre><code class=\"language-")?;
205
0
                            escape_html(&mut self.writer, lang)?;
206
0
                            self.write("\">")
207
                        }
208
                    }
209
0
                    CodeBlockKind::Indented => self.write("<pre><code>"),
210
                }
211
            }
212
            Tag::List(Some(1)) => {
213
0
                if self.end_newline {
214
0
                    self.write("<ol>\n")
215
                } else {
216
0
                    self.write("\n<ol>\n")
217
                }
218
            }
219
0
            Tag::List(Some(start)) => {
220
0
                if self.end_newline {
221
0
                    self.write("<ol start=\"")?;
222
                } else {
223
0
                    self.write("\n<ol start=\"")?;
224
                }
225
0
                write!(&mut self.writer, "{}", start)?;
226
0
                self.write("\">\n")
227
            }
228
            Tag::List(None) => {
229
0
                if self.end_newline {
230
0
                    self.write("<ul>\n")
231
                } else {
232
0
                    self.write("\n<ul>\n")
233
                }
234
            }
235
            Tag::Item => {
236
0
                if self.end_newline {
237
0
                    self.write("<li>")
238
                } else {
239
0
                    self.write("\n<li>")
240
                }
241
            }
242
0
            Tag::Emphasis => self.write("<em>"),
243
0
            Tag::Strong => self.write("<strong>"),
244
0
            Tag::Strikethrough => self.write("<del>"),
245
0
            Tag::Link(LinkType::Email, dest, title) => {
246
0
                self.write("<a href=\"mailto:")?;
247
0
                escape_href(&mut self.writer, &dest)?;
248
0
                if !title.is_empty() {
249
0
                    self.write("\" title=\"")?;
250
0
                    escape_html(&mut self.writer, &title)?;
251
0
                }
252
0
                self.write("\">")
253
            }
254
0
            Tag::Link(_link_type, dest, title) => {
255
0
                self.write("<a href=\"")?;
256
0
                escape_href(&mut self.writer, &dest)?;
257
0
                if !title.is_empty() {
258
0
                    self.write("\" title=\"")?;
259
0
                    escape_html(&mut self.writer, &title)?;
260
0
                }
261
0
                self.write("\">")
262
            }
263
0
            Tag::Image(_link_type, dest, title) => {
264
0
                self.write("<img src=\"")?;
265
0
                escape_href(&mut self.writer, &dest)?;
266
0
                self.write("\" alt=\"")?;
267
0
                self.raw_text()?;
268
0
                if !title.is_empty() {
269
0
                    self.write("\" title=\"")?;
270
0
                    escape_html(&mut self.writer, &title)?;
271
0
                }
272
0
                self.write("\" />")
273
            }
274
0
            Tag::FootnoteDefinition(name) => {
275
0
                if self.end_newline {
276
0
                    self.write("<div class=\"footnote-definition\" id=\"")?;
277
                } else {
278
0
                    self.write("\n<div class=\"footnote-definition\" id=\"")?;
279
                }
280
0
                escape_html(&mut self.writer, &*name)?;
281
0
                self.write("\"><sup class=\"footnote-definition-label\">")?;
282
0
                let len = self.numbers.len() + 1;
283
0
                let number = *self.numbers.entry(name).or_insert(len);
284
0
                write!(&mut self.writer, "{}", number)?;
285
0
                self.write("</sup>")
286
            }
287
        }
288
0
    }
289
290
    fn end_tag(&mut self, tag: Tag) -> io::Result<()> {
291
0
        match tag {
292
            Tag::Paragraph => {
293
0
                self.write("</p>\n")?;
294
            }
295
0
            Tag::Heading(level) => {
296
0
                self.write("</h")?;
297
0
                write!(&mut self.writer, "{}", level)?;
298
0
                self.write(">\n")?;
299
            }
300
            Tag::Table(_) => {
301
0
                self.write("</tbody></table>\n")?;
302
            }
303
            Tag::TableHead => {
304
0
                self.write("</tr></thead><tbody>\n")?;
305
0
                self.table_state = TableState::Body;
306
            }
307
            Tag::TableRow => {
308
0
                self.write("</tr>\n")?;
309
            }
310
            Tag::TableCell => {
311
0
                match self.table_state {
312
                    TableState::Head => {
313
0
                        self.write("</th>")?;
314
                    }
315
                    TableState::Body => {
316
0
                        self.write("</td>")?;
317
                    }
318
                }
319
0
                self.table_cell_index += 1;
320
            }
321
            Tag::BlockQuote => {
322
0
                self.write("</blockquote>\n")?;
323
            }
324
            Tag::CodeBlock(_) => {
325
0
                self.write("</code></pre>\n")?;
326
            }
327
            Tag::List(Some(_)) => {
328
0
                self.write("</ol>\n")?;
329
            }
330
            Tag::List(None) => {
331
0
                self.write("</ul>\n")?;
332
            }
333
            Tag::Item => {
334
0
                self.write("</li>\n")?;
335
            }
336
            Tag::Emphasis => {
337
0
                self.write("</em>")?;
338
            }
339
            Tag::Strong => {
340
0
                self.write("</strong>")?;
341
            }
342
            Tag::Strikethrough => {
343
0
                self.write("</del>")?;
344
            }
345
            Tag::Link(_, _, _) => {
346
0
                self.write("</a>")?;
347
            }
348
0
            Tag::Image(_, _, _) => (), // shouldn't happen, handled in start
349
            Tag::FootnoteDefinition(_) => {
350
0
                self.write("</div>\n")?;
351
            }
352
        }
353
0
        Ok(())
354
0
    }
355
356
    // run raw text, consuming end tag
357
0
    fn raw_text(&mut self) -> io::Result<()> {
358
0
        let mut nest = 0;
359
0
        while let Some(event) = self.iter.next() {
360
0
            match event {
361
0
                Start(_) => nest += 1,
362
                End(_) => {
363
0
                    if nest == 0 {
364
0
                        break;
365
0
                    }
366
0
                    nest -= 1;
367
                }
368
0
                Html(text) | Code(text) | Text(text) => {
369
0
                    escape_html(&mut self.writer, &text)?;
370
0
                    self.end_newline = text.ends_with('\n');
371
                }
372
                SoftBreak | HardBreak | Rule => {
373
0
                    self.write(" ")?;
374
                }
375
0
                FootnoteReference(name) => {
376
0
                    let len = self.numbers.len() + 1;
377
0
                    let number = *self.numbers.entry(name).or_insert(len);
378
0
                    write!(&mut self.writer, "[{}]", number)?;
379
                }
380
0
                TaskListMarker(true) => self.write("[x]")?,
381
0
                TaskListMarker(false) => self.write("[ ]")?,
382
            }
383
        }
384
0
        Ok(())
385
0
    }
386
}
387
388
/// Iterate over an `Iterator` of `Event`s, generate HTML for each `Event`, and
389
/// push it to a `String`.
390
///
391
/// # Examples
392
///
393
/// ```
394
/// use pulldown_cmark::{html, Parser};
395
///
396
/// let markdown_str = r#"
397
/// hello
398
/// =====
399
///
400
/// * alpha
401
/// * beta
402
/// "#;
403
/// let parser = Parser::new(markdown_str);
404
///
405
/// let mut html_buf = String::new();
406
/// html::push_html(&mut html_buf, parser);
407
///
408
/// assert_eq!(html_buf, r#"<h1>hello</h1>
409
/// <ul>
410
/// <li>alpha</li>
411
/// <li>beta</li>
412
/// </ul>
413
/// "#);
414
/// ```
415
0
pub fn push_html<'a, I>(s: &mut String, iter: I)
416
0
where
417
0
    I: Iterator<Item = Event<'a>>,
418
0
{
419
0
    HtmlWriter::new(iter, s).run().unwrap();
420
0
}
421
422
/// Iterate over an `Iterator` of `Event`s, generate HTML for each `Event`, and
423
/// write it out to a writable stream.
424
///
425
/// **Note**: using this function with an unbuffered writer like a file or socket
426
/// will result in poor performance. Wrap these in a
427
/// [`BufWriter`](https://doc.rust-lang.org/std/io/struct.BufWriter.html) to
428
/// prevent unnecessary slowdowns.
429
///
430
/// # Examples
431
///
432
/// ```
433
/// use pulldown_cmark::{html, Parser};
434
/// use std::io::Cursor;
435
///
436
/// let markdown_str = r#"
437
/// hello
438
/// =====
439
///
440
/// * alpha
441
/// * beta
442
/// "#;
443
/// let mut bytes = Vec::new();
444
/// let parser = Parser::new(markdown_str);
445
///
446
/// html::write_html(Cursor::new(&mut bytes), parser);
447
///
448
/// assert_eq!(&String::from_utf8_lossy(&bytes)[..], r#"<h1>hello</h1>
449
/// <ul>
450
/// <li>alpha</li>
451
/// <li>beta</li>
452
/// </ul>
453
/// "#);
454
/// ```
455
0
pub fn write_html<'a, I, W>(writer: W, iter: I) -> io::Result<()>
456
0
where
457
0
    I: Iterator<Item = Event<'a>>,
458
0
    W: Write,
459
0
{
460
0
    HtmlWriter::new(iter, WriteWrapper(writer)).run()
461
0
}