sphinxcontrib-bibtex icon indicating copy to clipboard operation
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

Open raek opened this issue 6 months ago • 2 comments

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)

raek avatar Aug 20 '24 12:08 raek