# -*- coding: utf-8 -*- """ sphinx.writers.html ~~~~~~~~~~~~~~~~~~~ docutils writers handling Sphinx' custom nodes. :copyright: Copyright 2007-2011 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 import addnodes from sphinx.locale import admonitionlabels, versionlabels, _ from sphinx.util.smartypants import sphinx_smarty_pants try: from PIL 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.permalink_text = builder.config.html_add_permalinks # support backwards-compatible setting to a bool if not isinstance(self.permalink_text, basestring): self.permalink_text = self.permalink_text and u'\u00B6' or '' self.permalink_text = self.encode(self.permalink_text) self.secnumber_suffix = builder.config.html_secnumber_suffix self.param_separator = '' self._table_row_index = 0 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.permalink_text and self.builder.add_permalinks: self.body.append(u'%s' % ( _('Permalink to this definition'), self.permalink_text)) 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 self.param_separator = node.child_text_separator def depart_desc_parameterlist(self, node): self.body.append(')') def visit_desc_parameter(self, node): if not self.first_param: self.body.append(self.param_separator) 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') or 'refuri' not in node: 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.get('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.get('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 self.builder.secnumbers.get(anchorname): 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 highlight_args = node.get('highlight_args', {}) if node.has_key('language'): # code-block directives lang = node['language'] highlight_args['force'] = True if node.has_key('linenos'): linenos = node['linenos'] def warner(msg): self.builder.warn(msg, (self.builder.current_docname, node.line)) highlighted = self.highlighter.highlight_block( node.rawsource, lang, warn=warner, linenos=linenos, **highlight_args) 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 to add thetags around paragraph can be omitted.""" if isinstance(node.parent, addnodes.desc_content): # Never compact desc_content items. return False return BaseTranslator.should_be_compact_paragraph(self, node) 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 = {'src': node['uri']} if node.has_key('width'): atts['width'] = node['width'] if node.has_key('height'): atts['height'] = node['height'] if node.has_key('alt'): atts['alt'] = node['alt'] if node.has_key('align'): self.body.append('