Skip to content

Commit d7e50f0

Browse files
authored
Merge pull request commonmark#230 from github/fix-footnotes-plus-fix-fnref-label-and-backrefs
Fix footnotes plus fix footnote reference labels and backrefs
2 parents eb5b719 + 8474289 commit d7e50f0

File tree

6 files changed

+135
-51
lines changed

6 files changed

+135
-51
lines changed

src/blocks.c

+9
Original file line numberDiff line numberDiff line change
@@ -484,6 +484,15 @@ static void process_footnotes(cmark_parser *parser) {
484484
if (!footnote->ix)
485485
footnote->ix = ++ix;
486486

487+
// store a reference to this footnote reference's footnote definition
488+
// this is used by renderers when generating label ids
489+
cur->parent_footnote_def = footnote->node;
490+
491+
// keep track of a) count of how many times this footnote def has been
492+
// referenced, and b) which reference index this footnote ref is at.
493+
// this is used by renderers when generating links and backreferences.
494+
cur->footnote.ref_ix = ++footnote->node->footnote.def_count;
495+
487496
char n[32];
488497
snprintf(n, sizeof(n), "%d", footnote->ix);
489498
cmark_chunk_free(parser->mem, &cur->as.literal);

src/commonmark.c

+14-4
Original file line numberDiff line numberDiff line change
@@ -477,7 +477,13 @@ static int S_render_node(cmark_renderer *renderer, cmark_node *node,
477477
case CMARK_NODE_FOOTNOTE_REFERENCE:
478478
if (entering) {
479479
LIT("[^");
480-
OUT(cmark_chunk_to_cstr(renderer->mem, &node->as.literal), false, LITERAL);
480+
481+
char *footnote_label = renderer->mem->calloc(node->parent_footnote_def->as.literal.len + 1, sizeof(char));
482+
memmove(footnote_label, node->parent_footnote_def->as.literal.data, node->parent_footnote_def->as.literal.len);
483+
484+
OUT(footnote_label, false, LITERAL);
485+
renderer->mem->free(footnote_label);
486+
481487
LIT("]");
482488
}
483489
break;
@@ -486,9 +492,13 @@ static int S_render_node(cmark_renderer *renderer, cmark_node *node,
486492
if (entering) {
487493
renderer->footnote_ix += 1;
488494
LIT("[^");
489-
char n[32];
490-
snprintf(n, sizeof(n), "%d", renderer->footnote_ix);
491-
OUT(n, false, LITERAL);
495+
496+
char *footnote_label = renderer->mem->calloc(node->as.literal.len + 1, sizeof(char));
497+
memmove(footnote_label, node->as.literal.data, node->as.literal.len);
498+
499+
OUT(footnote_label, false, LITERAL);
500+
renderer->mem->free(footnote_label);
501+
492502
LIT("]:\n");
493503

494504
cmark_strbuf_puts(renderer->prefix, " ");

src/html.c

+37-16
Original file line numberDiff line numberDiff line change
@@ -59,17 +59,31 @@ static void filter_html_block(cmark_html_renderer *renderer, uint8_t *data, size
5959
cmark_strbuf_put(html, data, (bufsize_t)len);
6060
}
6161

62-
static bool S_put_footnote_backref(cmark_html_renderer *renderer, cmark_strbuf *html) {
62+
static bool S_put_footnote_backref(cmark_html_renderer *renderer, cmark_strbuf *html, cmark_node *node) {
6363
if (renderer->written_footnote_ix >= renderer->footnote_ix)
6464
return false;
6565
renderer->written_footnote_ix = renderer->footnote_ix;
6666

67-
cmark_strbuf_puts(html, "<a href=\"#fnref");
68-
char n[32];
69-
snprintf(n, sizeof(n), "%d", renderer->footnote_ix);
70-
cmark_strbuf_puts(html, n);
67+
cmark_strbuf_puts(html, "<a href=\"#fnref-");
68+
houdini_escape_href(html, node->as.literal.data, node->as.literal.len);
7169
cmark_strbuf_puts(html, "\" class=\"footnote-backref\">↩</a>");
7270

71+
if (node->footnote.def_count > 1)
72+
{
73+
for(int i = 2; i <= node->footnote.def_count; i++) {
74+
char n[32];
75+
snprintf(n, sizeof(n), "%d", i);
76+
77+
cmark_strbuf_puts(html, " <a href=\"#fnref-");
78+
houdini_escape_href(html, node->as.literal.data, node->as.literal.len);
79+
cmark_strbuf_puts(html, "-");
80+
cmark_strbuf_puts(html, n);
81+
cmark_strbuf_puts(html, "\" class=\"footnote-backref\">↩<sup class=\"footnote-ref\">");
82+
cmark_strbuf_puts(html, n);
83+
cmark_strbuf_puts(html, "</sup></a>");
84+
}
85+
}
86+
7387
return true;
7488
}
7589

@@ -273,7 +287,7 @@ static int S_render_node(cmark_html_renderer *renderer, cmark_node *node,
273287
} else {
274288
if (parent->type == CMARK_NODE_FOOTNOTE_DEFINITION && node->next == NULL) {
275289
cmark_strbuf_putc(html, ' ');
276-
S_put_footnote_backref(renderer, html);
290+
S_put_footnote_backref(renderer, html, parent);
277291
}
278292
cmark_strbuf_puts(html, "</p>\n");
279293
}
@@ -395,13 +409,12 @@ static int S_render_node(cmark_html_renderer *renderer, cmark_node *node,
395409
cmark_strbuf_puts(html, "<section class=\"footnotes\">\n<ol>\n");
396410
}
397411
++renderer->footnote_ix;
398-
cmark_strbuf_puts(html, "<li id=\"fn");
399-
char n[32];
400-
snprintf(n, sizeof(n), "%d", renderer->footnote_ix);
401-
cmark_strbuf_puts(html, n);
412+
413+
cmark_strbuf_puts(html, "<li id=\"fn-");
414+
houdini_escape_href(html, node->as.literal.data, node->as.literal.len);
402415
cmark_strbuf_puts(html, "\">\n");
403416
} else {
404-
if (S_put_footnote_backref(renderer, html)) {
417+
if (S_put_footnote_backref(renderer, html, node)) {
405418
cmark_strbuf_putc(html, '\n');
406419
}
407420
cmark_strbuf_puts(html, "</li>\n");
@@ -410,12 +423,20 @@ static int S_render_node(cmark_html_renderer *renderer, cmark_node *node,
410423

411424
case CMARK_NODE_FOOTNOTE_REFERENCE:
412425
if (entering) {
413-
cmark_strbuf_puts(html, "<sup class=\"footnote-ref\"><a href=\"#fn");
414-
cmark_strbuf_put(html, node->as.literal.data, node->as.literal.len);
415-
cmark_strbuf_puts(html, "\" id=\"fnref");
416-
cmark_strbuf_put(html, node->as.literal.data, node->as.literal.len);
426+
cmark_strbuf_puts(html, "<sup class=\"footnote-ref\"><a href=\"#fn-");
427+
houdini_escape_href(html, node->parent_footnote_def->as.literal.data, node->parent_footnote_def->as.literal.len);
428+
cmark_strbuf_puts(html, "\" id=\"fnref-");
429+
houdini_escape_href(html, node->parent_footnote_def->as.literal.data, node->parent_footnote_def->as.literal.len);
430+
431+
if (node->footnote.ref_ix > 1) {
432+
char n[32];
433+
snprintf(n, sizeof(n), "%d", node->footnote.ref_ix);
434+
cmark_strbuf_puts(html, "-");
435+
cmark_strbuf_puts(html, n);
436+
}
437+
417438
cmark_strbuf_puts(html, "\">");
418-
cmark_strbuf_put(html, node->as.literal.data, node->as.literal.len);
439+
houdini_escape_href(html, node->as.literal.data, node->as.literal.len);
419440
cmark_strbuf_puts(html, "</a></sup>");
420441
}
421442
break;

src/node.h

+7
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,13 @@ struct cmark_node {
7676

7777
cmark_syntax_extension *extension;
7878

79+
union {
80+
int ref_ix;
81+
int def_count;
82+
} footnote;
83+
84+
cmark_node *parent_footnote_def;
85+
7986
union {
8087
cmark_chunk literal;
8188
cmark_list list;

test/extensions.txt

+48-11
Original file line numberDiff line numberDiff line change
@@ -672,31 +672,68 @@ Hi!
672672

673673
[^unused]: This is unused.
674674
.
675-
<p>This is some text!<sup class="footnote-ref"><a href="#fn1" id="fnref1">1</a></sup>. Other text.<sup class="footnote-ref"><a href="#fn2" id="fnref2">2</a></sup>.</p>
676-
<p>Here's a thing<sup class="footnote-ref"><a href="#fn3" id="fnref3">3</a></sup>.</p>
677-
<p>And another thing<sup class="footnote-ref"><a href="#fn4" id="fnref4">4</a></sup>.</p>
675+
<p>This is some text!<sup class="footnote-ref"><a href="#fn-1" id="fnref-1">1</a></sup>. Other text.<sup class="footnote-ref"><a href="#fn-footnote" id="fnref-footnote">2</a></sup>.</p>
676+
<p>Here's a thing<sup class="footnote-ref"><a href="#fn-other-note" id="fnref-other-note">3</a></sup>.</p>
677+
<p>And another thing<sup class="footnote-ref"><a href="#fn-codeblock-note" id="fnref-codeblock-note">4</a></sup>.</p>
678678
<p>This doesn't have a referent[^nope].</p>
679679
<p>Hi!</p>
680680
<section class="footnotes">
681681
<ol>
682-
<li id="fn1">
683-
<p>Some <em>bolded</em> footnote definition. <a href="#fnref1" class="footnote-backref">↩</a></p>
682+
<li id="fn-1">
683+
<p>Some <em>bolded</em> footnote definition. <a href="#fnref-1" class="footnote-backref">↩</a></p>
684684
</li>
685-
<li id="fn2">
685+
<li id="fn-footnote">
686686
<blockquote>
687687
<p>Blockquotes can be in a footnote.</p>
688688
</blockquote>
689689
<pre><code>as well as code blocks
690690
</code></pre>
691-
<p>or, naturally, simple paragraphs. <a href="#fnref2" class="footnote-backref">↩</a></p>
691+
<p>or, naturally, simple paragraphs. <a href="#fnref-footnote" class="footnote-backref">↩</a></p>
692692
</li>
693-
<li id="fn3">
694-
<p>no code block here (spaces are stripped away) <a href="#fnref3" class="footnote-backref">↩</a></p>
693+
<li id="fn-other-note">
694+
<p>no code block here (spaces are stripped away) <a href="#fnref-other-note" class="footnote-backref">↩</a></p>
695695
</li>
696-
<li id="fn4">
696+
<li id="fn-codeblock-note">
697697
<pre><code>this is now a code block (8 spaces indentation)
698698
</code></pre>
699-
<a href="#fnref4" class="footnote-backref">↩</a>
699+
<a href="#fnref-codeblock-note" class="footnote-backref">↩</a>
700+
</li>
701+
</ol>
702+
</section>
703+
````````````````````````````````
704+
705+
## When a footnote is used multiple times, we insert multiple backrefs.
706+
707+
```````````````````````````````` example
708+
This is some text. It has a footnote[^a-footnote].
709+
710+
This footnote is referenced[^a-footnote] multiple times, in lots of different places.[^a-footnote]
711+
712+
[^a-footnote]: This footnote definition should have three backrefs.
713+
.
714+
<p>This is some text. It has a footnote<sup class="footnote-ref"><a href="#fn-a-footnote" id="fnref-a-footnote">1</a></sup>.</p>
715+
<p>This footnote is referenced<sup class="footnote-ref"><a href="#fn-a-footnote" id="fnref-a-footnote-2">1</a></sup> multiple times, in lots of different places.<sup class="footnote-ref"><a href="#fn-a-footnote" id="fnref-a-footnote-3">1</a></sup></p>
716+
<section class="footnotes">
717+
<ol>
718+
<li id="fn-a-footnote">
719+
<p>This footnote definition should have three backrefs. <a href="#fnref-a-footnote" class="footnote-backref">↩</a> <a href="#fnref-a-footnote-2" class="footnote-backref">↩<sup class="footnote-ref">2</sup></a> <a href="#fnref-a-footnote-3" class="footnote-backref">↩<sup class="footnote-ref">3</sup></a></p>
720+
</li>
721+
</ol>
722+
</section>
723+
````````````````````````````````
724+
725+
## Footnote reference labels are href escaped
726+
727+
```````````````````````````````` example
728+
Hello[^"><script>alert(1)</script>]
729+
730+
[^"><script>alert(1)</script>]: pwned
731+
.
732+
<p>Hello<sup class="footnote-ref"><a href="#fn-%22%3E%3Cscript%3Ealert(1)%3C/script%3E" id="fnref-%22%3E%3Cscript%3Ealert(1)%3C/script%3E">1</a></sup></p>
733+
<section class="footnotes">
734+
<ol>
735+
<li id="fn-%22%3E%3Cscript%3Ealert(1)%3C/script%3E">
736+
<p>pwned <a href="#fnref-%22%3E%3Cscript%3Ealert(1)%3C/script%3E" class="footnote-backref">↩</a></p>
700737
</li>
701738
</ol>
702739
</section>

test/regression.txt

+20-20
Original file line numberDiff line numberDiff line change
@@ -175,7 +175,7 @@ A footnote in a paragraph[^1]
175175

176176
[^1]: a footnote
177177
.
178-
<p>A footnote in a paragraph<sup class="footnote-ref"><a href="#fn1" id="fnref1">1</a></sup></p>
178+
<p>A footnote in a paragraph<sup class="footnote-ref"><a href="#fn-1" id="fnref-1">1</a></sup></p>
179179
<table>
180180
<thead>
181181
<tr>
@@ -185,15 +185,15 @@ A footnote in a paragraph[^1]
185185
</thead>
186186
<tbody>
187187
<tr>
188-
<td>foot <sup class="footnote-ref"><a href="#fn1" id="fnref1">1</a></sup></td>
188+
<td>foot <sup class="footnote-ref"><a href="#fn-1" id="fnref-1-2">1</a></sup></td>
189189
<td>note</td>
190190
</tr>
191191
</tbody>
192192
</table>
193193
<section class="footnotes">
194194
<ol>
195-
<li id="fn1">
196-
<p>a footnote <a href="#fnref1" class="footnote-backref">↩</a></p>
195+
<li id="fn-1">
196+
<p>a footnote <a href="#fnref-1" class="footnote-backref">↩</a> <a href="#fnref-1-2" class="footnote-backref">↩<sup class="footnote-ref">2</sup></a></p>
197197
</li>
198198
</ol>
199199
</section>
@@ -279,14 +279,14 @@ This is some text. It has a citation.[^citation]
279279

280280
[^citation]: This is a long winded parapgraph that also has another citation.[^another-citation]
281281
.
282-
<p>This is some text. It has a citation.<sup class="footnote-ref"><a href="#fn1" id="fnref1">1</a></sup></p>
282+
<p>This is some text. It has a citation.<sup class="footnote-ref"><a href="#fn-citation" id="fnref-citation">1</a></sup></p>
283283
<section class="footnotes">
284284
<ol>
285-
<li id="fn1">
286-
<p>This is a long winded parapgraph that also has another citation.<sup class="footnote-ref"><a href="#fn2" id="fnref2">2</a></sup> <a href="#fnref1" class="footnote-backref">↩</a></p>
285+
<li id="fn-citation">
286+
<p>This is a long winded parapgraph that also has another citation.<sup class="footnote-ref"><a href="#fn-another-citation" id="fnref-another-citation">2</a></sup> <a href="#fnref-citation" class="footnote-backref">↩</a></p>
287287
</li>
288-
<li id="fn2">
289-
<p>My second citation. <a href="#fnref2" class="footnote-backref">↩</a></p>
288+
<li id="fn-another-citation">
289+
<p>My second citation. <a href="#fnref-another-citation" class="footnote-backref">↩</a></p>
290290
</li>
291291
</ol>
292292
</section>
@@ -301,14 +301,14 @@ This is some text. It has two footnotes references, side-by-side without any spa
301301

302302
[^footnote2]: Goodbye.
303303
.
304-
<p>This is some text. It has two footnotes references, side-by-side without any spaces,<sup class="footnote-ref"><a href="#fn1" id="fnref1">1</a></sup><sup class="footnote-ref"><a href="#fn2" id="fnref2">2</a></sup> which are definitely not link references.</p>
304+
<p>This is some text. It has two footnotes references, side-by-side without any spaces,<sup class="footnote-ref"><a href="#fn-footnote1" id="fnref-footnote1">1</a></sup><sup class="footnote-ref"><a href="#fn-footnote2" id="fnref-footnote2">2</a></sup> which are definitely not link references.</p>
305305
<section class="footnotes">
306306
<ol>
307-
<li id="fn1">
308-
<p>Hello. <a href="#fnref1" class="footnote-backref">↩</a></p>
307+
<li id="fn-footnote1">
308+
<p>Hello. <a href="#fnref-footnote1" class="footnote-backref">↩</a></p>
309309
</li>
310-
<li id="fn2">
311-
<p>Goodbye. <a href="#fnref2" class="footnote-backref">↩</a></p>
310+
<li id="fn-footnote2">
311+
<p>Goodbye. <a href="#fnref-footnote2" class="footnote-backref">↩</a></p>
312312
</li>
313313
</ol>
314314
</section>
@@ -325,15 +325,15 @@ It has another footnote that contains many different characters (the autolinker
325325

326326
[^widely-cited]: this renders properly.
327327
.
328-
<p>This is some text. Sometimes the autolinker splits up text into multiple nodes, hoping it will find a hyperlink, so this text has a footnote whose reference label begins with a <code>w</code>.<sup class="footnote-ref"><a href="#fn1" id="fnref1">1</a></sup></p>
329-
<p>It has another footnote that contains many different characters (the autolinker was also breaking on <code>_</code>).<sup class="footnote-ref"><a href="#fn2" id="fnref2">2</a></sup></p>
328+
<p>This is some text. Sometimes the autolinker splits up text into multiple nodes, hoping it will find a hyperlink, so this text has a footnote whose reference label begins with a <code>w</code>.<sup class="footnote-ref"><a href="#fn-widely-cited" id="fnref-widely-cited">1</a></sup></p>
329+
<p>It has another footnote that contains many different characters (the autolinker was also breaking on <code>_</code>).<sup class="footnote-ref"><a href="#fn-sphinx-of-black-quartz_judge-my-vow-0123456789" id="fnref-sphinx-of-black-quartz_judge-my-vow-0123456789">2</a></sup></p>
330330
<section class="footnotes">
331331
<ol>
332-
<li id="fn1">
333-
<p>this renders properly. <a href="#fnref1" class="footnote-backref">↩</a></p>
332+
<li id="fn-widely-cited">
333+
<p>this renders properly. <a href="#fnref-widely-cited" class="footnote-backref">↩</a></p>
334334
</li>
335-
<li id="fn2">
336-
<p>so does this. <a href="#fnref2" class="footnote-backref">↩</a></p>
335+
<li id="fn-sphinx-of-black-quartz_judge-my-vow-0123456789">
336+
<p>so does this. <a href="#fnref-sphinx-of-black-quartz_judge-my-vow-0123456789" class="footnote-backref">↩</a></p>
337337
</li>
338338
</ol>
339339
</section>

0 commit comments

Comments
 (0)