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