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

/src/docutils/writers/latex2e/__init__.py

https://bitbucket.org/kiv/appengine-fb
Python | 2636 lines | 2442 code | 78 blank | 116 comment | 69 complexity | af97396db414197feff1883e24f692ba MD5 | raw file
  1. # $Id: __init__.py 6095 2009-08-22 22:01:26Z milde $
  2. # Author: Engelbert Gruber <grubert@users.sourceforge.net>
  3. # Copyright: This module has been placed in the public domain.
  4. """LaTeX2e document tree Writer."""
  5. __docformat__ = 'reStructuredText'
  6. # code contributions from several people included, thanks to all.
  7. # some named: David Abrahams, Julien Letessier, Lele Gaifax, and others.
  8. #
  9. # convention deactivate code by two # i.e. ##.
  10. import sys
  11. import os
  12. import time
  13. import re
  14. import string
  15. from docutils import frontend, nodes, languages, writers, utils, transforms
  16. from docutils.writers.newlatex2e import unicode_map
  17. class Writer(writers.Writer):
  18. supported = ('latex','latex2e')
  19. """Formats this writer supports."""
  20. settings_spec = (
  21. 'LaTeX-Specific Options',
  22. 'The LaTeX "--output-encoding" default is "latin-1:strict".',
  23. (('Specify documentclass. Default is "article".',
  24. ['--documentclass'],
  25. {'default': 'article', }),
  26. ('Specify document options. Multiple options can be given, '
  27. 'separated by commas. Default is "a4paper".',
  28. ['--documentoptions'],
  29. {'default': 'a4paper', }),
  30. ('Use LaTeX footnotes (currently supports only numbered footnotes). '
  31. 'Default: no, uses figures.',
  32. ['--use-latex-footnotes'],
  33. {'default': 0, 'action': 'store_true',
  34. 'validator': frontend.validate_boolean}),
  35. ('Format for footnote references: one of "superscript" or '
  36. '"brackets". Default is "superscript".',
  37. ['--footnote-references'],
  38. {'choices': ['superscript', 'brackets'], 'default': 'superscript',
  39. 'metavar': '<format>',
  40. 'overrides': 'trim_footnote_reference_space'}),
  41. ('Use LaTeX citations. '
  42. 'Default: no, uses figures which might get mixed with images.',
  43. ['--use-latex-citations'],
  44. {'default': 0, 'action': 'store_true',
  45. 'validator': frontend.validate_boolean}),
  46. ('Format for block quote attributions: one of "dash" (em-dash '
  47. 'prefix), "parentheses"/"parens", or "none". Default is "dash".',
  48. ['--attribution'],
  49. {'choices': ['dash', 'parentheses', 'parens', 'none'],
  50. 'default': 'dash', 'metavar': '<format>'}),
  51. ('Specify LaTeX packages/stylesheets. '
  52. ' A style is referenced with \\usepackage if extension is '
  53. '".sty" or omitted and with \\input else. '
  54. ' Overrides previous --stylesheet and --stylesheet-path settings.',
  55. ['--stylesheet'],
  56. {'default': '', 'metavar': '<file>',
  57. 'overrides': 'stylesheet_path'}),
  58. ('Like --stylesheet, but a relative path is converted from relative '
  59. 'to the current working directory to relative to the output file. ',
  60. ['--stylesheet-path'],
  61. {'metavar': '<file>', 'overrides': 'stylesheet'}),
  62. ('Embed the stylesheet in the output LaTeX file. The stylesheet '
  63. 'file must be accessible during processing. '
  64. ' Default: link to stylesheets',
  65. ['--embed-stylesheet'],
  66. {'default': 0, 'action': 'store_true',
  67. 'validator': frontend.validate_boolean}),
  68. ('Link to the stylesheet(s) in the output file. '
  69. ' This is the default (if not changed in a config file).',
  70. ['--link-stylesheet'],
  71. {'dest': 'embed_stylesheet', 'action': 'store_false'}),
  72. ('Table of contents by Docutils (default) or LaTeX. '
  73. '(Docutils does not know of pagenumbers.) ',
  74. ['--use-latex-toc'],
  75. {'default': 0, 'action': 'store_true',
  76. 'validator': frontend.validate_boolean}),
  77. ('Add parts on top of the section hierarchy.',
  78. ['--use-part-section'],
  79. {'default': 0, 'action': 'store_true',
  80. 'validator': frontend.validate_boolean}),
  81. ('Enclose titlepage in LaTeX titlepage environment.',
  82. ['--use-titlepage-env'],
  83. {'default': 0, 'action': 'store_true',
  84. 'validator': frontend.validate_boolean}),
  85. ('Let LaTeX print author and date, do not show it in docutils '
  86. 'document info.',
  87. ['--use-latex-docinfo'],
  88. {'default': 0, 'action': 'store_true',
  89. 'validator': frontend.validate_boolean}),
  90. ("Use LaTeX abstract environment for the document's abstract. "
  91. 'Per default the abstract is an unnumbered section.',
  92. ['--use-latex-abstract'],
  93. {'default': 0, 'action': 'store_true',
  94. 'validator': frontend.validate_boolean}),
  95. ('Color of any hyperlinks embedded in text '
  96. '(default: "blue", "0" to disable).',
  97. ['--hyperlink-color'], {'default': 'blue'}),
  98. ('Enable compound enumerators for nested enumerated lists '
  99. '(e.g. "1.2.a.ii"). Default: disabled.',
  100. ['--compound-enumerators'],
  101. {'default': None, 'action': 'store_true',
  102. 'validator': frontend.validate_boolean}),
  103. ('Disable compound enumerators for nested enumerated lists. '
  104. 'This is the default.',
  105. ['--no-compound-enumerators'],
  106. {'action': 'store_false', 'dest': 'compound_enumerators'}),
  107. ('Enable section ("." subsection ...) prefixes for compound '
  108. 'enumerators. This has no effect without --compound-enumerators.'
  109. 'Default: disabled.',
  110. ['--section-prefix-for-enumerators'],
  111. {'default': None, 'action': 'store_true',
  112. 'validator': frontend.validate_boolean}),
  113. ('Disable section prefixes for compound enumerators. '
  114. 'This is the default.',
  115. ['--no-section-prefix-for-enumerators'],
  116. {'action': 'store_false', 'dest': 'section_prefix_for_enumerators'}),
  117. ('Set the separator between section number and enumerator '
  118. 'for compound enumerated lists. Default is "-".',
  119. ['--section-enumerator-separator'],
  120. {'default': '-', 'metavar': '<char>'}),
  121. ('When possibile, use the specified environment for literal-blocks. '
  122. 'Default is quoting of whitespace and special chars.',
  123. ['--literal-block-env'],
  124. {'default': ''}),
  125. ('When possibile, use verbatim for literal-blocks. '
  126. 'Compatibility alias for "--literal-block-env=verbatim".',
  127. ['--use-verbatim-when-possible'],
  128. {'default': 0, 'action': 'store_true',
  129. 'validator': frontend.validate_boolean}),
  130. ('Table style. "standard" with horizontal and vertical lines, '
  131. '"booktabs" (LaTeX booktabs style) only horizontal lines '
  132. 'above and below the table and below the header or "nolines". '
  133. 'Default: "standard"',
  134. ['--table-style'],
  135. {'choices': ['standard', 'booktabs','nolines'],
  136. 'default': 'standard',
  137. 'metavar': '<format>'}),
  138. ('LaTeX graphicx package option. '
  139. 'Possible values are "dvips", "pdftex". "auto" includes LaTeX code '
  140. 'to use "pdftex" if processing with pdf(la)tex and dvips otherwise. '
  141. 'Default is no option.',
  142. ['--graphicx-option'],
  143. {'default': ''}),
  144. ('LaTeX font encoding. '
  145. 'Possible values are "", "T1", "OT1", "LGR,T1" or any other '
  146. 'combination of options to the `fontenc` package. '
  147. 'Default is "" which does not load `fontenc`.',
  148. ['--font-encoding'],
  149. {'default': ''}),
  150. ('Per default the latex-writer puts the reference title into '
  151. 'hyperreferences. Specify "ref*" or "pageref*" to get the section '
  152. 'number or the page number.',
  153. ['--reference-label'],
  154. {'default': None, }),
  155. ('Specify style and database for bibtex, for example '
  156. '"--use-bibtex=mystyle,mydb1,mydb2".',
  157. ['--use-bibtex'],
  158. {'default': None, }),
  159. ),)
  160. settings_defaults = {'output_encoding': 'latin-1',
  161. 'sectnum_depth': 0 # updated by SectNum transform
  162. }
  163. relative_path_settings = ('stylesheet_path',)
  164. config_section = 'latex2e writer'
  165. config_section_dependencies = ('writers',)
  166. visitor_attributes = ('head_prefix', 'stylesheet', 'head',
  167. 'body_prefix', 'body', 'body_suffix')
  168. output = None
  169. """Final translated form of `document`."""
  170. def __init__(self):
  171. writers.Writer.__init__(self)
  172. self.translator_class = LaTeXTranslator
  173. # Override parent method to add latex-specific transforms
  174. ## def get_transforms(self):
  175. ## # call the parent class' method
  176. ## transforms = writers.Writer.get_transforms(self)
  177. ## # print transforms
  178. ## # TODO: footnote collection transform
  179. ## # transforms.append(footnotes.collect)
  180. ## return transforms
  181. def translate(self):
  182. visitor = self.translator_class(self.document)
  183. self.document.walkabout(visitor)
  184. self.output = visitor.astext()
  185. # copy parts
  186. for attr in self.visitor_attributes:
  187. setattr(self, attr, getattr(visitor, attr))
  188. def assemble_parts(self):
  189. writers.Writer.assemble_parts(self)
  190. for part in self.visitor_attributes:
  191. if part.startswith('body'):
  192. # body contains inline elements, so join without newline
  193. self.parts[part] = ''.join(getattr(self, part))
  194. else:
  195. self.parts[part] = '\n'.join(getattr(self, part))
  196. class Babel(object):
  197. """Language specifics for LaTeX."""
  198. # country code by a.schlock.
  199. # partly manually converted from iso and babel stuff, dialects and some
  200. _ISO639_TO_BABEL = {
  201. 'no': 'norsk', #XXX added by hand ( forget about nynorsk?)
  202. 'gd': 'scottish', #XXX added by hand
  203. 'hu': 'magyar', #XXX added by hand
  204. 'pt': 'portuguese',#XXX added by hand
  205. 'sl': 'slovenian',
  206. 'af': 'afrikaans',
  207. 'bg': 'bulgarian',
  208. 'br': 'breton',
  209. 'ca': 'catalan',
  210. 'cs': 'czech',
  211. 'cy': 'welsh',
  212. 'da': 'danish',
  213. 'fr': 'french',
  214. # french, francais, canadien, acadian
  215. 'de': 'ngerman', #XXX rather than german
  216. # ngerman, naustrian, german, germanb, austrian
  217. 'el': 'greek',
  218. 'en': 'english',
  219. # english, USenglish, american, UKenglish, british, canadian
  220. 'eo': 'esperanto',
  221. 'es': 'spanish',
  222. 'et': 'estonian',
  223. 'eu': 'basque',
  224. 'fi': 'finnish',
  225. 'ga': 'irish',
  226. 'gl': 'galician',
  227. 'he': 'hebrew',
  228. 'hr': 'croatian',
  229. 'hu': 'hungarian',
  230. 'is': 'icelandic',
  231. 'it': 'italian',
  232. 'la': 'latin',
  233. 'nl': 'dutch',
  234. 'pl': 'polish',
  235. 'pt': 'portuguese',
  236. 'ro': 'romanian',
  237. 'ru': 'russian',
  238. 'sk': 'slovak',
  239. 'sr': 'serbian',
  240. 'sv': 'swedish',
  241. 'tr': 'turkish',
  242. 'uk': 'ukrainian'
  243. }
  244. def __init__(self, lang):
  245. self.language = lang
  246. self.quote_index = 0
  247. self.quotes = ('``', "''")
  248. self.setup = '' # language dependent configuration code
  249. # double quotes are "active" in some languages (e.g. German).
  250. # TODO: use \textquotedbl in OT1 font encoding?
  251. self.literal_double_quote = '"'
  252. if self.language.startswith('de'):
  253. self.quotes = ('{\\glqq}', '\\grqq{}')
  254. self.literal_double_quote = '\\dq{}'
  255. if self.language.startswith('it'):
  256. self.literal_double_quote = r'{\char`\"}'
  257. if self.language.startswith('es'):
  258. # reset tilde ~ to the original binding (nobreakspace):
  259. self.setup = ('\n'
  260. r'\addto\shorthandsspanish{\spanishdeactivate{."~<>}}')
  261. def next_quote(self):
  262. q = self.quotes[self.quote_index]
  263. self.quote_index = (self.quote_index+1) % 2
  264. return q
  265. def quote_quotes(self,text):
  266. t = None
  267. for part in text.split('"'):
  268. if t == None:
  269. t = part
  270. else:
  271. t += self.next_quote() + part
  272. return t
  273. def get_language(self):
  274. lang = self.language.split('_')[0] # filter dialects
  275. return self._ISO639_TO_BABEL.get(lang, "")
  276. # Building blocks for the latex preamble
  277. # --------------------------------------
  278. class SortableDict(dict):
  279. """Dictionary with additional sorting methods"""
  280. def sortedkeys(self):
  281. """Return sorted list of keys"""
  282. keys = self.keys()
  283. keys.sort()
  284. return keys
  285. def sortedvalues(self):
  286. """Return list of values sorted by keys"""
  287. return [self[key] for key in self.sortedkeys()]
  288. # PreambleCmds
  289. # `````````````
  290. # A container for LaTeX code snippets that can be
  291. # inserted into the preamble if required in the document.
  292. #
  293. # .. The package 'makecmds' would enable shorter definitions using the
  294. # \providelength and \provideenvironment commands.
  295. # However, it is pretty non-standard (texlive-latex-extra).
  296. class PreambleCmds(object):
  297. """Building blocks for the latex preamble."""
  298. PreambleCmds.abstract = r"""
  299. % abstract title
  300. \providecommand*{\DUtitleabstract}[1]{\centerline{\textbf{#1}}}"""
  301. PreambleCmds.admonition = r"""
  302. % admonition (specially marked topic)
  303. \providecommand{\DUadmonition}[2][class-arg]{%
  304. % try \DUadmonition#1{#2}:
  305. \ifcsname DUadmonition#1\endcsname%
  306. \csname DUadmonition#1\endcsname{#2}%
  307. \else
  308. \begin{center}
  309. \fbox{\parbox{0.9\textwidth}{#2}}
  310. \end{center}
  311. \fi
  312. }"""
  313. ## PreambleCmds.caption = r"""% configure caption layout
  314. ## \usepackage{caption}
  315. ## \captionsetup{singlelinecheck=false}% no exceptions for one-liners"""
  316. PreambleCmds.color = r"""\usepackage{color}"""
  317. PreambleCmds.docinfo = r"""
  318. % docinfo (width of docinfo table)
  319. \DUprovidelength{\DUdocinfowidth}{0.9\textwidth}"""
  320. # PreambleCmds.docinfo._requirements = 'providelength'
  321. PreambleCmds.embedded_package_wrapper = r"""\makeatletter
  322. %% embedded stylesheet: %s
  323. %s
  324. \makeatother"""
  325. PreambleCmds.dedication = r"""
  326. % dedication topic
  327. \providecommand{\DUtopicdedication}[1]{\begin{center}#1\end{center}}"""
  328. PreambleCmds.error = r"""
  329. % error admonition title
  330. \providecommand*{\DUtitleerror}[1]{\DUtitle{\color{red}#1}}"""
  331. # PreambleCmds.errortitle._requirements = 'color'
  332. PreambleCmds.fieldlist = r"""
  333. % fieldlist environment
  334. \ifthenelse{\isundefined{\DUfieldlist}}{
  335. \newenvironment{DUfieldlist}%
  336. {\quote\description}
  337. {\enddescription\endquote}
  338. }{}"""
  339. PreambleCmds.float_settings = r"""\usepackage{float} % float configuration
  340. \floatplacement{figure}{H} % place figures here definitely"""
  341. PreambleCmds.footnote_floats = r"""% settings for footnotes as floats:
  342. \setlength{\floatsep}{0.5em}
  343. \setlength{\textfloatsep}{\fill}
  344. \addtolength{\textfloatsep}{3em}
  345. \renewcommand{\textfraction}{0.5}
  346. \renewcommand{\topfraction}{0.5}
  347. \renewcommand{\bottomfraction}{0.5}
  348. \setcounter{totalnumber}{50}
  349. \setcounter{topnumber}{50}
  350. \setcounter{bottomnumber}{50}"""
  351. PreambleCmds.graphicx_auto = r"""% Check output format
  352. \ifx\pdftexversion\undefined
  353. \usepackage{graphicx}
  354. \else
  355. \usepackage[pdftex]{graphicx}
  356. \fi'))"""
  357. PreambleCmds.inline = r"""
  358. % inline markup (custom roles)
  359. % \DUrole{#1}{#2} tries \DUrole#1{#2}
  360. \providecommand*{\DUrole}[2]{%
  361. \ifcsname DUrole#1\endcsname%
  362. \csname DUrole#1\endcsname{#2}%
  363. \else% backwards compatibility: try \docutilsrole#1{#2}
  364. \ifcsname docutilsrole#1\endcsname%
  365. \csname docutilsrole#1\endcsname{#2}%
  366. \else%
  367. #2%
  368. \fi%
  369. \fi%
  370. }"""
  371. PreambleCmds.legend = r"""
  372. % legend environment
  373. \ifthenelse{\isundefined{\DUlegend}}{
  374. \newenvironment{DUlegend}{\small}{}
  375. }{}"""
  376. PreambleCmds.lineblock = r"""
  377. % lineblock environment
  378. \DUprovidelength{\DUlineblockindent}{2.5em}
  379. \ifthenelse{\isundefined{\DUlineblock}}{
  380. \newenvironment{DUlineblock}[1]{%
  381. \list{}{\setlength{\partopsep}{\parskip}
  382. \addtolength{\partopsep}{\baselineskip}
  383. \setlength{\topsep}{0pt}
  384. \setlength{\itemsep}{0.15\baselineskip}
  385. \setlength{\parsep}{0pt}
  386. \setlength{\leftmargin}{#1}}
  387. \raggedright
  388. }
  389. {\endlist}
  390. }{}"""
  391. # PreambleCmds.lineblock._requirements = 'providelength'
  392. PreambleCmds.linking = r"""
  393. %% hyperref package (PDF hyperlinks):
  394. \ifthenelse{\isundefined{\hypersetup}}{
  395. \usepackage[colorlinks=%s,linkcolor=%s,urlcolor=%s]{hyperref}
  396. }{}"""
  397. PreambleCmds.minitoc = r"""%% local table of contents
  398. \usepackage{minitoc}"""
  399. PreambleCmds.optionlist = r"""
  400. % optionlist environment
  401. \providecommand*{\DUoptionlistlabel}[1]{\bf #1 \hfill}
  402. \DUprovidelength{\DUoptionlistindent}{3cm}
  403. \ifthenelse{\isundefined{\DUoptionlist}}{
  404. \newenvironment{DUoptionlist}{%
  405. \list{}{\setlength{\labelwidth}{\DUoptionlistindent}
  406. \setlength{\rightmargin}{1cm}
  407. \setlength{\leftmargin}{\rightmargin}
  408. \addtolength{\leftmargin}{\labelwidth}
  409. \addtolength{\leftmargin}{\labelsep}
  410. \renewcommand{\makelabel}{\DUoptionlistlabel}}
  411. }
  412. {\endlist}
  413. }{}"""
  414. # PreambleCmds.optionlist._requirements = 'providelength'
  415. PreambleCmds.providelength = r"""
  416. % providelength (provide a length variable and set default, if it is new)
  417. \providecommand*{\DUprovidelength}[2]{
  418. \ifthenelse{\isundefined{#1}}{\newlength{#1}\setlength{#1}{#2}}{}
  419. }"""
  420. PreambleCmds.rubric = r"""
  421. % rubric (informal heading)
  422. \providecommand*{\DUrubric}[2][class-arg]{%
  423. \subsubsection*{\centering\textit{\textmd{#2}}}}"""
  424. PreambleCmds.sidebar = r"""
  425. % sidebar (text outside the main text flow)
  426. \providecommand{\DUsidebar}[2][class-arg]{%
  427. \begin{center}
  428. \colorbox[gray]{0.80}{\parbox{0.9\textwidth}{#2}}
  429. \end{center}
  430. }"""
  431. PreambleCmds.subtitle = r"""
  432. % subtitle (for topic/sidebar)
  433. \providecommand*{\DUsubtitle}[2][class-arg]{\par\emph{#2}\smallskip}"""
  434. PreambleCmds.table = r"""\usepackage{longtable}
  435. \usepackage{array}
  436. \setlength{\extrarowheight}{2pt}
  437. \newlength{\DUtablewidth} % internal use in tables"""
  438. PreambleCmds.documenttitle = r"""
  439. %%%%%% Title metadata
  440. \title{%s}
  441. \author{%s}
  442. \date{%s}"""
  443. PreambleCmds.titlereference = r"""
  444. % titlereference role
  445. \providecommand*{\DUroletitlereference}[1]{\textsl{#1}}"""
  446. PreambleCmds.title = r"""
  447. % title for topics, admonitions and sidebar
  448. \providecommand*{\DUtitle}[2][class-arg]{%
  449. % call \DUtitle#1{#2} if it exists:
  450. \ifcsname DUtitle#1\endcsname%
  451. \csname DUtitle#1\endcsname{#2}%
  452. \else
  453. \smallskip\noindent\textbf{#2}\smallskip%
  454. \fi
  455. }"""
  456. PreambleCmds.topic = r"""
  457. % topic (quote with heading)
  458. \providecommand{\DUtopic}[2][class-arg]{%
  459. \ifcsname DUtopic#1\endcsname%
  460. \csname DUtopic#1\endcsname{#2}%
  461. \else
  462. \begin{quote}#2\end{quote}
  463. \fi
  464. }"""
  465. PreambleCmds.transition = r"""
  466. % transition (break, fancybreak, anonymous section)
  467. \providecommand*{\DUtransition}[1][class-arg]{%
  468. \hspace*{\fill}\hrulefill\hspace*{\fill}
  469. \vskip 0.5\baselineskip
  470. }"""
  471. class DocumentClass(object):
  472. """Details of a LaTeX document class."""
  473. def __init__(self, document_class, with_part=False):
  474. self.document_class = document_class
  475. self._with_part = with_part
  476. self.sections = ['section', 'subsection', 'subsubsection',
  477. 'paragraph', 'subparagraph']
  478. if self.document_class in ('book', 'memoir', 'report',
  479. 'scrbook', 'scrreprt'):
  480. self.sections.insert(0, 'chapter')
  481. if self._with_part:
  482. self.sections.insert(0, 'part')
  483. def section(self, level):
  484. """Return the LaTeX section name for section `level`.
  485. The name depends on the specific document class.
  486. Level is 1,2,3..., as level 0 is the title.
  487. """
  488. if level <= len(self.sections):
  489. return self.sections[level-1]
  490. else:
  491. return self.sections[-1]
  492. class Table(object):
  493. """Manage a table while traversing.
  494. Maybe change to a mixin defining the visit/departs, but then
  495. class Table internal variables are in the Translator.
  496. Table style might be
  497. :standard: horizontal and vertical lines
  498. :booktabs: only horizontal lines (requires "booktabs" LaTeX package)
  499. :nolines: (or borderless) no lines
  500. """
  501. def __init__(self,translator,latex_type,table_style):
  502. self._translator = translator
  503. self._latex_type = latex_type
  504. self._table_style = table_style
  505. self._open = 0
  506. # miscellaneous attributes
  507. self._attrs = {}
  508. self._col_width = []
  509. self._rowspan = []
  510. self.stubs = []
  511. self._in_thead = 0
  512. def open(self):
  513. self._open = 1
  514. self._col_specs = []
  515. self.caption = None
  516. self._attrs = {}
  517. self._in_head = 0 # maybe context with search
  518. def close(self):
  519. self._open = 0
  520. self._col_specs = None
  521. self.caption = None
  522. self._attrs = {}
  523. self.stubs = []
  524. def is_open(self):
  525. return self._open
  526. def set_table_style(self, table_style):
  527. if not table_style in ('standard','booktabs','borderless','nolines'):
  528. return
  529. self._table_style = table_style
  530. def get_latex_type(self):
  531. return self._latex_type
  532. def set(self,attr,value):
  533. self._attrs[attr] = value
  534. def get(self,attr):
  535. if attr in self._attrs:
  536. return self._attrs[attr]
  537. return None
  538. def get_vertical_bar(self):
  539. if self._table_style == 'standard':
  540. return '|'
  541. return ''
  542. # horizontal lines are drawn below a row,
  543. def get_opening(self):
  544. if self._latex_type == 'longtable':
  545. # otherwise longtable might move before paragraph and subparagraph
  546. prefix = '\\leavevmode\n'
  547. else:
  548. prefix = ''
  549. prefix += '\setlength{\DUtablewidth}{\linewidth}'
  550. return '%s\n\\begin{%s}[c]' % (prefix, self._latex_type)
  551. def get_closing(self):
  552. line = ''
  553. if self._table_style == 'booktabs':
  554. line = '\\bottomrule\n'
  555. elif self._table_style == 'standard':
  556. lines = '\\hline\n'
  557. return '%s\\end{%s}' % (line,self._latex_type)
  558. def visit_colspec(self, node):
  559. self._col_specs.append(node)
  560. # "stubs" list is an attribute of the tgroup element:
  561. self.stubs.append(node.attributes.get('stub'))
  562. def get_colspecs(self):
  563. """Return column specification for longtable.
  564. Assumes reST line length being 80 characters.
  565. Table width is hairy.
  566. === ===
  567. ABC DEF
  568. === ===
  569. usually gets to narrow, therefore we add 1 (fiddlefactor).
  570. """
  571. width = 80
  572. total_width = 0.0
  573. # first see if we get too wide.
  574. for node in self._col_specs:
  575. colwidth = float(node['colwidth']+1) / width
  576. total_width += colwidth
  577. self._col_width = []
  578. self._rowspan = []
  579. # donot make it full linewidth
  580. factor = 0.93
  581. if total_width > 1.0:
  582. factor /= total_width
  583. bar = self.get_vertical_bar()
  584. latex_table_spec = ''
  585. for node in self._col_specs:
  586. colwidth = factor * float(node['colwidth']+1) / width
  587. self._col_width.append(colwidth+0.005)
  588. self._rowspan.append(0)
  589. latex_table_spec += '%sp{%.3f\\DUtablewidth}' % (bar, colwidth+0.005)
  590. return latex_table_spec+bar
  591. def get_column_width(self):
  592. """Return columnwidth for current cell (not multicell)."""
  593. return '%.2f\\DUtablewidth' % self._col_width[self._cell_in_row-1]
  594. def get_caption(self):
  595. if not self.caption:
  596. return ''
  597. if 1 == self._translator.thead_depth():
  598. return r'\caption{%s}\\' '\n' % self.caption
  599. return r'\caption[]{%s (... continued)}\\' '\n' % self.caption
  600. def need_recurse(self):
  601. if self._latex_type == 'longtable':
  602. return 1 == self._translator.thead_depth()
  603. return 0
  604. def visit_thead(self):
  605. self._in_thead += 1
  606. if self._table_style == 'standard':
  607. return ['\\hline\n']
  608. elif self._table_style == 'booktabs':
  609. return ['\\toprule\n']
  610. return []
  611. def depart_thead(self):
  612. a = []
  613. #if self._table_style == 'standard':
  614. # a.append('\\hline\n')
  615. if self._table_style == 'booktabs':
  616. a.append('\\midrule\n')
  617. if self._latex_type == 'longtable':
  618. if 1 == self._translator.thead_depth():
  619. a.append('\\endfirsthead\n')
  620. else:
  621. a.append('\\endhead\n')
  622. a.append(r'\multicolumn{%d}{c}' % len(self._col_specs) +
  623. r'{\hfill ... continued on next page} \\')
  624. a.append('\n\\endfoot\n\\endlastfoot\n')
  625. # for longtable one could add firsthead, foot and lastfoot
  626. self._in_thead -= 1
  627. return a
  628. def visit_row(self):
  629. self._cell_in_row = 0
  630. def depart_row(self):
  631. res = [' \\\\\n']
  632. self._cell_in_row = None # remove cell counter
  633. for i in range(len(self._rowspan)):
  634. if (self._rowspan[i]>0):
  635. self._rowspan[i] -= 1
  636. if self._table_style == 'standard':
  637. rowspans = [i+1 for i in range(len(self._rowspan))
  638. if (self._rowspan[i]<=0)]
  639. if len(rowspans)==len(self._rowspan):
  640. res.append('\\hline\n')
  641. else:
  642. cline = ''
  643. rowspans.reverse()
  644. # TODO merge clines
  645. while 1:
  646. try:
  647. c_start = rowspans.pop()
  648. except:
  649. break
  650. cline += '\\cline{%d-%d}\n' % (c_start,c_start)
  651. res.append(cline)
  652. return res
  653. def set_rowspan(self,cell,value):
  654. try:
  655. self._rowspan[cell] = value
  656. except:
  657. pass
  658. def get_rowspan(self,cell):
  659. try:
  660. return self._rowspan[cell]
  661. except:
  662. return 0
  663. def get_entry_number(self):
  664. return self._cell_in_row
  665. def visit_entry(self):
  666. self._cell_in_row += 1
  667. def is_stub_column(self):
  668. if len(self.stubs) >= self._cell_in_row:
  669. return self.stubs[self._cell_in_row-1]
  670. return False
  671. class LaTeXTranslator(nodes.NodeVisitor):
  672. # When options are given to the documentclass, latex will pass them
  673. # to other packages, as done with babel.
  674. # Dummy settings might be taken from document settings
  675. # Templates
  676. # ---------
  677. latex_head = r'\documentclass[%s]{%s}'
  678. linking = PreambleCmds.linking # if false, hyperref is not loaded
  679. # TODO: use a config value?
  680. generator = '% generated by Docutils <http://docutils.sourceforge.net/>'
  681. # Config setting defaults
  682. # -----------------------
  683. # TODO: use mixins for different implementations.
  684. # list environment for docinfo. else tabularx
  685. ## use_optionlist_for_docinfo = False # TODO: NOT YET IN USE
  686. # Use compound enumerations (1.A.1.)
  687. compound_enumerators = 0
  688. # If using compound enumerations, include section information.
  689. section_prefix_for_enumerators = 0
  690. # This is the character that separates the section ("." subsection ...)
  691. # prefix from the regular list enumerator.
  692. section_enumerator_separator = '-'
  693. # default link color
  694. hyperlink_color = 'blue'
  695. # Auxiliary variables
  696. # -------------------
  697. has_latex_toc = False # is there a toc in the doc? (needed by minitoc)
  698. is_toc_list = False # is the current bullet_list a ToC?
  699. section_level = 0
  700. # Flags to encode():
  701. # inside citation reference labels underscores dont need to be escaped
  702. inside_citation_reference_label = False
  703. verbatim = False # do not encode
  704. insert_non_breaking_blanks = False # replace blanks by "~"
  705. insert_newline = False # add latex newline commands
  706. literal = False # teletype: replace underscores
  707. literal_block = False # inside literal block: no quote mangling
  708. def __init__(self, document):
  709. nodes.NodeVisitor.__init__(self, document)
  710. self.settings = settings = document.settings
  711. self.latex_encoding = self.to_latex_encoding(settings.output_encoding)
  712. self.use_latex_toc = settings.use_latex_toc
  713. self.use_latex_docinfo = settings.use_latex_docinfo
  714. self.use_latex_footnotes = settings.use_latex_footnotes
  715. self._use_latex_citations = settings.use_latex_citations
  716. self.embed_stylesheet = settings.embed_stylesheet
  717. self._reference_label = settings.reference_label
  718. self.hyperlink_color = settings.hyperlink_color
  719. self.compound_enumerators = settings.compound_enumerators
  720. self.font_encoding = settings.font_encoding
  721. self.section_prefix_for_enumerators = (
  722. settings.section_prefix_for_enumerators)
  723. self.section_enumerator_separator = (
  724. settings.section_enumerator_separator.replace('_', '\\_'))
  725. if self.hyperlink_color == '0':
  726. self.hyperlink_color = 'black'
  727. self.colorlinks = 'false'
  728. else:
  729. self.colorlinks = 'true'
  730. # literal blocks:
  731. self.literal_block_env = ''
  732. self.literal_block_options = ''
  733. if settings.literal_block_env != '':
  734. (none,
  735. self.literal_block_env,
  736. self.literal_block_options,
  737. none ) = re.split('(\w+)(.*)', settings.literal_block_env)
  738. elif settings.use_verbatim_when_possible:
  739. self.literal_block_env = 'verbatim'
  740. #
  741. if self.settings.use_bibtex:
  742. self.bibtex = self.settings.use_bibtex.split(',',1)
  743. # TODO avoid errors on not declared citations.
  744. else:
  745. self.bibtex = None
  746. # language: labels, bibliographic_fields, and author_separators.
  747. # to allow writing labes for specific languages.
  748. self.language = languages.get_language(settings.language_code)
  749. self.babel = Babel(settings.language_code)
  750. self.author_separator = self.language.author_separators[0]
  751. self.d_options = [self.settings.documentoptions,
  752. self.babel.get_language()]
  753. self.d_options = ','.join([opt for opt in self.d_options if opt])
  754. self.d_class = DocumentClass(settings.documentclass,
  755. settings.use_part_section)
  756. # object for a table while proccessing.
  757. self.table_stack = []
  758. self.active_table = Table(self,'longtable',settings.table_style)
  759. # Two special dictionaries to collect document-specific requirements.
  760. # Values will be inserted
  761. # * in alphabetic order of their keys,
  762. # * only once, even if required multiple times.
  763. self.requirements = SortableDict() # inserted before style sheet
  764. self.fallbacks = SortableDict() # inserted after style sheet
  765. # Page layout with typearea (if there are relevant document options).
  766. if (settings.documentclass.find('scr') == -1 and
  767. (self.d_options.find('DIV') != -1 or
  768. self.d_options.find('BCOR') != -1)):
  769. self.requirements['typearea'] = r'\usepackage{typearea}'
  770. if self.font_encoding == '':
  771. fontenc_header = r'%\usepackage[OT1]{fontenc}'
  772. else:
  773. fontenc_header = r'\usepackage[%s]{fontenc}' % self.font_encoding
  774. if self.settings.graphicx_option == '':
  775. self.graphicx_package = r'\usepackage{graphicx}'
  776. elif self.settings.graphicx_option.lower() == 'auto':
  777. self.graphicx_package = PreambleCmds.graphicx_auto
  778. else:
  779. self.graphicx_package = (r'\usepackage[%s]{graphicx}' %
  780. self.settings.graphicx_option)
  781. # include all supported sections in toc and PDF bookmarks
  782. # (or use documentclass-default (as currently))?
  783. ## if self.use_latex_toc:
  784. ## self.requirements['tocdepth'] = (r'\setcounter{tocdepth}{%d}' %
  785. ## len(self.d_class.sections))
  786. if not self.settings.sectnum_xform: # section numbering by LaTeX:
  787. sectnum_cmds = []
  788. # sectnum_depth:
  789. # 0 no "sectnum" directive -> no section numbers
  790. # None "sectnum" directive without depth arg -> LaTeX default
  791. # else value of the "depth" argument -> limit to supported
  792. # section levels
  793. if settings.sectnum_depth is not None:
  794. sectnum_depth = min(settings.sectnum_depth,
  795. len(self.d_class.sections))
  796. sectnum_cmds.append(r'\setcounter{secnumdepth}{%d}' %
  797. sectnum_depth)
  798. # start with specified number:
  799. if (hasattr(settings, 'sectnum_start') and
  800. settings.sectnum_start != 1):
  801. sectnum_cmds.append(r'\setcounter{%s}{%d}' %
  802. (self.d_class.sections[0],
  803. settings.sectnum_start-1))
  804. # currently ignored (configure in a stylesheet):
  805. ## settings.sectnum_prefix
  806. ## settings.sectnum_suffix
  807. if sectnum_cmds:
  808. self.requirements['sectnum'] = '\n'.join(sectnum_cmds)
  809. # packages and/or stylesheets
  810. # ---------------------------
  811. self.stylesheet = ['\n%%% User specified packages and stylesheets']
  812. # get list of style sheets from settings
  813. styles = utils.get_stylesheet_list(settings)
  814. # adapt path if --stylesheet_path is used
  815. if settings.stylesheet_path and not(self.embed_stylesheet):
  816. styles = [utils.relative_path(settings._destination, sheet)
  817. for sheet in styles]
  818. for sheet in styles:
  819. (base, ext) = os.path.splitext(sheet)
  820. is_package = ext in ['.sty', '']
  821. if self.embed_stylesheet:
  822. if is_package:
  823. sheet = base + '.sty' # adapt package name
  824. # wrap in \makeatletter, \makeatother
  825. wrapper = PreambleCmds.embedded_package_wrapper
  826. else:
  827. wrapper = '%% embedded stylesheet: %s\n%s'
  828. settings.record_dependencies.add(sheet)
  829. self.stylesheet.append(wrapper % (sheet, open(sheet).read()))
  830. else: # link to style sheet
  831. if is_package:
  832. self.stylesheet.append(r'\usepackage{%s}' % base)
  833. else:
  834. self.stylesheet.append(r'\input{%s}' % sheet)
  835. # Part of LaTeX preamble before style sheet(s)
  836. self.head_prefix = [
  837. self.generator,
  838. self.latex_head % (self.d_options,self.settings.documentclass),
  839. ## '%%% Requirements',
  840. # multi-language support (language is in document settings)
  841. '\\usepackage{babel}%s' % self.babel.setup,
  842. fontenc_header,
  843. r'\usepackage[%s]{inputenc}' % self.latex_encoding,
  844. r'\usepackage{ifthen}',
  845. r'\usepackage{fixltx2e} % fix LaTeX2e shortcomings',
  846. ] # custom requirements will be added later
  847. # Part of LaTeX preamble following the stylesheet
  848. self.head = []
  849. if self.linking: # and maybe check for pdf
  850. self.pdfinfo = []
  851. self.pdfauthor = []
  852. # pdftitle, pdfsubject, pdfauthor, pdfkeywords,
  853. # pdfcreator, pdfproducer
  854. else:
  855. self.pdfinfo = None
  856. # Title metadata: Title, Date, and Author(s)
  857. # separate title, so we can append subtitle.
  858. self.title = []
  859. # if use_latex_docinfo: collects lists of
  860. # author/organization/contact/address lines
  861. self.author_stack = []
  862. # an empty string supresses the "auto-date" feature of \maketitle
  863. self.date = ''
  864. self.body_prefix = ['\n%%% Body\n\\begin{document}\n']
  865. self.body = []
  866. self.body_suffix = ['\n\\end{document}\n']
  867. self.context = []
  868. # Stack of section counters so that we don't have to use_latex_toc.
  869. # This will grow and shrink as processing occurs.
  870. # Initialized for potential first-level sections.
  871. self._section_number = [0]
  872. # The current stack of enumerations so that we can expand
  873. # them into a compound enumeration.
  874. self._enumeration_counters = []
  875. # The maximum number of enumeration counters we've used.
  876. # If we go beyond this number, we need to create a new
  877. # counter; otherwise, just reuse an old one.
  878. self._max_enumeration_counters = 0
  879. self._bibitems = []
  880. self.docinfo = []
  881. self.literal_block_stack = []
  882. # Auxiliary Methods
  883. # -----------------
  884. def to_latex_encoding(self,docutils_encoding):
  885. """Translate docutils encoding name into LaTeX's.
  886. Default method is remove "-" and "_" chars from docutils_encoding.
  887. """
  888. tr = { 'iso-8859-1': 'latin1', # west european
  889. 'iso-8859-2': 'latin2', # east european
  890. 'iso-8859-3': 'latin3', # esperanto, maltese
  891. 'iso-8859-4': 'latin4', # north european, scandinavian, baltic
  892. 'iso-8859-5': 'iso88595', # cyrillic (ISO)
  893. 'iso-8859-9': 'latin5', # turkish
  894. 'iso-8859-15': 'latin9', # latin9, update to latin1.
  895. 'mac_cyrillic': 'maccyr', # cyrillic (on Mac)
  896. 'windows-1251': 'cp1251', # cyrillic (on Windows)
  897. 'koi8-r': 'koi8-r', # cyrillic (Russian)
  898. 'koi8-u': 'koi8-u', # cyrillic (Ukrainian)
  899. 'windows-1250': 'cp1250', #
  900. 'windows-1252': 'cp1252', #
  901. 'us-ascii': 'ascii', # ASCII (US)
  902. # unmatched encodings
  903. #'': 'applemac',
  904. #'': 'ansinew', # windows 3.1 ansi
  905. #'': 'ascii', # ASCII encoding for the range 32--127.
  906. #'': 'cp437', # dos latin us
  907. #'': 'cp850', # dos latin 1
  908. #'': 'cp852', # dos latin 2
  909. #'': 'decmulti',
  910. #'': 'latin10',
  911. #'iso-8859-6': '' # arabic
  912. #'iso-8859-7': '' # greek
  913. #'iso-8859-8': '' # hebrew
  914. #'iso-8859-10': '' # latin6, more complete iso-8859-4
  915. }
  916. encoding = docutils_encoding.lower()
  917. if encoding in tr:
  918. return tr[encoding]
  919. # convert: latin-1, latin_1, utf-8 and similar things
  920. encoding = encoding.replace('_', '').replace('-', '')
  921. # strip the error handler
  922. return encoding.split(':')[0]
  923. def language_label(self, docutil_label):
  924. return self.language.labels[docutil_label]
  925. latex_equivalents = {
  926. u'\u00A0' : '~',
  927. u'\u2013' : '{--}',
  928. u'\u2014' : '{---}',
  929. u'\u2018' : '`',
  930. u'\u2019' : "'",
  931. u'\u201A' : ',',
  932. u'\u201C' : '``',
  933. u'\u201D' : "''",
  934. u'\u201E' : ',,',
  935. u'\u2020' : '\\dag{}',
  936. u'\u2021' : '\\ddag{}',
  937. u'\u2026' : '\\dots{}',
  938. u'\u2122' : '\\texttrademark{}',
  939. u'\u21d4' : '$\\Leftrightarrow$',
  940. # greek alphabet ?
  941. }
  942. def unicode_to_latex(self,text):
  943. # see LaTeX codec
  944. # http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/252124
  945. # Only some special chracters are translated, for documents with many
  946. # utf-8 chars one should use the LaTeX unicode package.
  947. for uchar in self.latex_equivalents.keys():
  948. text = text.replace(uchar,self.latex_equivalents[uchar])
  949. return text
  950. def ensure_math(self, text):
  951. if not hasattr(self, 'ensure_math_re'):
  952. chars = { # lnot,pm,twosuperior,threesuperior,mu,onesuperior,times,div
  953. 'latin1' : '\xac\xb1\xb2\xb3\xb5\xb9\xd7\xf7' ,
  954. # TODO?: also latin5 and latin9
  955. }
  956. self.ensure_math_re = re.compile('([%s])' % chars['latin1'])
  957. text = self.ensure_math_re.sub(r'\\ensuremath{\1}', text)
  958. return text
  959. def encode(self, text):
  960. """Return text with special characters escaped.
  961. Escape the ten special printing characters ``# $ % & ~ _ ^ \ { }``,
  962. square brackets ``[ ]``, double quotes and (in OT1) ``< | >``.
  963. Separate ``-`` (and more in literal text) to prevent input ligatures.
  964. """
  965. if self.verbatim:
  966. return text
  967. # Bootstrapping: backslash and braces are also part of replacements
  968. # 1. backslash (terminated below)
  969. text = text.replace('\\', r'\textbackslash')
  970. # 2. braces
  971. text = text.replace('{', r'\{')
  972. text = text.replace('}', r'\}')
  973. # 3. terminate \textbackslash
  974. text = text.replace('\\textbackslash', '\\textbackslash{}')
  975. # 4. the remaining special printing characters
  976. text = text.replace('#', r'\#')
  977. text = text.replace('$', r'\$')
  978. text = text.replace('%', r'\%')
  979. text = text.replace('&', r'\&')
  980. text = text.replace('~', r'\textasciitilde{}')
  981. if not self.inside_citation_reference_label:
  982. text = text.replace('_', r'\_')
  983. text = text.replace('^', r'\textasciicircum{}')
  984. # Square brackets
  985. #
  986. # \item and all the other commands with optional arguments check
  987. # if the token right after the macro name is an opening bracket.
  988. # In that case the contents between that bracket and the following
  989. # closing bracket on the same grouping level are taken as the
  990. # optional argument. What makes this unintuitive is the fact that
  991. # the square brackets aren't grouping characters themselves, so in
  992. # your last example \item[[...]] the optional argument consists of
  993. # [... (without the closing bracket).
  994. #
  995. # How to escape?
  996. #
  997. # Square brackets are ordinary chars and cannot be escaped with '\',
  998. # so we put them in a group '{[}'. (Alternative: ensure that all
  999. # macros with optional arguments are terminated with {} and text
  1000. # inside any optional argument is put in a group ``[{text}]``).
  1001. # Commands with optional args inside an optional arg must be put
  1002. # in a group, e.g. ``\item[{\hyperref[label]{text}}]``.
  1003. text = text.replace('[', '{[}')
  1004. text = text.replace(']', '{]}')
  1005. # Workarounds for OT1 font-encoding
  1006. if self.font_encoding in ['OT1', '']:
  1007. # * out-of-order characters in cmtt
  1008. if self.literal_block or self.literal:
  1009. # replace underscore by underlined blank, because this has
  1010. # correct width.
  1011. text = text.replace(r'\_', r'\underline{ }')
  1012. # the backslash doesn't work, so we use a mirrored slash.
  1013. # \reflectbox is provided by graphicx:
  1014. self.requirements['graphicx'] = self.graphicx_package
  1015. text = text.replace(r'\textbackslash', r'\reflectbox{/}')
  1016. # * ``< | >`` come out as different chars (except for cmtt):
  1017. else:
  1018. text = text.replace('|', '\\textbar{}')
  1019. text = text.replace('<', '\\textless{}')
  1020. text = text.replace('>', '\\textgreater{}')
  1021. #
  1022. # Separate compound characters, e.g. '--' to '-{}-'. (The
  1023. # actual separation is done later; see below.)
  1024. separate_chars = '-'
  1025. if self.literal_block or self.literal:
  1026. # In monospace-font, we also separate ',,', '``' and "''"
  1027. # and some other characters which can't occur in
  1028. # non-literal text.
  1029. separate_chars += ',`\'"<>'
  1030. # double quotes are 'active' in some languages
  1031. text = text.replace('"', self.babel.literal_double_quote)
  1032. else:
  1033. text = self.babel.quote_quotes(text)
  1034. for char in separate_chars * 2:
  1035. # Do it twice ("* 2") because otherwise we would replace
  1036. # '---' by '-{}--'.
  1037. text = text.replace(char + char, char + '{}' + char)
  1038. if self.insert_newline or self.literal_block:
  1039. # Literal line breaks (in address or literal blocks):
  1040. # for blank lines, insert a protected space, to avoid
  1041. # ! LaTeX Error: There's no line here to end.
  1042. textlines = [line + '~'*(not line.lstrip())
  1043. for line in text.split('\n')]
  1044. text = '\\\\\n'.join(textlines)
  1045. if self.insert_non_breaking_blanks:
  1046. text = text.replace(' ', '~')
  1047. if not self.latex_encoding.startswith('utf8'):
  1048. text = self.unicode_to_latex(text)
  1049. text = self.ensure_math(text)
  1050. if self.latex_encoding == 'utf8':
  1051. # no-break space is not supported by (plain) utf8 input encoding
  1052. text = text.replace(u'\u00A0', '~')
  1053. return text
  1054. def attval(self, text,
  1055. whitespace=re.compile('[\n\r\t\v\f]')):
  1056. """Cleanse, encode, and return attribute value text."""
  1057. return self.encode(whitespace.sub(' ', text))
  1058. def astext(self):
  1059. """Assemble document parts and return as string."""
  1060. head = '\n'.join(self.head_prefix + self.stylesheet + self.head)
  1061. body = ''.join(self.body_prefix + self.body + self.body_suffix)
  1062. return head + '\n' + body
  1063. def is_inline(self, node):
  1064. """Check whether a node represents an inline element"""
  1065. return isinstance(node.parent, nodes.TextElement)
  1066. def append_hypertargets(self, node):
  1067. """Append hypertargets for all ids of `node`"""
  1068. # hypertarget places the anchor at the target's baseline,
  1069. # so we raise it explicitely
  1070. self.body.append('%\n'.join(['\\raisebox{1em}{\\hypertarget{%s}{}}' %
  1071. id for id in node['ids']]))
  1072. def ids_to_labels(self, node, set_anchor=True):
  1073. """Return list of label definitions for all ids of `node`
  1074. If `set_anchor` is True, an anchor is set with \phantomsection.
  1075. """
  1076. labels = ['\\label{%s}' % id for id in node.get('ids', [])]
  1077. if set_anchor and labels:
  1078. labels.insert(0, '\\phantomsection')
  1079. return labels
  1080. # Visitor methods
  1081. # ---------------
  1082. def visit_Text(self, node):
  1083. self.body.append(self.encode(node.astext()))
  1084. def depart_Text(self, node):
  1085. pass
  1086. def visit_address(self, node):
  1087. self.visit_docinfo_item(node, 'address')
  1088. def depart_address(self, node):
  1089. self.depart_docinfo_item(node)
  1090. def visit_admonition(self, node):
  1091. self.fallbacks['admonition'] = PreambleCmds.admonition
  1092. if node.tagname is 'admonition':
  1093. classes = ','.join(node['classes'])
  1094. title = ''
  1095. else: # specific admonitions
  1096. classes = node.tagname.replace('_', '-')
  1097. title = '\\DUtitle[%s]{%s}\n' % (
  1098. classes, self.language.labels.get(classes, classes))
  1099. self.body.append('\n\\DUadmonition[%s]{\n%s' % (classes, title))
  1100. def depart_admonition(self, node=None):
  1101. self.body.append('}\n')
  1102. def visit_attention(self, node):
  1103. self.visit_admonition(node)
  1104. def depart_attention(self, node):
  1105. self.depart_admonition()
  1106. def visit_author(self, node):
  1107. self.visit_docinfo_item(node, 'author')
  1108. def depart_author(self, node):
  1109. self.depart_docinfo_item(node)
  1110. def visit_authors(self, node):
  1111. # not used: visit_author is called anyway for each author.
  1112. pass
  1113. def depart_authors(self, node):
  1114. pass
  1115. def visit_block_quote(self, node):
  1116. self.body.append( '%\n\\begin{quote}\n')
  1117. def depart_block_quote(self, node):
  1118. self.body.append( '\n\\end{quote}\n')
  1119. def visit_bullet_list(self, node):
  1120. if self.is_toc_list:
  1121. self.body.append( '%\n\\begin{list}{}{}\n' )
  1122. else:
  1123. self.body.append( '%\n\\begin{itemize}\n' )
  1124. def depart_bullet_list(self, node):
  1125. if self.is_toc_list:
  1126. self.body.append( '\n\\end{list}\n' )
  1127. else:
  1128. self.body.append( '\n\\end{itemize}\n' )
  1129. def visit_superscript(self, node):
  1130. self.body.append(r'\textsuperscript{')
  1131. if node['classes']:
  1132. self.visit_inline(node)
  1133. def depart_superscript(self, node):
  1134. if node['classes']:
  1135. self.depart_inline(node)
  1136. self.body.append('}')
  1137. def visit_subscript(self, node):
  1138. self.body.append(r'\textsubscript{') # requires `fixltx2e`
  1139. if node['classes']:
  1140. self.visit_inline(node)
  1141. def depart_subscript(self, node):
  1142. if node['classes']:
  1143. self.depart_inline(node)
  1144. self.body.append('}')
  1145. def visit_caption(self, node):
  1146. self.body.append( '\\caption{' )
  1147. def depart_caption(self, node):
  1148. self.body.append('}\n')
  1149. def visit_caution(self, node):
  1150. self.visit_admonition(node)
  1151. def depart_caution(self, node):
  1152. self.depart_admonition()
  1153. def visit_title_reference(self, node):
  1154. self.fallbacks['titlereference'] = PreambleCmds.titlereference
  1155. self.body.append(r'\DUroletitlereference{')
  1156. if node['classes']:
  1157. self.visit_inline(node)
  1158. def depart_title_reference(self, node):
  1159. if node['classes']:
  1160. self.depart_inline(node)
  1161. self.body.append( '}' )
  1162. def visit_citation(self, node):
  1163. # TODO maybe use cite bibitems
  1164. if self._use_latex_citations:
  1165. self.context.append(len(self.body))
  1166. else:
  1167. self.body.append(r'\begin{figure}[b]')
  1168. self.append_hypertargets(node)
  1169. def depart_citation(self, node):
  1170. if self._use_latex_citations:
  1171. size = self.context.pop()
  1172. label = self.body[size]
  1173. text = ''.join(self.body[size+1:])
  1174. del self.body[size:]
  1175. self._bibitems.append([label, text])
  1176. else:
  1177. self.body.append('\\end{figure}\n')
  1178. def visit_citation_reference(self, node):
  1179. if self._use_latex_citations:
  1180. if not self.inside_citation_reference_label:
  1181. self.body.append(r'\cite{')
  1182. self.inside_citation_reference_label = 1
  1183. else:
  1184. assert self.body[-1] in (' ', '\n'),\
  1185. 'unexpected non-whitespace while in reference label'
  1186. del self.body[-1]
  1187. else:
  1188. href = ''
  1189. if 'refid' in node:
  1190. href = node['refid']
  1191. elif 'refname' in node:
  1192. href = self.document.nameids[node['refname']]
  1193. self.body.append('[\\hyperlink{%s}{' % href)
  1194. def depart_citation_reference(self, node):
  1195. if self._use_latex_citations:
  1196. followup_citation = False
  1197. # check for a following citation separated by a space or newline
  1198. next_siblings = node.traverse(descend=0, siblings=1,
  1199. include_self=0)
  1200. if len(next_siblings) > 1:
  1201. next = next_siblings[0]
  1202. if (isinstance(next, nodes.Text) and
  1203. next.astext() in (' ', '\n')):
  1204. if next_siblings[1].__class__ == node.__class__:
  1205. followup_citation = True
  1206. if followup_citation:
  1207. self.body.append(',')
  1208. else:
  1209. self.body.append('}')
  1210. self.inside_citation_reference_label = False
  1211. else:
  1212. self.body.append('}]')
  1213. def visit_classifier(self, node):
  1214. self.body.append( '(\\textbf{' )
  1215. def depart_classifier(self, node):
  1216. self.body.append( '})\n' )
  1217. def visit_colspec(self, node):
  1218. self.active_table.visit_colspec(node)
  1219. def depart_colspec(self, node):
  1220. pass
  1221. def visit_comment(self, node):
  1222. # Precede every line with a comment sign, wrap in newlines
  1223. self.body.append('\n%% %s\n' % node.astext().replace('\n', '\n% '))
  1224. raise nodes.SkipNode
  1225. def depart_comment(self, node):
  1226. pass
  1227. def visit_compound(self, node):
  1228. pass
  1229. def depart_compound(self, node):
  1230. pass
  1231. def visit_contact(self, node):
  1232. self.visit_docinfo_item(node, 'contact')
  1233. def depart_contact(self, node):
  1234. self.depart_docinfo_item(node)
  1235. def visit_container(self, node):
  1236. pass
  1237. def depart_container(self, node):
  1238. pass
  1239. def visit_copyright(self, node):
  1240. self.visit_docinfo_item(node, 'copyright')
  1241. def depart_copyright(self, node):
  1242. self.depart_docinfo_item(node)
  1243. def visit_danger(self, node):
  1244. self.visit_admonition(node)
  1245. def depart_danger(self, node):
  1246. self.depart_admonition()
  1247. def visit_date(self, node):
  1248. self.visit_docinfo_item(node, 'date')
  1249. def depart_date(self, node):
  1250. self.depart_docinfo_item(node)
  1251. def visit_decoration(self, node):
  1252. pass
  1253. def depart_decoration(self, node):
  1254. pass
  1255. def visit_definition(self, node):
  1256. pass
  1257. def depart_definition(self, node):
  1258. self.body.append('\n')
  1259. def visit_definition_list(self, node):
  1260. self.body.append( '%\n\\begin{description}\n' )
  1261. def depart_definition_list(self, node):
  1262. self.body.append( '\\end{description}\n' )
  1263. def visit_definition_list_item(self, node):
  1264. pass
  1265. def depart_definition_list_item(self, node):
  1266. pass
  1267. def visit_description(self, node):
  1268. self.body.append(' ')
  1269. def depart_description(self, node):
  1270. pass
  1271. def visit_docinfo(self, node):
  1272. # tabularx: automatic width of columns, no page breaks allowed.
  1273. self.requirements['tabularx'] = r'\usepackage{tabularx}'
  1274. self.fallbacks['_providelength'] = PreambleCmds.providelength
  1275. self.fallbacks['docinfo'] = PreambleCmds.docinfo
  1276. self.docinfo = ['%' + '_'*75 + '\n',
  1277. '\\begin{center}\n',
  1278. '\\begin{tabularx}{\\DUdocinfowidth}{lX}\n']
  1279. def depart_docinfo(self, node):
  1280. self.docinfo.append('\\end{tabularx}\n')
  1281. self.docinfo.append('\\end{center}\n')
  1282. self.body = self.docinfo + self.body # prepend to self.body
  1283. # clear docinfo, so field names are no longer appended.
  1284. self.docinfo = None
  1285. def visit_docinfo_item(self, node, name):
  1286. if name == 'author':
  1287. if self.pdfinfo is not None:
  1288. self.pdfauthor += [self.attval(node.astext())]
  1289. if self.use_latex_docinfo:
  1290. if name in ('author', 'organization', 'contact', 'address'):
  1291. # We attach these to the last author. If any of them precedes
  1292. # the first author, put them in a separate "author" group
  1293. # (in lack of better semantics).
  1294. if name == 'author' or not self.author_stack:
  1295. self.author_stack.append([])
  1296. if name == 'address': # newlines are meaningful
  1297. self.insert_newline = 1
  1298. text = self.encode(node.astext())
  1299. self.insert_newline = False
  1300. else:
  1301. text = self.attval(node.astext())
  1302. self.author_stack[-1].append(text)
  1303. raise nodes.SkipNode
  1304. elif name == 'date':
  1305. self.date = self.attval(node.astext())
  1306. raise nodes.SkipNode
  1307. self.docinfo.append('\\textbf{%s}: &\n\t' % self.language_label(name))
  1308. if name == 'address':
  1309. self.insert_newline = 1
  1310. self.docinfo.append('{\\raggedright\n')
  1311. self.context.append(' } \\\\\n')
  1312. else:
  1313. self.context.append(' \\\\\n')
  1314. self.context.append(self.docinfo)
  1315. self.context.append(len(self.body))
  1316. def depart_docinfo_item(self, node):
  1317. size = self.context.pop()
  1318. dest = self.context.pop()
  1319. tail = self.context.pop()
  1320. tail = self.body[size:] + [tail]
  1321. del self.body[size:]
  1322. dest.extend(tail)
  1323. # for address we did set insert_newline
  1324. self.insert_newline = False
  1325. def visit_doctest_block(self, node):
  1326. self.body.append( '\\begin{verbatim}' )
  1327. self.verbatim = 1
  1328. def depart_doctest_block(self, node):
  1329. self.body.append( '\\end{verbatim}\n' )
  1330. self.verbatim = False
  1331. def visit_document(self, node):
  1332. if self.settings.use_titlepage_env:
  1333. self.body_prefix.append('\\begin{titlepage}\n')
  1334. # titled document?
  1335. if (self.use_latex_docinfo or len(node) and
  1336. isinstance(node[0], nodes.title)):
  1337. if node['ids']:
  1338. self.title += self.ids_to_labels(node)
  1339. self.body_prefix.append('\\maketitle\n\n')
  1340. def depart_document(self, node):
  1341. # Complete header with information gained from walkabout
  1342. # a) conditional requirements (before style sheet)
  1343. self.head_prefix += self.requirements.sortedvalues()
  1344. # b) coditional fallback definitions (after style sheet)
  1345. self.head.append(
  1346. '\n%%% Fallback definitions for Docutils-specific commands')
  1347. self.head += self.fallbacks.sortedvalues()
  1348. # c) hyperlink setup and PDF metadata
  1349. self.head.append(self.linking % (self.colorlinks,
  1350. self.hyperlink_color,
  1351. self.hyperlink_color))
  1352. if self.pdfinfo is not None and self.pdfauthor:
  1353. self.pdfauthor = self.author_separator.join(self.pdfauthor)
  1354. self.pdfinfo.append(' pdfauthor={%s}' % self.pdfauthor)
  1355. if self.pdfinfo:
  1356. self.head += [r'\hypersetup{'] + self.pdfinfo + ['}']
  1357. # d) Title metadata
  1358. # NOTE: Docutils puts this into docinfo, so normally we do not want
  1359. # LaTeX author/date handling (via \maketitle).
  1360. # To deactivate it, self.astext() adds \title{...}, \author{...},
  1361. # \date{...}, even if the"..." are empty strings.
  1362. if '\\maketitle\n\n' in self.body_prefix:
  1363. title = [PreambleCmds.documenttitle % (
  1364. '%\n '.join(self.title),
  1365. ' \\and\n'.join(['\\\\\n'.join(author_lines)
  1366. for author_lines in self.author_stack]),
  1367. self.date)]
  1368. self.head += title
  1369. # Add bibliography
  1370. # TODO insertion point of bibliography should be configurable.
  1371. if self._use_latex_citations and len(self._bibitems)>0:
  1372. if not self.bibtex:
  1373. widest_label = ''
  1374. for bi in self._bibitems:
  1375. if len(widest_label)<len(bi[0]):
  1376. widest_label = bi[0]
  1377. self.body.append('\n\\begin{thebibliography}{%s}\n' %
  1378. widest_label)
  1379. for bi in self._bibitems:
  1380. # cite_key: underscores must not be escaped
  1381. cite_key = bi[0].replace(r'\_','_')
  1382. self.body.append('\\bibitem[%s]{%s}{%s}\n' %
  1383. (bi[0], cite_key, bi[1]))
  1384. self.body.append('\\end{thebibliography}\n')
  1385. else:
  1386. self.body.append('\n\\bibliographystyle{%s}\n' %
  1387. self.bibtex[0])
  1388. self.body.append('\\bibliography{%s}\n' % self.bibtex[1])
  1389. # Make sure to generate a toc file if needed for local contents:
  1390. if 'minitoc' in self.requirements and not self.has_latex_toc:
  1391. self.body.append('\n\\faketableofcontents % for local ToCs\n')
  1392. def visit_emphasis(self, node):
  1393. self.body.append('\\emph{')
  1394. if node['classes']:
  1395. self.visit_inline(node)
  1396. self.literal_block_stack.append('\\emph{')
  1397. def depart_emphasis(self, node):
  1398. if node['classes']:
  1399. self.depart_inline(node)
  1400. self.body.append('}')
  1401. self.literal_block_stack.pop()
  1402. def visit_entry(self, node):
  1403. self.active_table.visit_entry()
  1404. # cell separation
  1405. # BUG: the following fails, with more than one multirow
  1406. # starting in the second column (or later) see
  1407. # ../../../test/functional/input/data/latex.txt
  1408. if self.active_table.get_entry_number() == 1:
  1409. # if the first row is a multirow, this actually is the second row.
  1410. # this gets hairy if rowspans follow each other.
  1411. if self.active_table.get_rowspan(0):
  1412. count = 0
  1413. while self.active_table.get_rowspan(count):
  1414. count += 1
  1415. self.body.append(' & ')
  1416. self.active_table.visit_entry() # increment cell count
  1417. else:
  1418. self.body.append(' & ')
  1419. # multirow, multicolumn
  1420. # IN WORK BUG TODO HACK continues here
  1421. # multirow in LaTeX simply will enlarge the cell over several rows
  1422. # (the following n if n is positive, the former if negative).
  1423. if 'morerows' in node and 'morecols' in node:
  1424. raise NotImplementedError('Cells that '
  1425. 'span multiple rows *and* columns are not supported, sorry.')
  1426. if 'morerows' in node:
  1427. self.requirements['multirow'] = r'\usepackage{multirow}'
  1428. count = node['morerows'] + 1
  1429. self.active_table.set_rowspan(
  1430. self.active_table.get_entry_number()-1,count)
  1431. self.body.append('\\multirow{%d}{%s}{%%' %
  1432. (count,self.active_table.get_column_width()))
  1433. self.context.append('}')
  1434. elif 'morecols' in node:
  1435. # the vertical bar before column is missing if it is the first
  1436. # column. the one after always.
  1437. if self.active_table.get_entry_number() == 1:
  1438. bar1 = self.active_table.get_vertical_bar()
  1439. else:
  1440. bar1 = ''
  1441. count = node['morecols'] + 1
  1442. self.body.append('\\multicolumn{%d}{%sl%s}{' %
  1443. (count, bar1, self.active_table.get_vertical_bar()))
  1444. self.context.append('}')
  1445. else:
  1446. self.context.append('')
  1447. # header / not header
  1448. if isinstance(node.parent.parent, nodes.thead):
  1449. self.body.append('\\textbf{%')
  1450. self.context.append('}')
  1451. elif self.active_table.is_stub_column():
  1452. self.body.append('\\textbf{')
  1453. self.context.append('}')
  1454. else:
  1455. self.context.append('')
  1456. def depart_entry(self, node):
  1457. self.body.append(self.context.pop()) # header / not header
  1458. self.body.append(self.context.pop()) # multirow/column
  1459. # if following row is spanned from above.
  1460. if self.active_table.get_rowspan(self.active_table.get_entry_number()):
  1461. self.body.append(' & ')
  1462. self.active_table.visit_entry() # increment cell count
  1463. def visit_row(self, node):
  1464. self.active_table.visit_row()
  1465. def depart_row(self, node):
  1466. self.body.extend(self.active_table.depart_row())
  1467. def visit_enumerated_list(self, node):
  1468. # We create our own enumeration list environment.
  1469. # This allows to set the style and starting value
  1470. # and unlimited nesting.
  1471. enum_style = {'arabic':'arabic',
  1472. 'loweralpha':'alph',
  1473. 'upperalpha':'Alph',
  1474. 'lowerroman':'roman',
  1475. 'upperroman':'Roman' }
  1476. enum_suffix = ''
  1477. if 'suffix' in node:
  1478. enum_suffix = node['suffix']
  1479. enum_prefix = ''
  1480. if 'prefix' in node:
  1481. enum_prefix = node['prefix']
  1482. if self.compound_enumerators:
  1483. pref = ''
  1484. if self.section_prefix_for_enumerators and self.section_level:
  1485. for i in range(self.section_level):
  1486. pref += '%d.' % self._section_number[i]
  1487. pref = pref[:-1] + self.section_enumerator_separator
  1488. enum_prefix += pref
  1489. for ctype, cname in self._enumeration_counters:
  1490. enum_prefix += '\\%s{%s}.' % (ctype, cname)
  1491. enum_type = 'arabic'
  1492. if 'enumtype' in node:
  1493. enum_type = node['enumtype']
  1494. if enum_type in enum_style:
  1495. enum_type = enum_style[enum_type]
  1496. counter_name = 'listcnt%d' % len(self._enumeration_counters)
  1497. self._enumeration_counters.append((enum_type, counter_name))
  1498. # If we haven't used this counter name before, then create a
  1499. # new counter; otherwise, reset & reuse the old counter.
  1500. if len(self._enumeration_counters) > self._max_enumeration_counters:
  1501. self._max_enumeration_counters = len(self._enumeration_counters)
  1502. self.body.append('\\newcounter{%s}\n' % counter_name)
  1503. else:
  1504. self.body.append('\\setcounter{%s}{0}\n' % counter_name)
  1505. self.body.append('\\begin{list}{%s\\%s{%s}%s}\n' %
  1506. (enum_prefix,enum_type,counter_name,enum_suffix))
  1507. self.body.append('{\n')
  1508. self.body.append('\\usecounter{%s}\n' % counter_name)
  1509. # set start after usecounter, because it initializes to zero.
  1510. if 'start' in node:
  1511. self.body.append('\\addtocounter{%s}{%d}\n' %
  1512. (counter_name,node['start']-1))
  1513. ## set rightmargin equal to leftmargin
  1514. self.body.append('\\setlength{\\rightmargin}{\\leftmargin}\n')
  1515. self.body.append('}\n')
  1516. def depart_enumerated_list(self, node):
  1517. self.body.append('\\end{list}\n')
  1518. self._enumeration_counters.pop()
  1519. def visit_error(self, node):
  1520. self.fallbacks['error'] = PreambleCmds.error
  1521. self.visit_admonition(node)
  1522. def depart_error(self, node):
  1523. self.depart_admonition()
  1524. def visit_field(self, node):
  1525. # real output is done in siblings: _argument, _body, _name
  1526. pass
  1527. def depart_field(self, node):
  1528. self.body.append('\n')
  1529. ##self.body.append('%[depart_field]\n')
  1530. def visit_field_argument(self, node):
  1531. self.body.append('%[visit_field_argument]\n')
  1532. def depart_field_argument(self, node):
  1533. self.body.append('%[depart_field_argument]\n')
  1534. def visit_field_body(self, node):
  1535. # BUG by attach as text we loose references.
  1536. if self.docinfo:
  1537. self.docinfo.append('%s \\\\\n' % self.encode(node.astext()))
  1538. raise nodes.SkipNode
  1539. # BUG: what happens if not docinfo
  1540. def depart_field_body(self, node):
  1541. pass
  1542. ## self.body.append( '\n' )
  1543. def visit_field_list(self, node):
  1544. self.fallbacks['fieldlist'] = PreambleCmds.fieldlist
  1545. if not self.docinfo:
  1546. self.body.append('%\n\\begin{DUfieldlist}\n')
  1547. def depart_field_list(self, node):
  1548. if not self.docinfo:
  1549. self.body.append('\\end{DUfieldlist}\n')
  1550. def visit_field_name(self, node):
  1551. # BUG this duplicates docinfo_item
  1552. if self.docinfo:
  1553. self.docinfo.append('\\textbf{%s}: &\n\t' %
  1554. self.encode(node.astext()))
  1555. raise nodes.SkipNode
  1556. else:
  1557. # Commands with optional args inside an optional arg must be put
  1558. # in a group, e.g. ``\item[{\hyperref[label]{text}}]``.
  1559. self.body.append('\\item[{')
  1560. def depart_field_name(self, node):
  1561. if not self.docinfo:
  1562. self.body.append(':}]')
  1563. def visit_figure(self, node):
  1564. self.requirements['float_settings'] = PreambleCmds.float_settings
  1565. # ! the 'align' attribute should set "outer alignment" !
  1566. # For "inner alignment" use LaTeX default alignment (similar to HTML)
  1567. ## if ('align' not in node.attributes or
  1568. ## node.attributes['align'] == 'center'):
  1569. ## align = '\n\\centering'
  1570. ## align_end = ''
  1571. ## else:
  1572. ## # TODO non vertical space for other alignments.
  1573. ## align = '\\begin{flush%s}' % node.attributes['align']
  1574. ## align_end = '\\end{flush%s}' % node.attributes['align']
  1575. ## self.body.append( '\\begin{figure}%s\n' % align )
  1576. ## self.context.append( '%s\\end{figure}\n' % align_end )
  1577. self.body.append('\\begin{figure}')
  1578. self.context.append('\\end{figure}\n')
  1579. def depart_figure(self, node):
  1580. self.body.append( self.context.pop() )
  1581. def visit_footer(self, node):
  1582. self.context.append(len(self.body))
  1583. def depart_footer(self, node):
  1584. start = self.context.pop()
  1585. footer = (['\n\\begin{center}\small\n'] +
  1586. self.body[start:] + ['\n\\end{center}\n'])
  1587. self.body_suffix[:0] = footer
  1588. del self.body[start:]
  1589. def visit_footnote(self, node):
  1590. if self.use_latex_footnotes:
  1591. num,text = node.astext().split(None,1)
  1592. num = self.encode(num.strip())
  1593. self.body.append('\\footnotetext['+num+']')
  1594. self.body.append('{')
  1595. else:
  1596. # use key starting with ~ for sorting after small letters
  1597. self.requirements['~footnote_floats'] = (
  1598. PreambleCmds.footnote_floats)
  1599. self.body.append('\\begin{figure}[b]')
  1600. self.append_hypertargets(node)
  1601. if node.get('id') == node.get('name'): # explicite label
  1602. self.body += self.ids_to_labels(node)
  1603. def depart_footnote(self, node):
  1604. if self.use_latex_footnotes:
  1605. self.body.append('}\n')
  1606. else:
  1607. self.body.append('\\end{figure}\n')
  1608. def visit_footnote_reference(self, node):
  1609. if self.use_latex_footnotes:
  1610. self.body.append('\\footnotemark['+self.encode(node.astext())+']')
  1611. raise nodes.SkipNode
  1612. href = ''
  1613. if 'refid' in node:
  1614. href = node['refid']
  1615. elif 'refname' in node:
  1616. href = self.document.nameids[node['refname']]
  1617. format = self.settings.footnote_references
  1618. if format == 'brackets':
  1619. suffix = '['
  1620. self.context.append(']')
  1621. elif format == 'superscript':
  1622. suffix = r'\textsuperscript{'
  1623. self.context.append('}')
  1624. else: # shouldn't happen
  1625. raise AssertionError('Illegal footnote reference format.')
  1626. self.body.append('%s\\hyperlink{%s}{' % (suffix,href))
  1627. def depart_footnote_reference(self, node):
  1628. if self.use_latex_footnotes:
  1629. return
  1630. self.body.append('}%s' % self.context.pop())
  1631. # footnote/citation label
  1632. def label_delim(self, node, bracket, superscript):
  1633. if isinstance(node.parent, nodes.footnote):
  1634. if self.use_latex_footnotes:
  1635. raise nodes.SkipNode
  1636. if self.settings.footnote_references == 'brackets':
  1637. self.body.append(bracket)
  1638. else:
  1639. self.body.append(superscript)
  1640. else:
  1641. assert isinstance(node.parent, nodes.citation)
  1642. if not self._use_latex_citations:
  1643. self.body.append(bracket)
  1644. def visit_label(self, node):
  1645. """footnote or citation label: in brackets or as superscript"""
  1646. self.label_delim(node, '[', '\\textsuperscript{')
  1647. def depart_label(self, node):
  1648. self.label_delim(node, ']', '}')
  1649. # elements generated by the framework e.g. section numbers.
  1650. def visit_generated(self, node):
  1651. pass
  1652. def depart_generated(self, node):
  1653. pass
  1654. def visit_header(self, node):
  1655. self.context.append(len(self.body))
  1656. def depart_header(self, node):
  1657. start = self.context.pop()
  1658. self.body_prefix.append('\n\\verb|begin_header|\n')
  1659. self.body_prefix.extend(self.body[start:])
  1660. self.body_prefix.append('\n\\verb|end_header|\n')
  1661. del self.body[start:]
  1662. def visit_hint(self, node):
  1663. self.visit_admonition(node)
  1664. def depart_hint(self, node):
  1665. self.depart_admonition()
  1666. def to_latex_length(self, length_str):
  1667. """Convert string with rst lenght to LaTeX"""
  1668. match = re.match('(\d*\.?\d*)\s*(\S*)', length_str)
  1669. if not match:
  1670. return length_str
  1671. value, unit = match.groups()[:2]
  1672. # no unit or "DTP" points (called 'bp' in TeX):
  1673. if unit in ('', 'pt'):
  1674. length_str = '%sbp' % value
  1675. # percentage: relate to current line width
  1676. elif unit == '%':
  1677. length_str = '%.3f\\linewidth' % (float(value)/100.0)
  1678. return length_str
  1679. def visit_image(self, node):
  1680. self.requirements['graphicx'] = self.graphicx_package
  1681. attrs = node.attributes
  1682. # Add image URI to dependency list, assuming that it's
  1683. # referring to a local file.
  1684. self.settings.record_dependencies.add(attrs['uri'])
  1685. # alignment defaults:
  1686. if not 'align' in attrs:
  1687. # Set default align of image in a figure to 'center'
  1688. if isinstance(node.parent, nodes.figure):
  1689. attrs['align'] = 'center'
  1690. # query 'align-*' class argument
  1691. for cls in node['classes']:
  1692. if cls.startswith('align-'):
  1693. attrs['align'] = cls.split('-')[1]
  1694. # pre- and postfix (prefix inserted in reverse order)
  1695. pre = []
  1696. post = []
  1697. include_graphics_options = []
  1698. is_inline = self.is_inline(node)
  1699. align_prepost = {
  1700. # key == (<is_inline>, <align>)
  1701. # By default latex aligns the bottom of an image.
  1702. (True, 'bottom'): ('', ''),
  1703. (True, 'middle'): (r'\raisebox{-0.5\height}{', '}'),
  1704. (True, 'top'): (r'\raisebox{-\height}{', '}'),
  1705. (False, 'center'): (r'\noindent\makebox[\textwidth][c]{', '}'),
  1706. (False, 'left'): (r'\noindent{', r'\hfill}'),
  1707. (False, 'right'): (r'\noindent{\hfill', '}'),}
  1708. if 'align' in attrs:
  1709. try:
  1710. pre.append(align_prepost[is_inline, attrs['align']][0])
  1711. post.append(align_prepost[is_inline, attrs['align']][1])
  1712. except KeyError:
  1713. pass # TODO: warn?
  1714. if 'height' in attrs:
  1715. include_graphics_options.append('height=%s' %
  1716. self.to_latex_length(attrs['height']))
  1717. if 'scale' in attrs:
  1718. include_graphics_options.append('scale=%f' %
  1719. (attrs['scale'] / 100.0))
  1720. ## # Could also be done with ``scale`` option to
  1721. ## # ``\includegraphics``; doing it this way for consistency.
  1722. ## pre.append('\\scalebox{%f}{' % (attrs['scale'] / 100.0,))
  1723. ## post.append('}')
  1724. if 'width' in attrs:
  1725. include_graphics_options.append('width=%s' %
  1726. self.to_latex_length(attrs['width']))
  1727. if not is_inline:
  1728. pre.append('\n')
  1729. post.append('\n')
  1730. pre.reverse()
  1731. self.body.extend(pre)
  1732. self.append_hypertargets(node)
  1733. options = ''
  1734. if include_graphics_options:
  1735. options = '[%s]' % (','.join(include_graphics_options))
  1736. self.body.append('\\includegraphics%s{%s}' %
  1737. (options, attrs['uri']))
  1738. self.body.extend(post)
  1739. def depart_image(self, node):
  1740. pass
  1741. def visit_important(self, node):
  1742. self.visit_admonition(node)
  1743. def depart_important(self, node):
  1744. self.depart_admonition()
  1745. def visit_interpreted(self, node):
  1746. # @@@ Incomplete, pending a proper implementation on the
  1747. # Parser/Reader end.
  1748. self.visit_literal(node)
  1749. def depart_interpreted(self, node):
  1750. self.depart_literal(node)
  1751. def visit_legend(self, node):
  1752. self.fallbacks['legend'] = PreambleCmds.legend
  1753. self.body.append('\\begin{DUlegend}')
  1754. def depart_legend(self, node):
  1755. self.body.append('\\end{DUlegend}\n')
  1756. def visit_line(self, node):
  1757. self.body.append('\item[] ')
  1758. def depart_line(self, node):
  1759. self.body.append('\n')
  1760. def visit_line_block(self, node):
  1761. self.fallbacks['_providelength'] = PreambleCmds.providelength
  1762. self.fallbacks['lineblock'] = PreambleCmds.lineblock
  1763. if isinstance(node.parent, nodes.line_block):
  1764. self.body.append('\\item[]\n'
  1765. '\\begin{DUlineblock}{\\DUlineblockindent}\n')
  1766. else:
  1767. self.body.append('\n\\begin{DUlineblock}{0em}\n')
  1768. def depart_line_block(self, node):
  1769. self.body.append('\\end{DUlineblock}\n')
  1770. def visit_list_item(self, node):
  1771. self.body.append('\n\\item ')
  1772. def depart_list_item(self, node):
  1773. pass
  1774. def visit_literal(self, node):
  1775. self.literal = True
  1776. self.body.append('\\texttt{')
  1777. if node['classes']:
  1778. self.visit_inline(node)
  1779. def depart_literal(self, node):
  1780. self.literal = False
  1781. if node['classes']:
  1782. self.depart_inline(node)
  1783. self.body.append('}')
  1784. # Literal blocks are used for '::'-prefixed literal-indented
  1785. # blocks of text, where the inline markup is not recognized,
  1786. # but are also the product of the "parsed-literal" directive,
  1787. # where the markup is respected.
  1788. #
  1789. # In both cases, we want to use a typewriter/monospaced typeface.
  1790. # For "real" literal-blocks, we can use \verbatim, while for all
  1791. # the others we must use \mbox or \alltt.
  1792. #
  1793. # We can distinguish between the two kinds by the number of
  1794. # siblings that compose this node: if it is composed by a
  1795. # single element, it's either
  1796. # * a real one,
  1797. # * a parsed-literal that does not contain any markup, or
  1798. # * a parsed-literal containing just one markup construct.
  1799. def is_plaintext(self, node):
  1800. """Check whether a node can be typeset verbatim"""
  1801. return (len(node) == 1) and isinstance(node[0], nodes.Text)
  1802. def visit_literal_block(self, node):
  1803. """Render a literal block."""
  1804. # environments and packages to typeset literal blocks
  1805. packages = {'listing': r'\usepackage{moreverb}',
  1806. 'lstlisting': r'\usepackage{listings}',
  1807. 'Verbatim': r'\usepackage{fancyvrb}',
  1808. # 'verbatim': '',
  1809. 'verbatimtab': r'\usepackage{moreverb}'}
  1810. if not self.active_table.is_open():
  1811. # no quote inside tables, to avoid vertical space between
  1812. # table border and literal block.
  1813. # BUG: fails if normal text preceeds the literal block.
  1814. self.body.append('%\n\\begin{quote}')
  1815. self.context.append('\n\\end{quote}\n')
  1816. else:
  1817. self.body.append('\n')
  1818. self.context.append('\n')
  1819. if self.literal_block_env != '' and self.is_plaintext(node):
  1820. self.requirements['literal_block'] = packages.get(
  1821. self.literal_block_env, '')
  1822. self.verbatim = 1
  1823. self.body.append('\\begin{%s}%s\n' % (self.literal_block_env,
  1824. self.literal_block_options))
  1825. else:
  1826. self.literal_block = 1
  1827. self.insert_non_breaking_blanks = 1
  1828. self.body.append('{\\ttfamily \\raggedright \\noindent\n')
  1829. def depart_literal_block(self, node):
  1830. if self.verbatim:
  1831. self.body.append('\n\\end{%s}\n' % self.literal_block_env)
  1832. self.verbatim = False
  1833. else:
  1834. self.body.append('\n}')
  1835. self.insert_non_breaking_blanks = False
  1836. self.literal_block = False
  1837. self.body.append(self.context.pop())
  1838. ## def visit_meta(self, node):
  1839. ## self.body.append('[visit_meta]\n')
  1840. # TODO: set keywords for pdf?
  1841. # But:
  1842. # The reStructuredText "meta" directive creates a "pending" node,
  1843. # which contains knowledge that the embedded "meta" node can only
  1844. # be handled by HTML-compatible writers. The "pending" node is
  1845. # resolved by the docutils.transforms.components.Filter transform,
  1846. # which checks that the calling writer supports HTML; if it doesn't,
  1847. # the "pending" node (and enclosed "meta" node) is removed from the
  1848. # document.
  1849. # --- docutils/docs/peps/pep-0258.html#transformer
  1850. ## def depart_meta(self, node):
  1851. ## self.body.append('[depart_meta]\n')
  1852. def visit_note(self, node):
  1853. self.visit_admonition(node)
  1854. def depart_note(self, node):
  1855. self.depart_admonition()
  1856. def visit_option(self, node):
  1857. if self.context[-1]:
  1858. # this is not the first option
  1859. self.body.append(', ')
  1860. def depart_option(self, node):
  1861. # flag tha the first option is done.
  1862. self.context[-1] += 1
  1863. def visit_option_argument(self, node):
  1864. """Append the delimiter betweeen an option and its argument to body."""
  1865. self.body.append(node.get('delimiter', ' '))
  1866. def depart_option_argument(self, node):
  1867. pass
  1868. def visit_option_group(self, node):
  1869. self.body.append('\n\\item[')
  1870. # flag for first option
  1871. self.context.append(0)
  1872. def depart_option_group(self, node):
  1873. self.context.pop() # the flag
  1874. self.body.append('] ')
  1875. def visit_option_list(self, node):
  1876. self.fallbacks['_providelength'] = PreambleCmds.providelength
  1877. self.fallbacks['optionlist'] = PreambleCmds.optionlist
  1878. self.body.append('%\n\\begin{DUoptionlist}\n')
  1879. def depart_option_list(self, node):
  1880. self.body.append('\n\\end{DUoptionlist}\n')
  1881. def visit_option_list_item(self, node):
  1882. pass
  1883. def depart_option_list_item(self, node):
  1884. pass
  1885. def visit_option_string(self, node):
  1886. ##self.body.append(self.starttag(node, 'span', '', CLASS='option'))
  1887. pass
  1888. def depart_option_string(self, node):
  1889. ##self.body.append('</span>')
  1890. pass
  1891. def visit_organization(self, node):
  1892. self.visit_docinfo_item(node, 'organization')
  1893. def depart_organization(self, node):
  1894. self.depart_docinfo_item(node)
  1895. def visit_paragraph(self, node):
  1896. # no newline if the paragraph is first in a list item
  1897. if ((isinstance(node.parent, nodes.list_item) or
  1898. isinstance(node.parent, nodes.description)) and
  1899. node is node.parent[0]):
  1900. return
  1901. index = node.parent.index(node)
  1902. if (isinstance(node.parent, nodes.compound) and
  1903. index > 0 and
  1904. not isinstance(node.parent[index - 1], nodes.paragraph) and
  1905. not isinstance(node.parent[index - 1], nodes.compound)):
  1906. return
  1907. self.body.append('\n')
  1908. if node.get('ids'):
  1909. self.body += self.ids_to_labels(node) + ['\n']
  1910. def depart_paragraph(self, node):
  1911. self.body.append('\n')
  1912. def visit_problematic(self, node):
  1913. self.requirements['color'] = PreambleCmds.color
  1914. self.body.append('%\n')
  1915. self.append_hypertargets(node)
  1916. self.body.append(r'\hyperlink{%s}{\textbf{\color{red}' % node['refid'])
  1917. def depart_problematic(self, node):
  1918. self.body.append('}}')
  1919. def visit_raw(self, node):
  1920. if 'latex' in node.get('format', '').split():
  1921. self.body.append(node.astext())
  1922. raise nodes.SkipNode
  1923. def visit_reference(self, node):
  1924. # BUG: hash_char '#' is troublesome in LaTeX.
  1925. # mbox and other environments do not like the '#'.
  1926. hash_char = '\\#'
  1927. if 'refuri' in node:
  1928. href = node['refuri'].replace('#', hash_char)
  1929. self.body.append('\\href{%s}{' % href.replace('%', '\\%'))
  1930. return
  1931. if 'refid' in node:
  1932. href = node['refid']
  1933. elif 'refname' in node:
  1934. href = self.document.nameids[node['refname']]
  1935. else:
  1936. raise AssertionError('Unknown reference.')
  1937. if not self.is_inline(node):
  1938. self.body.append('\n')
  1939. self.body.append('\\hyperref[%s]{' % href)
  1940. if self._reference_label and 'refuri' not in node:
  1941. self.body.append('\\%s{%s}}' % (self._reference_label,
  1942. href.replace(hash_char, '')))
  1943. raise nodes.SkipNode
  1944. def depart_reference(self, node):
  1945. self.body.append('}')
  1946. if not self.is_inline(node):
  1947. self.body.append('\n')
  1948. def visit_revision(self, node):
  1949. self.visit_docinfo_item(node, 'revision')
  1950. def depart_revision(self, node):
  1951. self.depart_docinfo_item(node)
  1952. def visit_section(self, node):
  1953. self.section_level += 1
  1954. # Initialize counter for potential subsections:
  1955. self._section_number.append(0)
  1956. # Counter for this section's level (initialized by parent section):
  1957. self._section_number[self.section_level - 1] += 1
  1958. def depart_section(self, node):
  1959. # Remove counter for potential subsections:
  1960. self._section_number.pop()
  1961. self.section_level -= 1
  1962. def visit_sidebar(self, node):
  1963. self.requirements['color'] = PreambleCmds.color
  1964. self.fallbacks['sidebar'] = PreambleCmds.sidebar
  1965. self.body.append('\n\\DUsidebar{\n')
  1966. def depart_sidebar(self, node):
  1967. self.body.append('}\n')
  1968. attribution_formats = {'dash': ('---', ''),
  1969. 'parentheses': ('(', ')'),
  1970. 'parens': ('(', ')'),
  1971. 'none': ('', '')}
  1972. def visit_attribution(self, node):
  1973. prefix, suffix = self.attribution_formats[self.settings.attribution]
  1974. self.body.append('\n\\begin{flushright}\n')
  1975. self.body.append(prefix)
  1976. self.context.append(suffix)
  1977. def depart_attribution(self, node):
  1978. self.body.append(self.context.pop() + '\n')
  1979. self.body.append('\\end{flushright}\n')
  1980. def visit_status(self, node):
  1981. self.visit_docinfo_item(node, 'status')
  1982. def depart_status(self, node):
  1983. self.depart_docinfo_item(node)
  1984. def visit_strong(self, node):
  1985. self.body.append('\\textbf{')
  1986. self.literal_block_stack.append('\\textbf{')
  1987. if node['classes']:
  1988. self.visit_inline(node)
  1989. def depart_strong(self, node):
  1990. if node['classes']:
  1991. self.depart_inline(node)
  1992. self.body.append('}')
  1993. self.literal_block_stack.pop()
  1994. def visit_substitution_definition(self, node):
  1995. raise nodes.SkipNode
  1996. def visit_substitution_reference(self, node):
  1997. self.unimplemented_visit(node)
  1998. def visit_subtitle(self, node):
  1999. if isinstance(node.parent, nodes.document):
  2000. self.title.append(r'\\ % subtitle')
  2001. self.title.append(r'\large{%s}' % self.encode(node.astext()))
  2002. self.title += self.ids_to_labels(node, set_anchor=False)
  2003. raise nodes.SkipNode
  2004. # Section subtitle -> always "starred": no number, not in ToC
  2005. elif isinstance(node.parent, nodes.section):
  2006. self.body.append(r'\%s*{' %
  2007. self.d_class.section(self.section_level + 1))
  2008. else:
  2009. self.fallbacks['subtitle'] = PreambleCmds.subtitle
  2010. self.body.append('\n\\DUsubtitle[%s]{' % node.parent.tagname)
  2011. def depart_subtitle(self, node):
  2012. self.body.append('}\n')
  2013. def visit_system_message(self, node):
  2014. self.requirements['color'] = PreambleCmds.color
  2015. self.fallbacks['error'] = PreambleCmds.error
  2016. self.visit_admonition(node) # error or warning
  2017. self.append_hypertargets(node)
  2018. try:
  2019. line = ', line~%s' % node['line']
  2020. except KeyError:
  2021. line = ''
  2022. self.body.append('\n\n{\color{red}%s/%s} in \\texttt{%s}%s\n' %
  2023. (node['type'], node['level'],
  2024. self.encode(node['source']), line))
  2025. if len(node['backrefs']) == 1:
  2026. self.body.append('\n\\hyperlink{%s}{' % node['backrefs'][0])
  2027. self.context.append('}')
  2028. else:
  2029. backrefs = ['\\hyperlink{%s}{%d}' % (href, i+1)
  2030. for (i, href) in enumerate(node['backrefs'])]
  2031. self.context.append('backrefs: ' + ' '.join(backrefs))
  2032. def depart_system_message(self, node):
  2033. self.body.append(self.context.pop())
  2034. self.depart_admonition()
  2035. def visit_table(self, node):
  2036. self.requirements['table'] = PreambleCmds.table
  2037. if self.active_table.is_open():
  2038. self.table_stack.append(self.active_table)
  2039. # nesting longtable does not work (e.g. 2007-04-18)
  2040. self.active_table = Table(self,'tabular',self.settings.table_style)
  2041. self.active_table.open()
  2042. for cls in node['classes']:
  2043. self.active_table.set_table_style(cls)
  2044. if self.active_table._table_style == 'booktabs':
  2045. self.requirements['booktabs'] = r'\usepackage{booktabs}'
  2046. self.body.append('\n' + self.active_table.get_opening())
  2047. def depart_table(self, node):
  2048. self.body.append(self.active_table.get_closing() + '\n')
  2049. self.active_table.close()
  2050. if len(self.table_stack)>0:
  2051. self.active_table = self.table_stack.pop()
  2052. else:
  2053. self.active_table.set_table_style(self.settings.table_style)
  2054. def visit_target(self, node):
  2055. # Skip indirect targets:
  2056. if ('refuri' in node # external hyperlink
  2057. or 'refid' in node # resolved internal link
  2058. or 'refname' in node): # unresolved internal link
  2059. ## self.body.append('%% %s\n' % node) # for debugging
  2060. return
  2061. self.body.append('%\n')
  2062. self.body += self.ids_to_labels(node)
  2063. def depart_target(self, node):
  2064. pass
  2065. def visit_tbody(self, node):
  2066. # BUG write preamble if not yet done (colspecs not [])
  2067. # for tables without heads.
  2068. if not self.active_table.get('preamble written'):
  2069. self.visit_thead(None)
  2070. self.depart_thead(None)
  2071. def depart_tbody(self, node):
  2072. pass
  2073. def visit_term(self, node):
  2074. """definition list term"""
  2075. # Commands with optional args inside an optional arg must be put
  2076. # in a group, e.g. ``\item[{\hyperref[label]{text}}]``.
  2077. self.body.append('\\item[{')
  2078. def depart_term(self, node):
  2079. # \leavevmode results in a line break if the
  2080. # term is followed by an item list.
  2081. self.body.append('}] \leavevmode ')
  2082. def visit_tgroup(self, node):
  2083. #self.body.append(self.starttag(node, 'colgroup'))
  2084. #self.context.append('</colgroup>\n')
  2085. pass
  2086. def depart_tgroup(self, node):
  2087. pass
  2088. _thead_depth = 0
  2089. def thead_depth (self):
  2090. return self._thead_depth
  2091. def visit_thead(self, node):
  2092. self._thead_depth += 1
  2093. if 1 == self.thead_depth():
  2094. self.body.append('{%s}\n' % self.active_table.get_colspecs())
  2095. self.active_table.set('preamble written',1)
  2096. self.body.append(self.active_table.get_caption())
  2097. self.body.extend(self.active_table.visit_thead())
  2098. def depart_thead(self, node):
  2099. if node is not None:
  2100. self.body.extend(self.active_table.depart_thead())
  2101. if self.active_table.need_recurse():
  2102. node.walkabout(self)
  2103. self._thead_depth -= 1
  2104. def visit_tip(self, node):
  2105. self.visit_admonition(node)
  2106. def depart_tip(self, node):
  2107. self.depart_admonition()
  2108. def bookmark(self, node):
  2109. """Return label and pdfbookmark string for titles."""
  2110. result = ['']
  2111. if self.settings.sectnum_xform: # "starred" section cmd
  2112. # add to the toc and pdfbookmarks
  2113. section_name = self.d_class.section(max(self.section_level, 1))
  2114. section_title = self.encode(node.astext())
  2115. result.append(r'\phantomsection')
  2116. result.append(r'\addcontentsline{toc}{%s}{%s}' %
  2117. (section_name, section_title))
  2118. result += self.ids_to_labels(node.parent, set_anchor=False)
  2119. return '%\n '.join(result) + '%\n'
  2120. def visit_title(self, node):
  2121. """Append section and other titles."""
  2122. # Document title
  2123. if node.parent.tagname == 'document':
  2124. self.title.insert(0, self.encode(node.astext()))
  2125. if not self.pdfinfo == None:
  2126. self.pdfinfo.append(' pdftitle={%s},' %
  2127. self.encode(node.astext()) )
  2128. raise nodes.SkipNode
  2129. # Topic titles (topic, admonition, sidebar)
  2130. elif (isinstance(node.parent, nodes.topic) or
  2131. isinstance(node.parent, nodes.admonition) or
  2132. isinstance(node.parent, nodes.sidebar)):
  2133. self.fallbacks['title'] = PreambleCmds.title
  2134. classes = ','.join(node.parent['classes'])
  2135. if not classes:
  2136. classes = node.tagname
  2137. self.body.append('\\DUtitle[%s]{' % classes)
  2138. self.context.append('}\n')
  2139. # Table caption
  2140. elif isinstance(node.parent, nodes.table):
  2141. # caption must be written after column spec
  2142. self.active_table.caption = self.encode(node.astext())
  2143. raise nodes.SkipNode
  2144. # Section title
  2145. else:
  2146. self.body.append('\n\n')
  2147. self.body.append('%' + '_' * 75)
  2148. self.body.append('\n\n')
  2149. #
  2150. section_name = self.d_class.section(self.section_level)
  2151. # number sections?
  2152. if (self.settings.sectnum_xform # numbering by Docutils
  2153. or (self.section_level > len(self.d_class.sections))):
  2154. section_star = '*'
  2155. else: # LaTeX numbered sections
  2156. section_star = ''
  2157. self.body.append(r'\%s%s{' % (section_name, section_star))
  2158. # System messages heading in red:
  2159. if ('system-messages' in node.parent['classes']):
  2160. self.requirements['color'] = PreambleCmds.color
  2161. self.body.append('\color{red}')
  2162. # label and ToC entry:
  2163. self.context.append(self.bookmark(node) + '}\n')
  2164. # MAYBE postfix paragraph and subparagraph with \leavemode to
  2165. # ensure floats stay in the section and text starts on a new line.
  2166. def depart_title(self, node):
  2167. self.body.append(self.context.pop())
  2168. def minitoc(self, title, depth):
  2169. """Generate a local table of contents with LaTeX package minitoc"""
  2170. section_name = self.d_class.section(self.section_level)
  2171. # name-prefix for current section level
  2172. minitoc_names = {'part': 'part', 'chapter': 'mini'}
  2173. if 'chapter' not in self.d_class.sections:
  2174. minitoc_names['section'] = 'sect'
  2175. try:
  2176. minitoc_name = minitoc_names[section_name]
  2177. except KeyError: # minitoc only supports part- and toplevel
  2178. warn = self.document.reporter.warning
  2179. warn('Skipping local ToC at %s level.\n' % section_name +
  2180. ' Feature not supported with option "use-latex-toc"')
  2181. return
  2182. # Requirements/Setup
  2183. self.requirements['minitoc'] = PreambleCmds.minitoc
  2184. self.requirements['minitoc-'+minitoc_name] = (r'\do%stoc' %
  2185. minitoc_name)
  2186. # depth: (Docutils defaults to unlimited depth)
  2187. maxdepth = len(self.d_class.sections)
  2188. self.requirements['minitoc-%s-depth' % minitoc_name] = (
  2189. r'\mtcsetdepth{%stoc}{%d}' % (minitoc_name, maxdepth))
  2190. # Process 'depth' argument (!Docutils stores a relative depth while
  2191. # minitoc expects an absolute depth!):
  2192. offset = {'sect': 1, 'mini': 0, 'part': 0}
  2193. if 'chapter' in self.d_class.sections:
  2194. offset['part'] = -1
  2195. if depth:
  2196. self.body.append('\\setcounter{%stocdepth}{%d}' %
  2197. (minitoc_name, depth + offset[minitoc_name]))
  2198. # title:
  2199. self.body.append('\\mtcsettitle{%stoc}{%s}\n' % (minitoc_name, title))
  2200. # the toc-generating command:
  2201. self.body.append('\\%stoc\n' % minitoc_name)
  2202. def visit_topic(self, node):
  2203. # Topic nodes can be generic topic, abstract, dedication, or ToC
  2204. # table of contents:
  2205. if 'contents' in node['classes']:
  2206. self.body.append('\n')
  2207. self.body += self.ids_to_labels(node)
  2208. if self.settings.use_titlepage_env:
  2209. # TODO: move this to a save place
  2210. # (what if the contents are at the end of the document?)
  2211. self.body.append('\\end{titlepage}\n')
  2212. # add contents to PDF bookmarks sidebar
  2213. if isinstance(node.next_node(), nodes.title):
  2214. self.body.append('\n\\pdfbookmark[%d]{%s}{%s}\n' %
  2215. (self.section_level+1,
  2216. node.next_node().astext(),
  2217. node.get('ids', ['contents'])[0]
  2218. ))
  2219. if self.use_latex_toc:
  2220. title = ''
  2221. if isinstance(node.next_node(), nodes.title):
  2222. title = self.encode(node.pop(0).astext())
  2223. depth = node.get('depth', 0)
  2224. if 'local' in node['classes']:
  2225. self.minitoc(title, depth)
  2226. self.context.append('')
  2227. return
  2228. if depth:
  2229. self.body.append('\\setcounter{tocdepth}{%d}\n' % depth)
  2230. if title != 'Contents':
  2231. self.body.append('\\renewcommand{\\contentsname}{%s}\n'
  2232. % title)
  2233. self.body.append('\\tableofcontents\n\n')
  2234. self.has_latex_toc = True
  2235. else: # Docutils generated contents list
  2236. # set flag for visit_bullet_list() and visit_title()
  2237. self.is_toc_list = True
  2238. self.context.append('')
  2239. elif ('abstract' in node['classes'] and
  2240. self.settings.use_latex_abstract):
  2241. self.body.append('\\begin{abstract}')
  2242. self.context.append('\\end{abstract}\n')
  2243. else:
  2244. self.fallbacks['topic'] = PreambleCmds.topic
  2245. # special topics:
  2246. if 'abstract' in node['classes']:
  2247. self.fallbacks['abstract'] = PreambleCmds.abstract
  2248. if 'dedication' in node['classes']:
  2249. self.fallbacks['dedication'] = PreambleCmds.dedication
  2250. self.body.append('\n\\DUtopic[%s]{\n' % ','.join(node['classes']))
  2251. self.context.append('}\n')
  2252. def depart_topic(self, node):
  2253. self.body.append(self.context.pop())
  2254. self.is_toc_list = False
  2255. def visit_inline(self, node): # <span>, i.e. custom roles
  2256. # insert fallback definition
  2257. self.fallbacks['inline'] = PreambleCmds.inline
  2258. self.body += [r'\DUrole{%s}{' % cls for cls in node['classes']]
  2259. self.context.append('}' * (len(node['classes'])))
  2260. def depart_inline(self, node):
  2261. self.body.append(self.context.pop())
  2262. def visit_rubric(self, node):
  2263. self.fallbacks['rubric'] = PreambleCmds.rubric
  2264. self.body.append('\n\\DUrubric{')
  2265. self.context.append('}\n')
  2266. def depart_rubric(self, node):
  2267. self.body.append(self.context.pop())
  2268. def visit_transition(self, node):
  2269. self.fallbacks['transition'] = PreambleCmds.transition
  2270. self.body.append('\n\n')
  2271. self.body.append('%' + '_' * 75 + '\n')
  2272. self.body.append(r'\DUtransition')
  2273. self.body.append('\n\n')
  2274. def depart_transition(self, node):
  2275. pass
  2276. def visit_version(self, node):
  2277. self.visit_docinfo_item(node, 'version')
  2278. def depart_version(self, node):
  2279. self.depart_docinfo_item(node)
  2280. def visit_warning(self, node):
  2281. self.visit_admonition(node)
  2282. def depart_warning(self, node):
  2283. self.depart_admonition()
  2284. def unimplemented_visit(self, node):
  2285. raise NotImplementedError('visiting unimplemented node type: %s' %
  2286. node.__class__.__name__)
  2287. # def unknown_visit(self, node):
  2288. # def default_visit(self, node):
  2289. # vim: set ts=4 et ai :