PageRenderTime 46ms CodeModel.GetById 16ms RepoModel.GetById 0ms app.codeStats 1ms

/sphinx/builders/qthelp.py

https://bitbucket.org/birkenfeld/sphinx/
Python | 297 lines | 254 code | 16 blank | 27 comment | 15 complexity | 0f5c0fc90a5e6583b5f37f4ccf73d5ae MD5 | raw file
Possible License(s): BSD-2-Clause
  1. # -*- coding: utf-8 -*-
  2. """
  3. sphinx.builders.qthelp
  4. ~~~~~~~~~~~~~~~~~~~~~~
  5. Build input files for the Qt collection generator.
  6. :copyright: Copyright 2007-2014 by the Sphinx team, see AUTHORS.
  7. :license: BSD, see LICENSE for details.
  8. """
  9. import os
  10. import re
  11. import codecs
  12. import posixpath
  13. from os import path
  14. from six import text_type
  15. from docutils import nodes
  16. from sphinx import addnodes
  17. from sphinx.builders.html import StandaloneHTMLBuilder
  18. from sphinx.util import force_decode
  19. from sphinx.util.pycompat import htmlescape
  20. _idpattern = re.compile(
  21. r'(?P<title>.+) (\((class in )?(?P<id>[\w\.]+)( (?P<descr>\w+))?\))$')
  22. # Qt Help Collection Project (.qhcp).
  23. # Is the input file for the help collection generator.
  24. # It contains references to compressed help files which should be
  25. # included in the collection.
  26. # It may contain various other information for customizing Qt Assistant.
  27. collection_template = u'''\
  28. <?xml version="1.0" encoding="utf-8" ?>
  29. <QHelpCollectionProject version="1.0">
  30. <assistant>
  31. <title>%(title)s</title>
  32. <homePage>%(homepage)s</homePage>
  33. <startPage>%(startpage)s</startPage>
  34. </assistant>
  35. <docFiles>
  36. <generate>
  37. <file>
  38. <input>%(outname)s.qhp</input>
  39. <output>%(outname)s.qch</output>
  40. </file>
  41. </generate>
  42. <register>
  43. <file>%(outname)s.qch</file>
  44. </register>
  45. </docFiles>
  46. </QHelpCollectionProject>
  47. '''
  48. # Qt Help Project (.qhp)
  49. # This is the input file for the help generator.
  50. # It contains the table of contents, indices and references to the
  51. # actual documentation files (*.html).
  52. # In addition it defines a unique namespace for the documentation.
  53. project_template = u'''\
  54. <?xml version="1.0" encoding="utf-8" ?>
  55. <QtHelpProject version="1.0">
  56. <namespace>%(namespace)s</namespace>
  57. <virtualFolder>doc</virtualFolder>
  58. <customFilter name="%(project)s %(version)s">
  59. <filterAttribute>%(outname)s</filterAttribute>
  60. <filterAttribute>%(version)s</filterAttribute>
  61. </customFilter>
  62. <filterSection>
  63. <filterAttribute>%(outname)s</filterAttribute>
  64. <filterAttribute>%(version)s</filterAttribute>
  65. <toc>
  66. <section title="%(title)s" ref="%(masterdoc)s.html">
  67. %(sections)s
  68. </section>
  69. </toc>
  70. <keywords>
  71. %(keywords)s
  72. </keywords>
  73. <files>
  74. %(files)s
  75. </files>
  76. </filterSection>
  77. </QtHelpProject>
  78. '''
  79. section_template = '<section title="%(title)s" ref="%(ref)s"/>'
  80. file_template = ' '*12 + '<file>%(filename)s</file>'
  81. class QtHelpBuilder(StandaloneHTMLBuilder):
  82. """
  83. Builder that also outputs Qt help project, contents and index files.
  84. """
  85. name = 'qthelp'
  86. # don't copy the reST source
  87. copysource = False
  88. supported_image_types = ['image/svg+xml', 'image/png', 'image/gif',
  89. 'image/jpeg']
  90. # don't add links
  91. add_permalinks = False
  92. # don't add sidebar etc.
  93. embedded = True
  94. def init(self):
  95. StandaloneHTMLBuilder.init(self)
  96. # the output files for HTML help must be .html only
  97. self.out_suffix = '.html'
  98. #self.config.html_style = 'traditional.css'
  99. def handle_finish(self):
  100. self.build_qhp(self.outdir, self.config.qthelp_basename)
  101. def build_qhp(self, outdir, outname):
  102. self.info('writing project file...')
  103. # sections
  104. tocdoc = self.env.get_and_resolve_doctree(self.config.master_doc, self,
  105. prune_toctrees=False)
  106. istoctree = lambda node: (
  107. isinstance(node, addnodes.compact_paragraph)
  108. and 'toctree' in node)
  109. sections = []
  110. for node in tocdoc.traverse(istoctree):
  111. sections.extend(self.write_toc(node))
  112. for indexname, indexcls, content, collapse in self.domain_indices:
  113. item = section_template % {'title': indexcls.localname,
  114. 'ref': '%s.html' % indexname}
  115. sections.append(' ' * 4 * 4 + item)
  116. # sections may be unicode strings or byte strings, we have to make sure
  117. # they are all unicode strings before joining them
  118. new_sections = []
  119. for section in sections:
  120. if not isinstance(section, text_type):
  121. new_sections.append(force_decode(section, None))
  122. else:
  123. new_sections.append(section)
  124. sections = u'\n'.join(new_sections)
  125. # keywords
  126. keywords = []
  127. index = self.env.create_index(self, group_entries=False)
  128. for (key, group) in index:
  129. for title, (refs, subitems) in group:
  130. keywords.extend(self.build_keywords(title, refs, subitems))
  131. keywords = u'\n'.join(keywords)
  132. # files
  133. if not outdir.endswith(os.sep):
  134. outdir += os.sep
  135. olen = len(outdir)
  136. projectfiles = []
  137. staticdir = path.join(outdir, '_static')
  138. imagesdir = path.join(outdir, self.imagedir)
  139. for root, dirs, files in os.walk(outdir):
  140. resourcedir = root.startswith(staticdir) or \
  141. root.startswith(imagesdir)
  142. for fn in files:
  143. if (resourcedir and not fn.endswith('.js')) or \
  144. fn.endswith('.html'):
  145. filename = path.join(root, fn)[olen:]
  146. projectfiles.append(file_template %
  147. {'filename': htmlescape(filename)})
  148. projectfiles = '\n'.join(projectfiles)
  149. # it seems that the "namespace" may not contain non-alphanumeric
  150. # characters, and more than one successive dot, or leading/trailing
  151. # dots, are also forbidden
  152. nspace = 'org.sphinx.%s.%s' % (outname, self.config.version)
  153. nspace = re.sub('[^a-zA-Z0-9.]', '', nspace)
  154. nspace = re.sub(r'\.+', '.', nspace).strip('.')
  155. nspace = nspace.lower()
  156. # write the project file
  157. f = codecs.open(path.join(outdir, outname+'.qhp'), 'w', 'utf-8')
  158. try:
  159. f.write(project_template % {
  160. 'outname': htmlescape(outname),
  161. 'title': htmlescape(self.config.html_title),
  162. 'version': htmlescape(self.config.version),
  163. 'project': htmlescape(self.config.project),
  164. 'namespace': htmlescape(nspace),
  165. 'masterdoc': htmlescape(self.config.master_doc),
  166. 'sections': sections,
  167. 'keywords': keywords,
  168. 'files': projectfiles})
  169. finally:
  170. f.close()
  171. homepage = 'qthelp://' + posixpath.join(
  172. nspace, 'doc', self.get_target_uri(self.config.master_doc))
  173. startpage = 'qthelp://' + posixpath.join(nspace, 'doc', 'index.html')
  174. self.info('writing collection project file...')
  175. f = codecs.open(path.join(outdir, outname+'.qhcp'), 'w', 'utf-8')
  176. try:
  177. f.write(collection_template % {
  178. 'outname': htmlescape(outname),
  179. 'title': htmlescape(self.config.html_short_title),
  180. 'homepage': htmlescape(homepage),
  181. 'startpage': htmlescape(startpage)})
  182. finally:
  183. f.close()
  184. def isdocnode(self, node):
  185. if not isinstance(node, nodes.list_item):
  186. return False
  187. if len(node.children) != 2:
  188. return False
  189. if not isinstance(node.children[0], addnodes.compact_paragraph):
  190. return False
  191. if not isinstance(node.children[0][0], nodes.reference):
  192. return False
  193. if not isinstance(node.children[1], nodes.bullet_list):
  194. return False
  195. return True
  196. def write_toc(self, node, indentlevel=4):
  197. # XXX this should return a Unicode string, not a bytestring
  198. parts = []
  199. if self.isdocnode(node):
  200. refnode = node.children[0][0]
  201. link = refnode['refuri']
  202. title = htmlescape(refnode.astext()).replace('"', '&quot;')
  203. item = '<section title="%(title)s" ref="%(ref)s">' % \
  204. {'title': title, 'ref': link}
  205. parts.append(' '*4*indentlevel + item)
  206. for subnode in node.children[1]:
  207. parts.extend(self.write_toc(subnode, indentlevel+1))
  208. parts.append(' '*4*indentlevel + '</section>')
  209. elif isinstance(node, nodes.list_item):
  210. for subnode in node:
  211. parts.extend(self.write_toc(subnode, indentlevel))
  212. elif isinstance(node, nodes.reference):
  213. link = node['refuri']
  214. title = htmlescape(node.astext()).replace('"','&quot;')
  215. item = section_template % {'title': title, 'ref': link}
  216. item = u' ' * 4 * indentlevel + item
  217. parts.append(item.encode('ascii', 'xmlcharrefreplace'))
  218. elif isinstance(node, nodes.bullet_list):
  219. for subnode in node:
  220. parts.extend(self.write_toc(subnode, indentlevel))
  221. elif isinstance(node, addnodes.compact_paragraph):
  222. for subnode in node:
  223. parts.extend(self.write_toc(subnode, indentlevel))
  224. return parts
  225. def keyword_item(self, name, ref):
  226. matchobj = _idpattern.match(name)
  227. if matchobj:
  228. groupdict = matchobj.groupdict()
  229. shortname = groupdict['title']
  230. id = groupdict.get('id')
  231. #descr = groupdict.get('descr')
  232. if shortname.endswith('()'):
  233. shortname = shortname[:-2]
  234. id = '%s.%s' % (id, shortname)
  235. else:
  236. id = None
  237. if id:
  238. item = ' '*12 + '<keyword name="%s" id="%s" ref="%s"/>' % (
  239. name, id, ref[1])
  240. else:
  241. item = ' '*12 + '<keyword name="%s" ref="%s"/>' % (name, ref[1])
  242. item.encode('ascii', 'xmlcharrefreplace')
  243. return item
  244. def build_keywords(self, title, refs, subitems):
  245. keywords = []
  246. title = htmlescape(title)
  247. # if len(refs) == 0: # XXX
  248. # write_param('See Also', title)
  249. if len(refs) == 1:
  250. keywords.append(self.keyword_item(title, refs[0]))
  251. elif len(refs) > 1:
  252. for i, ref in enumerate(refs): # XXX
  253. # item = (' '*12 +
  254. # '<keyword name="%s [%d]" ref="%s"/>' % (
  255. # title, i, ref))
  256. # item.encode('ascii', 'xmlcharrefreplace')
  257. # keywords.append(item)
  258. keywords.append(self.keyword_item(title, ref))
  259. if subitems:
  260. for subitem in subitems:
  261. keywords.extend(self.build_keywords(subitem[0], subitem[1], []))
  262. return keywords