pub fn write_html_io<'a, I, W>(writer: W, iter: I) -> Result<()>
Expand description
Iterate over an Iterator
of Event
s, 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 .";
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
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}