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
|
||||
# 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 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)
|
||||
|
||||
@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):
|
||||
# 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...'))
|
||||
index = IndexEntries(self.env).create_index(self)
|
||||
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 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 (')
|
||||
assert "Sphinx's documentation" in hhc
|
||||
|
||||
|
||||
def test_chm_htmlescape():
|
||||
assert chm_htmlescape('Hello world') == 'Hello world'
|
||||
assert chm_htmlescape(u'Unicode 文字') == u'Unicode 文字'
|
||||
|
Loading…
Reference in New Issue
Block a user