179
|
1 |
# $Id: __init__.py 5333 2007-07-10 17:31:28Z grubert $
|
|
2 |
# Author: Engelbert Gruber <grubert@users.sourceforge.net>
|
|
3 |
# Copyright: This module has been placed in the public domain.
|
|
4 |
|
|
5 |
"""
|
|
6 |
LaTeX2e document tree Writer.
|
|
7 |
"""
|
|
8 |
|
|
9 |
__docformat__ = 'reStructuredText'
|
|
10 |
|
|
11 |
# code contributions from several people included, thanks to all.
|
|
12 |
# some named: David Abrahams, Julien Letessier, Lele Gaifax, and others.
|
|
13 |
#
|
|
14 |
# convention deactivate code by two # e.g. ##.
|
|
15 |
|
|
16 |
import sys
|
|
17 |
import time
|
|
18 |
import re
|
|
19 |
import string
|
|
20 |
from types import ListType
|
|
21 |
from docutils import frontend, nodes, languages, writers, utils
|
|
22 |
from docutils.writers.newlatex2e import unicode_map
|
|
23 |
|
|
24 |
from docutils.transforms.references import DanglingReferencesVisitor
|
|
25 |
|
|
26 |
class Writer(writers.Writer):
|
|
27 |
|
|
28 |
supported = ('latex','latex2e')
|
|
29 |
"""Formats this writer supports."""
|
|
30 |
|
|
31 |
settings_spec = (
|
|
32 |
'LaTeX-Specific Options',
|
|
33 |
'The LaTeX "--output-encoding" default is "latin-1:strict".',
|
|
34 |
(('Specify documentclass. Default is "article".',
|
|
35 |
['--documentclass'],
|
|
36 |
{'default': 'article', }),
|
|
37 |
('Specify document options. Multiple options can be given, '
|
|
38 |
'separated by commas. Default is "10pt,a4paper".',
|
|
39 |
['--documentoptions'],
|
|
40 |
{'default': '10pt,a4paper', }),
|
|
41 |
('Use LaTeX footnotes. LaTeX supports only numbered footnotes (does it?). '
|
|
42 |
'Default: no, uses figures.',
|
|
43 |
['--use-latex-footnotes'],
|
|
44 |
{'default': 0, 'action': 'store_true',
|
|
45 |
'validator': frontend.validate_boolean}),
|
|
46 |
('Format for footnote references: one of "superscript" or '
|
|
47 |
'"brackets". Default is "superscript".',
|
|
48 |
['--footnote-references'],
|
|
49 |
{'choices': ['superscript', 'brackets'], 'default': 'superscript',
|
|
50 |
'metavar': '<format>',
|
|
51 |
'overrides': 'trim_footnote_reference_space'}),
|
|
52 |
('Use LaTeX citations. '
|
|
53 |
'Default: no, uses figures which might get mixed with images.',
|
|
54 |
['--use-latex-citations'],
|
|
55 |
{'default': 0, 'action': 'store_true',
|
|
56 |
'validator': frontend.validate_boolean}),
|
|
57 |
('Format for block quote attributions: one of "dash" (em-dash '
|
|
58 |
'prefix), "parentheses"/"parens", or "none". Default is "dash".',
|
|
59 |
['--attribution'],
|
|
60 |
{'choices': ['dash', 'parentheses', 'parens', 'none'],
|
|
61 |
'default': 'dash', 'metavar': '<format>'}),
|
|
62 |
('Specify a stylesheet file. The file will be "input" by latex in '
|
|
63 |
'the document header. Default is no stylesheet (""). '
|
|
64 |
'Overrides --stylesheet-path.',
|
|
65 |
['--stylesheet'],
|
|
66 |
{'default': '', 'metavar': '<file>',
|
|
67 |
'overrides': 'stylesheet_path'}),
|
|
68 |
('Specify a stylesheet file, relative to the current working '
|
|
69 |
'directory. Overrides --stylesheet.',
|
|
70 |
['--stylesheet-path'],
|
|
71 |
{'metavar': '<file>', 'overrides': 'stylesheet'}),
|
|
72 |
('Table of contents by docutils (default) or LaTeX. LaTeX (writer) '
|
|
73 |
'supports only one ToC per document, but docutils does not know of '
|
|
74 |
'pagenumbers. LaTeX table of contents also means LaTeX generates '
|
|
75 |
'sectionnumbers.',
|
|
76 |
['--use-latex-toc'],
|
|
77 |
{'default': 0, 'action': 'store_true',
|
|
78 |
'validator': frontend.validate_boolean}),
|
|
79 |
('Add parts on top of the section hierarchy.',
|
|
80 |
['--use-part-section'],
|
|
81 |
{'default': 0, 'action': 'store_true',
|
|
82 |
'validator': frontend.validate_boolean}),
|
|
83 |
('Let LaTeX print author and date, do not show it in docutils '
|
|
84 |
'document info.',
|
|
85 |
['--use-latex-docinfo'],
|
|
86 |
{'default': 0, 'action': 'store_true',
|
|
87 |
'validator': frontend.validate_boolean}),
|
|
88 |
('Use LaTeX abstract environment for the documents abstract.'
|
|
89 |
'Per default the abstract is an unnumbered section.',
|
|
90 |
['--use-latex-abstract'],
|
|
91 |
{'default': 0, 'action': 'store_true',
|
|
92 |
'validator': frontend.validate_boolean}),
|
|
93 |
('Color of any hyperlinks embedded in text '
|
|
94 |
'(default: "blue", "0" to disable).',
|
|
95 |
['--hyperlink-color'], {'default': 'blue'}),
|
|
96 |
('Enable compound enumerators for nested enumerated lists '
|
|
97 |
'(e.g. "1.2.a.ii"). Default: disabled.',
|
|
98 |
['--compound-enumerators'],
|
|
99 |
{'default': None, 'action': 'store_true',
|
|
100 |
'validator': frontend.validate_boolean}),
|
|
101 |
('Disable compound enumerators for nested enumerated lists. This is '
|
|
102 |
'the default.',
|
|
103 |
['--no-compound-enumerators'],
|
|
104 |
{'action': 'store_false', 'dest': 'compound_enumerators'}),
|
|
105 |
('Enable section ("." subsection ...) prefixes for compound '
|
|
106 |
'enumerators. This has no effect without --compound-enumerators. '
|
|
107 |
'Default: disabled.',
|
|
108 |
['--section-prefix-for-enumerators'],
|
|
109 |
{'default': None, 'action': 'store_true',
|
|
110 |
'validator': frontend.validate_boolean}),
|
|
111 |
('Disable section prefixes for compound enumerators. '
|
|
112 |
'This is the default.',
|
|
113 |
['--no-section-prefix-for-enumerators'],
|
|
114 |
{'action': 'store_false', 'dest': 'section_prefix_for_enumerators'}),
|
|
115 |
('Set the separator between section number and enumerator '
|
|
116 |
'for compound enumerated lists. Default is "-".',
|
|
117 |
['--section-enumerator-separator'],
|
|
118 |
{'default': '-', 'metavar': '<char>'}),
|
|
119 |
('When possibile, use verbatim for literal-blocks. '
|
|
120 |
'Default is to always use the mbox environment.',
|
|
121 |
['--use-verbatim-when-possible'],
|
|
122 |
{'default': 0, 'action': 'store_true',
|
|
123 |
'validator': frontend.validate_boolean}),
|
|
124 |
('Table style. "standard" with horizontal and vertical lines, '
|
|
125 |
'"booktabs" (LaTeX booktabs style) only horizontal lines '
|
|
126 |
'above and below the table and below the header or "nolines". '
|
|
127 |
'Default: "standard"',
|
|
128 |
['--table-style'],
|
|
129 |
{'choices': ['standard', 'booktabs','nolines'], 'default': 'standard',
|
|
130 |
'metavar': '<format>'}),
|
|
131 |
('LaTeX graphicx package option. '
|
|
132 |
'Possible values are "dvips", "pdftex". "auto" includes LaTeX code '
|
|
133 |
'to use "pdftex" if processing with pdf(la)tex and dvips otherwise. '
|
|
134 |
'Default is no option.',
|
|
135 |
['--graphicx-option'],
|
|
136 |
{'default': ''}),
|
|
137 |
('LaTeX font encoding. '
|
|
138 |
'Possible values are "T1", "OT1", "" or some other fontenc option. '
|
|
139 |
'The font encoding influences available symbols, e.g. "<<" as one '
|
|
140 |
'character. Default is "" which leads to package "ae" (a T1 '
|
|
141 |
'emulation using CM fonts).',
|
|
142 |
['--font-encoding'],
|
|
143 |
{'default': ''}),
|
|
144 |
('Per default the latex-writer puts the reference title into '
|
|
145 |
'hyperreferences. Specify "ref*" or "pageref*" to get the section '
|
|
146 |
'number or the page number.',
|
|
147 |
['--reference-label'],
|
|
148 |
{'default': None, }),
|
|
149 |
('Specify style and database for bibtex, for example '
|
|
150 |
'"--use-bibtex=mystyle,mydb1,mydb2".',
|
|
151 |
['--use-bibtex'],
|
|
152 |
{'default': None, }),
|
|
153 |
),)
|
|
154 |
|
|
155 |
settings_defaults = {'output_encoding': 'latin-1'}
|
|
156 |
|
|
157 |
relative_path_settings = ('stylesheet_path',)
|
|
158 |
|
|
159 |
config_section = 'latex2e writer'
|
|
160 |
config_section_dependencies = ('writers',)
|
|
161 |
|
|
162 |
visitor_attributes = ("head_prefix", "head",
|
|
163 |
"body_prefix", "body", "body_suffix")
|
|
164 |
|
|
165 |
output = None
|
|
166 |
"""Final translated form of `document`."""
|
|
167 |
|
|
168 |
def __init__(self):
|
|
169 |
writers.Writer.__init__(self)
|
|
170 |
self.translator_class = LaTeXTranslator
|
|
171 |
|
|
172 |
def translate(self):
|
|
173 |
visitor = self.translator_class(self.document)
|
|
174 |
self.document.walkabout(visitor)
|
|
175 |
self.output = visitor.astext()
|
|
176 |
# copy parts
|
|
177 |
for attr in self.visitor_attributes:
|
|
178 |
setattr(self, attr, getattr(visitor, attr))
|
|
179 |
|
|
180 |
def assemble_parts(self):
|
|
181 |
writers.Writer.assemble_parts(self)
|
|
182 |
for part in self.visitor_attributes:
|
|
183 |
self.parts[part] = ''.join(getattr(self, part))
|
|
184 |
|
|
185 |
|
|
186 |
"""
|
|
187 |
Notes on LaTeX
|
|
188 |
--------------
|
|
189 |
|
|
190 |
* LaTeX does not support multiple tocs in one document.
|
|
191 |
(might be no limitation except for docutils documentation)
|
|
192 |
|
|
193 |
The "minitoc" latex package can produce per-chapter tocs in
|
|
194 |
book and report document classes.
|
|
195 |
|
|
196 |
* width
|
|
197 |
|
|
198 |
* linewidth - width of a line in the local environment
|
|
199 |
* textwidth - the width of text on the page
|
|
200 |
|
|
201 |
Maybe always use linewidth ?
|
|
202 |
|
|
203 |
*Bug* inside a minipage a (e.g. Sidebar) the linewidth is
|
|
204 |
not changed, needs fix in docutils so that tables
|
|
205 |
are not too wide.
|
|
206 |
|
|
207 |
So we add locallinewidth set it initially and
|
|
208 |
on entering sidebar and reset on exit.
|
|
209 |
"""
|
|
210 |
|
|
211 |
class Babel:
|
|
212 |
"""Language specifics for LaTeX."""
|
|
213 |
# country code by a.schlock.
|
|
214 |
# partly manually converted from iso and babel stuff, dialects and some
|
|
215 |
_ISO639_TO_BABEL = {
|
|
216 |
'no': 'norsk', #XXX added by hand ( forget about nynorsk?)
|
|
217 |
'gd': 'scottish', #XXX added by hand
|
|
218 |
'hu': 'magyar', #XXX added by hand
|
|
219 |
'pt': 'portuguese',#XXX added by hand
|
|
220 |
'sl': 'slovenian',
|
|
221 |
'af': 'afrikaans',
|
|
222 |
'bg': 'bulgarian',
|
|
223 |
'br': 'breton',
|
|
224 |
'ca': 'catalan',
|
|
225 |
'cs': 'czech',
|
|
226 |
'cy': 'welsh',
|
|
227 |
'da': 'danish',
|
|
228 |
'fr': 'french',
|
|
229 |
# french, francais, canadien, acadian
|
|
230 |
'de': 'ngerman', #XXX rather than german
|
|
231 |
# ngerman, naustrian, german, germanb, austrian
|
|
232 |
'el': 'greek',
|
|
233 |
'en': 'english',
|
|
234 |
# english, USenglish, american, UKenglish, british, canadian
|
|
235 |
'eo': 'esperanto',
|
|
236 |
'es': 'spanish',
|
|
237 |
'et': 'estonian',
|
|
238 |
'eu': 'basque',
|
|
239 |
'fi': 'finnish',
|
|
240 |
'ga': 'irish',
|
|
241 |
'gl': 'galician',
|
|
242 |
'he': 'hebrew',
|
|
243 |
'hr': 'croatian',
|
|
244 |
'hu': 'hungarian',
|
|
245 |
'is': 'icelandic',
|
|
246 |
'it': 'italian',
|
|
247 |
'la': 'latin',
|
|
248 |
'nl': 'dutch',
|
|
249 |
'pl': 'polish',
|
|
250 |
'pt': 'portuguese',
|
|
251 |
'ro': 'romanian',
|
|
252 |
'ru': 'russian',
|
|
253 |
'sk': 'slovak',
|
|
254 |
'sr': 'serbian',
|
|
255 |
'sv': 'swedish',
|
|
256 |
'tr': 'turkish',
|
|
257 |
'uk': 'ukrainian'
|
|
258 |
}
|
|
259 |
|
|
260 |
def __init__(self,lang):
|
|
261 |
self.language = lang
|
|
262 |
# pdflatex does not produce double quotes for ngerman in tt.
|
|
263 |
self.double_quote_replacment = None
|
|
264 |
if re.search('^de',self.language):
|
|
265 |
#self.quotes = ("\"`", "\"'")
|
|
266 |
self.quotes = ('{\\glqq}', '{\\grqq}')
|
|
267 |
self.double_quote_replacment = "{\\dq}"
|
|
268 |
elif re.search('^it',self.language):
|
|
269 |
self.quotes = ("``", "''")
|
|
270 |
self.double_quote_replacment = r'{\char`\"}'
|
|
271 |
else:
|
|
272 |
self.quotes = ("``", "''")
|
|
273 |
self.quote_index = 0
|
|
274 |
|
|
275 |
def next_quote(self):
|
|
276 |
q = self.quotes[self.quote_index]
|
|
277 |
self.quote_index = (self.quote_index+1)%2
|
|
278 |
return q
|
|
279 |
|
|
280 |
def quote_quotes(self,text):
|
|
281 |
t = None
|
|
282 |
for part in text.split('"'):
|
|
283 |
if t == None:
|
|
284 |
t = part
|
|
285 |
else:
|
|
286 |
t += self.next_quote() + part
|
|
287 |
return t
|
|
288 |
|
|
289 |
def double_quotes_in_tt (self,text):
|
|
290 |
if not self.double_quote_replacment:
|
|
291 |
return text
|
|
292 |
return text.replace('"', self.double_quote_replacment)
|
|
293 |
|
|
294 |
def get_language(self):
|
|
295 |
if self._ISO639_TO_BABEL.has_key(self.language):
|
|
296 |
return self._ISO639_TO_BABEL[self.language]
|
|
297 |
else:
|
|
298 |
# support dialects.
|
|
299 |
l = self.language.split("_")[0]
|
|
300 |
if self._ISO639_TO_BABEL.has_key(l):
|
|
301 |
return self._ISO639_TO_BABEL[l]
|
|
302 |
return None
|
|
303 |
|
|
304 |
|
|
305 |
latex_headings = {
|
|
306 |
'optionlist_environment' : [
|
|
307 |
'\\newcommand{\\optionlistlabel}[1]{\\bf #1 \\hfill}\n'
|
|
308 |
'\\newenvironment{optionlist}[1]\n'
|
|
309 |
'{\\begin{list}{}\n'
|
|
310 |
' {\\setlength{\\labelwidth}{#1}\n'
|
|
311 |
' \\setlength{\\rightmargin}{1cm}\n'
|
|
312 |
' \\setlength{\\leftmargin}{\\rightmargin}\n'
|
|
313 |
' \\addtolength{\\leftmargin}{\\labelwidth}\n'
|
|
314 |
' \\addtolength{\\leftmargin}{\\labelsep}\n'
|
|
315 |
' \\renewcommand{\\makelabel}{\\optionlistlabel}}\n'
|
|
316 |
'}{\\end{list}}\n',
|
|
317 |
],
|
|
318 |
'lineblock_environment' : [
|
|
319 |
'\\newlength{\\lineblockindentation}\n'
|
|
320 |
'\\setlength{\\lineblockindentation}{2.5em}\n'
|
|
321 |
'\\newenvironment{lineblock}[1]\n'
|
|
322 |
'{\\begin{list}{}\n'
|
|
323 |
' {\\setlength{\\partopsep}{\\parskip}\n'
|
|
324 |
' \\addtolength{\\partopsep}{\\baselineskip}\n'
|
|
325 |
' \\topsep0pt\\itemsep0.15\\baselineskip\\parsep0pt\n'
|
|
326 |
' \\leftmargin#1}\n'
|
|
327 |
' \\raggedright}\n'
|
|
328 |
'{\\end{list}}\n'
|
|
329 |
],
|
|
330 |
'footnote_floats' : [
|
|
331 |
'% begin: floats for footnotes tweaking.\n',
|
|
332 |
'\\setlength{\\floatsep}{0.5em}\n',
|
|
333 |
'\\setlength{\\textfloatsep}{\\fill}\n',
|
|
334 |
'\\addtolength{\\textfloatsep}{3em}\n',
|
|
335 |
'\\renewcommand{\\textfraction}{0.5}\n',
|
|
336 |
'\\renewcommand{\\topfraction}{0.5}\n',
|
|
337 |
'\\renewcommand{\\bottomfraction}{0.5}\n',
|
|
338 |
'\\setcounter{totalnumber}{50}\n',
|
|
339 |
'\\setcounter{topnumber}{50}\n',
|
|
340 |
'\\setcounter{bottomnumber}{50}\n',
|
|
341 |
'% end floats for footnotes\n',
|
|
342 |
],
|
|
343 |
'some_commands' : [
|
|
344 |
'% some commands, that could be overwritten in the style file.\n'
|
|
345 |
'\\newcommand{\\rubric}[1]'
|
|
346 |
'{\\subsection*{~\\hfill {\\it #1} \\hfill ~}}\n'
|
|
347 |
'\\newcommand{\\titlereference}[1]{\\textsl{#1}}\n'
|
|
348 |
'% end of "some commands"\n',
|
|
349 |
]
|
|
350 |
}
|
|
351 |
|
|
352 |
class DocumentClass:
|
|
353 |
"""Details of a LaTeX document class."""
|
|
354 |
|
|
355 |
def __init__(self, document_class, with_part=False):
|
|
356 |
self.document_class = document_class
|
|
357 |
self._with_part = with_part
|
|
358 |
|
|
359 |
def section(self, level):
|
|
360 |
""" Return the section name at the given level for the specific
|
|
361 |
document class.
|
|
362 |
|
|
363 |
Level is 1,2,3..., as level 0 is the title."""
|
|
364 |
|
|
365 |
sections = [ 'section', 'subsection', 'subsubsection',
|
|
366 |
'paragraph', 'subparagraph' ]
|
|
367 |
if self.document_class in ('book', 'report', 'scrreprt', 'scrbook'):
|
|
368 |
sections.insert(0, 'chapter')
|
|
369 |
if self._with_part:
|
|
370 |
sections.insert(0, 'part')
|
|
371 |
if level <= len(sections):
|
|
372 |
return sections[level-1]
|
|
373 |
else:
|
|
374 |
return sections[-1]
|
|
375 |
|
|
376 |
class Table:
|
|
377 |
""" Manage a table while traversing.
|
|
378 |
Maybe change to a mixin defining the visit/departs, but then
|
|
379 |
class Table internal variables are in the Translator.
|
|
380 |
|
|
381 |
Table style might be
|
|
382 |
|
|
383 |
* standard: horizontal and vertical lines
|
|
384 |
* booktabs (requires booktabs latex package): only horizontal lines
|
|
385 |
* nolines, borderless : no lines
|
|
386 |
"""
|
|
387 |
def __init__(self,latex_type,table_style):
|
|
388 |
self._latex_type = latex_type
|
|
389 |
self._table_style = table_style
|
|
390 |
self._open = 0
|
|
391 |
# miscellaneous attributes
|
|
392 |
self._attrs = {}
|
|
393 |
self._col_width = []
|
|
394 |
self._rowspan = []
|
|
395 |
self.stubs = []
|
|
396 |
|
|
397 |
def open(self):
|
|
398 |
self._open = 1
|
|
399 |
self._col_specs = []
|
|
400 |
self.caption = None
|
|
401 |
self._attrs = {}
|
|
402 |
self._in_head = 0 # maybe context with search
|
|
403 |
def close(self):
|
|
404 |
self._open = 0
|
|
405 |
self._col_specs = None
|
|
406 |
self.caption = None
|
|
407 |
self._attrs = {}
|
|
408 |
self.stubs = []
|
|
409 |
def is_open(self):
|
|
410 |
return self._open
|
|
411 |
|
|
412 |
def set_table_style(self, table_style):
|
|
413 |
if not table_style in ('standard','booktabs','borderless','nolines'):
|
|
414 |
return
|
|
415 |
self._table_style = table_style
|
|
416 |
|
|
417 |
def used_packages(self):
|
|
418 |
if self._table_style == 'booktabs':
|
|
419 |
return '\\usepackage{booktabs}\n'
|
|
420 |
return ''
|
|
421 |
def get_latex_type(self):
|
|
422 |
return self._latex_type
|
|
423 |
|
|
424 |
def set(self,attr,value):
|
|
425 |
self._attrs[attr] = value
|
|
426 |
def get(self,attr):
|
|
427 |
if self._attrs.has_key(attr):
|
|
428 |
return self._attrs[attr]
|
|
429 |
return None
|
|
430 |
def get_vertical_bar(self):
|
|
431 |
if self._table_style == 'standard':
|
|
432 |
return '|'
|
|
433 |
return ''
|
|
434 |
# horizontal lines are drawn below a row, because we.
|
|
435 |
def get_opening(self):
|
|
436 |
if self._latex_type == 'longtable':
|
|
437 |
# otherwise longtable might move before paragraph and subparagraph
|
|
438 |
prefix = '\\leavevmode\n'
|
|
439 |
else:
|
|
440 |
prefix = ''
|
|
441 |
return '%s\\begin{%s}[c]' % (prefix, self._latex_type)
|
|
442 |
def get_closing(self):
|
|
443 |
line = ""
|
|
444 |
if self._table_style == 'booktabs':
|
|
445 |
line = '\\bottomrule\n'
|
|
446 |
elif self._table_style == 'standard':
|
|
447 |
lines = '\\hline\n'
|
|
448 |
return '%s\\end{%s}' % (line,self._latex_type)
|
|
449 |
|
|
450 |
def visit_colspec(self, node):
|
|
451 |
self._col_specs.append(node)
|
|
452 |
# "stubs" list is an attribute of the tgroup element:
|
|
453 |
self.stubs.append(node.attributes.get('stub'))
|
|
454 |
|
|
455 |
def get_colspecs(self):
|
|
456 |
"""
|
|
457 |
Return column specification for longtable.
|
|
458 |
|
|
459 |
Assumes reST line length being 80 characters.
|
|
460 |
Table width is hairy.
|
|
461 |
|
|
462 |
=== ===
|
|
463 |
ABC DEF
|
|
464 |
=== ===
|
|
465 |
|
|
466 |
usually gets to narrow, therefore we add 1 (fiddlefactor).
|
|
467 |
"""
|
|
468 |
width = 80
|
|
469 |
|
|
470 |
total_width = 0.0
|
|
471 |
# first see if we get too wide.
|
|
472 |
for node in self._col_specs:
|
|
473 |
colwidth = float(node['colwidth']+1) / width
|
|
474 |
total_width += colwidth
|
|
475 |
self._col_width = []
|
|
476 |
self._rowspan = []
|
|
477 |
# donot make it full linewidth
|
|
478 |
factor = 0.93
|
|
479 |
if total_width > 1.0:
|
|
480 |
factor /= total_width
|
|
481 |
bar = self.get_vertical_bar()
|
|
482 |
latex_table_spec = ""
|
|
483 |
for node in self._col_specs:
|
|
484 |
colwidth = factor * float(node['colwidth']+1) / width
|
|
485 |
self._col_width.append(colwidth+0.005)
|
|
486 |
self._rowspan.append(0)
|
|
487 |
latex_table_spec += "%sp{%.3f\\locallinewidth}" % (bar,colwidth+0.005)
|
|
488 |
return latex_table_spec+bar
|
|
489 |
|
|
490 |
def get_column_width(self):
|
|
491 |
""" return columnwidth for current cell (not multicell)
|
|
492 |
"""
|
|
493 |
return "%.2f\\locallinewidth" % self._col_width[self._cell_in_row-1]
|
|
494 |
|
|
495 |
def visit_thead(self):
|
|
496 |
self._in_thead = 1
|
|
497 |
if self._table_style == 'standard':
|
|
498 |
return ['\\hline\n']
|
|
499 |
elif self._table_style == 'booktabs':
|
|
500 |
return ['\\toprule\n']
|
|
501 |
return []
|
|
502 |
def depart_thead(self):
|
|
503 |
a = []
|
|
504 |
#if self._table_style == 'standard':
|
|
505 |
# a.append('\\hline\n')
|
|
506 |
if self._table_style == 'booktabs':
|
|
507 |
a.append('\\midrule\n')
|
|
508 |
if self._latex_type == 'longtable':
|
|
509 |
a.append('\\endhead\n')
|
|
510 |
# for longtable one could add firsthead, foot and lastfoot
|
|
511 |
self._in_thead = 0
|
|
512 |
return a
|
|
513 |
def visit_row(self):
|
|
514 |
self._cell_in_row = 0
|
|
515 |
def depart_row(self):
|
|
516 |
res = [' \\\\\n']
|
|
517 |
self._cell_in_row = None # remove cell counter
|
|
518 |
for i in range(len(self._rowspan)):
|
|
519 |
if (self._rowspan[i]>0):
|
|
520 |
self._rowspan[i] -= 1
|
|
521 |
|
|
522 |
if self._table_style == 'standard':
|
|
523 |
rowspans = []
|
|
524 |
for i in range(len(self._rowspan)):
|
|
525 |
if (self._rowspan[i]<=0):
|
|
526 |
rowspans.append(i+1)
|
|
527 |
if len(rowspans)==len(self._rowspan):
|
|
528 |
res.append('\\hline\n')
|
|
529 |
else:
|
|
530 |
cline = ''
|
|
531 |
rowspans.reverse()
|
|
532 |
# TODO merge clines
|
|
533 |
while 1:
|
|
534 |
try:
|
|
535 |
c_start = rowspans.pop()
|
|
536 |
except:
|
|
537 |
break
|
|
538 |
cline += '\\cline{%d-%d}\n' % (c_start,c_start)
|
|
539 |
res.append(cline)
|
|
540 |
return res
|
|
541 |
|
|
542 |
def set_rowspan(self,cell,value):
|
|
543 |
try:
|
|
544 |
self._rowspan[cell] = value
|
|
545 |
except:
|
|
546 |
pass
|
|
547 |
def get_rowspan(self,cell):
|
|
548 |
try:
|
|
549 |
return self._rowspan[cell]
|
|
550 |
except:
|
|
551 |
return 0
|
|
552 |
def get_entry_number(self):
|
|
553 |
return self._cell_in_row
|
|
554 |
def visit_entry(self):
|
|
555 |
self._cell_in_row += 1
|
|
556 |
def is_stub_column(self):
|
|
557 |
if len(self.stubs) >= self._cell_in_row:
|
|
558 |
return self.stubs[self._cell_in_row-1]
|
|
559 |
return False
|
|
560 |
|
|
561 |
|
|
562 |
class LaTeXTranslator(nodes.NodeVisitor):
|
|
563 |
|
|
564 |
# When options are given to the documentclass, latex will pass them
|
|
565 |
# to other packages, as done with babel.
|
|
566 |
# Dummy settings might be taken from document settings
|
|
567 |
|
|
568 |
# Templates
|
|
569 |
# ---------
|
|
570 |
|
|
571 |
latex_head = '\\documentclass[%s]{%s}\n'
|
|
572 |
linking = '\\usepackage[colorlinks=%s,linkcolor=%s,urlcolor=%s]{hyperref}\n'
|
|
573 |
stylesheet = '\\input{%s}\n'
|
|
574 |
# add a generated on day , machine by user using docutils version.
|
|
575 |
generator = '% generated by Docutils <http://docutils.sourceforge.net/>\n'
|
|
576 |
# Config setting defaults
|
|
577 |
# -----------------------
|
|
578 |
|
|
579 |
# use latex tableofcontents or let docutils do it.
|
|
580 |
use_latex_toc = 0
|
|
581 |
|
|
582 |
# TODO: use mixins for different implementations.
|
|
583 |
# list environment for docinfo. else tabularx
|
|
584 |
use_optionlist_for_docinfo = 0 # NOT YET IN USE
|
|
585 |
|
|
586 |
# Use compound enumerations (1.A.1.)
|
|
587 |
compound_enumerators = 0
|
|
588 |
|
|
589 |
# If using compound enumerations, include section information.
|
|
590 |
section_prefix_for_enumerators = 0
|
|
591 |
|
|
592 |
# This is the character that separates the section ("." subsection ...)
|
|
593 |
# prefix from the regular list enumerator.
|
|
594 |
section_enumerator_separator = '-'
|
|
595 |
|
|
596 |
# default link color
|
|
597 |
hyperlink_color = "blue"
|
|
598 |
|
|
599 |
def __init__(self, document):
|
|
600 |
nodes.NodeVisitor.__init__(self, document)
|
|
601 |
self.settings = settings = document.settings
|
|
602 |
self.latex_encoding = self.to_latex_encoding(settings.output_encoding)
|
|
603 |
self.use_latex_toc = settings.use_latex_toc
|
|
604 |
self.use_latex_docinfo = settings.use_latex_docinfo
|
|
605 |
self.use_latex_footnotes = settings.use_latex_footnotes
|
|
606 |
self._use_latex_citations = settings.use_latex_citations
|
|
607 |
self._reference_label = settings.reference_label
|
|
608 |
self.hyperlink_color = settings.hyperlink_color
|
|
609 |
self.compound_enumerators = settings.compound_enumerators
|
|
610 |
self.font_encoding = settings.font_encoding
|
|
611 |
self.section_prefix_for_enumerators = (
|
|
612 |
settings.section_prefix_for_enumerators)
|
|
613 |
self.section_enumerator_separator = (
|
|
614 |
settings.section_enumerator_separator.replace('_', '\\_'))
|
|
615 |
if self.hyperlink_color == '0':
|
|
616 |
self.hyperlink_color = 'black'
|
|
617 |
self.colorlinks = 'false'
|
|
618 |
else:
|
|
619 |
self.colorlinks = 'true'
|
|
620 |
|
|
621 |
if self.settings.use_bibtex:
|
|
622 |
self.bibtex = self.settings.use_bibtex.split(",",1)
|
|
623 |
# TODO avoid errors on not declared citations.
|
|
624 |
else:
|
|
625 |
self.bibtex = None
|
|
626 |
# language: labels, bibliographic_fields, and author_separators.
|
|
627 |
# to allow writing labes for specific languages.
|
|
628 |
self.language = languages.get_language(settings.language_code)
|
|
629 |
self.babel = Babel(settings.language_code)
|
|
630 |
self.author_separator = self.language.author_separators[0]
|
|
631 |
self.d_options = self.settings.documentoptions
|
|
632 |
if self.babel.get_language():
|
|
633 |
self.d_options += ',%s' % self.babel.get_language()
|
|
634 |
|
|
635 |
self.d_class = DocumentClass(settings.documentclass,
|
|
636 |
settings.use_part_section)
|
|
637 |
# object for a table while proccessing.
|
|
638 |
self.table_stack = []
|
|
639 |
self.active_table = Table('longtable',settings.table_style)
|
|
640 |
|
|
641 |
# HACK. Should have more sophisticated typearea handling.
|
|
642 |
if settings.documentclass.find('scr') == -1:
|
|
643 |
self.typearea = '\\usepackage[DIV12]{typearea}\n'
|
|
644 |
else:
|
|
645 |
if self.d_options.find('DIV') == -1 and self.d_options.find('BCOR') == -1:
|
|
646 |
self.typearea = '\\typearea{12}\n'
|
|
647 |
else:
|
|
648 |
self.typearea = ''
|
|
649 |
|
|
650 |
if self.font_encoding == 'OT1':
|
|
651 |
fontenc_header = ''
|
|
652 |
elif self.font_encoding == '':
|
|
653 |
fontenc_header = '\\usepackage{ae}\n\\usepackage{aeguill}\n'
|
|
654 |
else:
|
|
655 |
fontenc_header = '\\usepackage[%s]{fontenc}\n' % (self.font_encoding,)
|
|
656 |
if self.latex_encoding.startswith('utf8'):
|
|
657 |
input_encoding = '\\usepackage{ucs}\n\\usepackage[utf8x]{inputenc}\n'
|
|
658 |
else:
|
|
659 |
input_encoding = '\\usepackage[%s]{inputenc}\n' % self.latex_encoding
|
|
660 |
if self.settings.graphicx_option == '':
|
|
661 |
self.graphicx_package = '\\usepackage{graphicx}\n'
|
|
662 |
elif self.settings.graphicx_option.lower() == 'auto':
|
|
663 |
self.graphicx_package = '\n'.join(
|
|
664 |
('%Check if we are compiling under latex or pdflatex',
|
|
665 |
'\\ifx\\pdftexversion\\undefined',
|
|
666 |
' \\usepackage{graphicx}',
|
|
667 |
'\\else',
|
|
668 |
' \\usepackage[pdftex]{graphicx}',
|
|
669 |
'\\fi\n'))
|
|
670 |
else:
|
|
671 |
self.graphicx_package = (
|
|
672 |
'\\usepackage[%s]{graphicx}\n' % self.settings.graphicx_option)
|
|
673 |
|
|
674 |
self.head_prefix = [
|
|
675 |
self.latex_head % (self.d_options,self.settings.documentclass),
|
|
676 |
'\\usepackage{babel}\n', # language is in documents settings.
|
|
677 |
fontenc_header,
|
|
678 |
'\\usepackage{shortvrb}\n', # allows verb in footnotes.
|
|
679 |
input_encoding,
|
|
680 |
# * tabularx: for docinfo, automatic width of columns, always on one page.
|
|
681 |
'\\usepackage{tabularx}\n',
|
|
682 |
'\\usepackage{longtable}\n',
|
|
683 |
self.active_table.used_packages(),
|
|
684 |
# possible other packages.
|
|
685 |
# * fancyhdr
|
|
686 |
# * ltxtable is a combination of tabularx and longtable (pagebreaks).
|
|
687 |
# but ??
|
|
688 |
#
|
|
689 |
# extra space between text in tables and the line above them
|
|
690 |
'\\setlength{\\extrarowheight}{2pt}\n',
|
|
691 |
'\\usepackage{amsmath}\n', # what fore amsmath.
|
|
692 |
self.graphicx_package,
|
|
693 |
'\\usepackage{color}\n',
|
|
694 |
'\\usepackage{multirow}\n',
|
|
695 |
'\\usepackage{ifthen}\n', # before hyperref!
|
|
696 |
self.linking % (self.colorlinks, self.hyperlink_color, self.hyperlink_color),
|
|
697 |
self.typearea,
|
|
698 |
self.generator,
|
|
699 |
# latex lengths
|
|
700 |
'\\newlength{\\admonitionwidth}\n',
|
|
701 |
'\\setlength{\\admonitionwidth}{0.9\\textwidth}\n'
|
|
702 |
# width for docinfo tablewidth
|
|
703 |
'\\newlength{\\docinfowidth}\n',
|
|
704 |
'\\setlength{\\docinfowidth}{0.9\\textwidth}\n'
|
|
705 |
# linewidth of current environment, so tables are not wider
|
|
706 |
# than the sidebar: using locallinewidth seems to defer evaluation
|
|
707 |
# of linewidth, this is fixing it.
|
|
708 |
'\\newlength{\\locallinewidth}\n',
|
|
709 |
# will be set later.
|
|
710 |
]
|
|
711 |
self.head_prefix.extend( latex_headings['optionlist_environment'] )
|
|
712 |
self.head_prefix.extend( latex_headings['lineblock_environment'] )
|
|
713 |
self.head_prefix.extend( latex_headings['footnote_floats'] )
|
|
714 |
self.head_prefix.extend( latex_headings['some_commands'] )
|
|
715 |
## stylesheet is last: so it might be possible to overwrite defaults.
|
|
716 |
stylesheet = utils.get_stylesheet_reference(settings)
|
|
717 |
if stylesheet:
|
|
718 |
settings.record_dependencies.add(stylesheet)
|
|
719 |
self.head_prefix.append(self.stylesheet % (stylesheet))
|
|
720 |
|
|
721 |
if self.linking: # and maybe check for pdf
|
|
722 |
self.pdfinfo = [ ]
|
|
723 |
self.pdfauthor = None
|
|
724 |
# pdftitle, pdfsubject, pdfauthor, pdfkeywords,
|
|
725 |
# pdfcreator, pdfproducer
|
|
726 |
else:
|
|
727 |
self.pdfinfo = None
|
|
728 |
# NOTE: Latex wants a date and an author, rst puts this into
|
|
729 |
# docinfo, so normally we do not want latex author/date handling.
|
|
730 |
# latex article has its own handling of date and author, deactivate.
|
|
731 |
# self.astext() adds \title{...} \author{...} \date{...}, even if the
|
|
732 |
# "..." are empty strings.
|
|
733 |
self.head = [ ]
|
|
734 |
# separate title, so we can appen subtitle.
|
|
735 |
self.title = ''
|
|
736 |
# if use_latex_docinfo: collects lists of author/organization/contact/address lines
|
|
737 |
self.author_stack = []
|
|
738 |
self.date = ''
|
|
739 |
|
|
740 |
self.body_prefix = ['\\raggedbottom\n']
|
|
741 |
self.body = []
|
|
742 |
self.body_suffix = ['\n']
|
|
743 |
self.section_level = 0
|
|
744 |
self.context = []
|
|
745 |
self.topic_classes = []
|
|
746 |
# column specification for tables
|
|
747 |
self.table_caption = None
|
|
748 |
|
|
749 |
# Flags to encode
|
|
750 |
# ---------------
|
|
751 |
# verbatim: to tell encode not to encode.
|
|
752 |
self.verbatim = 0
|
|
753 |
# insert_newline: to tell encode to replace blanks by "~".
|
|
754 |
self.insert_none_breaking_blanks = 0
|
|
755 |
# insert_newline: to tell encode to add latex newline.
|
|
756 |
self.insert_newline = 0
|
|
757 |
# mbox_newline: to tell encode to add mbox and newline.
|
|
758 |
self.mbox_newline = 0
|
|
759 |
# inside citation reference labels underscores dont need to be escaped.
|
|
760 |
self.inside_citation_reference_label = 0
|
|
761 |
|
|
762 |
# Stack of section counters so that we don't have to use_latex_toc.
|
|
763 |
# This will grow and shrink as processing occurs.
|
|
764 |
# Initialized for potential first-level sections.
|
|
765 |
self._section_number = [0]
|
|
766 |
|
|
767 |
# The current stack of enumerations so that we can expand
|
|
768 |
# them into a compound enumeration.
|
|
769 |
self._enumeration_counters = []
|
|
770 |
|
|
771 |
# The maximum number of enumeration counters we've used.
|
|
772 |
# If we go beyond this number, we need to create a new
|
|
773 |
# counter; otherwise, just reuse an old one.
|
|
774 |
self._max_enumeration_counters = 0
|
|
775 |
|
|
776 |
self._bibitems = []
|
|
777 |
|
|
778 |
# docinfo.
|
|
779 |
self.docinfo = None
|
|
780 |
# inside literal block: no quote mangling.
|
|
781 |
self.literal_block = 0
|
|
782 |
self.literal_block_stack = []
|
|
783 |
self.literal = 0
|
|
784 |
# true when encoding in math mode
|
|
785 |
self.mathmode = 0
|
|
786 |
|
|
787 |
def to_latex_encoding(self,docutils_encoding):
|
|
788 |
"""
|
|
789 |
Translate docutils encoding name into latex's.
|
|
790 |
|
|
791 |
Default fallback method is remove "-" and "_" chars from docutils_encoding.
|
|
792 |
|
|
793 |
"""
|
|
794 |
tr = { "iso-8859-1": "latin1", # west european
|
|
795 |
"iso-8859-2": "latin2", # east european
|
|
796 |
"iso-8859-3": "latin3", # esperanto, maltese
|
|
797 |
"iso-8859-4": "latin4", # north european,scandinavian, baltic
|
|
798 |
"iso-8859-5": "iso88595", # cyrillic (ISO)
|
|
799 |
"iso-8859-9": "latin5", # turkish
|
|
800 |
"iso-8859-15": "latin9", # latin9, update to latin1.
|
|
801 |
"mac_cyrillic": "maccyr", # cyrillic (on Mac)
|
|
802 |
"windows-1251": "cp1251", # cyrillic (on Windows)
|
|
803 |
"koi8-r": "koi8-r", # cyrillic (Russian)
|
|
804 |
"koi8-u": "koi8-u", # cyrillic (Ukrainian)
|
|
805 |
"windows-1250": "cp1250", #
|
|
806 |
"windows-1252": "cp1252", #
|
|
807 |
"us-ascii": "ascii", # ASCII (US)
|
|
808 |
# unmatched encodings
|
|
809 |
#"": "applemac",
|
|
810 |
#"": "ansinew", # windows 3.1 ansi
|
|
811 |
#"": "ascii", # ASCII encoding for the range 32--127.
|
|
812 |
#"": "cp437", # dos latine us
|
|
813 |
#"": "cp850", # dos latin 1
|
|
814 |
#"": "cp852", # dos latin 2
|
|
815 |
#"": "decmulti",
|
|
816 |
#"": "latin10",
|
|
817 |
#"iso-8859-6": "" # arabic
|
|
818 |
#"iso-8859-7": "" # greek
|
|
819 |
#"iso-8859-8": "" # hebrew
|
|
820 |
#"iso-8859-10": "" # latin6, more complete iso-8859-4
|
|
821 |
}
|
|
822 |
if tr.has_key(docutils_encoding.lower()):
|
|
823 |
return tr[docutils_encoding.lower()]
|
|
824 |
# convert: latin-1 and utf-8 and similar things
|
|
825 |
return docutils_encoding.replace("_", "").replace("-", "").lower()
|
|
826 |
|
|
827 |
def language_label(self, docutil_label):
|
|
828 |
return self.language.labels[docutil_label]
|
|
829 |
|
|
830 |
latex_equivalents = {
|
|
831 |
u'\u00A0' : '~',
|
|
832 |
u'\u2013' : '{--}',
|
|
833 |
u'\u2014' : '{---}',
|
|
834 |
u'\u2018' : '`',
|
|
835 |
u'\u2019' : '\'',
|
|
836 |
u'\u201A' : ',',
|
|
837 |
u'\u201C' : '``',
|
|
838 |
u'\u201D' : '\'\'',
|
|
839 |
u'\u201E' : ',,',
|
|
840 |
u'\u2020' : '{\\dag}',
|
|
841 |
u'\u2021' : '{\\ddag}',
|
|
842 |
u'\u2026' : '{\\dots}',
|
|
843 |
u'\u2122' : '{\\texttrademark}',
|
|
844 |
u'\u21d4' : '{$\\Leftrightarrow$}',
|
|
845 |
# greek alphabet ?
|
|
846 |
}
|
|
847 |
|
|
848 |
def unicode_to_latex(self,text):
|
|
849 |
# see LaTeX codec
|
|
850 |
# http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/252124
|
|
851 |
# Only some special chracters are translated, for documents with many
|
|
852 |
# utf-8 chars one should use the LaTeX unicode package.
|
|
853 |
for uchar in self.latex_equivalents.keys():
|
|
854 |
text = text.replace(uchar,self.latex_equivalents[uchar])
|
|
855 |
return text
|
|
856 |
|
|
857 |
def ensure_math(self, text):
|
|
858 |
if not self.__dict__.has_key('ensure_math_re'):
|
|
859 |
chars = {
|
|
860 |
# lnot,pm,twosuperior,threesuperior,mu,onesuperior,times,div
|
|
861 |
'latin1' : '\xac\xb1\xb2\xb3\xb5\xb9\xd7\xf7' ,
|
|
862 |
# also latin5 and latin9
|
|
863 |
}
|
|
864 |
self.ensure_math_re = re.compile('([%s])' % chars['latin1'])
|
|
865 |
text = self.ensure_math_re.sub(r'\\ensuremath{\1}', text)
|
|
866 |
return text
|
|
867 |
|
|
868 |
def encode(self, text):
|
|
869 |
"""
|
|
870 |
Encode special characters (``# $ % & ~ _ ^ \ { }``) in `text` & return
|
|
871 |
"""
|
|
872 |
# Escaping with a backslash does not help with backslashes, ~ and ^.
|
|
873 |
|
|
874 |
# < > are only available in math-mode or tt font. (really ?)
|
|
875 |
# $ starts math- mode.
|
|
876 |
# AND quotes
|
|
877 |
if self.verbatim:
|
|
878 |
return text
|
|
879 |
# compile the regexps once. do it here so one can see them.
|
|
880 |
#
|
|
881 |
# first the braces.
|
|
882 |
if not self.__dict__.has_key('encode_re_braces'):
|
|
883 |
self.encode_re_braces = re.compile(r'([{}])')
|
|
884 |
text = self.encode_re_braces.sub(r'{\\\1}',text)
|
|
885 |
if not self.__dict__.has_key('encode_re_bslash'):
|
|
886 |
# find backslash: except in the form '{\{}' or '{\}}'.
|
|
887 |
self.encode_re_bslash = re.compile(r'(?<!{)(\\)(?![{}]})')
|
|
888 |
# then the backslash: except in the form from line above:
|
|
889 |
# either '{\{}' or '{\}}'.
|
|
890 |
text = self.encode_re_bslash.sub(r'{\\textbackslash}', text)
|
|
891 |
|
|
892 |
# then dollar
|
|
893 |
text = text.replace("$", '{\\$}')
|
|
894 |
if not ( self.literal_block or self.literal or self.mathmode ):
|
|
895 |
# the vertical bar: in mathmode |,\vert or \mid
|
|
896 |
# in textmode \textbar
|
|
897 |
text = text.replace("|", '{\\textbar}')
|
|
898 |
text = text.replace("<", '{\\textless}')
|
|
899 |
text = text.replace(">", '{\\textgreater}')
|
|
900 |
# then
|
|
901 |
text = text.replace("&", '{\\&}')
|
|
902 |
# the ^:
|
|
903 |
# * verb|^| does not work in mbox.
|
|
904 |
# * mathmode has wedge. hat{~} would also work.
|
|
905 |
# text = text.replace("^", '{\\ensuremath{^\\wedge}}')
|
|
906 |
text = text.replace("^", '{\\textasciicircum}')
|
|
907 |
text = text.replace("%", '{\\%}')
|
|
908 |
text = text.replace("#", '{\\#}')
|
|
909 |
text = text.replace("~", '{\\textasciitilde}')
|
|
910 |
# Separate compound characters, e.g. "--" to "-{}-". (The
|
|
911 |
# actual separation is done later; see below.)
|
|
912 |
separate_chars = '-'
|
|
913 |
if self.literal_block or self.literal:
|
|
914 |
# In monospace-font, we also separate ",,", "``" and "''"
|
|
915 |
# and some other characters which can't occur in
|
|
916 |
# non-literal text.
|
|
917 |
separate_chars += ',`\'"<>'
|
|
918 |
# pdflatex does not produce doublequotes for ngerman.
|
|
919 |
text = self.babel.double_quotes_in_tt(text)
|
|
920 |
if self.font_encoding == 'OT1':
|
|
921 |
# We're using OT1 font-encoding and have to replace
|
|
922 |
# underscore by underlined blank, because this has
|
|
923 |
# correct width.
|
|
924 |
text = text.replace('_', '{\\underline{ }}')
|
|
925 |
# And the tt-backslash doesn't work in OT1, so we use
|
|
926 |
# a mirrored slash.
|
|
927 |
text = text.replace('\\textbackslash', '\\reflectbox{/}')
|
|
928 |
else:
|
|
929 |
text = text.replace('_', '{\\_}')
|
|
930 |
else:
|
|
931 |
text = self.babel.quote_quotes(text)
|
|
932 |
if not self.inside_citation_reference_label:
|
|
933 |
text = text.replace("_", '{\\_}')
|
|
934 |
for char in separate_chars * 2:
|
|
935 |
# Do it twice ("* 2") becaues otherwise we would replace
|
|
936 |
# "---" by "-{}--".
|
|
937 |
text = text.replace(char + char, char + '{}' + char)
|
|
938 |
if self.insert_newline or self.literal_block:
|
|
939 |
# Insert a blank before the newline, to avoid
|
|
940 |
# ! LaTeX Error: There's no line here to end.
|
|
941 |
text = text.replace("\n", '~\\\\\n')
|
|
942 |
elif self.mbox_newline:
|
|
943 |
if self.literal_block:
|
|
944 |
closings = "}" * len(self.literal_block_stack)
|
|
945 |
openings = "".join(self.literal_block_stack)
|
|
946 |
else:
|
|
947 |
closings = ""
|
|
948 |
openings = ""
|
|
949 |
text = text.replace("\n", "%s}\\\\\n\\mbox{%s" % (closings,openings))
|
|
950 |
text = text.replace('[', '{[}').replace(']', '{]}')
|
|
951 |
if self.insert_none_breaking_blanks:
|
|
952 |
text = text.replace(' ', '~')
|
|
953 |
if self.latex_encoding != 'utf8':
|
|
954 |
text = self.unicode_to_latex(text)
|
|
955 |
text = self.ensure_math(text)
|
|
956 |
return text
|
|
957 |
|
|
958 |
def attval(self, text,
|
|
959 |
whitespace=re.compile('[\n\r\t\v\f]')):
|
|
960 |
"""Cleanse, encode, and return attribute value text."""
|
|
961 |
return self.encode(whitespace.sub(' ', text))
|
|
962 |
|
|
963 |
def astext(self):
|
|
964 |
if self.pdfinfo is not None and self.pdfauthor:
|
|
965 |
self.pdfinfo.append('pdfauthor={%s}' % self.pdfauthor)
|
|
966 |
if self.pdfinfo:
|
|
967 |
pdfinfo = '\\hypersetup{\n' + ',\n'.join(self.pdfinfo) + '\n}\n'
|
|
968 |
else:
|
|
969 |
pdfinfo = ''
|
|
970 |
head = '\\title{%s}\n\\author{%s}\n\\date{%s}\n' % \
|
|
971 |
(self.title,
|
|
972 |
' \\and\n'.join(['~\\\\\n'.join(author_lines)
|
|
973 |
for author_lines in self.author_stack]),
|
|
974 |
self.date)
|
|
975 |
return ''.join(self.head_prefix + [head] + self.head + [pdfinfo]
|
|
976 |
+ self.body_prefix + self.body + self.body_suffix)
|
|
977 |
|
|
978 |
def visit_Text(self, node):
|
|
979 |
self.body.append(self.encode(node.astext()))
|
|
980 |
|
|
981 |
def depart_Text(self, node):
|
|
982 |
pass
|
|
983 |
|
|
984 |
def visit_address(self, node):
|
|
985 |
self.visit_docinfo_item(node, 'address')
|
|
986 |
|
|
987 |
def depart_address(self, node):
|
|
988 |
self.depart_docinfo_item(node)
|
|
989 |
|
|
990 |
def visit_admonition(self, node, name=''):
|
|
991 |
self.body.append('\\begin{center}\\begin{sffamily}\n')
|
|
992 |
self.body.append('\\fbox{\\parbox{\\admonitionwidth}{\n')
|
|
993 |
if name:
|
|
994 |
self.body.append('\\textbf{\\large '+ self.language.labels[name] + '}\n');
|
|
995 |
self.body.append('\\vspace{2mm}\n')
|
|
996 |
|
|
997 |
|
|
998 |
def depart_admonition(self, node=None):
|
|
999 |
self.body.append('}}\n') # end parbox fbox
|
|
1000 |
self.body.append('\\end{sffamily}\n\\end{center}\n');
|
|
1001 |
|
|
1002 |
def visit_attention(self, node):
|
|
1003 |
self.visit_admonition(node, 'attention')
|
|
1004 |
|
|
1005 |
def depart_attention(self, node):
|
|
1006 |
self.depart_admonition()
|
|
1007 |
|
|
1008 |
def visit_author(self, node):
|
|
1009 |
self.visit_docinfo_item(node, 'author')
|
|
1010 |
|
|
1011 |
def depart_author(self, node):
|
|
1012 |
self.depart_docinfo_item(node)
|
|
1013 |
|
|
1014 |
def visit_authors(self, node):
|
|
1015 |
# not used: visit_author is called anyway for each author.
|
|
1016 |
pass
|
|
1017 |
|
|
1018 |
def depart_authors(self, node):
|
|
1019 |
pass
|
|
1020 |
|
|
1021 |
def visit_block_quote(self, node):
|
|
1022 |
self.body.append( '\\begin{quote}\n')
|
|
1023 |
|
|
1024 |
def depart_block_quote(self, node):
|
|
1025 |
self.body.append( '\\end{quote}\n')
|
|
1026 |
|
|
1027 |
def visit_bullet_list(self, node):
|
|
1028 |
if 'contents' in self.topic_classes:
|
|
1029 |
if self.use_latex_toc:
|
|
1030 |
raise nodes.SkipNode
|
|
1031 |
self.body.append( '\\begin{list}{}{}\n' )
|
|
1032 |
else:
|
|
1033 |
self.body.append( '\\begin{itemize}\n' )
|
|
1034 |
|
|
1035 |
def depart_bullet_list(self, node):
|
|
1036 |
if 'contents' in self.topic_classes:
|
|
1037 |
self.body.append( '\\end{list}\n' )
|
|
1038 |
else:
|
|
1039 |
self.body.append( '\\end{itemize}\n' )
|
|
1040 |
|
|
1041 |
# Imperfect superscript/subscript handling: mathmode italicizes
|
|
1042 |
# all letters by default.
|
|
1043 |
def visit_superscript(self, node):
|
|
1044 |
self.body.append('$^{')
|
|
1045 |
self.mathmode = 1
|
|
1046 |
|
|
1047 |
def depart_superscript(self, node):
|
|
1048 |
self.body.append('}$')
|
|
1049 |
self.mathmode = 0
|
|
1050 |
|
|
1051 |
def visit_subscript(self, node):
|
|
1052 |
self.body.append('$_{')
|
|
1053 |
self.mathmode = 1
|
|
1054 |
|
|
1055 |
def depart_subscript(self, node):
|
|
1056 |
self.body.append('}$')
|
|
1057 |
self.mathmode = 0
|
|
1058 |
|
|
1059 |
def visit_caption(self, node):
|
|
1060 |
self.body.append( '\\caption{' )
|
|
1061 |
|
|
1062 |
def depart_caption(self, node):
|
|
1063 |
self.body.append('}')
|
|
1064 |
|
|
1065 |
def visit_caution(self, node):
|
|
1066 |
self.visit_admonition(node, 'caution')
|
|
1067 |
|
|
1068 |
def depart_caution(self, node):
|
|
1069 |
self.depart_admonition()
|
|
1070 |
|
|
1071 |
def visit_title_reference(self, node):
|
|
1072 |
self.body.append( '\\titlereference{' )
|
|
1073 |
|
|
1074 |
def depart_title_reference(self, node):
|
|
1075 |
self.body.append( '}' )
|
|
1076 |
|
|
1077 |
def visit_citation(self, node):
|
|
1078 |
# TODO maybe use cite bibitems
|
|
1079 |
if self._use_latex_citations:
|
|
1080 |
self.context.append(len(self.body))
|
|
1081 |
else:
|
|
1082 |
self.body.append('\\begin{figure}[b]')
|
|
1083 |
for id in node['ids']:
|
|
1084 |
self.body.append('\\hypertarget{%s}' % id)
|
|
1085 |
|
|
1086 |
def depart_citation(self, node):
|
|
1087 |
if self._use_latex_citations:
|
|
1088 |
size = self.context.pop()
|
|
1089 |
label = self.body[size]
|
|
1090 |
text = ''.join(self.body[size+1:])
|
|
1091 |
del self.body[size:]
|
|
1092 |
self._bibitems.append([label, text])
|
|
1093 |
else:
|
|
1094 |
self.body.append('\\end{figure}\n')
|
|
1095 |
|
|
1096 |
def visit_citation_reference(self, node):
|
|
1097 |
if self._use_latex_citations:
|
|
1098 |
self.body.append('\\cite{')
|
|
1099 |
self.inside_citation_reference_label = 1
|
|
1100 |
else:
|
|
1101 |
href = ''
|
|
1102 |
if node.has_key('refid'):
|
|
1103 |
href = node['refid']
|
|
1104 |
elif node.has_key('refname'):
|
|
1105 |
href = self.document.nameids[node['refname']]
|
|
1106 |
self.body.append('[\\hyperlink{%s}{' % href)
|
|
1107 |
|
|
1108 |
def depart_citation_reference(self, node):
|
|
1109 |
if self._use_latex_citations:
|
|
1110 |
self.body.append('}')
|
|
1111 |
self.inside_citation_reference_label = 0
|
|
1112 |
else:
|
|
1113 |
self.body.append('}]')
|
|
1114 |
|
|
1115 |
def visit_classifier(self, node):
|
|
1116 |
self.body.append( '(\\textbf{' )
|
|
1117 |
|
|
1118 |
def depart_classifier(self, node):
|
|
1119 |
self.body.append( '})\n' )
|
|
1120 |
|
|
1121 |
def visit_colspec(self, node):
|
|
1122 |
self.active_table.visit_colspec(node)
|
|
1123 |
|
|
1124 |
def depart_colspec(self, node):
|
|
1125 |
pass
|
|
1126 |
|
|
1127 |
def visit_comment(self, node):
|
|
1128 |
# Escape end of line by a new comment start in comment text.
|
|
1129 |
self.body.append('%% %s \n' % node.astext().replace('\n', '\n% '))
|
|
1130 |
raise nodes.SkipNode
|
|
1131 |
|
|
1132 |
def visit_compound(self, node):
|
|
1133 |
pass
|
|
1134 |
|
|
1135 |
def depart_compound(self, node):
|
|
1136 |
pass
|
|
1137 |
|
|
1138 |
def visit_contact(self, node):
|
|
1139 |
self.visit_docinfo_item(node, 'contact')
|
|
1140 |
|
|
1141 |
def depart_contact(self, node):
|
|
1142 |
self.depart_docinfo_item(node)
|
|
1143 |
|
|
1144 |
def visit_container(self, node):
|
|
1145 |
pass
|
|
1146 |
|
|
1147 |
def depart_container(self, node):
|
|
1148 |
pass
|
|
1149 |
|
|
1150 |
def visit_copyright(self, node):
|
|
1151 |
self.visit_docinfo_item(node, 'copyright')
|
|
1152 |
|
|
1153 |
def depart_copyright(self, node):
|
|
1154 |
self.depart_docinfo_item(node)
|
|
1155 |
|
|
1156 |
def visit_danger(self, node):
|
|
1157 |
self.visit_admonition(node, 'danger')
|
|
1158 |
|
|
1159 |
def depart_danger(self, node):
|
|
1160 |
self.depart_admonition()
|
|
1161 |
|
|
1162 |
def visit_date(self, node):
|
|
1163 |
self.visit_docinfo_item(node, 'date')
|
|
1164 |
|
|
1165 |
def depart_date(self, node):
|
|
1166 |
self.depart_docinfo_item(node)
|
|
1167 |
|
|
1168 |
def visit_decoration(self, node):
|
|
1169 |
pass
|
|
1170 |
|
|
1171 |
def depart_decoration(self, node):
|
|
1172 |
pass
|
|
1173 |
|
|
1174 |
def visit_definition(self, node):
|
|
1175 |
pass
|
|
1176 |
|
|
1177 |
def depart_definition(self, node):
|
|
1178 |
self.body.append('\n')
|
|
1179 |
|
|
1180 |
def visit_definition_list(self, node):
|
|
1181 |
self.body.append( '\\begin{description}\n' )
|
|
1182 |
|
|
1183 |
def depart_definition_list(self, node):
|
|
1184 |
self.body.append( '\\end{description}\n' )
|
|
1185 |
|
|
1186 |
def visit_definition_list_item(self, node):
|
|
1187 |
pass
|
|
1188 |
|
|
1189 |
def depart_definition_list_item(self, node):
|
|
1190 |
pass
|
|
1191 |
|
|
1192 |
def visit_description(self, node):
|
|
1193 |
self.body.append( ' ' )
|
|
1194 |
|
|
1195 |
def depart_description(self, node):
|
|
1196 |
pass
|
|
1197 |
|
|
1198 |
def visit_docinfo(self, node):
|
|
1199 |
self.docinfo = []
|
|
1200 |
self.docinfo.append('%' + '_'*75 + '\n')
|
|
1201 |
self.docinfo.append('\\begin{center}\n')
|
|
1202 |
self.docinfo.append('\\begin{tabularx}{\\docinfowidth}{lX}\n')
|
|
1203 |
|
|
1204 |
def depart_docinfo(self, node):
|
|
1205 |
self.docinfo.append('\\end{tabularx}\n')
|
|
1206 |
self.docinfo.append('\\end{center}\n')
|
|
1207 |
self.body = self.docinfo + self.body
|
|
1208 |
# clear docinfo, so field names are no longer appended.
|
|
1209 |
self.docinfo = None
|
|
1210 |
|
|
1211 |
def visit_docinfo_item(self, node, name):
|
|
1212 |
if name == 'author':
|
|
1213 |
if not self.pdfinfo == None:
|
|
1214 |
if not self.pdfauthor:
|
|
1215 |
self.pdfauthor = self.attval(node.astext())
|
|
1216 |
else:
|
|
1217 |
self.pdfauthor += self.author_separator + self.attval(node.astext())
|
|
1218 |
if self.use_latex_docinfo:
|
|
1219 |
if name in ('author', 'organization', 'contact', 'address'):
|
|
1220 |
# We attach these to the last author. If any of them precedes
|
|
1221 |
# the first author, put them in a separate "author" group (for
|
|
1222 |
# no better semantics).
|
|
1223 |
if name == 'author' or not self.author_stack:
|
|
1224 |
self.author_stack.append([])
|
|
1225 |
if name == 'address': # newlines are meaningful
|
|
1226 |
self.insert_newline = 1
|
|
1227 |
text = self.encode(node.astext())
|
|
1228 |
self.insert_newline = 0
|
|
1229 |
else:
|
|
1230 |
text = self.attval(node.astext())
|
|
1231 |
self.author_stack[-1].append(text)
|
|
1232 |
raise nodes.SkipNode
|
|
1233 |
elif name == 'date':
|
|
1234 |
self.date = self.attval(node.astext())
|
|
1235 |
raise nodes.SkipNode
|
|
1236 |
self.docinfo.append('\\textbf{%s}: &\n\t' % self.language_label(name))
|
|
1237 |
if name == 'address':
|
|
1238 |
self.insert_newline = 1
|
|
1239 |
self.docinfo.append('{\\raggedright\n')
|
|
1240 |
self.context.append(' } \\\\\n')
|
|
1241 |
else:
|
|
1242 |
self.context.append(' \\\\\n')
|
|
1243 |
self.context.append(self.docinfo)
|
|
1244 |
self.context.append(len(self.body))
|
|
1245 |
|
|
1246 |
def depart_docinfo_item(self, node):
|
|
1247 |
size = self.context.pop()
|
|
1248 |
dest = self.context.pop()
|
|
1249 |
tail = self.context.pop()
|
|
1250 |
tail = self.body[size:] + [tail]
|
|
1251 |
del self.body[size:]
|
|
1252 |
dest.extend(tail)
|
|
1253 |
# for address we did set insert_newline
|
|
1254 |
self.insert_newline = 0
|
|
1255 |
|
|
1256 |
def visit_doctest_block(self, node):
|
|
1257 |
self.body.append( '\\begin{verbatim}' )
|
|
1258 |
self.verbatim = 1
|
|
1259 |
|
|
1260 |
def depart_doctest_block(self, node):
|
|
1261 |
self.body.append( '\\end{verbatim}\n' )
|
|
1262 |
self.verbatim = 0
|
|
1263 |
|
|
1264 |
def visit_document(self, node):
|
|
1265 |
self.body_prefix.append('\\begin{document}\n')
|
|
1266 |
# titled document?
|
|
1267 |
if self.use_latex_docinfo or len(node) and isinstance(node[0], nodes.title):
|
|
1268 |
self.body_prefix.append('\\maketitle\n')
|
|
1269 |
# alternative use titlepage environment.
|
|
1270 |
# \begin{titlepage}
|
|
1271 |
# ...
|
|
1272 |
self.body.append('\n\\setlength{\\locallinewidth}{\\linewidth}\n')
|
|
1273 |
|
|
1274 |
def depart_document(self, node):
|
|
1275 |
# TODO insertion point of bibliography should none automatic.
|
|
1276 |
if self._use_latex_citations and len(self._bibitems)>0:
|
|
1277 |
if not self.bibtex:
|
|
1278 |
widest_label = ""
|
|
1279 |
for bi in self._bibitems:
|
|
1280 |
if len(widest_label)<len(bi[0]):
|
|
1281 |
widest_label = bi[0]
|
|
1282 |
self.body.append('\n\\begin{thebibliography}{%s}\n'%widest_label)
|
|
1283 |
for bi in self._bibitems:
|
|
1284 |
# cite_key: underscores must not be escaped
|
|
1285 |
cite_key = bi[0].replace(r"{\_}","_")
|
|
1286 |
self.body.append('\\bibitem[%s]{%s}{%s}\n' % (bi[0], cite_key, bi[1]))
|
|
1287 |
self.body.append('\\end{thebibliography}\n')
|
|
1288 |
else:
|
|
1289 |
self.body.append('\n\\bibliographystyle{%s}\n' % self.bibtex[0])
|
|
1290 |
self.body.append('\\bibliography{%s}\n' % self.bibtex[1])
|
|
1291 |
|
|
1292 |
self.body_suffix.append('\\end{document}\n')
|
|
1293 |
|
|
1294 |
def visit_emphasis(self, node):
|
|
1295 |
self.body.append('\\emph{')
|
|
1296 |
self.literal_block_stack.append('\\emph{')
|
|
1297 |
|
|
1298 |
def depart_emphasis(self, node):
|
|
1299 |
self.body.append('}')
|
|
1300 |
self.literal_block_stack.pop()
|
|
1301 |
|
|
1302 |
def visit_entry(self, node):
|
|
1303 |
self.active_table.visit_entry()
|
|
1304 |
# cell separation
|
|
1305 |
if self.active_table.get_entry_number() == 1:
|
|
1306 |
# if the firstrow is a multirow, this actually is the second row.
|
|
1307 |
# this gets hairy if rowspans follow each other.
|
|
1308 |
if self.active_table.get_rowspan(0):
|
|
1309 |
count = 0
|
|
1310 |
while self.active_table.get_rowspan(count):
|
|
1311 |
count += 1
|
|
1312 |
self.body.append(' & ')
|
|
1313 |
self.active_table.visit_entry() # increment cell count
|
|
1314 |
else:
|
|
1315 |
self.body.append(' & ')
|
|
1316 |
|
|
1317 |
# multi{row,column}
|
|
1318 |
# IN WORK BUG TODO HACK continues here
|
|
1319 |
# multirow in LaTeX simply will enlarge the cell over several rows
|
|
1320 |
# (the following n if n is positive, the former if negative).
|
|
1321 |
if node.has_key('morerows') and node.has_key('morecols'):
|
|
1322 |
raise NotImplementedError('Cells that '
|
|
1323 |
'span multiple rows *and* columns are not supported, sorry.')
|
|
1324 |
if node.has_key('morerows'):
|
|
1325 |
count = node['morerows'] + 1
|
|
1326 |
self.active_table.set_rowspan(self.active_table.get_entry_number()-1,count)
|
|
1327 |
self.body.append('\\multirow{%d}{%s}{' % \
|
|
1328 |
(count,self.active_table.get_column_width()))
|
|
1329 |
self.context.append('}')
|
|
1330 |
# BUG following rows must have empty cells.
|
|
1331 |
elif node.has_key('morecols'):
|
|
1332 |
# the vertical bar before column is missing if it is the first column.
|
|
1333 |
# the one after always.
|
|
1334 |
if self.active_table.get_entry_number() == 1:
|
|
1335 |
bar1 = self.active_table.get_vertical_bar()
|
|
1336 |
else:
|
|
1337 |
bar1 = ''
|
|
1338 |
count = node['morecols'] + 1
|
|
1339 |
self.body.append('\\multicolumn{%d}{%sl%s}{' % \
|
|
1340 |
(count, bar1, self.active_table.get_vertical_bar()))
|
|
1341 |
self.context.append('}')
|
|
1342 |
else:
|
|
1343 |
self.context.append('')
|
|
1344 |
|
|
1345 |
# header / not header
|
|
1346 |
if isinstance(node.parent.parent, nodes.thead):
|
|
1347 |
self.body.append('\\textbf{')
|
|
1348 |
self.context.append('}')
|
|
1349 |
elif self.active_table.is_stub_column():
|
|
1350 |
self.body.append('\\textbf{')
|
|
1351 |
self.context.append('}')
|
|
1352 |
else:
|
|
1353 |
self.context.append('')
|
|
1354 |
|
|
1355 |
def depart_entry(self, node):
|
|
1356 |
self.body.append(self.context.pop()) # header / not header
|
|
1357 |
self.body.append(self.context.pop()) # multirow/column
|
|
1358 |
# if following row is spanned from above.
|
|
1359 |
if self.active_table.get_rowspan(self.active_table.get_entry_number()):
|
|
1360 |
self.body.append(' & ')
|
|
1361 |
self.active_table.visit_entry() # increment cell count
|
|
1362 |
|
|
1363 |
def visit_row(self, node):
|
|
1364 |
self.active_table.visit_row()
|
|
1365 |
|
|
1366 |
def depart_row(self, node):
|
|
1367 |
self.body.extend(self.active_table.depart_row())
|
|
1368 |
|
|
1369 |
def visit_enumerated_list(self, node):
|
|
1370 |
# We create our own enumeration list environment.
|
|
1371 |
# This allows to set the style and starting value
|
|
1372 |
# and unlimited nesting.
|
|
1373 |
enum_style = {'arabic':'arabic',
|
|
1374 |
'loweralpha':'alph',
|
|
1375 |
'upperalpha':'Alph',
|
|
1376 |
'lowerroman':'roman',
|
|
1377 |
'upperroman':'Roman' }
|
|
1378 |
enum_suffix = ""
|
|
1379 |
if node.has_key('suffix'):
|
|
1380 |
enum_suffix = node['suffix']
|
|
1381 |
enum_prefix = ""
|
|
1382 |
if node.has_key('prefix'):
|
|
1383 |
enum_prefix = node['prefix']
|
|
1384 |
if self.compound_enumerators:
|
|
1385 |
pref = ""
|
|
1386 |
if self.section_prefix_for_enumerators and self.section_level:
|
|
1387 |
for i in range(self.section_level):
|
|
1388 |
pref += '%d.' % self._section_number[i]
|
|
1389 |
pref = pref[:-1] + self.section_enumerator_separator
|
|
1390 |
enum_prefix += pref
|
|
1391 |
for ctype, cname in self._enumeration_counters:
|
|
1392 |
enum_prefix += '\\%s{%s}.' % (ctype, cname)
|
|
1393 |
enum_type = "arabic"
|
|
1394 |
if node.has_key('enumtype'):
|
|
1395 |
enum_type = node['enumtype']
|
|
1396 |
if enum_style.has_key(enum_type):
|
|
1397 |
enum_type = enum_style[enum_type]
|
|
1398 |
|
|
1399 |
counter_name = "listcnt%d" % len(self._enumeration_counters)
|
|
1400 |
self._enumeration_counters.append((enum_type, counter_name))
|
|
1401 |
# If we haven't used this counter name before, then create a
|
|
1402 |
# new counter; otherwise, reset & reuse the old counter.
|
|
1403 |
if len(self._enumeration_counters) > self._max_enumeration_counters:
|
|
1404 |
self._max_enumeration_counters = len(self._enumeration_counters)
|
|
1405 |
self.body.append('\\newcounter{%s}\n' % counter_name)
|
|
1406 |
else:
|
|
1407 |
self.body.append('\\setcounter{%s}{0}\n' % counter_name)
|
|
1408 |
|
|
1409 |
self.body.append('\\begin{list}{%s\\%s{%s}%s}\n' % \
|
|
1410 |
(enum_prefix,enum_type,counter_name,enum_suffix))
|
|
1411 |
self.body.append('{\n')
|
|
1412 |
self.body.append('\\usecounter{%s}\n' % counter_name)
|
|
1413 |
# set start after usecounter, because it initializes to zero.
|
|
1414 |
if node.has_key('start'):
|
|
1415 |
self.body.append('\\addtocounter{%s}{%d}\n' \
|
|
1416 |
% (counter_name,node['start']-1))
|
|
1417 |
## set rightmargin equal to leftmargin
|
|
1418 |
self.body.append('\\setlength{\\rightmargin}{\\leftmargin}\n')
|
|
1419 |
self.body.append('}\n')
|
|
1420 |
|
|
1421 |
def depart_enumerated_list(self, node):
|
|
1422 |
self.body.append('\\end{list}\n')
|
|
1423 |
self._enumeration_counters.pop()
|
|
1424 |
|
|
1425 |
def visit_error(self, node):
|
|
1426 |
self.visit_admonition(node, 'error')
|
|
1427 |
|
|
1428 |
def depart_error(self, node):
|
|
1429 |
self.depart_admonition()
|
|
1430 |
|
|
1431 |
def visit_field(self, node):
|
|
1432 |
# real output is done in siblings: _argument, _body, _name
|
|
1433 |
pass
|
|
1434 |
|
|
1435 |
def depart_field(self, node):
|
|
1436 |
self.body.append('\n')
|
|
1437 |
##self.body.append('%[depart_field]\n')
|
|
1438 |
|
|
1439 |
def visit_field_argument(self, node):
|
|
1440 |
self.body.append('%[visit_field_argument]\n')
|
|
1441 |
|
|
1442 |
def depart_field_argument(self, node):
|
|
1443 |
self.body.append('%[depart_field_argument]\n')
|
|
1444 |
|
|
1445 |
def visit_field_body(self, node):
|
|
1446 |
# BUG by attach as text we loose references.
|
|
1447 |
if self.docinfo:
|
|
1448 |
self.docinfo.append('%s \\\\\n' % self.encode(node.astext()))
|
|
1449 |
raise nodes.SkipNode
|
|
1450 |
# BUG: what happens if not docinfo
|
|
1451 |
|
|
1452 |
def depart_field_body(self, node):
|
|
1453 |
self.body.append( '\n' )
|
|
1454 |
|
|
1455 |
def visit_field_list(self, node):
|
|
1456 |
if not self.docinfo:
|
|
1457 |
self.body.append('\\begin{quote}\n')
|
|
1458 |
self.body.append('\\begin{description}\n')
|
|
1459 |
|
|
1460 |
def depart_field_list(self, node):
|
|
1461 |
if not self.docinfo:
|
|
1462 |
self.body.append('\\end{description}\n')
|
|
1463 |
self.body.append('\\end{quote}\n')
|
|
1464 |
|
|
1465 |
def visit_field_name(self, node):
|
|
1466 |
# BUG this duplicates docinfo_item
|
|
1467 |
if self.docinfo:
|
|
1468 |
self.docinfo.append('\\textbf{%s}: &\n\t' % self.encode(node.astext()))
|
|
1469 |
raise nodes.SkipNode
|
|
1470 |
else:
|
|
1471 |
self.body.append('\\item [')
|
|
1472 |
|
|
1473 |
def depart_field_name(self, node):
|
|
1474 |
if not self.docinfo:
|
|
1475 |
self.body.append(':]')
|
|
1476 |
|
|
1477 |
def visit_figure(self, node):
|
|
1478 |
if (not node.attributes.has_key('align') or
|
|
1479 |
node.attributes['align'] == 'center'):
|
|
1480 |
# centering does not add vertical space like center.
|
|
1481 |
align = '\n\\centering'
|
|
1482 |
align_end = ''
|
|
1483 |
else:
|
|
1484 |
# TODO non vertical space for other alignments.
|
|
1485 |
align = '\\begin{flush%s}' % node.attributes['align']
|
|
1486 |
align_end = '\\end{flush%s}' % node.attributes['align']
|
|
1487 |
self.body.append( '\\begin{figure}[htbp]%s\n' % align )
|
|
1488 |
self.context.append( '%s\\end{figure}\n' % align_end )
|
|
1489 |
|
|
1490 |
def depart_figure(self, node):
|
|
1491 |
self.body.append( self.context.pop() )
|
|
1492 |
|
|
1493 |
def visit_footer(self, node):
|
|
1494 |
self.context.append(len(self.body))
|
|
1495 |
|
|
1496 |
def depart_footer(self, node):
|
|
1497 |
start = self.context.pop()
|
|
1498 |
footer = (['\n\\begin{center}\small\n']
|
|
1499 |
+ self.body[start:] + ['\n\\end{center}\n'])
|
|
1500 |
self.body_suffix[:0] = footer
|
|
1501 |
del self.body[start:]
|
|
1502 |
|
|
1503 |
def visit_footnote(self, node):
|
|
1504 |
if self.use_latex_footnotes:
|
|
1505 |
num,text = node.astext().split(None,1)
|
|
1506 |
num = self.encode(num.strip())
|
|
1507 |
self.body.append('\\footnotetext['+num+']')
|
|
1508 |
self.body.append('{')
|
|
1509 |
else:
|
|
1510 |
self.body.append('\\begin{figure}[b]')
|
|
1511 |
for id in node['ids']:
|
|
1512 |
self.body.append('\\hypertarget{%s}' % id)
|
|
1513 |
|
|
1514 |
def depart_footnote(self, node):
|
|
1515 |
if self.use_latex_footnotes:
|
|
1516 |
self.body.append('}\n')
|
|
1517 |
else:
|
|
1518 |
self.body.append('\\end{figure}\n')
|
|
1519 |
|
|
1520 |
def visit_footnote_reference(self, node):
|
|
1521 |
if self.use_latex_footnotes:
|
|
1522 |
self.body.append("\\footnotemark["+self.encode(node.astext())+"]")
|
|
1523 |
raise nodes.SkipNode
|
|
1524 |
href = ''
|
|
1525 |
if node.has_key('refid'):
|
|
1526 |
href = node['refid']
|
|
1527 |
elif node.has_key('refname'):
|
|
1528 |
href = self.document.nameids[node['refname']]
|
|
1529 |
format = self.settings.footnote_references
|
|
1530 |
if format == 'brackets':
|
|
1531 |
suffix = '['
|
|
1532 |
self.context.append(']')
|
|
1533 |
elif format == 'superscript':
|
|
1534 |
suffix = '\\raisebox{.5em}[0em]{\\scriptsize'
|
|
1535 |
self.context.append('}')
|
|
1536 |
else: # shouldn't happen
|
|
1537 |
raise AssertionError('Illegal footnote reference format.')
|
|
1538 |
self.body.append('%s\\hyperlink{%s}{' % (suffix,href))
|
|
1539 |
|
|
1540 |
def depart_footnote_reference(self, node):
|
|
1541 |
if self.use_latex_footnotes:
|
|
1542 |
return
|
|
1543 |
self.body.append('}%s' % self.context.pop())
|
|
1544 |
|
|
1545 |
# footnote/citation label
|
|
1546 |
def label_delim(self, node, bracket, superscript):
|
|
1547 |
if isinstance(node.parent, nodes.footnote):
|
|
1548 |
if self.use_latex_footnotes:
|
|
1549 |
raise nodes.SkipNode
|
|
1550 |
if self.settings.footnote_references == 'brackets':
|
|
1551 |
self.body.append(bracket)
|
|
1552 |
else:
|
|
1553 |
self.body.append(superscript)
|
|
1554 |
else:
|
|
1555 |
assert isinstance(node.parent, nodes.citation)
|
|
1556 |
if not self._use_latex_citations:
|
|
1557 |
self.body.append(bracket)
|
|
1558 |
|
|
1559 |
def visit_label(self, node):
|
|
1560 |
self.label_delim(node, '[', '$^{')
|
|
1561 |
|
|
1562 |
def depart_label(self, node):
|
|
1563 |
self.label_delim(node, ']', '}$')
|
|
1564 |
|
|
1565 |
# elements generated by the framework e.g. section numbers.
|
|
1566 |
def visit_generated(self, node):
|
|
1567 |
pass
|
|
1568 |
|
|
1569 |
def depart_generated(self, node):
|
|
1570 |
pass
|
|
1571 |
|
|
1572 |
def visit_header(self, node):
|
|
1573 |
self.context.append(len(self.body))
|
|
1574 |
|
|
1575 |
def depart_header(self, node):
|
|
1576 |
start = self.context.pop()
|
|
1577 |
self.body_prefix.append('\n\\verb|begin_header|\n')
|
|
1578 |
self.body_prefix.extend(self.body[start:])
|
|
1579 |
self.body_prefix.append('\n\\verb|end_header|\n')
|
|
1580 |
del self.body[start:]
|
|
1581 |
|
|
1582 |
def visit_hint(self, node):
|
|
1583 |
self.visit_admonition(node, 'hint')
|
|
1584 |
|
|
1585 |
def depart_hint(self, node):
|
|
1586 |
self.depart_admonition()
|
|
1587 |
|
|
1588 |
def latex_image_length(self, width_str):
|
|
1589 |
match = re.match('(\d*\.?\d*)\s*(\S*)', width_str)
|
|
1590 |
if not match:
|
|
1591 |
# fallback
|
|
1592 |
return width_str
|
|
1593 |
res = width_str
|
|
1594 |
amount, unit = match.groups()[:2]
|
|
1595 |
if unit == "px":
|
|
1596 |
# LaTeX does not know pixels but points
|
|
1597 |
res = "%spt" % amount
|
|
1598 |
elif unit == "%":
|
|
1599 |
res = "%.3f\\linewidth" % (float(amount)/100.0)
|
|
1600 |
return res
|
|
1601 |
|
|
1602 |
def visit_image(self, node):
|
|
1603 |
attrs = node.attributes
|
|
1604 |
# Add image URI to dependency list, assuming that it's
|
|
1605 |
# referring to a local file.
|
|
1606 |
self.settings.record_dependencies.add(attrs['uri'])
|
|
1607 |
pre = [] # in reverse order
|
|
1608 |
post = []
|
|
1609 |
include_graphics_options = []
|
|
1610 |
inline = isinstance(node.parent, nodes.TextElement)
|
|
1611 |
if attrs.has_key('scale'):
|
|
1612 |
# Could also be done with ``scale`` option to
|
|
1613 |
# ``\includegraphics``; doing it this way for consistency.
|
|
1614 |
pre.append('\\scalebox{%f}{' % (attrs['scale'] / 100.0,))
|
|
1615 |
post.append('}')
|
|
1616 |
if attrs.has_key('width'):
|
|
1617 |
include_graphics_options.append('width=%s' % (
|
|
1618 |
self.latex_image_length(attrs['width']), ))
|
|
1619 |
if attrs.has_key('height'):
|
|
1620 |
include_graphics_options.append('height=%s' % (
|
|
1621 |
self.latex_image_length(attrs['height']), ))
|
|
1622 |
if attrs.has_key('align'):
|
|
1623 |
align_prepost = {
|
|
1624 |
# By default latex aligns the top of an image.
|
|
1625 |
(1, 'top'): ('', ''),
|
|
1626 |
(1, 'middle'): ('\\raisebox{-0.5\\height}{', '}'),
|
|
1627 |
(1, 'bottom'): ('\\raisebox{-\\height}{', '}'),
|
|
1628 |
(0, 'center'): ('{\\hfill', '\\hfill}'),
|
|
1629 |
# These 2 don't exactly do the right thing. The image should
|
|
1630 |
# be floated alongside the paragraph. See
|
|
1631 |
# http://www.w3.org/TR/html4/struct/objects.html#adef-align-IMG
|
|
1632 |
(0, 'left'): ('{', '\\hfill}'),
|
|
1633 |
(0, 'right'): ('{\\hfill', '}'),}
|
|
1634 |
try:
|
|
1635 |
pre.append(align_prepost[inline, attrs['align']][0])
|
|
1636 |
post.append(align_prepost[inline, attrs['align']][1])
|
|
1637 |
except KeyError:
|
|
1638 |
pass # XXX complain here?
|
|
1639 |
if not inline:
|
|
1640 |
pre.append('\n')
|
|
1641 |
post.append('\n')
|
|
1642 |
pre.reverse()
|
|
1643 |
self.body.extend( pre )
|
|
1644 |
options = ''
|
|
1645 |
if len(include_graphics_options)>0:
|
|
1646 |
options = '[%s]' % (','.join(include_graphics_options))
|
|
1647 |
self.body.append( '\\includegraphics%s{%s}' % (
|
|
1648 |
options, attrs['uri'] ) )
|
|
1649 |
self.body.extend( post )
|
|
1650 |
|
|
1651 |
def depart_image(self, node):
|
|
1652 |
pass
|
|
1653 |
|
|
1654 |
def visit_important(self, node):
|
|
1655 |
self.visit_admonition(node, 'important')
|
|
1656 |
|
|
1657 |
def depart_important(self, node):
|
|
1658 |
self.depart_admonition()
|
|
1659 |
|
|
1660 |
def visit_interpreted(self, node):
|
|
1661 |
# @@@ Incomplete, pending a proper implementation on the
|
|
1662 |
# Parser/Reader end.
|
|
1663 |
self.visit_literal(node)
|
|
1664 |
|
|
1665 |
def depart_interpreted(self, node):
|
|
1666 |
self.depart_literal(node)
|
|
1667 |
|
|
1668 |
def visit_legend(self, node):
|
|
1669 |
self.body.append('{\\small ')
|
|
1670 |
|
|
1671 |
def depart_legend(self, node):
|
|
1672 |
self.body.append('}')
|
|
1673 |
|
|
1674 |
def visit_line(self, node):
|
|
1675 |
self.body.append('\item[] ')
|
|
1676 |
|
|
1677 |
def depart_line(self, node):
|
|
1678 |
self.body.append('\n')
|
|
1679 |
|
|
1680 |
def visit_line_block(self, node):
|
|
1681 |
if isinstance(node.parent, nodes.line_block):
|
|
1682 |
self.body.append('\\item[] \n'
|
|
1683 |
'\\begin{lineblock}{\\lineblockindentation}\n')
|
|
1684 |
else:
|
|
1685 |
self.body.append('\n\\begin{lineblock}{0em}\n')
|
|
1686 |
|
|
1687 |
def depart_line_block(self, node):
|
|
1688 |
self.body.append('\\end{lineblock}\n')
|
|
1689 |
|
|
1690 |
def visit_list_item(self, node):
|
|
1691 |
# Append "{}" in case the next character is "[", which would break
|
|
1692 |
# LaTeX's list environment (no numbering and the "[" is not printed).
|
|
1693 |
self.body.append('\\item {} ')
|
|
1694 |
|
|
1695 |
def depart_list_item(self, node):
|
|
1696 |
self.body.append('\n')
|
|
1697 |
|
|
1698 |
def visit_literal(self, node):
|
|
1699 |
self.literal = 1
|
|
1700 |
self.body.append('\\texttt{')
|
|
1701 |
|
|
1702 |
def depart_literal(self, node):
|
|
1703 |
self.body.append('}')
|
|
1704 |
self.literal = 0
|
|
1705 |
|
|
1706 |
def visit_literal_block(self, node):
|
|
1707 |
"""
|
|
1708 |
Render a literal-block.
|
|
1709 |
|
|
1710 |
Literal blocks are used for "::"-prefixed literal-indented
|
|
1711 |
blocks of text, where the inline markup is not recognized,
|
|
1712 |
but are also the product of the parsed-literal directive,
|
|
1713 |
where the markup is respected.
|
|
1714 |
"""
|
|
1715 |
# In both cases, we want to use a typewriter/monospaced typeface.
|
|
1716 |
# For "real" literal-blocks, we can use \verbatim, while for all
|
|
1717 |
# the others we must use \mbox.
|
|
1718 |
#
|
|
1719 |
# We can distinguish between the two kinds by the number of
|
|
1720 |
# siblings that compose this node: if it is composed by a
|
|
1721 |
# single element, it's surely either a real one or a
|
|
1722 |
# parsed-literal that does not contain any markup.
|
|
1723 |
#
|
|
1724 |
if not self.active_table.is_open():
|
|
1725 |
# no quote inside tables, to avoid vertical space between
|
|
1726 |
# table border and literal block.
|
|
1727 |
# BUG: fails if normal text preceeds the literal block.
|
|
1728 |
self.body.append('\\begin{quote}')
|
|
1729 |
self.context.append('\\end{quote}\n')
|
|
1730 |
else:
|
|
1731 |
self.body.append('\n')
|
|
1732 |
self.context.append('\n')
|
|
1733 |
if (self.settings.use_verbatim_when_possible and (len(node) == 1)
|
|
1734 |
# in case of a parsed-literal containing just a "**bold**" word:
|
|
1735 |
and isinstance(node[0], nodes.Text)):
|
|
1736 |
self.verbatim = 1
|
|
1737 |
self.body.append('\\begin{verbatim}\n')
|
|
1738 |
else:
|
|
1739 |
self.literal_block = 1
|
|
1740 |
self.insert_none_breaking_blanks = 1
|
|
1741 |
self.body.append('{\\ttfamily \\raggedright \\noindent\n')
|
|
1742 |
# * obey..: is from julien and never worked for me (grubert).
|
|
1743 |
# self.body.append('{\\obeylines\\obeyspaces\\ttfamily\n')
|
|
1744 |
|
|
1745 |
def depart_literal_block(self, node):
|
|
1746 |
if self.verbatim:
|
|
1747 |
self.body.append('\n\\end{verbatim}\n')
|
|
1748 |
self.verbatim = 0
|
|
1749 |
else:
|
|
1750 |
self.body.append('\n}')
|
|
1751 |
self.insert_none_breaking_blanks = 0
|
|
1752 |
self.literal_block = 0
|
|
1753 |
# obey end: self.body.append('}\n')
|
|
1754 |
self.body.append(self.context.pop())
|
|
1755 |
|
|
1756 |
def visit_meta(self, node):
|
|
1757 |
self.body.append('[visit_meta]\n')
|
|
1758 |
# BUG maybe set keywords for pdf
|
|
1759 |
##self.head.append(self.starttag(node, 'meta', **node.attributes))
|
|
1760 |
|
|
1761 |
def depart_meta(self, node):
|
|
1762 |
self.body.append('[depart_meta]\n')
|
|
1763 |
|
|
1764 |
def visit_note(self, node):
|
|
1765 |
self.visit_admonition(node, 'note')
|
|
1766 |
|
|
1767 |
def depart_note(self, node):
|
|
1768 |
self.depart_admonition()
|
|
1769 |
|
|
1770 |
def visit_option(self, node):
|
|
1771 |
if self.context[-1]:
|
|
1772 |
# this is not the first option
|
|
1773 |
self.body.append(', ')
|
|
1774 |
|
|
1775 |
def depart_option(self, node):
|
|
1776 |
# flag tha the first option is done.
|
|
1777 |
self.context[-1] += 1
|
|
1778 |
|
|
1779 |
def visit_option_argument(self, node):
|
|
1780 |
"""The delimiter betweeen an option and its argument."""
|
|
1781 |
self.body.append(node.get('delimiter', ' '))
|
|
1782 |
|
|
1783 |
def depart_option_argument(self, node):
|
|
1784 |
pass
|
|
1785 |
|
|
1786 |
def visit_option_group(self, node):
|
|
1787 |
self.body.append('\\item [')
|
|
1788 |
# flag for first option
|
|
1789 |
self.context.append(0)
|
|
1790 |
|
|
1791 |
def depart_option_group(self, node):
|
|
1792 |
self.context.pop() # the flag
|
|
1793 |
self.body.append('] ')
|
|
1794 |
|
|
1795 |
def visit_option_list(self, node):
|
|
1796 |
self.body.append('\\begin{optionlist}{3cm}\n')
|
|
1797 |
|
|
1798 |
def depart_option_list(self, node):
|
|
1799 |
self.body.append('\\end{optionlist}\n')
|
|
1800 |
|
|
1801 |
def visit_option_list_item(self, node):
|
|
1802 |
pass
|
|
1803 |
|
|
1804 |
def depart_option_list_item(self, node):
|
|
1805 |
pass
|
|
1806 |
|
|
1807 |
def visit_option_string(self, node):
|
|
1808 |
##self.body.append(self.starttag(node, 'span', '', CLASS='option'))
|
|
1809 |
pass
|
|
1810 |
|
|
1811 |
def depart_option_string(self, node):
|
|
1812 |
##self.body.append('</span>')
|
|
1813 |
pass
|
|
1814 |
|
|
1815 |
def visit_organization(self, node):
|
|
1816 |
self.visit_docinfo_item(node, 'organization')
|
|
1817 |
|
|
1818 |
def depart_organization(self, node):
|
|
1819 |
self.depart_docinfo_item(node)
|
|
1820 |
|
|
1821 |
def visit_paragraph(self, node):
|
|
1822 |
index = node.parent.index(node)
|
|
1823 |
if not ('contents' in self.topic_classes or
|
|
1824 |
(isinstance(node.parent, nodes.compound) and
|
|
1825 |
index > 0 and
|
|
1826 |
not isinstance(node.parent[index - 1], nodes.paragraph) and
|
|
1827 |
not isinstance(node.parent[index - 1], nodes.compound))):
|
|
1828 |
self.body.append('\n')
|
|
1829 |
|
|
1830 |
def depart_paragraph(self, node):
|
|
1831 |
self.body.append('\n')
|
|
1832 |
|
|
1833 |
def visit_problematic(self, node):
|
|
1834 |
self.body.append('{\\color{red}\\bfseries{}')
|
|
1835 |
|
|
1836 |
def depart_problematic(self, node):
|
|
1837 |
self.body.append('}')
|
|
1838 |
|
|
1839 |
def visit_raw(self, node):
|
|
1840 |
if 'latex' in node.get('format', '').split():
|
|
1841 |
self.body.append(node.astext())
|
|
1842 |
raise nodes.SkipNode
|
|
1843 |
|
|
1844 |
def visit_reference(self, node):
|
|
1845 |
# BUG: hash_char "#" is trouble some in LaTeX.
|
|
1846 |
# mbox and other environment do not like the '#'.
|
|
1847 |
hash_char = '\\#'
|
|
1848 |
if node.has_key('refuri'):
|
|
1849 |
href = node['refuri'].replace('#',hash_char)
|
|
1850 |
elif node.has_key('refid'):
|
|
1851 |
href = hash_char + node['refid']
|
|
1852 |
elif node.has_key('refname'):
|
|
1853 |
href = hash_char + self.document.nameids[node['refname']]
|
|
1854 |
else:
|
|
1855 |
raise AssertionError('Unknown reference.')
|
|
1856 |
self.body.append('\\href{%s}{' % href)
|
|
1857 |
if self._reference_label and not node.has_key('refuri'):
|
|
1858 |
self.body.append('\\%s{%s}}' % (self._reference_label,
|
|
1859 |
href.replace(hash_char, '')))
|
|
1860 |
raise nodes.SkipNode
|
|
1861 |
|
|
1862 |
def depart_reference(self, node):
|
|
1863 |
self.body.append('}')
|
|
1864 |
|
|
1865 |
def visit_revision(self, node):
|
|
1866 |
self.visit_docinfo_item(node, 'revision')
|
|
1867 |
|
|
1868 |
def depart_revision(self, node):
|
|
1869 |
self.depart_docinfo_item(node)
|
|
1870 |
|
|
1871 |
def visit_section(self, node):
|
|
1872 |
self.section_level += 1
|
|
1873 |
# Initialize counter for potential subsections:
|
|
1874 |
self._section_number.append(0)
|
|
1875 |
# Counter for this section's level (initialized by parent section):
|
|
1876 |
self._section_number[self.section_level - 1] += 1
|
|
1877 |
|
|
1878 |
def depart_section(self, node):
|
|
1879 |
# Remove counter for potential subsections:
|
|
1880 |
self._section_number.pop()
|
|
1881 |
self.section_level -= 1
|
|
1882 |
|
|
1883 |
def visit_sidebar(self, node):
|
|
1884 |
# BUG: this is just a hack to make sidebars render something
|
|
1885 |
self.body.append('\n\\setlength{\\locallinewidth}{0.9\\admonitionwidth}\n')
|
|
1886 |
self.body.append('\\begin{center}\\begin{sffamily}\n')
|
|
1887 |
self.body.append('\\fbox{\\colorbox[gray]{0.80}{\\parbox{\\admonitionwidth}{\n')
|
|
1888 |
|
|
1889 |
def depart_sidebar(self, node):
|
|
1890 |
self.body.append('}}}\n') # end parbox colorbox fbox
|
|
1891 |
self.body.append('\\end{sffamily}\n\\end{center}\n');
|
|
1892 |
self.body.append('\n\\setlength{\\locallinewidth}{\\linewidth}\n')
|
|
1893 |
|
|
1894 |
|
|
1895 |
attribution_formats = {'dash': ('---', ''),
|
|
1896 |
'parentheses': ('(', ')'),
|
|
1897 |
'parens': ('(', ')'),
|
|
1898 |
'none': ('', '')}
|
|
1899 |
|
|
1900 |
def visit_attribution(self, node):
|
|
1901 |
prefix, suffix = self.attribution_formats[self.settings.attribution]
|
|
1902 |
self.body.append('\n\\begin{flushright}\n')
|
|
1903 |
self.body.append(prefix)
|
|
1904 |
self.context.append(suffix)
|
|
1905 |
|
|
1906 |
def depart_attribution(self, node):
|
|
1907 |
self.body.append(self.context.pop() + '\n')
|
|
1908 |
self.body.append('\\end{flushright}\n')
|
|
1909 |
|
|
1910 |
def visit_status(self, node):
|
|
1911 |
self.visit_docinfo_item(node, 'status')
|
|
1912 |
|
|
1913 |
def depart_status(self, node):
|
|
1914 |
self.depart_docinfo_item(node)
|
|
1915 |
|
|
1916 |
def visit_strong(self, node):
|
|
1917 |
self.body.append('\\textbf{')
|
|
1918 |
self.literal_block_stack.append('\\textbf{')
|
|
1919 |
|
|
1920 |
def depart_strong(self, node):
|
|
1921 |
self.body.append('}')
|
|
1922 |
self.literal_block_stack.pop()
|
|
1923 |
|
|
1924 |
def visit_substitution_definition(self, node):
|
|
1925 |
raise nodes.SkipNode
|
|
1926 |
|
|
1927 |
def visit_substitution_reference(self, node):
|
|
1928 |
self.unimplemented_visit(node)
|
|
1929 |
|
|
1930 |
def visit_subtitle(self, node):
|
|
1931 |
if isinstance(node.parent, nodes.sidebar):
|
|
1932 |
self.body.append('~\\\\\n\\textbf{')
|
|
1933 |
self.context.append('}\n\\smallskip\n')
|
|
1934 |
elif isinstance(node.parent, nodes.document):
|
|
1935 |
self.title = self.title + \
|
|
1936 |
'\\\\\n\\large{%s}\n' % self.encode(node.astext())
|
|
1937 |
raise nodes.SkipNode
|
|
1938 |
elif isinstance(node.parent, nodes.section):
|
|
1939 |
self.body.append('\\textbf{')
|
|
1940 |
self.context.append('}\\vspace{0.2cm}\n\n\\noindent ')
|
|
1941 |
|
|
1942 |
def depart_subtitle(self, node):
|
|
1943 |
self.body.append(self.context.pop())
|
|
1944 |
|
|
1945 |
def visit_system_message(self, node):
|
|
1946 |
pass
|
|
1947 |
|
|
1948 |
def depart_system_message(self, node):
|
|
1949 |
self.body.append('\n')
|
|
1950 |
|
|
1951 |
def visit_table(self, node):
|
|
1952 |
if self.active_table.is_open():
|
|
1953 |
self.table_stack.append(self.active_table)
|
|
1954 |
# nesting longtable does not work (e.g. 2007-04-18)
|
|
1955 |
self.active_table = Table('tabular',self.settings.table_style)
|
|
1956 |
self.active_table.open()
|
|
1957 |
for cl in node['classes']:
|
|
1958 |
self.active_table.set_table_style(cl)
|
|
1959 |
self.body.append('\n' + self.active_table.get_opening())
|
|
1960 |
|
|
1961 |
def depart_table(self, node):
|
|
1962 |
self.body.append(self.active_table.get_closing() + '\n')
|
|
1963 |
self.active_table.close()
|
|
1964 |
if len(self.table_stack)>0:
|
|
1965 |
self.active_table = self.table_stack.pop()
|
|
1966 |
else:
|
|
1967 |
self.active_table.set_table_style(self.settings.table_style)
|
|
1968 |
|
|
1969 |
def visit_target(self, node):
|
|
1970 |
# BUG: why not (refuri or refid or refname) means not footnote ?
|
|
1971 |
if not (node.has_key('refuri') or node.has_key('refid')
|
|
1972 |
or node.has_key('refname')):
|
|
1973 |
for id in node['ids']:
|
|
1974 |
self.body.append('\\hypertarget{%s}{' % id)
|
|
1975 |
self.context.append('}' * len(node['ids']))
|
|
1976 |
elif node.get("refid"):
|
|
1977 |
self.body.append('\\hypertarget{%s}{' % node.get("refid"))
|
|
1978 |
self.context.append('}')
|
|
1979 |
else:
|
|
1980 |
self.context.append('')
|
|
1981 |
|
|
1982 |
def depart_target(self, node):
|
|
1983 |
self.body.append(self.context.pop())
|
|
1984 |
|
|
1985 |
def visit_tbody(self, node):
|
|
1986 |
# BUG write preamble if not yet done (colspecs not [])
|
|
1987 |
# for tables without heads.
|
|
1988 |
if not self.active_table.get('preamble written'):
|
|
1989 |
self.visit_thead(None)
|
|
1990 |
# self.depart_thead(None)
|
|
1991 |
|
|
1992 |
def depart_tbody(self, node):
|
|
1993 |
pass
|
|
1994 |
|
|
1995 |
def visit_term(self, node):
|
|
1996 |
self.body.append('\\item[{')
|
|
1997 |
|
|
1998 |
def depart_term(self, node):
|
|
1999 |
# definition list term.
|
|
2000 |
# \leavevmode results in a line break if the term is followed by a item list.
|
|
2001 |
self.body.append('}] \leavevmode ')
|
|
2002 |
|
|
2003 |
def visit_tgroup(self, node):
|
|
2004 |
#self.body.append(self.starttag(node, 'colgroup'))
|
|
2005 |
#self.context.append('</colgroup>\n')
|
|
2006 |
pass
|
|
2007 |
|
|
2008 |
def depart_tgroup(self, node):
|
|
2009 |
pass
|
|
2010 |
|
|
2011 |
def visit_thead(self, node):
|
|
2012 |
self.body.append('{%s}\n' % self.active_table.get_colspecs())
|
|
2013 |
if self.active_table.caption:
|
|
2014 |
self.body.append('\\caption{%s}\\\\\n' % self.active_table.caption)
|
|
2015 |
self.active_table.set('preamble written',1)
|
|
2016 |
# TODO longtable supports firsthead and lastfoot too.
|
|
2017 |
self.body.extend(self.active_table.visit_thead())
|
|
2018 |
|
|
2019 |
def depart_thead(self, node):
|
|
2020 |
# the table header written should be on every page
|
|
2021 |
# => \endhead
|
|
2022 |
self.body.extend(self.active_table.depart_thead())
|
|
2023 |
# and the firsthead => \endfirsthead
|
|
2024 |
# BUG i want a "continued from previous page" on every not
|
|
2025 |
# firsthead, but then we need the header twice.
|
|
2026 |
#
|
|
2027 |
# there is a \endfoot and \endlastfoot too.
|
|
2028 |
# but we need the number of columns to
|
|
2029 |
# self.body.append('\\multicolumn{%d}{c}{"..."}\n' % number_of_columns)
|
|
2030 |
# self.body.append('\\hline\n\\endfoot\n')
|
|
2031 |
# self.body.append('\\hline\n')
|
|
2032 |
# self.body.append('\\endlastfoot\n')
|
|
2033 |
|
|
2034 |
def visit_tip(self, node):
|
|
2035 |
self.visit_admonition(node, 'tip')
|
|
2036 |
|
|
2037 |
def depart_tip(self, node):
|
|
2038 |
self.depart_admonition()
|
|
2039 |
|
|
2040 |
def bookmark(self, node):
|
|
2041 |
"""Append latex href and pdfbookmarks for titles.
|
|
2042 |
"""
|
|
2043 |
if node.parent['ids']:
|
|
2044 |
for id in node.parent['ids']:
|
|
2045 |
self.body.append('\\hypertarget{%s}{}\n' % id)
|
|
2046 |
if not self.use_latex_toc:
|
|
2047 |
# BUG level depends on style. pdflatex allows level 0 to 3
|
|
2048 |
# ToC would be the only on level 0 so i choose to decrement the rest.
|
|
2049 |
# "Table of contents" bookmark to see the ToC. To avoid this
|
|
2050 |
# we set all zeroes to one.
|
|
2051 |
l = self.section_level
|
|
2052 |
if l>0:
|
|
2053 |
l = l-1
|
|
2054 |
# pdftex does not like "_" subscripts in titles
|
|
2055 |
text = self.encode(node.astext())
|
|
2056 |
for id in node.parent['ids']:
|
|
2057 |
self.body.append('\\pdfbookmark[%d]{%s}{%s}\n' % \
|
|
2058 |
(l, text, id))
|
|
2059 |
|
|
2060 |
def visit_title(self, node):
|
|
2061 |
"""Section and other titles."""
|
|
2062 |
|
|
2063 |
if isinstance(node.parent, nodes.topic):
|
|
2064 |
# the table of contents.
|
|
2065 |
self.bookmark(node)
|
|
2066 |
if ('contents' in self.topic_classes
|
|
2067 |
and self.use_latex_toc):
|
|
2068 |
self.body.append('\\renewcommand{\\contentsname}{')
|
|
2069 |
self.context.append('}\n\\tableofcontents\n\n\\bigskip\n')
|
|
2070 |
elif ('abstract' in self.topic_classes
|
|
2071 |
and self.settings.use_latex_abstract):
|
|
2072 |
raise nodes.SkipNode
|
|
2073 |
else: # or section titles before the table of contents.
|
|
2074 |
# BUG: latex chokes on center environment with
|
|
2075 |
# "perhaps a missing item", therefore we use hfill.
|
|
2076 |
self.body.append('\\subsubsection*{~\\hfill ')
|
|
2077 |
# the closing brace for subsection.
|
|
2078 |
self.context.append('\\hfill ~}\n')
|
|
2079 |
# TODO: for admonition titles before the first section
|
|
2080 |
# either specify every possible node or ... ?
|
|
2081 |
elif isinstance(node.parent, nodes.sidebar) \
|
|
2082 |
or isinstance(node.parent, nodes.admonition):
|
|
2083 |
self.body.append('\\textbf{\\large ')
|
|
2084 |
self.context.append('}\n\\smallskip\n')
|
|
2085 |
elif isinstance(node.parent, nodes.table):
|
|
2086 |
# caption must be written after column spec
|
|
2087 |
self.active_table.caption = self.encode(node.astext())
|
|
2088 |
raise nodes.SkipNode
|
|
2089 |
elif self.section_level == 0:
|
|
2090 |
# document title
|
|
2091 |
self.title = self.encode(node.astext())
|
|
2092 |
if not self.pdfinfo == None:
|
|
2093 |
self.pdfinfo.append( 'pdftitle={%s}' % self.encode(node.astext()) )
|
|
2094 |
raise nodes.SkipNode
|
|
2095 |
else:
|
|
2096 |
self.body.append('\n\n')
|
|
2097 |
self.body.append('%' + '_' * 75)
|
|
2098 |
self.body.append('\n\n')
|
|
2099 |
self.bookmark(node)
|
|
2100 |
|
|
2101 |
if self.use_latex_toc:
|
|
2102 |
section_star = ""
|
|
2103 |
else:
|
|
2104 |
section_star = "*"
|
|
2105 |
|
|
2106 |
section_name = self.d_class.section(self.section_level)
|
|
2107 |
self.body.append('\\%s%s{' % (section_name, section_star))
|
|
2108 |
# MAYBE postfix paragraph and subparagraph with \leavemode to
|
|
2109 |
# ensure floatables stay in the section and text starts on a new line.
|
|
2110 |
self.context.append('}\n')
|
|
2111 |
|
|
2112 |
def depart_title(self, node):
|
|
2113 |
self.body.append(self.context.pop())
|
|
2114 |
for id in node.parent['ids']:
|
|
2115 |
self.body.append('\\label{%s}\n' % id)
|
|
2116 |
|
|
2117 |
def visit_topic(self, node):
|
|
2118 |
self.topic_classes = node['classes']
|
|
2119 |
if ('abstract' in self.topic_classes
|
|
2120 |
and self.settings.use_latex_abstract):
|
|
2121 |
self.body.append('\\begin{abstract}\n')
|
|
2122 |
|
|
2123 |
def depart_topic(self, node):
|
|
2124 |
if ('abstract' in self.topic_classes
|
|
2125 |
and self.settings.use_latex_abstract):
|
|
2126 |
self.body.append('\\end{abstract}\n')
|
|
2127 |
self.topic_classes = []
|
|
2128 |
if 'contents' in node['classes'] and self.use_latex_toc:
|
|
2129 |
pass
|
|
2130 |
else:
|
|
2131 |
self.body.append('\n')
|
|
2132 |
|
|
2133 |
def visit_inline(self, node): # titlereference
|
|
2134 |
classes = node.get('classes', ['Unknown', ])
|
|
2135 |
for cls in classes:
|
|
2136 |
self.body.append( '\\docutilsrole%s{' % cls)
|
|
2137 |
self.context.append('}'*len(classes))
|
|
2138 |
|
|
2139 |
def depart_inline(self, node):
|
|
2140 |
self.body.append(self.context.pop())
|
|
2141 |
|
|
2142 |
def visit_rubric(self, node):
|
|
2143 |
self.body.append('\\rubric{')
|
|
2144 |
self.context.append('}\n')
|
|
2145 |
|
|
2146 |
def depart_rubric(self, node):
|
|
2147 |
self.body.append(self.context.pop())
|
|
2148 |
|
|
2149 |
def visit_transition(self, node):
|
|
2150 |
self.body.append('\n\n')
|
|
2151 |
self.body.append('%' + '_' * 75)
|
|
2152 |
self.body.append('\n\\hspace*{\\fill}\\hrulefill\\hspace*{\\fill}')
|
|
2153 |
self.body.append('\n\n')
|
|
2154 |
|
|
2155 |
def depart_transition(self, node):
|
|
2156 |
pass
|
|
2157 |
|
|
2158 |
def visit_version(self, node):
|
|
2159 |
self.visit_docinfo_item(node, 'version')
|
|
2160 |
|
|
2161 |
def depart_version(self, node):
|
|
2162 |
self.depart_docinfo_item(node)
|
|
2163 |
|
|
2164 |
def visit_warning(self, node):
|
|
2165 |
self.visit_admonition(node, 'warning')
|
|
2166 |
|
|
2167 |
def depart_warning(self, node):
|
|
2168 |
self.depart_admonition()
|
|
2169 |
|
|
2170 |
def unimplemented_visit(self, node):
|
|
2171 |
raise NotImplementedError('visiting unimplemented node type: %s'
|
|
2172 |
% node.__class__.__name__)
|
|
2173 |
|
|
2174 |
# def unknown_visit(self, node):
|
|
2175 |
# def default_visit(self, node):
|
|
2176 |
|
|
2177 |
# vim: set ts=4 et ai :
|