Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Override generation of id & href attributes for API documentation #1208

Merged
merged 3 commits into from Apr 25, 2023
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
82 changes: 82 additions & 0 deletions src/pydata_sphinx_theme/translator.py
@@ -1,8 +1,10 @@
"""A custom Sphinx HTML Translator for Bootstrap layout."""

import types
from functools import partial

import sphinx
from docutils.nodes import Element
from packaging.version import Version
from sphinx.application import Sphinx
from sphinx.ext.autosummary import autosummary_table
Expand Down Expand Up @@ -59,6 +61,86 @@ def visit_table(self, node):
tag = self.starttag(node, "table", CLASS=" ".join(classes), **atts)
self.body.append(tag)

# NOTE: `visit_section`, `visit_desc_signature` & `visit_reference` are extended
# here to resolve #1026 & #1207. There is an open issue with Sphinx to address this:
# https://github.com/sphinx-doc/sphinx/issues/11208
# If the issue is resolved within Sphinx, these methods can be removed.

def visit_section(self, node):
"""Handle section nodes to replace dots with underscores.
This will modify the ``id`` of HTML ``<section>`` tags, where Python modules
are documented. Replacing dots with underscores allows the tags to be recognized
as navigation targets by ScrollSpy.
"""
if "ids" in node:
node["ids"] = [id_.replace(".", "_") for id_ in node["ids"]]
super().visit_section(node)

def visit_desc_signature(self, node):
"""Handle function & method signature nodes to replace dots with underscores.
This will modify the ``id`` attribute of HTML ``<dt>`` & ``<dd>`` tags, where
Python functions are documented. Replacing dots with underscores allows the tags
to be recognized as navigation targets by ScrollSpy.
"""
if "ids" in node:
ids = node["ids"]
for i, id_ in enumerate(ids):
ids[i] = id_.replace(".", "_")
super().visit_desc_signature(node)

def visit_reference(self, node):
"""Handle reference nodes to replace dots with underscores.
This will modify the ``href`` attribute of any internal HTML ``<a>`` tags, e.g.
the sidebar navigation links.
"""
try:
# We are only interested in internal anchor references
internal, anchorname = node["internal"], node["anchorname"]
if internal and anchorname.startswith("#") and "." in anchorname:
# Get the root node of the current document
document = self.builder.env.get_doctree(self.builder.current_docname)

# Get the target anchor ID
target_id = anchorname.lstrip("#")
sanitized_id = target_id.replace(".", "_")
# Update the node `href`
node["refuri"] = node["anchorname"] = "#" + sanitized_id

# Define a search condition to find the target node by ID
def find_target(search_id, node):
return (
isinstance(node, Element)
and ("ids" in node)
and (search_id in node["ids"])
)

# NOTE: Replacing with underscores creates the possibility for
# conflicting references. We should check for these and warn the
# user if any are found.
if any(document.traverse(condition=partial(find_target, sanitized_id))):
logger.warning(
f'Sanitized reference "{sanitized_id}" for "{target_id}" '
"conflicts with an existing reference!"
)

# Find nodes with the given ID (there should only be one)
targets = document.traverse(condition=partial(find_target, target_id))
# Replace dots with underscores in the target node ID
for target in targets:
# NOTE: By itself, modifying the target `ids` here seems to be
# insufficient, however it helps ensure that the reference `refuri`
# and target `ids` remain consistent during the build process
target["ids"] = [
sanitized_id if id_ == target_id else id_
for id_ in target["ids"]
]
except KeyError:
pass
super().visit_reference(node)


def setup_translators(app: Sphinx):
"""Add bootstrap HTML functionality if we are using an HTML translator.
Expand Down