pulldown_cmark::html

Function write_html_io

source
pub fn write_html_io<'a, I, W>(writer: W, iter: I) -> Result<()>
where I: Iterator<Item = Event<'a>>, W: Write,
Expand description

Iterate over an Iterator of Events, generate HTML for each Event, and write it out to an I/O stream.

Note: using this function with an unbuffered writer like a file or socket will result in poor performance. Wrap these in a BufWriter 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_io(Cursor::new(&mut bytes), parser);

assert_eq!(&String::from_utf8_lossy(&bytes)[..], r#"<h1>hello</h1>
<ul>
<li>alpha</li>
<li>beta</li>
</ul>
"#);
Examples found in repository?
examples/event-filter.rs (line 26)
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
fn main() {
    let markdown_input: &str = "This is Peter on ![holiday in Greece](pearl_beach.jpg).";
    println!("Parsing the following markdown string:\n{}", markdown_input);

    // Set up parser. We can treat is as any other iterator. We replace Peter by John
    // and image by its alt text.
    let parser = Parser::new_ext(markdown_input, Options::empty())
        .map(|event| match event {
            Event::Text(text) => Event::Text(text.replace("Peter", "John").into()),
            _ => event,
        })
        .filter(|event| match event {
            Event::Start(Tag::Image { .. }) | Event::End(TagEnd::Image) => false,
            _ => true,
        });

    // Write to anything implementing the `Write` trait. This could also be a file
    // or network socket.
    let stdout = std::io::stdout();
    let mut handle = stdout.lock();
    handle.write_all(b"\nHTML output:\n").unwrap();
    html::write_html_io(&mut handle, parser).unwrap();
}
More examples
Hide additional examples
examples/footnote-rewrite.rs (line 56)
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
fn main() {
    let markdown_input: &str = "This is an [^a] footnote [^a].\n\n[^a]: footnote contents";
    println!("Parsing the following markdown string:\n{}", markdown_input);

    // To generate this style, you have to collect the footnotes at the end, while parsing.
    // You also need to count usages.
    let mut footnotes = Vec::new();
    let mut in_footnote = Vec::new();
    let mut footnote_numbers = HashMap::new();
    // ENABLE_FOOTNOTES is used in this example, but ENABLE_OLD_FOOTNOTES would work, too.
    let parser = Parser::new_ext(markdown_input, Options::ENABLE_FOOTNOTES)
        .filter_map(|event| {
            match event {
                Event::Start(Tag::FootnoteDefinition(_)) => {
                    in_footnote.push(vec![event]);
                    None
                }
                Event::End(TagEnd::FootnoteDefinition) => {
                    let mut f = in_footnote.pop().unwrap();
                    f.push(event);
                    footnotes.push(f);
                    None
                }
                Event::FootnoteReference(name) => {
                    let n = footnote_numbers.len() + 1;
                    let (n, nr) = footnote_numbers.entry(name.clone()).or_insert((n, 0usize));
                    *nr += 1;
                    let html = Event::Html(format!(r##"<sup class="footnote-reference" id="fr-{name}-{nr}"><a href="#fn-{name}">[{n}]</a></sup>"##).into());
                    if in_footnote.is_empty() {
                        Some(html)
                    } else {
                        in_footnote.last_mut().unwrap().push(html);
                        None
                    }
                }
                _ if !in_footnote.is_empty() => {
                    in_footnote.last_mut().unwrap().push(event);
                    None
                }
                _ => Some(event),
            }
        });

    // Write to anything implementing the `Write` trait. This could also be a file
    // or network socket.
    let stdout = std::io::stdout();
    let mut handle = stdout.lock();
    handle.write_all(b"\nHTML output:\n").unwrap();
    html::write_html_io(&mut handle, parser).unwrap();

    // To make the footnotes look right, we need to sort them by their appearance order, not by
    // the in-tree order of their actual definitions. Unused items are omitted entirely.
    //
    // For example, this code:
    //
    //     test [^1] [^2]
    //     [^2]: second used, first defined
    //     [^1]: test
    //
    // Gets rendered like *this* if you copy it into a GitHub comment box:
    //
    //     <p>test <sup>[1]</sup> <sup>[2]</sup></p>
    //     <hr>
    //     <ol>
    //     <li>test ↩</li>
    //     <li>second used, first defined ↩</li>
    //     </ol>
    if !footnotes.is_empty() {
        footnotes.retain(|f| match f.first() {
            Some(Event::Start(Tag::FootnoteDefinition(name))) => {
                footnote_numbers.get(name).unwrap_or(&(0, 0)).1 != 0
            }
            _ => false,
        });
        footnotes.sort_by_cached_key(|f| match f.first() {
            Some(Event::Start(Tag::FootnoteDefinition(name))) => {
                footnote_numbers.get(name).unwrap_or(&(0, 0)).0
            }
            _ => unreachable!(),
        });
        handle
            .write_all(b"<hr><ol class=\"footnotes-list\">\n")
            .unwrap();
        html::write_html_io(
            &mut handle,
            footnotes.into_iter().flat_map(|fl| {
                // To write backrefs, the name needs kept until the end of the footnote definition.
                let mut name = CowStr::from("");
                // Backrefs are included in the final paragraph of the footnote, if it's normal text.
                // For example, this DOM can be produced:
                //
                // Markdown:
                //
                //     five [^feet].
                //
                //     [^feet]:
                //         A foot is defined, in this case, as 0.3048 m.
                //
                //         Historically, the foot has not been defined this way, corresponding to many
                //         subtly different units depending on the location.
                //
                // HTML:
                //
                //     <p>five <sup class="footnote-reference" id="fr-feet-1"><a href="#fn-feet">[1]</a></sup>.</p>
                //
                //     <ol class="footnotes-list">
                //     <li id="fn-feet">
                //     <p>A foot is defined, in this case, as 0.3048 m.</p>
                //     <p>Historically, the foot has not been defined this way, corresponding to many
                //     subtly different units depending on the location. <a href="#fr-feet-1">↩</a></p>
                //     </li>
                //     </ol>
                //
                // This is mostly a visual hack, so that footnotes use less vertical space.
                //
                // If there is no final paragraph, such as a tabular, list, or image footnote, it gets
                // pushed after the last tag instead.
                let mut has_written_backrefs = false;
                let fl_len = fl.len();
                let footnote_numbers = &footnote_numbers;
                fl.into_iter().enumerate().map(move |(i, f)| match f {
                    Event::Start(Tag::FootnoteDefinition(current_name)) => {
                        name = current_name;
                        has_written_backrefs = false;
                        Event::Html(format!(r##"<li id="fn-{name}">"##).into())
                    }
                    Event::End(TagEnd::FootnoteDefinition) | Event::End(TagEnd::Paragraph)
                        if !has_written_backrefs && i >= fl_len - 2 =>
                    {
                        let usage_count = footnote_numbers.get(&name).unwrap().1;
                        let mut end = String::with_capacity(
                            name.len() + (r##" <a href="#fr--1">↩</a></li>"##.len() * usage_count),
                        );
                        for usage in 1..=usage_count {
                            if usage == 1 {
                                write!(&mut end, r##" <a href="#fr-{name}-{usage}">↩</a>"##)
                                    .unwrap();
                            } else {
                                write!(&mut end, r##" <a href="#fr-{name}-{usage}">↩{usage}</a>"##)
                                    .unwrap();
                            }
                        }
                        has_written_backrefs = true;
                        if f == Event::End(TagEnd::FootnoteDefinition) {
                            end.push_str("</li>\n");
                        } else {
                            end.push_str("</p>\n");
                        }
                        Event::Html(end.into())
                    }
                    Event::End(TagEnd::FootnoteDefinition) => Event::Html("</li>\n".into()),
                    Event::FootnoteReference(_) => unreachable!("converted to HTML earlier"),
                    f => f,
                })
            }),
        )
        .unwrap();
        handle.write_all(b"</ol>\n").unwrap();
    }
}