sphinxcontrib-bibtex
sphinxcontrib-bibtex copied to clipboard
When `cite` is used inside an `only` directive, the entry in the `bibliography` is present regardless of the `only` condition
Description
At work we use the only
directive to produce multiple document variants from a single document source. We discovered that if a source was cited inside an only
directive, then the bibliography
entry was always present, regardless whether the citation reference was included or not.
Minimal example:
Some paragraph.
.. only:: april_fools
This paragraph only appears in some configurations :cite:p:`1987:nelson`.
Some other paragraph
.. bibliography:: refs.bib
The expected behavior is for "1987:nelson" to not appear in the bibliography when the "april_fools" tag is not set, but now it is always included.
Analysis
The sphinxcontrib.bibtex.domain.BibtexDomain.env_updated
method is responsible for pruning bibliography entries that are not referenced in the document. It runs after any registered transform has been applied but before any post-transform is applied.
The conditional rendering of the only
directive is applied by the sphinx.transforms.post_transforms.OnlyNodeTransform
post-transform. As this is applied after BibtexDomain.env_updated
has already run, the bilbiography already contains an entry which will "survive".
Workaround
I developed a workaround that solves our immediate problem, but it would be interesting to discuss how this problem could be fixed properly (if it is considered a real bug). Do you have any ideas? This workaround is what I've come up with so far.
import docutils.nodes
from sphinx.transforms.post_transforms import (
SphinxPostTransform, OnlyNodeTransform)
from sphinxcontrib.bibtex.transforms import BibliographyTransform
class PruneCitationPostTransform(SphinxPostTransform):
default_priority = max(BibliographyTransform.default_priority,
OnlyNodeTransform.default_priority) + 1
def run(self, **kwargs) -> None:
env = self.document.settings.env
domain = env.get_domain("cite")
found_ids = set()
for node in self.document.traverse(docutils.nodes.Element):
found_ids.update(node["ids"])
for bibliography in domain.bibliographies.values():
for citation_node in bibliography.citation_nodes.values():
if not citation_node.children:
# This citation was already pruned in
# BibtexDomain.env_updated
continue
if not (set(citation_node["backrefs"]) & found_ids):
# All references to this citations has been
# removed by an Only directive. Prune it!
citation_node.replace_self(docutils.nodes.comment())
def setup(app):
app.add_post_transform(PruneCitationPostTransform)