sphinx/sphinx/latexwriter.py
2008-06-18 18:34:18 +00:00

1013 lines
35 KiB
Python

# -*- coding: utf-8 -*-
"""
sphinx.latexwriter
~~~~~~~~~~~~~~~~~~
Custom docutils writer for LaTeX.
Much of this code is adapted from Dave Kuhlman's "docpy" writer from his
docutils sandbox.
:copyright: 2007-2008 by Georg Brandl, Dave Kuhlman.
:license: BSD.
"""
import re
import sys
import time
from os import path
from docutils import nodes, writers
from sphinx import addnodes
from sphinx import highlighting
from sphinx.util.smartypants import educateQuotesLatex
# XXX: Move to a template?
HEADER = r'''%% Generated by Sphinx.
\documentclass[%(papersize)s,%(pointsize)s]{%(docclass)s}
\usepackage[utf8]{inputenc}
\usepackage[T1]{fontenc}
\title{%(title)s}
\date{%(date)s}
\release{%(release)s}
\author{%(author)s}
\newcommand{\sphinxlogo}{%(logo)s}
%(preamble)s
\makeindex
'''
FOOTER = r'''
\printindex
\end{document}
'''
GRAPHICX = r'''
%% Check if we are compiling under latex or pdflatex.
\ifx\pdftexversion\undefined
\usepackage{graphicx}
\else
\usepackage[pdftex]{graphicx}
\fi
'''
class LaTeXWriter(writers.Writer):
supported = ('sphinxlatex',)
settings_spec = ('No options here.', '', ())
settings_defaults = {}
output = None
def __init__(self, builder):
writers.Writer.__init__(self)
self.builder = builder
def translate(self):
visitor = LaTeXTranslator(self.document, self.builder)
self.document.walkabout(visitor)
self.output = visitor.astext()
# Helper classes
class Table(object):
def __init__(self):
self.col = 0
self.colcount = 0
self.colspec = None
self.had_head = False
self.has_verbatim = False
class Desc(object):
def __init__(self, node):
self.env = LaTeXTranslator.desc_map.get(node['desctype'], 'describe')
self.ni = node['noindex']
self.type = self.cls = self.name = self.params = ''
self.count = 0
class LaTeXTranslator(nodes.NodeVisitor):
sectionnames = ["part", "chapter", "section", "subsection",
"subsubsection", "paragraph", "subparagraph"]
def __init__(self, document, builder):
nodes.NodeVisitor.__init__(self, document)
self.builder = builder
self.body = []
docclass = document.settings.docclass
paper = builder.config.latex_paper_size + 'paper'
if paper == 'paper': # e.g. command line "-D latex_paper_size="
paper = 'letterpaper'
date = time.strftime(builder.config.today_fmt)
logo = (builder.config.latex_logo and
"\\includegraphics{%s}\\par" % path.basename(builder.config.latex_logo)
or '')
self.options = {'docclass': docclass,
'papersize': paper,
'pointsize': builder.config.latex_font_size,
'preamble': builder.config.latex_preamble,
'modindex': builder.config.latex_use_modindex,
'author': document.settings.author,
'docname': document.settings.docname,
# if empty, the title is set to the first section title
'title': document.settings.title,
'release': builder.config.release,
'logo': logo,
'date': date,
}
self.highlighter = highlighting.PygmentsBridge(
'latex', builder.config.pygments_style)
self.context = []
self.descstack = []
self.bibitems = []
self.table = None
self.next_table_colspec = None
self.highlightlang = 'python'
self.highlightlinenothreshold = sys.maxint
self.written_ids = set()
if docclass == 'manual':
if builder.config.latex_use_parts:
self.top_sectionlevel = 0
else:
self.top_sectionlevel = 1
else:
self.top_sectionlevel = 2
self.next_section_target = None
# flags
self.verbatim = None
self.in_title = 0
self.in_production_list = 0
self.first_document = 1
self.this_is_the_title = 1
self.literal_whitespace = 0
self.need_graphicx = 0
def astext(self):
return (HEADER % self.options) + \
(self.options['modindex'] and '\\makemodindex\n' or '') + \
self.highlighter.get_stylesheet() + \
(self.need_graphicx and GRAPHICX or '') + \
'\n\n' + \
u''.join(self.body) + \
(self.options['modindex'] and
'\\renewcommand{\\indexname}{Module Index}'
'\\printmodindex'
'\\renewcommand{\\indexname}{Index}\n' or '') + \
(FOOTER % self.options)
def visit_document(self, node):
if self.first_document == 1:
# the first document is all the regular content ...
self.body.append('\\begin{document}\n\\maketitle\n\\tableofcontents\n')
self.first_document = 0
elif self.first_document == 0:
# ... and all others are the appendices
self.body.append('\n\\appendix\n')
self.first_document = -1
# "- 1" because the level is increased before the title is visited
self.sectionlevel = self.top_sectionlevel - 1
def depart_document(self, node):
if self.bibitems:
widest_label = ""
for bi in self.bibitems:
if len(widest_label) < len(bi[0]):
widest_label = bi[0]
self.body.append('\n\\begin{thebibliography}{%s}\n' % widest_label)
for bi in self.bibitems:
# cite_key: underscores must not be escaped
cite_key = bi[0].replace(r"\_", "_")
self.body.append('\\bibitem[%s]{%s}{%s}\n' % (bi[0], cite_key, bi[1]))
self.body.append('\\end{thebibliography}\n')
self.bibitems = []
def visit_start_of_file(self, node):
# This marks the begin of a new file; therefore the current module and
# class must be reset
self.body.append('\n\\resetcurrentobjects\n')
raise nodes.SkipNode
def visit_highlightlang(self, node):
self.highlightlang = node['lang']
self.highlightlinenothreshold = node['linenothreshold']
raise nodes.SkipNode
def visit_comment(self, node):
raise nodes.SkipNode
def visit_section(self, node):
if not self.this_is_the_title:
self.sectionlevel += 1
self.body.append('\n\n')
if self.next_section_target:
self.body.append(r'\hypertarget{%s}{}' % self.next_section_target)
self.next_section_target = None
#if node.get('ids'):
# for id in node['ids']:
# if id not in self.written_ids:
# self.body.append(r'\hypertarget{%s}{}' % id)
# self.written_ids.add(id)
def depart_section(self, node):
self.sectionlevel = max(self.sectionlevel - 1, self.top_sectionlevel - 1)
def visit_problematic(self, node):
self.body.append(r'{\color{red}\bfseries{}')
def depart_problematic(self, node):
self.body.append('}')
def visit_topic(self, node):
self.body.append('\\setbox0\\vbox{\n'
'\\begin{minipage}{0.95\\textwidth}\n')
def depart_topic(self, node):
self.body.append('\\end{minipage}}\n'
'\\begin{center}\\setlength{\\fboxsep}{5pt}'
'\\shadowbox{\\box0}\\end{center}\n')
visit_sidebar = visit_topic
depart_sidebar = depart_topic
def visit_glossary(self, node):
pass
def depart_glossary(self, node):
pass
def visit_productionlist(self, node):
self.body.append('\n\n\\begin{productionlist}\n')
self.in_production_list = 1
def depart_productionlist(self, node):
self.body.append('\\end{productionlist}\n\n')
self.in_production_list = 0
def visit_production(self, node):
if node['tokenname']:
self.body.append('\\production{%s}{' % self.encode(node['tokenname']))
else:
self.body.append('\\productioncont{')
def depart_production(self, node):
self.body.append('}\n')
def visit_transition(self, node):
self.body.append('\n\n\\bigskip\\hrule{}\\bigskip\n\n')
def depart_transition(self, node):
pass
def visit_title(self, node):
if isinstance(node.parent, addnodes.seealso):
# the environment already handles this
raise nodes.SkipNode
elif self.this_is_the_title:
if len(node.children) != 1 and not isinstance(node.children[0], nodes.Text):
self.builder.warn('document title is not a single Text node')
if not self.options['title']:
self.options['title'] = node.astext()
self.this_is_the_title = 0
raise nodes.SkipNode
elif isinstance(node.parent, nodes.section):
self.body.append(r'\%s{' % self.sectionnames[self.sectionlevel])
self.context.append('}\n')
elif isinstance(node.parent, (nodes.topic, nodes.sidebar, nodes.admonition)):
self.body.append(r'\textbf{')
self.context.append('}\n\n\medskip\n\n')
else:
self.builder.warn('encountered title node not in section, topic, admonition'
' or sidebar')
self.body.append('\\textbf{')
self.context.append('}')
self.in_title = 1
def depart_title(self, node):
self.in_title = 0
self.body.append(self.context.pop())
desc_map = {
'function' : 'funcdesc',
'class': 'classdesc',
'method': 'methoddesc',
'exception': 'excdesc',
'data': 'datadesc',
'attribute': 'memberdesc',
'opcode': 'opcodedesc',
'cfunction': 'cfuncdesc',
'cmember': 'cmemberdesc',
'cmacro': 'csimplemacrodesc',
'ctype': 'ctypedesc',
'cvar': 'cvardesc',
'describe': 'describe',
# and all others are 'describe' too
}
def visit_desc(self, node):
self.descstack.append(Desc(node))
def depart_desc(self, node):
d = self.descstack.pop()
self.body.append("\\end{%s%s}\n" % (d.env, d.ni and 'ni' or ''))
def visit_desc_signature(self, node):
d = self.descstack[-1]
# reset these for every signature
d.type = d.cls = d.name = d.params = ''
def depart_desc_signature(self, node):
d = self.descstack[-1]
d.cls = d.cls.rstrip('.')
if node.parent['desctype'] != 'describe' and node['ids']:
hyper = '\\hypertarget{%s}{}' % node['ids'][0]
else:
hyper = ''
if d.count == 0:
t1 = "\n\n%s\\begin{%s%s}" % (hyper, d.env, (d.ni and 'ni' or ''))
else:
t1 = "\n%s\\%sline%s" % (hyper, d.env[:-4], (d.ni and 'ni' or ''))
d.count += 1
if d.env in ('funcdesc', 'classdesc', 'excclassdesc'):
t2 = "{%s}{%s}" % (d.name, d.params)
elif d.env in ('datadesc', 'classdesc*', 'excdesc', 'csimplemacrodesc'):
t2 = "{%s}" % (d.name)
elif d.env == 'methoddesc':
if d.cls:
t2 = "[%s]{%s}{%s}" % (d.cls, d.name, d.params)
else:
t2 = "{%s}{%s}" % (d.name, d.params)
elif d.env == 'memberdesc':
if d.cls:
t2 = "[%s]{%s}" % (d.cls, d.name)
else:
t2 = "{%s}" % d.name
elif d.env == 'cfuncdesc':
if d.cls:
# C++ class names
d.name = '%s::%s' % (d.cls, d.name)
t2 = "{%s}{%s}{%s}" % (d.type, d.name, d.params)
elif d.env == 'cmemberdesc':
try:
type, container = d.type.rsplit(' ', 1)
container = container.rstrip('.')
except ValueError:
container = ''
type = d.type
t2 = "{%s}{%s}{%s}" % (container, type, d.name)
elif d.env == 'cvardesc':
t2 = "{%s}{%s}" % (d.type, d.name)
elif d.env == 'ctypedesc':
t2 = "{%s}" % (d.name)
elif d.env == 'opcodedesc':
t2 = "{%s}{%s}" % (d.name, d.params)
elif d.env == 'describe':
t2 = "{%s}" % d.name
self.body.append(t1 + t2)
def visit_desc_type(self, node):
d = self.descstack[-1]
if d.env == 'describe':
d.name += self.encode(node.astext())
else:
self.descstack[-1].type = self.encode(node.astext().strip())
raise nodes.SkipNode
def visit_desc_name(self, node):
d = self.descstack[-1]
if d.env == 'describe':
d.name += self.encode(node.astext())
else:
self.descstack[-1].name = self.encode(node.astext().strip())
raise nodes.SkipNode
def visit_desc_addname(self, node):
d = self.descstack[-1]
if d.env == 'describe':
d.name += self.encode(node.astext())
else:
self.descstack[-1].cls = self.encode(node.astext().strip())
raise nodes.SkipNode
def visit_desc_parameterlist(self, node):
d = self.descstack[-1]
if d.env == 'describe':
d.name += self.encode(node.astext())
else:
self.descstack[-1].params = self.encode(node.astext().strip())
raise nodes.SkipNode
def visit_refcount(self, node):
self.body.append("\\emph{")
def depart_refcount(self, node):
self.body.append("}\\\\")
def visit_desc_content(self, node):
if node.children and isinstance(node.children[0], addnodes.desc):
# avoid empty desc environment which causes a formatting bug
self.body.append('~')
def depart_desc_content(self, node):
pass
def visit_seealso(self, node):
self.body.append("\n\n\\begin{seealso}\n")
def depart_seealso(self, node):
self.body.append("\n\\end{seealso}\n")
def visit_rubric(self, node):
if len(node.children) == 1 and node.children[0].astext() == 'Footnotes':
raise nodes.SkipNode
self.builder.warn('encountered rubric node not used for footnotes, '
'content will be lost')
def visit_footnote(self, node):
# XXX not optimal, footnotes are at section end
num = node.children[0].astext().strip()
self.body.append('\\footnotetext[%s]{' % num)
def depart_footnote(self, node):
self.body.append('}')
def visit_label(self, node):
if isinstance(node.parent, nodes.citation):
self.bibitems[-1][0] = node.astext()
raise nodes.SkipNode
def visit_tabular_col_spec(self, node):
self.next_table_colspec = node['spec']
raise nodes.SkipNode
def visit_table(self, node):
if self.table:
raise NotImplementedError('Nested tables are not supported.')
self.table = Table()
self.tablebody = []
# Redirect body output until table is finished.
self._body = self.body
self.body = self.tablebody
def depart_table(self, node):
self.body = self._body
if self.table.has_verbatim:
self.body.append('\n\\begin{tabular}')
else:
self.body.append('\n\\begin{tabulary}{\\textwidth}')
if self.table.colspec:
self.body.append(self.table.colspec)
else:
if self.table.has_verbatim:
colwidth = 0.95 / self.table.colcount
colspec = ('p{%.3f\\textwidth}|' % colwidth) * self.table.colcount
self.body.append('{|' + colspec + '}\n')
else:
self.body.append('{|' + ('L|' * self.table.colcount) + '}\n')
self.body.extend(self.tablebody)
if self.table.has_verbatim:
self.body.append('\\end{tabular}\n\n')
else:
self.body.append('\\end{tabulary}\n\n')
self.table = None
self.tablebody = None
def visit_colspec(self, node):
self.table.colcount += 1
def depart_colspec(self, node):
pass
def visit_tgroup(self, node):
pass
def depart_tgroup(self, node):
pass
def visit_thead(self, node):
if self.next_table_colspec:
self.table.colspec = '{%s}\n' % self.next_table_colspec
self.next_table_colspec = None
self.body.append('\\hline\n')
self.table.had_head = True
def depart_thead(self, node):
self.body.append('\\hline\n')
def visit_tbody(self, node):
if not self.table.had_head:
self.visit_thead(node)
def depart_tbody(self, node):
self.body.append('\\hline\n')
def visit_row(self, node):
self.table.col = 0
def depart_row(self, node):
self.body.append('\\\\\n')
def visit_entry(self, node):
if node.has_key('morerows') or node.has_key('morecols'):
raise NotImplementedError('Column or row spanning cells are '
'not implemented.')
if self.table.col > 0:
self.body.append(' & ')
self.table.col += 1
if isinstance(node.parent.parent, nodes.thead):
self.body.append('\\textbf{')
self.context.append('}')
else:
self.context.append('')
def depart_entry(self, node):
self.body.append(self.context.pop()) # header
def visit_acks(self, node):
# this is a list in the source, but should be rendered as a
# comma-separated list here
self.body.append('\n\n')
self.body.append(', '.join(n.astext() for n in node.children[0].children) + '.')
self.body.append('\n\n')
raise nodes.SkipNode
def visit_bullet_list(self, node):
self.body.append('\\begin{itemize}\n' )
def depart_bullet_list(self, node):
self.body.append('\\end{itemize}\n' )
def visit_enumerated_list(self, node):
self.body.append('\\begin{enumerate}\n' )
def depart_enumerated_list(self, node):
self.body.append('\\end{enumerate}\n' )
def visit_list_item(self, node):
# Append "{}" in case the next character is "[", which would break
# LaTeX's list environment (no numbering and the "[" is not printed).
self.body.append(r'\item {} ')
def depart_list_item(self, node):
self.body.append('\n')
def visit_definition_list(self, node):
self.body.append('\\begin{description}\n')
def depart_definition_list(self, node):
self.body.append('\\end{description}\n')
def visit_definition_list_item(self, node):
pass
def depart_definition_list_item(self, node):
pass
def visit_term(self, node):
ctx = ']'
if node.has_key('ids') and node['ids']:
ctx += '\\hypertarget{%s}{}' % node['ids'][0]
self.body.append('\\item[')
self.context.append(ctx)
def depart_term(self, node):
self.body.append(self.context.pop())
def visit_classifier(self, node):
self.body.append('{[}')
def depart_classifier(self, node):
self.body.append('{]}')
def visit_definition(self, node):
pass
def depart_definition(self, node):
self.body.append('\n')
def visit_field_list(self, node):
self.body.append('\\begin{quote}\\begin{description}\n')
def depart_field_list(self, node):
self.body.append('\\end{description}\\end{quote}\n')
def visit_field(self, node):
pass
def depart_field(self, node):
pass
visit_field_name = visit_term
depart_field_name = depart_term
visit_field_body = visit_definition
depart_field_body = depart_definition
def visit_paragraph(self, node):
self.body.append('\n')
def depart_paragraph(self, node):
self.body.append('\n')
def visit_centered(self, node):
self.body.append('\n\\begin{centering}')
def depart_centered(self, node):
self.body.append('\n\\end{centering}')
def visit_module(self, node):
modname = node['modname']
self.body.append('\n\\declaremodule[%s]{}{%s}' % (modname.replace('_', ''),
self.encode(modname)))
self.body.append('\n\\modulesynopsis{%s}' % self.encode(node['synopsis']))
if node.has_key('platform'):
self.body.append('\\platform{%s}' % self.encode(node['platform']))
def depart_module(self, node):
pass
def visit_image(self, node):
self.need_graphicx = 1
attrs = node.attributes
pre = [] # in reverse order
post = []
include_graphics_options = ""
inline = isinstance(node.parent, nodes.TextElement)
if attrs.has_key('scale'):
# Could also be done with ``scale`` option to
# ``\includegraphics``; doing it this way for consistency.
pre.append('\\scalebox{%f}{' % (attrs['scale'] / 100.0,))
post.append('}')
if attrs.has_key('width'):
include_graphics_options = '[width=%s]' % attrs['width']
if attrs.has_key('align'):
align_prepost = {
# By default latex aligns the top of an image.
(1, 'top'): ('', ''),
(1, 'middle'): ('\\raisebox{-0.5\\height}{', '}'),
(1, 'bottom'): ('\\raisebox{-\\height}{', '}'),
(0, 'center'): ('{\\hfill', '\\hfill}'),
# These 2 don't exactly do the right thing. The image should
# be floated alongside the paragraph. See
# http://www.w3.org/TR/html4/struct/objects.html#adef-align-IMG
(0, 'left'): ('{', '\\hfill}'),
(0, 'right'): ('{\\hfill', '}'),}
try:
pre.append(align_prepost[inline, attrs['align']][0])
post.append(align_prepost[inline, attrs['align']][1])
except KeyError:
pass
if not inline:
pre.append('\n')
post.append('\n')
pre.reverse()
if node['uri'] in self.builder.images:
uri = self.builder.images[node['uri']]
else:
uri = node['uri']
if uri.find('://') != -1:
# ignore remote images
return
self.body.extend(pre)
# XXX: for now, don't fiddle around with graphics formats
self.body.append('\\includegraphics%s{%s}' % (include_graphics_options, uri))
self.body.extend(post)
def depart_image(self, node):
pass
def visit_figure(self, node):
self.body.append('\\begin{figure}\n')
def depart_figure(self, node):
self.body.append('\\end{figure}\n')
def visit_caption(self, node):
self.body.append('\\caption{')
def depart_caption(self, node):
self.body.append('}')
def visit_admonition(self, node):
self.body.append('\n\\begin{quote}')
def depart_admonition(self, node):
self.body.append('\\end{quote}\n')
def _make_visit_admonition(name):
def visit_admonition(self, node):
self.body.append('\n\\begin{notice}[%s]' % name)
return visit_admonition
def _depart_named_admonition(self, node):
self.body.append('\\end{notice}\n')
visit_attention = _make_visit_admonition('attention')
depart_attention = _depart_named_admonition
visit_caution = _make_visit_admonition('caution')
depart_caution = _depart_named_admonition
visit_danger = _make_visit_admonition('danger')
depart_danger = _depart_named_admonition
visit_error = _make_visit_admonition('error')
depart_error = _depart_named_admonition
visit_hint = _make_visit_admonition('hint')
depart_hint = _depart_named_admonition
visit_important = _make_visit_admonition('important')
depart_important = _depart_named_admonition
visit_note = _make_visit_admonition('note')
depart_note = _depart_named_admonition
visit_tip = _make_visit_admonition('tip')
depart_tip = _depart_named_admonition
visit_warning = _make_visit_admonition('warning')
depart_warning = _depart_named_admonition
def visit_versionmodified(self, node):
self.body.append('\\%s' % node['type'])
if node['type'] == 'deprecated':
self.body.append('{%s}{' % node['version'])
self.context.append('}')
else:
if len(node):
self.body.append('[')
self.context.append(']{%s}' % node['version'])
else:
self.body.append('{%s}' % node['version'])
self.context.append('')
def depart_versionmodified(self, node):
self.body.append(self.context.pop())
def visit_target(self, node):
def add_target(id):
# indexing uses standard LaTeX index markup, so the targets
# will be generated differently
if not id.startswith('index-'):
self.body.append(r'\hypertarget{%s}{}' % id)
if node.has_key('refid') and node['refid'] not in self.written_ids:
parindex = node.parent.index(node)
try:
next = node.parent[parindex+1]
if isinstance(next, nodes.section):
self.next_section_target = node['refid']
return
except IndexError:
pass
add_target(node['refid'])
self.written_ids.add(node['refid'])
def depart_target(self, node):
pass
indextype_map = {
'module': 'refmodindex',
'keyword': 'kwindex',
'operator': 'opindex',
'object': 'obindex',
'exception': 'exindex',
'statement': 'stindex',
'builtin': 'bifuncindex',
}
def visit_index(self, node, scre=re.compile(r';\s*')):
entries = node['entries']
for type, string, tid, _ in entries:
if type == 'single':
self.body.append(r'\index{%s}' % scre.sub('!', self.encode(string)))
elif type == 'pair':
parts = tuple(self.encode(x.strip()) for x in string.split(';', 1))
self.body.append(r'\indexii{%s}{%s}' % parts)
elif type == 'triple':
parts = tuple(self.encode(x.strip()) for x in string.split(';', 2))
self.body.append(r'\indexiii{%s}{%s}{%s}' % parts)
elif type in self.indextype_map:
self.body.append(r'\%s{%s}' % (self.indextype_map[type],
self.encode(string)))
else:
self.builder.warn('unknown index entry type %s found' % type)
raise nodes.SkipNode
def visit_raw(self, node):
if 'latex' in node.get('format', '').split():
self.body.append(r'%s' % node.astext())
raise nodes.SkipNode
def visit_reference(self, node):
uri = node.get('refuri', '')
if self.in_title or not uri:
self.context.append('')
elif uri.startswith('mailto:') or uri.startswith('http:') or \
uri.startswith('ftp:'):
self.body.append('\\href{%s}{' % self.encode(uri))
self.context.append('}')
elif uri.startswith('#'):
self.body.append('\\hyperlink{%s}{' % uri[1:])
self.context.append('}')
elif uri.startswith('@token'):
if self.in_production_list:
self.body.append('\\token{')
else:
self.body.append('\\grammartoken{')
self.context.append('}')
else:
self.builder.warn('unusable reference target found: %s' % uri)
self.context.append('')
def depart_reference(self, node):
self.body.append(self.context.pop())
def visit_pending_xref(self, node):
pass
def depart_pending_xref(self, node):
pass
def visit_emphasis(self, node):
self.body.append(r'\emph{')
def depart_emphasis(self, node):
self.body.append('}')
def visit_literal_emphasis(self, node):
self.body.append(r'\emph{\texttt{')
def depart_literal_emphasis(self, node):
self.body.append('}}')
def visit_strong(self, node):
self.body.append(r'\textbf{')
def depart_strong(self, node):
self.body.append('}')
def visit_title_reference(self, node):
self.body.append(r'\emph{')
def depart_title_reference(self, node):
self.body.append('}')
def visit_citation(self, node):
# TODO maybe use cite bibitems
self.bibitems.append(['', ''])
self.context.append(len(self.body))
def depart_citation(self, node):
size = self.context.pop()
text = ''.join(self.body[size:])
del self.body[size:]
self.bibitems[-1][1] = text
def visit_citation_reference(self, node):
citeid = node.astext()
self.body.append('\\cite{%s}' % citeid)
raise nodes.SkipNode
def visit_literal(self, node):
content = self.encode(node.astext().strip())
if self.in_title:
self.body.append(r'\texttt{%s}' % content)
elif re.search('[ \t\n]', content):
self.body.append(r'\samp{%s}' % content)
else:
self.body.append(r'\code{%s}' % content)
raise nodes.SkipNode
def visit_footnote_reference(self, node):
self.body.append('\\footnotemark[%s]' % node.astext())
raise nodes.SkipNode
def visit_literal_block(self, node):
self.verbatim = ''
def depart_literal_block(self, node):
code = self.verbatim.rstrip('\n')
lang = self.highlightlang
linenos = code.count('\n') >= self.highlightlinenothreshold - 1
if node.has_key('language'):
# code-block directives
lang = node['language']
if node.has_key('linenos'):
linenos = node['linenos']
hlcode = self.highlighter.highlight_block(code, lang, linenos)
# workaround for Unicode issue
hlcode = hlcode.replace(u'', u'@texteuro[]')
# must use original Verbatim environment and "tabular" environment
if self.table:
hlcode = hlcode.replace('\\begin{Verbatim}',
'\\begin{OriginalVerbatim}')
self.table.has_verbatim = True
# get consistent trailer
hlcode = hlcode.rstrip()[:-14] # strip \end{Verbatim}
hlcode = hlcode.rstrip() + '\n'
self.body.append('\n' + hlcode + '\\end{%sVerbatim}\n' %
(self.table and 'Original' or ''))
self.verbatim = None
visit_doctest_block = visit_literal_block
depart_doctest_block = depart_literal_block
def visit_line_block(self, node):
"""line-block:
* whitespace (including linebreaks) is significant
* inline markup is supported.
* serif typeface
"""
self.body.append('{\\raggedright{}')
self.literal_whitespace = 1
def depart_line_block(self, node):
self.literal_whitespace = 0
# remove the last \\
del self.body[-1]
self.body.append('}\n')
def visit_line(self, node):
self._line_start = len(self.body)
def depart_line(self, node):
if self._line_start == len(self.body):
# no output in this line -- add a nonbreaking space, else the
# \\ command will give an error
self.body.append('~')
self.body.append('\\\\\n')
def visit_block_quote(self, node):
# If the block quote contains a single object and that object
# is a list, then generate a list not a block quote.
# This lets us indent lists.
done = 0
if len(node.children) == 1:
child = node.children[0]
if isinstance(child, nodes.bullet_list) or \
isinstance(child, nodes.enumerated_list):
done = 1
if not done:
self.body.append('\\begin{quote}\n')
def depart_block_quote(self, node):
done = 0
if len(node.children) == 1:
child = node.children[0]
if isinstance(child, nodes.bullet_list) or \
isinstance(child, nodes.enumerated_list):
done = 1
if not done:
self.body.append('\\end{quote}\n')
# option node handling copied from docutils' latex writer
def visit_option(self, node):
if self.context[-1]:
# this is not the first option
self.body.append(', ')
def depart_option(self, node):
# flag that the first option is done.
self.context[-1] += 1
def visit_option_argument(self, node):
"""The delimiter betweeen an option and its argument."""
self.body.append(node.get('delimiter', ' '))
def depart_option_argument(self, node):
pass
def visit_option_group(self, node):
self.body.append('\\item [')
# flag for first option
self.context.append(0)
def depart_option_group(self, node):
self.context.pop() # the flag
self.body.append('] ')
def visit_option_list(self, node):
self.body.append('\\begin{optionlist}{3cm}\n')
def depart_option_list(self, node):
self.body.append('\\end{optionlist}\n')
def visit_option_list_item(self, node):
pass
def depart_option_list_item(self, node):
pass
def visit_option_string(self, node):
pass
def depart_option_string(self, node):
pass
def visit_description(self, node):
self.body.append( ' ' )
def depart_description(self, node):
pass
def visit_substitution_definition(self, node):
raise nodes.SkipNode
# text handling
replacements = [
(u"\\", u"\x00"),
(u"$", ur"\$"),
(r"%", ur"\%"),
(u"&", ur"\&"),
(u"#", ur"\#"),
(u"_", ur"\_"),
(u"{", ur"\{"),
(u"}", ur"\}"),
(u"[", ur"{[}"),
(u"]", ur"{]}"),
(u"", ur"\P{}"),
(u"§", ur"\S{}"),
(u"", ur"$\infty$"),
(u"±", ur"$\pm$"),
(u"", ur"$\rightarrow$"),
(u"", ur"$\Omega$"),
(u"Ω", ur"$\Omega$"),
(u"φ", ur"$\phi$"),
(u"π", ur"$\pi$"),
(u"~", ur"\textasciitilde{}"),
(u"", ur"\texteuro{}"),
(u"<", ur"\textless{}"),
(u">", ur"\textgreater{}"),
(u"^", ur"\textasciicircum{}"),
(u"\x00", ur"\textbackslash{}"),
(u"\N{RIGHTWARDS ARROW}", ur"$\rightarrow$"),
]
def encode(self, text):
for x, y in self.replacements:
text = text.replace(x, y)
if self.literal_whitespace:
# Insert a blank before the newline, to avoid
# ! LaTeX Error: There's no line here to end.
text = text.replace("\n", '~\\\\\n').replace(" ", "~")
return text
def visit_Text(self, node):
if self.verbatim is not None:
self.verbatim += node.astext()
else:
text = self.encode(node.astext())
self.body.append(educateQuotesLatex(text))
def depart_Text(self, node):
pass
def visit_comment(self, node):
raise nodes.SkipNode
def visit_system_message(self, node):
pass
def depart_system_message(self, node):
self.body.append('\n')
def unknown_visit(self, node):
raise NotImplementedError("Unknown node: " + node.__class__.__name__)