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