1# .. coding: utf-8
2# $Id: __init__.py 7745 2014-02-28 14:15:59Z milde $
3# Author: Engelbert Gruber, Günter Milde
4# Maintainer: docutils-develop@lists.sourceforge.net
5# Copyright: This module has been placed in the public domain.
6
7"""LaTeX2e document tree Writer."""
8
9__docformat__ = 'reStructuredText'
10
11# code contributions from several people included, thanks to all.
12# some named: David Abrahams, Julien Letessier, Lele Gaifax, and others.
13#
14# convention deactivate code by two # i.e. ##.
15
16import sys
17import os
18import time
19import re
20import string
21import urllib
22try:
23    import roman
24except ImportError:
25    import docutils.utils.roman as roman
26from docutils import frontend, nodes, languages, writers, utils, io
27from docutils.utils.error_reporting import SafeString
28from docutils.transforms import writer_aux
29from docutils.utils.math import pick_math_environment, unichar2tex
30
31class Writer(writers.Writer):
32
33    supported = ('latex','latex2e')
34    """Formats this writer supports."""
35
36    default_template = 'default.tex'
37    default_template_path = os.path.dirname(__file__)
38
39    default_preamble = '\n'.join([r'% PDF Standard Fonts',
40                                  r'\usepackage{mathptmx} % Times',
41                                  r'\usepackage[scaled=.90]{helvet}',
42                                  r'\usepackage{courier}'])
43    settings_spec = (
44        'LaTeX-Specific Options',
45        None,
46        (('Specify documentclass.  Default is "article".',
47          ['--documentclass'],
48          {'default': 'article', }),
49         ('Specify document options.  Multiple options can be given, '
50          'separated by commas.  Default is "a4paper".',
51          ['--documentoptions'],
52          {'default': 'a4paper', }),
53         ('Footnotes with numbers/symbols by Docutils. (default)',
54          ['--docutils-footnotes'],
55          {'default': True, 'action': 'store_true',
56           'validator': frontend.validate_boolean}),
57         ('Alias for --docutils-footnotes (deprecated)',
58          ['--use-latex-footnotes'],
59          {'action': 'store_true',
60           'validator': frontend.validate_boolean}),
61         ('Use figure floats for footnote text (deprecated)',
62          ['--figure-footnotes'],
63          {'action': 'store_true',
64           'validator': frontend.validate_boolean}),
65         ('Format for footnote references: one of "superscript" or '
66          '"brackets".  Default is "superscript".',
67          ['--footnote-references'],
68          {'choices': ['superscript', 'brackets'], 'default': 'superscript',
69           'metavar': '<format>',
70           'overrides': 'trim_footnote_reference_space'}),
71         ('Use \\cite command for citations. ',
72          ['--use-latex-citations'],
73          {'default': 0, 'action': 'store_true',
74           'validator': frontend.validate_boolean}),
75         ('Use figure floats for citations '
76          '(might get mixed with real figures). (default)',
77          ['--figure-citations'],
78          {'dest': 'use_latex_citations', 'action': 'store_false',
79           'validator': frontend.validate_boolean}),
80         ('Format for block quote attributions: one of "dash" (em-dash '
81          'prefix), "parentheses"/"parens", or "none".  Default is "dash".',
82          ['--attribution'],
83          {'choices': ['dash', 'parentheses', 'parens', 'none'],
84           'default': 'dash', 'metavar': '<format>'}),
85         ('Specify LaTeX packages/stylesheets. '
86         ' A style is referenced with \\usepackage if extension is '
87         '".sty" or omitted and with \\input else. '
88          ' Overrides previous --stylesheet and --stylesheet-path settings.',
89          ['--stylesheet'],
90          {'default': '', 'metavar': '<file[,file,...]>',
91           'overrides': 'stylesheet_path',
92           'validator': frontend.validate_comma_separated_list}),
93         ('Comma separated list of LaTeX packages/stylesheets. '
94          'Relative paths are expanded if a matching file is found in '
95          'the --stylesheet-dirs. With --link-stylesheet, '
96          'the path is rewritten relative to the output *.tex file. ',
97          ['--stylesheet-path'],
98          {'metavar': '<file[,file,...]>', 'overrides': 'stylesheet',
99           'validator': frontend.validate_comma_separated_list}),
100         ('Link to the stylesheet(s) in the output file. (default)',
101          ['--link-stylesheet'],
102          {'dest': 'embed_stylesheet', 'action': 'store_false'}),
103         ('Embed the stylesheet(s) in the output file. '
104          'Stylesheets must be accessible during processing. ',
105          ['--embed-stylesheet'],
106          {'default': 0, 'action': 'store_true',
107           'validator': frontend.validate_boolean}),
108         ('Comma-separated list of directories where stylesheets are found. '
109          'Used by --stylesheet-path when expanding relative path arguments. '
110          'Default: "."',
111          ['--stylesheet-dirs'],
112          {'metavar': '<dir[,dir,...]>',
113           'validator': frontend.validate_comma_separated_list,
114           'default': ['.']}),
115         ('Customization by LaTeX code in the preamble. '
116          'Default: select PDF standard fonts (Times, Helvetica, Courier).',
117          ['--latex-preamble'],
118          {'default': default_preamble}),
119         ('Specify the template file. Default: "%s".' % default_template,
120          ['--template'],
121          {'default': default_template, 'metavar': '<file>'}),
122         ('Table of contents by LaTeX. (default) ',
123          ['--use-latex-toc'],
124          {'default': 1, 'action': 'store_true',
125           'validator': frontend.validate_boolean}),
126         ('Table of contents by Docutils (without page numbers). ',
127          ['--use-docutils-toc'],
128          {'dest': 'use_latex_toc', 'action': 'store_false',
129           'validator': frontend.validate_boolean}),
130         ('Add parts on top of the section hierarchy.',
131          ['--use-part-section'],
132          {'default': 0, 'action': 'store_true',
133           'validator': frontend.validate_boolean}),
134         ('Attach author and date to the document info table. (default) ',
135          ['--use-docutils-docinfo'],
136          {'dest': 'use_latex_docinfo', 'action': 'store_false',
137           'validator': frontend.validate_boolean}),
138         ('Attach author and date to the document title.',
139          ['--use-latex-docinfo'],
140          {'default': 0, 'action': 'store_true',
141           'validator': frontend.validate_boolean}),
142         ("Typeset abstract as topic. (default)",
143          ['--topic-abstract'],
144          {'dest': 'use_latex_abstract', 'action': 'store_false',
145           'validator': frontend.validate_boolean}),
146         ("Use LaTeX abstract environment for the document's abstract. ",
147          ['--use-latex-abstract'],
148          {'default': 0, 'action': 'store_true',
149           'validator': frontend.validate_boolean}),
150         ('Color of any hyperlinks embedded in text '
151          '(default: "blue", "false" to disable).',
152          ['--hyperlink-color'], {'default': 'blue'}),
153         ('Additional options to the "hyperref" package '
154          '(default: "").',
155          ['--hyperref-options'], {'default': ''}),
156         ('Enable compound enumerators for nested enumerated lists '
157          '(e.g. "1.2.a.ii").  Default: disabled.',
158          ['--compound-enumerators'],
159          {'default': None, 'action': 'store_true',
160           'validator': frontend.validate_boolean}),
161         ('Disable compound enumerators for nested enumerated lists. '
162          'This is the default.',
163          ['--no-compound-enumerators'],
164          {'action': 'store_false', 'dest': 'compound_enumerators'}),
165         ('Enable section ("." subsection ...) prefixes for compound '
166          'enumerators.  This has no effect without --compound-enumerators.'
167          'Default: disabled.',
168          ['--section-prefix-for-enumerators'],
169          {'default': None, 'action': 'store_true',
170           'validator': frontend.validate_boolean}),
171         ('Disable section prefixes for compound enumerators.  '
172          'This is the default.',
173          ['--no-section-prefix-for-enumerators'],
174          {'action': 'store_false', 'dest': 'section_prefix_for_enumerators'}),
175         ('Set the separator between section number and enumerator '
176          'for compound enumerated lists.  Default is "-".',
177          ['--section-enumerator-separator'],
178          {'default': '-', 'metavar': '<char>'}),
179         ('When possibile, use the specified environment for literal-blocks. '
180          'Default is quoting of whitespace and special chars.',
181          ['--literal-block-env'],
182          {'default': ''}),
183         ('When possibile, use verbatim for literal-blocks. '
184          'Compatibility alias for "--literal-block-env=verbatim".',
185          ['--use-verbatim-when-possible'],
186          {'default': 0, 'action': 'store_true',
187           'validator': frontend.validate_boolean}),
188         ('Table style. "standard" with horizontal and vertical lines, '
189          '"booktabs" (LaTeX booktabs style) only horizontal lines '
190          'above and below the table and below the header or "borderless".  '
191          'Default: "standard"',
192          ['--table-style'],
193          {'choices': ['standard', 'booktabs','nolines', 'borderless'],
194           'default': 'standard',
195           'metavar': '<format>'}),
196         ('LaTeX graphicx package option. '
197          'Possible values are "dvips", "pdftex". "auto" includes LaTeX code '
198          'to use "pdftex" if processing with pdf(la)tex and dvips otherwise. '
199          'Default is no option.',
200          ['--graphicx-option'],
201          {'default': ''}),
202         ('LaTeX font encoding. '
203          'Possible values are "", "T1" (default), "OT1", "LGR,T1" or '
204          'any other combination of options to the `fontenc` package. ',
205          ['--font-encoding'],
206          {'default': 'T1'}),
207         ('Per default the latex-writer puts the reference title into '
208          'hyperreferences. Specify "ref*" or "pageref*" to get the section '
209          'number or the page number.',
210          ['--reference-label'],
211          {'default': None, }),
212         ('Specify style and database for bibtex, for example '
213          '"--use-bibtex=mystyle,mydb1,mydb2".',
214          ['--use-bibtex'],
215          {'default': None, }),
216          ),)
217
218    settings_defaults = {'sectnum_depth': 0 # updated by SectNum transform
219                        }
220    config_section = 'latex2e writer'
221    config_section_dependencies = ('writers',)
222
223    head_parts = ('head_prefix', 'requirements', 'latex_preamble',
224                  'stylesheet', 'fallbacks', 'pdfsetup',
225                  'title', 'subtitle', 'titledata')
226    visitor_attributes = head_parts + ('body_pre_docinfo', 'docinfo',
227                                       'dedication', 'abstract', 'body')
228
229    output = None
230    """Final translated form of `document`."""
231
232    def __init__(self):
233        writers.Writer.__init__(self)
234        self.translator_class = LaTeXTranslator
235
236    # Override parent method to add latex-specific transforms
237    def get_transforms(self):
238       return writers.Writer.get_transforms(self) + [
239       # Convert specific admonitions to generic one
240       writer_aux.Admonitions,
241       # TODO: footnote collection transform
242       ]
243
244    def translate(self):
245        visitor = self.translator_class(self.document)
246        self.document.walkabout(visitor)
247        # copy parts
248        for part in self.visitor_attributes:
249            setattr(self, part, getattr(visitor, part))
250        # get template string from file
251        try:
252            template_file = open(self.document.settings.template, 'rb')
253        except IOError:
254            template_file = open(os.path.join(self.default_template_path,
255                                     self.document.settings.template), 'rb')
256        template = string.Template(unicode(template_file.read(), 'utf-8'))
257        template_file.close()
258        # fill template
259        self.assemble_parts() # create dictionary of parts
260        self.output = template.substitute(self.parts)
261
262    def assemble_parts(self):
263        """Assemble the `self.parts` dictionary of output fragments."""
264        writers.Writer.assemble_parts(self)
265        for part in self.visitor_attributes:
266            lines = getattr(self, part)
267            if part in self.head_parts:
268                if lines:
269                    lines.append('') # to get a trailing newline
270                self.parts[part] = '\n'.join(lines)
271            else:
272                # body contains inline elements, so join without newline
273                self.parts[part] = ''.join(lines)
274
275
276class Babel(object):
277    """Language specifics for LaTeX."""
278
279    # TeX (babel) language names:
280    # ! not all of these are supported by Docutils!
281    #
282    # based on LyX' languages file with adaptions to `BCP 47`_
283    # (http://www.rfc-editor.org/rfc/bcp/bcp47.txt) and
284    # http://www.tug.org/TUGboat/Articles/tb29-3/tb93miklavec.pdf
285    # * the key without subtags is the default
286    # * case is ignored
287    # cf. http://docutils.sourceforge.net/docs/howto/i18n.html
288    #     http://www.w3.org/International/articles/language-tags/
289    # and http://www.iana.org/assignments/language-subtag-registry
290    language_codes = {
291        # code          TeX/Babel-name       comment
292        'af':           'afrikaans',
293        'ar':           'arabic',
294        # 'be':           'belarusian',
295        'bg':           'bulgarian',
296        'br':           'breton',
297        'ca':           'catalan',
298        # 'cop':          'coptic',
299        'cs':           'czech',
300        'cy':           'welsh',
301        'da':           'danish',
302        'de':           'ngerman', # new spelling (de_1996)
303        'de-1901':      'german', # old spelling
304        'de-AT':        'naustrian',
305        'de-AT-1901':   'austrian',
306        'dsb':          'lowersorbian',
307        'el':           'greek', # monotonic (el-monoton)
308        'el-polyton':   'polutonikogreek',
309        'en':           'english',  # TeX' default language
310        'en-AU':        'australian',
311        'en-CA':        'canadian',
312        'en-GB':        'british',
313        'en-NZ':        'newzealand',
314        'en-US':        'american',
315        'eo':           'esperanto',
316        'es':           'spanish',
317        'et':           'estonian',
318        'eu':           'basque',
319        # 'fa':           'farsi',
320        'fi':           'finnish',
321        'fr':           'french',
322        'fr-CA':        'canadien',
323        'ga':           'irish',    # Irish Gaelic
324        # 'grc':                    # Ancient Greek
325        'grc-ibycus':   'ibycus',   # Ibycus encoding
326        'gl':           'galician',
327        'he':           'hebrew',
328        'hr':           'croatian',
329        'hsb':          'uppersorbian',
330        'hu':           'magyar',
331        'ia':           'interlingua',
332        'id':           'bahasai',  # Bahasa (Indonesian)
333        'is':           'icelandic',
334        'it':           'italian',
335        'ja':           'japanese',
336        'kk':           'kazakh',
337        'la':           'latin',
338        'lt':           'lithuanian',
339        'lv':           'latvian',
340        'mn':           'mongolian', # Mongolian, Cyrillic script (mn-cyrl)
341        'ms':           'bahasam',   # Bahasa (Malay)
342        'nb':           'norsk',     # Norwegian Bokmal
343        'nl':           'dutch',
344        'nn':           'nynorsk',   # Norwegian Nynorsk
345        'no':           'norsk',     # Norwegian (Bokmal)
346        'pl':           'polish',
347        'pt':           'portuges',
348        'pt-BR':        'brazil',
349        'ro':           'romanian',
350        'ru':           'russian',
351        'se':           'samin',     # North Sami
352        'sh-Cyrl':      'serbianc',  # Serbo-Croatian, Cyrillic script
353        'sh-Latn':      'serbian',   # Serbo-Croatian, Latin script see also 'hr'
354        'sk':           'slovak',
355        'sl':           'slovene',
356        'sq':           'albanian',
357        'sr':           'serbianc',  # Serbian, Cyrillic script (contributed)
358        'sr-Latn':      'serbian',   # Serbian, Latin script
359        'sv':           'swedish',
360        # 'th':           'thai',
361        'tr':           'turkish',
362        'uk':           'ukrainian',
363        'vi':           'vietnam',
364        # zh-Latn:      Chinese Pinyin
365        }
366    # normalize (downcase) keys
367    language_codes = dict([(k.lower(), v) for (k,v) in language_codes.items()])
368
369    warn_msg = 'Language "%s" not supported by LaTeX (babel)'
370
371    # "Active characters" are shortcuts that start a LaTeX macro and may need
372    # escaping for literals use. Characters that prevent literal use (e.g.
373    # starting accent macros like "a -> ä) will be deactivated if one of the
374    # defining languages is used in the document.
375    # Special cases:
376    #  ~ (tilde) -- used in estonian, basque, galician, and old versions of
377    #    spanish -- cannot be deactivated as it denotes a no-break space macro,
378    #  " (straight quote) -- used in albanian, austrian, basque
379    #    brazil, bulgarian, catalan, czech, danish, dutch, estonian,
380    #    finnish, galician, german, icelandic, italian, latin, naustrian,
381    #    ngerman, norsk, nynorsk, polish, portuges, russian, serbian, slovak,
382    #    slovene, spanish, swedish, ukrainian, and uppersorbian --
383    #    is escaped as ``\textquotedbl``.
384    active_chars = {# TeX/Babel-name:  active characters to deactivate
385                    # 'breton':        ':;!?' # ensure whitespace
386                    # 'esperanto':     '^',
387                    # 'estonian':      '~"`',
388                    # 'french':        ':;!?' # ensure whitespace
389                    'galician':        '.<>', # also '~"'
390                    # 'magyar':        '`', # for special hyphenation cases
391                    'spanish':         '.<>', # old versions also '~'
392                    # 'turkish':       ':!=' # ensure whitespace
393                   }
394
395    def __init__(self, language_code, reporter=None):
396        self.reporter = reporter
397        self.language = self.language_name(language_code)
398        self.otherlanguages = {}
399
400    def __call__(self):
401        """Return the babel call with correct options and settings"""
402        languages = sorted(self.otherlanguages.keys())
403        languages.append(self.language or 'english')
404        self.setup = [r'\usepackage[%s]{babel}' % ','.join(languages)]
405        # Deactivate "active characters"
406        shorthands = []
407        for c in ''.join([self.active_chars.get(l, '') for l in languages]):
408            if c not in shorthands:
409                shorthands.append(c)
410        if shorthands:
411            self.setup.append(r'\AtBeginDocument{\shorthandoff{%s}}'
412                              % ''.join(shorthands))
413        # Including '~' in shorthandoff prevents its use as no-break space
414        if 'galician' in languages:
415            self.setup.append(r'\deactivatetilden % restore ~ in Galician')
416        if 'estonian' in languages:
417            self.setup.extend([r'\makeatletter',
418                               r'  \addto\extrasestonian{\bbl@deactivate{~}}',
419                               r'\makeatother'])
420        if 'basque' in languages:
421            self.setup.extend([r'\makeatletter',
422                               r'  \addto\extrasbasque{\bbl@deactivate{~}}',
423                               r'\makeatother'])
424        if (languages[-1] == 'english' and
425            'french' in self.otherlanguages.keys()):
426            self.setup += ['% Prevent side-effects if French hyphenation '
427                           'patterns are not loaded:',
428                           r'\frenchbsetup{StandardLayout}',
429                           r'\AtBeginDocument{\selectlanguage{%s}'
430                           r'\noextrasfrench}' % self.language]
431        return '\n'.join(self.setup)
432
433    def language_name(self, language_code):
434        """Return TeX language name for `language_code`"""
435        for tag in utils.normalize_language_tag(language_code):
436            try:
437                return self.language_codes[tag]
438            except KeyError:
439                pass
440        if self.reporter is not None:
441            self.reporter.warning(self.warn_msg % language_code)
442        return ''
443
444    def get_language(self):
445        # Obsolete, kept for backwards compatibility with Sphinx
446        return self.language
447
448
449# Building blocks for the latex preamble
450# --------------------------------------
451
452class SortableDict(dict):
453    """Dictionary with additional sorting methods
454
455    Tip: use key starting with with '_' for sorting before small letters
456         and with '~' for sorting after small letters.
457    """
458    def sortedkeys(self):
459        """Return sorted list of keys"""
460        keys = self.keys()
461        keys.sort()
462        return keys
463
464    def sortedvalues(self):
465        """Return list of values sorted by keys"""
466        return [self[key] for key in self.sortedkeys()]
467
468
469# PreambleCmds
470# `````````````
471# A container for LaTeX code snippets that can be
472# inserted into the preamble if required in the document.
473#
474# .. The package 'makecmds' would enable shorter definitions using the
475#    \providelength and \provideenvironment commands.
476#    However, it is pretty non-standard (texlive-latex-extra).
477
478class PreambleCmds(object):
479    """Building blocks for the latex preamble."""
480
481PreambleCmds.abstract = r"""
482% abstract title
483\providecommand*{\DUtitleabstract}[1]{\centering\textbf{#1}}"""
484
485PreambleCmds.admonition = r"""
486% admonition (specially marked topic)
487\providecommand{\DUadmonition}[2][class-arg]{%
488  % try \DUadmonition#1{#2}:
489  \ifcsname DUadmonition#1\endcsname%
490    \csname DUadmonition#1\endcsname{#2}%
491  \else
492    \begin{center}
493      \fbox{\parbox{0.9\textwidth}{#2}}
494    \end{center}
495  \fi
496}"""
497
498PreambleCmds.align_center = r"""
499\makeatletter
500\@namedef{DUrolealign-center}{\centering}
501\makeatother
502"""
503
504## PreambleCmds.caption = r"""% configure caption layout
505## \usepackage{caption}
506## \captionsetup{singlelinecheck=false}% no exceptions for one-liners"""
507
508PreambleCmds.color = r"""\usepackage{color}"""
509
510PreambleCmds.docinfo = r"""
511% docinfo (width of docinfo table)
512\DUprovidelength{\DUdocinfowidth}{0.9\textwidth}"""
513# PreambleCmds.docinfo._depends = 'providelength'
514
515PreambleCmds.dedication = r"""
516% dedication topic
517\providecommand{\DUtopicdedication}[1]{\begin{center}#1\end{center}}"""
518
519PreambleCmds.error = r"""
520% error admonition title
521\providecommand*{\DUtitleerror}[1]{\DUtitle{\color{red}#1}}"""
522# PreambleCmds.errortitle._depends = 'color'
523
524PreambleCmds.fieldlist = r"""
525% fieldlist environment
526\ifthenelse{\isundefined{\DUfieldlist}}{
527  \newenvironment{DUfieldlist}%
528    {\quote\description}
529    {\enddescription\endquote}
530}{}"""
531
532PreambleCmds.float_settings = r"""\usepackage{float} % float configuration
533\floatplacement{figure}{H} % place figures here definitely"""
534
535PreambleCmds.footnotes = r"""% numeric or symbol footnotes with hyperlinks
536\providecommand*{\DUfootnotemark}[3]{%
537  \raisebox{1em}{\hypertarget{#1}{}}%
538  \hyperlink{#2}{\textsuperscript{#3}}%
539}
540\providecommand{\DUfootnotetext}[4]{%
541  \begingroup%
542  \renewcommand{\thefootnote}{%
543    \protect\raisebox{1em}{\protect\hypertarget{#1}{}}%
544    \protect\hyperlink{#2}{#3}}%
545  \footnotetext{#4}%
546  \endgroup%
547}"""
548
549PreambleCmds.footnote_floats = r"""% settings for footnotes as floats:
550\setlength{\floatsep}{0.5em}
551\setlength{\textfloatsep}{\fill}
552\addtolength{\textfloatsep}{3em}
553\renewcommand{\textfraction}{0.5}
554\renewcommand{\topfraction}{0.5}
555\renewcommand{\bottomfraction}{0.5}
556\setcounter{totalnumber}{50}
557\setcounter{topnumber}{50}
558\setcounter{bottomnumber}{50}"""
559
560PreambleCmds.graphicx_auto = r"""% Check output format
561\ifx\pdftexversion\undefined
562  \usepackage{graphicx}
563\else
564  \usepackage[pdftex]{graphicx}
565\fi"""
566
567PreambleCmds.highlight_rules = r"""% basic code highlight:
568\providecommand*\DUrolecomment[1]{\textcolor[rgb]{0.40,0.40,0.40}{#1}}
569\providecommand*\DUroledeleted[1]{\textcolor[rgb]{0.40,0.40,0.40}{#1}}
570\providecommand*\DUrolekeyword[1]{\textbf{#1}}
571\providecommand*\DUrolestring[1]{\textit{#1}}"""
572
573PreambleCmds.inline = r"""
574% inline markup (custom roles)
575% \DUrole{#1}{#2} tries \DUrole#1{#2}
576\providecommand*{\DUrole}[2]{%
577  \ifcsname DUrole#1\endcsname%
578    \csname DUrole#1\endcsname{#2}%
579  \else% backwards compatibility: try \docutilsrole#1{#2}
580    \ifcsname docutilsrole#1\endcsname%
581      \csname docutilsrole#1\endcsname{#2}%
582    \else%
583      #2%
584    \fi%
585  \fi%
586}"""
587
588PreambleCmds.legend = r"""
589% legend environment
590\ifthenelse{\isundefined{\DUlegend}}{
591  \newenvironment{DUlegend}{\small}{}
592}{}"""
593
594PreambleCmds.lineblock = r"""
595% lineblock environment
596\DUprovidelength{\DUlineblockindent}{2.5em}
597\ifthenelse{\isundefined{\DUlineblock}}{
598  \newenvironment{DUlineblock}[1]{%
599    \list{}{\setlength{\partopsep}{\parskip}
600            \addtolength{\partopsep}{\baselineskip}
601            \setlength{\topsep}{0pt}
602            \setlength{\itemsep}{0.15\baselineskip}
603            \setlength{\parsep}{0pt}
604            \setlength{\leftmargin}{#1}}
605    \raggedright
606  }
607  {\endlist}
608}{}"""
609# PreambleCmds.lineblock._depends = 'providelength'
610
611PreambleCmds.linking = r"""
612%% hyperlinks:
613\ifthenelse{\isundefined{\hypersetup}}{
614  \usepackage[%s]{hyperref}
615  \urlstyle{same} %% normal text font (alternatives: tt, rm, sf)
616}{}"""
617
618PreambleCmds.minitoc = r"""%% local table of contents
619\usepackage{minitoc}"""
620
621PreambleCmds.optionlist = r"""
622% optionlist environment
623\providecommand*{\DUoptionlistlabel}[1]{\bf #1 \hfill}
624\DUprovidelength{\DUoptionlistindent}{3cm}
625\ifthenelse{\isundefined{\DUoptionlist}}{
626  \newenvironment{DUoptionlist}{%
627    \list{}{\setlength{\labelwidth}{\DUoptionlistindent}
628            \setlength{\rightmargin}{1cm}
629            \setlength{\leftmargin}{\rightmargin}
630            \addtolength{\leftmargin}{\labelwidth}
631            \addtolength{\leftmargin}{\labelsep}
632            \renewcommand{\makelabel}{\DUoptionlistlabel}}
633  }
634  {\endlist}
635}{}"""
636# PreambleCmds.optionlist._depends = 'providelength'
637
638PreambleCmds.providelength = r"""
639% providelength (provide a length variable and set default, if it is new)
640\providecommand*{\DUprovidelength}[2]{
641  \ifthenelse{\isundefined{#1}}{\newlength{#1}\setlength{#1}{#2}}{}
642}"""
643
644PreambleCmds.rubric = r"""
645% rubric (informal heading)
646\providecommand*{\DUrubric}[2][class-arg]{%
647  \subsubsection*{\centering\textit{\textmd{#2}}}}"""
648
649PreambleCmds.sidebar = r"""
650% sidebar (text outside the main text flow)
651\providecommand{\DUsidebar}[2][class-arg]{%
652  \begin{center}
653    \colorbox[gray]{0.80}{\parbox{0.9\textwidth}{#2}}
654  \end{center}
655}"""
656
657PreambleCmds.subtitle = r"""
658% subtitle (for topic/sidebar)
659\providecommand*{\DUsubtitle}[2][class-arg]{\par\emph{#2}\smallskip}"""
660
661PreambleCmds.documentsubtitle = r"""
662% subtitle (in document title)
663\providecommand*{\DUdocumentsubtitle}[1]{{\large #1}}"""
664
665PreambleCmds.table = r"""\usepackage{longtable,ltcaption,array}
666\setlength{\extrarowheight}{2pt}
667\newlength{\DUtablewidth} % internal use in tables"""
668
669# Options [force,almostfull] prevent spurious error messages, see
670# de.comp.text.tex/2005-12/msg01855
671PreambleCmds.textcomp = """\
672\\usepackage{textcomp} % text symbol macros"""
673
674PreambleCmds.titlereference = r"""
675% titlereference role
676\providecommand*{\DUroletitlereference}[1]{\textsl{#1}}"""
677
678PreambleCmds.title = r"""
679% title for topics, admonitions, unsupported section levels, and sidebar
680\providecommand*{\DUtitle}[2][class-arg]{%
681  % call \DUtitle#1{#2} if it exists:
682  \ifcsname DUtitle#1\endcsname%
683    \csname DUtitle#1\endcsname{#2}%
684  \else
685    \smallskip\noindent\textbf{#2}\smallskip%
686  \fi
687}"""
688
689PreambleCmds.topic = r"""
690% topic (quote with heading)
691\providecommand{\DUtopic}[2][class-arg]{%
692  \ifcsname DUtopic#1\endcsname%
693    \csname DUtopic#1\endcsname{#2}%
694  \else
695    \begin{quote}#2\end{quote}
696  \fi
697}"""
698
699PreambleCmds.transition = r"""
700% transition (break, fancybreak, anonymous section)
701\providecommand*{\DUtransition}[1][class-arg]{%
702  \hspace*{\fill}\hrulefill\hspace*{\fill}
703  \vskip 0.5\baselineskip
704}"""
705
706
707# LaTeX encoding maps
708# -------------------
709# ::
710
711class CharMaps(object):
712    """LaTeX representations for active and Unicode characters."""
713
714    # characters that always need escaping:
715    special = {
716        ord('#'): ur'\#',
717        ord('$'): ur'\$',
718        ord('%'): ur'\%',
719        ord('&'): ur'\&',
720        ord('~'): ur'\textasciitilde{}',
721        ord('_'): ur'\_',
722        ord('^'): ur'\textasciicircum{}',
723        ord('\\'): ur'\textbackslash{}',
724        ord('{'): ur'\{',
725        ord('}'): ur'\}',
726        # straight double quotes are 'active' in many languages
727        ord('"'): ur'\textquotedbl{}',
728        # Square brackets are ordinary chars and cannot be escaped with '\',
729        # so we put them in a group '{[}'. (Alternative: ensure that all
730        # macros with optional arguments are terminated with {} and text
731        # inside any optional argument is put in a group ``[{text}]``).
732        # Commands with optional args inside an optional arg must be put in a
733        # group, e.g. ``\item[{\hyperref[label]{text}}]``.
734        ord('['): ur'{[}',
735        ord(']'): ur'{]}',
736        # the soft hyphen is unknown in 8-bit text
737        # and not properly handled by XeTeX
738        0x00AD: ur'\-', # SOFT HYPHEN
739    }
740    # Unicode chars that are not recognized by LaTeX's utf8 encoding
741    unsupported_unicode = {
742        0x00A0: ur'~', # NO-BREAK SPACE
743        # TODO: ensure white space also at the beginning of a line?
744        # 0x00A0: ur'\leavevmode\nobreak\vadjust{}~'
745        0x2008: ur'\,', # PUNCTUATION SPACE   
746        0x2011: ur'\hbox{-}', # NON-BREAKING HYPHEN
747        0x202F: ur'\,', # NARROW NO-BREAK SPACE
748        0x21d4: ur'$\Leftrightarrow$',
749        # Docutils footnote symbols:
750        0x2660: ur'$\spadesuit$',
751        0x2663: ur'$\clubsuit$',
752    }
753    # Unicode chars that are recognized by LaTeX's utf8 encoding
754    utf8_supported_unicode = {
755        0x00AB: ur'\guillemotleft', # LEFT-POINTING DOUBLE ANGLE QUOTATION MARK
756        0x00bb: ur'\guillemotright', # RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK
757        0x200C: ur'\textcompwordmark', # ZERO WIDTH NON-JOINER
758        0x2013: ur'\textendash{}',
759        0x2014: ur'\textemdash{}',
760        0x2018: ur'\textquoteleft{}',
761        0x2019: ur'\textquoteright{}',
762        0x201A: ur'\quotesinglbase{}', # SINGLE LOW-9 QUOTATION MARK
763        0x201C: ur'\textquotedblleft{}',
764        0x201D: ur'\textquotedblright{}',
765        0x201E: ur'\quotedblbase{}', # DOUBLE LOW-9 QUOTATION MARK
766        0x2030: ur'\textperthousand{}',   # PER MILLE SIGN
767        0x2031: ur'\textpertenthousand{}', # PER TEN THOUSAND SIGN
768        0x2039: ur'\guilsinglleft{}',
769        0x203A: ur'\guilsinglright{}',
770        0x2423: ur'\textvisiblespace{}',  # OPEN BOX
771        0x2020: ur'\dag{}',
772        0x2021: ur'\ddag{}',
773        0x2026: ur'\dots{}',
774        0x2122: ur'\texttrademark{}',
775    }
776    # recognized with 'utf8', if textcomp is loaded
777    textcomp = {
778        # Latin-1 Supplement
779        0x00a2: ur'\textcent{}',          # ¢ CENT SIGN
780        0x00a4: ur'\textcurrency{}',      # ¤ CURRENCY SYMBOL
781        0x00a5: ur'\textyen{}',           # ¥ YEN SIGN
782        0x00a6: ur'\textbrokenbar{}',     # ¦ BROKEN BAR
783        0x00a7: ur'\textsection{}',       # § SECTION SIGN
784        0x00a8: ur'\textasciidieresis{}', # ¨ DIAERESIS
785        0x00a9: ur'\textcopyright{}',     # © COPYRIGHT SIGN
786        0x00aa: ur'\textordfeminine{}',   # ª FEMININE ORDINAL INDICATOR
787        0x00ac: ur'\textlnot{}',          # ¬ NOT SIGN
788        0x00ae: ur'\textregistered{}',    # ® REGISTERED SIGN
789        0x00af: ur'\textasciimacron{}',   # ¯ MACRON
790        0x00b0: ur'\textdegree{}',        # ° DEGREE SIGN
791        0x00b1: ur'\textpm{}',            # ± PLUS-MINUS SIGN
792        0x00b2: ur'\texttwosuperior{}',   # ² SUPERSCRIPT TWO
793        0x00b3: ur'\textthreesuperior{}', # ³ SUPERSCRIPT THREE
794        0x00b4: ur'\textasciiacute{}',    # ´ ACUTE ACCENT
795        0x00b5: ur'\textmu{}',            # µ MICRO SIGN
796        0x00b6: ur'\textparagraph{}',     # ¶ PILCROW SIGN # != \textpilcrow
797        0x00b9: ur'\textonesuperior{}',   # ¹ SUPERSCRIPT ONE
798        0x00ba: ur'\textordmasculine{}',  # º MASCULINE ORDINAL INDICATOR
799        0x00bc: ur'\textonequarter{}',    # 1/4 FRACTION
800        0x00bd: ur'\textonehalf{}',       # 1/2 FRACTION
801        0x00be: ur'\textthreequarters{}', # 3/4 FRACTION
802        0x00d7: ur'\texttimes{}',         # × MULTIPLICATION SIGN
803        0x00f7: ur'\textdiv{}',           # ÷ DIVISION SIGN
804        # others
805        0x0192: ur'\textflorin{}',        # LATIN SMALL LETTER F WITH HOOK
806        0x02b9: ur'\textasciiacute{}',    # MODIFIER LETTER PRIME
807        0x02ba: ur'\textacutedbl{}',      # MODIFIER LETTER DOUBLE PRIME
808        0x2016: ur'\textbardbl{}',        # DOUBLE VERTICAL LINE
809        0x2022: ur'\textbullet{}',        # BULLET
810        0x2032: ur'\textasciiacute{}',    # PRIME
811        0x2033: ur'\textacutedbl{}',      # DOUBLE PRIME
812        0x2035: ur'\textasciigrave{}',    # REVERSED PRIME
813        0x2036: ur'\textgravedbl{}',      # REVERSED DOUBLE PRIME
814        0x203b: ur'\textreferencemark{}', # REFERENCE MARK
815        0x203d: ur'\textinterrobang{}',   # INTERROBANG
816        0x2044: ur'\textfractionsolidus{}', # FRACTION SLASH
817        0x2045: ur'\textlquill{}',        # LEFT SQUARE BRACKET WITH QUILL
818        0x2046: ur'\textrquill{}',        # RIGHT SQUARE BRACKET WITH QUILL
819        0x2052: ur'\textdiscount{}',      # COMMERCIAL MINUS SIGN
820        0x20a1: ur'\textcolonmonetary{}', # COLON SIGN
821        0x20a3: ur'\textfrenchfranc{}',   # FRENCH FRANC SIGN
822        0x20a4: ur'\textlira{}',          # LIRA SIGN
823        0x20a6: ur'\textnaira{}',         # NAIRA SIGN
824        0x20a9: ur'\textwon{}',           # WON SIGN
825        0x20ab: ur'\textdong{}',          # DONG SIGN
826        0x20ac: ur'\texteuro{}',          # EURO SIGN
827        0x20b1: ur'\textpeso{}',          # PESO SIGN
828        0x20b2: ur'\textguarani{}',       # GUARANI SIGN
829        0x2103: ur'\textcelsius{}',       # DEGREE CELSIUS
830        0x2116: ur'\textnumero{}',        # NUMERO SIGN
831        0x2117: ur'\textcircledP{}',      # SOUND RECORDING COYRIGHT
832        0x211e: ur'\textrecipe{}',        # PRESCRIPTION TAKE
833        0x2120: ur'\textservicemark{}',   # SERVICE MARK
834        0x2122: ur'\texttrademark{}',     # TRADE MARK SIGN
835        0x2126: ur'\textohm{}',           # OHM SIGN
836        0x2127: ur'\textmho{}',           # INVERTED OHM SIGN
837        0x212e: ur'\textestimated{}',     # ESTIMATED SYMBOL
838        0x2190: ur'\textleftarrow{}',     # LEFTWARDS ARROW
839        0x2191: ur'\textuparrow{}',       # UPWARDS ARROW
840        0x2192: ur'\textrightarrow{}',    # RIGHTWARDS ARROW
841        0x2193: ur'\textdownarrow{}',     # DOWNWARDS ARROW
842        0x2212: ur'\textminus{}',         # MINUS SIGN
843        0x2217: ur'\textasteriskcentered{}', # ASTERISK OPERATOR
844        0x221a: ur'\textsurd{}',          # SQUARE ROOT
845        0x2422: ur'\textblank{}',         # BLANK SYMBOL
846        0x25e6: ur'\textopenbullet{}',    # WHITE BULLET
847        0x25ef: ur'\textbigcircle{}',     # LARGE CIRCLE
848        0x266a: ur'\textmusicalnote{}',   # EIGHTH NOTE
849        0x26ad: ur'\textmarried{}',       # MARRIAGE SYMBOL
850        0x26ae: ur'\textdivorced{}',      # DIVORCE SYMBOL
851        0x27e8: ur'\textlangle{}',        # MATHEMATICAL LEFT ANGLE BRACKET
852        0x27e9: ur'\textrangle{}',        # MATHEMATICAL RIGHT ANGLE BRACKET
853    }
854    # Unicode chars that require a feature/package to render
855    pifont = {
856        0x2665: ur'\ding{170}',     # black heartsuit
857        0x2666: ur'\ding{169}',     # black diamondsuit
858        0x2713: ur'\ding{51}',      # check mark
859        0x2717: ur'\ding{55}',      # check mark
860    }
861    # TODO: greek alphabet ... ?
862    # see also LaTeX codec
863    # http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/252124
864    # and unimap.py from TeXML
865
866
867class DocumentClass(object):
868    """Details of a LaTeX document class."""
869
870    def __init__(self, document_class, with_part=False):
871        self.document_class = document_class
872        self._with_part = with_part
873        self.sections = ['section', 'subsection', 'subsubsection',
874                         'paragraph', 'subparagraph']
875        if self.document_class in ('book', 'memoir', 'report',
876                                   'scrbook', 'scrreprt'):
877            self.sections.insert(0, 'chapter')
878        if self._with_part:
879            self.sections.insert(0, 'part')
880
881    def section(self, level):
882        """Return the LaTeX section name for section `level`.
883
884        The name depends on the specific document class.
885        Level is 1,2,3..., as level 0 is the title.
886        """
887        if level <= len(self.sections):
888            return self.sections[level-1]
889        else:  # unsupported levels
890            return 'DUtitle[section%s]' % roman.toRoman(level)
891
892class Table(object):
893    """Manage a table while traversing.
894
895    Maybe change to a mixin defining the visit/departs, but then
896    class Table internal variables are in the Translator.
897
898    Table style might be
899
900    :standard:   horizontal and vertical lines
901    :booktabs:   only horizontal lines (requires "booktabs" LaTeX package)
902    :borderless: no borders around table cells
903    :nolines:    alias for borderless
904    """
905    def __init__(self,translator,latex_type,table_style):
906        self._translator = translator
907        self._latex_type = latex_type
908        self._table_style = table_style
909        self._open = False
910        # miscellaneous attributes
911        self._attrs = {}
912        self._col_width = []
913        self._rowspan = []
914        self.stubs = []
915        self._in_thead = 0
916
917    def open(self):
918        self._open = True
919        self._col_specs = []
920        self.caption = []
921        self._attrs = {}
922        self._in_head = False # maybe context with search
923    def close(self):
924        self._open = False
925        self._col_specs = None
926        self.caption = []
927        self._attrs = {}
928        self.stubs = []
929    def is_open(self):
930        return self._open
931
932    def set_table_style(self, table_style):
933        if not table_style in ('standard','booktabs','borderless','nolines'):
934            return
935        self._table_style = table_style
936
937    def get_latex_type(self):
938        if self._latex_type == 'longtable' and not self.caption:
939            # do not advance the "table" counter (requires "ltcaption" package)
940            return('longtable*')
941        return self._latex_type
942
943    def set(self,attr,value):
944        self._attrs[attr] = value
945    def get(self,attr):
946        if attr in self._attrs:
947            return self._attrs[attr]
948        return None
949
950    def get_vertical_bar(self):
951        if self._table_style == 'standard':
952            return '|'
953        return ''
954
955    # horizontal lines are drawn below a row,
956    def get_opening(self):
957        return '\n'.join([r'\setlength{\DUtablewidth}{\linewidth}',
958                          r'\begin{%s}[c]' % self.get_latex_type()])
959
960    def get_closing(self):
961        closing = []
962        if self._table_style == 'booktabs':
963            closing.append(r'\bottomrule')
964        # elif self._table_style == 'standard':
965        #     closing.append(r'\hline')
966        closing.append(r'\end{%s}' % self.get_latex_type())
967        return '\n'.join(closing)
968
969    def visit_colspec(self, node):
970        self._col_specs.append(node)
971        # "stubs" list is an attribute of the tgroup element:
972        self.stubs.append(node.attributes.get('stub'))
973
974    def get_colspecs(self):
975        """Return column specification for longtable.
976
977        Assumes reST line length being 80 characters.
978        Table width is hairy.
979
980        === ===
981        ABC DEF
982        === ===
983
984        usually gets to narrow, therefore we add 1 (fiddlefactor).
985        """
986        width = 80
987
988        total_width = 0.0
989        # first see if we get too wide.
990        for node in self._col_specs:
991            colwidth = float(node['colwidth']+1) / width
992            total_width += colwidth
993        self._col_width = []
994        self._rowspan = []
995        # donot make it full linewidth
996        factor = 0.93
997        if total_width > 1.0:
998            factor /= total_width
999        bar = self.get_vertical_bar()
1000        latex_table_spec = ''
1001        for node in self._col_specs:
1002            colwidth = factor * float(node['colwidth']+1) / width
1003            self._col_width.append(colwidth+0.005)
1004            self._rowspan.append(0)
1005            latex_table_spec += '%sp{%.3f\\DUtablewidth}' % (bar, colwidth+0.005)
1006        return latex_table_spec+bar
1007
1008    def get_column_width(self):
1009        """Return columnwidth for current cell (not multicell)."""
1010        return '%.2f\\DUtablewidth' % self._col_width[self._cell_in_row-1]
1011
1012    def get_multicolumn_width(self, start, len_):
1013        """Return sum of columnwidths for multicell."""
1014        mc_width = sum([width
1015                       for width in ([self._col_width[start + co - 1]
1016                                     for co in range (len_)])])
1017        return '%.2f\\DUtablewidth' % mc_width
1018
1019    def get_caption(self):
1020        if not self.caption:
1021            return ''
1022        caption = ''.join(self.caption)
1023        if 1 == self._translator.thead_depth():
1024            return r'\caption{%s}\\' '\n' % caption
1025        return r'\caption[]{%s (... continued)}\\' '\n' % caption
1026
1027    def need_recurse(self):
1028        if self._latex_type == 'longtable':
1029            return 1 == self._translator.thead_depth()
1030        return 0
1031
1032    def visit_thead(self):
1033        self._in_thead += 1
1034        if self._table_style == 'standard':
1035            return ['\\hline\n']
1036        elif self._table_style == 'booktabs':
1037            return ['\\toprule\n']
1038        return []
1039
1040    def depart_thead(self):
1041        a = []
1042        #if self._table_style == 'standard':
1043        #    a.append('\\hline\n')
1044        if self._table_style == 'booktabs':
1045            a.append('\\midrule\n')
1046        if self._latex_type == 'longtable':
1047            if 1 == self._translator.thead_depth():
1048                a.append('\\endfirsthead\n')
1049            else:
1050                a.append('\\endhead\n')
1051                a.append(r'\multicolumn{%d}{c}' % len(self._col_specs) +
1052                         r'{\hfill ... continued on next page} \\')
1053                a.append('\n\\endfoot\n\\endlastfoot\n')
1054        # for longtable one could add firsthead, foot and lastfoot
1055        self._in_thead -= 1
1056        return a
1057    def visit_row(self):
1058        self._cell_in_row = 0
1059    def depart_row(self):
1060        res = [' \\\\\n']
1061        self._cell_in_row = None  # remove cell counter
1062        for i in range(len(self._rowspan)):
1063            if (self._rowspan[i]>0):
1064                self._rowspan[i] -= 1
1065
1066        if self._table_style == 'standard':
1067            rowspans = [i+1 for i in range(len(self._rowspan))
1068                        if (self._rowspan[i]<=0)]
1069            if len(rowspans)==len(self._rowspan):
1070                res.append('\\hline\n')
1071            else:
1072                cline = ''
1073                rowspans.reverse()
1074                # TODO merge clines
1075                while True:
1076                    try:
1077                        c_start = rowspans.pop()
1078                    except:
1079                        break
1080                    cline += '\\cline{%d-%d}\n' % (c_start,c_start)
1081                res.append(cline)
1082        return res
1083
1084    def set_rowspan(self,cell,value):
1085        try:
1086            self._rowspan[cell] = value
1087        except:
1088            pass
1089    def get_rowspan(self,cell):
1090        try:
1091            return self._rowspan[cell]
1092        except:
1093            return 0
1094    def get_entry_number(self):
1095        return self._cell_in_row
1096    def visit_entry(self):
1097        self._cell_in_row += 1
1098    def is_stub_column(self):
1099        if len(self.stubs) >= self._cell_in_row:
1100            return self.stubs[self._cell_in_row-1]
1101        return False
1102
1103
1104class LaTeXTranslator(nodes.NodeVisitor):
1105
1106    # When options are given to the documentclass, latex will pass them
1107    # to other packages, as done with babel.
1108    # Dummy settings might be taken from document settings
1109
1110    # Write code for typesetting with 8-bit tex/pdftex (vs. xetex/luatex) engine
1111    # overwritten by the XeTeX writer
1112    is_xetex = False
1113
1114    # Config setting defaults
1115    # -----------------------
1116
1117    # TODO: use mixins for different implementations.
1118    # list environment for docinfo. else tabularx
1119    ## use_optionlist_for_docinfo = False # TODO: NOT YET IN USE
1120
1121    # Use compound enumerations (1.A.1.)
1122    compound_enumerators = False
1123
1124    # If using compound enumerations, include section information.
1125    section_prefix_for_enumerators = False
1126
1127    # This is the character that separates the section ("." subsection ...)
1128    # prefix from the regular list enumerator.
1129    section_enumerator_separator = '-'
1130
1131    # Auxiliary variables
1132    # -------------------
1133
1134    has_latex_toc = False # is there a toc in the doc? (needed by minitoc)
1135    is_toc_list = False   # is the current bullet_list a ToC?
1136    section_level = 0
1137
1138    # Flags to encode():
1139    # inside citation reference labels underscores dont need to be escaped
1140    inside_citation_reference_label = False
1141    verbatim = False                   # do not encode
1142    insert_non_breaking_blanks = False # replace blanks by "~"
1143    insert_newline = False             # add latex newline commands
1144    literal = False                    # literal text (block or inline)
1145
1146
1147    def __init__(self, document, babel_class=Babel):
1148        nodes.NodeVisitor.__init__(self, document)
1149        # Reporter
1150        # ~~~~~~~~
1151        self.warn = self.document.reporter.warning
1152        self.error = self.document.reporter.error
1153
1154        # Settings
1155        # ~~~~~~~~
1156        self.settings = settings = document.settings
1157        self.latex_encoding = self.to_latex_encoding(settings.output_encoding)
1158        self.use_latex_toc = settings.use_latex_toc
1159        self.use_latex_docinfo = settings.use_latex_docinfo
1160        self._use_latex_citations = settings.use_latex_citations
1161        self._reference_label = settings.reference_label
1162        self.hyperlink_color = settings.hyperlink_color
1163        self.compound_enumerators = settings.compound_enumerators
1164        self.font_encoding = getattr(settings, 'font_encoding', '')
1165        self.section_prefix_for_enumerators = (
1166            settings.section_prefix_for_enumerators)
1167        self.section_enumerator_separator = (
1168            settings.section_enumerator_separator.replace('_', r'\_'))
1169        # literal blocks:
1170        self.literal_block_env = ''
1171        self.literal_block_options = ''
1172        if settings.literal_block_env != '':
1173            (none,
1174             self.literal_block_env,
1175             self.literal_block_options,
1176             none ) = re.split('(\w+)(.*)', settings.literal_block_env)
1177        elif settings.use_verbatim_when_possible:
1178            self.literal_block_env = 'verbatim'
1179        #
1180        if self.settings.use_bibtex:
1181            self.bibtex = self.settings.use_bibtex.split(',',1)
1182            # TODO avoid errors on not declared citations.
1183        else:
1184            self.bibtex = None
1185        # language module for Docutils-generated text
1186        # (labels, bibliographic_fields, and author_separators)
1187        self.language_module = languages.get_language(settings.language_code,
1188                                              document.reporter)
1189        self.babel = babel_class(settings.language_code, document.reporter)
1190        self.author_separator = self.language_module.author_separators[0]
1191        d_options = [self.settings.documentoptions]
1192        if self.babel.language not in ('english', ''):
1193            d_options.append(self.babel.language)
1194        self.documentoptions = ','.join(filter(None, d_options))
1195        self.d_class = DocumentClass(settings.documentclass,
1196                                     settings.use_part_section)
1197        # graphic package options:
1198        if self.settings.graphicx_option == '':
1199            self.graphicx_package = r'\usepackage{graphicx}'
1200        elif self.settings.graphicx_option.lower() == 'auto':
1201            self.graphicx_package = PreambleCmds.graphicx_auto
1202        else:
1203            self.graphicx_package = (r'\usepackage[%s]{graphicx}' %
1204                                     self.settings.graphicx_option)
1205        # footnotes:
1206        self.docutils_footnotes = settings.docutils_footnotes
1207        if settings.use_latex_footnotes:
1208            self.docutils_footnotes = True
1209            self.warn('`use_latex_footnotes` is deprecated. '
1210                      'The setting has been renamed to `docutils_footnotes` '
1211                      'and the alias will be removed in a future version.')
1212        self.figure_footnotes = settings.figure_footnotes
1213        if self.figure_footnotes:
1214            self.docutils_footnotes = True
1215            self.warn('The "figure footnotes" workaround/setting is strongly '
1216                      'deprecated and will be removed in a future version.')
1217
1218        # Output collection stacks
1219        # ~~~~~~~~~~~~~~~~~~~~~~~~
1220
1221        # Document parts
1222        self.head_prefix = [r'\documentclass[%s]{%s}' %
1223            (self.documentoptions, self.settings.documentclass)]
1224        self.requirements = SortableDict() # made a list in depart_document()
1225        self.requirements['__static'] = r'\usepackage{ifthen}'
1226        self.latex_preamble = [settings.latex_preamble]
1227        self.fallbacks = SortableDict() # made a list in depart_document()
1228        self.pdfsetup = [] # PDF properties (hyperref package)
1229        self.title = []
1230        self.subtitle = []
1231        self.titledata = [] # \title, \author, \date
1232        ## self.body_prefix = ['\\begin{document}\n']
1233        self.body_pre_docinfo = [] # \maketitle
1234        self.docinfo = []
1235        self.dedication = []
1236        self.abstract = []
1237        self.body = []
1238        ## self.body_suffix = ['\\end{document}\n']
1239
1240        # A heterogenous stack used in conjunction with the tree traversal.
1241        # Make sure that the pops correspond to the pushes:
1242        self.context = []
1243
1244        # Title metadata:
1245        self.title_labels = []
1246        self.subtitle_labels = []
1247        # (if use_latex_docinfo: collects lists of
1248        # author/organization/contact/address lines)
1249        self.author_stack = []
1250        self.date = []
1251
1252        # PDF properties: pdftitle, pdfauthor
1253        # TODO?: pdfcreator, pdfproducer, pdfsubject, pdfkeywords
1254        self.pdfinfo = []
1255        self.pdfauthor = []
1256
1257        # Stack of section counters so that we don't have to use_latex_toc.
1258        # This will grow and shrink as processing occurs.
1259        # Initialized for potential first-level sections.
1260        self._section_number = [0]
1261
1262        # The current stack of enumerations so that we can expand
1263        # them into a compound enumeration.
1264        self._enumeration_counters = []
1265        # The maximum number of enumeration counters we've used.
1266        # If we go beyond this number, we need to create a new
1267        # counter; otherwise, just reuse an old one.
1268        self._max_enumeration_counters = 0
1269
1270        self._bibitems = []
1271
1272        # object for a table while proccessing.
1273        self.table_stack = []
1274        self.active_table = Table(self, 'longtable', settings.table_style)
1275
1276        # Where to collect the output of visitor methods (default: body)
1277        self.out = self.body
1278        self.out_stack = []  # stack of output collectors
1279
1280        # Process settings
1281        # ~~~~~~~~~~~~~~~~
1282        # Encodings:
1283        # Docutils' output-encoding => TeX input encoding
1284        if self.latex_encoding != 'ascii':
1285            self.requirements['_inputenc'] = (r'\usepackage[%s]{inputenc}'
1286                                              % self.latex_encoding)
1287        # TeX font encoding
1288        if not self.is_xetex:
1289            if self.font_encoding:
1290                self.requirements['_fontenc'] = (r'\usepackage[%s]{fontenc}' %
1291                                                 self.font_encoding)
1292            # ensure \textquotedbl is defined:
1293            for enc in self.font_encoding.split(','):
1294                enc = enc.strip()
1295                if enc == 'OT1':
1296                    self.requirements['_textquotedblOT1'] = (
1297                        r'\DeclareTextSymbol{\textquotedbl}{OT1}{`\"}')
1298                elif enc not in ('T1', 'T2A', 'T2B', 'T2C', 'T4', 'T5'):
1299                    self.requirements['_textquotedbl'] = (
1300                        r'\DeclareTextSymbolDefault{\textquotedbl}{T1}')
1301        # page layout with typearea (if there are relevant document options)
1302        if (settings.documentclass.find('scr') == -1 and
1303            (self.documentoptions.find('DIV') != -1 or
1304             self.documentoptions.find('BCOR') != -1)):
1305            self.requirements['typearea'] = r'\usepackage{typearea}'
1306
1307        # Stylesheets
1308        # (the name `self.stylesheet` is singular because only one
1309        # stylesheet was supported before Docutils 0.6).
1310        self.stylesheet = [self.stylesheet_call(path)
1311                           for path in utils.get_stylesheet_list(settings)]
1312
1313        # PDF setup
1314        if self.hyperlink_color in ('0', 'false', 'False', ''):
1315            self.hyperref_options = ''
1316        else:
1317            self.hyperref_options = 'colorlinks=true,linkcolor=%s,urlcolor=%s' % (
1318                                      self.hyperlink_color, self.hyperlink_color)
1319        if settings.hyperref_options:
1320            self.hyperref_options += ',' + settings.hyperref_options
1321
1322        # LaTeX Toc
1323        # include all supported sections in toc and PDF bookmarks
1324        # (or use documentclass-default (as currently))?
1325        ## if self.use_latex_toc:
1326        ##    self.requirements['tocdepth'] = (r'\setcounter{tocdepth}{%d}' %
1327        ##                                     len(self.d_class.sections))
1328
1329        # Section numbering
1330        if settings.sectnum_xform: # section numbering by Docutils
1331            PreambleCmds.secnumdepth = r'\setcounter{secnumdepth}{0}'
1332        else: # section numbering by LaTeX:
1333            secnumdepth = settings.sectnum_depth
1334            # Possible values of settings.sectnum_depth:
1335            # None  "sectnum" directive without depth arg -> LaTeX default
1336            #  0    no "sectnum" directive -> no section numbers
1337            # >0    value of "depth" argument -> translate to LaTeX levels:
1338            #       -1  part    (0 with "article" document class)
1339            #        0  chapter (missing in "article" document class)
1340            #        1  section
1341            #        2  subsection
1342            #        3  subsubsection
1343            #        4  paragraph
1344            #        5  subparagraph
1345            if secnumdepth is not None:
1346                # limit to supported levels
1347                secnumdepth = min(secnumdepth, len(self.d_class.sections))
1348                # adjust to document class and use_part_section settings
1349                if 'chapter' in  self.d_class.sections:
1350                    secnumdepth -= 1
1351                if self.d_class.sections[0] == 'part':
1352                    secnumdepth -= 1
1353                PreambleCmds.secnumdepth = \
1354                    r'\setcounter{secnumdepth}{%d}' % secnumdepth
1355
1356            # start with specified number:
1357            if (hasattr(settings, 'sectnum_start') and
1358                settings.sectnum_start != 1):
1359                self.requirements['sectnum_start'] = (
1360                    r'\setcounter{%s}{%d}' % (self.d_class.sections[0],
1361                                              settings.sectnum_start-1))
1362            # TODO: currently ignored (configure in a stylesheet):
1363            ## settings.sectnum_prefix
1364            ## settings.sectnum_suffix
1365
1366    # Auxiliary Methods
1367    # -----------------
1368
1369    def stylesheet_call(self, path):
1370        """Return code to reference or embed stylesheet file `path`"""
1371        # is it a package (no extension or *.sty) or "normal" tex code:
1372        (base, ext) = os.path.splitext(path)
1373        is_package = ext in ['.sty', '']
1374        # Embed content of style file:
1375        if self.settings.embed_stylesheet:
1376            if is_package:
1377                path = base + '.sty' # ensure extension
1378            try:
1379                content = io.FileInput(source_path=path,
1380                                       encoding='utf-8').read()
1381                self.settings.record_dependencies.add(path)
1382            except IOError, err:
1383                msg = u"Cannot embed stylesheet '%s':\n  %s." % (
1384                                path, SafeString(err.strerror))
1385                self.document.reporter.error(msg)
1386                return '% ' + msg.replace('\n', '\n% ')
1387            if is_package:
1388                content = '\n'.join([r'\makeatletter',
1389                                     content,
1390                                     r'\makeatother'])
1391            return '%% embedded stylesheet: %s\n%s' % (path, content)
1392        # Link to style file:
1393        if is_package:
1394            path = base # drop extension
1395            cmd = r'\usepackage{%s}'
1396        else:
1397            cmd = r'\input{%s}'
1398        if self.settings.stylesheet_path:
1399            # adapt path relative to output (cf. config.html#stylesheet-path)
1400            path = utils.relative_path(self.settings._destination, path)
1401        return cmd % path
1402
1403    def to_latex_encoding(self,docutils_encoding):
1404        """Translate docutils encoding name into LaTeX's.
1405
1406        Default method is remove "-" and "_" chars from docutils_encoding.
1407        """
1408        tr = {  'iso-8859-1': 'latin1',     # west european
1409                'iso-8859-2': 'latin2',     # east european
1410                'iso-8859-3': 'latin3',     # esperanto, maltese
1411                'iso-8859-4': 'latin4',     # north european, scandinavian, baltic
1412                'iso-8859-5': 'iso88595',   # cyrillic (ISO)
1413                'iso-8859-9': 'latin5',     # turkish
1414                'iso-8859-15': 'latin9',    # latin9, update to latin1.
1415                'mac_cyrillic': 'maccyr',   # cyrillic (on Mac)
1416                'windows-1251': 'cp1251',   # cyrillic (on Windows)
1417                'koi8-r': 'koi8-r',         # cyrillic (Russian)
1418                'koi8-u': 'koi8-u',         # cyrillic (Ukrainian)
1419                'windows-1250': 'cp1250',   #
1420                'windows-1252': 'cp1252',   #
1421                'us-ascii': 'ascii',        # ASCII (US)
1422                # unmatched encodings
1423                #'': 'applemac',
1424                #'': 'ansinew',  # windows 3.1 ansi
1425                #'': 'ascii',    # ASCII encoding for the range 32--127.
1426                #'': 'cp437',    # dos latin us
1427                #'': 'cp850',    # dos latin 1
1428                #'': 'cp852',    # dos latin 2
1429                #'': 'decmulti',
1430                #'': 'latin10',
1431                #'iso-8859-6': ''   # arabic
1432                #'iso-8859-7': ''   # greek
1433                #'iso-8859-8': ''   # hebrew
1434                #'iso-8859-10': ''   # latin6, more complete iso-8859-4
1435             }
1436        encoding = docutils_encoding.lower()
1437        if encoding in tr:
1438            return tr[encoding]
1439        # drop hyphen or low-line from "latin-1", "latin_1", "utf-8" and similar
1440        encoding = encoding.replace('_', '').replace('-', '')
1441        # strip the error handler
1442        return encoding.split(':')[0]
1443
1444    def language_label(self, docutil_label):
1445        return self.language_module.labels[docutil_label]
1446
1447    def encode(self, text):
1448        """Return text with 'problematic' characters escaped.
1449
1450        * Escape the ten special printing characters ``# $ % & ~ _ ^ \ { }``,
1451          square brackets ``[ ]``, double quotes and (in OT1) ``< | >``.
1452        * Translate non-supported Unicode characters.
1453        * Separate ``-`` (and more in literal text) to prevent input ligatures.
1454        """
1455        if self.verbatim:
1456            return text
1457
1458        # Set up the translation table:
1459        table = CharMaps.special.copy()
1460        # keep the underscore in citation references
1461        if self.inside_citation_reference_label:
1462            del(table[ord('_')])
1463        # Workarounds for OT1 font-encoding
1464        if self.font_encoding in ['OT1', ''] and not self.is_xetex:
1465            # * out-of-order characters in cmtt
1466            if self.literal:
1467                # replace underscore by underlined blank,
1468                # because this has correct width.
1469                table[ord('_')] = u'\\underline{~}'
1470                # the backslash doesn't work, so we use a mirrored slash.
1471                # \reflectbox is provided by graphicx:
1472                self.requirements['graphicx'] = self.graphicx_package
1473                table[ord('\\')] = ur'\reflectbox{/}'
1474            # * ``< | >`` come out as different chars (except for cmtt):
1475            else:
1476                table[ord('|')] = ur'\textbar{}'
1477                table[ord('<')] = ur'\textless{}'
1478                table[ord('>')] = ur'\textgreater{}'
1479        if self.insert_non_breaking_blanks:
1480            table[ord(' ')] = ur'~'
1481        # Unicode replacements for 8-bit tex engines (not required with XeTeX/LuaTeX):
1482        if not self.is_xetex:
1483            table.update(CharMaps.unsupported_unicode)
1484            if not self.latex_encoding.startswith('utf8'):
1485                table.update(CharMaps.utf8_supported_unicode)
1486                table.update(CharMaps.textcomp)
1487            table.update(CharMaps.pifont)
1488            # Characters that require a feature/package to render
1489            if [True for ch in text if ord(ch) in CharMaps.textcomp]:
1490                self.requirements['textcomp'] = PreambleCmds.textcomp
1491            if [True for ch in text if ord(ch) in CharMaps.pifont]:
1492                    self.requirements['pifont'] = '\\usepackage{pifont}'
1493
1494        text = text.translate(table)
1495
1496        # Break up input ligatures e.g. '--' to '-{}-'.
1497        if not self.is_xetex: # Not required with xetex/luatex
1498            separate_chars = '-'
1499            # In monospace-font, we also separate ',,', '``' and "''" and some
1500            # other characters which can't occur in non-literal text.
1501            if self.literal:
1502                separate_chars += ',`\'"<>'
1503            for char in separate_chars * 2:
1504                # Do it twice ("* 2") because otherwise we would replace
1505                # '---' by '-{}--'.
1506                text = text.replace(char + char, char + '{}' + char)
1507
1508        # Literal line breaks (in address or literal blocks):
1509        if self.insert_newline:
1510            lines = text.split('\n')
1511            # Add a protected space to blank lines (except the last)
1512            # to avoid ``! LaTeX Error: There's no line here to end.``
1513            for i, line in enumerate(lines[:-1]):
1514                if not line.lstrip():
1515                    lines[i] += '~'
1516            text = (r'\\' + '\n').join(lines)
1517        if self.literal and not self.insert_non_breaking_blanks:
1518            # preserve runs of spaces but allow wrapping
1519            text = text.replace('  ', ' ~')
1520        return text
1521
1522    def attval(self, text,
1523               whitespace=re.compile('[\n\r\t\v\f]')):
1524        """Cleanse, encode, and return attribute value text."""
1525        return self.encode(whitespace.sub(' ', text))
1526
1527    # TODO: is this used anywhere? -> update (use template) or delete
1528    ## def astext(self):
1529    ##     """Assemble document parts and return as string."""
1530    ##     head = '\n'.join(self.head_prefix + self.stylesheet + self.head)
1531    ##     body = ''.join(self.body_prefix  + self.body + self.body_suffix)
1532    ##     return head + '\n' + body
1533
1534    def is_inline(self, node):
1535        """Check whether a node represents an inline or block-level element"""
1536        return isinstance(node.parent, nodes.TextElement)
1537
1538    def append_hypertargets(self, node):
1539        """Append hypertargets for all ids of `node`"""
1540        # hypertarget places the anchor at the target's baseline,
1541        # so we raise it explicitely
1542        self.out.append('%\n'.join(['\\raisebox{1em}{\\hypertarget{%s}{}}' %
1543                                    id for id in node['ids']]))
1544
1545    def ids_to_labels(self, node, set_anchor=True):
1546        """Return list of label definitions for all ids of `node`
1547
1548        If `set_anchor` is True, an anchor is set with \phantomsection.
1549        """
1550        labels = ['\\label{%s}' % id for id in node.get('ids', [])]
1551        if set_anchor and labels:
1552            labels.insert(0, '\\phantomsection')
1553        return labels
1554
1555    def push_output_collector(self, new_out):
1556        self.out_stack.append(self.out)
1557        self.out = new_out
1558
1559    def pop_output_collector(self):
1560        self.out = self.out_stack.pop()
1561
1562    # Visitor methods
1563    # ---------------
1564
1565    def visit_Text(self, node):
1566        self.out.append(self.encode(node.astext()))
1567
1568    def depart_Text(self, node):
1569        pass
1570
1571    def visit_abbreviation(self, node):
1572        node['classes'].insert(0, 'abbreviation')
1573        self.visit_inline(node)
1574
1575    def depart_abbreviation(self, node):
1576        self.depart_inline(node)
1577
1578    def visit_acronym(self, node):
1579        node['classes'].insert(0, 'acronym')
1580        self.visit_inline(node)
1581
1582    def depart_acronym(self, node):
1583        self.depart_inline(node)
1584
1585    def visit_address(self, node):
1586        self.visit_docinfo_item(node, 'address')
1587
1588    def depart_address(self, node):
1589        self.depart_docinfo_item(node)
1590
1591    def visit_admonition(self, node):
1592        self.fallbacks['admonition'] = PreambleCmds.admonition
1593        if 'error' in node['classes']:
1594            self.fallbacks['error'] = PreambleCmds.error
1595        # strip the generic 'admonition' from the list of classes
1596        node['classes'] = [cls for cls in node['classes']
1597                           if cls != 'admonition']
1598        self.out.append('\n\\DUadmonition[%s]{\n' % ','.join(node['classes']))
1599
1600    def depart_admonition(self, node=None):
1601        self.out.append('}\n')
1602
1603    def visit_author(self, node):
1604        self.visit_docinfo_item(node, 'author')
1605
1606    def depart_author(self, node):
1607        self.depart_docinfo_item(node)
1608
1609    def visit_authors(self, node):
1610        # not used: visit_author is called anyway for each author.
1611        pass
1612
1613    def depart_authors(self, node):
1614        pass
1615
1616    def visit_block_quote(self, node):
1617        self.out.append( '%\n\\begin{quote}\n')
1618        if node['classes']:
1619            self.visit_inline(node)
1620
1621    def depart_block_quote(self, node):
1622        if node['classes']:
1623            self.depart_inline(node)
1624        self.out.append( '\n\\end{quote}\n')
1625
1626    def visit_bullet_list(self, node):
1627        if self.is_toc_list:
1628            self.out.append( '%\n\\begin{list}{}{}\n' )
1629        else:
1630            self.out.append( '%\n\\begin{itemize}\n' )
1631        # if node['classes']:
1632        #     self.visit_inline(node)
1633
1634    def depart_bullet_list(self, node):
1635        # if node['classes']:
1636        #     self.depart_inline(node)
1637        if self.is_toc_list:
1638            self.out.append( '\n\\end{list}\n' )
1639        else:
1640            self.out.append( '\n\\end{itemize}\n' )
1641
1642    def visit_superscript(self, node):
1643        self.out.append(r'\textsuperscript{')
1644        if node['classes']:
1645            self.visit_inline(node)
1646
1647    def depart_superscript(self, node):
1648        if node['classes']:
1649            self.depart_inline(node)
1650        self.out.append('}')
1651
1652    def visit_subscript(self, node):
1653        self.out.append(r'\textsubscript{') # requires `fixltx2e`
1654        if node['classes']:
1655            self.visit_inline(node)
1656
1657    def depart_subscript(self, node):
1658        if node['classes']:
1659            self.depart_inline(node)
1660        self.out.append('}')
1661
1662    def visit_caption(self, node):
1663        self.out.append('\n\\caption{')
1664
1665    def depart_caption(self, node):
1666        self.out.append('}\n')
1667
1668    def visit_title_reference(self, node):
1669        self.fallbacks['titlereference'] = PreambleCmds.titlereference
1670        self.out.append(r'\DUroletitlereference{')
1671        if node['classes']:
1672            self.visit_inline(node)
1673
1674    def depart_title_reference(self, node):
1675        if node['classes']:
1676            self.depart_inline(node)
1677        self.out.append( '}' )
1678
1679    def visit_citation(self, node):
1680        # TODO maybe use cite bibitems
1681        if self._use_latex_citations:
1682            self.push_output_collector([])
1683        else:
1684            # TODO: do we need these?
1685            ## self.requirements['~fnt_floats'] = PreambleCmds.footnote_floats
1686            self.out.append(r'\begin{figure}[b]')
1687            self.append_hypertargets(node)
1688
1689    def depart_citation(self, node):
1690        if self._use_latex_citations:
1691            label = self.out[0]
1692            text = ''.join(self.out[1:])
1693            self._bibitems.append([label, text])
1694            self.pop_output_collector()
1695        else:
1696            self.out.append('\\end{figure}\n')
1697
1698    def visit_citation_reference(self, node):
1699        if self._use_latex_citations:
1700            if not self.inside_citation_reference_label:
1701                self.out.append(r'\cite{')
1702                self.inside_citation_reference_label = 1
1703            else:
1704                assert self.body[-1] in (' ', '\n'),\
1705                        'unexpected non-whitespace while in reference label'
1706                del self.body[-1]
1707        else:
1708            href = ''
1709            if 'refid' in node:
1710                href = node['refid']
1711            elif 'refname' in node:
1712                href = self.document.nameids[node['refname']]
1713            self.out.append('\\hyperlink{%s}{[' % href)
1714
1715    def depart_citation_reference(self, node):
1716        if self._use_latex_citations:
1717            followup_citation = False
1718            # check for a following citation separated by a space or newline
1719            next_siblings = node.traverse(descend=False, siblings=True,
1720                                          include_self=False)
1721            if len(next_siblings) > 1:
1722                next = next_siblings[0]
1723                if (isinstance(next, nodes.Text) and
1724                    next.astext() in (' ', '\n')):
1725                    if next_siblings[1].__class__ == node.__class__:
1726                        followup_citation = True
1727            if followup_citation:
1728                self.out.append(',')
1729            else:
1730                self.out.append('}')
1731                self.inside_citation_reference_label = False
1732        else:
1733            self.out.append(']}')
1734
1735    def visit_classifier(self, node):
1736        self.out.append( '(\\textbf{' )
1737
1738    def depart_classifier(self, node):
1739        self.out.append( '})\n' )
1740
1741    def visit_colspec(self, node):
1742        self.active_table.visit_colspec(node)
1743
1744    def depart_colspec(self, node):
1745        pass
1746
1747    def visit_comment(self, node):
1748        # Precede every line with a comment sign, wrap in newlines
1749        self.out.append('\n%% %s\n' % node.astext().replace('\n', '\n% '))
1750        raise nodes.SkipNode
1751
1752    def depart_comment(self, node):
1753        pass
1754
1755    def visit_compound(self, node):
1756        pass
1757
1758    def depart_compound(self, node):
1759        pass
1760
1761    def visit_contact(self, node):
1762        self.visit_docinfo_item(node, 'contact')
1763
1764    def depart_contact(self, node):
1765        self.depart_docinfo_item(node)
1766
1767    def visit_container(self, node):
1768        pass
1769
1770    def depart_container(self, node):
1771        pass
1772
1773    def visit_copyright(self, node):
1774        self.visit_docinfo_item(node, 'copyright')
1775
1776    def depart_copyright(self, node):
1777        self.depart_docinfo_item(node)
1778
1779    def visit_date(self, node):
1780        self.visit_docinfo_item(node, 'date')
1781
1782    def depart_date(self, node):
1783        self.depart_docinfo_item(node)
1784
1785    def visit_decoration(self, node):
1786        # header and footer
1787        pass
1788
1789    def depart_decoration(self, node):
1790        pass
1791
1792    def visit_definition(self, node):
1793        pass
1794
1795    def depart_definition(self, node):
1796        self.out.append('\n')
1797
1798    def visit_definition_list(self, node):
1799        self.out.append( '%\n\\begin{description}\n' )
1800
1801    def depart_definition_list(self, node):
1802        self.out.append( '\\end{description}\n' )
1803
1804    def visit_definition_list_item(self, node):
1805        pass
1806
1807    def depart_definition_list_item(self, node):
1808        pass
1809
1810    def visit_description(self, node):
1811        self.out.append(' ')
1812
1813    def depart_description(self, node):
1814        pass
1815
1816    def visit_docinfo(self, node):
1817        self.push_output_collector(self.docinfo)
1818
1819    def depart_docinfo(self, node):
1820        self.pop_output_collector()
1821        # Some itmes (e.g. author) end up at other places
1822        if self.docinfo:
1823            # tabularx: automatic width of columns, no page breaks allowed.
1824            self.requirements['tabularx'] = r'\usepackage{tabularx}'
1825            self.fallbacks['_providelength'] = PreambleCmds.providelength
1826            self.fallbacks['docinfo'] = PreambleCmds.docinfo
1827            #
1828            self.docinfo.insert(0, '\n% Docinfo\n'
1829                                '\\begin{center}\n'
1830                                '\\begin{tabularx}{\\DUdocinfowidth}{lX}\n')
1831            self.docinfo.append('\\end{tabularx}\n'
1832                                '\\end{center}\n')
1833
1834    def visit_docinfo_item(self, node, name):
1835        if name == 'author':
1836            self.pdfauthor.append(self.attval(node.astext()))
1837        if self.use_latex_docinfo:
1838            if name in ('author', 'organization', 'contact', 'address'):
1839                # We attach these to the last author.  If any of them precedes
1840                # the first author, put them in a separate "author" group
1841                # (in lack of better semantics).
1842                if name == 'author' or not self.author_stack:
1843                    self.author_stack.append([])
1844                if name == 'address':   # newlines are meaningful
1845                    self.insert_newline = True
1846                    text = self.encode(node.astext())
1847                    self.insert_newline = False
1848                else:
1849                    text = self.attval(node.astext())
1850                self.author_stack[-1].append(text)
1851                raise nodes.SkipNode
1852            elif name == 'date':
1853                self.date.append(self.attval(node.astext()))
1854                raise nodes.SkipNode
1855        self.out.append('\\textbf{%s}: &\n\t' % self.language_label(name))
1856        if name == 'address':
1857            self.insert_newline = 1
1858            self.out.append('{\\raggedright\n')
1859            self.context.append(' } \\\\\n')
1860        else:
1861            self.context.append(' \\\\\n')
1862
1863    def depart_docinfo_item(self, node):
1864        self.out.append(self.context.pop())
1865        # for address we did set insert_newline
1866        self.insert_newline = False
1867
1868    def visit_doctest_block(self, node):
1869        self.visit_literal_block(node)
1870
1871    def depart_doctest_block(self, node):
1872        self.depart_literal_block(node)
1873
1874    def visit_document(self, node):
1875        # titled document?
1876        if (self.use_latex_docinfo or len(node) and
1877            isinstance(node[0], nodes.title)):
1878            self.title_labels += self.ids_to_labels(node, set_anchor=False)
1879
1880    def depart_document(self, node):
1881        # Complete header with information gained from walkabout
1882        # * language setup
1883        if (self.babel.otherlanguages or
1884            self.babel.language not in ('', 'english')):
1885            self.requirements['babel'] = self.babel()
1886        # * conditional requirements (before style sheet)
1887        self.requirements = self.requirements.sortedvalues()
1888        # * coditional fallback definitions (after style sheet)
1889        self.fallbacks = self.fallbacks.sortedvalues()
1890        # * PDF properties
1891        self.pdfsetup.append(PreambleCmds.linking % self.hyperref_options)
1892        if self.pdfauthor:
1893            authors = self.author_separator.join(self.pdfauthor)
1894            self.pdfinfo.append('  pdfauthor={%s}' % authors)
1895        if self.pdfinfo:
1896            self.pdfsetup += [r'\hypersetup{'] + self.pdfinfo + ['}']
1897        # Complete body
1898        # * document title (with "use_latex_docinfo" also
1899        #   'author', 'organization', 'contact', 'address' and 'date')
1900        if self.title or (
1901           self.use_latex_docinfo and (self.author_stack or self.date)):
1902            # with the default template, titledata is written to the preamble
1903            self.titledata.append('%%% Title Data')
1904            # \title (empty \title prevents error with \maketitle)
1905            if self.title:
1906                self.title.insert(0, '\phantomsection%\n  ')
1907            title = [''.join(self.title)] + self.title_labels
1908            if self.subtitle:
1909                title += [r'\\ % subtitle',
1910                          r'\DUdocumentsubtitle{%s}' % ''.join(self.subtitle)
1911                         ] + self.subtitle_labels
1912            self.titledata.append(r'\title{%s}' % '%\n  '.join(title))
1913            # \author (empty \author prevents warning with \maketitle)
1914            authors = ['\\\\\n'.join(author_entry)
1915                        for author_entry in self.author_stack]
1916            self.titledata.append(r'\author{%s}' %
1917                                      ' \\and\n'.join(authors))
1918            # \date (empty \date prevents defaulting to \today)
1919            self.titledata.append(r'\date{%s}' % ', '.join(self.date))
1920            # \maketitle in the body formats title with LaTeX
1921            self.body_pre_docinfo.append('\\maketitle\n')
1922
1923        # * bibliography
1924        #   TODO insertion point of bibliography should be configurable.
1925        if self._use_latex_citations and len(self._bibitems)>0:
1926            if not self.bibtex:
1927                widest_label = ''
1928                for bi in self._bibitems:
1929                    if len(widest_label)<len(bi[0]):
1930                        widest_label = bi[0]
1931                self.out.append('\n\\begin{thebibliography}{%s}\n' %
1932                                 widest_label)
1933                for bi in self._bibitems:
1934                    # cite_key: underscores must not be escaped
1935                    cite_key = bi[0].replace(r'\_','_')
1936                    self.out.append('\\bibitem[%s]{%s}{%s}\n' %
1937                                     (bi[0], cite_key, bi[1]))
1938                self.out.append('\\end{thebibliography}\n')
1939            else:
1940                self.out.append('\n\\bibliographystyle{%s}\n' %
1941                                self.bibtex[0])
1942                self.out.append('\\bibliography{%s}\n' % self.bibtex[1])
1943        # * make sure to generate a toc file if needed for local contents:
1944        if 'minitoc' in self.requirements and not self.has_latex_toc:
1945            self.out.append('\n\\faketableofcontents % for local ToCs\n')
1946
1947    def visit_emphasis(self, node):
1948        self.out.append('\\emph{')
1949        if node['classes']:
1950            self.visit_inline(node)
1951
1952    def depart_emphasis(self, node):
1953        if node['classes']:
1954            self.depart_inline(node)
1955        self.out.append('}')
1956
1957    def visit_entry(self, node):
1958        self.active_table.visit_entry()
1959        # cell separation
1960        # BUG: the following fails, with more than one multirow
1961        # starting in the second column (or later) see
1962        # ../../../test/functional/input/data/latex.txt
1963        if self.active_table.get_entry_number() == 1:
1964            # if the first row is a multirow, this actually is the second row.
1965            # this gets hairy if rowspans follow each other.
1966            if self.active_table.get_rowspan(0):
1967                count = 0
1968                while self.active_table.get_rowspan(count):
1969                    count += 1
1970                    self.out.append(' & ')
1971                self.active_table.visit_entry() # increment cell count
1972        else:
1973            self.out.append(' & ')
1974        # multirow, multicolumn
1975        # IN WORK BUG TODO HACK continues here
1976        # multirow in LaTeX simply will enlarge the cell over several rows
1977        # (the following n if n is positive, the former if negative).
1978        if 'morerows' in node and 'morecols' in node:
1979            raise NotImplementedError('Cells that '
1980            'span multiple rows *and* columns are not supported, sorry.')
1981        if 'morerows' in node:
1982            self.requirements['multirow'] = r'\usepackage{multirow}'
1983            count = node['morerows'] + 1
1984            self.active_table.set_rowspan(
1985                                self.active_table.get_entry_number()-1,count)
1986            # TODO why does multirow end on % ? needs to be checked for below
1987            self.out.append('\\multirow{%d}{%s}{%%' %
1988                            (count,self.active_table.get_column_width()))
1989            self.context.append('}')
1990        elif 'morecols' in node:
1991            # the vertical bar before column is missing if it is the first
1992            # column. the one after always.
1993            if self.active_table.get_entry_number() == 1:
1994                bar1 = self.active_table.get_vertical_bar()
1995            else:
1996                bar1 = ''
1997            count = node['morecols'] + 1
1998            self.out.append('\\multicolumn{%d}{%sp{%s}%s}{' %
1999                    (count, bar1,
2000                     self.active_table.get_multicolumn_width(
2001                        self.active_table.get_entry_number(),
2002                        count),
2003                     self.active_table.get_vertical_bar()))
2004            self.context.append('}')
2005        else:
2006            self.context.append('')
2007
2008        # header / not header
2009        if isinstance(node.parent.parent, nodes.thead):
2010            if self.out[-1].endswith("%"):
2011                self.out.append("\n")
2012            self.out.append('\\textbf{%')
2013            self.context.append('}')
2014        elif self.active_table.is_stub_column():
2015            if self.out[-1].endswith("%"):
2016                self.out.append("\n")
2017            self.out.append('\\textbf{')
2018            self.context.append('}')
2019        else:
2020            self.context.append('')
2021
2022    def depart_entry(self, node):
2023        self.out.append(self.context.pop()) # header / not header
2024        self.out.append(self.context.pop()) # multirow/column
2025        # if following row is spanned from above.
2026        if self.active_table.get_rowspan(self.active_table.get_entry_number()):
2027           self.out.append(' & ')
2028           self.active_table.visit_entry() # increment cell count
2029
2030    def visit_row(self, node):
2031        self.active_table.visit_row()
2032
2033    def depart_row(self, node):
2034        self.out.extend(self.active_table.depart_row())
2035
2036    def visit_enumerated_list(self, node):
2037        # We create our own enumeration list environment.
2038        # This allows to set the style and starting value
2039        # and unlimited nesting.
2040        enum_style = {'arabic':'arabic',
2041                'loweralpha':'alph',
2042                'upperalpha':'Alph',
2043                'lowerroman':'roman',
2044                'upperroman':'Roman' }
2045        enum_suffix = ''
2046        if 'suffix' in node:
2047            enum_suffix = node['suffix']
2048        enum_prefix = ''
2049        if 'prefix' in node:
2050            enum_prefix = node['prefix']
2051        if self.compound_enumerators:
2052            pref = ''
2053            if self.section_prefix_for_enumerators and self.section_level:
2054                for i in range(self.section_level):
2055                    pref += '%d.' % self._section_number[i]
2056                pref = pref[:-1] + self.section_enumerator_separator
2057                enum_prefix += pref
2058            for ctype, cname in self._enumeration_counters:
2059                enum_prefix += '\\%s{%s}.' % (ctype, cname)
2060        enum_type = 'arabic'
2061        if 'enumtype' in node:
2062            enum_type = node['enumtype']
2063        if enum_type in enum_style:
2064            enum_type = enum_style[enum_type]
2065
2066        counter_name = 'listcnt%d' % len(self._enumeration_counters)
2067        self._enumeration_counters.append((enum_type, counter_name))
2068        # If we haven't used this counter name before, then create a
2069        # new counter; otherwise, reset & reuse the old counter.
2070        if len(self._enumeration_counters) > self._max_enumeration_counters:
2071            self._max_enumeration_counters = len(self._enumeration_counters)
2072            self.out.append('\\newcounter{%s}\n' % counter_name)
2073        else:
2074            self.out.append('\\setcounter{%s}{0}\n' % counter_name)
2075
2076        self.out.append('\\begin{list}{%s\\%s{%s}%s}\n' %
2077                        (enum_prefix,enum_type,counter_name,enum_suffix))
2078        self.out.append('{\n')
2079        self.out.append('\\usecounter{%s}\n' % counter_name)
2080        # set start after usecounter, because it initializes to zero.
2081        if 'start' in node:
2082            self.out.append('\\addtocounter{%s}{%d}\n' %
2083                            (counter_name,node['start']-1))
2084        ## set rightmargin equal to leftmargin
2085        self.out.append('\\setlength{\\rightmargin}{\\leftmargin}\n')
2086        self.out.append('}\n')
2087
2088    def depart_enumerated_list(self, node):
2089        self.out.append('\\end{list}\n')
2090        self._enumeration_counters.pop()
2091
2092    def visit_field(self, node):
2093        # real output is done in siblings: _argument, _body, _name
2094        pass
2095
2096    def depart_field(self, node):
2097        self.out.append('\n')
2098        ##self.out.append('%[depart_field]\n')
2099
2100    def visit_field_argument(self, node):
2101        self.out.append('%[visit_field_argument]\n')
2102
2103    def depart_field_argument(self, node):
2104        self.out.append('%[depart_field_argument]\n')
2105
2106    def visit_field_body(self, node):
2107        pass
2108
2109    def depart_field_body(self, node):
2110        if self.out is self.docinfo:
2111            self.out.append(r'\\')
2112
2113    def visit_field_list(self, node):
2114        if self.out is not self.docinfo:
2115            self.fallbacks['fieldlist'] = PreambleCmds.fieldlist
2116            self.out.append('%\n\\begin{DUfieldlist}\n')
2117
2118    def depart_field_list(self, node):
2119        if self.out is not self.docinfo:
2120            self.out.append('\\end{DUfieldlist}\n')
2121
2122    def visit_field_name(self, node):
2123        if self.out is self.docinfo:
2124            self.out.append('\\textbf{')
2125        else:
2126            # Commands with optional args inside an optional arg must be put
2127            # in a group, e.g. ``\item[{\hyperref[label]{text}}]``.
2128            self.out.append('\\item[{')
2129
2130    def depart_field_name(self, node):
2131        if self.out is self.docinfo:
2132            self.out.append('}: &')
2133        else:
2134            self.out.append(':}]')
2135
2136    def visit_figure(self, node):
2137        self.requirements['float_settings'] = PreambleCmds.float_settings
2138        # The 'align' attribute sets the "outer alignment",
2139        # for "inner alignment" use LaTeX default alignment (similar to HTML)
2140        alignment = node.attributes.get('align', 'center')
2141        if alignment != 'center':
2142            # The LaTeX "figure" environment always uses the full textwidth,
2143            # so "outer alignment" is ignored. Just write a comment.
2144            # TODO: use the wrapfigure environment?
2145            self.out.append('\n\\begin{figure} %% align = "%s"\n' % alignment)
2146        else:
2147            self.out.append('\n\\begin{figure}\n')
2148        if node.get('ids'):
2149            self.out += self.ids_to_labels(node) + ['\n']
2150
2151    def depart_figure(self, node):
2152        self.out.append('\\end{figure}\n')
2153
2154    def visit_footer(self, node):
2155        self.push_output_collector([])
2156        self.out.append(r'\newcommand{\DUfooter}{')
2157
2158    def depart_footer(self, node):
2159        self.out.append('}')
2160        self.requirements['~footer'] = ''.join(self.out)
2161        self.pop_output_collector()
2162
2163    def visit_footnote(self, node):
2164        try:
2165            backref = node['backrefs'][0]
2166        except IndexError:
2167            backref = node['ids'][0] # no backref, use self-ref instead
2168        if self.settings.figure_footnotes:
2169            self.requirements['~fnt_floats'] = PreambleCmds.footnote_floats
2170            self.out.append('\\begin{figure}[b]')
2171            self.append_hypertargets(node)
2172            if node.get('id') == node.get('name'):  # explicite label
2173                self.out += self.ids_to_labels(node)
2174        elif self.docutils_footnotes:
2175            self.fallbacks['footnotes'] = PreambleCmds.footnotes
2176            num,text = node.astext().split(None,1)
2177            if self.settings.footnote_references == 'brackets':
2178                num = '[%s]' % num
2179            self.out.append('%%\n\\DUfootnotetext{%s}{%s}{%s}{' %
2180                            (node['ids'][0], backref, self.encode(num)))
2181            if node['ids'] == node['names']:
2182                self.out += self.ids_to_labels(node)
2183            # mask newline to prevent spurious whitespace:
2184            self.out.append('%')
2185        ## else:  # TODO: "real" LaTeX \footnote{}s
2186
2187    def depart_footnote(self, node):
2188        if self.figure_footnotes:
2189            self.out.append('\\end{figure}\n')
2190        else:
2191            self.out.append('}\n')
2192
2193    def visit_footnote_reference(self, node):
2194        href = ''
2195        if 'refid' in node:
2196            href = node['refid']
2197        elif 'refname' in node:
2198            href = self.document.nameids[node['refname']]
2199        # if not self.docutils_footnotes:
2200            # TODO: insert footnote content at (or near) this place
2201            # print "footnote-ref to", node['refid']
2202            # footnotes = (self.document.footnotes +
2203            #              self.document.autofootnotes +
2204            #              self.document.symbol_footnotes)
2205            # for footnote in footnotes:
2206            #     # print footnote['ids']
2207            #     if node.get('refid', '') in footnote['ids']:
2208            #         print 'matches', footnote['ids']
2209        format = self.settings.footnote_references
2210        if format == 'brackets':
2211            self.append_hypertargets(node)
2212            self.out.append('\\hyperlink{%s}{[' % href)
2213            self.context.append(']}')
2214        else:
2215            self.fallbacks['footnotes'] = PreambleCmds.footnotes
2216            self.out.append(r'\DUfootnotemark{%s}{%s}{' %
2217                            (node['ids'][0], href))
2218            self.context.append('}')
2219
2220    def depart_footnote_reference(self, node):
2221        self.out.append(self.context.pop())
2222
2223    # footnote/citation label
2224    def label_delim(self, node, bracket, superscript):
2225        if isinstance(node.parent, nodes.footnote):
2226            if not self.figure_footnotes:
2227                raise nodes.SkipNode
2228            if self.settings.footnote_references == 'brackets':
2229                self.out.append(bracket)
2230            else:
2231                self.out.append(superscript)
2232        else:
2233            assert isinstance(node.parent, nodes.citation)
2234            if not self._use_latex_citations:
2235                self.out.append(bracket)
2236
2237    def visit_label(self, node):
2238        """footnote or citation label: in brackets or as superscript"""
2239        self.label_delim(node, '[', '\\textsuperscript{')
2240
2241    def depart_label(self, node):
2242        self.label_delim(node, ']', '}')
2243
2244    # elements generated by the framework e.g. section numbers.
2245    def visit_generated(self, node):
2246        pass
2247
2248    def depart_generated(self, node):
2249        pass
2250
2251    def visit_header(self, node):
2252        self.push_output_collector([])
2253        self.out.append(r'\newcommand{\DUheader}{')
2254
2255    def depart_header(self, node):
2256        self.out.append('}')
2257        self.requirements['~header'] = ''.join(self.out)
2258        self.pop_output_collector()
2259
2260    def to_latex_length(self, length_str, pxunit=None):
2261        """Convert `length_str` with rst lenght to LaTeX length
2262        """
2263        if pxunit is not None:
2264            sys.stderr.write('deprecation warning: LaTeXTranslator.to_latex_length()'
2265                             ' option `pxunit` will be removed.')
2266        match = re.match('(\d*\.?\d*)\s*(\S*)', length_str)
2267        if not match:
2268            return length_str
2269        value, unit = match.groups()[:2]
2270        # no unit or "DTP" points (called 'bp' in TeX):
2271        if unit in ('', 'pt'):
2272            length_str = '%sbp' % value
2273        # percentage: relate to current line width
2274        elif unit == '%':
2275            length_str = '%.3f\\linewidth' % (float(value)/100.0)
2276        elif self.is_xetex and unit == 'px':
2277            # XeTeX does not know the length unit px.
2278            # Use \pdfpxdimen, the macro to set the value of 1 px in pdftex.
2279            # This way, configuring works the same for pdftex and xetex.
2280            self.fallbacks['_providelength'] = PreambleCmds.providelength
2281            self.fallbacks['px'] = '\n\\DUprovidelength{\\pdfpxdimen}{1bp}\n'
2282            length_str = r'%s\pdfpxdimen' % value
2283        return length_str
2284
2285    def visit_image(self, node):
2286        self.requirements['graphicx'] = self.graphicx_package
2287        attrs = node.attributes
2288        # Convert image URI to a local file path
2289        imagepath = urllib.url2pathname(attrs['uri']).replace('\\', '/')
2290        # alignment defaults:
2291        if not 'align' in attrs:
2292            # Set default align of image in a figure to 'center'
2293            if isinstance(node.parent, nodes.figure):
2294                attrs['align'] = 'center'
2295            # query 'align-*' class argument
2296            for cls in node['classes']:
2297                if cls.startswith('align-'):
2298                    attrs['align'] = cls.split('-')[1]
2299        # pre- and postfix (prefix inserted in reverse order)
2300        pre = []
2301        post = []
2302        include_graphics_options = []
2303        align_codes = {
2304            # inline images: by default latex aligns the bottom.
2305            'bottom': ('', ''),
2306            'middle': (r'\raisebox{-0.5\height}{', '}'),
2307            'top':    (r'\raisebox{-\height}{', '}'),
2308            # block level images:
2309            'center': (r'\noindent\makebox[\textwidth][c]{', '}'),
2310            'left':   (r'\noindent{', r'\hfill}'),
2311            'right':  (r'\noindent{\hfill', '}'),}
2312        if 'align' in attrs:
2313            # TODO: warn or ignore non-applicable alignment settings?
2314            try:
2315                align_code = align_codes[attrs['align']]
2316                pre.append(align_code[0])
2317                post.append(align_code[1])
2318            except KeyError:
2319                pass                    # TODO: warn?
2320        if 'height' in attrs:
2321            include_graphics_options.append('height=%s' %
2322                            self.to_latex_length(attrs['height']))
2323        if 'scale' in attrs:
2324            include_graphics_options.append('scale=%f' %
2325                                            (attrs['scale'] / 100.0))
2326        if 'width' in attrs:
2327            include_graphics_options.append('width=%s' %
2328                            self.to_latex_length(attrs['width']))
2329        if not (self.is_inline(node) or
2330                isinstance(node.parent, nodes.figure)):
2331            pre.append('\n')
2332            post.append('\n')
2333        pre.reverse()
2334        self.out.extend(pre)
2335        options = ''
2336        if include_graphics_options:
2337            options = '[%s]' % (','.join(include_graphics_options))
2338        self.out.append('\\includegraphics%s{%s}' % (options, imagepath))
2339        self.out.extend(post)
2340
2341    def depart_image(self, node):
2342        if node.get('ids'):
2343            self.out += self.ids_to_labels(node) + ['\n']
2344
2345    def visit_inline(self, node): # <span>, i.e. custom roles
2346        self.context.append('}' * len(node['classes']))
2347        for cls in node['classes']:
2348            if cls == 'align-center':
2349                self.fallbacks['align-center'] = PreambleCmds.align_center
2350            if cls.startswith('language-'):
2351                language = self.babel.language_name(cls[9:])
2352                if language:
2353                    self.babel.otherlanguages[language] = True
2354                    self.out.append(r'\foreignlanguage{%s}{' % language)
2355            else:
2356                self.fallbacks['inline'] = PreambleCmds.inline
2357                self.out.append(r'\DUrole{%s}{' % cls)
2358
2359    def depart_inline(self, node):
2360        self.out.append(self.context.pop())
2361
2362    def visit_interpreted(self, node):
2363        # @@@ Incomplete, pending a proper implementation on the
2364        # Parser/Reader end.
2365        self.visit_literal(node)
2366
2367    def depart_interpreted(self, node):
2368        self.depart_literal(node)
2369
2370    def visit_legend(self, node):
2371        self.fallbacks['legend'] = PreambleCmds.legend
2372        self.out.append('\\begin{DUlegend}')
2373
2374    def depart_legend(self, node):
2375        self.out.append('\\end{DUlegend}\n')
2376
2377    def visit_line(self, node):
2378        self.out.append('\item[] ')
2379
2380    def depart_line(self, node):
2381        self.out.append('\n')
2382
2383    def visit_line_block(self, node):
2384        self.fallbacks['_providelength'] = PreambleCmds.providelength
2385        self.fallbacks['lineblock'] = PreambleCmds.lineblock
2386        if isinstance(node.parent, nodes.line_block):
2387            self.out.append('\\item[]\n'
2388                             '\\begin{DUlineblock}{\\DUlineblockindent}\n')
2389        else:
2390            self.out.append('\n\\begin{DUlineblock}{0em}\n')
2391        if node['classes']:
2392            self.visit_inline(node)
2393            self.out.append('\n')
2394
2395    def depart_line_block(self, node):
2396        if node['classes']:
2397            self.depart_inline(node)
2398            self.out.append('\n')
2399        self.out.append('\\end{DUlineblock}\n')
2400
2401    def visit_list_item(self, node):
2402        self.out.append('\n\\item ')
2403
2404    def depart_list_item(self, node):
2405        pass
2406
2407    def visit_literal(self, node):
2408        self.literal = True
2409        if 'code' in node['classes'] and (
2410                    self.settings.syntax_highlight != 'none'):
2411            self.requirements['color'] = PreambleCmds.color
2412            self.fallbacks['code'] = PreambleCmds.highlight_rules
2413        self.out.append('\\texttt{')
2414        if node['classes']:
2415            self.visit_inline(node)
2416
2417    def depart_literal(self, node):
2418        self.literal = False
2419        if node['classes']:
2420            self.depart_inline(node)
2421        self.out.append('}')
2422
2423    # Literal blocks are used for '::'-prefixed literal-indented
2424    # blocks of text, where the inline markup is not recognized,
2425    # but are also the product of the "parsed-literal" directive,
2426    # where the markup is respected.
2427    #
2428    # In both cases, we want to use a typewriter/monospaced typeface.
2429    # For "real" literal-blocks, we can use \verbatim, while for all
2430    # the others we must use \mbox or \alltt.
2431    #
2432    # We can distinguish between the two kinds by the number of
2433    # siblings that compose this node: if it is composed by a
2434    # single element, it's either
2435    # * a real one,
2436    # * a parsed-literal that does not contain any markup, or
2437    # * a parsed-literal containing just one markup construct.
2438    def is_plaintext(self, node):
2439        """Check whether a node can be typeset verbatim"""
2440        return (len(node) == 1) and isinstance(node[0], nodes.Text)
2441
2442    def visit_literal_block(self, node):
2443        """Render a literal block."""
2444        # environments and packages to typeset literal blocks
2445        packages = {'listing': r'\usepackage{moreverb}',
2446                    'lstlisting': r'\usepackage{listings}',
2447                    'Verbatim': r'\usepackage{fancyvrb}',
2448                    # 'verbatim': '',
2449                    'verbatimtab': r'\usepackage{moreverb}'}
2450
2451        if not self.active_table.is_open():
2452            # no quote inside tables, to avoid vertical space between
2453            # table border and literal block.
2454            # BUG: fails if normal text precedes the literal block.
2455            self.out.append('%\n\\begin{quote}')
2456            self.context.append('\n\\end{quote}\n')
2457        else:
2458            self.out.append('\n')
2459            self.context.append('\n')
2460        if self.literal_block_env != '' and self.is_plaintext(node):
2461            self.requirements['literal_block'] = packages.get(
2462                                                  self.literal_block_env, '')
2463            self.verbatim = True
2464            self.out.append('\\begin{%s}%s\n' % (self.literal_block_env,
2465                                                 self.literal_block_options))
2466        else:
2467            self.literal = True
2468            self.insert_newline = True
2469            self.insert_non_breaking_blanks = True
2470            if 'code' in node['classes'] and (
2471                    self.settings.syntax_highlight != 'none'):
2472                self.requirements['color'] = PreambleCmds.color
2473                self.fallbacks['code'] = PreambleCmds.highlight_rules
2474            self.out.append('{\\ttfamily \\raggedright \\noindent\n')
2475
2476    def depart_literal_block(self, node):
2477        if self.verbatim:
2478            self.out.append('\n\\end{%s}\n' % self.literal_block_env)
2479            self.verbatim = False
2480        else:
2481            self.out.append('\n}')
2482            self.insert_non_breaking_blanks = False
2483            self.insert_newline = False
2484            self.literal = False
2485        self.out.append(self.context.pop())
2486
2487    ## def visit_meta(self, node):
2488    ##     self.out.append('[visit_meta]\n')
2489        # TODO: set keywords for pdf?
2490        # But:
2491        #  The reStructuredText "meta" directive creates a "pending" node,
2492        #  which contains knowledge that the embedded "meta" node can only
2493        #  be handled by HTML-compatible writers. The "pending" node is
2494        #  resolved by the docutils.transforms.components.Filter transform,
2495        #  which checks that the calling writer supports HTML; if it doesn't,
2496        #  the "pending" node (and enclosed "meta" node) is removed from the
2497        #  document.
2498        #  --- docutils/docs/peps/pep-0258.html#transformer
2499
2500    ## def depart_meta(self, node):
2501    ##     self.out.append('[depart_meta]\n')
2502
2503    def visit_math(self, node, math_env='$'):
2504        """math role"""
2505        if node['classes']:
2506            self.visit_inline(node)
2507        self.requirements['amsmath'] = r'\usepackage{amsmath}'
2508        math_code = node.astext().translate(unichar2tex.uni2tex_table)
2509        if node.get('ids'):
2510            math_code = '\n'.join([math_code] + self.ids_to_labels(node))
2511        if math_env == '$':
2512            wrapper = u'$%s$'
2513        else:
2514            wrapper = u'\n'.join(['%%',
2515                                 r'\begin{%s}' % math_env,
2516                                 '%s',
2517                                 r'\end{%s}' % math_env])
2518        # print repr(wrapper), repr(math_code)
2519        self.out.append(wrapper % math_code)
2520        if node['classes']:
2521            self.depart_inline(node)
2522        # Content already processed:
2523        raise nodes.SkipNode
2524
2525    def depart_math(self, node):
2526        pass # never reached
2527
2528    def visit_math_block(self, node):
2529        math_env = pick_math_environment(node.astext())
2530        self.visit_math(node, math_env=math_env)
2531
2532    def depart_math_block(self, node):
2533        pass # never reached
2534
2535    def visit_option(self, node):
2536        if self.context[-1]:
2537            # this is not the first option
2538            self.out.append(', ')
2539
2540    def depart_option(self, node):
2541        # flag that the first option is done.
2542        self.context[-1] += 1
2543
2544    def visit_option_argument(self, node):
2545        """Append the delimiter betweeen an option and its argument to body."""
2546        self.out.append(node.get('delimiter', ' '))
2547
2548    def depart_option_argument(self, node):
2549        pass
2550
2551    def visit_option_group(self, node):
2552        self.out.append('\n\\item[')
2553        # flag for first option
2554        self.context.append(0)
2555
2556    def depart_option_group(self, node):
2557        self.context.pop() # the flag
2558        self.out.append('] ')
2559
2560    def visit_option_list(self, node):
2561        self.fallbacks['_providelength'] = PreambleCmds.providelength
2562        self.fallbacks['optionlist'] = PreambleCmds.optionlist
2563        self.out.append('%\n\\begin{DUoptionlist}\n')
2564
2565    def depart_option_list(self, node):
2566        self.out.append('\n\\end{DUoptionlist}\n')
2567
2568    def visit_option_list_item(self, node):
2569        pass
2570
2571    def depart_option_list_item(self, node):
2572        pass
2573
2574    def visit_option_string(self, node):
2575        ##self.out.append(self.starttag(node, 'span', '', CLASS='option'))
2576        pass
2577
2578    def depart_option_string(self, node):
2579        ##self.out.append('</span>')
2580        pass
2581
2582    def visit_organization(self, node):
2583        self.visit_docinfo_item(node, 'organization')
2584
2585    def depart_organization(self, node):
2586        self.depart_docinfo_item(node)
2587
2588    def visit_paragraph(self, node):
2589        # insert blank line, if the paragraph is not first in a list item
2590        # nor follows a non-paragraph node in a compound
2591        index = node.parent.index(node)
2592        if (index == 0 and (isinstance(node.parent, nodes.list_item) or
2593                            isinstance(node.parent, nodes.description))):
2594            pass
2595        elif (index > 0 and isinstance(node.parent, nodes.compound) and
2596              not isinstance(node.parent[index - 1], nodes.paragraph) and
2597              not isinstance(node.parent[index - 1], nodes.compound)):
2598            pass
2599        else:
2600            self.out.append('\n')
2601        if node.get('ids'):
2602            self.out += self.ids_to_labels(node) + ['\n']
2603        if node['classes']:
2604            self.visit_inline(node)
2605
2606    def depart_paragraph(self, node):
2607        if node['classes']:
2608            self.depart_inline(node)
2609        self.out.append('\n')
2610
2611    def visit_problematic(self, node):
2612        self.requirements['color'] = PreambleCmds.color
2613        self.out.append('%\n')
2614        self.append_hypertargets(node)
2615        self.out.append(r'\hyperlink{%s}{\textbf{\color{red}' % node['refid'])
2616
2617    def depart_problematic(self, node):
2618        self.out.append('}}')
2619
2620    def visit_raw(self, node):
2621        if not 'latex' in node.get('format', '').split():
2622            raise nodes.SkipNode
2623        if not self.is_inline(node):
2624            self.out.append('\n')
2625        if node['classes']:
2626            self.visit_inline(node)
2627        # append "as-is" skipping any LaTeX-encoding
2628        self.verbatim = True
2629
2630    def depart_raw(self, node):
2631        self.verbatim = False
2632        if node['classes']:
2633            self.depart_inline(node)
2634        if not self.is_inline(node):
2635            self.out.append('\n')
2636
2637    def has_unbalanced_braces(self, string):
2638        """Test whether there are unmatched '{' or '}' characters."""
2639        level = 0
2640        for ch in string:
2641            if ch == '{':
2642                level += 1
2643            if ch == '}':
2644                level -= 1
2645            if level < 0:
2646                return True
2647        return level != 0
2648
2649    def visit_reference(self, node):
2650        # We need to escape #, \, and % if we use the URL in a command.
2651        special_chars = {ord('#'): ur'\#',
2652                         ord('%'): ur'\%',
2653                         ord('\\'): ur'\\',
2654                        }
2655        # external reference (URL)
2656        if 'refuri' in node:
2657            href = unicode(node['refuri']).translate(special_chars)
2658            # problematic chars double caret and unbalanced braces:
2659            if href.find('^^') != -1 or self.has_unbalanced_braces(href):
2660                self.error(
2661                    'External link "%s" not supported by LaTeX.\n'
2662                    ' (Must not contain "^^" or unbalanced braces.)' % href)
2663            if node['refuri'] == node.astext():
2664                self.out.append(r'\url{%s}' % href)
2665                raise nodes.SkipNode
2666            self.out.append(r'\href{%s}{' % href)
2667            return
2668        # internal reference
2669        if 'refid' in node:
2670            href = node['refid']
2671        elif 'refname' in node:
2672            href = self.document.nameids[node['refname']]
2673        else:
2674            raise AssertionError('Unknown reference.')
2675        if not self.is_inline(node):
2676            self.out.append('\n')
2677        self.out.append('\\hyperref[%s]{' % href)
2678        if self._reference_label:
2679            self.out.append('\\%s{%s}}' %
2680                            (self._reference_label, href.replace('#', '')))
2681            raise nodes.SkipNode
2682
2683    def depart_reference(self, node):
2684        self.out.append('}')
2685        if not self.is_inline(node):
2686            self.out.append('\n')
2687
2688    def visit_revision(self, node):
2689        self.visit_docinfo_item(node, 'revision')
2690
2691    def depart_revision(self, node):
2692        self.depart_docinfo_item(node)
2693
2694    def visit_section(self, node):
2695        self.section_level += 1
2696        # Initialize counter for potential subsections:
2697        self._section_number.append(0)
2698        # Counter for this section's level (initialized by parent section):
2699        self._section_number[self.section_level - 1] += 1
2700
2701    def depart_section(self, node):
2702        # Remove counter for potential subsections:
2703        self._section_number.pop()
2704        self.section_level -= 1
2705
2706    def visit_sidebar(self, node):
2707        self.requirements['color'] = PreambleCmds.color
2708        self.fallbacks['sidebar'] = PreambleCmds.sidebar
2709        self.out.append('\n\\DUsidebar{\n')
2710
2711    def depart_sidebar(self, node):
2712        self.out.append('}\n')
2713
2714    attribution_formats = {'dash': (u'—', ''), # EM DASH
2715                           'parentheses': ('(', ')'),
2716                           'parens': ('(', ')'),
2717                           'none': ('', '')}
2718
2719    def visit_attribution(self, node):
2720        prefix, suffix = self.attribution_formats[self.settings.attribution]
2721        self.out.append('\\nopagebreak\n\n\\raggedleft ')
2722        self.out.append(prefix)
2723        self.context.append(suffix)
2724
2725    def depart_attribution(self, node):
2726        self.out.append(self.context.pop() + '\n')
2727
2728    def visit_status(self, node):
2729        self.visit_docinfo_item(node, 'status')
2730
2731    def depart_status(self, node):
2732        self.depart_docinfo_item(node)
2733
2734    def visit_strong(self, node):
2735        self.out.append('\\textbf{')
2736        if node['classes']:
2737            self.visit_inline(node)
2738
2739    def depart_strong(self, node):
2740        if node['classes']:
2741            self.depart_inline(node)
2742        self.out.append('}')
2743
2744    def visit_substitution_definition(self, node):
2745        raise nodes.SkipNode
2746
2747    def visit_substitution_reference(self, node):
2748        self.unimplemented_visit(node)
2749
2750    def visit_subtitle(self, node):
2751        if isinstance(node.parent, nodes.document):
2752            self.push_output_collector(self.subtitle)
2753            self.fallbacks['documentsubtitle'] = PreambleCmds.documentsubtitle
2754            self.subtitle_labels += self.ids_to_labels(node, set_anchor=False)
2755        # section subtitle: "starred" (no number, not in ToC)
2756        elif isinstance(node.parent, nodes.section):
2757            self.out.append(r'\%s*{' %
2758                             self.d_class.section(self.section_level + 1))
2759        else:
2760            self.fallbacks['subtitle'] = PreambleCmds.subtitle
2761            self.out.append('\n\\DUsubtitle[%s]{' % node.parent.tagname)
2762
2763    def depart_subtitle(self, node):
2764        if isinstance(node.parent, nodes.document):
2765            self.pop_output_collector()
2766        else:
2767            self.out.append('}\n')
2768
2769    def visit_system_message(self, node):
2770        self.requirements['color'] = PreambleCmds.color
2771        self.fallbacks['title'] = PreambleCmds.title
2772        node['classes'] = ['system-message']
2773        self.visit_admonition(node)
2774        self.out.append('\\DUtitle[system-message]{system-message}\n')
2775        self.append_hypertargets(node)
2776        try:
2777            line = ', line~%s' % node['line']
2778        except KeyError:
2779            line = ''
2780        self.out.append('\n\n{\color{red}%s/%s} in \\texttt{%s}%s\n' %
2781                         (node['type'], node['level'],
2782                          self.encode(node['source']), line))
2783        if len(node['backrefs']) == 1:
2784            self.out.append('\n\\hyperlink{%s}{' % node['backrefs'][0])
2785            self.context.append('}')
2786        else:
2787            backrefs = ['\\hyperlink{%s}{%d}' % (href, i+1)
2788                        for (i, href) in enumerate(node['backrefs'])]
2789            self.context.append('backrefs: ' + ' '.join(backrefs))
2790
2791    def depart_system_message(self, node):
2792        self.out.append(self.context.pop())
2793        self.depart_admonition()
2794
2795    def visit_table(self, node):
2796        self.requirements['table'] = PreambleCmds.table
2797        if self.active_table.is_open():
2798            self.table_stack.append(self.active_table)
2799            # nesting longtable does not work (e.g. 2007-04-18)
2800            self.active_table = Table(self,'tabular',self.settings.table_style)
2801        # A longtable moves before \paragraph and \subparagraph
2802        # section titles if it immediately follows them:
2803        if (self.active_table._latex_type == 'longtable' and
2804            isinstance(node.parent, nodes.section) and
2805            node.parent.index(node) == 1 and
2806            self.d_class.section(self.section_level).find('paragraph') != -1):
2807            self.out.append('\\leavevmode')
2808        self.active_table.open()
2809        for cls in node['classes']:
2810            self.active_table.set_table_style(cls)
2811        if self.active_table._table_style == 'booktabs':
2812            self.requirements['booktabs'] = r'\usepackage{booktabs}'
2813        self.push_output_collector([])
2814
2815    def depart_table(self, node):
2816        # wrap content in the right environment:
2817        content = self.out
2818        self.pop_output_collector()
2819        self.out.append('\n' + self.active_table.get_opening())
2820        self.out += content
2821        self.out.append(self.active_table.get_closing() + '\n')
2822        self.active_table.close()
2823        if len(self.table_stack)>0:
2824            self.active_table = self.table_stack.pop()
2825        else:
2826            self.active_table.set_table_style(self.settings.table_style)
2827        # Insert hyperlabel after (long)table, as
2828        # other places (beginning, caption) result in LaTeX errors.
2829        if node.get('ids'):
2830            self.out += self.ids_to_labels(node, set_anchor=False) + ['\n']
2831
2832    def visit_target(self, node):
2833        # Skip indirect targets:
2834        if ('refuri' in node       # external hyperlink
2835            or 'refid' in node     # resolved internal link
2836            or 'refname' in node): # unresolved internal link
2837            ## self.out.append('%% %s\n' % node)   # for debugging
2838            return
2839        self.out.append('%\n')
2840        # do we need an anchor (\phantomsection)?
2841        set_anchor = not(isinstance(node.parent, nodes.caption) or
2842                         isinstance(node.parent, nodes.title))
2843        # TODO: where else can/must we omit the \phantomsection?
2844        self.out += self.ids_to_labels(node, set_anchor)
2845
2846    def depart_target(self, node):
2847        pass
2848
2849    def visit_tbody(self, node):
2850        # BUG write preamble if not yet done (colspecs not [])
2851        # for tables without heads.
2852        if not self.active_table.get('preamble written'):
2853            self.visit_thead(None)
2854            self.depart_thead(None)
2855
2856    def depart_tbody(self, node):
2857        pass
2858
2859    def visit_term(self, node):
2860        """definition list term"""
2861        # Commands with optional args inside an optional arg must be put
2862        # in a group, e.g. ``\item[{\hyperref[label]{text}}]``.
2863        self.out.append('\\item[{')
2864
2865    def depart_term(self, node):
2866        # \leavevmode results in a line break if the
2867        # term is followed by an item list.
2868        self.out.append('}] \leavevmode ')
2869
2870    def visit_tgroup(self, node):
2871        #self.out.append(self.starttag(node, 'colgroup'))
2872        #self.context.append('</colgroup>\n')
2873        pass
2874
2875    def depart_tgroup(self, node):
2876        pass
2877
2878    _thead_depth = 0
2879    def thead_depth (self):
2880        return self._thead_depth
2881
2882    def visit_thead(self, node):
2883        self._thead_depth += 1
2884        if 1 == self.thead_depth():
2885            self.out.append('{%s}\n' % self.active_table.get_colspecs())
2886            self.active_table.set('preamble written',1)
2887        self.out.append(self.active_table.get_caption())
2888        self.out.extend(self.active_table.visit_thead())
2889
2890    def depart_thead(self, node):
2891        if node is not None:
2892            self.out.extend(self.active_table.depart_thead())
2893            if self.active_table.need_recurse():
2894                node.walkabout(self)
2895        self._thead_depth -= 1
2896
2897    def visit_title(self, node):
2898        """Append section and other titles."""
2899        # Document title
2900        if node.parent.tagname == 'document':
2901            self.push_output_collector(self.title)
2902            self.context.append('')
2903            self.pdfinfo.append('  pdftitle={%s},' %
2904                                self.encode(node.astext()))
2905        # Topic titles (topic, admonition, sidebar)
2906        elif (isinstance(node.parent, nodes.topic) or
2907              isinstance(node.parent, nodes.admonition) or
2908              isinstance(node.parent, nodes.sidebar)):
2909            self.fallbacks['title'] = PreambleCmds.title
2910            classes = ','.join(node.parent['classes'])
2911            if not classes:
2912                classes = node.tagname
2913            self.out.append('\\DUtitle[%s]{' % classes)
2914            self.context.append('}\n')
2915        # Table caption
2916        elif isinstance(node.parent, nodes.table):
2917            self.push_output_collector(self.active_table.caption)
2918            self.context.append('')
2919        # Section title
2920        else:
2921            if hasattr(PreambleCmds, 'secnumdepth'):
2922                self.requirements['secnumdepth'] = PreambleCmds.secnumdepth
2923            section_name = self.d_class.section(self.section_level)
2924            self.out.append('\n\n')
2925            # System messages heading in red:
2926            if ('system-messages' in node.parent['classes']):
2927                self.requirements['color'] = PreambleCmds.color
2928                section_title = self.encode(node.astext())
2929                self.out.append(r'\%s[%s]{\color{red}' % (
2930                                section_name,section_title))
2931            else:
2932                self.out.append(r'\%s{' % section_name)
2933            if self.section_level > len(self.d_class.sections):
2934                # section level not supported by LaTeX
2935                self.fallbacks['title'] = PreambleCmds.title
2936                # self.out.append('\\phantomsection%\n  ')
2937            # label and ToC entry:
2938            bookmark = ['']
2939            # add sections with unsupported level to toc and pdfbookmarks?
2940            ## if self.section_level > len(self.d_class.sections):
2941            ##     section_title = self.encode(node.astext())
2942            ##     bookmark.append(r'\addcontentsline{toc}{%s}{%s}' %
2943            ##               (section_name, section_title))
2944            bookmark += self.ids_to_labels(node.parent, set_anchor=False)
2945            self.context.append('%\n  '.join(bookmark) + '%\n}\n')
2946
2947            # MAYBE postfix paragraph and subparagraph with \leavemode to
2948            # ensure floats stay in the section and text starts on a new line.
2949
2950    def depart_title(self, node):
2951        self.out.append(self.context.pop())
2952        if (isinstance(node.parent, nodes.table) or
2953            node.parent.tagname == 'document'):
2954            self.pop_output_collector()
2955
2956    def minitoc(self, node, title, depth):
2957        """Generate a local table of contents with LaTeX package minitoc"""
2958        section_name = self.d_class.section(self.section_level)
2959        # name-prefix for current section level
2960        minitoc_names = {'part': 'part', 'chapter': 'mini'}
2961        if 'chapter' not in self.d_class.sections:
2962            minitoc_names['section'] = 'sect'
2963        try:
2964            minitoc_name = minitoc_names[section_name]
2965        except KeyError: # minitoc only supports part- and toplevel
2966            self.warn('Skipping local ToC at %s level.\n' % section_name +
2967                      '  Feature not supported with option "use-latex-toc"',
2968                      base_node=node)
2969            return
2970        # Requirements/Setup
2971        self.requirements['minitoc'] = PreambleCmds.minitoc
2972        self.requirements['minitoc-'+minitoc_name] = (r'\do%stoc' %
2973                                                      minitoc_name)
2974        # depth: (Docutils defaults to unlimited depth)
2975        maxdepth = len(self.d_class.sections)
2976        self.requirements['minitoc-%s-depth' % minitoc_name] = (
2977            r'\mtcsetdepth{%stoc}{%d}' % (minitoc_name, maxdepth))
2978        # Process 'depth' argument (!Docutils stores a relative depth while
2979        # minitoc  expects an absolute depth!):
2980        offset = {'sect': 1, 'mini': 0, 'part': 0}
2981        if 'chapter' in self.d_class.sections:
2982            offset['part'] = -1
2983        if depth:
2984            self.out.append('\\setcounter{%stocdepth}{%d}' %
2985                             (minitoc_name, depth + offset[minitoc_name]))
2986        # title:
2987        self.out.append('\\mtcsettitle{%stoc}{%s}\n' % (minitoc_name, title))
2988        # the toc-generating command:
2989        self.out.append('\\%stoc\n' % minitoc_name)
2990
2991    def visit_topic(self, node):
2992        # Topic nodes can be generic topic, abstract, dedication, or ToC.
2993        # table of contents:
2994        if 'contents' in node['classes']:
2995            self.out.append('\n')
2996            self.out += self.ids_to_labels(node)
2997            # add contents to PDF bookmarks sidebar
2998            if isinstance(node.next_node(), nodes.title):
2999                self.out.append('\n\\pdfbookmark[%d]{%s}{%s}\n' %
3000                                (self.section_level+1,
3001                                 node.next_node().astext(),
3002                                 node.get('ids', ['contents'])[0]
3003                                ))
3004            if self.use_latex_toc:
3005                title = ''
3006                if isinstance(node.next_node(), nodes.title):
3007                    title = self.encode(node.pop(0).astext())
3008                depth = node.get('depth', 0)
3009                if 'local' in node['classes']:
3010                    self.minitoc(node, title, depth)
3011                    self.context.append('')
3012                    return
3013                if depth:
3014                    self.out.append('\\setcounter{tocdepth}{%d}\n' % depth)
3015                if title != 'Contents':
3016                    self.out.append('\\renewcommand{\\contentsname}{%s}\n' %
3017                                    title)
3018                self.out.append('\\tableofcontents\n\n')
3019                self.has_latex_toc = True
3020            else: # Docutils generated contents list
3021                # set flag for visit_bullet_list() and visit_title()
3022                self.is_toc_list = True
3023            self.context.append('')
3024        elif ('abstract' in node['classes'] and
3025              self.settings.use_latex_abstract):
3026            self.push_output_collector(self.abstract)
3027            self.out.append('\\begin{abstract}')
3028            self.context.append('\\end{abstract}\n')
3029            if isinstance(node.next_node(), nodes.title):
3030                node.pop(0) # LaTeX provides its own title
3031        else:
3032            self.fallbacks['topic'] = PreambleCmds.topic
3033            # special topics:
3034            if 'abstract' in node['classes']:
3035                self.fallbacks['abstract'] = PreambleCmds.abstract
3036                self.push_output_collector(self.abstract)
3037            if 'dedication' in node['classes']:
3038                self.fallbacks['dedication'] = PreambleCmds.dedication
3039                self.push_output_collector(self.dedication)
3040            self.out.append('\n\\DUtopic[%s]{\n' % ','.join(node['classes']))
3041            self.context.append('}\n')
3042
3043    def depart_topic(self, node):
3044        self.out.append(self.context.pop())
3045        self.is_toc_list = False
3046        if ('abstract' in node['classes'] or
3047            'dedication' in node['classes']):
3048            self.pop_output_collector()
3049
3050    def visit_rubric(self, node):
3051        self.fallbacks['rubric'] = PreambleCmds.rubric
3052        self.out.append('\n\\DUrubric{')
3053        self.context.append('}\n')
3054
3055    def depart_rubric(self, node):
3056        self.out.append(self.context.pop())
3057
3058    def visit_transition(self, node):
3059        self.fallbacks['transition'] = PreambleCmds.transition
3060        self.out.append('\n\n')
3061        self.out.append('%' + '_' * 75 + '\n')
3062        self.out.append(r'\DUtransition')
3063        self.out.append('\n\n')
3064
3065    def depart_transition(self, node):
3066        pass
3067
3068    def visit_version(self, node):
3069        self.visit_docinfo_item(node, 'version')
3070
3071    def depart_version(self, node):
3072        self.depart_docinfo_item(node)
3073
3074    def unimplemented_visit(self, node):
3075        raise NotImplementedError('visiting unimplemented node type: %s' %
3076                                  node.__class__.__name__)
3077
3078#    def unknown_visit(self, node):
3079#    def default_visit(self, node):
3080
3081# vim: set ts=4 et ai :
3082