# -*- coding: utf-8 -*- """ sphinx.writers.html ~~~~~~~~~~~~~~~~~~~ docutils writers handling Sphinx' custom nodes. :copyright: Copyright 2007-2010 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ import sys import posixpath import os from docutils import nodes from docutils.writers.html4css1 import Writer, HTMLTranslator as BaseTranslator from sphinx.locale import admonitionlabels, versionlabels, _ from sphinx.util.smartypants import sphinx_smarty_pants try: import Image # check for the Python Imaging Library except ImportError: Image = None class HTMLWriter(Writer): def __init__(self, builder): Writer.__init__(self) self.builder = builder def translate(self): # sadly, this is mostly copied from parent class self.visitor = visitor = self.builder.translator_class(self.builder, self.document) self.document.walkabout(visitor) self.output = visitor.astext() for attr in ('head_prefix', 'stylesheet', 'head', 'body_prefix', 'body_pre_docinfo', 'docinfo', 'body', 'fragment', 'body_suffix', 'meta', 'title', 'subtitle', 'header', 'footer', 'html_prolog', 'html_head', 'html_title', 'html_subtitle', 'html_body', ): setattr(self, attr, getattr(visitor, attr, None)) self.clean_meta = ''.join(visitor.meta[2:]) class HTMLTranslator(BaseTranslator): """ Our custom HTML translator. """ def __init__(self, builder, *args, **kwds): BaseTranslator.__init__(self, *args, **kwds) self.highlighter = builder.highlighter self.no_smarty = 0 self.builder = builder self.highlightlang = builder.config.highlight_language self.highlightlinenothreshold = sys.maxint self.protect_literal_text = 0 self.add_permalinks = builder.config.html_add_permalinks self.secnumber_suffix = builder.config.html_secnumber_suffix def visit_start_of_file(self, node): # only occurs in the single-file builder self.body.append('' % node['docname']) def depart_start_of_file(self, node): pass def visit_desc(self, node): self.body.append(self.starttag(node, 'dl', CLASS=node['objtype'])) def depart_desc(self, node): self.body.append('\n\n') def visit_desc_signature(self, node): # the id is set automatically self.body.append(self.starttag(node, 'dt')) # anchor for per-desc interactive data if node.parent['objtype'] != 'describe' \ and node['ids'] and node['first']: self.body.append('' % node['ids'][0]) def depart_desc_signature(self, node): if node['ids'] and self.add_permalinks and self.builder.add_permalinks: self.body.append(u'\u00B6' % _('Permalink to this definition')) self.body.append('\n') def visit_desc_addname(self, node): self.body.append(self.starttag(node, 'tt', '', CLASS='descclassname')) def depart_desc_addname(self, node): self.body.append('') def visit_desc_type(self, node): pass def depart_desc_type(self, node): pass def visit_desc_returns(self, node): self.body.append(' → ') def depart_desc_returns(self, node): pass def visit_desc_name(self, node): self.body.append(self.starttag(node, 'tt', '', CLASS='descname')) def depart_desc_name(self, node): self.body.append('') def visit_desc_parameterlist(self, node): self.body.append('(') self.first_param = 1 def depart_desc_parameterlist(self, node): self.body.append(')') def visit_desc_parameter(self, node): if not self.first_param: self.body.append(', ') else: self.first_param = 0 if not node.hasattr('noemph'): self.body.append('') def depart_desc_parameter(self, node): if not node.hasattr('noemph'): self.body.append('') def visit_desc_optional(self, node): self.body.append('[') def depart_desc_optional(self, node): self.body.append(']') def visit_desc_annotation(self, node): self.body.append(self.starttag(node, 'em', '', CLASS='property')) def depart_desc_annotation(self, node): self.body.append('') def visit_desc_content(self, node): self.body.append(self.starttag(node, 'dd', '')) def depart_desc_content(self, node): self.body.append('') def visit_refcount(self, node): self.body.append(self.starttag(node, 'em', '', CLASS='refcount')) def depart_refcount(self, node): self.body.append('') def visit_versionmodified(self, node): self.body.append(self.starttag(node, 'p', CLASS=node['type'])) text = versionlabels[node['type']] % node['version'] if len(node): text += ': ' else: text += '.' self.body.append('%s' % text) def depart_versionmodified(self, node): self.body.append('
\n') # overwritten def visit_reference(self, node): atts = {'class': 'reference'} if node.get('internal'): atts['class'] += ' internal' else: atts['class'] += ' external' if 'refuri' in node: atts['href'] = node['refuri'] if self.settings.cloak_email_addresses and \ atts['href'].startswith('mailto:'): atts['href'] = self.cloak_mailto(atts['href']) self.in_mailto = 1 else: assert 'refid' in node, \ 'References must have "refuri" or "refid" attribute.' atts['href'] = '#' + node['refid'] if not isinstance(node.parent, nodes.TextElement): assert len(node) == 1 and isinstance(node[0], nodes.image) atts['class'] += ' image-reference' if 'reftitle' in node: atts['title'] = node['reftitle'] self.body.append(self.starttag(node, 'a', '', **atts)) if node.hasattr('secnumber'): self.body.append(('%s' + self.secnumber_suffix) % '.'.join(map(str, node['secnumber']))) # overwritten -- we don't want source comments to show up in the HTML def visit_comment(self, node): raise nodes.SkipNode # overwritten def visit_admonition(self, node, name=''): self.body.append(self.starttag( node, 'div', CLASS=('admonition ' + name))) if name and name != 'seealso': node.insert(0, nodes.title(name, admonitionlabels[name])) self.set_first_last(node) def visit_seealso(self, node): self.visit_admonition(node, 'seealso') def depart_seealso(self, node): self.depart_admonition(node) def add_secnumber(self, node): if node.hasattr('secnumber'): self.body.append('.'.join(map(str, node['secnumber'])) + self.secnumber_suffix) elif isinstance(node.parent, nodes.section): anchorname = '#' + node.parent['ids'][0] if anchorname not in self.builder.secnumbers: anchorname = '' # try first heading which has no anchor if anchorname in self.builder.secnumbers: numbers = self.builder.secnumbers[anchorname] self.body.append('.'.join(map(str, numbers)) + self.secnumber_suffix) # overwritten def visit_title(self, node): BaseTranslator.visit_title(self, node) self.add_secnumber(node) # overwritten def visit_literal_block(self, node): if node.rawsource != node.astext(): # most probably a parsed-literal block -- don't highlight return BaseTranslator.visit_literal_block(self, node) lang = self.highlightlang linenos = node.rawsource.count('\n') >= \ self.highlightlinenothreshold - 1 if node.has_key('language'): # code-block directives lang = node['language'] if node.has_key('linenos'): linenos = node['linenos'] highlighted = self.highlighter.highlight_block(node.rawsource, lang, linenos) starttag = self.starttag(node, 'div', suffix='', CLASS='highlight-%s' % lang) self.body.append(starttag + highlighted + '\n') raise nodes.SkipNode def visit_doctest_block(self, node): self.visit_literal_block(node) # overwritten def visit_literal(self, node): if len(node.children) == 1 and \ node.children[0] in ('None', 'True', 'False'): node['classes'].append('xref') self.body.append(self.starttag(node, 'tt', '', CLASS='docutils literal')) self.protect_literal_text += 1 def depart_literal(self, node): self.protect_literal_text -= 1 self.body.append('') def visit_productionlist(self, node): self.body.append(self.starttag(node, 'pre')) names = [] for production in node: names.append(production['tokenname']) maxlen = max(len(name) for name in names) for production in node: if production['tokenname']: lastname = production['tokenname'].ljust(maxlen) self.body.append(self.starttag(production, 'strong', '')) self.body.append(lastname + ' ::= ') else: self.body.append('%s ' % (' '*len(lastname))) production.walkabout(self) self.body.append('\n') self.body.append('\n') raise nodes.SkipNode def depart_productionlist(self, node): pass def visit_production(self, node): pass def depart_production(self, node): pass def visit_centered(self, node): self.body.append(self.starttag(node, 'p', CLASS="centered") + '') def depart_centered(self, node): self.body.append('') def visit_compact_paragraph(self, node): pass def depart_compact_paragraph(self, node): pass def visit_highlightlang(self, node): self.highlightlang = node['lang'] self.highlightlinenothreshold = node['linenothreshold'] def depart_highlightlang(self, node): pass def visit_download_reference(self, node): if node.hasattr('filename'): self.body.append( '' % posixpath.join(self.builder.dlpath, node['filename'])) self.context.append('') else: self.context.append('') def depart_download_reference(self, node): self.body.append(self.context.pop()) # overwritten def visit_image(self, node): olduri = node['uri'] # rewrite the URI if the environment knows about it if olduri in self.builder.images: node['uri'] = posixpath.join(self.builder.imgpath, self.builder.images[olduri]) if node['uri'].lower().endswith('svg') or \ node['uri'].lower().endswith('svgz'): atts = {'data': node['uri'], 'type': 'image/svg+xml'} if node.has_key('width'): atts['width'] = node['width'] if node.has_key('height'): atts['height'] = node['height'] if node.has_key('align'): self.body.append('