buildframework/helium/external/python/lib/2.5/docutils-0.5-py2.5.egg/docutils/writers/newlatex2e/__init__.py
changeset 179 d8ac696cc51f
parent 1 be27ed110b50
child 180 e02a83d4c571
child 592 3215c239276a
equal deleted inserted replaced
1:be27ed110b50 179:d8ac696cc51f
     1 # $Id: __init__.py 5174 2007-05-31 00:01:52Z wiemann $
       
     2 # Author: Lea Wiemann <LeWiemann@gmail.com>
       
     3 # Copyright: This module has been placed in the public domain.
       
     4 
       
     5 """
       
     6 LaTeX2e document tree Writer.
       
     7 """
       
     8 
       
     9 # Thanks to Engelbert Gruber and various contributors for the original
       
    10 # LaTeX writer, some code and many ideas of which have been used for
       
    11 # this writer.
       
    12 
       
    13 __docformat__ = 'reStructuredText'
       
    14 
       
    15 
       
    16 import re
       
    17 import os.path
       
    18 from types import ListType
       
    19 
       
    20 import docutils
       
    21 from docutils import nodes, writers, utils
       
    22 from docutils.writers.newlatex2e import unicode_map
       
    23 from docutils.transforms import writer_aux
       
    24 
       
    25 
       
    26 class Writer(writers.Writer):
       
    27 
       
    28     supported = ('newlatex', 'newlatex2e')
       
    29     """Formats this writer supports."""
       
    30 
       
    31     default_stylesheet = 'base.tex'
       
    32 
       
    33     default_stylesheet_path = utils.relative_path(
       
    34         os.path.join(os.getcwd(), 'dummy'),
       
    35         os.path.join(os.path.dirname(__file__), default_stylesheet))
       
    36 
       
    37     settings_spec = (
       
    38         'LaTeX-Specific Options',
       
    39         'Note that this LaTeX writer is still EXPERIMENTAL and not '
       
    40         'feature-complete. ',
       
    41         (('Specify a stylesheet file.  The path is used verbatim to include '
       
    42           'the file.  Overrides --stylesheet-path.',
       
    43           ['--stylesheet'],
       
    44           {'default': '', 'metavar': '<file>',
       
    45            'overrides': 'stylesheet_path'}),
       
    46          ('Specify a stylesheet file, relative to the current working '
       
    47           'directory.  Overrides --stylesheet.  Default: "%s"'
       
    48           % default_stylesheet_path,
       
    49           ['--stylesheet-path'],
       
    50           {'metavar': '<file>', 'overrides': 'stylesheet',
       
    51            'default': default_stylesheet_path}),
       
    52          ('Specify a user stylesheet file.  See --stylesheet.',
       
    53           ['--user-stylesheet'],
       
    54           {'default': '', 'metavar': '<file>',
       
    55            'overrides': 'user_stylesheet_path'}),
       
    56          ('Specify a user stylesheet file.  See --stylesheet-path.',
       
    57           ['--user-stylesheet-path'],
       
    58           {'metavar': '<file>', 'overrides': 'user_stylesheet'})
       
    59          ),)
       
    60 
       
    61     settings_defaults = {
       
    62         # Many Unicode characters are provided by unicode_map.py, so
       
    63         # we can default to latin-1.
       
    64         'output_encoding': 'latin-1',
       
    65         'output_encoding_error_handler': 'strict',
       
    66         # Since we are using superscript footnotes, it is necessary to
       
    67         # trim whitespace in front of footnote references.
       
    68         'trim_footnote_reference_space': 1,
       
    69         # Currently unsupported:
       
    70         'docinfo_xform': 0,
       
    71         # During development:
       
    72         'traceback': 1
       
    73         }
       
    74 
       
    75     relative_path_settings = ('stylesheet_path', 'user_stylesheet_path')
       
    76 
       
    77     config_section = 'newlatex2e writer'
       
    78     config_section_dependencies = ('writers',)
       
    79 
       
    80     output = None
       
    81     """Final translated form of `document`."""
       
    82 
       
    83     def get_transforms(self):
       
    84         return writers.Writer.get_transforms(self) + [
       
    85             writer_aux.Compound, writer_aux.Admonitions]
       
    86 
       
    87     def __init__(self):
       
    88         writers.Writer.__init__(self)
       
    89         self.translator_class = LaTeXTranslator
       
    90 
       
    91     def translate(self):
       
    92         visitor = self.translator_class(self.document)
       
    93         self.document.walkabout(visitor)
       
    94         assert not visitor.context, 'context not empty: %s' % visitor.context
       
    95         self.output = visitor.astext()
       
    96         self.head = visitor.header
       
    97         self.body = visitor.body
       
    98 
       
    99 
       
   100 class LaTeXException(Exception):
       
   101     """
       
   102     Exception base class to for exceptions which influence the
       
   103     automatic generation of LaTeX code.
       
   104     """
       
   105 
       
   106 
       
   107 class SkipAttrParentLaTeX(LaTeXException):
       
   108     """
       
   109     Do not generate ``\DECattr`` and ``\renewcommand{\DEVparent}{...}`` for this
       
   110     node.
       
   111 
       
   112     To be raised from ``before_...`` methods.
       
   113     """
       
   114 
       
   115 
       
   116 class SkipParentLaTeX(LaTeXException):
       
   117     """
       
   118     Do not generate ``\renewcommand{\DEVparent}{...}`` for this node.
       
   119 
       
   120     To be raised from ``before_...`` methods.
       
   121     """
       
   122 
       
   123 
       
   124 class LaTeXTranslator(nodes.SparseNodeVisitor):
       
   125 
       
   126     # Country code by a.schlock.
       
   127     # Partly manually converted from iso and babel stuff.
       
   128     iso639_to_babel = {
       
   129         'no': 'norsk',     # added by hand
       
   130         'gd': 'scottish',  # added by hand
       
   131         'sl': 'slovenian',
       
   132         'af': 'afrikaans',
       
   133         'bg': 'bulgarian',
       
   134         'br': 'breton',
       
   135         'ca': 'catalan',
       
   136         'cs': 'czech',
       
   137         'cy': 'welsh',
       
   138         'da': 'danish',
       
   139         'fr': 'french',
       
   140         # french, francais, canadien, acadian
       
   141         'de': 'ngerman',
       
   142         # ngerman, naustrian, german, germanb, austrian
       
   143         'el': 'greek',
       
   144         'en': 'english',
       
   145         # english, USenglish, american, UKenglish, british, canadian
       
   146         'eo': 'esperanto',
       
   147         'es': 'spanish',
       
   148         'et': 'estonian',
       
   149         'eu': 'basque',
       
   150         'fi': 'finnish',
       
   151         'ga': 'irish',
       
   152         'gl': 'galician',
       
   153         'he': 'hebrew',
       
   154         'hr': 'croatian',
       
   155         'hu': 'hungarian',
       
   156         'is': 'icelandic',
       
   157         'it': 'italian',
       
   158         'la': 'latin',
       
   159         'nl': 'dutch',
       
   160         'pl': 'polish',
       
   161         'pt': 'portuguese',
       
   162         'ro': 'romanian',
       
   163         'ru': 'russian',
       
   164         'sk': 'slovak',
       
   165         'sr': 'serbian',
       
   166         'sv': 'swedish',
       
   167         'tr': 'turkish',
       
   168         'uk': 'ukrainian'
       
   169     }
       
   170 
       
   171     # Start with left double quote.
       
   172     left_quote = 1
       
   173 
       
   174     def __init__(self, document):
       
   175         nodes.NodeVisitor.__init__(self, document)
       
   176         self.settings = document.settings
       
   177         self.header = []
       
   178         self.body = []
       
   179         self.context = []
       
   180         self.stylesheet_path = utils.get_stylesheet_reference(
       
   181             self.settings, os.path.join(os.getcwd(), 'dummy'))
       
   182         if self.stylesheet_path:
       
   183             self.settings.record_dependencies.add(self.stylesheet_path)
       
   184         # This ugly hack will be cleaned up when refactoring the
       
   185         # stylesheet mess.
       
   186         self.settings.stylesheet = self.settings.user_stylesheet
       
   187         self.settings.stylesheet_path = self.settings.user_stylesheet_path
       
   188         self.user_stylesheet_path = utils.get_stylesheet_reference(
       
   189             self.settings, os.path.join(os.getcwd(), 'dummy'))
       
   190         if self.user_stylesheet_path:
       
   191             self.settings.record_dependencies.add(self.user_stylesheet_path)
       
   192         self.write_header()
       
   193 
       
   194     def write_header(self):
       
   195         a = self.header.append
       
   196         a('%% Generated by Docutils %s <http://docutils.sourceforge.net>.'
       
   197           % docutils.__version__)
       
   198         a('')
       
   199         a('% Docutils settings:')
       
   200         lang = self.settings.language_code or ''
       
   201         a(r'\providecommand{\DEVlanguageiso}{%s}' % lang)
       
   202         a(r'\providecommand{\DEVlanguagebabel}{%s}' % self.iso639_to_babel.get(
       
   203             lang, self.iso639_to_babel.get(lang.split('_')[0], '')))
       
   204         a('')
       
   205         if self.user_stylesheet_path:
       
   206             a('% User stylesheet:')
       
   207             a(r'\input{%s}' % self.user_stylesheet_path)
       
   208         a('% Docutils stylesheet:')
       
   209         a(r'\input{%s}' % self.stylesheet_path)
       
   210         a('')
       
   211         a('% Default definitions for Docutils nodes:')
       
   212         for node_name in nodes.node_class_names:
       
   213             a(r'\providecommand{\DN%s}[1]{#1}' % node_name.replace('_', ''))
       
   214         a('')
       
   215         a('% Auxiliary definitions:')
       
   216         for attr in (r'\DEVparent \DEVattrlen \DEVtitleastext '
       
   217                      r'\DEVsinglebackref \DEVmultiplebackrefs'
       
   218                      ).split():
       
   219             # Later set using \renewcommand.
       
   220             a(r'\providecommand{%s}{DOCUTILSUNINITIALIZEDVARIABLE}' % attr)
       
   221         for attr in (r'\DEVparagraphindented \DEVhassubtitle').split():
       
   222             # Initialize as boolean variables.
       
   223             a(r'\providecommand{%s}{false}' % attr)
       
   224         a('\n\n')
       
   225 
       
   226     unicode_map = unicode_map.unicode_map # comprehensive Unicode map
       
   227     # Fix problems with unimap.py.
       
   228     unicode_map.update({
       
   229         # We have AE or T1 encoding, so "``" etc. work.  The macros
       
   230         # from unimap.py may *not* work.
       
   231         u'\u201C': '{``}',
       
   232         u'\u201D': "{''}",
       
   233         u'\u201E': '{,,}',
       
   234         })
       
   235 
       
   236     character_map = {
       
   237         '\\': r'{\textbackslash}',
       
   238         '{': r'{\{}',
       
   239         '}': r'{\}}',
       
   240         '$': r'{\$}',
       
   241         '&': r'{\&}',
       
   242         '%': r'{\%}',
       
   243         '#': r'{\#}',
       
   244         '[': r'{[}',
       
   245         ']': r'{]}',
       
   246         '-': r'{-}',
       
   247         '`': r'{`}',
       
   248         "'": r"{'}",
       
   249         ',': r'{,}',
       
   250         '"': r'{"}',
       
   251         '|': r'{\textbar}',
       
   252         '<': r'{\textless}',
       
   253         '>': r'{\textgreater}',
       
   254         '^': r'{\textasciicircum}',
       
   255         '~': r'{\textasciitilde}',
       
   256         '_': r'{\DECtextunderscore}',
       
   257         }
       
   258     character_map.update(unicode_map)
       
   259     #character_map.update(special_map)
       
   260     
       
   261     # `att_map` is for encoding attributes.  According to
       
   262     # <http://www-h.eng.cam.ac.uk/help/tpl/textprocessing/teTeX/latex/latex2e-html/ltx-164.html>,
       
   263     # the following characters are special: # $ % & ~ _ ^ \ { }
       
   264     # These work without special treatment in macro parameters:
       
   265     # $, &, ~, _, ^
       
   266     att_map = {'#': '\\#',
       
   267                '%': '\\%',
       
   268                # We cannot do anything about backslashes.
       
   269                '\\': '',
       
   270                '{': '\\{',
       
   271                '}': '\\}',
       
   272                # The quotation mark may be redefined by babel.
       
   273                '"': '"{}',
       
   274                }
       
   275     att_map.update(unicode_map)
       
   276 
       
   277     def encode(self, text, attval=None):
       
   278         """
       
   279         Encode special characters in ``text`` and return it.
       
   280 
       
   281         If attval is true, preserve as much as possible verbatim (used
       
   282         in attribute value encoding).  If attval is 'width' or
       
   283         'height', `text` is interpreted as a length value.
       
   284         """
       
   285         if attval in ('width', 'height'):
       
   286             match = re.match(r'([0-9.]+)(\S*)$', text)
       
   287             assert match, '%s="%s" must be a length' % (attval, text)
       
   288             value, unit = match.groups()
       
   289             if unit == '%':
       
   290                 value = str(float(value) / 100)
       
   291                 unit = r'\DECrelativeunit'
       
   292             elif unit in ('', 'px'):
       
   293                 # If \DECpixelunit is "pt", this gives the same notion
       
   294                 # of pixels as graphicx.  This is a bit of a hack.
       
   295                 value = str(float(value) * 0.75)
       
   296                 unit = '\DECpixelunit'
       
   297             return '%s%s' % (value, unit)
       
   298         if attval:
       
   299             get = self.att_map.get
       
   300         else:
       
   301             get = self.character_map.get
       
   302         text = ''.join([get(c, c) for c in text])
       
   303         if (self.literal_block or self.inline_literal) and not attval:
       
   304             # NB: We can have inline literals within literal blocks.
       
   305             # Shrink '\r\n'.
       
   306             text = text.replace('\r\n', '\n')
       
   307             # Convert space.  If "{ }~~~~~" is wrapped (at the
       
   308             # brace-enclosed space "{ }"), the following non-breaking
       
   309             # spaces ("~~~~") do *not* wind up at the beginning of the
       
   310             # next line.  Also note that no hyphenation is done if the
       
   311             # breaking space ("{ }") comes *after* the non-breaking
       
   312             # spaces.
       
   313             if self.literal_block:
       
   314                 # Replace newlines with real newlines.
       
   315                 text = text.replace('\n', '\mbox{}\\\\{}')
       
   316                 replace_fn = self.encode_replace_for_literal_block_spaces
       
   317             else:
       
   318                 replace_fn = self.encode_replace_for_inline_literal_spaces
       
   319             text = re.sub(r'\s+', replace_fn, text)
       
   320             # Protect hyphens; if we don't, line breaks will be
       
   321             # possible at the hyphens and even the \textnhtt macro
       
   322             # from the hyphenat package won't change that.
       
   323             text = text.replace('-', r'\mbox{-}')
       
   324             text = text.replace("'", r'{\DECtextliteralsinglequote}')
       
   325             return text
       
   326         else:
       
   327             if not attval:
       
   328                 # Replace space with single protected space.
       
   329                 text = re.sub(r'\s+', '{ }', text)
       
   330                 # Replace double quotes with macro calls.
       
   331                 L = []
       
   332                 for part in text.split(self.character_map['"']):
       
   333                     if L:
       
   334                         # Insert quote.
       
   335                         L.append(self.left_quote and r'{\DECtextleftdblquote}'
       
   336                                  or r'{\DECtextrightdblquote}')
       
   337                         self.left_quote = not self.left_quote
       
   338                     L.append(part)
       
   339                 return ''.join(L)
       
   340             else:
       
   341                 return text
       
   342 
       
   343     def encode_replace_for_literal_block_spaces(self, match):
       
   344         return '~' * len(match.group())
       
   345 
       
   346     def encode_replace_for_inline_literal_spaces(self, match):
       
   347         return '{ }' + '~' * (len(match.group()) - 1)
       
   348 
       
   349     def astext(self):
       
   350         return '\n'.join(self.header) + (''.join(self.body))
       
   351 
       
   352     def append(self, text, newline='%\n'):
       
   353         """
       
   354         Append text, stripping newlines, producing nice LaTeX code.
       
   355         """
       
   356         lines = ['  ' * self.indentation_level + line + newline
       
   357                  for line in text.splitlines(0)]
       
   358         self.body.append(''.join(lines))
       
   359 
       
   360     def visit_Text(self, node):
       
   361         self.append(self.encode(node.astext()))
       
   362 
       
   363     def depart_Text(self, node):
       
   364         pass
       
   365 
       
   366     def is_indented(self, paragraph):
       
   367         """Return true if `paragraph` should be first-line-indented."""
       
   368         assert isinstance(paragraph, nodes.paragraph)
       
   369         siblings = [n for n in paragraph.parent if
       
   370                     self.is_visible(n) and not isinstance(n, nodes.Titular)]
       
   371         index = siblings.index(paragraph)
       
   372         if ('continued' in paragraph['classes'] or
       
   373             index > 0 and isinstance(siblings[index-1], nodes.transition)):
       
   374             return 0
       
   375         # Indent all but the first paragraphs.
       
   376         return index > 0
       
   377 
       
   378     def before_paragraph(self, node):
       
   379         self.append(r'\renewcommand{\DEVparagraphindented}{%s}'
       
   380                     % (self.is_indented(node) and 'true' or 'false'))
       
   381 
       
   382     def before_title(self, node):
       
   383         self.append(r'\renewcommand{\DEVtitleastext}{%s}'
       
   384                     % self.encode(node.astext()))
       
   385         self.append(r'\renewcommand{\DEVhassubtitle}{%s}'
       
   386                     % ((len(node.parent) > 2 and
       
   387                         isinstance(node.parent[1], nodes.subtitle))
       
   388                        and 'true' or 'false'))
       
   389 
       
   390     def before_generated(self, node):
       
   391         if 'sectnum' in node['classes']:
       
   392             node[0] = node[0].strip()
       
   393 
       
   394     literal_block = 0
       
   395 
       
   396     def visit_literal_block(self, node):
       
   397         self.literal_block = 1
       
   398 
       
   399     def depart_literal_block(self, node):
       
   400         self.literal_block = 0
       
   401 
       
   402     visit_doctest_block = visit_literal_block
       
   403     depart_doctest_block = depart_literal_block
       
   404 
       
   405     inline_literal = 0
       
   406 
       
   407     def visit_literal(self, node):
       
   408         self.inline_literal += 1
       
   409 
       
   410     def depart_literal(self, node):
       
   411         self.inline_literal -= 1
       
   412 
       
   413     def _make_encodable(self, text):
       
   414         """
       
   415         Return text (a unicode object) with all unencodable characters
       
   416         replaced with '?'.
       
   417 
       
   418         Thus, the returned unicode string is guaranteed to be encodable.
       
   419         """
       
   420         encoding = self.settings.output_encoding
       
   421         return text.encode(encoding, 'replace').decode(encoding)
       
   422 
       
   423     def visit_comment(self, node):
       
   424         """
       
   425         Insert the comment unchanged into the document, replacing
       
   426         unencodable characters with '?'.
       
   427 
       
   428         (This is done in order not to fail if comments contain unencodable
       
   429         characters, because our default encoding is not UTF-8.)
       
   430         """
       
   431         self.append('\n'.join(['% ' + self._make_encodable(line) for line
       
   432                                in node.astext().splitlines(0)]), newline='\n')
       
   433         raise nodes.SkipChildren
       
   434 
       
   435     def before_topic(self, node):
       
   436         if 'contents' in node['classes']:
       
   437             for bullet_list in list(node.traverse(nodes.bullet_list)):
       
   438                 p = bullet_list.parent
       
   439                 if isinstance(p, nodes.list_item):
       
   440                     p.parent.insert(p.parent.index(p) + 1, bullet_list)
       
   441                     del p[1]
       
   442             for paragraph in node.traverse(nodes.paragraph):
       
   443                 paragraph.attributes.update(paragraph[0].attributes)
       
   444                 paragraph[:] = paragraph[0]
       
   445                 paragraph.parent['tocrefid'] = paragraph['refid']
       
   446             node['contents'] = 1
       
   447         else:
       
   448             node['contents'] = 0
       
   449 
       
   450     bullet_list_level = 0
       
   451 
       
   452     def visit_bullet_list(self, node):
       
   453         self.append(r'\DECsetbullet{\labelitem%s}' %
       
   454                     ['i', 'ii', 'iii', 'iv'][min(self.bullet_list_level, 3)])
       
   455         self.bullet_list_level += 1
       
   456 
       
   457     def depart_bullet_list(self, node):
       
   458         self.bullet_list_level -= 1
       
   459 
       
   460     enum_styles = {'arabic': 'arabic', 'loweralpha': 'alph', 'upperalpha':
       
   461                    'Alph', 'lowerroman': 'roman', 'upperroman': 'Roman'}
       
   462 
       
   463     enum_counter = 0
       
   464 
       
   465     def visit_enumerated_list(self, node):
       
   466         # We create our own enumeration list environment.  This allows
       
   467         # to set the style and starting value and unlimited nesting.
       
   468         # Maybe the actual creation (\DEC) can be moved to the
       
   469         # stylesheet?
       
   470         self.enum_counter += 1
       
   471         enum_prefix = self.encode(node['prefix'])
       
   472         enum_suffix = self.encode(node['suffix'])
       
   473         enum_type = '\\' + self.enum_styles.get(node['enumtype'], r'arabic')
       
   474         start = node.get('start', 1) - 1
       
   475         counter = 'Denumcounter%d' % self.enum_counter
       
   476         self.append(r'\DECmakeenumeratedlist{%s}{%s}{%s}{%s}{%s}{'
       
   477                     % (enum_prefix, enum_type, enum_suffix, counter, start))
       
   478                     # for Emacs: }
       
   479 
       
   480     def depart_enumerated_list(self, node):
       
   481         self.append('}')  # for Emacs: {
       
   482 
       
   483     def before_list_item(self, node):
       
   484         # XXX needs cleanup.
       
   485         if (len(node) and (isinstance(node[-1], nodes.TextElement) or
       
   486                            isinstance(node[-1], nodes.Text)) and
       
   487             node.parent.index(node) == len(node.parent) - 1):
       
   488             node['lastitem'] = 'true'
       
   489 
       
   490     before_line = before_list_item
       
   491 
       
   492     def before_raw(self, node):
       
   493         if 'latex' in node.get('format', '').split():
       
   494             # We're inserting the text in before_raw and thus outside
       
   495             # of \DN... and \DECattr in order to make grouping with
       
   496             # curly brackets work.
       
   497             self.append(node.astext())
       
   498         raise nodes.SkipChildren
       
   499 
       
   500     def process_backlinks(self, node, type):
       
   501         """
       
   502         Add LaTeX handling code for backlinks of footnote or citation
       
   503         node `node`.  `type` is either 'footnote' or 'citation'.
       
   504         """
       
   505         self.append(r'\renewcommand{\DEVsinglebackref}{}')
       
   506         self.append(r'\renewcommand{\DEVmultiplebackrefs}{}')
       
   507         if len(node['backrefs']) > 1:
       
   508             refs = []
       
   509             for i in range(len(node['backrefs'])):
       
   510                 # \DECmulticitationbacklink or \DECmultifootnotebacklink.
       
   511                 refs.append(r'\DECmulti%sbacklink{%s}{%s}'
       
   512                             % (type, node['backrefs'][i], i + 1))
       
   513             self.append(r'\renewcommand{\DEVmultiplebackrefs}{(%s){ }}'
       
   514                         % ', '.join(refs))
       
   515         elif len(node['backrefs']) == 1:
       
   516             self.append(r'\renewcommand{\DEVsinglebackref}{%s}'
       
   517                         % node['backrefs'][0])
       
   518 
       
   519     def visit_footnote(self, node):
       
   520         self.process_backlinks(node, 'footnote')
       
   521 
       
   522     def visit_citation(self, node):
       
   523         self.process_backlinks(node, 'citation')
       
   524 
       
   525     def before_table(self, node):
       
   526         # A table contains exactly one tgroup.  See before_tgroup.
       
   527         pass
       
   528 
       
   529     def before_tgroup(self, node):
       
   530         widths = []
       
   531         total_width = 0
       
   532         for i in range(int(node['cols'])):
       
   533             assert isinstance(node[i], nodes.colspec)
       
   534             widths.append(int(node[i]['colwidth']) + 1)
       
   535             total_width += widths[-1]
       
   536         del node[:len(widths)]
       
   537         tablespec = '|'
       
   538         for w in widths:
       
   539             # 0.93 is probably wrong in many cases.  XXX Find a
       
   540             # solution which works *always*.
       
   541             tablespec += r'p{%s\textwidth}|' % (0.93 * w /
       
   542                                                 max(total_width, 60))
       
   543         self.append(r'\DECmaketable{%s}{' % tablespec)
       
   544         self.context.append('}')
       
   545         raise SkipAttrParentLaTeX
       
   546 
       
   547     def depart_tgroup(self, node):
       
   548         self.append(self.context.pop())
       
   549 
       
   550     def before_row(self, node):
       
   551         raise SkipAttrParentLaTeX
       
   552 
       
   553     def before_thead(self, node):
       
   554         raise SkipAttrParentLaTeX
       
   555 
       
   556     def before_tbody(self, node):
       
   557         raise SkipAttrParentLaTeX
       
   558 
       
   559     def is_simply_entry(self, node):
       
   560         return (len(node) == 1 and isinstance(node[0], nodes.paragraph) or
       
   561                 len(node) == 0)
       
   562 
       
   563     def before_entry(self, node):
       
   564         is_leftmost = 0
       
   565         if node.hasattr('morerows'):
       
   566             self.document.reporter.severe('Rowspans are not supported.')
       
   567             # Todo: Add empty cells below rowspanning cell and issue
       
   568             # warning instead of severe.
       
   569         if node.hasattr('morecols'):
       
   570             # The author got a headache trying to implement
       
   571             # multicolumn support.
       
   572             if not self.is_simply_entry(node):
       
   573                 self.document.reporter.severe(
       
   574                     'Colspanning table cells may only contain one paragraph.')
       
   575                 # Todo: Same as above.
       
   576             # The number of columns this entry spans (as a string).
       
   577             colspan = int(node['morecols']) + 1
       
   578             del node['morecols']
       
   579         else:
       
   580             colspan = 1
       
   581         # Macro to call -- \DECcolspan or \DECcolspanleft.
       
   582         macro_name = r'\DECcolspan'
       
   583         if node.parent.index(node) == 0:
       
   584             # Leftmost column.
       
   585             macro_name += 'left'
       
   586             is_leftmost = 1
       
   587         if colspan > 1:
       
   588             self.append('%s{%s}{' % (macro_name, colspan))
       
   589             self.context.append('}')
       
   590         else:
       
   591             # Do not add a multicolumn with colspan 1 beacuse we need
       
   592             # at least one non-multicolumn cell per column to get the
       
   593             # desired column widths, and we can only do colspans with
       
   594             # cells consisting of only one paragraph.
       
   595             if not is_leftmost:
       
   596                 self.append(r'\DECsubsequententry{')
       
   597                 self.context.append('}')
       
   598             else:
       
   599                 self.context.append('')
       
   600         if isinstance(node.parent.parent, nodes.thead):
       
   601             node['tableheaderentry'] = 'true'
       
   602 
       
   603         # Don't add \renewcommand{\DEVparent}{...} because there must
       
   604         # not be any non-expandable commands in front of \multicolumn.
       
   605         raise SkipParentLaTeX
       
   606 
       
   607     def depart_entry(self, node):
       
   608         self.append(self.context.pop())
       
   609 
       
   610     def before_substitution_definition(self, node):
       
   611         raise nodes.SkipNode
       
   612 
       
   613     indentation_level = 0
       
   614 
       
   615     def node_name(self, node):
       
   616         return node.__class__.__name__.replace('_', '')
       
   617 
       
   618     # Attribute propagation order.
       
   619     attribute_order = ['align', 'classes', 'ids']
       
   620 
       
   621     def attribute_cmp(self, a1, a2):
       
   622         """
       
   623         Compare attribute names `a1` and `a2`.  Used in
       
   624         propagate_attributes to determine propagation order.
       
   625 
       
   626         See built-in function `cmp` for return value.
       
   627         """
       
   628         if a1 in self.attribute_order and a2 in self.attribute_order:
       
   629             return cmp(self.attribute_order.index(a1),
       
   630                        self.attribute_order.index(a2))
       
   631         if (a1 in self.attribute_order) != (a2 in self.attribute_order):
       
   632             # Attributes not in self.attribute_order come last.
       
   633             return a1 in self.attribute_order and -1 or 1
       
   634         else:
       
   635             return cmp(a1, a2)
       
   636 
       
   637     def propagate_attributes(self, node):
       
   638         # Propagate attributes using \DECattr macros.
       
   639         node_name = self.node_name(node)
       
   640         attlist = []
       
   641         if isinstance(node, nodes.Element):
       
   642             attlist = node.attlist()
       
   643         attlist.sort(lambda pair1, pair2: self.attribute_cmp(pair1[0],
       
   644                                                              pair2[0]))
       
   645         # `numatts` may be greater than len(attlist) due to list
       
   646         # attributes.
       
   647         numatts = 0
       
   648         pass_contents = self.pass_contents(node)
       
   649         for key, value in attlist:
       
   650             if isinstance(value, ListType):
       
   651                 self.append(r'\renewcommand{\DEVattrlen}{%s}' % len(value))
       
   652                 for i in range(len(value)):
       
   653                     self.append(r'\DECattr{%s}{%s}{%s}{%s}{' %
       
   654                                 (i+1, key, self.encode(value[i], attval=key),
       
   655                                  node_name))
       
   656                     if not pass_contents:
       
   657                         self.append('}')
       
   658                 numatts += len(value)
       
   659             else:
       
   660                 self.append(r'\DECattr{}{%s}{%s}{%s}{' %
       
   661                             (key, self.encode(unicode(value), attval=key),
       
   662                              node_name))
       
   663                 if not pass_contents:
       
   664                     self.append('}')
       
   665                 numatts += 1
       
   666         if pass_contents:
       
   667             self.context.append('}' * numatts)  # for Emacs: {
       
   668         else:
       
   669             self.context.append('')
       
   670 
       
   671     def visit_docinfo(self, node):
       
   672         raise NotImplementedError('Docinfo not yet implemented.')
       
   673 
       
   674     def visit_document(self, node):
       
   675         document = node
       
   676         # Move IDs into TextElements.  This won't work for images.
       
   677         # Need to review this.
       
   678         for node in document.traverse(nodes.Element):
       
   679             if node.has_key('ids') and not isinstance(node,
       
   680                                                       nodes.TextElement):
       
   681                 next_text_element = node.next_node(nodes.TextElement)
       
   682                 if next_text_element:
       
   683                     next_text_element['ids'].extend(node['ids'])
       
   684                     node['ids'] = []
       
   685 
       
   686     def pass_contents(self, node):
       
   687         r"""
       
   688         Return True if the node contents should be passed in
       
   689         \DN<nodename>{<contents>} and \DECattr{}{}{}{}{<contents>}.
       
   690         Return False if the node contents should be passed in
       
   691         \DECvisit<nodename> <contents> \DECdepart<nodename>, and no
       
   692         attribute handler should be called.
       
   693         """
       
   694         # Passing the whole document or whole sections as parameters
       
   695         # to \DN... or \DECattr causes LaTeX to run out of memory.
       
   696         return not isinstance(node, (nodes.document, nodes.section))
       
   697 
       
   698     def dispatch_visit(self, node):
       
   699         skip_attr = skip_parent = 0
       
   700         # TreePruningException to be propagated.
       
   701         tree_pruning_exception = None
       
   702         if hasattr(self, 'before_' + node.__class__.__name__):
       
   703             try:
       
   704                 getattr(self, 'before_' + node.__class__.__name__)(node)
       
   705             except SkipParentLaTeX:
       
   706                 skip_parent = 1
       
   707             except SkipAttrParentLaTeX:
       
   708                 skip_attr = 1
       
   709                 skip_parent = 1
       
   710             except nodes.SkipNode:
       
   711                 raise
       
   712             except (nodes.SkipChildren, nodes.SkipSiblings), instance:
       
   713                 tree_pruning_exception = instance
       
   714             except nodes.SkipDeparture:
       
   715                 raise NotImplementedError(
       
   716                     'SkipDeparture not usable in LaTeX writer')
       
   717 
       
   718         if not isinstance(node, nodes.Text):
       
   719             node_name = self.node_name(node)
       
   720             # attribute_deleters will be appended to self.context.
       
   721             attribute_deleters = []
       
   722             if not skip_parent and not isinstance(node, nodes.document):
       
   723                 self.append(r'\renewcommand{\DEVparent}{%s}'
       
   724                             % self.node_name(node.parent))
       
   725                 for name, value in node.attlist():
       
   726                     if not isinstance(value, ListType) and not ':' in name:
       
   727                         # For non-list and non-special (like
       
   728                         # 'xml:preserve') attributes, set
       
   729                         # \DEVcurrentN<nodename>A<attribute> to the
       
   730                         # attribute value, so that the value of the
       
   731                         # attribute is available in the node handler
       
   732                         # and all children.
       
   733                         macro = r'\DEVcurrentN%sA%s' % (node_name, name)
       
   734                         self.append(r'\def%s{%s}' % (
       
   735                             macro, self.encode(unicode(value), attval=name)))
       
   736                         # Make the attribute undefined afterwards.
       
   737                         attribute_deleters.append(r'\let%s=\relax' % macro)
       
   738             self.context.append('\n'.join(attribute_deleters))
       
   739             if self.pass_contents(node):
       
   740                 # Call \DN<nodename>{<contents>}.
       
   741                 self.append(r'\DN%s{' % node_name)
       
   742                 self.context.append('}')
       
   743             else:
       
   744                 # Call \DECvisit<nodename> <contents>
       
   745                 # \DECdepart<nodename>.  (Maybe we should use LaTeX
       
   746                 # environments for this?)
       
   747                 self.append(r'\DECvisit%s' % node_name)
       
   748                 self.context.append(r'\DECdepart%s' % node_name)
       
   749             self.indentation_level += 1
       
   750             if not skip_attr:
       
   751                 self.propagate_attributes(node)
       
   752             else:
       
   753                 self.context.append('')
       
   754 
       
   755         if (isinstance(node, nodes.TextElement) and
       
   756             not isinstance(node.parent, nodes.TextElement)):
       
   757             # Reset current quote to left.
       
   758             self.left_quote = 1
       
   759 
       
   760         # Call visit_... method.
       
   761         try:
       
   762             nodes.SparseNodeVisitor.dispatch_visit(self, node)
       
   763         except LaTeXException:
       
   764             raise NotImplementedError(
       
   765                 'visit_... methods must not raise LaTeXExceptions')
       
   766 
       
   767         if tree_pruning_exception:
       
   768             # Propagate TreePruningException raised in before_... method.
       
   769             raise tree_pruning_exception
       
   770 
       
   771     def is_invisible(self, node):
       
   772         # Return true if node is invisible or moved away in the LaTeX
       
   773         # rendering.
       
   774         return (not isinstance(node, nodes.Text) and
       
   775                 (isinstance(node, nodes.Invisible) or
       
   776                  isinstance(node, nodes.footnote) or
       
   777                  isinstance(node, nodes.citation) or
       
   778                  # Assume raw nodes to be invisible.
       
   779                  isinstance(node, nodes.raw) or
       
   780                  # Floating image or figure.
       
   781                  node.get('align') in ('left', 'right')))
       
   782 
       
   783     def is_visible(self, node):
       
   784         return not self.is_invisible(node)
       
   785 
       
   786     def needs_space(self, node):
       
   787         """Two nodes for which `needs_space` is true need auxiliary space."""
       
   788         # Return true if node is a visible block-level element.
       
   789         return ((isinstance(node, nodes.Body) or
       
   790                  isinstance(node, nodes.topic)) and
       
   791                 not (self.is_invisible(node) or
       
   792                      isinstance(node.parent, nodes.TextElement)))
       
   793 
       
   794     def always_needs_space(self, node):
       
   795         """
       
   796         Always add space around nodes for which `always_needs_space()`
       
   797         is true, regardless of whether the other node needs space as
       
   798         well.  (E.g. transition next to section.)
       
   799         """
       
   800         return isinstance(node, nodes.transition)
       
   801 
       
   802     def dispatch_departure(self, node):
       
   803         # Call departure method.
       
   804         nodes.SparseNodeVisitor.dispatch_departure(self, node)
       
   805 
       
   806         if not isinstance(node, nodes.Text):
       
   807             # Close attribute and node handler call (\DN...{...}).
       
   808             self.indentation_level -= 1
       
   809             self.append(self.context.pop() + self.context.pop())
       
   810             # Delete \DECcurrentN... attribute macros.
       
   811             self.append(self.context.pop())
       
   812             # Get next sibling.
       
   813             next_node = node.next_node(
       
   814                 ascend=0, siblings=1, descend=0,
       
   815                 condition=self.is_visible)
       
   816             # Insert space if necessary.
       
   817             if  (self.needs_space(node) and self.needs_space(next_node) or
       
   818                  self.always_needs_space(node) or
       
   819                  self.always_needs_space(next_node)):
       
   820                 if isinstance(node, nodes.paragraph) and isinstance(next_node, nodes.paragraph):
       
   821                     # Space between paragraphs.
       
   822                     self.append(r'\DECparagraphspace')
       
   823                 else:
       
   824                     # One of the elements is not a paragraph.
       
   825                     self.append(r'\DECauxiliaryspace')