mirror of
https://github.com/sphinx-doc/sphinx.git
synced 2025-02-25 18:55:22 -06:00
URI-escape image filenames (#10268)
Without this change, local images with `#` in their name result in incorrect URLs There is already a similar call to `urllib.parse.quote` for file downloads, suggesting this is a sensible approach. Co-authored-by: Adam Turner <9087854+AA-Turner@users.noreply.github.com> Co-authored-by: Takeshi KOMIYA <i.tkomiya@gmail.com>
This commit is contained in:
parent
e008e16200
commit
fa6d42597f
1
CHANGES
1
CHANGES
@ -20,6 +20,7 @@ Features added
|
|||||||
``:option:`--module[=foobar]``` or ``:option:`--module foobar```.
|
``:option:`--module[=foobar]``` or ``:option:`--module foobar```.
|
||||||
Patch by Martin Liska.
|
Patch by Martin Liska.
|
||||||
* #10881: autosectionlabel: Record the generated section label to the debug log.
|
* #10881: autosectionlabel: Record the generated section label to the debug log.
|
||||||
|
* #10268: Correctly URI-escape image filenames.
|
||||||
|
|
||||||
Bugs fixed
|
Bugs fixed
|
||||||
----------
|
----------
|
||||||
|
@ -5,6 +5,7 @@ import os
|
|||||||
import re
|
import re
|
||||||
from os import path
|
from os import path
|
||||||
from typing import Any, Dict, List, NamedTuple, Optional, Set, Tuple
|
from typing import Any, Dict, List, NamedTuple, Optional, Set, Tuple
|
||||||
|
from urllib.parse import quote
|
||||||
from zipfile import ZIP_DEFLATED, ZIP_STORED, ZipFile
|
from zipfile import ZIP_DEFLATED, ZIP_STORED, ZipFile
|
||||||
|
|
||||||
from docutils import nodes
|
from docutils import nodes
|
||||||
@ -524,7 +525,7 @@ class EpubBuilder(StandaloneHTMLBuilder):
|
|||||||
type='epub', subtype='unknown_project_files')
|
type='epub', subtype='unknown_project_files')
|
||||||
continue
|
continue
|
||||||
filename = filename.replace(os.sep, '/')
|
filename = filename.replace(os.sep, '/')
|
||||||
item = ManifestItem(html.escape(filename),
|
item = ManifestItem(html.escape(quote(filename)),
|
||||||
html.escape(self.make_id(filename)),
|
html.escape(self.make_id(filename)),
|
||||||
html.escape(self.media_types[ext]))
|
html.escape(self.media_types[ext]))
|
||||||
metadata['manifest_items'].append(item)
|
metadata['manifest_items'].append(item)
|
||||||
|
@ -620,7 +620,7 @@ class HTMLTranslator(SphinxTranslator, BaseTranslator):
|
|||||||
# rewrite the URI if the environment knows about it
|
# rewrite the URI if the environment knows about it
|
||||||
if olduri in self.builder.images:
|
if olduri in self.builder.images:
|
||||||
node['uri'] = posixpath.join(self.builder.imgpath,
|
node['uri'] = posixpath.join(self.builder.imgpath,
|
||||||
self.builder.images[olduri])
|
urllib.parse.quote(self.builder.images[olduri]))
|
||||||
|
|
||||||
if 'scale' in node:
|
if 'scale' in node:
|
||||||
# Try to figure out image height and width. Docutils does that too,
|
# Try to figure out image height and width. Docutils does that too,
|
||||||
|
@ -567,7 +567,7 @@ class HTML5Translator(SphinxTranslator, BaseTranslator):
|
|||||||
# rewrite the URI if the environment knows about it
|
# rewrite the URI if the environment knows about it
|
||||||
if olduri in self.builder.images:
|
if olduri in self.builder.images:
|
||||||
node['uri'] = posixpath.join(self.builder.imgpath,
|
node['uri'] = posixpath.join(self.builder.imgpath,
|
||||||
self.builder.images[olduri])
|
urllib.parse.quote(self.builder.images[olduri]))
|
||||||
|
|
||||||
if 'scale' in node:
|
if 'scale' in node:
|
||||||
# Try to figure out image height and width. Docutils does that too,
|
# Try to figure out image height and width. Docutils does that too,
|
||||||
|
@ -1319,14 +1319,17 @@ class LaTeXTranslator(SphinxTranslator):
|
|||||||
if include_graphics_options:
|
if include_graphics_options:
|
||||||
options = '[%s]' % ','.join(include_graphics_options)
|
options = '[%s]' % ','.join(include_graphics_options)
|
||||||
base, ext = path.splitext(uri)
|
base, ext = path.splitext(uri)
|
||||||
|
|
||||||
if self.in_title and base:
|
if self.in_title and base:
|
||||||
# Lowercase tokens forcely because some fncychap themes capitalize
|
# Lowercase tokens forcely because some fncychap themes capitalize
|
||||||
# the options of \sphinxincludegraphics unexpectedly (ex. WIDTH=...).
|
# the options of \sphinxincludegraphics unexpectedly (ex. WIDTH=...).
|
||||||
self.body.append(r'\lowercase{\sphinxincludegraphics%s}{{%s}%s}' %
|
cmd = r'\lowercase{\sphinxincludegraphics%s}{{%s}%s}' % (options, base, ext)
|
||||||
(options, base, ext))
|
|
||||||
else:
|
else:
|
||||||
self.body.append(r'\sphinxincludegraphics%s{{%s}%s}' %
|
cmd = r'\sphinxincludegraphics%s{{%s}%s}' % (options, base, ext)
|
||||||
(options, base, ext))
|
# escape filepath for includegraphics, https://tex.stackexchange.com/a/202714/41112
|
||||||
|
if '#' in base:
|
||||||
|
cmd = r'{\catcode`\#=12' + cmd + '}'
|
||||||
|
self.body.append(cmd)
|
||||||
self.body.extend(post)
|
self.body.extend(post)
|
||||||
|
|
||||||
def depart_image(self, node: Element) -> None:
|
def depart_image(self, node: Element) -> None:
|
||||||
|
0
tests/roots/test-image-escape/conf.py
Normal file
0
tests/roots/test-image-escape/conf.py
Normal file
BIN
tests/roots/test-image-escape/img_#1.png
Normal file
BIN
tests/roots/test-image-escape/img_#1.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 65 KiB |
5
tests/roots/test-image-escape/index.rst
Normal file
5
tests/roots/test-image-escape/index.rst
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
Sphinx image handling
|
||||||
|
=====================
|
||||||
|
|
||||||
|
.. an image with a character that is valid in a local file path but not a URL
|
||||||
|
.. image:: img_#1.png
|
@ -1397,6 +1397,15 @@ def test_html_remote_images(app, status, warning):
|
|||||||
assert not (app.outdir / 'python-logo.png').exists()
|
assert not (app.outdir / 'python-logo.png').exists()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.sphinx('html', testroot='image-escape')
|
||||||
|
def test_html_encoded_image(app, status, warning):
|
||||||
|
app.builder.build_all()
|
||||||
|
|
||||||
|
result = (app.outdir / 'index.html').read_text()
|
||||||
|
assert ('<img alt="_images/img_%231.png" src="_images/img_%231.png" />' in result)
|
||||||
|
assert (app.outdir / '_images/img_#1.png').exists()
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.sphinx('html', testroot='remote-logo')
|
@pytest.mark.sphinx('html', testroot='remote-logo')
|
||||||
def test_html_remote_logo(app, status, warning):
|
def test_html_remote_logo(app, status, warning):
|
||||||
app.builder.build_all()
|
app.builder.build_all()
|
||||||
|
@ -59,8 +59,8 @@ def compile_latex_document(app, filename='python.tex'):
|
|||||||
except OSError as exc: # most likely the latex executable was not found
|
except OSError as exc: # most likely the latex executable was not found
|
||||||
raise pytest.skip.Exception from exc
|
raise pytest.skip.Exception from exc
|
||||||
except CalledProcessError as exc:
|
except CalledProcessError as exc:
|
||||||
print(exc.stdout)
|
print(exc.stdout.decode('utf8'))
|
||||||
print(exc.stderr)
|
print(exc.stderr.decode('utf8'))
|
||||||
raise AssertionError('%s exited with return code %s' % (app.config.latex_engine,
|
raise AssertionError('%s exited with return code %s' % (app.config.latex_engine,
|
||||||
exc.returncode))
|
exc.returncode))
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user