SF.net SVN: docutils:[10317 ] trunk/docutils
milde--- via Docutils-checkins <[email protected]> Thu, 30 Apr 2026 19:17:18 +0000
| Newsgroups | gmane.text.docutils.cvs |
|---|---|
| Message-ID | <[email protected]> |
Revision: 10317
http://sourceforge.net/p/docutils/code/10317
Author: milde
Date: 2026-04-30 19:17:18 +0000 (Thu, 30 Apr 2026)
Log Message:
-----------
latex-footnotes: Fix nested footnotes and multiple references.
Store footnote ID (instead of footnote content) in the "footnote_queue".
Store IDs of already set footnotes in "processed_footnotes".
Add a `\label` to all footnotes.
Use `\footref` and `\footnotetext` for nested footnotes.
If a footnote is referenced again, just insert a `\footref` reference.
Call `visit_reference()` if the target of a footnote-reference
is not a footnote.
Modified Paths:
--------------
trunk/docutils/docutils/writers/latex2e/__init__.py
trunk/docutils/test/functional/expected/latex_footnotes.tex
trunk/docutils/test/test_writers/test_latex2e.py
Modified: trunk/docutils/docutils/writers/latex2e/__init__.py
===================================================================
--- trunk/docutils/docutils/writers/latex2e/__init__.py 2026-04-30 19:17:00 UTC (rev 10316)
+++ trunk/docutils/docutils/writers/latex2e/__init__.py 2026-04-30 19:17:18 UTC (rev 10317)
@@ -1327,14 +1327,9 @@
self.out = self.body
self.out_stack = [] # stack of output collectors
- # Texts of nested footnotes to emit once we finish the topmost
- # footnote. footnote_queues[i] contains the text of footnotes
- # encountered while processing the current footnote (which is nested
- # within `i` higher footnotes). If i == 0, they will be emitted
- # immediately after the current footnote ends; if i > 0; they will be
- # added to footnote_queues[i-1] after ending the current footnote,
- # which is added to the same queue before them.
- self.footnote_queues = []
+ # LaTeX footnotes (see `visit_footnote_reference()`)
+ self.footnote_queue = [] # FIFO queue of footnote IDs
+ self.processed_footnotes = set() # IDs of already set footnotes
# Process settings
# ~~~~~~~~~~~~~~~~
@@ -2380,7 +2375,7 @@
# prevent spurious whitespace if footnote starts with paragraph:
if len(node) > 1 and isinstance(node[1], nodes.paragraph):
self.out.append('%')
- elif not self.footnote_queues:
+ elif not self.footnote_queue:
# latex-footnotes: set only scheduled footnotes
# (see `visit_footnote_reference()`)
raise nodes.SkipNode
@@ -2404,33 +2399,40 @@
(node['ids'][0], href))
self.context.append('}')
else: # latex-footnotes
- footnote = self.document.ids[href]
- # write footnote content into string `text`
- self.footnote_queues.append([])
- self.push_output_collector([])
- footnote.walkabout(self)
- text = ''.join(self.pop_output_collector())
- queued = self.footnote_queues.pop()
- if not self.footnote_queues:
- self.out.append("\\footnote{%")
- self.out.append(text)
- self.out.append("}")
- for fn in queued:
- self.out.append("\\footnotetext{%")
- self.out.append(fn)
- self.out.append("}")
- else:
- self.out.append("\\footnotemark{}")
- self.footnote_queues[-1].append(text)
- self.footnote_queues[-1].extend(queued)
- raise nodes.SkipNode
- # TODO:
- # * Prepend a label if the footnote is an explicit target.
- # * use \footref (part of LaTeX since 2021-05-01)
- # for multiple refs to the same footnote.
+ target = self.document.ids[href]
+ if not isinstance(target, nodes.footnote):
+ self.out.append(r'\textsuperscript{')
+ self.visit_reference(node)
+ return
+ if href in self.processed_footnotes:
+ # footnote is already set, just insert a reference:
+ self.out.append(r'\footref{%s}' % href)
+ raise nodes.SkipNode
+ self.footnote_queue.append(href)
+ if len(self.footnote_queue) > 1:
+ # nested footnote: insert reference now and text later
+ self.out.append(r'\footref{%s}' % href)
+ raise nodes.SkipNode
+ # if we reach this point, the first queue item is a top level note
+ footnote_cmd = r'\footnote'
+ while self.footnote_queue:
+ ID = self.footnote_queue[0]
+ footnote = self.document.ids[ID]
+ self.out += [footnote_cmd, r'{\label{', ID, '}%']
+ footnote.walkabout(self) # visit/depart footnote & content
+ self.out.append('}')
+ # update queue, keep ID in case the note is referred to again
+ self.processed_footnotes.add(self.footnote_queue.pop(0))
+ # remaining items have the footnote-reference already set:
+ footnote_cmd = '%\n\\refstepcounter{footnote}\\footnotetext'
+ raise nodes.SkipNode # ignore content (footnote number/symbol)
def depart_footnote_reference(self, node) -> None:
- self.out.append(self.context.pop())
+ if self.docutils_footnotes:
+ self.out.append(self.context.pop())
+ elif not isinstance(self.document.ids[node['refid']], nodes.footnote):
+ self.depart_reference(node)
+ self.out.append('}')
# footnote/citation label
def label_delim(self, node, bracket, superscript):
Modified: trunk/docutils/test/functional/expected/latex_footnotes.tex
===================================================================
--- trunk/docutils/test/functional/expected/latex_footnotes.tex 2026-04-30 19:17:00 UTC (rev 10316)
+++ trunk/docutils/test/functional/expected/latex_footnotes.tex 2026-04-30 19:17:18 UTC (rev 10317)
@@ -28,44 +28,40 @@
\section{Test features of rST footnotes}
-\emph{Numbered} footnotes\footnote{%
+\emph{Numbered} footnotes\footnote{\label{exi}%
autonumber-label
-}\footnote{%
+}\footnote{\label{footnote-2}%
auto-numbered (skipping 2)
-}\footnote{%
+}\footnote{\label{footnote-1}%
manually numbered
-}\footnote{%
+}\footnote{\label{footnote-3}%
second auto-numbered (skipping \hyperref[footnote-1]{4} and \hyperref[section-2]{5})
} may be referenced more than
once and also via hyperlink references:
\begin{description}
-\item[{autonumber label\footnote{%
-autonumber-label
-}}]
+\item[{autonumber label\footref{exi}}]
Reference name is the \emph{label} (without \#),
\hyperref[exi]{exi}, not the number (both \texttt{1\_} and \texttt{{[}1{]}\_} fail).
-\item[{manually numbered\footnote{%
-manually numbered
-}}]
+\item[{manually numbered\footref{footnote-1}}]
Reference name is the specified number, \hyperref[footnote-1]{4}.
-\item[{auto-numbered\footnote{%
-auto-numbered (skipping 2)
-}}]
+\item[{auto-numbered\footref{footnote-2}}]
Reference name is the auto-assigned number, \hyperref[footnote-2]{3}
(caution: the assigned number may change if another
auto-numbered footnote is added or removed).
\end{description}
-\emph{Symbol} footnotes\footnote{%
-auto-symbol with nested footnotes\footnotemark{}\footnotemark{}
-}\footnotetext{%
+\emph{Symbol} footnotes\footnote{\label{footnote-4}%
+auto-symbol with nested footnotes\footref{footnote-6}\footref{footnote-7}
+}%
+\refstepcounter{footnote}\footnotetext{\label{footnote-6}%
referenced from the first auto-symbol footnote
-}\footnotetext{%
+}%
+\refstepcounter{footnote}\footnotetext{\label{footnote-7}%
referenced from the first auto-symbol footnote, too
-}\footnote{%
+}\footnote{\label{footnote-5}%
second auto-symbol
} do not have a reference name.
They can only be referenced once and only with a footnote-reference.
@@ -82,12 +78,8 @@
\section{4}
An \emph{implicit} target with conflicting refname (like this section) is
-overwritten by manually numbered footnotes\footnote{%
-manually numbered
-} and footnotes with
-autonumber-label\footnote{%
-autonumber-label
-}. An INFO is generated.
+overwritten by manually numbered footnotes\footref{footnote-1} and footnotes with
+autonumber-label\footref{exi}. An INFO is generated.
Hyperlink references to \hyperref[footnote-1]{4} and \hyperref[exi]{exi} point to the footnotes, not the
sections.
@@ -108,7 +100,7 @@
(e.g. the inline target \textquotedbl{}%
\phantomsection\label{target-4}2\textquotedbl{} and the section title \textquotedbl{}5\textquotedbl{} above)
cause a gap in footnote auto-numbering. The number can be used in a
-footnote-reference although it does not refer to a footnote!
+footnote-reference although it does not refer to a footnote!\textsuperscript{\hyperref[section-2]{5}}\textsuperscript{\hyperref[target-4]{2}}
\begin{description}
\item[{TODO:}]
Modified: trunk/docutils/test/test_writers/test_latex2e.py
===================================================================
--- trunk/docutils/test/test_writers/test_latex2e.py 2026-04-30 19:17:00 UTC (rev 10316)
+++ trunk/docutils/test/test_writers/test_latex2e.py 2026-04-30 19:17:18 UTC (rev 10317)
@@ -985,97 +985,97 @@
samples['latex-footnotes'] = ({'docutils_footnotes': False}, [
-# simple
-["""\
+# different markup variants
+[r"""
Paragraphs contain text and may contain footnote references (manually
numbered [1]_, anonymous auto-numbered [#]_, labeled auto-numbered
[#label]_, or symbolic [*]_).
-.. [1] A footnote contains body elements, consistently indented by at
- least 3 spaces.
+.. [1] A footnote.
- This is the footnote's second paragraph.
-
.. [#label] Footnotes may be numbered, either manually or
automatically using a "#"-prefixed label. This footnote has a
label so it can be referred to from multiple places, both as a
- footnote reference and as a hyperlink reference.
+ footnote reference ([#label]_) and as a `hyperlink reference`__.
+ __ label_
+
.. [#] This footnote is numbered automatically and anonymously using a
label of "#" only.
-.. [*] Footnotes may also use symbols, specified with a "*" label.
+.. [*] With "latex-footnotes", symbolic footnotes and numbered footnotes
+ are merged, the choice of symbol vs. number is done by LaTeX styling.
""",
r"""
Paragraphs contain text and may contain footnote references (manually
-numbered\footnote{%
-A footnote contains body elements, consistently indented by at
-least 3 spaces.
-
-This is the footnote's second paragraph.
-}, anonymous auto-numbered\footnote{%
+numbered\footnote{\label{footnote-1}%
+A footnote.
+}, anonymous auto-numbered\footnote{\label{footnote-2}%
This footnote is numbered automatically and anonymously using a
label of \textquotedbl{}\#\textquotedbl{} only.
-}, labeled auto-numbered\footnote{%
+}, labeled auto-numbered\footnote{\label{label}%
Footnotes may be numbered, either manually or
automatically using a \textquotedbl{}\#\textquotedbl{}-prefixed label. This footnote has a
label so it can be referred to from multiple places, both as a
-footnote reference and as a hyperlink reference.
-}, or symbolic\footnote{%
-Footnotes may also use symbols, specified with a \textquotedbl{}*\textquotedbl{} label.
+footnote reference (\footref{label}) and as a \hyperref[label]{hyperlink reference}.
+}%
+\refstepcounter{footnote}\footnotetext{\label{label}%
+Footnotes may be numbered, either manually or
+automatically using a \textquotedbl{}\#\textquotedbl{}-prefixed label. This footnote has a
+label so it can be referred to from multiple places, both as a
+footnote reference (\footref{label}) and as a \hyperref[label]{hyperlink reference}.
+}, or symbolic\footnote{\label{footnote-3}%
+With \textquotedbl{}latex-footnotes\textquotedbl{}, symbolic footnotes and numbered footnotes
+are merged, the choice of symbol vs. number is done by LaTeX styling.
}).
"""],
-# nested
+# multi-nested footnotes
["""\
-It's possible to produce nested footnotes in LaTeX. [#]_
+A footnote [#multi]_
-.. [#] It takes some work, though. [#]_
-.. [#] And don't even get me started on how tricky recursive footnotes would be.
+.. [#multi] This is a footnote with nested [#]_ footnotes. [#]_ [#]_
+.. [#] First nested [#]_ footnote. [#]_
+.. [#] Second nested footnote. [#]_
+.. [#] Third nested footnote.
+.. [#] First double-nested footnote.
+.. [#] Second double-nested footnote.
+.. [#] First triple-nested footnote.
+.. [#] Not nested, referenced after footnote text.
+
+Ref to a new footnote [#]_
+
+A second reference to the first one [#multi]_.
+We can also write a hyperlink to multi_.
""",
r"""
-It's possible to produce nested footnotes in LaTeX.\footnote{%
-It takes some work, though.\footnotemark{}
-}\footnotetext{%
-And don't even get me started on how tricky recursive footnotes would be.
+A footnote\footnote{\label{multi}%
+This is a footnote with nested\footref{footnote-1} footnotes.\footref{footnote-2}\footref{footnote-3}
+}%
+\refstepcounter{footnote}\footnotetext{\label{footnote-1}%
+First nested\footref{footnote-4} footnote.\footref{footnote-5}
+}%
+\refstepcounter{footnote}\footnotetext{\label{footnote-2}%
+Second nested footnote.\footref{footnote-6}
+}%
+\refstepcounter{footnote}\footnotetext{\label{footnote-3}%
+Third nested footnote.
+}%
+\refstepcounter{footnote}\footnotetext{\label{footnote-4}%
+First double-nested footnote.
+}%
+\refstepcounter{footnote}\footnotetext{\label{footnote-5}%
+Second double-nested footnote.
+}%
+\refstepcounter{footnote}\footnotetext{\label{footnote-6}%
+First triple-nested footnote.
}
-"""],
-# chained
-["""\
-It's possible to produce chained footnotes in LaTeX. [#]_
-.. [#] They're just a special case of nested footnotes. [#]_
-.. [#] A nested footnote is a footnote on a footnote. [#]_
-.. [#] This is a footnote on a footnote on a footnote.
-""",
-r"""
-It's possible to produce chained footnotes in LaTeX.\footnote{%
-They're just a special case of nested footnotes.\footnotemark{}
-}\footnotetext{%
-A nested footnote is a footnote on a footnote.\footnotemark{}
-}\footnotetext{%
-This is a footnote on a footnote on a footnote.
+Ref to a new footnote\footnote{\label{footnote-7}%
+Not nested, referenced after footnote text.
}
-"""],
-# multinested
-["""\
-LaTeX isn't the best at nested footnote support. [#]_
-.. [#] Specifically, it gets the numbers wrong [#]_ for "multinested"
- footnotes. [#]_
-.. [#] For example, this should be footnote 2, but both it and the next one
- show up as footnote 3.
-.. [#] That's a footnote that contains more than one footnote of its own.
-""",
-r"""
-LaTeX isn't the best at nested footnote support.\footnote{%
-Specifically, it gets the numbers wrong\footnotemark{} for \textquotedbl{}multinested\textquotedbl{}
-footnotes.\footnotemark{}
-}\footnotetext{%
-For example, this should be footnote 2, but both it and the next one
-show up as footnote 3.
-}\footnotetext{%
-That's a footnote that contains more than one footnote of its own.
-}
+A second reference to the first one\footref{multi}.
+We can also write a hyperlink to \hyperref[multi]{multi}.
"""],
])
This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.