Skip to content

Commit 3e4c093

Browse files
authored
Merge pull request #308 from digitalmoksha/bw-escape-footnote-name
Escape footnote name
2 parents 127da5c + d91c947 commit 3e4c093

File tree

6 files changed

+90
-22
lines changed

6 files changed

+90
-22
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
target
22
comrak-*
33
.vscode
4+
.idea

src/html.rs

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -944,7 +944,9 @@ impl<'o> HtmlFormatter<'o> {
944944
self.footnote_ix += 1;
945945
self.output.write_all(b"<li")?;
946946
self.render_sourcepos(node)?;
947-
writeln!(self.output, " id=\"fn-{}\">", nfd.name)?;
947+
self.output.write_all(b" id=\"fn-")?;
948+
self.escape_href(nfd.name.as_bytes())?;
949+
self.output.write_all(b"\">")?;
948950
} else {
949951
if self.put_footnote_backref(nfd)? {
950952
self.output.write_all(b"\n")?;
@@ -962,10 +964,13 @@ impl<'o> HtmlFormatter<'o> {
962964
if nfr.ref_num > 1 {
963965
ref_id = format!("{}-{}", ref_id, nfr.ref_num);
964966
}
965-
write!(
966-
self.output, " class=\"footnote-ref\"><a href=\"#fn-{}\" id=\"{}\" data-footnote-ref>{}</a></sup>",
967-
nfr.name, ref_id, nfr.ix,
968-
)?;
967+
968+
self.output
969+
.write_all(b" class=\"footnote-ref\"><a href=\"#fn-")?;
970+
self.escape_href(nfr.name.as_bytes())?;
971+
self.output.write_all(b"\" id=\"")?;
972+
self.escape_href(ref_id.as_bytes())?;
973+
write!(self.output, "\" data-footnote-ref>{}</a></sup>", nfr.ix)?;
969974
}
970975
}
971976
NodeValue::TaskItem(symbol) => {
@@ -1018,10 +1023,12 @@ impl<'o> HtmlFormatter<'o> {
10181023
write!(self.output, " ")?;
10191024
}
10201025

1026+
self.output.write_all(b"<a href=\"#fnref-")?;
1027+
self.escape_href(nfd.name.as_bytes())?;
10211028
write!(
10221029
self.output,
1023-
"<a href=\"#fnref-{}{}\" class=\"footnote-backref\" data-footnote-backref data-footnote-backref-idx=\"{}{}\" aria-label=\"Back to reference {}{}\">↩{}</a>",
1024-
nfd.name, ref_suffix, self.footnote_ix, ref_suffix, self.footnote_ix, ref_suffix, superscript
1030+
"{}\" class=\"footnote-backref\" data-footnote-backref data-footnote-backref-idx=\"{}{}\" aria-label=\"Back to reference {}{}\">↩{}</a>",
1031+
ref_suffix, self.footnote_ix, ref_suffix, self.footnote_ix, ref_suffix, superscript
10251032
)?;
10261033
}
10271034
Ok(true)

src/parser/inlines.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1197,7 +1197,7 @@ impl<'a, 'r, 'o, 'd, 'i, 'c, 'subj> Subject<'a, 'r, 'o, 'd, 'i, 'c, 'subj> {
11971197
}
11981198

11991199
// Need to normalize both to lookup in refmap and to call callback
1200-
let lab = strings::normalize_label(&lab);
1200+
let lab = strings::normalize_label(&lab, false);
12011201
let mut reff = if found_label {
12021202
self.refmap.lookup(&lab)
12031203
} else {

src/parser/mod.rs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1785,11 +1785,11 @@ impl<'a, 'o, 'c> Parser<'a, 'o, 'c> {
17851785
NodeValue::FootnoteDefinition(ref nfd) => {
17861786
node.detach();
17871787
map.insert(
1788-
strings::normalize_label(&nfd.name),
1788+
strings::normalize_label(&nfd.name, false),
17891789
FootnoteDefinition {
17901790
ix: None,
17911791
node,
1792-
name: strings::normalize_label(&nfd.name),
1792+
name: strings::normalize_label(&nfd.name, true),
17931793
total_references: 0,
17941794
},
17951795
);
@@ -1811,7 +1811,8 @@ impl<'a, 'o, 'c> Parser<'a, 'o, 'c> {
18111811
let mut replace = None;
18121812
match ast.value {
18131813
NodeValue::FootnoteReference(ref mut nfr) => {
1814-
if let Some(ref mut footnote) = map.get_mut(&nfr.name) {
1814+
let normalized = strings::normalize_label(&nfr.name, false);
1815+
if let Some(ref mut footnote) = map.get_mut(&normalized) {
18151816
let ix = match footnote.ix {
18161817
Some(ix) => ix,
18171818
None => {
@@ -1823,6 +1824,7 @@ impl<'a, 'o, 'c> Parser<'a, 'o, 'c> {
18231824
footnote.total_references += 1;
18241825
nfr.ref_num = footnote.total_references;
18251826
nfr.ix = ix;
1827+
nfr.name = strings::normalize_label(&footnote.name, true);
18261828
} else {
18271829
replace = Some(nfr.name.clone());
18281830
}
@@ -2023,7 +2025,7 @@ impl<'a, 'o, 'c> Parser<'a, 'o, 'c> {
20232025
}
20242026
}
20252027

2026-
lab = strings::normalize_label(&lab);
2028+
lab = strings::normalize_label(&lab, false);
20272029
if !lab.is_empty() {
20282030
subj.refmap.map.entry(lab).or_insert(Reference {
20292031
url: String::from_utf8(strings::clean_url(url)).unwrap(),

src/strings.rs

Lines changed: 24 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -237,23 +237,25 @@ pub fn is_blank(s: &[u8]) -> bool {
237237
true
238238
}
239239

240-
pub fn normalize_label(i: &str) -> String {
240+
pub fn normalize_label(i: &str, preserve_case: bool) -> String {
241241
// trim_slice only removes bytes from start and end that match isspace();
242242
// result is UTF-8.
243243
let i = unsafe { str::from_utf8_unchecked(trim_slice(i.as_bytes())) };
244244

245245
let mut v = String::with_capacity(i.len());
246246
let mut last_was_whitespace = false;
247247
for c in i.chars() {
248-
for e in c.to_lowercase() {
249-
if e.is_whitespace() {
250-
if !last_was_whitespace {
251-
last_was_whitespace = true;
252-
v.push(' ');
253-
}
248+
if c.is_whitespace() {
249+
if !last_was_whitespace {
250+
last_was_whitespace = true;
251+
v.push(' ');
252+
}
253+
} else {
254+
last_was_whitespace = false;
255+
if preserve_case {
256+
v.push(c);
254257
} else {
255-
last_was_whitespace = false;
256-
v.push(e);
258+
v.push_str(&c.to_lowercase().to_string());
257259
}
258260
}
259261
}
@@ -308,7 +310,7 @@ pub fn trim_start_match<'s>(s: &'s str, pat: &str) -> &'s str {
308310

309311
#[cfg(test)]
310312
pub mod tests {
311-
use super::{normalize_code, split_off_front_matter};
313+
use super::{normalize_code, normalize_label, split_off_front_matter};
312314

313315
#[test]
314316
fn normalize_code_handles_lone_newline() {
@@ -341,4 +343,16 @@ pub mod tests {
341343
Some(("!@#\r\n\r\nfoo: \n!@# \r\nquux\n!@#\r\n\n", "\nYes!\n"))
342344
);
343345
}
346+
347+
#[test]
348+
fn normalize_label_lowercase() {
349+
assert_eq!(normalize_label(" Foo\u{A0}BAR ", false), "foo bar");
350+
assert_eq!(normalize_label(" FooİBAR ", false), "fooi\u{307}bar");
351+
}
352+
353+
#[test]
354+
fn normalize_label_preserve() {
355+
assert_eq!(normalize_label(" Foo\u{A0}BAR ", true), "Foo BAR");
356+
assert_eq!(normalize_label(" FooİBAR ", true), "FooİBAR");
357+
}
344358
}

src/tests/footnotes.rs

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,50 @@ fn footnote_with_superscript() {
147147
);
148148
}
149149

150+
#[test]
151+
fn footnote_escapes_name() {
152+
html_opts!(
153+
[extension.footnotes],
154+
concat!(
155+
"Here is a footnote reference.[^😄ref]\n",
156+
"\n",
157+
"[^😄ref]: Here is the footnote.\n",
158+
),
159+
concat!(
160+
"<p>Here is a footnote reference.<sup class=\"footnote-ref\"><a href=\"#fn-%F0%9F%98%84ref\" id=\"fnref-%F0%9F%98%84ref\" data-footnote-ref>1</a></sup></p>\n",
161+
"<section class=\"footnotes\" data-footnotes>\n",
162+
"<ol>\n",
163+
"<li id=\"fn-%F0%9F%98%84ref\">\n",
164+
"<p>Here is the footnote. <a href=\"#fnref-%F0%9F%98%84ref\" class=\"footnote-backref\" data-footnote-backref data-footnote-backref-idx=\"1\" aria-label=\"Back to reference 1\">↩</a></p>\n",
165+
"</li>\n",
166+
"</ol>\n",
167+
"</section>\n"
168+
),
169+
);
170+
}
171+
172+
#[test]
173+
fn footnote_case_insensitive_and_case_preserving() {
174+
html_opts!(
175+
[extension.footnotes],
176+
concat!(
177+
"Here is a footnote reference.[^AB] and [^ab]\n",
178+
"\n",
179+
"[^aB]: Here is the footnote.\n",
180+
),
181+
concat!(
182+
"<p>Here is a footnote reference.<sup class=\"footnote-ref\"><a href=\"#fn-aB\" id=\"fnref-aB\" data-footnote-ref>1</a></sup> and <sup class=\"footnote-ref\"><a href=\"#fn-aB\" id=\"fnref-aB-2\" data-footnote-ref>1</a></sup></p>\n",
183+
"<section class=\"footnotes\" data-footnotes>\n",
184+
"<ol>\n",
185+
"<li id=\"fn-aB\">\n",
186+
"<p>Here is the footnote. <a href=\"#fnref-aB\" class=\"footnote-backref\" data-footnote-backref data-footnote-backref-idx=\"1\" aria-label=\"Back to reference 1\">↩</a> <a href=\"#fnref-aB-2\" class=\"footnote-backref\" data-footnote-backref data-footnote-backref-idx=\"1-2\" aria-label=\"Back to reference 1-2\">↩<sup class=\"footnote-ref\">2</sup></a></p>\n",
187+
"</li>\n",
188+
"</ol>\n",
189+
"</section>\n"
190+
),
191+
);
192+
}
193+
150194
#[test]
151195
fn sourcepos() {
152196
assert_ast_match!(

0 commit comments

Comments
 (0)