Skip to content

Commit

Permalink
singlehtml: Use same-document hyperlinks for internal project referen…
Browse files Browse the repository at this point in the history
…ces (#12551)

Co-authored-by: Bénédikt Tran <[email protected]>
Co-authored-by: Adam Turner <[email protected]>
  • Loading branch information
3 people authored Aug 11, 2024
1 parent a3f1383 commit 0bfaadf
Show file tree
Hide file tree
Showing 5 changed files with 45 additions and 2 deletions.
5 changes: 5 additions & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,11 @@ Bugs fixed
get passed to :program:`latexmk`. Let :option:`-Q <sphinx-build -Q>`
(silent) apply as well to the PDF build phase.
Patch by Jean-François B.
* #11970, #12551: singlehtml builder: make target URIs to be same-document
references in the sense of :rfc:`RFC 3986, §4.4 <3986#section-4.4>`,
e.g., ``index.html#foo`` becomes ``#foo``.
(note: continuation of a partial fix added in Sphinx 7.3.0)
Patch by James Addison (with reference to prior work by Eric Norige)

Testing
-------
Expand Down
4 changes: 2 additions & 2 deletions sphinx/builders/singlehtml.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,6 @@ def get_relative_uri(self, from_: str, to: str, typ: str | None = None) -> str:

def fix_refuris(self, tree: Node) -> None:
# fix refuris with double anchor
fname = self.config.root_doc + self.out_suffix
for refnode in tree.findall(nodes.reference):
if 'refuri' not in refnode:
continue
Expand All @@ -62,7 +61,8 @@ def fix_refuris(self, tree: Node) -> None:
continue
hashindex = refuri.find('#', hashindex + 1)
if hashindex >= 0:
refnode['refuri'] = fname + refuri[hashindex:]
# all references are on the same page...
refnode['refuri'] = refuri[hashindex:]

def _get_local_toctree(self, docname: str, collapse: bool = True, **kwargs: Any) -> str:
if isinstance(includehidden := kwargs.get('includehidden'), str):
Expand Down
2 changes: 2 additions & 0 deletions tests/test_builders/test_build_html_tocdepth.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import pytest

from tests.test_builders.xpath_html_util import _intradocument_hyperlink_check
from tests.test_builders.xpath_util import check_xpath


Expand Down Expand Up @@ -78,6 +79,7 @@ def test_tocdepth(app, cached_etree_parse, fname, path, check, be_found):
(".//li[@class='toctree-l3']/a", '1.2.1. Foo B1', True),
(".//li[@class='toctree-l3']/a", '2.1.1. Bar A1', False),
(".//li[@class='toctree-l3']/a", '2.2.1. Bar B1', False),
(".//ul/li[@class='toctree-l1']/..//a", _intradocument_hyperlink_check),
# index.rst
('.//h1', 'test-tocdepth', True),
# foo.rst
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@

import pytest

from tests.test_builders.xpath_html_util import _intradocument_hyperlink_check
from tests.test_builders.xpath_util import check_xpath


@pytest.mark.sphinx(testroot='toctree-glob')
def test_relations(app):
Expand Down Expand Up @@ -45,3 +48,17 @@ def test_numbered_toctree(app):
index = re.sub(':numbered:.*', ':numbered: 1', index)
(app.srcdir / 'index.rst').write_text(index, encoding='utf8')
app.build(force_all=True)


@pytest.mark.parametrize(
'expect',
[
# internal references should be same-document; external should not
(".//a[@class='reference internal']", _intradocument_hyperlink_check),
(".//a[@class='reference external']", r'https?://'),
],
)
@pytest.mark.sphinx('singlehtml', testroot='toctree')
def test_singlehtml_hyperlinks(app, cached_etree_parse, expect):
app.build()
check_xpath(cached_etree_parse(app.outdir / 'index.html'), 'index.html', *expect)
19 changes: 19 additions & 0 deletions tests/test_builders/xpath_html_util.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
from __future__ import annotations

from typing import TYPE_CHECKING

if TYPE_CHECKING:
from collections.abc import Sequence
from xml.etree.ElementTree import Element


def _intradocument_hyperlink_check(nodes: Sequence[Element]) -> None:
"""Confirm that a series of nodes are all HTML hyperlinks to the current page"""
assert nodes, 'Expected at least one node to check'
for node in nodes:
assert node.tag == 'a', 'Attempted to check hyperlink on a non-anchor element'
href = node.attrib.get('href')
# Allow Sphinx index and table hyperlinks to be non-same-document, as exceptions.
if href in {'genindex.html', 'py-modindex.html', 'search.html'}:
continue
assert not href or href.startswith('#'), 'Hyperlink failed same-document check'

0 comments on commit 0bfaadf

Please sign in to comment.