PageRenderTime 65ms CodeModel.GetById 14ms RepoModel.GetById 0ms app.codeStats 1ms

/sandbox/rst2context/context/__init__.py

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