Skip to content

Commit ae275ad

Browse files
authored
Merge pull request #1785 from ehuss/summary-escape
Don't try to render summary links as markdown.
2 parents ba324cd + ceff050 commit ae275ad

File tree

5 files changed

+75
-33
lines changed

5 files changed

+75
-33
lines changed

src/book/book.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ use std::path::{Path, PathBuf};
77
use super::summary::{parse_summary, Link, SectionNumber, Summary, SummaryItem};
88
use crate::config::BuildConfig;
99
use crate::errors::*;
10+
use crate::utils::bracket_escape;
1011

1112
/// Load a book into memory from its `src/` directory.
1213
pub fn load_book<P: AsRef<Path>>(src_dir: P, cfg: &BuildConfig) -> Result<Book> {
@@ -53,7 +54,7 @@ fn create_missing(src_dir: &Path, summary: &Summary) -> Result<()> {
5354
let mut f = File::create(&filename).with_context(|| {
5455
format!("Unable to create missing file: {}", filename.display())
5556
})?;
56-
writeln!(f, "# {}", link.name)?;
57+
writeln!(f, "# {}", bracket_escape(&link.name))?;
5758
}
5859
}
5960

src/renderer/html_handlebars/helpers/toc.rs

Lines changed: 3 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
11
use std::collections::BTreeMap;
2-
use std::io;
32
use std::path::Path;
43

54
use crate::utils;
5+
use crate::utils::bracket_escape;
66

77
use handlebars::{Context, Handlebars, Helper, HelperDef, Output, RenderContext, RenderError};
8-
use pulldown_cmark::{html, Event, Parser};
98

109
// Handlebars helper to construct TOC
1110
#[derive(Clone, Copy)]
@@ -103,7 +102,7 @@ impl HelperDef for RenderToc {
103102
// Part title
104103
if let Some(title) = item.get("part") {
105104
out.write("<li class=\"part-title\">")?;
106-
write_escaped(out, title)?;
105+
out.write(&bracket_escape(title))?;
107106
out.write("</li>")?;
108107
continue;
109108
}
@@ -148,20 +147,7 @@ impl HelperDef for RenderToc {
148147
}
149148

150149
if let Some(name) = item.get("name") {
151-
// Render only inline code blocks
152-
153-
// filter all events that are not inline code blocks
154-
let parser = Parser::new(name).filter(|event| match *event {
155-
Event::Code(_) | Event::Html(_) | Event::Text(_) => true,
156-
_ => false,
157-
});
158-
159-
// render markdown to html
160-
let mut markdown_parsed_name = String::with_capacity(name.len() * 3 / 2);
161-
html::push_html(&mut markdown_parsed_name, parser);
162-
163-
// write to the handlebars template
164-
write_escaped(out, &markdown_parsed_name)?;
150+
out.write(&bracket_escape(name))?
165151
}
166152

167153
if path_exists {
@@ -205,18 +191,3 @@ fn write_li_open_tag(
205191
li.push_str("\">");
206192
out.write(&li)
207193
}
208-
209-
fn write_escaped(out: &mut dyn Output, mut title: &str) -> io::Result<()> {
210-
let needs_escape: &[char] = &['<', '>'];
211-
while let Some(next) = title.find(needs_escape) {
212-
out.write(&title[..next])?;
213-
match title.as_bytes()[next] {
214-
b'<' => out.write("&lt;")?,
215-
b'>' => out.write("&gt;")?,
216-
_ => unreachable!(),
217-
}
218-
title = &title[next + 1..];
219-
}
220-
out.write(title)?;
221-
Ok(())
222-
}

src/utils/mod.rs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -233,8 +233,26 @@ pub fn log_backtrace(e: &Error) {
233233
}
234234
}
235235

236+
pub(crate) fn bracket_escape(mut s: &str) -> String {
237+
let mut escaped = String::with_capacity(s.len());
238+
let needs_escape: &[char] = &['<', '>'];
239+
while let Some(next) = s.find(needs_escape) {
240+
escaped.push_str(&s[..next]);
241+
match s.as_bytes()[next] {
242+
b'<' => escaped.push_str("&lt;"),
243+
b'>' => escaped.push_str("&gt;"),
244+
_ => unreachable!(),
245+
}
246+
s = &s[next + 1..];
247+
}
248+
escaped.push_str(s);
249+
escaped
250+
}
251+
236252
#[cfg(test)]
237253
mod tests {
254+
use super::bracket_escape;
255+
238256
mod render_markdown {
239257
use super::super::render_markdown;
240258

@@ -431,4 +449,14 @@ more text with spaces
431449
assert_eq!(unique_id_from_content("## Über", &mut id_counter), "Über-2");
432450
}
433451
}
452+
453+
#[test]
454+
fn escaped_brackets() {
455+
assert_eq!(bracket_escape(""), "");
456+
assert_eq!(bracket_escape("<"), "&lt;");
457+
assert_eq!(bracket_escape(">"), "&gt;");
458+
assert_eq!(bracket_escape("<>"), "&lt;&gt;");
459+
assert_eq!(bracket_escape("<test>"), "&lt;test&gt;");
460+
assert_eq!(bracket_escape("a<test>b"), "a&lt;test&gt;b");
461+
}
434462
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
# Summary formatting tests
2+
3+
- [*Italic* `code` \*escape\* \`escape2\`](formatted-summary.md)
4+
- [Soft
5+
line break](soft.md)
6+
- [\<escaped tag\>](escaped-tag.md)

tests/rendered_output.rs

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -621,6 +621,42 @@ fn remove_absolute_components(path: &Path) -> impl Iterator<Item = Component> +
621621
})
622622
}
623623

624+
/// Checks formatting of summary names with inline elements.
625+
#[test]
626+
fn summary_with_markdown_formatting() {
627+
let temp = DummyBook::new().build().unwrap();
628+
let mut cfg = Config::default();
629+
cfg.set("book.src", "summary-formatting").unwrap();
630+
let md = MDBook::load_with_config(temp.path(), cfg).unwrap();
631+
md.build().unwrap();
632+
633+
let rendered_path = temp.path().join("book/formatted-summary.html");
634+
assert_contains_strings(
635+
rendered_path,
636+
&[
637+
r#"<a href="formatted-summary.html" class="active"><strong aria-hidden="true">1.</strong> Italic code *escape* `escape2`</a>"#,
638+
r#"<a href="soft.html"><strong aria-hidden="true">2.</strong> Soft line break</a>"#,
639+
r#"<a href="escaped-tag.html"><strong aria-hidden="true">3.</strong> &lt;escaped tag&gt;</a>"#,
640+
],
641+
);
642+
643+
let generated_md = temp.path().join("summary-formatting/formatted-summary.md");
644+
assert_eq!(
645+
fs::read_to_string(generated_md).unwrap(),
646+
"# Italic code *escape* `escape2`\n"
647+
);
648+
let generated_md = temp.path().join("summary-formatting/soft.md");
649+
assert_eq!(
650+
fs::read_to_string(generated_md).unwrap(),
651+
"# Soft line break\n"
652+
);
653+
let generated_md = temp.path().join("summary-formatting/escaped-tag.md");
654+
assert_eq!(
655+
fs::read_to_string(generated_md).unwrap(),
656+
"# &lt;escaped tag&gt;\n"
657+
);
658+
}
659+
624660
#[cfg(feature = "search")]
625661
mod search {
626662
use crate::dummy_book::DummyBook;

0 commit comments

Comments
 (0)