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)
5fn main() {
6    let markdown_input: &str = "This is Peter on ![holiday in Greece](pearl_beach.jpg).";
7    println!("Parsing the following markdown string:\n{}", markdown_input);
8
9    // Set up parser. We can treat is as any other iterator. We replace Peter by John
10    // and image by its alt text.
11    let parser = Parser::new_ext(markdown_input, Options::empty())
12        .map(|event| match event {
13            Event::Text(text) => Event::Text(text.replace("Peter", "John").into()),
14            _ => event,
15        })
16        .filter(|event| match event {
17            Event::Start(Tag::Image { .. }) | Event::End(TagEnd::Image) => false,
18            _ => true,
19        });
20
21    // Write to anything implementing the `Write` trait. This could also be a file
22    // or network socket.
23    let stdout = std::io::stdout();
24    let mut handle = stdout.lock();
25    handle.write_all(b"\nHTML output:\n").unwrap();
26    html::write_html_io(&mut handle, parser).unwrap();
27}
More examples
Hide additional examples
examples/normalize-wikilink.rs (line 39)
7fn main() {
8    let markdown_input: &str = r#"
9Example provided by [[https://example.org/]].
10Some people might prefer the wikilink syntax for autolinks.
11
12Wanna go for a [[Wiki Walk]]?"#;
13
14    let parser = Parser::new_ext(markdown_input, Options::ENABLE_WIKILINKS).map(|event| {
15        if let Event::Start(Tag::Link {
16            link_type: LinkType::WikiLink { has_pothole },
17            dest_url,
18            title,
19            id,
20        }) = event
21        {
22            let new_link = normalize_wikilink(dest_url);
23            Event::Start(Tag::Link {
24                link_type: LinkType::WikiLink { has_pothole },
25                dest_url: new_link,
26                title,
27                id,
28            })
29        } else {
30            event
31        }
32    });
33
34    // Write to anything implementing the `Write` trait. This could also be a file
35    // or network socket.
36    let stdout = std::io::stdout();
37    let mut handle = stdout.lock();
38    handle.write_all(b"\nHTML output:\n").unwrap();
39    html::write_html_io(&mut handle, parser).unwrap();
40}
examples/footnote-rewrite.rs (line 56)
8fn main() {
9    let markdown_input: &str = "This is an [^a] footnote [^a].\n\n[^a]: footnote contents";
10    println!("Parsing the following markdown string:\n{}", markdown_input);
11
12    // To generate this style, you have to collect the footnotes at the end, while parsing.
13    // You also need to count usages.
14    let mut footnotes = Vec::new();
15    let mut in_footnote = Vec::new();
16    let mut footnote_numbers = HashMap::new();
17    // ENABLE_FOOTNOTES is used in this example, but ENABLE_OLD_FOOTNOTES would work, too.
18    let parser = Parser::new_ext(markdown_input, Options::ENABLE_FOOTNOTES)
19        .filter_map(|event| {
20            match event {
21                Event::Start(Tag::FootnoteDefinition(_)) => {
22                    in_footnote.push(vec![event]);
23                    None
24                }
25                Event::End(TagEnd::FootnoteDefinition) => {
26                    let mut f = in_footnote.pop().unwrap();
27                    f.push(event);
28                    footnotes.push(f);
29                    None
30                }
31                Event::FootnoteReference(name) => {
32                    let n = footnote_numbers.len() + 1;
33                    let (n, nr) = footnote_numbers.entry(name.clone()).or_insert((n, 0usize));
34                    *nr += 1;
35                    let html = Event::Html(format!(r##"<sup class="footnote-reference" id="fr-{name}-{nr}"><a href="#fn-{name}">[{n}]</a></sup>"##).into());
36                    if in_footnote.is_empty() {
37                        Some(html)
38                    } else {
39                        in_footnote.last_mut().unwrap().push(html);
40                        None
41                    }
42                }
43                _ if !in_footnote.is_empty() => {
44                    in_footnote.last_mut().unwrap().push(event);
45                    None
46                }
47                _ => Some(event),
48            }
49        });
50
51    // Write to anything implementing the `Write` trait. This could also be a file
52    // or network socket.
53    let stdout = std::io::stdout();
54    let mut handle = stdout.lock();
55    handle.write_all(b"\nHTML output:\n").unwrap();
56    html::write_html_io(&mut handle, parser).unwrap();
57
58    // To make the footnotes look right, we need to sort them by their appearance order, not by
59    // the in-tree order of their actual definitions. Unused items are omitted entirely.
60    //
61    // For example, this code:
62    //
63    //     test [^1] [^2]
64    //     [^2]: second used, first defined
65    //     [^1]: test
66    //
67    // Gets rendered like *this* if you copy it into a GitHub comment box:
68    //
69    //     <p>test <sup>[1]</sup> <sup>[2]</sup></p>
70    //     <hr>
71    //     <ol>
72    //     <li>test ↩</li>
73    //     <li>second used, first defined ↩</li>
74    //     </ol>
75    if !footnotes.is_empty() {
76        footnotes.retain(|f| match f.first() {
77            Some(Event::Start(Tag::FootnoteDefinition(name))) => {
78                footnote_numbers.get(name).unwrap_or(&(0, 0)).1 != 0
79            }
80            _ => false,
81        });
82        footnotes.sort_by_cached_key(|f| match f.first() {
83            Some(Event::Start(Tag::FootnoteDefinition(name))) => {
84                footnote_numbers.get(name).unwrap_or(&(0, 0)).0
85            }
86            _ => unreachable!(),
87        });
88        handle
89            .write_all(b"<hr><ol class=\"footnotes-list\">\n")
90            .unwrap();
91        html::write_html_io(
92            &mut handle,
93            footnotes.into_iter().flat_map(|fl| {
94                // To write backrefs, the name needs kept until the end of the footnote definition.
95                let mut name = CowStr::from("");
96                // Backrefs are included in the final paragraph of the footnote, if it's normal text.
97                // For example, this DOM can be produced:
98                //
99                // Markdown:
100                //
101                //     five [^feet].
102                //
103                //     [^feet]:
104                //         A foot is defined, in this case, as 0.3048 m.
105                //
106                //         Historically, the foot has not been defined this way, corresponding to many
107                //         subtly different units depending on the location.
108                //
109                // HTML:
110                //
111                //     <p>five <sup class="footnote-reference" id="fr-feet-1"><a href="#fn-feet">[1]</a></sup>.</p>
112                //
113                //     <ol class="footnotes-list">
114                //     <li id="fn-feet">
115                //     <p>A foot is defined, in this case, as 0.3048 m.</p>
116                //     <p>Historically, the foot has not been defined this way, corresponding to many
117                //     subtly different units depending on the location. <a href="#fr-feet-1">↩</a></p>
118                //     </li>
119                //     </ol>
120                //
121                // This is mostly a visual hack, so that footnotes use less vertical space.
122                //
123                // If there is no final paragraph, such as a tabular, list, or image footnote, it gets
124                // pushed after the last tag instead.
125                let mut has_written_backrefs = false;
126                let fl_len = fl.len();
127                let footnote_numbers = &footnote_numbers;
128                fl.into_iter().enumerate().map(move |(i, f)| match f {
129                    Event::Start(Tag::FootnoteDefinition(current_name)) => {
130                        name = current_name;
131                        has_written_backrefs = false;
132                        Event::Html(format!(r##"<li id="fn-{name}">"##).into())
133                    }
134                    Event::End(TagEnd::FootnoteDefinition) | Event::End(TagEnd::Paragraph)
135                        if !has_written_backrefs && i >= fl_len - 2 =>
136                    {
137                        let usage_count = footnote_numbers.get(&name).unwrap().1;
138                        let mut end = String::with_capacity(
139                            name.len() + (r##" <a href="#fr--1">↩</a></li>"##.len() * usage_count),
140                        );
141                        for usage in 1..=usage_count {
142                            if usage == 1 {
143                                write!(&mut end, r##" <a href="#fr-{name}-{usage}">↩</a>"##)
144                                    .unwrap();
145                            } else {
146                                write!(&mut end, r##" <a href="#fr-{name}-{usage}">↩{usage}</a>"##)
147                                    .unwrap();
148                            }
149                        }
150                        has_written_backrefs = true;
151                        if f == Event::End(TagEnd::FootnoteDefinition) {
152                            end.push_str("</li>\n");
153                        } else {
154                            end.push_str("</p>\n");
155                        }
156                        Event::Html(end.into())
157                    }
158                    Event::End(TagEnd::FootnoteDefinition) => Event::Html("</li>\n".into()),
159                    Event::FootnoteReference(_) => unreachable!("converted to HTML earlier"),
160                    f => f,
161                })
162            }),
163        )
164        .unwrap();
165        handle.write_all(b"</ol>\n").unwrap();
166    }
167}