mirror of
https://github.com/sphinx-doc/sphinx.git
synced 2025-02-25 18:55:22 -06:00
Fix #4501: graphviz: epub3 validation error caused if graph is not clickable
This commit is contained in:
1
CHANGES
1
CHANGES
@@ -24,6 +24,7 @@ Bugs fixed
|
||||
* #4477: Build fails after building specific files
|
||||
* #4449: apidoc: include "empty" packages that contain modules
|
||||
* #3917: citation labels are tranformed to ellipsis
|
||||
* #4501: graphviz: epub3 validation error caused if graph is not clickable
|
||||
|
||||
Testing
|
||||
--------
|
||||
|
||||
@@ -38,13 +38,47 @@ if False:
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
mapname_re = re.compile(r'<map id="(.*?)"')
|
||||
|
||||
|
||||
class GraphvizError(SphinxError):
|
||||
category = 'Graphviz error'
|
||||
|
||||
|
||||
class ClickableMapDefinition(object):
|
||||
"""A manipulator for clickable map file of graphviz."""
|
||||
maptag_re = re.compile('<map id="(.*?)"')
|
||||
href_re = re.compile('href=".*?"')
|
||||
|
||||
def __init__(self, filename, content):
|
||||
# type: (unicode, unicode) -> None
|
||||
self.id = None
|
||||
self.filename = filename
|
||||
self.content = content.splitlines()
|
||||
self.clickable = [] # type: List[unicode]
|
||||
|
||||
self.parse()
|
||||
|
||||
def parse(self):
|
||||
# type: () -> None
|
||||
matched = self.maptag_re.match(self.content[0]) # type: ignore
|
||||
if not matched:
|
||||
raise GraphvizError('Invalid clickable map file found: %s' % self.filename)
|
||||
|
||||
self.id = matched.group(1)
|
||||
for line in self.content:
|
||||
if self.href_re.search(line):
|
||||
self.clickable.append(line)
|
||||
|
||||
def generate_clickable_map(self):
|
||||
# type: () -> None
|
||||
"""Generate clickable map tags if clickable item exists.
|
||||
|
||||
If not exists, this only returns empty string.
|
||||
"""
|
||||
if self.clickable:
|
||||
return '\n'.join([self.content[0]] + self.clickable + [self.content[-1]])
|
||||
else:
|
||||
return ''
|
||||
|
||||
|
||||
class graphviz(nodes.General, nodes.Inline, nodes.Element):
|
||||
pass
|
||||
|
||||
@@ -254,18 +288,17 @@ def render_dot_html(self, node, code, options, prefix='graphviz',
|
||||
<p class="warning">%s</p></object>\n''' % (fname, alt)
|
||||
self.body.append(svgtag)
|
||||
else:
|
||||
with open(outfn + '.map', 'rb') as mapfile:
|
||||
imgmap = mapfile.readlines()
|
||||
if len(imgmap) == 2:
|
||||
# nothing in image map (the lines are <map> and </map>)
|
||||
self.body.append('<img src="%s" alt="%s" %s/>\n' %
|
||||
(fname, alt, imgcss))
|
||||
else:
|
||||
# has a map: get the name of the map and connect the parts
|
||||
mapname = mapname_re.match(imgmap[0].decode('utf-8')).group(1) # type: ignore
|
||||
self.body.append('<img src="%s" alt="%s" usemap="#%s" %s/>\n' %
|
||||
(fname, alt, mapname, imgcss))
|
||||
self.body.extend([item.decode('utf-8') for item in imgmap])
|
||||
with codecs.open(outfn + '.map', 'r', encoding='utf-8') as mapfile:
|
||||
imgmap = ClickableMapDefinition(outfn + '.map', mapfile.read())
|
||||
if imgmap.clickable:
|
||||
# has a map
|
||||
self.body.append('<img src="%s" alt="%s" usemap="#%s" %s/>\n' %
|
||||
(fname, alt, imgmap.id, imgcss))
|
||||
self.body.append(imgmap.generate_clickable_map())
|
||||
else:
|
||||
# nothing in image map
|
||||
self.body.append('<img src="%s" alt="%s" %s/>\n' %
|
||||
(fname, alt, imgcss))
|
||||
if 'align' in node:
|
||||
self.body.append('</div>\n')
|
||||
|
||||
|
||||
@@ -13,6 +13,8 @@ import re
|
||||
|
||||
import pytest
|
||||
|
||||
from sphinx.ext.graphviz import ClickableMapDefinition
|
||||
|
||||
|
||||
@pytest.mark.sphinx('html', testroot='ext-graphviz')
|
||||
@pytest.mark.usefixtures('if_graphviz_found')
|
||||
@@ -113,3 +115,61 @@ def test_graphviz_i18n(app, status, warning):
|
||||
content = (app.outdir / 'index.html').text()
|
||||
html = '<img src=".*?" alt="digraph {\n BAR -> BAZ\n}" />'
|
||||
assert re.search(html, content, re.M)
|
||||
|
||||
|
||||
def test_graphviz_parse_mapfile():
|
||||
# digraph {
|
||||
# }
|
||||
content = ('<map id="%3" name="%3">\n'
|
||||
'</map>')
|
||||
cmap = ClickableMapDefinition('dummy.map', content)
|
||||
assert cmap.filename == 'dummy.map'
|
||||
assert cmap.id == '%3'
|
||||
assert len(cmap.clickable) == 0
|
||||
assert cmap.generate_clickable_map() == ''
|
||||
|
||||
# digraph {
|
||||
# foo [href="http://www.google.com/"];
|
||||
# foo -> bar;
|
||||
# }
|
||||
content = ('<map id="%3" name="%3">\n'
|
||||
'<area shape="poly" id="node1" href="http://www.google.com/" title="foo" alt=""'
|
||||
' coords="77,29,76,22,70,15,62,10,52,7,41,5,30,7,20,10,12,15,7,22,5,29,7,37,12,'
|
||||
'43,20,49,30,52,41,53,52,52,62,49,70,43,76,37"/>\n'
|
||||
'</map>')
|
||||
cmap = ClickableMapDefinition('dummy.map', content)
|
||||
assert cmap.filename == 'dummy.map'
|
||||
assert cmap.id == '%3'
|
||||
assert len(cmap.clickable) == 1
|
||||
assert cmap.generate_clickable_map() == content
|
||||
|
||||
# inheritance-diagram:: sphinx.builders.html
|
||||
content = (
|
||||
'<map id="inheritance66ff5471b9" name="inheritance66ff5471b9">\n'
|
||||
'<area shape="rect" id="node1" title="Builds target formats from the reST sources."'
|
||||
' alt="" coords="26,95,125,110"/>\n'
|
||||
'<area shape="rect" id="node5" title="Builds standalone HTML docs."'
|
||||
' alt="" coords="179,95,362,110"/>\n'
|
||||
'<area shape="rect" id="node2" title="buildinfo file manipulator." '
|
||||
' alt="" coords="14,64,138,80"/>\n'
|
||||
'<area shape="rect" id="node3" title="The container of stylesheets."'
|
||||
' alt="" coords="3,34,148,49"/>\n'
|
||||
'<area shape="rect" id="node4" title="A StandaloneHTMLBuilder that creates all HTML'
|
||||
' pages as "index.html" in" alt="" coords="395,64,569,80"/>\n'
|
||||
'<area shape="rect" id="node7" title="An abstract builder that serializes'
|
||||
' the generated HTML." alt="" coords="392,95,571,110"/>\n'
|
||||
'<area shape="rect" id="node9" title="A StandaloneHTMLBuilder subclass that puts'
|
||||
' the whole document tree on one" alt="" coords="393,125,570,141"/>\n'
|
||||
'<area shape="rect" id="node6" title="A builder that dumps the generated HTML'
|
||||
' into JSON files." alt="" coords="602,80,765,95"/>\n'
|
||||
'<area shape="rect" id="node8" title="A Builder that dumps the generated HTML'
|
||||
' into pickle files." alt="" coords="602,110,765,125"/>\n'
|
||||
'<area shape="rect" id="node10" title="The metadata of stylesheet."'
|
||||
' alt="" coords="11,3,141,19"/>\n'
|
||||
'</map>'
|
||||
)
|
||||
cmap = ClickableMapDefinition('dummy.map', content)
|
||||
assert cmap.filename == 'dummy.map'
|
||||
assert cmap.id == 'inheritance66ff5471b9'
|
||||
assert len(cmap.clickable) == 0
|
||||
assert cmap.generate_clickable_map() == ''
|
||||
|
||||
Reference in New Issue
Block a user