mirror of
https://github.com/sphinx-doc/sphinx.git
synced 2025-02-25 18:55:22 -06:00
Fix relative references in SVGs generated by `sphinx.ext.graphviz
` (#11078)
Co-authored-by: Ralf Grubenmann <ralf.grubenmann@gmail.com> Co-authored-by: Adam Turner <9087854+aa-turner@users.noreply.github.com>
This commit is contained in:
parent
2c0b81d88b
commit
6178163cb1
3
CHANGES
3
CHANGES
@ -28,6 +28,9 @@ Features added
|
||||
Bugs fixed
|
||||
----------
|
||||
|
||||
* #11077: graphviz: Fix relative links from within the graph.
|
||||
Patch by Ralf Grubenmann.
|
||||
|
||||
Testing
|
||||
-------
|
||||
|
||||
|
@ -6,10 +6,13 @@ from __future__ import annotations
|
||||
import posixpath
|
||||
import re
|
||||
import subprocess
|
||||
import xml.etree.ElementTree as ET
|
||||
from hashlib import sha1
|
||||
from itertools import chain
|
||||
from os import path
|
||||
from subprocess import CalledProcessError
|
||||
from typing import TYPE_CHECKING, Any
|
||||
from urllib.parse import urlsplit, urlunsplit
|
||||
|
||||
from docutils import nodes
|
||||
from docutils.nodes import Node
|
||||
@ -214,6 +217,37 @@ class GraphvizSimple(SphinxDirective):
|
||||
return [figure]
|
||||
|
||||
|
||||
def fix_svg_relative_paths(self: SphinxTranslator, filepath: str) -> None:
|
||||
"""Change relative links in generated svg files to be relative to imgpath."""
|
||||
tree = ET.parse(filepath) # NoQA: S314
|
||||
root = tree.getroot()
|
||||
ns = {'svg': 'http://www.w3.org/2000/svg', 'xlink': 'http://www.w3.org/1999/xlink'}
|
||||
href_name = '{http://www.w3.org/1999/xlink}href'
|
||||
modified = False
|
||||
|
||||
for element in chain(
|
||||
root.findall('.//svg:image[@xlink:href]', ns),
|
||||
root.findall('.//svg:a[@xlink:href]', ns),
|
||||
):
|
||||
scheme, hostname, url, query, fragment = urlsplit(element.attrib[href_name])
|
||||
if hostname:
|
||||
# not a relative link
|
||||
continue
|
||||
|
||||
old_path = path.join(self.builder.outdir, url)
|
||||
new_path = path.relpath(
|
||||
old_path,
|
||||
start=path.join(self.builder.outdir, self.builder.imgpath),
|
||||
)
|
||||
modified_url = urlunsplit((scheme, hostname, new_path, query, fragment))
|
||||
|
||||
element.set(href_name, modified_url)
|
||||
modified = True
|
||||
|
||||
if modified:
|
||||
tree.write(filepath)
|
||||
|
||||
|
||||
def render_dot(self: SphinxTranslator, code: str, options: dict, format: str,
|
||||
prefix: str = 'graphviz', filename: str | None = None,
|
||||
) -> tuple[str | None, str | None]:
|
||||
@ -251,10 +285,6 @@ def render_dot(self: SphinxTranslator, code: str, options: dict, format: str,
|
||||
try:
|
||||
ret = subprocess.run(dot_args, input=code.encode(), capture_output=True,
|
||||
cwd=cwd, check=True)
|
||||
if not path.isfile(outfn):
|
||||
raise GraphvizError(__('dot did not produce an output file:\n[stderr]\n%r\n'
|
||||
'[stdout]\n%r') % (ret.stderr, ret.stdout))
|
||||
return relfn, outfn
|
||||
except OSError:
|
||||
logger.warning(__('dot command %r cannot be run (needed for graphviz '
|
||||
'output), check the graphviz_dot setting'), graphviz_dot)
|
||||
@ -265,6 +295,14 @@ def render_dot(self: SphinxTranslator, code: str, options: dict, format: str,
|
||||
except CalledProcessError as exc:
|
||||
raise GraphvizError(__('dot exited with error:\n[stderr]\n%r\n'
|
||||
'[stdout]\n%r') % (exc.stderr, exc.stdout)) from exc
|
||||
if not path.isfile(outfn):
|
||||
raise GraphvizError(__('dot did not produce an output file:\n[stderr]\n%r\n'
|
||||
'[stdout]\n%r') % (ret.stderr, ret.stdout))
|
||||
|
||||
if format == 'svg':
|
||||
fix_svg_relative_paths(self, outfn)
|
||||
|
||||
return relfn, outfn
|
||||
|
||||
|
||||
def render_dot_html(self: HTML5Translator, node: graphviz, code: str, options: dict,
|
||||
|
8
tests/roots/test-ext-graphviz/_static/images/test.svg
Normal file
8
tests/roots/test-ext-graphviz/_static/images/test.svg
Normal file
@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg version="1.1"
|
||||
height="128" width="128"
|
||||
xmlns="http://www.w3.org/2000/svg">
|
||||
|
||||
<rect width="100%" height="100%" fill="red" />
|
||||
|
||||
</svg>
|
After Width: | Height: | Size: 187 B |
@ -1,2 +1,3 @@
|
||||
extensions = ['sphinx.ext.graphviz']
|
||||
exclude_patterns = ['_build']
|
||||
html_static_path = ["_static"]
|
||||
|
@ -31,3 +31,13 @@ Hello |graph| graphviz world
|
||||
:align: center
|
||||
|
||||
centered
|
||||
|
||||
.. graphviz::
|
||||
:align: center
|
||||
|
||||
digraph test {
|
||||
foo [label="foo", URL="#graphviz", target="_parent"]
|
||||
bar [label="bar", image="./_static/images/test.svg"]
|
||||
baz [label="baz", URL="./_static/images/test.svg"]
|
||||
foo -> bar -> baz
|
||||
}
|
||||
|
@ -82,6 +82,17 @@ def test_graphviz_svg_html(app, status, warning):
|
||||
r'</div>')
|
||||
assert re.search(html, content, re.S)
|
||||
|
||||
image_re = r'.*data="([^"]+)".*?digraph test'
|
||||
image_path_match = re.search(image_re, content, re.S)
|
||||
assert image_path_match
|
||||
|
||||
image_path = image_path_match.group(1)
|
||||
image_content = (app.outdir / image_path).read_text(encoding='utf8')
|
||||
assert '"./_static/' not in image_content
|
||||
assert '<ns0:image ns1:href="../_static/images/test.svg"' in image_content
|
||||
assert '<ns0:a ns1:href="../_static/images/test.svg"' in image_content
|
||||
assert '<ns0:a ns1:href="..#graphviz"' in image_content
|
||||
|
||||
|
||||
@pytest.mark.sphinx('latex', testroot='ext-graphviz')
|
||||
@pytest.mark.usefixtures('if_graphviz_found')
|
||||
|
Loading…
Reference in New Issue
Block a user