4 :Author: Engelbert Gruber
5 :Contact: grubert@users.sourceforge.net
8 :Copyright: This module has been placed in the public domain.
10 LaTeX2e document tree Writer.
13 __docformat__
= 'reStructuredText'
15 # convention deactivate code by two # e.g. ##.
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."""
30 'LaTeX-Specific Options',
31 'The LaTeX "--output-encoding" default is "latin-1".',
32 (('Specify documentclass. Default is "article".',
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".',
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.',
50 {'default': 'style.tex', 'metavar': '<file>'}),
51 ('Specify a stylesheet file, relative to the current working '
53 'Overrides --stylesheet.',
54 ['--stylesheet-path'],
55 {'metavar': '<file>'}),
56 ('Link to the stylesheet in the output LaTeX file. This is the '
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 '
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 '
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'}
81 """Final translated form of `document`."""
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
97 * latex does not support multiple tocs in one document.
98 (might be no limitation except for docutils documentation)
102 * linewidth - width of a line in the local environment
103 * textwidth - the width of text on the page
105 Maybe always use linewidth ?
109 """Language specifics for LaTeX."""
110 # country code by a.schlock.
111 # partly manually converted from iso and babel stuff, dialects and some
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
126 # french, francais, canadien, acadian
127 'de': 'ngerman', #XXX rather than german
128 # ngerman, naustrian, german, germanb, austrian
131 # english, USenglish, american, UKenglish, british, canadian
157 def __init__(self
,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}"
166 self
.quotes
= ("``", "''")
169 def next_quote(self
):
170 q
= self
.quotes
[self
.quote_index
]
171 self
.quote_index
= (self
.quote_index
+1)%2
174 def quote_quotes(self
,text
):
176 for part
in text
.split('"'):
180 t
+= self
.next_quote() + part
183 def double_quotes_in_tt (self
,text
):
184 if not self
.double_quote_replacment
:
186 return text
.replace('"', self
.double_quote_replacment
)
188 def get_language(self
):
189 if self
._ISO
639_TO
_BABEL
.has_key(self
.language
):
190 return self
._ISO
639_TO
_BABEL
[self
.language
]
193 l
= self
.language
.split("_")[0]
194 if self
._ISO
639_TO
_BABEL
.has_key(l
):
195 return self
._ISO
639_TO
_BABEL
[l
]
200 'optionlist_environment' : [
201 '\\newcommand{\\optionlistlabel}[1]{\\bf #1 \\hfill}\n'
202 '\\newenvironment{optionlist}[1]\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'
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
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.
247 # table kind: if 0 tabularx (single page), 1 longtable
248 # maybe should be decided on row count.
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
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'
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()
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.
283 # * tabularx: for docinfo, automatic width of columns, always on one page.
284 '\\usepackage{tabularx}\n',
285 '\\usepackage{longtable}\n',
286 # possible other packages.
288 # * ltxtable is a combination of tabularx and longtable (pagebreaks).
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
),
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()
314 self
.head_prefix
.append(self
.stylesheet
% (stylesheet
))
316 if self
.linking
: # and maybe check for pdf
318 self
.pdfauthor
= None
319 # pdftitle, pdfsubject, pdfauthor, pdfkeywords, pdfcreator, pdfproducer
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
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.
333 self
.body_suffix
= ['\n']
334 self
.section_level
= 0
336 self
.topic_class
= ''
337 # column specification for tables
341 # verbatim: to tell encode not to encode.
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.
354 # inside literal block: no quote mangling.
355 self
.literal_block
= 0
358 def get_stylesheet_reference(self
):
359 if self
.settings
.stylesheet_path
:
360 return self
.settings
.stylesheet_path
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.
371 Escaping with a backslash does not help with backslashes, ~ and ^.
373 < > are only available in math-mode (really ?)
380 # compile the regexps once. do it here so one can see them.
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
)
394 text
= text
.replace("$", '{\\$}')
395 # then all that needs math mode
396 text
= text
.replace("<", '{$<$}')
397 text
= text
.replace(">", '{$>$}')
399 text
= text
.replace("&", '{\\&}')
400 text
= text
.replace("_", '{\\_}')
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
)
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(' ', '~')
422 text
= text
.replace(u
'\u2020', '{$\\dagger$}')
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
))
433 self
.pdfinfo
.append('pdfauthor={%s}' % self
.pdfauthor
)
434 pdfinfo
= '\\hypersetup{\n' + ',\n'.join(self
.pdfinfo
) + '\n}\n'
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
):
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')
482 def depart_authors(self
, node
):
483 # self.depart_docinfo_item(node)
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' )
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' )
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?
526 def depart_title_reference(self
, node
):
529 def visit_citation_reference(self
, node
):
531 if node
.has_key('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
)
550 self
.context
[-1] += 1
552 def depart_colspec(self
, node
):
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()))
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):
588 # def depart_decoration(self, node):
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( ' ' )
614 self
.body
.append( ' & ' )
616 def depart_description(self
, node
):
619 def visit_docinfo(self
, node
):
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.
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
))
638 if not self
.pdfinfo
== None:
639 if not self
.pdfauthor
:
640 self
.pdfauthor
= self
.attval(node
.astext())
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()))
647 if self
.latex_docinfo
:
648 self
.head
.append('\\date{%s}\n' % self
.attval(node
.astext()))
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')
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
]
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}' )
674 def depart_doctest_block(self
, node
):
675 self
.body
.append( '\\end{verbatim}\n' )
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.
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
):
696 if self
.context
[-1] > 0:
699 self
.body
.append(' & ')
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.')
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.
717 count
= node
['morecols'] + 1
718 self
.body
.append('\\multicolumn{%d}{%sl|}{' % (count
, bar
))
719 self
.context
.append('}')
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('}')
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.
741 enum_style
= {'arabic':'arabic',
744 'lowerroman':'roman',
745 'upperroman':'Roman' }
747 if node
.has_key('suffix'):
748 enum_suffix
= node
['suffix']
750 if node
.has_key('prefix'):
751 enum_prefix
= node
['prefix']
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
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.
798 self
.docinfo
.append('%s \\\\\n' % node
.astext())
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
):
807 self
.body
.append('\\begin{quote}\n')
808 self
.body
.append('\\begin{description}\n')
810 def depart_field_list(self
, node
):
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
818 self
.docinfo
.append('\\textbf{%s}: &\n\t' % node
.astext())
821 self
.body
.append('\\item [')
823 def depart_field_name(self
, node
):
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
):
853 if node
.has_key('refid'):
855 elif node
.has_key('refname'):
856 href
= self
.document
.nameids
[node
['refname']]
857 format
= self
.settings
.footnote_references
858 if format
== 'brackets':
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
):
876 def depart_generated(self
, node
):
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()
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
):
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
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
):
934 * whitespace (including linebreaks) is significant
935 * inline markup is supported.
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
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
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
):
964 self
.body
.append('\\texttt{')
966 def depart_literal(self
, node
):
967 self
.body
.append('}')
970 def visit_literal_block(self
, node
):
974 # typically in a typewriter/monospaced typeface.
975 # care must be taken with the text, because inline markup is recognized.
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{')
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
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
):
1028 def visit_option_group(self
, node
):
1029 if self
.use_optionlist_for_option_list
:
1030 self
.body
.append('\\item [')
1033 if len(node
.astext()) > 14:
1034 self
.body
.append('\\multicolumn{2}{l}{')
1035 self
.context
.append('} \\\\\n ')
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('] ')
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')
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')
1063 self
.body
.append('\\end{tabularx}\n')
1064 self
.body
.append('\\end{center}\n')
1066 def visit_option_list_item(self
, node
):
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'))
1077 def depart_option_string(self
, node
):
1078 ##self.body.append('</span>')
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')
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': ('(', ')'),
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
):
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.
1209 for node
in self
.colspecs
:
1210 colwidth
= 0.93 * float(node
['colwidth']) / width
1211 s
+= "|p{%.2f\\linewidth}" % colwidth
1215 def visit_table(self
, node
):
1216 if self
.use_longtable
:
1217 self
.body
.append('\n\\begin{longtable}[c]')
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')
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())
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('}')
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.
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')
1275 def depart_tgroup(self
, node
):
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
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".
1317 self
.body
.append('\\subsection*{~\\hfill ')
1318 # the closing brace for subsection.
1319 self
.context
.append('\\hfill ~}\n')
1320 elif self
.section_level
== 0:
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
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
:
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
))
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
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]')
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 :