ssh git@grit.deadkey.org man

Commit 74b2026 h

Pat Ambrosio · 26-08-27 19:04

Parse footnote bodies instead of pre-escaping them

Changes: 2 files +46 -2

Changes (Side-by-Side)

share/jenny/lib/md2html.awk +7 -2

BEGIN {
451 id = substr($0, 1, RSTART - 1); 451 id = substr($0, 1, RSTART - 1);
452 sub(id "\\]:[ ]+", ""); 452 sub(id "\\]:[ ]+", "");
453 sub(/[ ]+".*"$/, ""); 453 sub(/[ ]+".*"$/, "");
454 url = eschtml($0);
455 fnref[id] = url;
454 # Stored raw, like a citation's body: inline() escapes what needs it and
455 # resolves what does not. Pre-escaping turned every "<" into "&lt;" before
456 # the parser could see it, so an autolink -- <https://example.com> -- came
457 # out as literal text in a footnote while working in prose and in
458 # citations. The variable was called "url" because this rule began life
459 # handling link-reference definitions, where a bare URL was all it held.
460 fnref[id] = $0;
456 461
457 subref(id); 462 subref(id);
458 next; 463 next;

test/regressions.sh +39 -0

it "the build is clean"
2521 assert_eq "$stderr_output" "" 2521 assert_eq "$stderr_output" ""
2522 2522
2523 summary 2523 summary
2524
2525 suite "Footnote bodies go through the inline parser"
2526
2527 # A footnote body must be parsed like prose and like a citation body. It used
2528 # to be HTML-escaped on the way in, which turned every "<" into "&lt;" before
2529 # the parser could see it -- so an autolink rendered as literal text inside a
2530 # footnote while working everywhere else.
2531 fi="$TMPROOT/footnote-inline"
2532 make_blog_min "$fi" 1 10
2533 printf '# Inline\n\nProse <https://example.com/prose> here.\n\nOne[^a] two[^b] three[^c].\n\n[^a]: See <https://example.com/auto> for details.\n\n[^b]: Tom & Jerry, 5 < 6.\n\n[^c]: With *emphasis* and [a link](https://example.com/md).\n' > "$fi/2024-01-01 inline.md"
2534 run_jenny "$fi"
2535 fi_post="$fi/.dist/post/inline.html"
2536 fi_flat=$(tr '\n' ' ' < "$fi_post")
2537 fi_a=$(printf '%s' "$fi_flat" | "$GREP" -o 'id="fn-a".\{0,200\}' | head -1)
2538 fi_b=$(printf '%s' "$fi_flat" | "$GREP" -o 'id="fn-b".\{0,200\}' | head -1)
2539 fi_c=$(printf '%s' "$fi_flat" | "$GREP" -o 'id="fn-c".\{0,200\}' | head -1)
2540
2541 it "an autolink inside a footnote becomes a link"
2542 # The behaviour the fix exists for.
2543 assert_contains "$fi_a" '<a href="https://example.com/auto">'
2544
2545 it "an autolink inside a footnote leaves no literal angle bracket"
2546 # The old output was "&lt;https://example.com/auto>" -- escaped, then never parsed.
2547 assert_not_contains "$fi_a" '&lt;https://example.com/auto'
2548
2549 it "an autolink in prose still becomes a link"
2550 # The footnote fix must not disturb the path that already worked.
2551 assert_contains "$fi_flat" '<a href="https://example.com/prose">'
2552
2553 it "ampersand and less-than in a footnote are still escaped"
2554 # Storing the body raw does not mean skipping escaping: inline() escapes what
2555 # is not markup. Unescaped "&" or "<" here would be invalid HTML.
2556 assert_contains "$fi_b" 'Tom &amp; Jerry, 5 &lt; 6.'
2557
2558 it "emphasis inside a footnote still renders"
2559 assert_contains "$fi_c" '<em>emphasis</em>'
2560
2561 it "a markdown link inside a footnote still renders"
2562 assert_contains "$fi_c" '<a href="https://example.com/md">a link</a>'

Raw

commit 74b2026127978ece522402c6a667fb777d14a08b
Author: Pat Ambrosio <cp.ambrosio@gmail.com>
Date:   Thu Aug 27 19:04:36 2026 +0800

    Parse footnote bodies instead of pre-escaping them
    
    A footnote body was run through eschtml() before being stored, so every
    "<" became "&lt;" before the inline parser could see it. An autolink --
    <https://example.com> -- therefore came out as literal text inside a
    footnote while resolving normally in prose and in citation bodies, which
    already stored raw and let inline() do the escaping.
    
    Store the body raw, like a citation. inline() escapes what is not markup,
    so "&" and "<" in ordinary prose are still escaped; what changes is that
    markup in a footnote is now parsed rather than blinded. That also makes
    inline HTML behave in footnotes as it already does elsewhere, removing
    the last place where a footnote body was parsed differently from prose.
    
    The local was named "url" because the rule began life handling link
    reference definitions, where a bare URL was the whole body.

diff --git a/share/jenny/lib/md2html.awk b/share/jenny/lib/md2html.awk
index 3fd54c9..b5f2dce 100755
--- a/share/jenny/lib/md2html.awk
+++ b/share/jenny/lib/md2html.awk
@@ -451,8 +451,13 @@ BEGIN {
 	id = substr($0, 1, RSTART - 1);
 	sub(id "\\]:[ 	]+", "");
 	sub(/[ 	]+".*"$/, "");
-	url = eschtml($0);
-	fnref[id] = url;
+	# Stored raw, like a citation's body: inline() escapes what needs it and
+	# resolves what does not. Pre-escaping turned every "<" into "&lt;" before
+	# the parser could see it, so an autolink -- <https://example.com> -- came
+	# out as literal text in a footnote while working in prose and in
+	# citations. The variable was called "url" because this rule began life
+	# handling link-reference definitions, where a bare URL was all it held.
+	fnref[id] = $0;
 
 	subref(id);
 	next;
diff --git a/test/regressions.sh b/test/regressions.sh
index a098835..354b596 100755
--- a/test/regressions.sh
+++ b/test/regressions.sh
@@ -2521,3 +2521,42 @@ it "the build is clean"
 assert_eq "$stderr_output" ""
 
 summary
+
+suite "Footnote bodies go through the inline parser"
+
+# A footnote body must be parsed like prose and like a citation body. It used
+# to be HTML-escaped on the way in, which turned every "<" into "&lt;" before
+# the parser could see it -- so an autolink rendered as literal text inside a
+# footnote while working everywhere else.
+fi="$TMPROOT/footnote-inline"
+make_blog_min "$fi" 1 10
+printf '# Inline\n\nProse <https://example.com/prose> here.\n\nOne[^a] two[^b] three[^c].\n\n[^a]: See <https://example.com/auto> for details.\n\n[^b]: Tom & Jerry, 5 < 6.\n\n[^c]: With *emphasis* and [a link](https://example.com/md).\n' > "$fi/2024-01-01 inline.md"
+run_jenny "$fi"
+fi_post="$fi/.dist/post/inline.html"
+fi_flat=$(tr '\n' ' ' < "$fi_post")
+fi_a=$(printf '%s' "$fi_flat" | "$GREP" -o 'id="fn-a".\{0,200\}' | head -1)
+fi_b=$(printf '%s' "$fi_flat" | "$GREP" -o 'id="fn-b".\{0,200\}' | head -1)
+fi_c=$(printf '%s' "$fi_flat" | "$GREP" -o 'id="fn-c".\{0,200\}' | head -1)
+
+it "an autolink inside a footnote becomes a link"
+# The behaviour the fix exists for.
+assert_contains "$fi_a" '<a href="https://example.com/auto">'
+
+it "an autolink inside a footnote leaves no literal angle bracket"
+# The old output was "&lt;https://example.com/auto>" -- escaped, then never parsed.
+assert_not_contains "$fi_a" '&lt;https://example.com/auto'
+
+it "an autolink in prose still becomes a link"
+# The footnote fix must not disturb the path that already worked.
+assert_contains "$fi_flat" '<a href="https://example.com/prose">'
+
+it "ampersand and less-than in a footnote are still escaped"
+# Storing the body raw does not mean skipping escaping: inline() escapes what
+# is not markup. Unescaped "&" or "<" here would be invalid HTML.
+assert_contains "$fi_b" 'Tom &amp; Jerry, 5 &lt; 6.'
+
+it "emphasis inside a footnote still renders"
+assert_contains "$fi_c" '<em>emphasis</em>'
+
+it "a markdown link inside a footnote still renders"
+assert_contains "$fi_c" '<a href="https://example.com/md">a link</a>'