Add attribution and align to the right.
[docutils.git] / docutils / writers / latex2e.py
blob29c15012195e1e509794084e92353638d2253515
1 #! /usr/bin/env python
3 """
4 :Author: Engelbert Gruber
5 :Contact: grubert@users.sourceforge.net
6 :Revision: $Revision$
7 :Date: $Date$
8 :Copyright: This module has been placed in the public domain.
10 LaTeX2e document tree Writer.
11 """
13 __docformat__ = 'reStructuredText'
15 # convention deactivate code by two # e.g. ##.
17 import sys
18 import time
19 import re
20 import string
21 from types import ListType
22 from docutils import writers, nodes, languages
24 class Writer(writers.Writer):
26 supported = ('latex','latex2e')
27 """Formats this writer supports."""
29 settings_spec = (
30 'LaTeX-Specific Options',
31 'The LaTeX "--output-encoding" default is "latin-1".',
32 (('Specify documentclass. Default is "article".',
33 ['--documentclass'],
34 {'default': 'article', }),
35 ('Format for footnote references: one of "superscript" or '
36 '"brackets". Default is "brackets".',
37 ['--footnote-references'],
38 {'choices': ['superscript', 'brackets'], 'default': 'brackets',
39 'metavar': '<format>'}),
40 ('Format for block quote attributions: one of "dash" (em-dash '
41 'prefix), "parentheses"/"parens", or "none". Default is "dash".',
42 ['--attribution'],
43 {'choices': ['dash', 'parentheses', 'parens', 'none'],
44 'default': 'dash', 'metavar': '<format>'}),
45 ('Specify a stylesheet file. The file will be "input" by latex '
46 'in the document header. Default is "style.tex". '
47 'If this is set to "" disables input.'
48 'Overridden by --stylesheet-path.',
49 ['--stylesheet'],
50 {'default': 'style.tex', 'metavar': '<file>'}),
51 ('Specify a stylesheet file, relative to the current working '
52 'directory.'
53 'Overrides --stylesheet.',
54 ['--stylesheet-path'],
55 {'metavar': '<file>'}),
56 ('Link to the stylesheet in the output LaTeX file. This is the '
57 'default.',
58 ['--link-stylesheet'],
59 {'dest': 'embed_stylesheet', 'action': 'store_false'}),
60 ('Embed the stylesheet in the output LaTeX file. The stylesheet '
61 'file must be accessible during processing (--stylesheet-path is '
62 'recommended).',
63 ['--embed-stylesheet'],
64 {'action': 'store_true'}),
65 ('Table of contents by docutils (default) or latex. Latex(writer) '
66 'supports only one ToC per document, but docutils does not write '
67 'pagenumbers.',
68 ['--use-latex-toc'], {'default': 0}),
69 ('Color of any hyperlinks embedded in text '
70 '(default: "blue", "0" to disable).',
71 ['--hyperlink-color'], {'default': 'blue'}),
72 ('Remove trailing blanks and line ends from the element before a'
73 'footnote_reference (default: off, "1" to enable).',
74 ['--snap-footnote-refs'], {'default': 0}),
78 settings_default_overrides = {'output_encoding': 'latin-1'}
80 output = None
81 """Final translated form of `document`."""
83 def translate(self):
84 visitor = LaTeXTranslator(self.document)
85 self.document.walkabout(visitor)
86 self.output = visitor.astext()
87 self.head_prefix = visitor.head_prefix
88 self.head = visitor.head
89 self.body_prefix = visitor.body_prefix
90 self.body = visitor.body
91 self.body_suffix = visitor.body_suffix
93 """
94 Notes on LaTeX
95 --------------
97 * latex does not support multiple tocs in one document.
98 (might be no limitation except for docutils documentation)
100 * width
102 * linewidth - width of a line in the local environment
103 * textwidth - the width of text on the page
105 Maybe always use linewidth ?
106 """
108 class Babel:
109 """Language specifics for LaTeX."""
110 # country code by a.schlock.
111 # partly manually converted from iso and babel stuff, dialects and some
112 _ISO639_TO_BABEL = {
113 'no': 'norsk', #XXX added by hand ( forget about nynorsk?)
114 'gd': 'scottish', #XXX added by hand
115 'hu': 'magyar', #XXX added by hand
116 'pt': 'portuguese',#XXX added by hand
117 'sl': 'slovenian',
118 'af': 'afrikaans',
119 'bg': 'bulgarian',
120 'br': 'breton',
121 'ca': 'catalan',
122 'cs': 'czech',
123 'cy': 'welsh',
124 'da': 'danish',
125 'fr': 'french',
126 # french, francais, canadien, acadian
127 'de': 'ngerman', #XXX rather than german
128 # ngerman, naustrian, german, germanb, austrian
129 'el': 'greek',
130 'en': 'english',
131 # english, USenglish, american, UKenglish, british, canadian
132 'eo': 'esperanto',
133 'es': 'spanish',
134 'et': 'estonian',
135 'eu': 'basque',
136 'fi': 'finnish',
137 'ga': 'irish',
138 'gl': 'galician',
139 'he': 'hebrew',
140 'hr': 'croatian',
141 'hu': 'hungarian',
142 'is': 'icelandic',
143 'it': 'italian',
144 'la': 'latin',
145 'nl': 'dutch',
146 'pl': 'polish',
147 'pt': 'portuguese',
148 'ro': 'romanian',
149 'ru': 'russian',
150 'sk': 'slovak',
151 'sr': 'serbian',
152 'sv': 'swedish',
153 'tr': 'turkish',
154 'uk': 'ukrainian'
157 def __init__(self,lang):
158 self.language = lang
159 # pdflatex does not produce double quotes for ngerman in tt.
160 self.double_quote_replacment = None
161 if re.search('^de',self.language):
162 # maybe use: {\glqq} {\grqq}.
163 self.quotes = ("\"`", "\"'")
164 self.double_quote_replacment = "{\\dq}"
165 else:
166 self.quotes = ("``", "''")
167 self.quote_index = 0
169 def next_quote(self):
170 q = self.quotes[self.quote_index]
171 self.quote_index = (self.quote_index+1)%2
172 return q
174 def quote_quotes(self,text):
175 t = None
176 for part in text.split('"'):
177 if t == None:
178 t = part
179 else:
180 t += self.next_quote() + part
181 return t
183 def double_quotes_in_tt (self,text):
184 if not self.double_quote_replacment:
185 return text
186 return text.replace('"', self.double_quote_replacment)
188 def get_language(self):
189 if self._ISO639_TO_BABEL.has_key(self.language):
190 return self._ISO639_TO_BABEL[self.language]
191 else:
192 # support dialects.
193 l = self.language.split("_")[0]
194 if self._ISO639_TO_BABEL.has_key(l):
195 return self._ISO639_TO_BABEL[l]
196 return None
199 latex_headings = {
200 'optionlist_environment' : [
201 '\\newcommand{\\optionlistlabel}[1]{\\bf #1 \\hfill}\n'
202 '\\newenvironment{optionlist}[1]\n'
203 '{\\begin{list}{}\n'
204 ' {\\setlength{\\labelwidth}{#1}\n'
205 ' \\setlength{\\rightmargin}{1cm}\n'
206 ' \\setlength{\\leftmargin}{\\rightmargin}\n'
207 ' \\addtolength{\\leftmargin}{\\labelwidth}\n'
208 ' \\addtolength{\\leftmargin}{\\labelsep}\n'
209 ' \\renewcommand{\\makelabel}{\\optionlistlabel}}\n'
210 '}{\\end{list}}\n',
212 'footnote_floats' : [
213 '% begin: floats for footnotes tweaking.\n',
214 '\\setlength{\\floatsep}{0.5em}\n',
215 '\\setlength{\\textfloatsep}{\\fill}\n',
216 '\\addtolength{\\textfloatsep}{3em}\n',
217 '\\renewcommand{\\textfraction}{0.5}\n',
218 '\\renewcommand{\\topfraction}{0.5}\n',
219 '\\renewcommand{\\bottomfraction}{0.5}\n',
220 '\\setcounter{totalnumber}{50}\n',
221 '\\setcounter{topnumber}{50}\n',
222 '\\setcounter{bottomnumber}{50}\n',
223 '% end floats for footnotes\n',
228 class LaTeXTranslator(nodes.NodeVisitor):
229 # When options are given to the documentclass, latex will pass them
230 # to other packages, as done with babel.
231 # Dummy settings might be taken from document settings
233 d_options = '10pt' # papersize, fontsize
234 d_paper = 'a4paper'
235 d_margins = '2cm'
237 latex_head = '\\documentclass[%s]{%s}\n'
238 encoding = '\\usepackage[latin1]{inputenc}\n'
239 linking = '\\usepackage[colorlinks=%s,linkcolor=%s,urlcolor=%s]{hyperref}\n'
240 geometry = '\\usepackage[%s,margin=%s,nohead]{geometry}\n'
241 stylesheet = '\\input{%s}\n'
242 # add a generated on day , machine by user using docutils version.
243 generator = '%% generator Docutils: http://docutils.sourceforge.net/\n'
245 # use latex tableofcontents or let docutils do it.
246 use_latex_toc = 0
247 # table kind: if 0 tabularx (single page), 1 longtable
248 # maybe should be decided on row count.
249 use_longtable = 1
250 # TODO: use mixins for different implementations.
251 # list environment for option-list. else tabularx
252 use_optionlist_for_option_list = 1
253 # list environment for docinfo. else tabularx
254 use_optionlist_for_docinfo = 0 # NOT YET IN USE
256 # default link color
257 hyperlink_color = "blue"
259 def __init__(self, document):
260 nodes.NodeVisitor.__init__(self, document)
261 self.settings = settings = document.settings
262 self.use_latex_toc = settings.use_latex_toc
263 self.hyperlink_color = settings.hyperlink_color
264 if self.hyperlink_color == '0':
265 self.hyperlink_color = 'black'
266 self.colorlinks = 'false'
267 else:
268 self.colorlinks = 'true'
270 # language: labels, bibliographic_fields, and author_separators.
271 # to allow writing labes for specific languages.
272 self.language = languages.get_language(settings.language_code)
273 self.babel = Babel(settings.language_code)
274 self.author_separator = self.language.author_separators[0]
275 if self.babel.get_language():
276 self.d_options += ',%s' % \
277 self.babel.get_language()
278 self.head_prefix = [
279 self.latex_head % (self.d_options,self.settings.documentclass),
280 '\\usepackage{babel}\n', # language is in documents settings.
281 '\\usepackage{shortvrb}\n', # allows verb in footnotes.
282 self.encoding,
283 # * tabularx: for docinfo, automatic width of columns, always on one page.
284 '\\usepackage{tabularx}\n',
285 '\\usepackage{longtable}\n',
286 # possible other packages.
287 # * fancyhdr
288 # * ltxtable is a combination of tabularx and longtable (pagebreaks).
289 # but ??
291 # extra space between text in tables and the line above them
292 '\\setlength{\\extrarowheight}{2pt}\n',
293 '\\usepackage{amsmath}\n', # what fore amsmath.
294 '\\usepackage{graphicx}\n',
295 '\\usepackage{color}\n',
296 '\\usepackage{multirow}\n',
297 self.linking % (self.colorlinks, self.hyperlink_color, self.hyperlink_color),
298 # geometry and fonts might go into style.tex.
299 self.geometry % (self.d_paper, self.d_margins),
301 self.generator,
302 # latex lengths
303 '\\newlength{\\admonitionwidth}\n',
304 '\\setlength{\\admonitionwidth}{0.9\\textwidth}\n'
305 # width for docinfo tablewidth
306 '\\newlength{\\docinfowidth}\n',
307 '\\setlength{\\docinfowidth}{0.9\\textwidth}\n'
309 self.head_prefix.extend( latex_headings['optionlist_environment'] )
310 self.head_prefix.extend( latex_headings['footnote_floats'] )
311 ## stylesheet is last: so it might be possible to overwrite defaults.
312 stylesheet = self.get_stylesheet_reference()
313 if stylesheet:
314 self.head_prefix.append(self.stylesheet % (stylesheet))
316 if self.linking: # and maybe check for pdf
317 self.pdfinfo = [ ]
318 self.pdfauthor = None
319 # pdftitle, pdfsubject, pdfauthor, pdfkeywords, pdfcreator, pdfproducer
320 else:
321 self.pdfinfo = None
322 # NOTE: Latex wants a date and an author, rst puts this into
323 # docinfo, so normally we donot want latex author/date handling.
324 # latex article has its own handling of date and author, deactivate.
325 self.latex_docinfo = 0
326 self.head = [ ]
327 if not self.latex_docinfo:
328 self.head.extend( [ '\\author{}\n', '\\date{}\n' ] )
329 self.body_prefix = ['\\raggedbottom\n']
330 # separate title, so we can appen subtitle.
331 self.title = ""
332 self.body = []
333 self.body_suffix = ['\n']
334 self.section_level = 0
335 self.context = []
336 self.topic_class = ''
337 # column specification for tables
338 self.colspecs = []
339 # Flags to encode
340 # ---------------
341 # verbatim: to tell encode not to encode.
342 self.verbatim = 0
343 # insert_newline: to tell encode to replace blanks by "~".
344 self.insert_none_breaking_blanks = 0
345 # insert_newline: to tell encode to add latex newline.
346 self.insert_newline = 0
347 # mbox_newline: to tell encode to add mbox and newline.
348 self.mbox_newline = 0
350 # enumeration is done by list environment.
351 self._enum_cnt = 0
352 # docinfo.
353 self.docinfo = None
354 # inside literal block: no quote mangling.
355 self.literal_block = 0
356 self.literal = 0
358 def get_stylesheet_reference(self):
359 if self.settings.stylesheet_path:
360 return self.settings.stylesheet_path
361 else:
362 return self.settings.stylesheet
364 def language_label(self, docutil_label):
365 return self.language.labels[docutil_label]
367 def encode(self, text):
369 Encode special characters in `text` & return.
370 # $ % & ~ _ ^ \ { }
371 Escaping with a backslash does not help with backslashes, ~ and ^.
373 < > are only available in math-mode (really ?)
374 $ starts math- mode.
375 AND quotes:
378 if self.verbatim:
379 return text
380 # compile the regexps once. do it here so one can see them.
382 # first the braces.
383 if not self.__dict__.has_key('encode_re_braces'):
384 self.encode_re_braces = re.compile(r'([{}])')
385 text = self.encode_re_braces.sub(r'{\\\1}',text)
386 if not self.__dict__.has_key('encode_re_bslash'):
387 # find backslash: except in the form '{\{}' or '{\}}'.
388 self.encode_re_bslash = re.compile(r'(?<!{)(\\)(?![{}]})')
389 # then the backslash: except in the form from line above:
390 # either '{\{}' or '{\}}'.
391 text = self.encode_re_bslash.sub(r'{\\textbackslash}', text)
393 # then dollar
394 text = text.replace("$", '{\\$}')
395 # then all that needs math mode
396 text = text.replace("<", '{$<$}')
397 text = text.replace(">", '{$>$}')
398 # then
399 text = text.replace("&", '{\\&}')
400 text = text.replace("_", '{\\_}')
401 # the ^:
402 # * verb|^| does not work in mbox.
403 # * mathmode has wedge. hat{~} would also work.
404 text = text.replace("^", '{\\ensuremath{^\\wedge}}')
405 text = text.replace("%", '{\\%}')
406 text = text.replace("#", '{\\#}')
407 text = text.replace("~", '{\\~{ }}')
408 if self.literal_block or self.literal:
409 # pdflatex does not produce doublequotes for ngerman.
410 text = self.babel.double_quotes_in_tt(text)
411 else:
412 text = self.babel.quote_quotes(text)
413 if self.insert_newline:
414 # HACK: insert a blank before the newline, to avoid
415 # ! LaTeX Error: There's no line here to end.
416 text = text.replace("\n", '~\\\\\n')
417 elif self.mbox_newline:
418 text = text.replace("\n", '}\\\\\n\\mbox{')
419 if self.insert_none_breaking_blanks:
420 text = text.replace(' ', '~')
421 # unicode !!!
422 text = text.replace(u'\u2020', '{$\\dagger$}')
423 return text
425 def attval(self, text,
426 whitespace=re.compile('[\n\r\t\v\f]')):
427 """Cleanse, encode, and return attribute value text."""
428 return self.encode(whitespace.sub(' ', text))
430 def astext(self):
431 if self.pdfinfo:
432 if self.pdfauthor:
433 self.pdfinfo.append('pdfauthor={%s}' % self.pdfauthor)
434 pdfinfo = '\\hypersetup{\n' + ',\n'.join(self.pdfinfo) + '\n}\n'
435 else:
436 pdfinfo = ''
437 title = '\\title{%s}\n' % self.title
438 return ''.join(self.head_prefix + [title]
439 + self.head + [pdfinfo]
440 + self.body_prefix + self.body + self.body_suffix)
442 def visit_Text(self, node):
443 self.body.append(self.encode(node.astext()))
445 def depart_Text(self, node):
446 pass
448 def visit_address(self, node):
449 self.visit_docinfo_item(node, 'address')
451 def depart_address(self, node):
452 self.depart_docinfo_item(node)
454 def visit_admonition(self, node, name):
455 self.body.append('\\begin{center}\\begin{sffamily}\n')
456 self.body.append('\\fbox{\\parbox{\\admonitionwidth}{\n')
457 self.body.append('\\textbf{\\large '+ self.language.labels[name] + '}\n');
458 self.body.append('\\vspace{2mm}\n')
461 def depart_admonition(self):
462 self.body.append('}}\n') # end parbox fbox
463 self.body.append('\\end{sffamily}\n\\end{center}\n');
465 def visit_attention(self, node):
466 self.visit_admonition(node, 'attention')
468 def depart_attention(self, node):
469 self.depart_admonition()
471 def visit_author(self, node):
472 self.visit_docinfo_item(node, 'author')
474 def depart_author(self, node):
475 self.depart_docinfo_item(node)
477 def visit_authors(self, node):
478 # ignore. visit_author is called for each one
479 # self.visit_docinfo_item(node, 'author')
480 pass
482 def depart_authors(self, node):
483 # self.depart_docinfo_item(node)
484 pass
486 def visit_block_quote(self, node):
487 self.body.append( '\\begin{quote}\n')
489 def depart_block_quote(self, node):
490 self.body.append( '\\end{quote}\n')
492 def visit_bullet_list(self, node):
493 if not self.use_latex_toc and self.topic_class == 'contents':
494 self.body.append( '\\begin{list}{}{}\n' )
495 else:
496 self.body.append( '\\begin{itemize}\n' )
498 def depart_bullet_list(self, node):
499 if not self.use_latex_toc and self.topic_class == 'contents':
500 self.body.append( '\\end{list}\n' )
501 else:
502 self.body.append( '\\end{itemize}\n' )
504 def visit_caption(self, node):
505 self.body.append( '\\caption{' )
507 def depart_caption(self, node):
508 self.body.append('}')
510 def visit_caution(self, node):
511 self.visit_admonition(node, 'caution')
513 def depart_caution(self, node):
514 self.depart_admonition()
516 def visit_citation(self, node):
517 self.visit_footnote(node)
519 def depart_citation(self, node):
520 self.depart_footnote(node)
522 def visit_title_reference(self, node):
523 # BUG title-references are what?
524 pass
526 def depart_title_reference(self, node):
527 pass
529 def visit_citation_reference(self, node):
530 href = ''
531 if node.has_key('refid'):
532 href = node['refid']
533 elif node.has_key('refname'):
534 href = self.document.nameids[node['refname']]
535 self.body.append('[\\hyperlink{%s}{' % href)
537 def depart_citation_reference(self, node):
538 self.body.append('}]')
540 def visit_classifier(self, node):
541 self.body.append( '(\\textbf{' )
543 def depart_classifier(self, node):
544 self.body.append( '})\n' )
546 def visit_colspec(self, node):
547 if self.use_longtable:
548 self.colspecs.append(node)
549 else:
550 self.context[-1] += 1
552 def depart_colspec(self, node):
553 pass
555 def visit_comment(self, node,
556 sub=re.compile('\n').sub):
557 """Escape end of line by a ne comment start in comment text."""
558 self.body.append('%% %s \n' % sub('\n% ', node.astext()))
559 raise nodes.SkipNode
561 def visit_contact(self, node):
562 self.visit_docinfo_item(node, 'contact')
564 def depart_contact(self, node):
565 self.depart_docinfo_item(node)
567 def visit_copyright(self, node):
568 self.visit_docinfo_item(node, 'copyright')
570 def depart_copyright(self, node):
571 self.depart_docinfo_item(node)
573 def visit_danger(self, node):
574 self.visit_admonition(node, 'danger')
576 def depart_danger(self, node):
577 self.depart_admonition()
579 def visit_date(self, node):
580 self.visit_docinfo_item(node, 'date')
582 def depart_date(self, node):
583 self.depart_docinfo_item(node)
585 # def visit_decoration(self, node):
586 # pass
588 # def depart_decoration(self, node):
589 # pass
591 def visit_definition(self, node):
592 self.body.append('%[visit_definition]\n')
594 def depart_definition(self, node):
595 self.body.append('\n')
596 self.body.append('%[depart_definition]\n')
598 def visit_definition_list(self, node):
599 self.body.append( '\\begin{description}\n' )
601 def depart_definition_list(self, node):
602 self.body.append( '\\end{description}\n' )
604 def visit_definition_list_item(self, node):
605 self.body.append('%[visit_definition_list_item]\n')
607 def depart_definition_list_item(self, node):
608 self.body.append('%[depart_definition_list_item]\n')
610 def visit_description(self, node):
611 if self.use_optionlist_for_option_list:
612 self.body.append( ' ' )
613 else:
614 self.body.append( ' & ' )
616 def depart_description(self, node):
617 pass
619 def visit_docinfo(self, node):
620 self.docinfo = []
621 self.docinfo.append('%' + '_'*75 + '\n')
622 self.docinfo.append('\\begin{center}\n')
623 self.docinfo.append('\\begin{tabularx}{\\docinfowidth}{lX}\n')
625 def depart_docinfo(self, node):
626 self.docinfo.append('\\end{tabularx}\n')
627 self.docinfo.append('\\end{center}\n')
628 self.body = self.docinfo + self.body
629 # clear docinfo, so field names are no longer appended.
630 self.docinfo = None
631 if self.use_latex_toc:
632 self.body.append('\\tableofcontents\n\n\\bigskip\n')
634 def visit_docinfo_item(self, node, name):
635 if not self.latex_docinfo:
636 self.docinfo.append('\\textbf{%s}: &\n\t' % self.language_label(name))
637 if name == 'author':
638 if not self.pdfinfo == None:
639 if not self.pdfauthor:
640 self.pdfauthor = self.attval(node.astext())
641 else:
642 self.pdfauthor += self.author_separator + self.attval(node.astext())
643 if self.latex_docinfo:
644 self.head.append('\\author{%s}\n' % self.attval(node.astext()))
645 raise nodes.SkipNode
646 elif name == 'date':
647 if self.latex_docinfo:
648 self.head.append('\\date{%s}\n' % self.attval(node.astext()))
649 raise nodes.SkipNode
650 if name == 'address':
651 # BUG will fail if latex_docinfo is set.
652 self.insert_newline = 1
653 self.docinfo.append('{\\raggedright\n')
654 self.context.append(' } \\\\\n')
655 else:
656 self.context.append(' \\\\\n')
657 self.context.append(self.docinfo)
658 self.context.append(len(self.body))
660 def depart_docinfo_item(self, node):
661 size = self.context.pop()
662 dest = self.context.pop()
663 tail = self.context.pop()
664 tail = self.body[size:] + [tail]
665 del self.body[size:]
666 dest.extend(tail)
667 # for address we did set insert_newline
668 self.insert_newline = 0
670 def visit_doctest_block(self, node):
671 self.body.append( '\\begin{verbatim}' )
672 self.verbatim = 1
674 def depart_doctest_block(self, node):
675 self.body.append( '\\end{verbatim}\n' )
676 self.verbatim = 0
678 def visit_document(self, node):
679 self.body_prefix.append('\\begin{document}\n')
680 self.body_prefix.append('\\maketitle\n\n')
681 # alternative use titlepage environment.
682 # \begin{titlepage}
684 def depart_document(self, node):
685 self.body_suffix.append('\\end{document}\n')
687 def visit_emphasis(self, node):
688 self.body.append('\\emph{')
690 def depart_emphasis(self, node):
691 self.body.append('}')
693 def visit_entry(self, node):
694 # cell separation
695 column_one = 1
696 if self.context[-1] > 0:
697 column_one = 0
698 if not column_one:
699 self.body.append(' & ')
701 # multi{row,column}
702 if node.has_key('morerows') and node.has_key('morecols'):
703 raise NotImplementedError('LaTeX can\'t handle cells that'
704 'span multiple rows *and* columns, sorry.')
705 atts = {}
706 if node.has_key('morerows'):
707 count = node['morerows'] + 1
708 self.body.append('\\multirow{%d}*{' % count)
709 self.context.append('}')
710 elif node.has_key('morecols'):
711 # the vertical bar before column is missing if it is the first column.
712 # the one after always.
713 if column_one:
714 bar = '|'
715 else:
716 bar = ''
717 count = node['morecols'] + 1
718 self.body.append('\\multicolumn{%d}{%sl|}{' % (count, bar))
719 self.context.append('}')
720 else:
721 self.context.append('')
723 # header / not header
724 if isinstance(node.parent.parent, nodes.thead):
725 self.body.append('\\textbf{')
726 self.context.append('}')
727 else:
728 self.context.append('')
730 def depart_entry(self, node):
731 self.body.append(self.context.pop()) # header / not header
732 self.body.append(self.context.pop()) # multirow/column
733 self.context[-1] += 1
735 def visit_enumerated_list(self, node):
736 # We create our own enumeration list environment.
737 # This allows to set the style and starting value
738 # and unlimited nesting.
739 self._enum_cnt += 1
741 enum_style = {'arabic':'arabic',
742 'loweralpha':'alph',
743 'upperalpha':'Alph',
744 'lowerroman':'roman',
745 'upperroman':'Roman' }
746 enum_suffix = ""
747 if node.has_key('suffix'):
748 enum_suffix = node['suffix']
749 enum_prefix = ""
750 if node.has_key('prefix'):
751 enum_prefix = node['prefix']
753 enum_type = "arabic"
754 if node.has_key('enumtype'):
755 enum_type = node['enumtype']
756 if enum_style.has_key(enum_type):
757 enum_type = enum_style[enum_type]
758 counter_name = "listcnt%d" % self._enum_cnt;
759 self.body.append('\\newcounter{%s}\n' % counter_name)
760 self.body.append('\\begin{list}{%s\\%s{%s}%s}\n' % \
761 (enum_prefix,enum_type,counter_name,enum_suffix))
762 self.body.append('{\n')
763 self.body.append('\\usecounter{%s}\n' % counter_name)
764 # set start after usecounter, because it initializes to zero.
765 if node.has_key('start'):
766 self.body.append('\\addtocounter{%s}{%d}\n' \
767 % (counter_name,node['start']-1))
768 ## set rightmargin equal to leftmargin
769 self.body.append('\\setlength{\\rightmargin}{\\leftmargin}\n')
770 self.body.append('}\n')
772 def depart_enumerated_list(self, node):
773 self.body.append('\\end{list}\n')
775 def visit_error(self, node):
776 self.visit_admonition(node, 'error')
778 def depart_error(self, node):
779 self.depart_admonition()
781 def visit_field(self, node):
782 # real output is done in siblings: _argument, _body, _name
783 pass
785 def depart_field(self, node):
786 self.body.append('\n')
787 ##self.body.append('%[depart_field]\n')
789 def visit_field_argument(self, node):
790 self.body.append('%[visit_field_argument]\n')
792 def depart_field_argument(self, node):
793 self.body.append('%[depart_field_argument]\n')
795 def visit_field_body(self, node):
796 # BUG by attach as text we loose references.
797 if self.docinfo:
798 self.docinfo.append('%s \\\\\n' % node.astext())
799 raise nodes.SkipNode
800 # BUG: what happens if not docinfo
802 def depart_field_body(self, node):
803 self.body.append( '\n' )
805 def visit_field_list(self, node):
806 if not self.docinfo:
807 self.body.append('\\begin{quote}\n')
808 self.body.append('\\begin{description}\n')
810 def depart_field_list(self, node):
811 if not self.docinfo:
812 self.body.append('\\end{description}\n')
813 self.body.append('\\end{quote}\n')
815 def visit_field_name(self, node):
816 # BUG this duplicates docinfo_item
817 if self.docinfo:
818 self.docinfo.append('\\textbf{%s}: &\n\t' % node.astext())
819 raise nodes.SkipNode
820 else:
821 self.body.append('\\item [')
823 def depart_field_name(self, node):
824 if not self.docinfo:
825 self.body.append(':]')
827 def visit_figure(self, node):
828 self.body.append( '\\begin{figure}\n' )
830 def depart_figure(self, node):
831 self.body.append( '\\end{figure}\n' )
833 def visit_footer(self, node):
834 self.context.append(len(self.body))
836 def depart_footer(self, node):
837 start = self.context.pop()
838 footer = (['\n\\begin{center}\small\n']
839 + self.body[start:] + ['\n\\end{center}\n'])
840 self.body_suffix[:0] = footer
841 del self.body[start:]
843 def visit_footnote(self, node):
844 notename = node['id']
845 self.body.append('\\begin{figure}[b]')
846 self.body.append('\\hypertarget{%s}' % notename)
848 def depart_footnote(self, node):
849 self.body.append('\\end{figure}\n')
851 def visit_footnote_reference(self, node):
852 href = ''
853 if node.has_key('refid'):
854 href = node['refid']
855 elif node.has_key('refname'):
856 href = self.document.nameids[node['refname']]
857 format = self.settings.footnote_references
858 if format == 'brackets':
859 suffix = '['
860 self.context.append(']')
861 elif format == 'superscript':
862 suffix = '\\raisebox{.5em}[0em]{\\scriptsize'
863 self.context.append('}')
864 else: # shouldn't happen
865 raise AssertionError('Illegal footnote reference format.')
866 if self.settings.snap_footnote_refs and len(self.body)>0:
867 self.body.append(self.body.pop().rstrip())
868 self.body.append('%s\\hyperlink{%s}{' % (suffix,href))
870 def depart_footnote_reference(self, node):
871 self.body.append('}%s' % self.context.pop())
873 def visit_generated(self, node):
874 pass
876 def depart_generated(self, node):
877 pass
879 def visit_header(self, node):
880 self.context.append(len(self.body))
882 def depart_header(self, node):
883 start = self.context.pop()
884 self.body_prefix.append('\n\\verb|begin_header|\n')
885 self.body_prefix.extend(self.body[start:])
886 self.body_prefix.append('\n\\verb|end_header|\n')
887 del self.body[start:]
889 def visit_hint(self, node):
890 self.visit_admonition(node, 'hint')
892 def depart_hint(self, node):
893 self.depart_admonition()
895 def visit_image(self, node):
896 atts = node.attributes.copy()
897 href = atts['uri']
898 ##self.body.append('\\begin{center}\n')
899 self.body.append('\n\\includegraphics{%s}\n' % href)
900 ##self.body.append('\\end{center}\n')
902 def depart_image(self, node):
903 pass
905 def visit_important(self, node):
906 self.visit_admonition(node, 'important')
908 def depart_important(self, node):
909 self.depart_admonition()
911 def visit_interpreted(self, node):
912 # @@@ Incomplete, pending a proper implementation on the
913 # Parser/Reader end.
914 self.visit_literal(node)
916 def depart_interpreted(self, node):
917 self.depart_literal(node)
919 def visit_label(self, node):
920 # footnote/citation label
921 self.body.append('[')
923 def depart_label(self, node):
924 self.body.append(']')
926 def visit_legend(self, node):
927 self.body.append('{\\small ')
929 def depart_legend(self, node):
930 self.body.append('}')
932 def visit_line_block(self, node):
933 """line-block:
934 * whitespace (including linebreaks) is significant
935 * inline markup is supported.
936 * serif typeface
938 self.body.append('\\begin{flushleft}\n')
939 self.insert_none_breaking_blanks = 1
940 self.line_block_without_mbox = 1
941 if self.line_block_without_mbox:
942 self.insert_newline = 1
943 else:
944 self.mbox_newline = 1
945 self.body.append('\\mbox{')
947 def depart_line_block(self, node):
948 if self.line_block_without_mbox:
949 self.insert_newline = 0
950 else:
951 self.body.append('}')
952 self.mbox_newline = 0
953 self.insert_none_breaking_blanks = 0
954 self.body.append('\n\\end{flushleft}\n')
956 def visit_list_item(self, node):
957 self.body.append('\\item ')
959 def depart_list_item(self, node):
960 self.body.append('\n')
962 def visit_literal(self, node):
963 self.literal = 1
964 self.body.append('\\texttt{')
966 def depart_literal(self, node):
967 self.body.append('}')
968 self.literal = 0
970 def visit_literal_block(self, node):
972 .. parsed-literal::
974 # typically in a typewriter/monospaced typeface.
975 # care must be taken with the text, because inline markup is recognized.
977 # possibilities:
978 # * verbatim: is no possibility, as inline markup does not work.
979 # * obey..: is from julien and never worked for me (grubert).
980 self.use_for_literal_block = "mbox"
981 self.literal_block = 1
982 if (self.use_for_literal_block == "mbox"):
983 self.mbox_newline = 1
984 self.insert_none_breaking_blanks = 1
985 self.body.append('\\begin{ttfamily}\\begin{flushleft}\n\\mbox{')
986 else:
987 self.body.append('{\\obeylines\\obeyspaces\\ttfamily\n')
989 def depart_literal_block(self, node):
990 if (self.use_for_literal_block == "mbox"):
991 self.body.append('}\n\\end{flushleft}\\end{ttfamily}\n')
992 self.insert_none_breaking_blanks = 0
993 self.mbox_newline = 0
994 else:
995 self.body.append('}\n')
996 self.literal_block = 0
998 def visit_meta(self, node):
999 self.body.append('[visit_meta]\n')
1000 # BUG maybe set keywords for pdf
1001 ##self.head.append(self.starttag(node, 'meta', **node.attributes))
1003 def depart_meta(self, node):
1004 self.body.append('[depart_meta]\n')
1006 def visit_note(self, node):
1007 self.visit_admonition(node, 'note')
1009 def depart_note(self, node):
1010 self.depart_admonition()
1012 def visit_option(self, node):
1013 if self.context[-1]:
1014 # this is not the first option
1015 self.body.append(', ')
1017 def depart_option(self, node):
1018 # flag tha the first option is done.
1019 self.context[-1] += 1
1021 def visit_option_argument(self, node):
1022 """The delimiter betweeen an option and its argument."""
1023 self.body.append(node.get('delimiter', ' '))
1025 def depart_option_argument(self, node):
1026 pass
1028 def visit_option_group(self, node):
1029 if self.use_optionlist_for_option_list:
1030 self.body.append('\\item [')
1031 else:
1032 atts = {}
1033 if len(node.astext()) > 14:
1034 self.body.append('\\multicolumn{2}{l}{')
1035 self.context.append('} \\\\\n ')
1036 else:
1037 self.context.append('')
1038 self.body.append('\\texttt{')
1039 # flag for first option
1040 self.context.append(0)
1042 def depart_option_group(self, node):
1043 self.context.pop() # the flag
1044 if self.use_optionlist_for_option_list:
1045 self.body.append('] ')
1046 else:
1047 self.body.append('}')
1048 self.body.append(self.context.pop())
1050 def visit_option_list(self, node):
1051 self.body.append('% [option list]\n')
1052 if self.use_optionlist_for_option_list:
1053 self.body.append('\\begin{optionlist}{3cm}\n')
1054 else:
1055 self.body.append('\\begin{center}\n')
1056 # BUG: use admwidth or make it relative to textwidth ?
1057 self.body.append('\\begin{tabularx}{.9\\linewidth}{lX}\n')
1059 def depart_option_list(self, node):
1060 if self.use_optionlist_for_option_list:
1061 self.body.append('\\end{optionlist}\n')
1062 else:
1063 self.body.append('\\end{tabularx}\n')
1064 self.body.append('\\end{center}\n')
1066 def visit_option_list_item(self, node):
1067 pass
1069 def depart_option_list_item(self, node):
1070 if not self.use_optionlist_for_option_list:
1071 self.body.append('\\\\\n')
1073 def visit_option_string(self, node):
1074 ##self.body.append(self.starttag(node, 'span', '', CLASS='option'))
1075 pass
1077 def depart_option_string(self, node):
1078 ##self.body.append('</span>')
1079 pass
1081 def visit_organization(self, node):
1082 self.visit_docinfo_item(node, 'organization')
1084 def depart_organization(self, node):
1085 self.depart_docinfo_item(node)
1087 def visit_paragraph(self, node):
1088 if not self.topic_class == 'contents':
1089 self.body.append('\n')
1091 def depart_paragraph(self, node):
1092 if self.topic_class == 'contents':
1093 self.body.append('\n')
1094 else:
1095 self.body.append('\n')
1097 def visit_problematic(self, node):
1098 self.body.append('{\\color{red}\\bfseries{}')
1100 def depart_problematic(self, node):
1101 self.body.append('}')
1103 def visit_raw(self, node):
1104 if node.has_key('format') and node['format'].lower() == 'latex':
1105 self.body.append(node.astext())
1106 raise nodes.SkipNode
1108 def visit_reference(self, node):
1109 # for pdflatex hyperrefs might be supported
1110 if node.has_key('refuri'):
1111 href = node['refuri']
1112 elif node.has_key('refid'):
1113 href = '#' + node['refid']
1114 elif node.has_key('refname'):
1115 href = '#' + self.document.nameids[node['refname']]
1116 ##self.body.append('[visit_reference]')
1117 self.body.append('\\href{%s}{' % href)
1119 def depart_reference(self, node):
1120 self.body.append('}')
1121 ##self.body.append('[depart_reference]')
1123 def visit_revision(self, node):
1124 self.visit_docinfo_item(node, 'revision')
1126 def depart_revision(self, node):
1127 self.depart_docinfo_item(node)
1129 def visit_row(self, node):
1130 self.context.append(0)
1132 def depart_row(self, node):
1133 self.context.pop() # remove cell counter
1134 self.body.append(' \\\\ \\hline\n')
1136 def visit_section(self, node):
1137 self.section_level += 1
1139 def depart_section(self, node):
1140 self.section_level -= 1
1142 def visit_sidebar(self, node):
1143 # BUG: this is just a hack to make sidebars render something
1144 self.body.append('\\begin{center}\\begin{sffamily}\n')
1145 self.body.append('\\fbox{\\colorbox[gray]{0.80}{\\parbox{\\admonitionwidth}{\n')
1147 def depart_sidebar(self, node):
1148 self.body.append('}}}\n') # end parbox colorbox fbox
1149 self.body.append('\\end{sffamily}\n\\end{center}\n');
1152 attribution_formats = {'dash': ('---', ''),
1153 'parentheses': ('(', ')'),
1154 'parens': ('(', ')'),
1155 'none': ('', '')}
1157 def visit_attribution(self, node):
1158 prefix, suffix = self.attribution_formats[self.settings.attribution]
1159 self.body.append('\n\\begin{flushright}\n')
1160 self.body.append(prefix)
1161 self.context.append(suffix)
1163 def depart_attribution(self, node):
1164 self.body.append(self.context.pop() + '\n')
1165 self.body.append('\\end{flushright}\n')
1167 def visit_status(self, node):
1168 self.visit_docinfo_item(node, 'status')
1170 def depart_status(self, node):
1171 self.depart_docinfo_item(node)
1173 def visit_strong(self, node):
1174 self.body.append('\\textbf{')
1176 def depart_strong(self, node):
1177 self.body.append('}')
1179 def visit_substitution_definition(self, node):
1180 raise nodes.SkipNode
1182 def visit_substitution_reference(self, node):
1183 self.unimplemented_visit(node)
1185 def visit_subtitle(self, node):
1186 self.title = self.title + \
1187 '\\\\\n\\large{%s}\n' % self.encode(node.astext())
1188 raise nodes.SkipNode
1190 def depart_subtitle(self, node):
1191 pass
1193 def visit_system_message(self, node):
1194 if node['level'] < self.document.reporter['writer'].report_level:
1195 raise nodes.SkipNode
1198 def depart_system_message(self, node):
1199 self.body.append('\n')
1201 def get_colspecs(self):
1203 Return column specification for longtable.
1205 Assumes reST line length being 80 characters.
1207 width = 80
1208 s = ""
1209 for node in self.colspecs:
1210 colwidth = 0.93 * float(node['colwidth']) / width
1211 s += "|p{%.2f\\linewidth}" % colwidth
1212 self.colspecs = []
1213 return s+"|"
1215 def visit_table(self, node):
1216 if self.use_longtable:
1217 self.body.append('\n\\begin{longtable}[c]')
1218 else:
1219 self.body.append('\n\\begin{tabularx}{\\linewidth}')
1220 self.context.append('table_sentinel') # sentinel
1221 self.context.append(0) # column counter
1223 def depart_table(self, node):
1224 if self.use_longtable:
1225 self.body.append('\\end{longtable}\n')
1226 else:
1227 self.body.append('\\end{tabularx}\n')
1228 sentinel = self.context.pop()
1229 if sentinel != 'table_sentinel':
1230 print 'context:', self.context + [sentinel]
1231 raise AssertionError
1233 def table_preamble(self):
1234 if self.use_longtable:
1235 self.body.append('{%s}\n' % self.get_colspecs())
1236 else:
1237 if self.context[-1] != 'table_sentinel':
1238 self.body.append('{%s}' % ('|X' * self.context.pop() + '|'))
1239 self.body.append('\n\\hline')
1241 def visit_target(self, node):
1242 if not (node.has_key('refuri') or node.has_key('refid')
1243 or node.has_key('refname')):
1244 self.body.append('\\hypertarget{%s}{' % node['name'])
1245 self.context.append('}')
1246 else:
1247 self.context.append('')
1249 def depart_target(self, node):
1250 self.body.append(self.context.pop())
1252 def visit_tbody(self, node):
1253 # BUG write preamble if not yet done (colspecs not [])
1254 # for tables without heads.
1255 if self.colspecs:
1256 self.visit_thead(None)
1257 self.depart_thead(None)
1258 self.body.append('%[visit_tbody]\n')
1260 def depart_tbody(self, node):
1261 self.body.append('%[depart_tbody]\n')
1263 def visit_term(self, node):
1264 self.body.append('\\item[')
1266 def depart_term(self, node):
1267 # definition list term.
1268 self.body.append(':]\n')
1270 def visit_tgroup(self, node):
1271 #self.body.append(self.starttag(node, 'colgroup'))
1272 #self.context.append('</colgroup>\n')
1273 pass
1275 def depart_tgroup(self, node):
1276 pass
1278 def visit_thead(self, node):
1279 # number_of_columns will be zero after get_colspecs.
1280 # BUG ! push onto context for depart to pop it.
1281 number_of_columns = len(self.colspecs)
1282 self.table_preamble()
1283 #BUG longtable needs firstpage and lastfooter too.
1284 self.body.append('\\hline\n')
1286 def depart_thead(self, node):
1287 if self.use_longtable:
1288 # the table header written should be on every page
1289 # => \endhead
1290 self.body.append('\\endhead\n')
1291 # and the firsthead => \endfirsthead
1292 # BUG i want a "continued from previous page" on every not
1293 # firsthead, but then we need the header twice.
1295 # there is a \endfoot and \endlastfoot too.
1296 # but we need the number of columns to
1297 # self.body.append('\\multicolumn{%d}{c}{"..."}\n' % number_of_columns)
1298 # self.body.append('\\hline\n\\endfoot\n')
1299 # self.body.append('\\hline\n')
1300 # self.body.append('\\endlastfoot\n')
1303 def visit_tip(self, node):
1304 self.visit_admonition(node, 'tip')
1306 def depart_tip(self, node):
1307 self.depart_admonition()
1309 def visit_title(self, node):
1310 """Only 3 section levels are supported by LaTeX article (AFAIR)."""
1311 if isinstance(node.parent, nodes.topic):
1312 # section titles before the table of contents.
1313 if node.parent.hasattr('id'):
1314 self.body.append('\\hypertarget{%s}{}' % node.parent['id'])
1315 # BUG: latex chokes on center environment with "perhaps a missing item".
1316 # so we use hfill.
1317 self.body.append('\\subsection*{~\\hfill ')
1318 # the closing brace for subsection.
1319 self.context.append('\\hfill ~}\n')
1320 elif self.section_level == 0:
1321 # document title
1322 self.title = self.encode(node.astext())
1323 if not self.pdfinfo == None:
1324 self.pdfinfo.append( 'pdftitle={%s}' % self.encode(node.astext()) )
1325 raise nodes.SkipNode
1326 else:
1327 self.body.append('\n\n')
1328 self.body.append('%' + '_' * 75)
1329 self.body.append('\n\n')
1330 if node.parent.hasattr('id'):
1331 self.body.append('\\hypertarget{%s}{}\n' % node.parent['id'])
1332 # section_level 0 is title and handled above.
1333 # BUG: latex has no deeper sections (actually paragrah is no section either).
1334 if self.use_latex_toc:
1335 section_star = ""
1336 else:
1337 section_star = "*"
1338 if (self.section_level<=3): # 1,2,3
1339 self.body.append('\\%ssection%s{' % ('sub'*(self.section_level-1),section_star))
1340 elif (self.section_level==4):
1341 #self.body.append('\\paragraph*{')
1342 self.body.append('\\subsubsection%s{' % (section_star))
1343 else:
1344 #self.body.append('\\subparagraph*{')
1345 self.body.append('\\subsubsection%s{' % (section_star))
1346 # BUG: self.body.append( '\\label{%s}\n' % name)
1347 self.context.append('}\n')
1349 def depart_title(self, node):
1350 self.body.append(self.context.pop())
1351 # BUG level depends on style.
1352 if node.parent.hasattr('id') and not self.use_latex_toc:
1353 # pdflatex allows level 0 to 3
1354 # ToC would be the only on level 0 so i choose to decrement the rest.
1355 # "Table of contents" bookmark to see the ToC. To avoid this
1356 # we set all zeroes to one.
1357 l = self.section_level
1358 if l>0:
1359 l = l-1
1360 self.body.append('\\pdfbookmark[%d]{%s}{%s}\n' % \
1361 (l,node.astext(),node.parent['id']))
1363 def visit_topic(self, node):
1364 self.topic_class = node.get('class')
1365 if self.use_latex_toc:
1366 self.topic_class = ''
1367 raise nodes.SkipNode
1369 def depart_topic(self, node):
1370 self.topic_class = ''
1371 self.body.append('\n')
1373 def visit_transition(self, node):
1374 self.body.append('\n\n')
1375 self.body.append('%' + '_' * 75)
1376 self.body.append('\n\\hspace*{\\fill}\\hrulefill\\hspace*{\\fill}')
1377 self.body.append('\n\n')
1379 def depart_transition(self, node):
1380 #self.body.append('[depart_transition]')
1381 pass
1383 def visit_version(self, node):
1384 self.visit_docinfo_item(node, 'version')
1386 def depart_version(self, node):
1387 self.depart_docinfo_item(node)
1389 def visit_warning(self, node):
1390 self.visit_admonition(node, 'warning')
1392 def depart_warning(self, node):
1393 self.depart_admonition()
1395 def unimplemented_visit(self, node):
1396 raise NotImplementedError('visiting unimplemented node type: %s'
1397 % node.__class__.__name__)
1399 # def unknown_visit(self, node):
1400 # def default_visit(self, node):
1402 # vim: set ts=4 et ai :