# -*- coding: utf-8 -*- """ sphinx.builders.qthelp ~~~~~~~~~~~~~~~~~~~~~~ Build input files for the Qt collection generator. :copyright: Copyright 2007-2011 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ import os import re import codecs import posixpath from os import path from docutils import nodes from sphinx import addnodes from sphinx.builders.html import StandaloneHTMLBuilder from sphinx.util import force_decode from sphinx.util.pycompat import htmlescape _idpattern = re.compile( r'(?P.+) (\((class in )?(?P<id>[\w\.]+)( (?P<descr>\w+))?\))$') # Qt Help Collection Project (.qhcp). # Is the input file for the help collection generator. # It contains references to compressed help files which should be # included in the collection. # It may contain various other information for customizing Qt Assistant. collection_template = u'''\ <?xml version="1.0" encoding="utf-8" ?> <QHelpCollectionProject version="1.0"> <assistant> <title>%(title)s %(homepage)s %(startpage)s %(outname)s.qhp %(outname)s.qch %(outname)s.qch ''' # Qt Help Project (.qhp) # This is the input file for the help generator. # It contains the table of contents, indices and references to the # actual documentation files (*.html). # In addition it defines a unique namespace for the documentation. project_template = u'''\ %(namespace)s doc %(outname)s %(version)s %(outname)s %(version)s
%(sections)s
%(keywords)s %(files)s
''' section_template = '
' file_template = ' '*12 + '%(filename)s' class QtHelpBuilder(StandaloneHTMLBuilder): """ Builder that also outputs Qt help project, contents and index files. """ name = 'qthelp' # don't copy the reST source copysource = False supported_image_types = ['image/svg+xml', 'image/png', 'image/gif', 'image/jpeg'] # don't add links add_permalinks = False # don't add sidebar etc. embedded = True def init(self): StandaloneHTMLBuilder.init(self) # the output files for HTML help must be .html only self.out_suffix = '.html' #self.config.html_style = 'traditional.css' def handle_finish(self): self.build_qhp(self.outdir, self.config.qthelp_basename) def build_qhp(self, outdir, outname): self.info('writing project file...') # sections tocdoc = self.env.get_and_resolve_doctree(self.config.master_doc, self, prune_toctrees=False) istoctree = lambda node: ( isinstance(node, addnodes.compact_paragraph) and node.has_key('toctree')) sections = [] for node in tocdoc.traverse(istoctree): sections.extend(self.write_toc(node)) for indexname, indexcls, content, collapse in self.domain_indices: item = section_template % {'title': indexcls.localname, 'ref': '%s.html' % indexname} sections.append(' ' * 4 * 4 + item) # sections may be unicode strings or byte strings, we have to make sure # they are all unicode strings before joining them new_sections = [] for section in sections: if not isinstance(section, unicode): new_sections.append(force_decode(section, None)) else: new_sections.append(section) sections = u'\n'.join(new_sections) # keywords keywords = [] index = self.env.create_index(self, group_entries=False) for (key, group) in index: for title, (refs, subitems) in group: keywords.extend(self.build_keywords(title, refs, subitems)) keywords = u'\n'.join(keywords) # files if not outdir.endswith(os.sep): outdir += os.sep olen = len(outdir) projectfiles = [] staticdir = path.join(outdir, '_static') imagesdir = path.join(outdir, '_images') for root, dirs, files in os.walk(outdir): resourcedir = root.startswith(staticdir) or \ root.startswith(imagesdir) for fn in files: if (resourcedir and not fn.endswith('.js')) or \ fn.endswith('.html'): filename = path.join(root, fn)[olen:] projectfiles.append(file_template % {'filename': htmlescape(filename)}) projectfiles = '\n'.join(projectfiles) # it seems that the "namespace" may not contain non-alphanumeric # characters, and more than one successive dot, or leading/trailing # dots, are also forbidden nspace = 'org.sphinx.%s.%s' % (outname, self.config.version) nspace = re.sub('[^a-zA-Z0-9.]', '', nspace) nspace = re.sub(r'\.+', '.', nspace).strip('.') nspace = nspace.lower() # write the project file f = codecs.open(path.join(outdir, outname+'.qhp'), 'w', 'utf-8') try: f.write(project_template % { 'outname': htmlescape(outname), 'title': htmlescape(self.config.html_title), 'version': htmlescape(self.config.version), 'project': htmlescape(self.config.project), 'namespace': htmlescape(nspace), 'masterdoc': htmlescape(self.config.master_doc), 'sections': sections, 'keywords': keywords, 'files': projectfiles}) finally: f.close() homepage = 'qthelp://' + posixpath.join( nspace, 'doc', self.get_target_uri(self.config.master_doc)) startpage = 'qthelp://' + posixpath.join(nspace, 'doc', 'index.html') self.info('writing collection project file...') f = codecs.open(path.join(outdir, outname+'.qhcp'), 'w', 'utf-8') try: f.write(collection_template % { 'outname': htmlescape(outname), 'title': htmlescape(self.config.html_short_title), 'homepage': htmlescape(homepage), 'startpage': htmlescape(startpage)}) finally: f.close() def isdocnode(self, node): if not isinstance(node, nodes.list_item): return False if len(node.children) != 2: return False if not isinstance(node.children[0], addnodes.compact_paragraph): return False if not isinstance(node.children[0][0], nodes.reference): return False if not isinstance(node.children[1], nodes.bullet_list): return False return True def write_toc(self, node, indentlevel=4): # XXX this should return a Unicode string, not a bytestring parts = [] if self.isdocnode(node): refnode = node.children[0][0] link = refnode['refuri'] title = htmlescape(refnode.astext()).replace('"', '"') item = '
' % \ {'title': title, 'ref': link} parts.append(' '*4*indentlevel + item) for subnode in node.children[1]: parts.extend(self.write_toc(subnode, indentlevel+1)) parts.append(' '*4*indentlevel + '
') elif isinstance(node, nodes.list_item): for subnode in node: parts.extend(self.write_toc(subnode, indentlevel)) elif isinstance(node, nodes.reference): link = node['refuri'] title = htmlescape(node.astext()).replace('"','"') item = section_template % {'title': title, 'ref': link} item = u' ' * 4 * indentlevel + item parts.append(item.encode('ascii', 'xmlcharrefreplace')) elif isinstance(node, nodes.bullet_list): for subnode in node: parts.extend(self.write_toc(subnode, indentlevel)) elif isinstance(node, addnodes.compact_paragraph): for subnode in node: parts.extend(self.write_toc(subnode, indentlevel)) return parts def keyword_item(self, name, ref): matchobj = _idpattern.match(name) if matchobj: groupdict = matchobj.groupdict() shortname = groupdict['title'] id = groupdict.get('id') #descr = groupdict.get('descr') if shortname.endswith('()'): shortname = shortname[:-2] id = '%s.%s' % (id, shortname) else: id = None if id: item = ' '*12 + '' % ( name, id, ref[1]) else: item = ' '*12 + '' % (name, ref[1]) item.encode('ascii', 'xmlcharrefreplace') return item def build_keywords(self, title, refs, subitems): keywords = [] title = htmlescape(title) # if len(refs) == 0: # XXX # write_param('See Also', title) if len(refs) == 1: keywords.append(self.keyword_item(title, refs[0])) elif len(refs) > 1: for i, ref in enumerate(refs): # XXX # item = (' '*12 + # '' % ( # title, i, ref)) # item.encode('ascii', 'xmlcharrefreplace') # keywords.append(item) keywords.append(self.keyword_item(title, ref)) if subitems: for subitem in subitems: keywords.extend(self.build_keywords(subitem[0], subitem[1], [])) return keywords