Chg: citations cant be done with LaTeX footnotes.
[docutils.git] / docutils / writers / latex2e.py
blob0bd7ed8318fdb12115be7ca7a4d225108d9b1ee7
1 """
2 :Author: Engelbert Gruber
3 :Contact: grubert@users.sourceforge.net
4 :Revision: $Revision$
5 :Date: $Date$
6 :Copyright: This module has been placed in the public domain.
8 LaTeX2e document tree Writer.
9 """
11 __docformat__ = 'reStructuredText'
13 # code contributions from several people included, thanks too all.
14 # some named: David Abrahams, Julien Letessier, Lele Gaifax, and others.
16 # convention deactivate code by two # e.g. ##.
18 import sys
19 import time
20 import re
21 import string
22 from types import ListType
23 from docutils import frontend, nodes, languages, writers
25 class Writer(writers.Writer):
27 supported = ('latex','latex2e')
28 """Formats this writer supports."""
30 settings_spec = (
31 'LaTeX-Specific Options',
32 'The LaTeX "--output-encoding" default is "latin-1:strict".',
33 (('Specify documentclass. Default is "article".',
34 ['--documentclass'],
35 {'default': 'article', }),
36 ('Specify document options. Multiple options can be given, '
37 'separated by commas. Default is "10pt".',
38 ['--documentoptions'],
39 {'default': '10pt', }),
40 ('Use LaTeX footnotes. LaTeX supports only numbered footnotes (does it?). '
41 'Default: no, uses figures.',
42 ['--use-latex-footnotes'],
43 {'default': 0, 'action': 'store_true',
44 'validator': frontend.validate_boolean}),
45 ('Format for footnote references: one of "superscript" or '
46 '"brackets". Default is "brackets".',
47 ['--footnote-references'],
48 {'choices': ['superscript', 'brackets'], 'default': 'brackets',
49 'metavar': '<format>'}),
50 ('Format for block quote attributions: one of "dash" (em-dash '
51 'prefix), "parentheses"/"parens", or "none". Default is "dash".',
52 ['--attribution'],
53 {'choices': ['dash', 'parentheses', 'parens', 'none'],
54 'default': 'dash', 'metavar': '<format>'}),
55 ('Specify a stylesheet file. The file will be "input" by latex in '
56 'the document header. Default is no stylesheet (""). '
57 'Overridden by --stylesheet-path.',
58 ['--stylesheet'],
59 {'default': '', 'metavar': '<file>'}),
60 ('Specify a stylesheet file, relative to the current working '
61 'directory. Overrides --stylesheet.',
62 ['--stylesheet-path'],
63 {'metavar': '<file>'}),
64 ('Link to the stylesheet in the output LaTeX file. This is the '
65 'default.',
66 ['--link-stylesheet'],
67 {'dest': 'embed_stylesheet', 'action': 'store_false',
68 'validator': frontend.validate_boolean}),
69 ('Embed the stylesheet in the output LaTeX file. The stylesheet '
70 'file must be accessible during processing (--stylesheet-path is '
71 'recommended).',
72 ['--embed-stylesheet'], {'action': 'store_true'}),
73 ('Table of contents by docutils (default) or latex. Latex (writer) '
74 'supports only one ToC per document, but docutils does not write '
75 'pagenumbers.',
76 ['--use-latex-toc'],
77 {'default': 0, 'action': 'store_true',
78 'validator': frontend.validate_boolean}),
79 ('Let LaTeX print author and date, do not show it in docutils '
80 'document info.',
81 ['--use-latex-docinfo'],
82 {'default': 0, 'action': 'store_true',
83 'validator': frontend.validate_boolean}),
84 ('Color of any hyperlinks embedded in text '
85 '(default: "blue", "0" to disable).',
86 ['--hyperlink-color'], {'default': 'blue'}),
87 ('Enable compound enumerators for nested enumerated lists '
88 '(e.g. "1.2.a.ii"). Default: disabled.',
89 ['--compound-enumerators'],
90 {'default': None, 'action': 'store_true',
91 'validator': frontend.validate_boolean}),
92 ('Disable compound enumerators for nested enumerated lists. This is '
93 'the default.',
94 ['--no-compound-enumerators'],
95 {'action': 'store_false', 'dest': 'compound_enumerators'}),
96 ('Enable section ("." subsection ...) prefixes for compound '
97 'enumerators. This has no effect without --compound-enumerators. '
98 'Default: disabled.',
99 ['--section-prefix-for-enumerators'],
100 {'default': None, 'action': 'store_true',
101 'validator': frontend.validate_boolean}),
102 ('Disable section prefixes for compound enumerators. '
103 'This is the default.',
104 ['--no-section-prefix-for-enumerators'],
105 {'action': 'store_false', 'dest': 'section_prefix_for_enumerators'}),
106 ('Set the separator between section number and enumerator '
107 'for compound enumerated lists. Default is "-".',
108 ['--section-enumerator-separator'],
109 {'default': '-', 'metavar': '<char>'}),
110 ('When possibile, use verbatim for literal-blocks.'
111 'Default is to always use the mbox environment.',
112 ['--use-verbatim-when-possible'],
113 {'default': 0, 'action': 'store_true',
114 'validator': frontend.validate_boolean}),
115 ('Table style. "standard" with horizontal and vertical lines, '
116 '"booktabs" (LaTeX booktabs style) only horizontal lines '
117 'above and below the table and below the header or "nolines".'
118 '(default: "standard"',
119 ['--table-style'],
120 {'choices': ['standard', 'booktabs','nolines'], 'default': 'standard',
121 'metavar': '<format>'}),
124 settings_defaults = {'output_encoding': 'latin-1'}
126 config_section = 'latex2e writer'
127 config_section_dependencies = ('writers',)
129 output = None
130 """Final translated form of `document`."""
132 def translate(self):
133 visitor = LaTeXTranslator(self.document)
134 self.document.walkabout(visitor)
135 self.output = visitor.astext()
136 self.head_prefix = visitor.head_prefix
137 self.head = visitor.head
138 self.body_prefix = visitor.body_prefix
139 self.body = visitor.body
140 self.body_suffix = visitor.body_suffix
143 Notes on LaTeX
144 --------------
146 * latex does not support multiple tocs in one document.
147 (might be no limitation except for docutils documentation)
149 * width
151 * linewidth - width of a line in the local environment
152 * textwidth - the width of text on the page
154 Maybe always use linewidth ?
156 *Bug* inside a minipage a (e.g. Sidebar) the linewidth is
157 not changed, needs fix in docutils so that tables
158 are not too wide.
160 So we add locallinewidth set it initially and
161 on entering sidebar and reset on exit.
164 class Babel:
165 """Language specifics for LaTeX."""
166 # country code by a.schlock.
167 # partly manually converted from iso and babel stuff, dialects and some
168 _ISO639_TO_BABEL = {
169 'no': 'norsk', #XXX added by hand ( forget about nynorsk?)
170 'gd': 'scottish', #XXX added by hand
171 'hu': 'magyar', #XXX added by hand
172 'pt': 'portuguese',#XXX added by hand
173 'sl': 'slovenian',
174 'af': 'afrikaans',
175 'bg': 'bulgarian',
176 'br': 'breton',
177 'ca': 'catalan',
178 'cs': 'czech',
179 'cy': 'welsh',
180 'da': 'danish',
181 'fr': 'french',
182 # french, francais, canadien, acadian
183 'de': 'ngerman', #XXX rather than german
184 # ngerman, naustrian, german, germanb, austrian
185 'el': 'greek',
186 'en': 'english',
187 # english, USenglish, american, UKenglish, british, canadian
188 'eo': 'esperanto',
189 'es': 'spanish',
190 'et': 'estonian',
191 'eu': 'basque',
192 'fi': 'finnish',
193 'ga': 'irish',
194 'gl': 'galician',
195 'he': 'hebrew',
196 'hr': 'croatian',
197 'hu': 'hungarian',
198 'is': 'icelandic',
199 'it': 'italian',
200 'la': 'latin',
201 'nl': 'dutch',
202 'pl': 'polish',
203 'pt': 'portuguese',
204 'ro': 'romanian',
205 'ru': 'russian',
206 'sk': 'slovak',
207 'sr': 'serbian',
208 'sv': 'swedish',
209 'tr': 'turkish',
210 'uk': 'ukrainian'
213 def __init__(self,lang):
214 self.language = lang
215 # pdflatex does not produce double quotes for ngerman in tt.
216 self.double_quote_replacment = None
217 if re.search('^de',self.language):
218 # maybe use: {\glqq} {\grqq}.
219 self.quotes = ("\"`", "\"'")
220 self.double_quote_replacment = "{\\dq}"
221 else:
222 self.quotes = ("``", "''")
223 self.quote_index = 0
225 def next_quote(self):
226 q = self.quotes[self.quote_index]
227 self.quote_index = (self.quote_index+1)%2
228 return q
230 def quote_quotes(self,text):
231 t = None
232 for part in text.split('"'):
233 if t == None:
234 t = part
235 else:
236 t += self.next_quote() + part
237 return t
239 def double_quotes_in_tt (self,text):
240 if not self.double_quote_replacment:
241 return text
242 return text.replace('"', self.double_quote_replacment)
244 def get_language(self):
245 if self._ISO639_TO_BABEL.has_key(self.language):
246 return self._ISO639_TO_BABEL[self.language]
247 else:
248 # support dialects.
249 l = self.language.split("_")[0]
250 if self._ISO639_TO_BABEL.has_key(l):
251 return self._ISO639_TO_BABEL[l]
252 return None
255 latex_headings = {
256 'optionlist_environment' : [
257 '\\newcommand{\\optionlistlabel}[1]{\\bf #1 \\hfill}\n'
258 '\\newenvironment{optionlist}[1]\n'
259 '{\\begin{list}{}\n'
260 ' {\\setlength{\\labelwidth}{#1}\n'
261 ' \\setlength{\\rightmargin}{1cm}\n'
262 ' \\setlength{\\leftmargin}{\\rightmargin}\n'
263 ' \\addtolength{\\leftmargin}{\\labelwidth}\n'
264 ' \\addtolength{\\leftmargin}{\\labelsep}\n'
265 ' \\renewcommand{\\makelabel}{\\optionlistlabel}}\n'
266 '}{\\end{list}}\n',
268 'footnote_floats' : [
269 '% begin: floats for footnotes tweaking.\n',
270 '\\setlength{\\floatsep}{0.5em}\n',
271 '\\setlength{\\textfloatsep}{\\fill}\n',
272 '\\addtolength{\\textfloatsep}{3em}\n',
273 '\\renewcommand{\\textfraction}{0.5}\n',
274 '\\renewcommand{\\topfraction}{0.5}\n',
275 '\\renewcommand{\\bottomfraction}{0.5}\n',
276 '\\setcounter{totalnumber}{50}\n',
277 '\\setcounter{topnumber}{50}\n',
278 '\\setcounter{bottomnumber}{50}\n',
279 '% end floats for footnotes\n',
281 'some_commands' : [
282 '% some commands, that could be overwritten in the style file.\n'
283 '\\newcommand{\\rubric}[1]'
284 '{\\subsection*{~\\hfill {\\it #1} \\hfill ~}}\n'
285 '\\newcommand{\\titlereference}[1]{\\textsl{#1}}\n'
286 '% end of "some commands"\n',
290 class DocumentClass:
291 """Details of a LaTeX document class."""
293 # BUG: LaTeX has no deeper sections (actually paragrah is no
294 # section either).
295 _class_sections = {
296 'book': ( 'chapter', 'section', 'subsection', 'subsubsection' ),
297 'report': ( 'chapter', 'section', 'subsection', 'subsubsection' ),
298 'article': ( 'section', 'subsection', 'subsubsection' ),
300 _deepest_section = 'subsubsection'
302 def __init__(self, document_class):
303 self.document_class = document_class
305 def section(self, level):
306 """ Return the section name at the given level for the specific
307 document class.
309 Level is 1,2,3..., as level 0 is the title."""
311 sections = self._class_sections[self.document_class]
312 if level <= len(sections):
313 return sections[level-1]
314 else:
315 return self._deepest_section
317 class Table:
318 """ Manage a table while traversing.
319 Maybe change to a mixin defining the visit/departs, but then
320 class Table internal variables are in the Translator.
322 def __init__(self,latex_type,table_style):
323 self._latex_type = latex_type
324 self._table_style = table_style
325 self._open = 0
326 # miscellaneous attributes
327 self._attrs = {}
328 self._col_width = []
329 self._rowspan = []
331 def open(self):
332 self._open = 1
333 self._col_specs = []
334 self.caption = None
335 self._attrs = {}
336 self._in_head = 0 # maybe context with search
337 def close(self):
338 self._open = 0
339 self._col_specs = None
340 self.caption = None
341 self._attrs = {}
342 def is_open(self):
343 return self._open
344 def used_packages(self):
345 if self._table_style == 'booktabs':
346 return '\\usepackage{booktabs}\n'
347 return ''
348 def is_open(self):
349 return self._open
350 def get_latex_type(self):
351 return self._latex_type
353 def set(self,attr,value):
354 self._attrs[attr] = value
355 def get(self,attr):
356 if self._attrs.has_key(attr):
357 return self._attrs[attr]
358 return None
359 def get_vertical_bar(self):
360 if self._table_style == 'standard':
361 return '|'
362 return ''
363 # horizontal lines are drawn below a row, because we.
364 def get_opening(self):
365 return '\\begin{%s}[c]' % self._latex_type
366 def get_closing(self):
367 line = ""
368 if self._table_style == 'booktabs':
369 line = '\\bottomrule\n'
370 elif self._table_style == 'standard':
371 lines = '\\hline\n'
372 return '%s\\end{%s}' % (line,self._latex_type)
374 def visit_colspec(self,node):
375 self._col_specs.append(node)
377 def get_colspecs(self):
379 Return column specification for longtable.
381 Assumes reST line length being 80 characters.
382 Table width is hairy.
384 === ===
385 ABC DEF
386 === ===
388 usually gets to narrow, therefore we add 1 (fiddlefactor).
390 width = 80
392 total_width = 0.0
393 # first see if we get too wide.
394 for node in self._col_specs:
395 colwidth = float(node['colwidth']+1) / width
396 total_width += colwidth
397 self._col_width = []
398 self._rowspan = []
399 # donot make it full linewidth
400 factor = 0.93
401 if total_width > 1.0:
402 factor /= total_width
403 bar = self.get_vertical_bar()
404 latex_table_spec = ""
405 for node in self._col_specs:
406 colwidth = factor * float(node['colwidth']+1) / width
407 self._col_width.append(colwidth+0.005)
408 self._rowspan.append(0)
409 latex_table_spec += "%sp{%.2f\\locallinewidth}" % (bar,colwidth+0.005)
410 return latex_table_spec+bar
412 def get_column_width(self):
413 """ return columnwidth for current cell (not multicell)
415 return "%.2f\\locallinewidth" % self._col_width[self._cell_in_row-1]
417 def visit_thead(self):
418 self._in_thead = 1
419 if self._table_style == 'standard':
420 return ['\\hline\n']
421 elif self._table_style == 'booktabs':
422 return ['\\toprule\n']
423 return []
424 def depart_thead(self):
425 a = []
426 #if self._table_style == 'standard':
427 # a.append('\\hline\n')
428 if self._table_style == 'booktabs':
429 a.append('\\midrule\n')
430 a.append('\\endhead\n')
431 # for longtable one could add firsthead, foot and lastfoot
432 self._in_thead = 0
433 return a
434 def visit_row(self):
435 self._cell_in_row = 0
436 def depart_row(self):
437 res = [' \\\\\n']
438 self._cell_in_row = None # remove cell counter
439 for i in range(len(self._rowspan)):
440 if (self._rowspan[i]>0):
441 self._rowspan[i] -= 1
443 if self._table_style == 'standard':
444 rowspans = []
445 for i in range(len(self._rowspan)):
446 if (self._rowspan[i]<=0):
447 rowspans.append(i+1)
448 if len(rowspans)==len(self._rowspan):
449 res.append('\\hline\n')
450 else:
451 cline = ''
452 rowspans.reverse()
453 # TODO merge clines
454 while 1:
455 try:
456 c_start = rowspans.pop()
457 except:
458 break
459 cline += '\\cline{%d-%d}\n' % (c_start,c_start)
460 res.append(cline)
461 return res
463 def set_rowspan(self,cell,value):
464 try:
465 self._rowspan[cell] = value
466 except:
467 pass
468 def get_rowspan(self,cell):
469 try:
470 return self._rowspan[cell]
471 except:
472 return 0
473 def get_entry_number(self):
474 return self._cell_in_row
475 def visit_entry(self):
476 self._cell_in_row += 1
479 class LaTeXTranslator(nodes.NodeVisitor):
480 # When options are given to the documentclass, latex will pass them
481 # to other packages, as done with babel.
482 # Dummy settings might be taken from document settings
484 d_paper = 'a4paper' # papersize
485 d_margins = '2cm'
487 latex_head = '\\documentclass[%s]{%s}\n'
488 encoding = '\\usepackage[%s]{inputenc}\n'
489 linking = '\\usepackage[colorlinks=%s,linkcolor=%s,urlcolor=%s]{hyperref}\n'
490 geometry = '\\usepackage[%s,margin=%s,nohead]{geometry}\n'
491 stylesheet = '\\input{%s}\n'
492 # add a generated on day , machine by user using docutils version.
493 generator = '%% generator Docutils: http://docutils.sourceforge.net/\n'
495 # use latex tableofcontents or let docutils do it.
496 use_latex_toc = 0
498 # TODO: use mixins for different implementations.
499 # list environment for option-list. else tabularx
500 use_optionlist_for_option_list = 1
501 # list environment for docinfo. else tabularx
502 use_optionlist_for_docinfo = 0 # NOT YET IN USE
504 # Use compound enumerations (1.A.1.)
505 compound_enumerators = 0
507 # If using compound enumerations, include section information.
508 section_prefix_for_enumerators = 0
510 # This is the character that separates the section ("." subsection ...)
511 # prefix from the regular list enumerator.
512 section_enumerator_separator = '-'
514 # default link color
515 hyperlink_color = "blue"
517 def __init__(self, document):
518 nodes.NodeVisitor.__init__(self, document)
519 self.settings = settings = document.settings
520 self.use_latex_toc = settings.use_latex_toc
521 self.use_latex_docinfo = settings.use_latex_docinfo
522 self.use_latex_footnotes = settings.use_latex_footnotes
523 self.hyperlink_color = settings.hyperlink_color
524 self.compound_enumerators = settings.compound_enumerators
525 self.section_prefix_for_enumerators = (
526 settings.section_prefix_for_enumerators)
527 self.section_enumerator_separator = (
528 settings.section_enumerator_separator.replace('_', '\\_'))
529 if self.hyperlink_color == '0':
530 self.hyperlink_color = 'black'
531 self.colorlinks = 'false'
532 else:
533 self.colorlinks = 'true'
535 # language: labels, bibliographic_fields, and author_separators.
536 # to allow writing labes for specific languages.
537 self.language = languages.get_language(settings.language_code)
538 self.babel = Babel(settings.language_code)
539 self.author_separator = self.language.author_separators[0]
540 self.d_options = self.settings.documentoptions
541 if self.babel.get_language():
542 self.d_options += ',%s' % \
543 self.babel.get_language()
545 self.d_class = DocumentClass(settings.documentclass)
547 # object for a table while proccessing.
548 self.active_table = Table('longtable',settings.table_style)
550 self.head_prefix = [
551 self.latex_head % (self.d_options,self.settings.documentclass),
552 '\\usepackage{babel}\n', # language is in documents settings.
553 '\\usepackage{shortvrb}\n', # allows verb in footnotes.
554 self.encoding % self.to_latex_encoding(settings.output_encoding),
555 # * tabularx: for docinfo, automatic width of columns, always on one page.
556 '\\usepackage{tabularx}\n',
557 '\\usepackage{longtable}\n',
558 self.active_table.used_packages(),
559 # possible other packages.
560 # * fancyhdr
561 # * ltxtable is a combination of tabularx and longtable (pagebreaks).
562 # but ??
564 # extra space between text in tables and the line above them
565 '\\setlength{\\extrarowheight}{2pt}\n',
566 '\\usepackage{amsmath}\n', # what fore amsmath.
567 '\\usepackage{graphicx}\n',
568 '\\usepackage{color}\n',
569 '\\usepackage{multirow}\n',
570 self.linking % (self.colorlinks, self.hyperlink_color, self.hyperlink_color),
571 # geometry and fonts might go into style.tex.
572 self.geometry % (self.d_paper, self.d_margins),
574 self.generator,
575 # latex lengths
576 '\\newlength{\\admonitionwidth}\n',
577 '\\setlength{\\admonitionwidth}{0.9\\textwidth}\n'
578 # width for docinfo tablewidth
579 '\\newlength{\\docinfowidth}\n',
580 '\\setlength{\\docinfowidth}{0.9\\textwidth}\n'
581 # linewidth of current environment, so tables are not wider
582 # than the sidebar: using locallinewidth seems to defer evaluation
583 # of linewidth, this is fixing it.
584 '\\newlength{\\locallinewidth}\n',
585 # will be set later.
587 self.head_prefix.extend( latex_headings['optionlist_environment'] )
588 self.head_prefix.extend( latex_headings['footnote_floats'] )
589 self.head_prefix.extend( latex_headings['some_commands'] )
590 ## stylesheet is last: so it might be possible to overwrite defaults.
591 stylesheet = self.get_stylesheet_reference()
592 if stylesheet:
593 self.head_prefix.append(self.stylesheet % (stylesheet))
595 if self.linking: # and maybe check for pdf
596 self.pdfinfo = [ ]
597 self.pdfauthor = None
598 # pdftitle, pdfsubject, pdfauthor, pdfkeywords, pdfcreator, pdfproducer
599 else:
600 self.pdfinfo = None
601 # NOTE: Latex wants a date and an author, rst puts this into
602 # docinfo, so normally we donot want latex author/date handling.
603 # latex article has its own handling of date and author, deactivate.
604 self.head = [ ]
605 if not self.use_latex_docinfo:
606 self.head.extend( [ '\\author{}\n', '\\date{}\n' ] )
607 self.body_prefix = ['\\raggedbottom\n']
608 # separate title, so we can appen subtitle.
609 self.title = ""
610 self.body = []
611 self.body_suffix = ['\n']
612 self.section_level = 0
613 self.context = []
614 self.topic_class = ''
615 # column specification for tables
616 self.table_caption = None
617 # do we have one or more authors
618 self.author_stack = None
619 # Flags to encode
620 # ---------------
621 # verbatim: to tell encode not to encode.
622 self.verbatim = 0
623 # insert_newline: to tell encode to replace blanks by "~".
624 self.insert_none_breaking_blanks = 0
625 # insert_newline: to tell encode to add latex newline.
626 self.insert_newline = 0
627 # mbox_newline: to tell encode to add mbox and newline.
628 self.mbox_newline = 0
630 # enumeration is done by list environment.
631 self._enum_cnt = 0
633 # Stack of section counters so that we don't have to use_latex_toc.
634 # This will grow and shrink as processing occurs.
635 # Initialized for potential first-level sections.
636 self._section_number = [0]
638 # The current stack of enumerations so that we can expand
639 # them into a compound enumeration
640 self._enumeration_counters = []
642 # docinfo.
643 self.docinfo = None
644 # inside literal block: no quote mangling.
645 self.literal_block = 0
646 self.literal_block_stack = []
647 self.literal = 0
648 # true when encoding in math mode
649 self.mathmode = 0
651 def get_stylesheet_reference(self):
652 if self.settings.stylesheet_path:
653 return self.settings.stylesheet_path
654 else:
655 return self.settings.stylesheet
657 def to_latex_encoding(self,docutils_encoding):
659 Translate docutils encoding name into latex's.
661 Default fallback method is remove "-" and "_" chars from docutils_encoding.
664 tr = { "iso-8859-1": "latin1", # west european
665 "iso-8859-2": "latin2", # east european
666 "iso-8859-3": "latin3", # esperanto, maltese
667 "iso-8859-4": "latin4", # north european,scandinavian, baltic
668 "iso-8859-5": "iso88595", # cyrillic (ISO)
669 "iso-8859-9": "latin5", # turkish
670 "iso-8859-15": "latin9", # latin9, update to latin1.
671 "mac_cyrillic": "maccyr", # cyrillic (on Mac)
672 "windows-1251": "cp1251", # cyrillic (on Windows)
673 "koi8-r": "koi8-r", # cyrillic (Russian)
674 "koi8-u": "koi8-u", # cyrillic (Ukrainian)
675 "windows-1250": "cp1250", #
676 "windows-1252": "cp1252", #
677 "us-ascii": "ascii", # ASCII (US)
678 # unmatched encodings
679 #"": "applemac",
680 #"": "ansinew", # windows 3.1 ansi
681 #"": "ascii", # ASCII encoding for the range 32--127.
682 #"": "cp437", # dos latine us
683 #"": "cp850", # dos latin 1
684 #"": "cp852", # dos latin 2
685 #"": "decmulti",
686 #"": "latin10",
687 #"iso-8859-6": "" # arabic
688 #"iso-8859-7": "" # greek
689 #"iso-8859-8": "" # hebrew
690 #"iso-8859-10": "" # latin6, more complete iso-8859-4
692 if tr.has_key(docutils_encoding.lower()):
693 return tr[docutils_encoding.lower()]
694 return docutils_encoding.translate(string.maketrans("",""),"_-").lower()
696 def language_label(self, docutil_label):
697 return self.language.labels[docutil_label]
699 def encode(self, text):
701 Encode special characters in `text` & return.
702 # $ % & ~ _ ^ \ { }
703 Escaping with a backslash does not help with backslashes, ~ and ^.
705 < > are only available in math-mode or tt font. (really ?)
706 $ starts math- mode.
707 AND quotes:
710 if self.verbatim:
711 return text
712 # compile the regexps once. do it here so one can see them.
714 # first the braces.
715 if not self.__dict__.has_key('encode_re_braces'):
716 self.encode_re_braces = re.compile(r'([{}])')
717 text = self.encode_re_braces.sub(r'{\\\1}',text)
718 if not self.__dict__.has_key('encode_re_bslash'):
719 # find backslash: except in the form '{\{}' or '{\}}'.
720 self.encode_re_bslash = re.compile(r'(?<!{)(\\)(?![{}]})')
721 # then the backslash: except in the form from line above:
722 # either '{\{}' or '{\}}'.
723 text = self.encode_re_bslash.sub(r'{\\textbackslash}', text)
725 # then dollar
726 text = text.replace("$", '{\\$}')
727 if not ( self.literal_block or self.literal or self.mathmode ):
728 # the vertical bar: in mathmode |,\vert or \mid
729 # in textmode \textbar
730 text = text.replace("|", '{\\textbar}')
731 text = text.replace("<", '{\\textless}')
732 text = text.replace(">", '{\\textgreater}')
733 # then
734 text = text.replace("&", '{\\&}')
735 text = text.replace("_", '{\\_}')
736 # the ^:
737 # * verb|^| does not work in mbox.
738 # * mathmode has wedge. hat{~} would also work.
739 # text = text.replace("^", '{\\ensuremath{^\\wedge}}')
740 text = text.replace("^", '{\\textasciicircum}')
741 text = text.replace("%", '{\\%}')
742 text = text.replace("#", '{\\#}')
743 text = text.replace("~", '{\\textasciitilde}')
744 if self.literal_block or self.literal:
745 # pdflatex does not produce doublequotes for ngerman.
746 text = self.babel.double_quotes_in_tt(text)
747 else:
748 text = self.babel.quote_quotes(text)
749 if self.insert_newline or self.literal_block:
750 # HACK: insert a blank before the newline, to avoid
751 # ! LaTeX Error: There's no line here to end.
752 text = text.replace("\n", '~\\\\\n')
753 # HACK: lines starting with "[" or "]" give errors.
754 elif self.mbox_newline:
755 if self.literal_block:
756 closings = "}" * len(self.literal_block_stack)
757 openings = "".join(self.literal_block_stack)
758 else:
759 closings = ""
760 openings = ""
761 text = text.replace("\n", "%s}\\\\\n\\mbox{%s" % (closings,openings))
762 if self.insert_none_breaking_blanks:
763 text = text.replace(' ', '~')
764 # unicode !!!
765 text = text.replace(u'\u2020', '{$\\dagger$}')
766 return text
768 def attval(self, text,
769 whitespace=re.compile('[\n\r\t\v\f]')):
770 """Cleanse, encode, and return attribute value text."""
771 return self.encode(whitespace.sub(' ', text))
773 def astext(self):
774 if self.pdfinfo:
775 if self.pdfauthor:
776 self.pdfinfo.append('pdfauthor={%s}' % self.pdfauthor)
777 pdfinfo = '\\hypersetup{\n' + ',\n'.join(self.pdfinfo) + '\n}\n'
778 else:
779 pdfinfo = ''
780 title = '\\title{%s}\n' % self.title
781 return ''.join(self.head_prefix + [title]
782 + self.head + [pdfinfo]
783 + self.body_prefix + self.body + self.body_suffix)
785 def visit_Text(self, node):
786 self.body.append(self.encode(node.astext()))
788 def depart_Text(self, node):
789 pass
791 def visit_address(self, node):
792 self.visit_docinfo_item(node, 'address')
794 def depart_address(self, node):
795 self.depart_docinfo_item(node)
797 def visit_admonition(self, node, name=''):
798 self.body.append('\\begin{center}\\begin{sffamily}\n')
799 self.body.append('\\fbox{\\parbox{\\admonitionwidth}{\n')
800 if name:
801 self.body.append('\\textbf{\\large '+ self.language.labels[name] + '}\n');
802 self.body.append('\\vspace{2mm}\n')
805 def depart_admonition(self, node=None):
806 self.body.append('}}\n') # end parbox fbox
807 self.body.append('\\end{sffamily}\n\\end{center}\n');
809 def visit_attention(self, node):
810 self.visit_admonition(node, 'attention')
812 def depart_attention(self, node):
813 self.depart_admonition()
815 def visit_author(self, node):
816 self.visit_docinfo_item(node, 'author')
818 def depart_author(self, node):
819 self.depart_docinfo_item(node)
821 def visit_authors(self, node):
822 # not used: visit_author is called anyway for each author.
823 if self.use_latex_docinfo:
824 self.author_stack = []
826 def depart_authors(self, node):
827 if self.use_latex_docinfo:
828 self.head.append('\\author{%s}\n' % \
829 ' \\and '.join(self.author_stack) )
830 self.author_stack = None
832 def visit_block_quote(self, node):
833 self.body.append( '\\begin{quote}\n')
835 def depart_block_quote(self, node):
836 self.body.append( '\\end{quote}\n')
838 def visit_bullet_list(self, node):
839 if self.topic_class == 'contents':
840 if not self.use_latex_toc:
841 self.body.append( '\\begin{list}{}{}\n' )
842 else:
843 self.body.append( '\\begin{itemize}\n' )
845 def depart_bullet_list(self, node):
846 if self.topic_class == 'contents':
847 if not self.use_latex_toc:
848 self.body.append( '\\end{list}\n' )
849 else:
850 self.body.append( '\\end{itemize}\n' )
852 # Imperfect superscript/subscript handling: mathmode italicizes
853 # all letters by default.
854 def visit_superscript(self, node):
855 self.body.append('$^{')
856 self.mathmode = 1
858 def depart_superscript(self, node):
859 self.body.append('}$')
860 self.mathmode = 0
862 def visit_subscript(self, node):
863 self.body.append('$_{')
864 self.mathmode = 1
866 def depart_subscript(self, node):
867 self.body.append('}$')
868 self.mathmode = 0
870 def visit_caption(self, node):
871 self.body.append( '\\caption{' )
873 def depart_caption(self, node):
874 self.body.append('}')
876 def visit_caution(self, node):
877 self.visit_admonition(node, 'caution')
879 def depart_caution(self, node):
880 self.depart_admonition()
882 def visit_citation(self, node):
883 # TODO maybe use cite bibitems
884 self.body.append('\\begin{figure}[b]')
885 self.body.append('\\hypertarget{%s}' % node['id'])
887 def depart_citation(self, node):
888 self.body.append('\\end{figure}\n')
890 def visit_title_reference(self, node):
891 self.body.append( '\\titlereference{' )
893 def depart_title_reference(self, node):
894 self.body.append( '}' )
896 def visit_citation_reference(self, node):
897 href = ''
898 if node.has_key('refid'):
899 href = node['refid']
900 elif node.has_key('refname'):
901 href = self.document.nameids[node['refname']]
902 self.body.append('[\\hyperlink{%s}{' % href)
904 def depart_citation_reference(self, node):
905 self.body.append('}]')
907 def visit_classifier(self, node):
908 self.body.append( '(\\textbf{' )
910 def depart_classifier(self, node):
911 self.body.append( '})\n' )
913 def visit_colspec(self, node):
914 self.active_table.visit_colspec(node)
916 def depart_colspec(self, node):
917 pass
919 def visit_comment(self, node,
920 sub=re.compile('\n').sub):
921 """Escape end of line by a ne comment start in comment text."""
922 self.body.append('%% %s \n' % sub('\n% ', node.astext()))
923 raise nodes.SkipNode
925 def visit_contact(self, node):
926 self.visit_docinfo_item(node, 'contact')
928 def depart_contact(self, node):
929 self.depart_docinfo_item(node)
931 def visit_copyright(self, node):
932 self.visit_docinfo_item(node, 'copyright')
934 def depart_copyright(self, node):
935 self.depart_docinfo_item(node)
937 def visit_danger(self, node):
938 self.visit_admonition(node, 'danger')
940 def depart_danger(self, node):
941 self.depart_admonition()
943 def visit_date(self, node):
944 self.visit_docinfo_item(node, 'date')
946 def depart_date(self, node):
947 self.depart_docinfo_item(node)
949 def visit_decoration(self, node):
950 pass
952 def depart_decoration(self, node):
953 pass
955 def visit_definition(self, node):
956 self.body.append('%[visit_definition]\n')
958 def depart_definition(self, node):
959 self.body.append('\n')
960 self.body.append('%[depart_definition]\n')
962 def visit_definition_list(self, node):
963 self.body.append( '\\begin{description}\n' )
965 def depart_definition_list(self, node):
966 self.body.append( '\\end{description}\n' )
968 def visit_definition_list_item(self, node):
969 self.body.append('%[visit_definition_list_item]\n')
971 def depart_definition_list_item(self, node):
972 self.body.append('%[depart_definition_list_item]\n')
974 def visit_description(self, node):
975 if self.use_optionlist_for_option_list:
976 self.body.append( ' ' )
977 else:
978 self.body.append( ' & ' )
980 def depart_description(self, node):
981 pass
983 def visit_docinfo(self, node):
984 self.docinfo = []
985 self.docinfo.append('%' + '_'*75 + '\n')
986 self.docinfo.append('\\begin{center}\n')
987 self.docinfo.append('\\begin{tabularx}{\\docinfowidth}{lX}\n')
989 def depart_docinfo(self, node):
990 self.docinfo.append('\\end{tabularx}\n')
991 self.docinfo.append('\\end{center}\n')
992 self.body = self.docinfo + self.body
993 # clear docinfo, so field names are no longer appended.
994 self.docinfo = None
996 def visit_docinfo_item(self, node, name):
997 if name == 'author':
998 if not self.pdfinfo == None:
999 if not self.pdfauthor:
1000 self.pdfauthor = self.attval(node.astext())
1001 else:
1002 self.pdfauthor += self.author_separator + self.attval(node.astext())
1003 if self.use_latex_docinfo:
1004 if self.author_stack == None:
1005 self.head.append('\\author{%s}\n' % self.attval(node.astext()))
1006 else:
1007 self.author_stack.append( self.attval(node.astext()) )
1008 raise nodes.SkipNode
1009 elif name == 'date':
1010 if self.use_latex_docinfo:
1011 self.head.append('\\date{%s}\n' % self.attval(node.astext()))
1012 raise nodes.SkipNode
1013 self.docinfo.append('\\textbf{%s}: &\n\t' % self.language_label(name))
1014 if name == 'address':
1015 self.insert_newline = 1
1016 self.docinfo.append('{\\raggedright\n')
1017 self.context.append(' } \\\\\n')
1018 else:
1019 self.context.append(' \\\\\n')
1020 self.context.append(self.docinfo)
1021 self.context.append(len(self.body))
1023 def depart_docinfo_item(self, node):
1024 size = self.context.pop()
1025 dest = self.context.pop()
1026 tail = self.context.pop()
1027 tail = self.body[size:] + [tail]
1028 del self.body[size:]
1029 dest.extend(tail)
1030 # for address we did set insert_newline
1031 self.insert_newline = 0
1033 def visit_doctest_block(self, node):
1034 self.body.append( '\\begin{verbatim}' )
1035 self.verbatim = 1
1037 def depart_doctest_block(self, node):
1038 self.body.append( '\\end{verbatim}\n' )
1039 self.verbatim = 0
1041 def visit_document(self, node):
1042 self.body_prefix.append('\\begin{document}\n')
1043 self.body_prefix.append('\\maketitle\n\n')
1044 # alternative use titlepage environment.
1045 # \begin{titlepage}
1046 self.body.append('\n\\setlength{\\locallinewidth}{\\linewidth}\n')
1048 def depart_document(self, node):
1049 self.body_suffix.append('\\end{document}\n')
1051 def visit_emphasis(self, node):
1052 self.body.append('\\emph{')
1053 self.literal_block_stack.append('\\emph{')
1055 def depart_emphasis(self, node):
1056 self.body.append('}')
1057 self.literal_block_stack.pop()
1059 def visit_entry(self, node):
1060 self.active_table.visit_entry()
1061 # cell separation
1062 if self.active_table.get_entry_number() == 1:
1063 # if the firstrow is a multirow, this actually is the second row.
1064 # this gets hairy if rowspans follow each other.
1065 if self.active_table.get_rowspan(0):
1066 self.body.append(' & ')
1067 self.active_table.visit_entry() # increment cell count
1068 else:
1069 self.body.append(' & ')
1071 # multi{row,column}
1072 # IN WORK BUG TODO HACK continues here
1073 # multirow in LaTeX simply will enlarge the cell over several rows
1074 # (the following n if n is positive, the former if negative).
1075 if node.has_key('morerows') and node.has_key('morecols'):
1076 raise NotImplementedError('Cells that '
1077 'span multiple rows *and* columns are not supported, sorry.')
1078 if node.has_key('morerows'):
1079 count = node['morerows'] + 1
1080 self.active_table.set_rowspan(self.active_table.get_entry_number()-1,count)
1081 self.body.append('\\multirow{%d}{%s}{' % \
1082 (count,self.active_table.get_column_width()))
1083 self.context.append('}')
1084 # BUG following rows must have empty cells.
1085 elif node.has_key('morecols'):
1086 # the vertical bar before column is missing if it is the first column.
1087 # the one after always.
1088 if self.active_table.get_entry_number() == 1:
1089 bar1 = self.active_table.get_vertical_bar()
1090 else:
1091 bar1 = ''
1092 count = node['morecols'] + 1
1093 self.body.append('\\multicolumn{%d}{%sl%s}{' % \
1094 (count, bar1, self.active_table.get_vertical_bar()))
1095 self.context.append('}')
1096 else:
1097 self.context.append('')
1099 # header / not header
1100 if isinstance(node.parent.parent, nodes.thead):
1101 self.body.append('\\textbf{')
1102 self.context.append('}')
1103 else:
1104 self.context.append('')
1106 def depart_entry(self, node):
1107 self.body.append(self.context.pop()) # header / not header
1108 self.body.append(self.context.pop()) # multirow/column
1109 # if following row is spanned from above.
1110 if self.active_table.get_rowspan(self.active_table.get_entry_number()):
1111 self.body.append(' & ')
1112 self.active_table.visit_entry() # increment cell count
1114 def visit_row(self, node):
1115 self.active_table.visit_row()
1117 def depart_row(self, node):
1118 self.body.extend(self.active_table.depart_row())
1120 def visit_enumerated_list(self, node):
1121 # We create our own enumeration list environment.
1122 # This allows to set the style and starting value
1123 # and unlimited nesting.
1124 self._enum_cnt += 1
1126 enum_style = {'arabic':'arabic',
1127 'loweralpha':'alph',
1128 'upperalpha':'Alph',
1129 'lowerroman':'roman',
1130 'upperroman':'Roman' }
1131 enum_suffix = ""
1132 if node.has_key('suffix'):
1133 enum_suffix = node['suffix']
1134 enum_prefix = ""
1135 if node.has_key('prefix'):
1136 enum_prefix = node['prefix']
1137 if self.compound_enumerators:
1138 pref = ""
1139 if self.section_prefix_for_enumerators and self.section_level:
1140 for i in range(self.section_level):
1141 pref += '%d.' % self._section_number[i]
1142 pref = pref[:-1] + self.section_enumerator_separator
1143 enum_prefix += pref
1144 for counter in self._enumeration_counters:
1145 enum_prefix += counter + '.'
1146 enum_type = "arabic"
1147 if node.has_key('enumtype'):
1148 enum_type = node['enumtype']
1149 if enum_style.has_key(enum_type):
1150 enum_type = enum_style[enum_type]
1151 counter_name = "listcnt%d" % self._enum_cnt;
1152 self._enumeration_counters.append("\\%s{%s}" % (enum_type,counter_name))
1153 self.body.append('\\newcounter{%s}\n' % counter_name)
1154 self.body.append('\\begin{list}{%s\\%s{%s}%s}\n' % \
1155 (enum_prefix,enum_type,counter_name,enum_suffix))
1156 self.body.append('{\n')
1157 self.body.append('\\usecounter{%s}\n' % counter_name)
1158 # set start after usecounter, because it initializes to zero.
1159 if node.has_key('start'):
1160 self.body.append('\\addtocounter{%s}{%d}\n' \
1161 % (counter_name,node['start']-1))
1162 ## set rightmargin equal to leftmargin
1163 self.body.append('\\setlength{\\rightmargin}{\\leftmargin}\n')
1164 self.body.append('}\n')
1166 def depart_enumerated_list(self, node):
1167 self.body.append('\\end{list}\n')
1168 self._enumeration_counters.pop()
1170 def visit_error(self, node):
1171 self.visit_admonition(node, 'error')
1173 def depart_error(self, node):
1174 self.depart_admonition()
1176 def visit_field(self, node):
1177 # real output is done in siblings: _argument, _body, _name
1178 pass
1180 def depart_field(self, node):
1181 self.body.append('\n')
1182 ##self.body.append('%[depart_field]\n')
1184 def visit_field_argument(self, node):
1185 self.body.append('%[visit_field_argument]\n')
1187 def depart_field_argument(self, node):
1188 self.body.append('%[depart_field_argument]\n')
1190 def visit_field_body(self, node):
1191 # BUG by attach as text we loose references.
1192 if self.docinfo:
1193 self.docinfo.append('%s \\\\\n' % node.astext())
1194 raise nodes.SkipNode
1195 # BUG: what happens if not docinfo
1197 def depart_field_body(self, node):
1198 self.body.append( '\n' )
1200 def visit_field_list(self, node):
1201 if not self.docinfo:
1202 self.body.append('\\begin{quote}\n')
1203 self.body.append('\\begin{description}\n')
1205 def depart_field_list(self, node):
1206 if not self.docinfo:
1207 self.body.append('\\end{description}\n')
1208 self.body.append('\\end{quote}\n')
1210 def visit_field_name(self, node):
1211 # BUG this duplicates docinfo_item
1212 if self.docinfo:
1213 self.docinfo.append('\\textbf{%s}: &\n\t' % node.astext())
1214 raise nodes.SkipNode
1215 else:
1216 self.body.append('\\item [')
1218 def depart_field_name(self, node):
1219 if not self.docinfo:
1220 self.body.append(':]')
1222 def visit_figure(self, node):
1223 self.body.append( '\\begin{figure}[htbp]\\begin{center}\n' )
1225 def depart_figure(self, node):
1226 self.body.append( '\\end{center}\\end{figure}\n' )
1228 def visit_footer(self, node):
1229 self.context.append(len(self.body))
1231 def depart_footer(self, node):
1232 start = self.context.pop()
1233 footer = (['\n\\begin{center}\small\n']
1234 + self.body[start:] + ['\n\\end{center}\n'])
1235 self.body_suffix[:0] = footer
1236 del self.body[start:]
1238 def visit_footnote(self, node):
1239 if self.use_latex_footnotes:
1240 num,text = node.astext().split(None,1)
1241 num = self.encode(num.strip())
1242 self.body.append('\\footnotetext['+num+']')
1243 self.body.append('{'+self.encode(text)+'}\n')
1244 raise nodes.SkipNode
1245 else:
1246 self.body.append('\\begin{figure}[b]')
1247 self.body.append('\\hypertarget{%s}' % node['id'])
1249 def depart_footnote(self, node):
1250 if self.use_latex_footnotes:
1251 self.body.append('}')
1252 else:
1253 self.body.append('\\end{figure}\n')
1255 def visit_footnote_reference(self, node):
1256 if self.use_latex_footnotes:
1257 self.body.append("\\footnotemark["+self.encode(node.astext())+"]")
1258 raise nodes.SkipNode
1259 return
1260 href = ''
1261 if node.has_key('refid'):
1262 href = node['refid']
1263 elif node.has_key('refname'):
1264 href = self.document.nameids[node['refname']]
1265 format = self.settings.footnote_references
1266 if format == 'brackets':
1267 suffix = '['
1268 self.context.append(']')
1269 elif format == 'superscript':
1270 suffix = '\\raisebox{.5em}[0em]{\\scriptsize'
1271 self.context.append('}')
1272 else: # shouldn't happen
1273 raise AssertionError('Illegal footnote reference format.')
1274 self.body.append('%s\\hyperlink{%s}{' % (suffix,href))
1276 def depart_footnote_reference(self, node):
1277 if self.use_latex_footnotes:
1278 return
1279 self.body.append('}%s' % self.context.pop())
1281 # elements generated by the framework e.g. section numbers.
1282 def visit_generated(self, node):
1283 pass
1285 def depart_generated(self, node):
1286 pass
1288 def visit_header(self, node):
1289 self.context.append(len(self.body))
1291 def depart_header(self, node):
1292 start = self.context.pop()
1293 self.body_prefix.append('\n\\verb|begin_header|\n')
1294 self.body_prefix.extend(self.body[start:])
1295 self.body_prefix.append('\n\\verb|end_header|\n')
1296 del self.body[start:]
1298 def visit_hint(self, node):
1299 self.visit_admonition(node, 'hint')
1301 def depart_hint(self, node):
1302 self.depart_admonition()
1304 def visit_image(self, node):
1305 attrs = node.attributes
1306 pre = [] # in reverse order
1307 post = ['\\includegraphics{%s}' % attrs['uri']]
1308 inline = isinstance(node.parent, nodes.TextElement)
1309 if 'scale' in attrs:
1310 # Could also be done with ``scale`` option to
1311 # ``\includegraphics``; doing it this way for consistency.
1312 pre.append('\\scalebox{%f}{' % (attrs['scale'] / 100.0,))
1313 post.append('}')
1314 if 'align' in attrs:
1315 align_prepost = {
1316 # By default latex aligns the top of an image.
1317 (1, 'top'): ('', ''),
1318 (1, 'middle'): ('\\raisebox{-0.5\\height}{', '}'),
1319 (1, 'bottom'): ('\\raisebox{-\\height}{', '}'),
1320 (0, 'center'): ('{\\hfill', '\\hfill}'),
1321 # These 2 don't exactly do the right thing. The image should
1322 # be floated alongside the paragraph. See
1323 # http://www.w3.org/TR/html4/struct/objects.html#adef-align-IMG
1324 (0, 'left'): ('{', '\\hfill}'),
1325 (0, 'right'): ('{\\hfill', '}'),}
1326 try:
1327 pre.append(align_prepost[inline, attrs['align']][0])
1328 post.append(align_prepost[inline, attrs['align']][1])
1329 except KeyError:
1330 pass # XXX complain here?
1331 if not inline:
1332 pre.append('\n')
1333 post.append('\n')
1334 pre.reverse()
1335 self.body.extend(pre + post)
1337 def depart_image(self, node):
1338 pass
1340 def visit_important(self, node):
1341 self.visit_admonition(node, 'important')
1343 def depart_important(self, node):
1344 self.depart_admonition()
1346 def visit_interpreted(self, node):
1347 # @@@ Incomplete, pending a proper implementation on the
1348 # Parser/Reader end.
1349 self.visit_literal(node)
1351 def depart_interpreted(self, node):
1352 self.depart_literal(node)
1354 def visit_label(self, node):
1355 # footnote/citation label
1356 self.body.append('[')
1358 def depart_label(self, node):
1359 self.body.append(']')
1361 def visit_legend(self, node):
1362 self.body.append('{\\small ')
1364 def depart_legend(self, node):
1365 self.body.append('}')
1367 def visit_line_block(self, node):
1368 """line-block:
1369 * whitespace (including linebreaks) is significant
1370 * inline markup is supported.
1371 * serif typeface
1374 self.body.append('\\begin{flushleft}\n')
1375 self.insert_none_breaking_blanks = 1
1376 # mbox would stop LaTeX from wrapping long lines.
1377 # but line_blocks are allowed to wrap.
1378 self.line_block_without_mbox = 1
1379 if self.line_block_without_mbox:
1380 self.insert_newline = 1
1381 else:
1382 self.mbox_newline = 1
1383 self.body.append('\\mbox{')
1385 def depart_line_block(self, node):
1386 if self.line_block_without_mbox:
1387 self.insert_newline = 0
1388 else:
1389 self.body.append('}')
1390 self.mbox_newline = 0
1391 self.insert_none_breaking_blanks = 0
1392 self.body.append('\n\\end{flushleft}\n')
1394 def visit_list_item(self, node):
1395 # HACK append "{}" in case the next character is "[", which would break
1396 # LaTeX's list environment (no numbering and the "[" is not printed).
1397 self.body.append('\\item {} ')
1399 def depart_list_item(self, node):
1400 self.body.append('\n')
1402 def visit_literal(self, node):
1403 self.literal = 1
1404 self.body.append('\\texttt{')
1406 def depart_literal(self, node):
1407 self.body.append('}')
1408 self.literal = 0
1410 def visit_literal_block(self, node):
1412 Render a literal-block.
1414 Literal blocks are used for "::"-prefixed literal-indented
1415 blocks of text, where the inline markup is not recognized,
1416 but are also the product of the parsed-literal directive,
1417 where the markup is respected.
1419 # In both cases, we want to use a typewriter/monospaced typeface.
1420 # For "real" literal-blocks, we can use \verbatim, while for all
1421 # the others we must use \mbox.
1423 # We can distinguish between the two kinds by the number of
1424 # siblings the compose this node: if it is composed by a
1425 # single element, it's surely is either a real one, otherwise
1426 # it's a parsed-literal that does not contain any markup.
1428 if (self.settings.use_verbatim_when_possible and (len(node) == 1)
1429 # in case of a parsed-literal containing just a "**bold**" word:
1430 and isinstance(node[0], nodes.Text)):
1431 self.verbatim = 1
1432 self.body.append('\\begin{verbatim}\n')
1433 else:
1434 self.literal_block = 1
1435 self.insert_none_breaking_blanks = 1
1436 if self.active_table.is_open():
1437 self.body.append('\n{\\ttfamily \\raggedright \\noindent\n')
1438 else:
1439 self.body.append('\\begin{ttfamily}')
1440 self.body.append('\\begin{flushleft}\n')
1441 # * obey..: is from julien and never worked for me (grubert).
1442 # self.body.append('{\\obeylines\\obeyspaces\\ttfamily\n')
1444 def depart_literal_block(self, node):
1445 if self.verbatim:
1446 self.body.append('\n\\end{verbatim}\n')
1447 self.verbatim = 0
1448 elif self.active_table.is_open():
1449 self.body.append('\n}\n')
1450 else:
1451 self.body.append('\n')
1452 self.body.append('\\end{flushleft}')
1453 self.body.append('\\end{ttfamily}\n')
1454 self.insert_none_breaking_blanks = 0
1455 # obey end: self.body.append('}\n')
1456 self.literal_block = 0
1458 def visit_meta(self, node):
1459 self.body.append('[visit_meta]\n')
1460 # BUG maybe set keywords for pdf
1461 ##self.head.append(self.starttag(node, 'meta', **node.attributes))
1463 def depart_meta(self, node):
1464 self.body.append('[depart_meta]\n')
1466 def visit_note(self, node):
1467 self.visit_admonition(node, 'note')
1469 def depart_note(self, node):
1470 self.depart_admonition()
1472 def visit_option(self, node):
1473 if self.context[-1]:
1474 # this is not the first option
1475 self.body.append(', ')
1477 def depart_option(self, node):
1478 # flag tha the first option is done.
1479 self.context[-1] += 1
1481 def visit_option_argument(self, node):
1482 """The delimiter betweeen an option and its argument."""
1483 self.body.append(node.get('delimiter', ' '))
1485 def depart_option_argument(self, node):
1486 pass
1488 def visit_option_group(self, node):
1489 if self.use_optionlist_for_option_list:
1490 self.body.append('\\item [')
1491 else:
1492 if len(node.astext()) > 14:
1493 self.body.append('\\multicolumn{2}{l}{')
1494 self.context.append('} \\\\\n ')
1495 else:
1496 self.context.append('')
1497 self.body.append('\\texttt{')
1498 # flag for first option
1499 self.context.append(0)
1501 def depart_option_group(self, node):
1502 self.context.pop() # the flag
1503 if self.use_optionlist_for_option_list:
1504 self.body.append('] ')
1505 else:
1506 self.body.append('}')
1507 self.body.append(self.context.pop())
1509 def visit_option_list(self, node):
1510 self.body.append('% [option list]\n')
1511 if self.use_optionlist_for_option_list:
1512 self.body.append('\\begin{optionlist}{3cm}\n')
1513 else:
1514 self.body.append('\\begin{center}\n')
1515 # BUG: use admwidth or make it relative to textwidth ?
1516 self.body.append('\\begin{tabularx}{.9\\linewidth}{lX}\n')
1518 def depart_option_list(self, node):
1519 if self.use_optionlist_for_option_list:
1520 self.body.append('\\end{optionlist}\n')
1521 else:
1522 self.body.append('\\end{tabularx}\n')
1523 self.body.append('\\end{center}\n')
1525 def visit_option_list_item(self, node):
1526 pass
1528 def depart_option_list_item(self, node):
1529 if not self.use_optionlist_for_option_list:
1530 self.body.append('\\\\\n')
1532 def visit_option_string(self, node):
1533 ##self.body.append(self.starttag(node, 'span', '', CLASS='option'))
1534 pass
1536 def depart_option_string(self, node):
1537 ##self.body.append('</span>')
1538 pass
1540 def visit_organization(self, node):
1541 self.visit_docinfo_item(node, 'organization')
1543 def depart_organization(self, node):
1544 self.depart_docinfo_item(node)
1546 def visit_paragraph(self, node):
1547 if not self.topic_class == 'contents':
1548 self.body.append('\n')
1550 def depart_paragraph(self, node):
1551 self.body.append('\n')
1553 def visit_problematic(self, node):
1554 self.body.append('{\\color{red}\\bfseries{}')
1556 def depart_problematic(self, node):
1557 self.body.append('}')
1559 def visit_raw(self, node):
1560 if node.has_key('format') and node['format'].lower() == 'latex':
1561 self.body.append(node.astext())
1562 raise nodes.SkipNode
1564 def visit_reference(self, node):
1565 # BUG: hash_char "#" is trouble some in LaTeX.
1566 # mbox and other environment do not like the '#'.
1567 hash_char = '\\#'
1568 if node.has_key('refuri'):
1569 href = node['refuri'].replace('#',hash_char)
1570 elif node.has_key('refid'):
1571 href = hash_char + node['refid']
1572 elif node.has_key('refname'):
1573 href = hash_char + self.document.nameids[node['refname']]
1574 else:
1575 raise AssertionError('Unknown reference.')
1576 self.body.append('\\href{%s}{' % href)
1578 def depart_reference(self, node):
1579 self.body.append('}')
1581 def visit_revision(self, node):
1582 self.visit_docinfo_item(node, 'revision')
1584 def depart_revision(self, node):
1585 self.depart_docinfo_item(node)
1587 def visit_section(self, node):
1588 self.section_level += 1
1589 # Initialize counter for potential subsections:
1590 self._section_number.append(0)
1591 # Counter for this section's level (initialized by parent section):
1592 self._section_number[self.section_level - 1] += 1
1594 def depart_section(self, node):
1595 # Remove counter for potential subsections:
1596 self._section_number.pop()
1597 self.section_level -= 1
1599 def visit_sidebar(self, node):
1600 # BUG: this is just a hack to make sidebars render something
1601 self.body.append('\n\\setlength{\\locallinewidth}{0.9\\admonitionwidth}\n')
1602 self.body.append('\\begin{center}\\begin{sffamily}\n')
1603 self.body.append('\\fbox{\\colorbox[gray]{0.80}{\\parbox{\\admonitionwidth}{\n')
1605 def depart_sidebar(self, node):
1606 self.body.append('}}}\n') # end parbox colorbox fbox
1607 self.body.append('\\end{sffamily}\n\\end{center}\n');
1608 self.body.append('\n\\setlength{\\locallinewidth}{\\linewidth}\n')
1611 attribution_formats = {'dash': ('---', ''),
1612 'parentheses': ('(', ')'),
1613 'parens': ('(', ')'),
1614 'none': ('', '')}
1616 def visit_attribution(self, node):
1617 prefix, suffix = self.attribution_formats[self.settings.attribution]
1618 self.body.append('\n\\begin{flushright}\n')
1619 self.body.append(prefix)
1620 self.context.append(suffix)
1622 def depart_attribution(self, node):
1623 self.body.append(self.context.pop() + '\n')
1624 self.body.append('\\end{flushright}\n')
1626 def visit_status(self, node):
1627 self.visit_docinfo_item(node, 'status')
1629 def depart_status(self, node):
1630 self.depart_docinfo_item(node)
1632 def visit_strong(self, node):
1633 self.body.append('\\textbf{')
1634 self.literal_block_stack.append('\\textbf{')
1636 def depart_strong(self, node):
1637 self.body.append('}')
1638 self.literal_block_stack.pop()
1640 def visit_substitution_definition(self, node):
1641 raise nodes.SkipNode
1643 def visit_substitution_reference(self, node):
1644 self.unimplemented_visit(node)
1646 def visit_subtitle(self, node):
1647 if isinstance(node.parent, nodes.sidebar):
1648 self.body.append('~\\\\\n\\textbf{')
1649 self.context.append('}\n\\smallskip\n')
1650 else:
1651 self.title = self.title + \
1652 '\\\\\n\\large{%s}\n' % self.encode(node.astext())
1653 raise nodes.SkipNode
1655 def depart_subtitle(self, node):
1656 if isinstance(node.parent, nodes.sidebar):
1657 self.body.append(self.context.pop())
1659 def visit_system_message(self, node):
1660 if node['level'] < self.document.reporter['writer'].report_level:
1661 raise nodes.SkipNode
1663 def depart_system_message(self, node):
1664 self.body.append('\n')
1666 def visit_table(self, node):
1667 if self.active_table.is_open():
1668 print 'nested tables are not supported'
1669 raise AssertionError
1670 self.active_table.open()
1671 self.body.append('\n' + self.active_table.get_opening())
1673 def depart_table(self, node):
1674 self.body.append(self.active_table.get_closing() + '\n')
1675 self.active_table.close()
1677 def visit_target(self, node):
1678 # BUG: why not (refuri or refid or refname) means not footnote ?
1679 if not (node.has_key('refuri') or node.has_key('refid')
1680 or node.has_key('refname')):
1681 self.body.append('\\hypertarget{%s}{' % node['id'])
1682 self.context.append('}')
1683 else:
1684 self.context.append('')
1686 def depart_target(self, node):
1687 self.body.append(self.context.pop())
1689 def visit_tbody(self, node):
1690 # BUG write preamble if not yet done (colspecs not [])
1691 # for tables without heads.
1692 if not self.active_table.get('preamble written'):
1693 self.visit_thead(None)
1694 # self.depart_thead(None)
1696 def depart_tbody(self, node):
1697 pass
1699 def visit_term(self, node):
1700 self.body.append('\\item[')
1702 def depart_term(self, node):
1703 # definition list term.
1704 self.body.append(']\n')
1706 def visit_tgroup(self, node):
1707 #self.body.append(self.starttag(node, 'colgroup'))
1708 #self.context.append('</colgroup>\n')
1709 pass
1711 def depart_tgroup(self, node):
1712 pass
1714 def visit_thead(self, node):
1715 self.body.append('{%s}\n' % self.active_table.get_colspecs())
1716 if self.active_table.caption:
1717 self.body.append('\\caption{%s}\\\\\n' % self.active_table.caption)
1718 self.active_table.set('preamble written',1)
1719 # TODO longtable supports firsthead and lastfoot too.
1720 self.body.extend(self.active_table.visit_thead())
1722 def depart_thead(self, node):
1723 # the table header written should be on every page
1724 # => \endhead
1725 self.body.extend(self.active_table.depart_thead())
1726 # and the firsthead => \endfirsthead
1727 # BUG i want a "continued from previous page" on every not
1728 # firsthead, but then we need the header twice.
1730 # there is a \endfoot and \endlastfoot too.
1731 # but we need the number of columns to
1732 # self.body.append('\\multicolumn{%d}{c}{"..."}\n' % number_of_columns)
1733 # self.body.append('\\hline\n\\endfoot\n')
1734 # self.body.append('\\hline\n')
1735 # self.body.append('\\endlastfoot\n')
1737 def visit_tip(self, node):
1738 self.visit_admonition(node, 'tip')
1740 def depart_tip(self, node):
1741 self.depart_admonition()
1743 def bookmark(self, node):
1744 """Append latex href and pdfbookmarks for titles.
1746 if node.parent.hasattr('id'):
1747 self.body.append('\\hypertarget{%s}{}\n' % node.parent['id'])
1748 if not self.use_latex_toc:
1749 # BUG level depends on style. pdflatex allows level 0 to 3
1750 # ToC would be the only on level 0 so i choose to decrement the rest.
1751 # "Table of contents" bookmark to see the ToC. To avoid this
1752 # we set all zeroes to one.
1753 l = self.section_level
1754 if l>0:
1755 l = l-1
1756 # pdftex does not like "_" subscripts in titles
1757 text = node.astext().replace("_","\\_")
1758 self.body.append('\\pdfbookmark[%d]{%s}{%s}\n' % \
1759 (l,text,node.parent['id']))
1761 def visit_title(self, node):
1762 """Only 3 section levels are supported by LaTeX article (AFAIR)."""
1764 if isinstance(node.parent, nodes.topic):
1765 # section titles before the table of contents.
1766 self.bookmark(node)
1767 # BUG: latex chokes on center environment with "perhaps a missing item".
1768 # so we use hfill.
1769 self.body.append('\\subsection*{~\\hfill ')
1770 # the closing brace for subsection.
1771 self.context.append('\\hfill ~}\n')
1772 elif isinstance(node.parent, nodes.sidebar):
1773 self.body.append('\\textbf{\\large ')
1774 self.context.append('}\n\\smallskip\n')
1775 elif isinstance(node.parent, nodes.table):
1776 # caption must be written after column spec
1777 self.active_table.caption = node.astext()
1778 raise nodes.SkipNode
1779 elif self.section_level == 0:
1780 # document title
1781 self.title = self.encode(node.astext())
1782 if not self.pdfinfo == None:
1783 self.pdfinfo.append( 'pdftitle={%s}' % self.encode(node.astext()) )
1784 raise nodes.SkipNode
1785 else:
1786 self.body.append('\n\n')
1787 self.body.append('%' + '_' * 75)
1788 self.body.append('\n\n')
1789 self.bookmark(node)
1791 if self.use_latex_toc:
1792 section_star = ""
1793 else:
1794 section_star = "*"
1796 section_name = self.d_class.section(self.section_level)
1797 self.body.append('\\%s%s{' % (section_name, section_star))
1799 self.context.append('}\n')
1801 def depart_title(self, node):
1802 self.body.append(self.context.pop())
1804 def visit_topic(self, node):
1805 self.topic_class = node.get('class')
1806 if self.use_latex_toc:
1807 self.body.append('\\tableofcontents\n\n\\bigskip\n')
1808 self.topic_class = ''
1809 raise nodes.SkipNode
1811 def depart_topic(self, node):
1812 self.topic_class = ''
1813 self.body.append('\n')
1815 def visit_rubric(self, node):
1816 self.body.append('\\rubric{')
1817 self.context.append('}\n')
1819 def depart_rubric(self, node):
1820 self.body.append(self.context.pop())
1822 def visit_transition(self, node):
1823 self.body.append('\n\n')
1824 self.body.append('%' + '_' * 75)
1825 self.body.append('\n\\hspace*{\\fill}\\hrulefill\\hspace*{\\fill}')
1826 self.body.append('\n\n')
1828 def depart_transition(self, node):
1829 pass
1831 def visit_version(self, node):
1832 self.visit_docinfo_item(node, 'version')
1834 def depart_version(self, node):
1835 self.depart_docinfo_item(node)
1837 def visit_warning(self, node):
1838 self.visit_admonition(node, 'warning')
1840 def depart_warning(self, node):
1841 self.depart_admonition()
1843 def unimplemented_visit(self, node):
1844 raise NotImplementedError('visiting unimplemented node type: %s'
1845 % node.__class__.__name__)
1847 # def unknown_visit(self, node):
1848 # def default_visit(self, node):
1850 # vim: set ts=4 et ai :