Skip to content

Commit

Permalink
Override generation of id & href attributes for API documentation (
Browse files Browse the repository at this point in the history
…#1208)

* Override generation of `id` & `href` attributes for API documentation

This will replace dots with underscores in qualified names of Python modules & functions (e.g. `mypackage.mymodule.myfunc`), allowing ScrollSpy to identify these tags as navigation targets.

Addresses #1207 and fixes #1026

* Apply linter corrections & add comment referencing open Sphinx issue

---------

Co-authored-by: Rambaud Pierrick <12rambau@users.noreply.github.com>
  • Loading branch information
bheberlein and 12rambau committed Apr 25, 2023
1 parent 46bb8db commit 1522b84
Showing 1 changed file with 82 additions and 0 deletions.
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

1 comment on commit 1522b84

@tpoint75
Copy link

@tpoint75 tpoint75 commented on 1522b84 May 3, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I add this to visit_reference to fix the internal links:
Please find the right way to do it..
tpoint75@f036f4b

Please sign in to comment.