summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--sphinx/builders/htmlhelp.py136
-rw-r--r--sphinx/templates/htmlhelp/project.hhc31
-rw-r--r--tests/roots/test-htmlhelp-hhc/bar.rst2
-rw-r--r--tests/roots/test-htmlhelp-hhc/baz.rst2
-rw-r--r--tests/roots/test-htmlhelp-hhc/conf.py1
-rw-r--r--tests/roots/test-htmlhelp-hhc/foo.rst6
-rw-r--r--tests/roots/test-htmlhelp-hhc/index.rst15
-rw-r--r--tests/test_build_htmlhelp.py51
8 files changed, 184 insertions, 60 deletions
diff --git a/sphinx/builders/htmlhelp.py b/sphinx/builders/htmlhelp.py
index a182c7632..ce30affba 100644
--- a/sphinx/builders/htmlhelp.py
+++ b/sphinx/builders/htmlhelp.py
@@ -75,24 +75,6 @@ template_dir = path.join(package_dir, 'templates', 'htmlhelp')
# 0x200000 TOC Next
# 0x400000 TOC Prev
-contents_header = '''\
-<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN">
-<HTML>
-<HEAD>
-<meta name="GENERATOR" content="Microsoft&reg; HTML Help Workshop 4.1">
-<!-- Sitemap 1.0 -->
-</HEAD><BODY>
-<OBJECT type="text/site properties">
- <param name="Window Styles" value="0x801227">
- <param name="ImageType" value="Folder">
-</OBJECT>
-<UL>
-'''
-
-contents_footer = '''\
-</UL></BODY></HTML>
-'''
-
object_sitemap = '''\
<OBJECT type="text/sitemap">
<param name="Name" value="%s">
@@ -151,6 +133,63 @@ def chm_htmlescape(s, quote=True):
return s
+class ToCTreeVisitor(nodes.NodeVisitor):
+ def __init__(self, document):
+ # type: (nodes.document) -> None
+ super().__init__(document)
+ self.body = [] # type: List[str]
+ self.depth = 0
+
+ def append(self, text):
+ # type: (str) -> None
+ indent = ' ' * (self.depth - 1)
+ self.body.append(indent + text)
+
+ def astext(self):
+ # type: () -> str
+ return '\n'.join(self.body)
+
+ def unknown_visit(self, node):
+ # type: (nodes.Node) -> None
+ pass
+
+ def unknown_departure(self, node):
+ # type: (nodes.Node) -> None
+ pass
+
+ def visit_bullet_list(self, node):
+ # type: (nodes.Element) -> None
+ if self.depth > 0:
+ self.append('<UL>')
+
+ self.depth += 1
+
+ def depart_bullet_list(self, node):
+ # type: (nodes.Element) -> None
+ self.depth -= 1
+ if self.depth > 0:
+ self.append('</UL>')
+
+ def visit_list_item(self, node):
+ # type: (nodes.Element) -> None
+ self.append('<LI>')
+ self.depth += 1
+
+ def depart_list_item(self, node):
+ # type: (nodes.Element) -> None
+ self.depth -= 1
+ self.append('</LI>')
+
+ def visit_reference(self, node):
+ # type: (nodes.Element) -> None
+ title = chm_htmlescape(node.astext(), True)
+ self.append('<OBJECT type="text/sitemap">')
+ self.append(' <PARAM name="Name" value="%s" />' % title)
+ self.append(' <PARAM name="Local" value="%s" />' % node['refuri'])
+ self.append('</OBJECT>')
+ raise nodes.SkipNode
+
+
class HTMLHelpBuilder(StandaloneHTMLBuilder):
"""
Builder that also outputs Windows HTML help project, contents and
@@ -202,6 +241,7 @@ class HTMLHelpBuilder(StandaloneHTMLBuilder):
# type: () -> None
self.copy_stopword_list()
self.build_project_file()
+ self.build_toc_file()
self.build_hhx(self.outdir, self.config.htmlhelp_basename)
def write_doc(self, docname, doctree):
@@ -263,48 +303,30 @@ class HTMLHelpBuilder(StandaloneHTMLBuilder):
body = self.render('project.hhp', context)
f.write(body)
- def build_hhx(self, outdir, outname):
- # type: (str, str) -> None
- logger.info(__('writing TOC file...'))
- filename = path.join(outdir, outname + '.hhc')
+ @progress_message(__('writing TOC file'))
+ def build_toc_file(self):
+ # type: () -> None
+ """Create a ToC file (.hhp) on outdir."""
+ filename = path.join(self.outdir, self.config.htmlhelp_basename + '.hhc')
with open(filename, 'w', encoding=self.encoding, errors='xmlcharrefreplace') as f:
- f.write(contents_header)
- # special books
- f.write('<LI> ' + object_sitemap % (self.config.html_short_title,
- self.config.master_doc + self.out_suffix))
- for indexname, indexcls, content, collapse in self.domain_indices:
- f.write('<LI> ' + object_sitemap % (indexcls.localname,
- '%s.html' % indexname))
- # the TOC
- tocdoc = self.env.get_and_resolve_doctree(
- self.config.master_doc, self, prune_toctrees=False)
-
- def write_toc(node, ullevel=0):
- # type: (nodes.Node, int) -> None
- if isinstance(node, nodes.list_item):
- f.write('<LI> ')
- for subnode in node:
- write_toc(subnode, ullevel)
- elif isinstance(node, nodes.reference):
- link = node['refuri']
- title = chm_htmlescape(node.astext(), True)
- f.write(object_sitemap % (title, link))
- elif isinstance(node, nodes.bullet_list):
- if ullevel != 0:
- f.write('<UL>\n')
- for subnode in node:
- write_toc(subnode, ullevel + 1)
- if ullevel != 0:
- f.write('</UL>\n')
- elif isinstance(node, addnodes.compact_paragraph):
- for subnode in node:
- write_toc(subnode, ullevel)
-
+ toctree = self.env.get_and_resolve_doctree(self.config.master_doc, self,
+ prune_toctrees=False)
+ visitor = ToCTreeVisitor(toctree)
matcher = NodeMatcher(addnodes.compact_paragraph, toctree=True)
- for node in tocdoc.traverse(matcher): # type: addnodes.compact_paragraph
- write_toc(node)
- f.write(contents_footer)
+ for node in toctree.traverse(matcher): # type: addnodes.compact_paragraph
+ node.walkabout(visitor)
+
+ context = {
+ 'body': visitor.astext(),
+ 'suffix': self.out_suffix,
+ 'short_title': self.config.html_short_title,
+ 'master_doc': self.config.master_doc,
+ 'domain_indices': self.domain_indices,
+ }
+ f.write(self.render('project.hhc', context))
+ def build_hhx(self, outdir, outname):
+ # type: (str, str) -> None
logger.info(__('writing index file...'))
index = IndexEntries(self.env).create_index(self)
filename = path.join(outdir, outname + '.hhk')
diff --git a/sphinx/templates/htmlhelp/project.hhc b/sphinx/templates/htmlhelp/project.hhc
new file mode 100644
index 000000000..c1096e711
--- /dev/null
+++ b/sphinx/templates/htmlhelp/project.hhc
@@ -0,0 +1,31 @@
+{%- macro sitemap(name, docname) -%}
+<OBJECT type="text/sitemap">
+ <PARAM name="Name" value="{{ name|e }}" />
+ <PARAM name="Local" value="{{ docname|e }}{{ suffix }}" />
+</OBJECT>
+{%- endmacro -%}
+
+<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN">
+<HTML>
+ <HEAD>
+ <META name="GENERATOR" content="Microsoft&reg; HTML Help Workshop 4.1" />
+ <!-- Sitemap 1.0 -->
+ </HEAD>
+ <BODY>
+ <OBJECT type="text/site properties">
+ <PARAM name="Window Styles" value="0x801227" />
+ <PARAM name="ImageType" value="Folder" />
+ </OBJECT>
+ <UL>
+ <LI>
+ {{ sitemap(short_title, master_doc)|indent(8) }}
+ </LI>
+ {%- for indexname, indexcls, content, collapse in domain_indices %}
+ <LI>
+ {{ sitemap(indexcls.localname, indexname)|indent(8) }}
+ </LI>
+ {%- endfor %}
+ {{ body|indent(6) }}
+ </UL>
+ </BODY>
+</HTML>
diff --git a/tests/roots/test-htmlhelp-hhc/bar.rst b/tests/roots/test-htmlhelp-hhc/bar.rst
new file mode 100644
index 000000000..0d1785bcf
--- /dev/null
+++ b/tests/roots/test-htmlhelp-hhc/bar.rst
@@ -0,0 +1,2 @@
+bar
+---
diff --git a/tests/roots/test-htmlhelp-hhc/baz.rst b/tests/roots/test-htmlhelp-hhc/baz.rst
new file mode 100644
index 000000000..69fe26493
--- /dev/null
+++ b/tests/roots/test-htmlhelp-hhc/baz.rst
@@ -0,0 +1,2 @@
+baz
+---
diff --git a/tests/roots/test-htmlhelp-hhc/conf.py b/tests/roots/test-htmlhelp-hhc/conf.py
new file mode 100644
index 000000000..20447e040
--- /dev/null
+++ b/tests/roots/test-htmlhelp-hhc/conf.py
@@ -0,0 +1 @@
+html_short_title = "Sphinx's documentation"
diff --git a/tests/roots/test-htmlhelp-hhc/foo.rst b/tests/roots/test-htmlhelp-hhc/foo.rst
new file mode 100644
index 000000000..6e06b7e69
--- /dev/null
+++ b/tests/roots/test-htmlhelp-hhc/foo.rst
@@ -0,0 +1,6 @@
+foo
+---
+
+.. toctree::
+
+ bar
diff --git a/tests/roots/test-htmlhelp-hhc/index.rst b/tests/roots/test-htmlhelp-hhc/index.rst
new file mode 100644
index 000000000..9b43b4129
--- /dev/null
+++ b/tests/roots/test-htmlhelp-hhc/index.rst
@@ -0,0 +1,15 @@
+test-htmlhelp-domain_indices
+----------------------------
+
+section
+~~~~~~~
+
+.. py:module:: sphinx
+
+subsection
+^^^^^^^^^^
+
+.. toctree::
+
+ foo
+ baz
diff --git a/tests/test_build_htmlhelp.py b/tests/test_build_htmlhelp.py
index 18acca921..4ad244a4e 100644
--- a/tests/test_build_htmlhelp.py
+++ b/tests/test_build_htmlhelp.py
@@ -11,10 +11,9 @@
import re
import pytest
+from html5lib import HTMLParser
-from sphinx.builders.htmlhelp import chm_htmlescape
-
-from sphinx.builders.htmlhelp import default_htmlhelp_basename
+from sphinx.builders.htmlhelp import chm_htmlescape, default_htmlhelp_basename
from sphinx.config import Config
@@ -72,6 +71,52 @@ def test_chm(app):
assert m is None, 'Hex escaping exists in .hhk file: ' + str(m.group(0))
+@pytest.mark.sphinx('htmlhelp', testroot='htmlhelp-hhc')
+def test_htmlhelp_hhc(app):
+ app.build()
+
+ def assert_sitemap(node, name, filename):
+ assert node.tag == 'object'
+ assert len(node) == 2
+ assert node[0].tag == 'param'
+ assert node[0].attrib == {'name': 'Name', 'value': name}
+ assert node[1].tag == 'param'
+ assert node[1].attrib == {'name': 'Local', 'value': filename}
+
+ # .hhc file
+ hhc = (app.outdir / 'pythondoc.hhc').text()
+ tree = HTMLParser(namespaceHTMLElements=False).parse(hhc)
+ items = tree.find('.//body/ul')
+ assert len(items) == 4
+
+ # index
+ assert items[0].tag == 'li'
+ assert len(items[0]) == 1
+ assert_sitemap(items[0][0], "Sphinx's documentation", 'index.html')
+
+ # py-modindex
+ assert items[1].tag == 'li'
+ assert len(items[1]) == 1
+ assert_sitemap(items[1][0], 'Python Module Index', 'py-modindex.html')
+
+ # toctree
+ assert items[2].tag == 'li'
+ assert len(items[2]) == 2
+ assert_sitemap(items[2][0], 'foo', 'foo.html')
+
+ assert items[2][1].tag == 'ul'
+ assert len(items[2][1]) == 1
+ assert items[2][1][0].tag == 'li'
+ assert_sitemap(items[2][1][0][0], 'bar', 'bar.html')
+
+ assert items[3].tag == 'li'
+ assert len(items[3]) == 1
+ assert_sitemap(items[3][0], 'baz', 'baz.html')
+
+ # single quotes should be escaped as decimal (&#39;)
+ assert "Sphinx&#39;s documentation" in hhc
+
+
def test_chm_htmlescape():
assert chm_htmlescape('Hello world') == 'Hello world'
assert chm_htmlescape(u'Unicode 文字') == u'Unicode 文字'