Skip to content

Commit c464de3

Browse files
committed
Merge branch 'footnotes-fix-when-across-multiple-nodes' into all-footnote-fixes
2 parents 582eb8a + 7fa2372 commit c464de3

File tree

2 files changed

+79
-10
lines changed

2 files changed

+79
-10
lines changed

src/inlines.c

Lines changed: 54 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1137,24 +1137,68 @@ static cmark_node *handle_close_bracket(cmark_parser *parser, subject *subj) {
11371137
// What if we're a footnote link?
11381138
if (parser->options & CMARK_OPT_FOOTNOTES &&
11391139
opener->inl_text->next &&
1140-
opener->inl_text->next->type == CMARK_NODE_TEXT &&
1141-
!opener->inl_text->next->next) {
1140+
opener->inl_text->next->type == CMARK_NODE_TEXT) {
1141+
11421142
cmark_chunk *literal = &opener->inl_text->next->as.literal;
1143-
if (literal->len > 1 && literal->data[0] == '^') {
1143+
1144+
// look back to the opening '[', and skip ahead to the next character
1145+
// if we're looking at a '[^' sequence, and there is other text or nodes
1146+
// after the ^, let's call it a footnote reference.
1147+
if (literal->data[0] == '^' && (literal->len > 1 || opener->inl_text->next->next)) {
11441148

11451149
// Before we got this far, the `handle_close_bracket` function may have
11461150
// advanced the current state beyond our footnote's actual closing
11471151
// bracket, ie if it went looking for a `link_label`.
11481152
// Let's just rewind the subject's position:
11491153
subj->pos = initial_pos;
11501154

1151-
inl = make_simple(subj->mem, CMARK_NODE_FOOTNOTE_REFERENCE);
1152-
inl->as.literal = cmark_chunk_dup(literal, 1, literal->len - 1);
1153-
inl->start_line = inl->end_line = subj->line;
1154-
inl->start_column = opener->inl_text->start_column;
1155-
inl->end_column = subj->pos + subj->column_offset + subj->block_offset;
1156-
cmark_node_insert_before(opener->inl_text, inl);
1157-
cmark_node_free(opener->inl_text->next);
1155+
cmark_node *fnref = make_simple(subj->mem, CMARK_NODE_FOOTNOTE_REFERENCE);
1156+
1157+
// the start and end of the footnote ref is the opening and closing brace
1158+
// i.e. the subject's current position, and the opener's start_column
1159+
int fnref_end_column = subj->pos + subj->column_offset + subj->block_offset;
1160+
int fnref_start_column = opener->inl_text->start_column;
1161+
1162+
// any given node delineates a substring of the line being processed,
1163+
// with the remainder of the line being pointed to thru its 'literal'
1164+
// struct member.
1165+
// here, we copy the literal's pointer, moving it past the '^' character
1166+
// for a length equal to the size of footnote reference text.
1167+
// i.e. end_col minus start_col, minus the [ and the ^ characters
1168+
//
1169+
// this copies the footnote reference string, even if between the
1170+
// `opener` and the subject's current position there are other nodes
1171+
fnref->as.literal = cmark_chunk_dup(literal, 1, (fnref_end_column - fnref_start_column) - 2);
1172+
1173+
fnref->start_line = fnref->end_line = subj->line;
1174+
fnref->start_column = fnref_start_column;
1175+
fnref->end_column = fnref_end_column;
1176+
1177+
// we then replace the opener with this new fnref node, the net effect
1178+
// being replacing the opening '[' text node with a `^footnote-ref]` node.
1179+
cmark_node_insert_before(opener->inl_text, fnref);
1180+
1181+
// sometimes, the footnote reference text gets parsed into multiple nodes
1182+
// i.e. '[^example]' parsed into '[', '^exam', 'ple]'.
1183+
// this happens for ex with the autolink extension. when the autolinker
1184+
// finds the 'w' character, it will split the text into multiple nodes
1185+
// in hopes of being able to match a 'www.' substring.
1186+
//
1187+
// because this function is called one character at a time via the
1188+
// `parse_inlines` function, and the current subj->pos is pointing at the
1189+
// closing ] brace, and because we copy all the text between the [ ]
1190+
// braces, we should be able to safely ignore and delete any nodes after
1191+
// the opener->inl_text->next.
1192+
//
1193+
// therefore, here we walk thru the list and free them all up
1194+
cmark_node *next_node;
1195+
cmark_node *current_node = opener->inl_text->next;
1196+
while(current_node) {
1197+
next_node = current_node->next;
1198+
cmark_node_free(current_node);
1199+
current_node = next_node;
1200+
}
1201+
11581202
cmark_node_free(opener->inl_text);
11591203
process_emphasis(parser, subj, opener->previous_delimiter);
11601204
pop_bracket(subj);

test/regression.txt

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -313,3 +313,28 @@ This is some text. It has two footnotes references, side-by-side without any spa
313313
</ol>
314314
</section>
315315
````````````````````````````````
316+
317+
Footnotes may begin with or have a 'w' or a '_' in their reference label.
318+
319+
```````````````````````````````` example footnotes autolink
320+
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 `w`.[^widely-cited]
321+
322+
It has another footnote that contains many different characters (the autolinker was also breaking on `_`).[^sphinx-of-black-quartz_judge-my-vow-0123456789]
323+
324+
[^sphinx-of-black-quartz_judge-my-vow-0123456789]: so does this.
325+
326+
[^widely-cited]: this renders properly.
327+
.
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>
330+
<section class="footnotes">
331+
<ol>
332+
<li id="fn1">
333+
<p>this renders properly. <a href="#fnref1" class="footnote-backref">↩</a></p>
334+
</li>
335+
<li id="fn2">
336+
<p>so does this. <a href="#fnref2" class="footnote-backref">↩</a></p>
337+
</li>
338+
</ol>
339+
</section>
340+
````````````````````````````````

0 commit comments

Comments
 (0)