root/galaxy-central/eggs/docutils-0.4-py2.6.egg/docutils/writers/newlatex2e/__init__.py @ 3

リビジョン 3, 29.3 KB (コミッタ: kohda, 14 年 前)

Install Unix tools  http://hannonlab.cshl.edu/galaxy_unix_tools/galaxy.html

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