mirror of
https://github.com/sphinx-doc/sphinx.git
synced 2025-02-25 18:55:22 -06:00
refactor: htmlhelp: Generate .hhc file from template
This commit is contained in:
parent
28b0b744b6
commit
7415f64eab
@ -75,24 +75,6 @@ template_dir = path.join(package_dir, 'templates', 'htmlhelp')
|
|||||||
# 0x200000 TOC Next
|
# 0x200000 TOC Next
|
||||||
# 0x400000 TOC Prev
|
# 0x400000 TOC Prev
|
||||||
|
|
||||||
contents_header = '''\
|
|
||||||
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN">
|
|
||||||
<HTML>
|
|
||||||
<HEAD>
|
|
||||||
<meta name="GENERATOR" content="Microsoft® 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_sitemap = '''\
|
||||||
<OBJECT type="text/sitemap">
|
<OBJECT type="text/sitemap">
|
||||||
<param name="Name" value="%s">
|
<param name="Name" value="%s">
|
||||||
@ -151,6 +133,63 @@ def chm_htmlescape(s, quote=True):
|
|||||||
return s
|
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):
|
class HTMLHelpBuilder(StandaloneHTMLBuilder):
|
||||||
"""
|
"""
|
||||||
Builder that also outputs Windows HTML help project, contents and
|
Builder that also outputs Windows HTML help project, contents and
|
||||||
@ -202,6 +241,7 @@ class HTMLHelpBuilder(StandaloneHTMLBuilder):
|
|||||||
# type: () -> None
|
# type: () -> None
|
||||||
self.copy_stopword_list()
|
self.copy_stopword_list()
|
||||||
self.build_project_file()
|
self.build_project_file()
|
||||||
|
self.build_toc_file()
|
||||||
self.build_hhx(self.outdir, self.config.htmlhelp_basename)
|
self.build_hhx(self.outdir, self.config.htmlhelp_basename)
|
||||||
|
|
||||||
def write_doc(self, docname, doctree):
|
def write_doc(self, docname, doctree):
|
||||||
@ -263,48 +303,30 @@ class HTMLHelpBuilder(StandaloneHTMLBuilder):
|
|||||||
body = self.render('project.hhp', context)
|
body = self.render('project.hhp', context)
|
||||||
f.write(body)
|
f.write(body)
|
||||||
|
|
||||||
|
@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:
|
||||||
|
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 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):
|
def build_hhx(self, outdir, outname):
|
||||||
# type: (str, str) -> None
|
# type: (str, str) -> None
|
||||||
logger.info(__('writing TOC file...'))
|
|
||||||
filename = path.join(outdir, outname + '.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)
|
|
||||||
|
|
||||||
matcher = NodeMatcher(addnodes.compact_paragraph, toctree=True)
|
|
||||||
for node in tocdoc.traverse(matcher): # type: addnodes.compact_paragraph
|
|
||||||
write_toc(node)
|
|
||||||
f.write(contents_footer)
|
|
||||||
|
|
||||||
logger.info(__('writing index file...'))
|
logger.info(__('writing index file...'))
|
||||||
index = IndexEntries(self.env).create_index(self)
|
index = IndexEntries(self.env).create_index(self)
|
||||||
filename = path.join(outdir, outname + '.hhk')
|
filename = path.join(outdir, outname + '.hhk')
|
||||||
|
31
sphinx/templates/htmlhelp/project.hhc
Normal file
31
sphinx/templates/htmlhelp/project.hhc
Normal file
@ -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® 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>
|
2
tests/roots/test-htmlhelp-hhc/bar.rst
Normal file
2
tests/roots/test-htmlhelp-hhc/bar.rst
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
bar
|
||||||
|
---
|
2
tests/roots/test-htmlhelp-hhc/baz.rst
Normal file
2
tests/roots/test-htmlhelp-hhc/baz.rst
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
baz
|
||||||
|
---
|
1
tests/roots/test-htmlhelp-hhc/conf.py
Normal file
1
tests/roots/test-htmlhelp-hhc/conf.py
Normal file
@ -0,0 +1 @@
|
|||||||
|
html_short_title = "Sphinx's documentation"
|
6
tests/roots/test-htmlhelp-hhc/foo.rst
Normal file
6
tests/roots/test-htmlhelp-hhc/foo.rst
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
foo
|
||||||
|
---
|
||||||
|
|
||||||
|
.. toctree::
|
||||||
|
|
||||||
|
bar
|
15
tests/roots/test-htmlhelp-hhc/index.rst
Normal file
15
tests/roots/test-htmlhelp-hhc/index.rst
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
test-htmlhelp-domain_indices
|
||||||
|
----------------------------
|
||||||
|
|
||||||
|
section
|
||||||
|
~~~~~~~
|
||||||
|
|
||||||
|
.. py:module:: sphinx
|
||||||
|
|
||||||
|
subsection
|
||||||
|
^^^^^^^^^^
|
||||||
|
|
||||||
|
.. toctree::
|
||||||
|
|
||||||
|
foo
|
||||||
|
baz
|
@ -11,10 +11,9 @@
|
|||||||
import re
|
import re
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
from html5lib import HTMLParser
|
||||||
|
|
||||||
from sphinx.builders.htmlhelp import chm_htmlescape
|
from sphinx.builders.htmlhelp import chm_htmlescape, default_htmlhelp_basename
|
||||||
|
|
||||||
from sphinx.builders.htmlhelp import default_htmlhelp_basename
|
|
||||||
from sphinx.config import Config
|
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))
|
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 (')
|
||||||
|
assert "Sphinx's documentation" in hhc
|
||||||
|
|
||||||
|
|
||||||
def test_chm_htmlescape():
|
def test_chm_htmlescape():
|
||||||
assert chm_htmlescape('Hello world') == 'Hello world'
|
assert chm_htmlescape('Hello world') == 'Hello world'
|
||||||
assert chm_htmlescape(u'Unicode 文字') == u'Unicode 文字'
|
assert chm_htmlescape(u'Unicode 文字') == u'Unicode 文字'
|
||||||
|
Loading…
Reference in New Issue
Block a user