Repository: Python-Markdown/markdown
Pull request #1218 (2022-02-03 02:59:11)

Footnotes improvements
Hi, I suggest in this PR 2 improvements for the footnotes module.

- Small refactor of the `BACKLINK_TITLE` option; The use of `format()` instead of "old" `%d` formatter allows
to specify text without the need to have the number of the footnote in it (like footnotes on Wikipedia for example).
The modification is backward compatible so no configuration change is required.

- Addition of a new option `SUPERSCRIPT_TEXT` that allows to specify a custom placeholder for the footnote itself in the text.
Ex: <sup>[1]</sup> or <sup>(1)</sup> or just by default, the current behavior: <sup>1</sup>.

Some writing rules specify these subtleties I think it could be useful.

Thanks for reading.

waylan: These look like some good improvements/changes to me. Of course, we do need to include an update to the changelog. However, as this is adding a new feature, it will need to be in a new feature release, which means a new document will need to be added for the release notes. However, we already have PR #1187 which handles that. This can be finalized and merged after that.
waylan: Oh, I just realized there are no tests for the new features. We also need some new tests added to [tests/test_syntax/extensions/test_footnotes.py](https://github.com/Python-Markdown/markdown/blob/master/tests/test_syntax/extensions/test_footnotes.py).
ysard: Thanks for the review, tests are added and passing `(Ran 945 tests in 3.294s, OK (skipped=21))`. I also added the changelog hoping that it will make your work easier after the merge for 3.4.

Patch:
diff --git a/docs/change_log/release-3.4.md b/docs/change_log/release-3.4.md
index 0070da9..71f9a20 100644
--- a/docs/change_log/release-3.4.md
+++ b/docs/change_log/release-3.4.md
@@ -2,7 +2,8 @@ title: Release Notes for v3.4
 
 # Python-Markdown 3.4 Release Notes
 
-Python-Markdown version 3.4 supports Python versions 3.6, 3.7, 3.8, 3.9 and PyPy3.
+Python-Markdown version 3.4 supports Python versions 3.6, 3.7, 3.8, 3.9, 3.10 and
+PyPy3.
 
 ## Backwards-incompatible changes
 
@@ -37,8 +38,19 @@ The following new features have been included in the 3.3 release:
   inter-operation. The old behavior is available by setting `use_align_attribute=True` when
   adding the extension.
 
-## Bug fixes
+* Some new configuration options have been added to the [footnotes](../extensions/footnotes.md)
+  extension (#1218):
 
-The following bug fixes are included in the 3.4 release:
+    * Small refactor of the `BACKLINK_TITLE` option; The use of `format()` instead
+      of "old" `%d` formatter allows to specify text without the need to have the
+      number of the footnote in it (like footnotes on Wikipedia for example).
+      The modification is backward compatible so no configuration change is required.
 
+    * Addition of a new option `SUPERSCRIPT_TEXT` that allows to specify a custom
+      placeholder for the footnote itself in the text.
+      Ex: `[{}]` will give <sup>[1]</sup>, `({})` will give <sup>(1)</sup>,
+      or just by default, the current behavior: <sup>1</sup>.
 
+## Bug fixes
+
+The following bug fixes are included in the 3.4 release:
diff --git a/docs/extensions/footnotes.md b/docs/extensions/footnotes.md
index 0794b7f..e841a32 100644
--- a/docs/extensions/footnotes.md
+++ b/docs/extensions/footnotes.md
@@ -86,10 +86,14 @@ The following options are provided to configure the output:
     The text string that links from the footnote definition back to the position
     in the document. Defaults to `&#8617;`.
 
+* **`SUPERSCRIPT_TEXT`**:
+    The text string that links from the position in the document to the footnote
+    definition. Defaults to `{}`, i.e. only the footnote's number.
+
 * **`BACKLINK_TITLE`**:
     The text string for the `title` HTML attribute of the footnote definition link.
-    `%d` will be replaced by the footnote number. Defaults to `Jump back to
-    footnote %d in the text`
+    The placeholder `{}` will be replaced by the footnote number. Defaults to
+    `Jump back to footnote {} in the text`.
 
 * **`SEPARATOR`**:
     The text string used to set the footnote separator. Defaults to `:`.
diff --git a/markdown/extensions/footnotes.py b/markdown/extensions/footnotes.py
index 1cc7118..96ed5c2 100644
--- a/markdown/extensions/footnotes.py
+++ b/markdown/extensions/footnotes.py
@@ -47,6 +47,10 @@ class FootnoteExtension(Extension):
                 ["&#8617;",
                  "The text string that links from the footnote "
                  "to the reader's place."],
+            "SUPERSCRIPT_TEXT":
+                ["{}",
+                 "The text string that links from the reader's place "
+                 "to the footnote."],
             "BACKLINK_TITLE":
                 ["Jump back to footnote %d in the text",
                  "The text string used for the title HTML attribute "
@@ -170,6 +174,9 @@ class FootnoteExtension(Extension):
         ol = etree.SubElement(div, "ol")
         surrogate_parent = etree.Element("div")
 
+        # Backward compatibility with old '%d' placeholder
+        backlink_title = self.getConfig("BACKLINK_TITLE").replace("%d", "{}")
+
         for index, id in enumerate(self.footnotes.keys(), start=1):
             li = etree.SubElement(ol, "li")
             li.set("id", self.makeFootnoteId(id))
@@ -185,7 +192,7 @@ class FootnoteExtension(Extension):
             backlink.set("class", "footnote-backref")
             backlink.set(
                 "title",
-                self.getConfig("BACKLINK_TITLE") % (index)
+                backlink_title.format(index)
             )
             backlink.text = FN_BACKLINK_TEXT
 
@@ -303,7 +310,9 @@ class FootnoteInlineProcessor(InlineProcessor):
             sup.set('id', self.footnotes.makeFootnoteRefId(id, found=True))
             a.set('href', '#' + self.footnotes.makeFootnoteId(id))
             a.set('class', 'footnote-ref')
-            a.text = str(list(self.footnotes.footnotes.keys()).index(id) + 1)
+            a.text = self.footnotes.getConfig("SUPERSCRIPT_TEXT").format(
+                list(self.footnotes.footnotes.keys()).index(id) + 1
+            )
             return sup, m.start(0), m.end(0)
         else:
             return None, None, None


Test patch:
diff --git a/tests/test_syntax/extensions/test_footnotes.py b/tests/test_syntax/extensions/test_footnotes.py
index 1a3a2b0..9a6b32a 100644
--- a/tests/test_syntax/extensions/test_footnotes.py
+++ b/tests/test_syntax/extensions/test_footnotes.py
@@ -300,3 +300,39 @@ class TestFootnotes(TestCase):
             '</div>',
             extension_configs={'footnotes': {'SEPARATOR': '-'}}
         )
+
+    def test_backlink_title(self):
+        """Test backlink title configuration without placeholder."""
+
+        self.assertMarkdownRenders(
+            'paragraph[^1]\n\n[^1]: A Footnote',
+            '<p>paragraph<sup id="fnref:1"><a class="footnote-ref" href="#fn:1">1</a></sup></p>\n'
+            '<div class="footnote">\n'
+            '<hr />\n'
+            '<ol>\n'
+            '<li id="fn:1">\n'
+            '<p>A Footnote&#160;<a class="footnote-backref" href="#fnref:1"'
+            ' title="Jump back to footnote">&#8617;</a></p>\n'
+            '</li>\n'
+            '</ol>\n'
+            '</div>',
+            extension_configs={'footnotes': {'BACKLINK_TITLE': 'Jump back to footnote'}}
+        )
+
+    def test_superscript_text(self):
+        """Test superscript text configuration."""
+
+        self.assertMarkdownRenders(
+            'paragraph[^1]\n\n[^1]: A Footnote',
+            '<p>paragraph<sup id="fnref:1"><a class="footnote-ref" href="#fn:1">[1]</a></sup></p>\n'
+            '<div class="footnote">\n'
+            '<hr />\n'
+            '<ol>\n'
+            '<li id="fn:1">\n'
+            '<p>A Footnote&#160;<a class="footnote-backref" href="#fnref:1"'
+            ' title="Jump back to footnote 1 in the text">&#8617;</a></p>\n'
+            '</li>\n'
+            '</ol>\n'
+            '</div>',
+            extension_configs={'footnotes': {'SUPERSCRIPT_TEXT': '[{}]'}}
+        )