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

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

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

行番号 
1# Author: David Goodger
2# Contact: goodger@users.sourceforge.net
3# Revision: $Revision: 4219 $
4# Date: $Date: 2005-12-15 15:32:01 +0100 (Thu, 15 Dec 2005) $
5# Copyright: This module has been placed in the public domain.
6
7"""
8Simple HyperText Markup Language document tree Writer.
9
10The output conforms to the XHTML version 1.0 Transitional DTD
11(*almost* strict).  The output contains a minimum of formatting
12information.  The cascading style sheet "html4css1.css" is required
13for proper viewing with a modern graphical browser.
14"""
15
16__docformat__ = 'reStructuredText'
17
18
19import sys
20import os
21import os.path
22import time
23import re
24from types import ListType
25try:
26    import Image                        # check for the Python Imaging Library
27except ImportError:
28    Image = None
29import docutils
30from docutils import frontend, nodes, utils, writers, languages
31
32
33class Writer(writers.Writer):
34
35    supported = ('html', 'html4css1', 'xhtml')
36    """Formats this writer supports."""
37
38    default_stylesheet = 'html4css1.css'
39
40    default_stylesheet_path = utils.relative_path(
41        os.path.join(os.getcwd(), 'dummy'),
42        os.path.join(os.path.dirname(__file__), default_stylesheet))
43
44    settings_spec = (
45        'HTML-Specific Options',
46        None,
47        (('Specify a stylesheet URL, used verbatim.  Overrides '
48          '--stylesheet-path.',
49          ['--stylesheet'],
50          {'metavar': '<URL>', 'overrides': 'stylesheet_path'}),
51         ('Specify a stylesheet file, relative to the current working '
52          'directory.  The path is adjusted relative to the output HTML '
53          'file.  Overrides --stylesheet.  Default: "%s"'
54          % default_stylesheet_path,
55          ['--stylesheet-path'],
56          {'metavar': '<file>', 'overrides': 'stylesheet',
57           'default': default_stylesheet_path}),
58         ('Embed the stylesheet in the output HTML file.  The stylesheet '
59          'file must be accessible during processing (--stylesheet-path is '
60          'recommended).  This is the default.',
61          ['--embed-stylesheet'],
62          {'default': 1, 'action': 'store_true',
63           'validator': frontend.validate_boolean}),
64         ('Link to the stylesheet in the output HTML file.  Default: '
65          'embed the stylesheet, do not link to it.',
66          ['--link-stylesheet'],
67          {'dest': 'embed_stylesheet', 'action': 'store_false',
68           'validator': frontend.validate_boolean}),
69         ('Specify the initial header level.  Default is 1 for "<h1>".  '
70          'Does not affect document title & subtitle (see --no-doc-title).',
71          ['--initial-header-level'],
72          {'choices': '1 2 3 4 5 6'.split(), 'default': '1',
73           'metavar': '<level>'}),
74         ('Specify the maximum width (in characters) for one-column field '
75          'names.  Longer field names will span an entire row of the table '
76          'used to render the field list.  Default is 14 characters.  '
77          'Use 0 for "no limit".',
78          ['--field-name-limit'],
79          {'default': 14, 'metavar': '<level>',
80           'validator': frontend.validate_nonnegative_int}),
81         ('Specify the maximum width (in characters) for options in option '
82          'lists.  Longer options will span an entire row of the table used '
83          'to render the option list.  Default is 14 characters.  '
84          'Use 0 for "no limit".',
85          ['--option-limit'],
86          {'default': 14, 'metavar': '<level>',
87           'validator': frontend.validate_nonnegative_int}),
88         ('Format for footnote references: one of "superscript" or '
89          '"brackets".  Default is "brackets".',
90          ['--footnote-references'],
91          {'choices': ['superscript', 'brackets'], 'default': 'brackets',
92           'metavar': '<format>',
93           'overrides': 'trim_footnote_reference_space'}),
94         ('Format for block quote attributions: one of "dash" (em-dash '
95          'prefix), "parentheses"/"parens", or "none".  Default is "dash".',
96          ['--attribution'],
97          {'choices': ['dash', 'parentheses', 'parens', 'none'],
98           'default': 'dash', 'metavar': '<format>'}),
99         ('Remove extra vertical whitespace between items of "simple" bullet '
100          'lists and enumerated lists.  Default: enabled.',
101          ['--compact-lists'],
102          {'default': 1, 'action': 'store_true',
103           'validator': frontend.validate_boolean}),
104         ('Disable compact simple bullet and enumerated lists.',
105          ['--no-compact-lists'],
106          {'dest': 'compact_lists', 'action': 'store_false'}),
107         ('Remove extra vertical whitespace between items of simple field '
108          'lists.  Default: enabled.',
109          ['--compact-field-lists'],
110          {'default': 1, 'action': 'store_true',
111           'validator': frontend.validate_boolean}),
112         ('Disable compact simple field lists.',
113          ['--no-compact-field-lists'],
114          {'dest': 'compact_field_lists', 'action': 'store_false'}),
115         ('Omit the XML declaration.  Use with caution.',
116          ['--no-xml-declaration'],
117          {'dest': 'xml_declaration', 'default': 1, 'action': 'store_false',
118           'validator': frontend.validate_boolean}),
119         ('Obfuscate email addresses to confuse harvesters while still '
120          'keeping email links usable with standards-compliant browsers.',
121          ['--cloak-email-addresses'],
122          {'action': 'store_true', 'validator': frontend.validate_boolean}),))
123
124    settings_defaults = {'output_encoding_error_handler': 'xmlcharrefreplace'}
125
126    relative_path_settings = ('stylesheet_path',)
127
128    config_section = 'html4css1 writer'
129    config_section_dependencies = ('writers',)
130
131    def __init__(self):
132        writers.Writer.__init__(self)
133        self.translator_class = HTMLTranslator
134
135    def translate(self):
136        self.visitor = visitor = self.translator_class(self.document)
137        self.document.walkabout(visitor)
138        self.output = visitor.astext()
139        for attr in ('head_prefix', 'stylesheet', 'head', 'body_prefix',
140                     'body_pre_docinfo', 'docinfo', 'body', 'fragment',
141                     'body_suffix'):
142            setattr(self, attr, getattr(visitor, attr))
143
144    def assemble_parts(self):
145        writers.Writer.assemble_parts(self)
146        for part in ('title', 'subtitle', 'docinfo', 'body', 'header',
147                     'footer', 'meta', 'stylesheet', 'fragment',
148                     'html_prolog', 'html_head', 'html_title', 'html_subtitle',
149                     'html_body'):
150            self.parts[part] = ''.join(getattr(self.visitor, part))
151
152
153class HTMLTranslator(nodes.NodeVisitor):
154
155    """
156    This HTML writer has been optimized to produce visually compact
157    lists (less vertical whitespace).  HTML's mixed content models
158    allow list items to contain "<li><p>body elements</p></li>" or
159    "<li>just text</li>" or even "<li>text<p>and body
160    elements</p>combined</li>", each with different effects.  It would
161    be best to stick with strict body elements in list items, but they
162    affect vertical spacing in browsers (although they really
163    shouldn't).
164
165    Here is an outline of the optimization:
166
167    - Check for and omit <p> tags in "simple" lists: list items
168      contain either a single paragraph, a nested simple list, or a
169      paragraph followed by a nested simple list.  This means that
170      this list can be compact:
171
172          - Item 1.
173          - Item 2.
174
175      But this list cannot be compact:
176
177          - Item 1.
178
179            This second paragraph forces space between list items.
180
181          - Item 2.
182
183    - In non-list contexts, omit <p> tags on a paragraph if that
184      paragraph is the only child of its parent (footnotes & citations
185      are allowed a label first).
186
187    - Regardless of the above, in definitions, table cells, field bodies,
188      option descriptions, and list items, mark the first child with
189      'class="first"' and the last child with 'class="last"'.  The stylesheet
190      sets the margins (top & bottom respectively) to 0 for these elements.
191
192    The ``no_compact_lists`` setting (``--no-compact-lists`` command-line
193    option) disables list whitespace optimization.
194    """
195
196    xml_declaration = '<?xml version="1.0" encoding="%s" ?>\n'
197    doctype = ('<!DOCTYPE html'
198               ' PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"'
199               ' "http://www.w3.org/TR/xhtml1/DTD/'
200               'xhtml1-transitional.dtd">\n')
201    head_prefix_template = ('<html xmlns="http://www.w3.org/1999/xhtml"'
202                            ' xml:lang="%s" lang="%s">\n<head>\n')
203    content_type = ('<meta http-equiv="Content-Type"'
204                    ' content="text/html; charset=%s" />\n')
205    generator = ('<meta name="generator" content="Docutils %s: '
206                 'http://docutils.sourceforge.net/" />\n')
207    stylesheet_link = '<link rel="stylesheet" href="%s" type="text/css" />\n'
208    embedded_stylesheet = '<style type="text/css">\n\n%s\n</style>\n'
209    named_tags = ['a', 'applet', 'form', 'frame', 'iframe', 'img', 'map']
210    words_and_spaces = re.compile(r'\S+| +|\n')
211
212    def __init__(self, document):
213        nodes.NodeVisitor.__init__(self, document)
214        self.settings = settings = document.settings
215        lcode = settings.language_code
216        self.language = languages.get_language(lcode)
217        self.meta = [self.content_type % settings.output_encoding,
218                     self.generator % docutils.__version__]
219        self.head_prefix = []
220        self.html_prolog = []
221        if settings.xml_declaration:
222            self.head_prefix.append(self.xml_declaration
223                                    % settings.output_encoding)
224            # encoding not interpolated:
225            self.html_prolog.append(self.xml_declaration)
226        self.head_prefix.extend([self.doctype,
227                                 self.head_prefix_template % (lcode, lcode)])
228        self.html_prolog.append(self.doctype)
229        self.head = self.meta[:]
230        stylesheet = utils.get_stylesheet_reference(settings)
231        self.stylesheet = []
232        if stylesheet:
233            if settings.embed_stylesheet:
234                stylesheet = utils.get_stylesheet_reference(
235                    settings, os.path.join(os.getcwd(), 'dummy'))
236                settings.record_dependencies.add(stylesheet)
237                stylesheet_text = open(stylesheet).read()
238                self.stylesheet = [self.embedded_stylesheet % stylesheet_text]
239            else:
240                self.stylesheet = [self.stylesheet_link
241                                   % self.encode(stylesheet)]
242        self.body_prefix = ['</head>\n<body>\n']
243        # document title, subtitle display
244        self.body_pre_docinfo = []
245        # author, date, etc.
246        self.docinfo = []
247        self.body = []
248        self.fragment = []
249        self.body_suffix = ['</body>\n</html>\n']
250        self.section_level = 0
251        self.initial_header_level = int(settings.initial_header_level)
252        # A heterogenous stack used in conjunction with the tree traversal.
253        # Make sure that the pops correspond to the pushes:
254        self.context = []
255        self.topic_classes = []
256        self.colspecs = []
257        self.compact_p = 1
258        self.compact_simple = None
259        self.compact_field_list = None
260        self.in_docinfo = None
261        self.in_sidebar = None
262        self.title = []
263        self.subtitle = []
264        self.header = []
265        self.footer = []
266        self.html_head = [self.content_type] # charset not interpolated
267        self.html_title = []
268        self.html_subtitle = []
269        self.html_body = []
270        self.in_document_title = 0
271        self.in_mailto = 0
272        self.author_in_authors = None
273
274    def astext(self):
275        return ''.join(self.head_prefix + self.head
276                       + self.stylesheet + self.body_prefix
277                       + self.body_pre_docinfo + self.docinfo
278                       + self.body + self.body_suffix)
279
280    def encode(self, text):
281        """Encode special characters in `text` & return."""
282        # @@@ A codec to do these and all other HTML entities would be nice.
283        text = text.replace("&", "&amp;")
284        text = text.replace("<", "&lt;")
285        text = text.replace('"', "&quot;")
286        text = text.replace(">", "&gt;")
287        text = text.replace("@", "&#64;") # may thwart some address harvesters
288        # Replace the non-breaking space character with the HTML entity:
289        text = text.replace(u'\u00a0', "&nbsp;")
290        return text
291
292    def cloak_mailto(self, uri):
293        """Try to hide a mailto: URL from harvesters."""
294        # Encode "@" using a URL octet reference (see RFC 1738).
295        # Further cloaking with HTML entities will be done in the
296        # `attval` function.
297        return uri.replace('@', '%40')
298
299    def cloak_email(self, addr):
300        """Try to hide the link text of a email link from harversters."""
301        # Surround at-signs and periods with <span> tags.  ("@" has
302        # already been encoded to "&#64;" by the `encode` method.)
303        addr = addr.replace('&#64;', '<span>&#64;</span>')
304        addr = addr.replace('.', '<span>&#46;</span>')
305        return addr
306
307    def attval(self, text,
308               whitespace=re.compile('[\n\r\t\v\f]')):
309        """Cleanse, HTML encode, and return attribute value text."""
310        encoded = self.encode(whitespace.sub(' ', text))
311        if self.in_mailto and self.settings.cloak_email_addresses:
312            # Cloak at-signs ("%40") and periods with HTML entities.
313            encoded = encoded.replace('%40', '&#37;&#52;&#48;')
314            encoded = encoded.replace('.', '&#46;')
315        return encoded
316
317    def starttag(self, node, tagname, suffix='\n', empty=0, **attributes):
318        """
319        Construct and return a start tag given a node (id & class attributes
320        are extracted), tag name, and optional attributes.
321        """
322        tagname = tagname.lower()
323        prefix = []
324        atts = {}
325        ids = []
326        for (name, value) in attributes.items():
327            atts[name.lower()] = value
328        classes = node.get('classes', [])
329        if atts.has_key('class'):
330            classes.append(atts['class'])
331        if classes:
332            atts['class'] = ' '.join(classes)
333        assert not atts.has_key('id')
334        ids.extend(node.get('ids', []))
335        if atts.has_key('ids'):
336            ids.extend(atts['ids'])
337            del atts['ids']
338        if ids:
339            atts['id'] = ids[0]
340            for id in ids[1:]:
341                # Add empty "span" elements for additional IDs.  Note
342                # that we cannot use empty "a" elements because there
343                # may be targets inside of references, but nested "a"
344                # elements aren't allowed in XHTML (even if they do
345                # not all have a "href" attribute).
346                if empty:
347                    # Empty tag.  Insert target right in front of element.
348                    prefix.append('<span id="%s"></span>' % id)
349                else:
350                    # Non-empty tag.  Place the auxiliary <span> tag
351                    # *inside* the element, as the first child.
352                    suffix += '<span id="%s"></span>' % id
353        # !!! next 2 lines to be removed in Docutils 0.5:
354        if atts.has_key('id') and tagname in self.named_tags:
355            atts['name'] = atts['id']   # for compatibility with old browsers
356        attlist = atts.items()
357        attlist.sort()
358        parts = [tagname]
359        for name, value in attlist:
360            # value=None was used for boolean attributes without
361            # value, but this isn't supported by XHTML.
362            assert value is not None
363            if isinstance(value, ListType):
364                values = [unicode(v) for v in value]
365                parts.append('%s="%s"' % (name.lower(),
366                                          self.attval(' '.join(values))))
367            else:
368                try:
369                    uval = unicode(value)
370                except TypeError:       # for Python 2.1 compatibility:
371                    uval = unicode(str(value))
372                parts.append('%s="%s"' % (name.lower(), self.attval(uval)))
373        if empty:
374            infix = ' /'
375        else:
376            infix = ''
377        return ''.join(prefix) + '<%s%s>' % (' '.join(parts), infix) + suffix
378
379    def emptytag(self, node, tagname, suffix='\n', **attributes):
380        """Construct and return an XML-compatible empty tag."""
381        return self.starttag(node, tagname, suffix, empty=1, **attributes)
382
383    # !!! to be removed in Docutils 0.5 (change calls to use "starttag"):
384    def start_tag_with_title(self, node, tagname, **atts):
385        """ID and NAME attributes will be handled in the title."""
386        node = {'classes': node.get('classes', [])}
387        return self.starttag(node, tagname, **atts)
388
389    def set_class_on_child(self, node, class_, index=0):
390        """
391        Set class `class_` on the visible child no. index of `node`.
392        Do nothing if node has fewer children than `index`.
393        """
394        children = [n for n in node if not isinstance(n, nodes.Invisible)]
395        try:
396            child = children[index]
397        except IndexError:
398            return
399        child['classes'].append(class_)
400
401    def set_first_last(self, node):
402        self.set_class_on_child(node, 'first', 0)
403        self.set_class_on_child(node, 'last', -1)
404
405    def visit_Text(self, node):
406        text = node.astext()
407        encoded = self.encode(text)
408        if self.in_mailto and self.settings.cloak_email_addresses:
409            encoded = self.cloak_email(encoded)
410        self.body.append(encoded)
411
412    def depart_Text(self, node):
413        pass
414
415    def visit_abbreviation(self, node):
416        # @@@ implementation incomplete ("title" attribute)
417        self.body.append(self.starttag(node, 'abbr', ''))
418
419    def depart_abbreviation(self, node):
420        self.body.append('</abbr>')
421
422    def visit_acronym(self, node):
423        # @@@ implementation incomplete ("title" attribute)
424        self.body.append(self.starttag(node, 'acronym', ''))
425
426    def depart_acronym(self, node):
427        self.body.append('</acronym>')
428
429    def visit_address(self, node):
430        self.visit_docinfo_item(node, 'address', meta=None)
431        self.body.append(self.starttag(node, 'pre', CLASS='address'))
432
433    def depart_address(self, node):
434        self.body.append('\n</pre>\n')
435        self.depart_docinfo_item()
436
437    def visit_admonition(self, node, name=''):
438        self.body.append(self.start_tag_with_title(
439            node, 'div', CLASS=(name or 'admonition')))
440        if name:
441            node.insert(0, nodes.title(name, self.language.labels[name]))
442        self.set_first_last(node)
443
444    def depart_admonition(self, node=None):
445        self.body.append('</div>\n')
446
447    def visit_attention(self, node):
448        self.visit_admonition(node, 'attention')
449
450    def depart_attention(self, node):
451        self.depart_admonition()
452
453    attribution_formats = {'dash': ('&mdash;', ''),
454                           'parentheses': ('(', ')'),
455                           'parens': ('(', ')'),
456                           'none': ('', '')}
457
458    def visit_attribution(self, node):
459        prefix, suffix = self.attribution_formats[self.settings.attribution]
460        self.context.append(suffix)
461        self.body.append(
462            self.starttag(node, 'p', prefix, CLASS='attribution'))
463
464    def depart_attribution(self, node):
465        self.body.append(self.context.pop() + '</p>\n')
466
467    def visit_author(self, node):
468        if isinstance(node.parent, nodes.authors):
469            if self.author_in_authors:
470                self.body.append('\n<br />')
471        else:
472            self.visit_docinfo_item(node, 'author')
473
474    def depart_author(self, node):
475        if isinstance(node.parent, nodes.authors):
476            self.author_in_authors += 1
477        else:
478            self.depart_docinfo_item()
479
480    def visit_authors(self, node):
481        self.visit_docinfo_item(node, 'authors')
482        self.author_in_authors = 0      # initialize counter
483
484    def depart_authors(self, node):
485        self.depart_docinfo_item()
486        self.author_in_authors = None
487
488    def visit_block_quote(self, node):
489        self.body.append(self.starttag(node, 'blockquote'))
490
491    def depart_block_quote(self, node):
492        self.body.append('</blockquote>\n')
493
494    def check_simple_list(self, node):
495        """Check for a simple list that can be rendered compactly."""
496        visitor = SimpleListChecker(self.document)
497        try:
498            node.walk(visitor)
499        except nodes.NodeFound:
500            return None
501        else:
502            return 1
503
504    def is_compactable(self, node):
505        return ('compact' in node['classes']
506                or (self.settings.compact_lists
507                    and 'open' not in node['classes']
508                    and (self.compact_simple
509                         or self.topic_classes == ['contents']
510                         or self.check_simple_list(node))))
511
512    def visit_bullet_list(self, node):
513        atts = {}
514        old_compact_simple = self.compact_simple
515        self.context.append((self.compact_simple, self.compact_p))
516        self.compact_p = None
517        self.compact_simple = self.is_compactable(node)
518        if self.compact_simple and not old_compact_simple:
519            atts['class'] = 'simple'
520        self.body.append(self.starttag(node, 'ul', **atts))
521
522    def depart_bullet_list(self, node):
523        self.compact_simple, self.compact_p = self.context.pop()
524        self.body.append('</ul>\n')
525
526    def visit_caption(self, node):
527        self.body.append(self.starttag(node, 'p', '', CLASS='caption'))
528
529    def depart_caption(self, node):
530        self.body.append('</p>\n')
531
532    def visit_caution(self, node):
533        self.visit_admonition(node, 'caution')
534
535    def depart_caution(self, node):
536        self.depart_admonition()
537
538    def visit_citation(self, node):
539        self.body.append(self.starttag(node, 'table',
540                                       CLASS='docutils citation',
541                                       frame="void", rules="none"))
542        self.body.append('<colgroup><col class="label" /><col /></colgroup>\n'
543                         '<tbody valign="top">\n'
544                         '<tr>')
545        self.footnote_backrefs(node)
546
547    def depart_citation(self, node):
548        self.body.append('</td></tr>\n'
549                         '</tbody>\n</table>\n')
550
551    def visit_citation_reference(self, node):
552        href = '#' + node['refid']
553        self.body.append(self.starttag(
554            node, 'a', '[', CLASS='citation-reference', href=href))
555
556    def depart_citation_reference(self, node):
557        self.body.append(']</a>')
558
559    def visit_classifier(self, node):
560        self.body.append(' <span class="classifier-delimiter">:</span> ')
561        self.body.append(self.starttag(node, 'span', '', CLASS='classifier'))
562
563    def depart_classifier(self, node):
564        self.body.append('</span>')
565
566    def visit_colspec(self, node):
567        self.colspecs.append(node)
568        # "stubs" list is an attribute of the tgroup element:
569        node.parent.stubs.append(node.attributes.get('stub'))
570
571    def depart_colspec(self, node):
572        pass
573
574    def write_colspecs(self):
575        width = 0
576        for node in self.colspecs:
577            width += node['colwidth']
578        for node in self.colspecs:
579            colwidth = int(node['colwidth'] * 100.0 / width + 0.5)
580            self.body.append(self.emptytag(node, 'col',
581                                           width='%i%%' % colwidth))
582        self.colspecs = []
583
584    def visit_comment(self, node,
585                      sub=re.compile('-(?=-)').sub):
586        """Escape double-dashes in comment text."""
587        self.body.append('<!-- %s -->\n' % sub('- ', node.astext()))
588        # Content already processed:
589        raise nodes.SkipNode
590
591    def visit_compound(self, node):
592        self.body.append(self.starttag(node, 'div', CLASS='compound'))
593        if len(node) > 1:
594            node[0]['classes'].append('compound-first')
595            node[-1]['classes'].append('compound-last')
596            for child in node[1:-1]:
597                child['classes'].append('compound-middle')
598
599    def depart_compound(self, node):
600        self.body.append('</div>\n')
601
602    def visit_container(self, node):
603        self.body.append(self.starttag(node, 'div', CLASS='container'))
604
605    def depart_container(self, node):
606        self.body.append('</div>\n')
607
608    def visit_contact(self, node):
609        self.visit_docinfo_item(node, 'contact', meta=None)
610
611    def depart_contact(self, node):
612        self.depart_docinfo_item()
613
614    def visit_copyright(self, node):
615        self.visit_docinfo_item(node, 'copyright')
616
617    def depart_copyright(self, node):
618        self.depart_docinfo_item()
619
620    def visit_danger(self, node):
621        self.visit_admonition(node, 'danger')
622
623    def depart_danger(self, node):
624        self.depart_admonition()
625
626    def visit_date(self, node):
627        self.visit_docinfo_item(node, 'date')
628
629    def depart_date(self, node):
630        self.depart_docinfo_item()
631
632    def visit_decoration(self, node):
633        pass
634
635    def depart_decoration(self, node):
636        pass
637
638    def visit_definition(self, node):
639        self.body.append('</dt>\n')
640        self.body.append(self.starttag(node, 'dd', ''))
641        self.set_first_last(node)
642
643    def depart_definition(self, node):
644        self.body.append('</dd>\n')
645
646    def visit_definition_list(self, node):
647        self.body.append(self.starttag(node, 'dl', CLASS='docutils'))
648
649    def depart_definition_list(self, node):
650        self.body.append('</dl>\n')
651
652    def visit_definition_list_item(self, node):
653        pass
654
655    def depart_definition_list_item(self, node):
656        pass
657
658    def visit_description(self, node):
659        self.body.append(self.starttag(node, 'td', ''))
660        self.set_first_last(node)
661
662    def depart_description(self, node):
663        self.body.append('</td>')
664
665    def visit_docinfo(self, node):
666        self.context.append(len(self.body))
667        self.body.append(self.starttag(node, 'table',
668                                       CLASS='docinfo',
669                                       frame="void", rules="none"))
670        self.body.append('<col class="docinfo-name" />\n'
671                         '<col class="docinfo-content" />\n'
672                         '<tbody valign="top">\n')
673        self.in_docinfo = 1
674
675    def depart_docinfo(self, node):
676        self.body.append('</tbody>\n</table>\n')
677        self.in_docinfo = None
678        start = self.context.pop()
679        self.docinfo = self.body[start:]
680        self.body = []
681
682    def visit_docinfo_item(self, node, name, meta=1):
683        if meta:
684            meta_tag = '<meta name="%s" content="%s" />\n' \
685                       % (name, self.attval(node.astext()))
686            self.add_meta(meta_tag)
687        self.body.append(self.starttag(node, 'tr', ''))
688        self.body.append('<th class="docinfo-name">%s:</th>\n<td>'
689                         % self.language.labels[name])
690        if len(node):
691            if isinstance(node[0], nodes.Element):
692                node[0]['classes'].append('first')
693            if isinstance(node[-1], nodes.Element):
694                node[-1]['classes'].append('last')
695
696    def depart_docinfo_item(self):
697        self.body.append('</td></tr>\n')
698
699    def visit_doctest_block(self, node):
700        self.body.append(self.starttag(node, 'pre', CLASS='doctest-block'))
701
702    def depart_doctest_block(self, node):
703        self.body.append('\n</pre>\n')
704
705    def visit_document(self, node):
706        self.head.append('<title>%s</title>\n'
707                         % self.encode(node.get('title', '')))
708
709    def depart_document(self, node):
710        self.fragment.extend(self.body)
711        self.body_prefix.append(self.starttag(node, 'div', CLASS='document'))
712        self.body_suffix.insert(0, '</div>\n')
713        # skip content-type meta tag with interpolated charset value:
714        self.html_head.extend(self.head[1:])
715        self.html_body.extend(self.body_prefix[1:] + self.body_pre_docinfo
716                              + self.docinfo + self.body
717                              + self.body_suffix[:-1])
718
719    def visit_emphasis(self, node):
720        self.body.append('<em>')
721
722    def depart_emphasis(self, node):
723        self.body.append('</em>')
724
725    def visit_entry(self, node):
726        atts = {'class': []}
727        if isinstance(node.parent.parent, nodes.thead):
728            atts['class'].append('head')
729        if node.parent.parent.parent.stubs[node.parent.column]:
730            # "stubs" list is an attribute of the tgroup element
731            atts['class'].append('stub')
732        if atts['class']:
733            tagname = 'th'
734            atts['class'] = ' '.join(atts['class'])
735        else:
736            tagname = 'td'
737            del atts['class']
738        node.parent.column += 1
739        if node.has_key('morerows'):
740            atts['rowspan'] = node['morerows'] + 1
741        if node.has_key('morecols'):
742            atts['colspan'] = node['morecols'] + 1
743            node.parent.column += node['morecols']
744        self.body.append(self.starttag(node, tagname, '', **atts))
745        self.context.append('</%s>\n' % tagname.lower())
746        if len(node) == 0:              # empty cell
747            self.body.append('&nbsp;')
748        self.set_first_last(node)
749
750    def depart_entry(self, node):
751        self.body.append(self.context.pop())
752
753    def visit_enumerated_list(self, node):
754        """
755        The 'start' attribute does not conform to HTML 4.01's strict.dtd, but
756        CSS1 doesn't help. CSS2 isn't widely enough supported yet to be
757        usable.
758        """
759        atts = {}
760        if node.has_key('start'):
761            atts['start'] = node['start']
762        if node.has_key('enumtype'):
763            atts['class'] = node['enumtype']
764        # @@@ To do: prefix, suffix. How? Change prefix/suffix to a
765        # single "format" attribute? Use CSS2?
766        old_compact_simple = self.compact_simple
767        self.context.append((self.compact_simple, self.compact_p))
768        self.compact_p = None
769        self.compact_simple = self.is_compactable(node)
770        if self.compact_simple and not old_compact_simple:
771            atts['class'] = (atts.get('class', '') + ' simple').strip()
772        self.body.append(self.starttag(node, 'ol', **atts))
773
774    def depart_enumerated_list(self, node):
775        self.compact_simple, self.compact_p = self.context.pop()
776        self.body.append('</ol>\n')
777
778    def visit_error(self, node):
779        self.visit_admonition(node, 'error')
780
781    def depart_error(self, node):
782        self.depart_admonition()
783
784    def visit_field(self, node):
785        self.body.append(self.starttag(node, 'tr', '', CLASS='field'))
786
787    def depart_field(self, node):
788        self.body.append('</tr>\n')
789
790    def visit_field_body(self, node):
791        self.body.append(self.starttag(node, 'td', '', CLASS='field-body'))
792        self.set_class_on_child(node, 'first', 0)
793        field = node.parent
794        if (self.compact_field_list or
795            isinstance(field.parent, nodes.docinfo) or
796            field.parent.index(field) == len(field.parent) - 1):
797            # If we are in a compact list, the docinfo, or if this is
798            # the last field of the field list, do not add vertical
799            # space after last element.
800            self.set_class_on_child(node, 'last', -1)
801
802    def depart_field_body(self, node):
803        self.body.append('</td>\n')
804
805    def visit_field_list(self, node):
806        self.context.append((self.compact_field_list, self.compact_p))
807        self.compact_p = None
808        if 'compact' in node['classes']:
809            self.compact_field_list = 1
810        elif (self.settings.compact_field_lists
811              and 'open' not in node['classes']):
812            self.compact_field_list = 1
813        if self.compact_field_list:
814            for field in node:
815                field_body = field[-1]
816                assert isinstance(field_body, nodes.field_body)
817                children = [n for n in field_body
818                            if not isinstance(n, nodes.Invisible)]
819                if not (len(children) == 0 or
820                        len(children) == 1 and
821                        isinstance(children[0], nodes.paragraph)):
822                    self.compact_field_list = 0
823                    break
824        self.body.append(self.starttag(node, 'table', frame='void',
825                                       rules='none',
826                                       CLASS='docutils field-list'))
827        self.body.append('<col class="field-name" />\n'
828                         '<col class="field-body" />\n'
829                         '<tbody valign="top">\n')
830
831    def depart_field_list(self, node):
832        self.body.append('</tbody>\n</table>\n')
833        self.compact_field_list, self.compact_p = self.context.pop()
834
835    def visit_field_name(self, node):
836        atts = {}
837        if self.in_docinfo:
838            atts['class'] = 'docinfo-name'
839        else:
840            atts['class'] = 'field-name'
841        if ( self.settings.field_name_limit
842             and len(node.astext()) > self.settings.field_name_limit):
843            atts['colspan'] = 2
844            self.context.append('</tr>\n<tr><td>&nbsp;</td>')
845        else:
846            self.context.append('')
847        self.body.append(self.starttag(node, 'th', '', **atts))
848
849    def depart_field_name(self, node):
850        self.body.append(':</th>')
851        self.body.append(self.context.pop())
852
853    def visit_figure(self, node):
854        atts = {'class': 'figure'}
855        if node.get('width'):
856            atts['style'] = 'width: %spx' % node['width']
857        if node.get('align'):
858            atts['align'] = node['align']
859        self.body.append(self.starttag(node, 'div', **atts))
860
861    def depart_figure(self, node):
862        self.body.append('</div>\n')
863
864    def visit_footer(self, node):
865        self.context.append(len(self.body))
866
867    def depart_footer(self, node):
868        start = self.context.pop()
869        footer = [self.starttag(node, 'div', CLASS='footer'),
870                  '<hr class="footer" />\n']
871        footer.extend(self.body[start:])
872        footer.append('\n</div>\n')
873        self.footer.extend(footer)
874        self.body_suffix[:0] = footer
875        del self.body[start:]
876
877    def visit_footnote(self, node):
878        self.body.append(self.starttag(node, 'table',
879                                       CLASS='docutils footnote',
880                                       frame="void", rules="none"))
881        self.body.append('<colgroup><col class="label" /><col /></colgroup>\n'
882                         '<tbody valign="top">\n'
883                         '<tr>')
884        self.footnote_backrefs(node)
885
886    def footnote_backrefs(self, node):
887        backlinks = []
888        backrefs = node['backrefs']
889        if self.settings.footnote_backlinks and backrefs:
890            if len(backrefs) == 1:
891                self.context.append('')
892                self.context.append(
893                    '<a class="fn-backref" href="#%s" name="%s">'
894                    % (backrefs[0], node['ids'][0]))
895            else:
896                i = 1
897                for backref in backrefs:
898                    backlinks.append('<a class="fn-backref" href="#%s">%s</a>'
899                                     % (backref, i))
900                    i += 1
901                self.context.append('<em>(%s)</em> ' % ', '.join(backlinks))
902                self.context.append('<a name="%s">' % node['ids'][0])
903        else:
904            self.context.append('')
905            self.context.append('<a name="%s">' % node['ids'][0])
906        # If the node does not only consist of a label.
907        if len(node) > 1:
908            # If there are preceding backlinks, we do not set class
909            # 'first', because we need to retain the top-margin.
910            if not backlinks:
911                node[1]['classes'].append('first')
912            node[-1]['classes'].append('last')
913
914    def depart_footnote(self, node):
915        self.body.append('</td></tr>\n'
916                         '</tbody>\n</table>\n')
917
918    def visit_footnote_reference(self, node):
919        href = '#' + node['refid']
920        format = self.settings.footnote_references
921        if format == 'brackets':
922            suffix = '['
923            self.context.append(']')
924        else:
925            assert format == 'superscript'
926            suffix = '<sup>'
927            self.context.append('</sup>')
928        self.body.append(self.starttag(node, 'a', suffix,
929                                       CLASS='footnote-reference', href=href))
930
931    def depart_footnote_reference(self, node):
932        self.body.append(self.context.pop() + '</a>')
933
934    def visit_generated(self, node):
935        pass
936
937    def depart_generated(self, node):
938        pass
939
940    def visit_header(self, node):
941        self.context.append(len(self.body))
942
943    def depart_header(self, node):
944        start = self.context.pop()
945        header = [self.starttag(node, 'div', CLASS='header')]
946        header.extend(self.body[start:])
947        header.append('\n<hr class="header"/>\n</div>\n')
948        self.body_prefix.extend(header)
949        self.header.extend(header)
950        del self.body[start:]
951
952    def visit_hint(self, node):
953        self.visit_admonition(node, 'hint')
954
955    def depart_hint(self, node):
956        self.depart_admonition()
957
958    def visit_image(self, node):
959        atts = {}
960        atts['src'] = node['uri']
961        if node.has_key('width'):
962            atts['width'] = node['width']
963        if node.has_key('height'):
964            atts['height'] = node['height']
965        if node.has_key('scale'):
966            if Image and not (node.has_key('width')
967                              and node.has_key('height')):
968                try:
969                    im = Image.open(str(atts['src']))
970                except (IOError, # Source image can't be found or opened
971                        UnicodeError):  # PIL doesn't like Unicode paths.
972                    pass
973                else:
974                    if not atts.has_key('width'):
975                        atts['width'] = str(im.size[0])
976                    if not atts.has_key('height'):
977                        atts['height'] = str(im.size[1])
978                    del im
979            for att_name in 'width', 'height':
980                if atts.has_key(att_name):
981                    match = re.match(r'([0-9.]+)(\S*)$', atts[att_name])
982                    assert match
983                    atts[att_name] = '%s%s' % (
984                        float(match.group(1)) * (float(node['scale']) / 100),
985                        match.group(2))
986        style = []
987        for att_name in 'width', 'height':
988            if atts.has_key(att_name):
989                if re.match(r'^[0-9.]+$', atts[att_name]):
990                    # Interpret unitless values as pixels.
991                    atts[att_name] += 'px'
992                style.append('%s: %s;' % (att_name, atts[att_name]))
993                del atts[att_name]
994        if style:
995            atts['style'] = ' '.join(style)
996        atts['alt'] = node.get('alt', atts['src'])
997        if (isinstance(node.parent, nodes.TextElement) or
998            (isinstance(node.parent, nodes.reference) and
999             not isinstance(node.parent.parent, nodes.TextElement))):
1000            # Inline context or surrounded by <a>...</a>.
1001            suffix = ''
1002        else:
1003            suffix = '\n'
1004        if node.has_key('align'):
1005            if node['align'] == 'center':
1006                # "align" attribute is set in surrounding "div" element.
1007                self.body.append('<div align="center" class="align-center">')
1008                self.context.append('</div>\n')
1009                suffix = ''
1010            else:
1011                # "align" attribute is set in "img" element.
1012                atts['align'] = node['align']
1013                self.context.append('')
1014            atts['class'] = 'align-%s' % node['align']
1015        else:
1016            self.context.append('')
1017        self.body.append(self.emptytag(node, 'img', suffix, **atts))
1018
1019    def depart_image(self, node):
1020        self.body.append(self.context.pop())
1021
1022    def visit_important(self, node):
1023        self.visit_admonition(node, 'important')
1024
1025    def depart_important(self, node):
1026        self.depart_admonition()
1027
1028    def visit_inline(self, node):
1029        self.body.append(self.starttag(node, 'span', ''))
1030
1031    def depart_inline(self, node):
1032        self.body.append('</span>')
1033
1034    def visit_label(self, node):
1035        self.body.append(self.starttag(node, 'td', '%s[' % self.context.pop(),
1036                                       CLASS='label'))
1037
1038    def depart_label(self, node):
1039        self.body.append(']</a></td><td>%s' % self.context.pop())
1040
1041    def visit_legend(self, node):
1042        self.body.append(self.starttag(node, 'div', CLASS='legend'))
1043
1044    def depart_legend(self, node):
1045        self.body.append('</div>\n')
1046
1047    def visit_line(self, node):
1048        self.body.append(self.starttag(node, 'div', suffix='', CLASS='line'))
1049        if not len(node):
1050            self.body.append('<br />')
1051
1052    def depart_line(self, node):
1053        self.body.append('</div>\n')
1054
1055    def visit_line_block(self, node):
1056        self.body.append(self.starttag(node, 'div', CLASS='line-block'))
1057
1058    def depart_line_block(self, node):
1059        self.body.append('</div>\n')
1060
1061    def visit_list_item(self, node):
1062        self.body.append(self.starttag(node, 'li', ''))
1063        if len(node):
1064            node[0]['classes'].append('first')
1065
1066    def depart_list_item(self, node):
1067        self.body.append('</li>\n')
1068
1069    def visit_literal(self, node):
1070        """Process text to prevent tokens from wrapping."""
1071        self.body.append(
1072            self.starttag(node, 'tt', '', CLASS='docutils literal'))
1073        text = node.astext()
1074        for token in self.words_and_spaces.findall(text):
1075            if token.strip():
1076                # Protect text like "--an-option" from bad line wrapping:
1077                self.body.append('<span class="pre">%s</span>'
1078                                 % self.encode(token))
1079            elif token in ('\n', ' '):
1080                # Allow breaks at whitespace:
1081                self.body.append(token)
1082            else:
1083                # Protect runs of multiple spaces; the last space can wrap:
1084                self.body.append('&nbsp;' * (len(token) - 1) + ' ')
1085        self.body.append('</tt>')
1086        # Content already processed:
1087        raise nodes.SkipNode
1088
1089    def visit_literal_block(self, node):
1090        self.body.append(self.starttag(node, 'pre', CLASS='literal-block'))
1091
1092    def depart_literal_block(self, node):
1093        self.body.append('\n</pre>\n')
1094
1095    def visit_meta(self, node):
1096        meta = self.emptytag(node, 'meta', **node.non_default_attributes())
1097        self.add_meta(meta)
1098
1099    def depart_meta(self, node):
1100        pass
1101
1102    def add_meta(self, tag):
1103        self.meta.append(tag)
1104        self.head.append(tag)
1105
1106    def visit_note(self, node):
1107        self.visit_admonition(node, 'note')
1108
1109    def depart_note(self, node):
1110        self.depart_admonition()
1111
1112    def visit_option(self, node):
1113        if self.context[-1]:
1114            self.body.append(', ')
1115        self.body.append(self.starttag(node, 'span', '', CLASS='option'))
1116
1117    def depart_option(self, node):
1118        self.body.append('</span>')
1119        self.context[-1] += 1
1120
1121    def visit_option_argument(self, node):
1122        self.body.append(node.get('delimiter', ' '))
1123        self.body.append(self.starttag(node, 'var', ''))
1124
1125    def depart_option_argument(self, node):
1126        self.body.append('</var>')
1127
1128    def visit_option_group(self, node):
1129        atts = {}
1130        if ( self.settings.option_limit
1131             and len(node.astext()) > self.settings.option_limit):
1132            atts['colspan'] = 2
1133            self.context.append('</tr>\n<tr><td>&nbsp;</td>')
1134        else:
1135            self.context.append('')
1136        self.body.append(
1137            self.starttag(node, 'td', CLASS='option-group', **atts))
1138        self.body.append('<kbd>')
1139        self.context.append(0)          # count number of options
1140
1141    def depart_option_group(self, node):
1142        self.context.pop()
1143        self.body.append('</kbd></td>\n')
1144        self.body.append(self.context.pop())
1145
1146    def visit_option_list(self, node):
1147        self.body.append(
1148              self.starttag(node, 'table', CLASS='docutils option-list',
1149                            frame="void", rules="none"))
1150        self.body.append('<col class="option" />\n'
1151                         '<col class="description" />\n'
1152                         '<tbody valign="top">\n')
1153
1154    def depart_option_list(self, node):
1155        self.body.append('</tbody>\n</table>\n')
1156
1157    def visit_option_list_item(self, node):
1158        self.body.append(self.starttag(node, 'tr', ''))
1159
1160    def depart_option_list_item(self, node):
1161        self.body.append('</tr>\n')
1162
1163    def visit_option_string(self, node):
1164        pass
1165
1166    def depart_option_string(self, node):
1167        pass
1168
1169    def visit_organization(self, node):
1170        self.visit_docinfo_item(node, 'organization')
1171
1172    def depart_organization(self, node):
1173        self.depart_docinfo_item()
1174
1175    def should_be_compact_paragraph(self, node):
1176        """
1177        Determine if the <p> tags around paragraph ``node`` can be omitted.
1178        """
1179        if (isinstance(node.parent, nodes.document) or
1180            isinstance(node.parent, nodes.compound)):
1181            # Never compact paragraphs in document or compound.
1182            return 0
1183        for key, value in node.attlist():
1184            if (node.is_not_default(key) and
1185                not (key == 'classes' and value in
1186                     ([], ['first'], ['last'], ['first', 'last']))):
1187                # Attribute which needs to survive.
1188                return 0
1189        first = isinstance(node.parent[0], nodes.label) # skip label
1190        for child in node.parent.children[first:]:
1191            # only first paragraph can be compact
1192            if isinstance(child, nodes.Invisible):
1193                continue
1194            if child is node:
1195                break
1196            return 0
1197        if ( self.compact_simple
1198             or self.compact_field_list
1199             or (self.compact_p
1200                 and (len(node.parent) == 1
1201                      or len(node.parent) == 2
1202                      and isinstance(node.parent[0], nodes.label)))):
1203            return 1
1204        return 0
1205
1206    def visit_paragraph(self, node):
1207        if self.should_be_compact_paragraph(node):
1208            self.context.append('')
1209        else:
1210            self.body.append(self.starttag(node, 'p', ''))
1211            self.context.append('</p>\n')
1212
1213    def depart_paragraph(self, node):
1214        self.body.append(self.context.pop())
1215
1216    def visit_problematic(self, node):
1217        if node.hasattr('refid'):
1218            self.body.append('<a href="#%s" name="%s">' % (node['refid'],
1219                                                           node['ids'][0]))
1220            self.context.append('</a>')
1221        else:
1222            self.context.append('')
1223        self.body.append(self.starttag(node, 'span', '', CLASS='problematic'))
1224
1225    def depart_problematic(self, node):
1226        self.body.append('</span>')
1227        self.body.append(self.context.pop())
1228
1229    def visit_raw(self, node):
1230        if 'html' in node.get('format', '').split():
1231            t = isinstance(node.parent, nodes.TextElement) and 'span' or 'div'
1232            if node['classes']:
1233                self.body.append(self.starttag(node, t, suffix=''))
1234            self.body.append(node.astext())
1235            if node['classes']:
1236                self.body.append('</%s>' % t)
1237        # Keep non-HTML raw text out of output:
1238        raise nodes.SkipNode
1239
1240    def visit_reference(self, node):
1241        if node.has_key('refuri'):
1242            href = node['refuri']
1243            if ( self.settings.cloak_email_addresses
1244                 and href.startswith('mailto:')):
1245                href = self.cloak_mailto(href)
1246                self.in_mailto = 1
1247        else:
1248            assert node.has_key('refid'), \
1249                   'References must have "refuri" or "refid" attribute.'
1250            href = '#' + node['refid']
1251        atts = {'href': href, 'class': 'reference'}
1252        if not isinstance(node.parent, nodes.TextElement):
1253            assert len(node) == 1 and isinstance(node[0], nodes.image)
1254            atts['class'] += ' image-reference'
1255        self.body.append(self.starttag(node, 'a', '', **atts))
1256
1257    def depart_reference(self, node):
1258        self.body.append('</a>')
1259        if not isinstance(node.parent, nodes.TextElement):
1260            self.body.append('\n')
1261        self.in_mailto = 0
1262
1263    def visit_revision(self, node):
1264        self.visit_docinfo_item(node, 'revision', meta=None)
1265
1266    def depart_revision(self, node):
1267        self.depart_docinfo_item()
1268
1269    def visit_row(self, node):
1270        self.body.append(self.starttag(node, 'tr', ''))
1271        node.column = 0
1272
1273    def depart_row(self, node):
1274        self.body.append('</tr>\n')
1275
1276    def visit_rubric(self, node):
1277        self.body.append(self.starttag(node, 'p', '', CLASS='rubric'))
1278
1279    def depart_rubric(self, node):
1280        self.body.append('</p>\n')
1281
1282    def visit_section(self, node):
1283        self.section_level += 1
1284        self.body.append(
1285            self.start_tag_with_title(node, 'div', CLASS='section'))
1286
1287    def depart_section(self, node):
1288        self.section_level -= 1
1289        self.body.append('</div>\n')
1290
1291    def visit_sidebar(self, node):
1292        self.body.append(
1293            self.start_tag_with_title(node, 'div', CLASS='sidebar'))
1294        self.set_first_last(node)
1295        self.in_sidebar = 1
1296
1297    def depart_sidebar(self, node):
1298        self.body.append('</div>\n')
1299        self.in_sidebar = None
1300
1301    def visit_status(self, node):
1302        self.visit_docinfo_item(node, 'status', meta=None)
1303
1304    def depart_status(self, node):
1305        self.depart_docinfo_item()
1306
1307    def visit_strong(self, node):
1308        self.body.append('<strong>')
1309
1310    def depart_strong(self, node):
1311        self.body.append('</strong>')
1312
1313    def visit_subscript(self, node):
1314        self.body.append(self.starttag(node, 'sub', ''))
1315
1316    def depart_subscript(self, node):
1317        self.body.append('</sub>')
1318
1319    def visit_substitution_definition(self, node):
1320        """Internal only."""
1321        raise nodes.SkipNode
1322
1323    def visit_substitution_reference(self, node):
1324        self.unimplemented_visit(node)
1325
1326    def visit_subtitle(self, node):
1327        if isinstance(node.parent, nodes.sidebar):
1328            self.body.append(self.starttag(node, 'p', '',
1329                                           CLASS='sidebar-subtitle'))
1330            self.context.append('</p>\n')
1331        elif isinstance(node.parent, nodes.document):
1332            self.body.append(self.starttag(node, 'h2', '', CLASS='subtitle'))
1333            self.context.append('</h2>\n')
1334            self.in_document_title = len(self.body)
1335        elif isinstance(node.parent, nodes.section):
1336            tag = 'h%s' % (self.section_level + self.initial_header_level - 1)
1337            self.body.append(
1338                self.starttag(node, tag, '', CLASS='section-subtitle') +
1339                self.starttag({}, 'span', '', CLASS='section-subtitle'))
1340            self.context.append('</span></%s>\n' % tag)
1341
1342    def depart_subtitle(self, node):
1343        self.body.append(self.context.pop())
1344        if self.in_document_title:
1345            self.subtitle = self.body[self.in_document_title:-1]
1346            self.in_document_title = 0
1347            self.body_pre_docinfo.extend(self.body)
1348            self.html_subtitle.extend(self.body)
1349            del self.body[:]
1350
1351    def visit_superscript(self, node):
1352        self.body.append(self.starttag(node, 'sup', ''))
1353
1354    def depart_superscript(self, node):
1355        self.body.append('</sup>')
1356
1357    def visit_system_message(self, node):
1358        self.body.append(self.starttag(node, 'div', CLASS='system-message'))
1359        self.body.append('<p class="system-message-title">')
1360        attr = {}
1361        backref_text = ''
1362        if node['ids']:
1363            attr['name'] = node['ids'][0]
1364        if len(node['backrefs']):
1365            backrefs = node['backrefs']
1366            if len(backrefs) == 1:
1367                backref_text = ('; <em><a href="#%s">backlink</a></em>'
1368                                % backrefs[0])
1369            else:
1370                i = 1
1371                backlinks = []
1372                for backref in backrefs:
1373                    backlinks.append('<a href="#%s">%s</a>' % (backref, i))
1374                    i += 1
1375                backref_text = ('; <em>backlinks: %s</em>'
1376                                % ', '.join(backlinks))
1377        if node.hasattr('line'):
1378            line = ', line %s' % node['line']
1379        else:
1380            line = ''
1381        if attr:
1382            a_start = self.starttag({}, 'a', '', **attr)
1383            a_end = '</a>'
1384        else:
1385            a_start = a_end = ''
1386        self.body.append('System Message: %s%s/%s%s '
1387                         '(<tt class="docutils">%s</tt>%s)%s</p>\n'
1388                         % (a_start, node['type'], node['level'], a_end,
1389                            self.encode(node['source']), line, backref_text))
1390
1391    def depart_system_message(self, node):
1392        self.body.append('</div>\n')
1393
1394    def visit_table(self, node):
1395        self.body.append(
1396            self.starttag(node, 'table', CLASS='docutils', border="1"))
1397
1398    def depart_table(self, node):
1399        self.body.append('</table>\n')
1400
1401    def visit_target(self, node):
1402        if not (node.has_key('refuri') or node.has_key('refid')
1403                or node.has_key('refname')):
1404            self.body.append(self.starttag(node, 'span', '', CLASS='target'))
1405            self.context.append('</span>')
1406        else:
1407            self.context.append('')
1408
1409    def depart_target(self, node):
1410        self.body.append(self.context.pop())
1411
1412    def visit_tbody(self, node):
1413        self.write_colspecs()
1414        self.body.append(self.context.pop()) # '</colgroup>\n' or ''
1415        self.body.append(self.starttag(node, 'tbody', valign='top'))
1416
1417    def depart_tbody(self, node):
1418        self.body.append('</tbody>\n')
1419
1420    def visit_term(self, node):
1421        self.body.append(self.starttag(node, 'dt', ''))
1422
1423    def depart_term(self, node):
1424        """
1425        Leave the end tag to `self.visit_definition()`, in case there's a
1426        classifier.
1427        """
1428        pass
1429
1430    def visit_tgroup(self, node):
1431        # Mozilla needs <colgroup>:
1432        self.body.append(self.starttag(node, 'colgroup'))
1433        # Appended by thead or tbody:
1434        self.context.append('</colgroup>\n')
1435        node.stubs = []
1436
1437    def depart_tgroup(self, node):
1438        pass
1439
1440    def visit_thead(self, node):
1441        self.write_colspecs()
1442        self.body.append(self.context.pop()) # '</colgroup>\n'
1443        # There may or may not be a <thead>; this is for <tbody> to use:
1444        self.context.append('')
1445        self.body.append(self.starttag(node, 'thead', valign='bottom'))
1446
1447    def depart_thead(self, node):
1448        self.body.append('</thead>\n')
1449
1450    def visit_tip(self, node):
1451        self.visit_admonition(node, 'tip')
1452
1453    def depart_tip(self, node):
1454        self.depart_admonition()
1455
1456    def visit_title(self, node, move_ids=1):
1457        """Only 6 section levels are supported by HTML."""
1458        check_id = 0
1459        close_tag = '</p>\n'
1460        if isinstance(node.parent, nodes.topic):
1461            self.body.append(
1462                  self.starttag(node, 'p', '', CLASS='topic-title first'))
1463            check_id = 1
1464        elif isinstance(node.parent, nodes.sidebar):
1465            self.body.append(
1466                  self.starttag(node, 'p', '', CLASS='sidebar-title'))
1467            check_id = 1
1468        elif isinstance(node.parent, nodes.Admonition):
1469            self.body.append(
1470                  self.starttag(node, 'p', '', CLASS='admonition-title'))
1471            check_id = 1
1472        elif isinstance(node.parent, nodes.table):
1473            self.body.append(
1474                  self.starttag(node, 'caption', ''))
1475            check_id = 1
1476            close_tag = '</caption>\n'
1477        elif isinstance(node.parent, nodes.document):
1478            self.body.append(self.starttag(node, 'h1', '', CLASS='title'))
1479            self.context.append('</h1>\n')
1480            self.in_document_title = len(self.body)
1481        else:
1482            assert isinstance(node.parent, nodes.section)
1483            h_level = self.section_level + self.initial_header_level - 1
1484            atts = {}
1485            if (len(node.parent) >= 2 and
1486                isinstance(node.parent[1], nodes.subtitle)):
1487                atts['CLASS'] = 'with-subtitle'
1488            self.body.append(
1489                  self.starttag(node, 'h%s' % h_level, '', **atts))
1490            atts = {}
1491            # !!! conditional to be removed in Docutils 0.5:
1492            if move_ids:
1493                if node.parent['ids']:
1494                    atts['ids'] = node.parent['ids']
1495            if node.hasattr('refid'):
1496                atts['class'] = 'toc-backref'
1497                atts['href'] = '#' + node['refid']
1498            if atts:
1499                self.body.append(self.starttag({}, 'a', '', **atts))
1500                self.context.append('</a></h%s>\n' % (h_level))
1501            else:
1502                self.context.append('</h%s>\n' % (h_level))
1503        # !!! conditional to be removed in Docutils 0.5:
1504        if check_id:
1505            if node.parent['ids']:
1506                atts={'ids': node.parent['ids']}
1507                self.body.append(
1508                    self.starttag({}, 'a', '', **atts))
1509                self.context.append('</a>' + close_tag)
1510            else:
1511                self.context.append(close_tag)
1512
1513    def depart_title(self, node):
1514        self.body.append(self.context.pop())
1515        if self.in_document_title:
1516            self.title = self.body[self.in_document_title:-1]
1517            self.in_document_title = 0
1518            self.body_pre_docinfo.extend(self.body)
1519            self.html_title.extend(self.body)
1520            del self.body[:]
1521
1522    def visit_title_reference(self, node):
1523        self.body.append(self.starttag(node, 'cite', ''))
1524
1525    def depart_title_reference(self, node):
1526        self.body.append('</cite>')
1527
1528    def visit_topic(self, node):
1529        self.body.append(self.start_tag_with_title(node, 'div', CLASS='topic'))
1530        self.topic_classes = node['classes']
1531
1532    def depart_topic(self, node):
1533        self.body.append('</div>\n')
1534        self.topic_classes = []
1535
1536    def visit_transition(self, node):
1537        self.body.append(self.emptytag(node, 'hr', CLASS='docutils'))
1538
1539    def depart_transition(self, node):
1540        pass
1541
1542    def visit_version(self, node):
1543        self.visit_docinfo_item(node, 'version', meta=None)
1544
1545    def depart_version(self, node):
1546        self.depart_docinfo_item()
1547
1548    def visit_warning(self, node):
1549        self.visit_admonition(node, 'warning')
1550
1551    def depart_warning(self, node):
1552        self.depart_admonition()
1553
1554    def unimplemented_visit(self, node):
1555        raise NotImplementedError('visiting unimplemented node type: %s'
1556                                  % node.__class__.__name__)
1557
1558
1559class SimpleListChecker(nodes.GenericNodeVisitor):
1560
1561    """
1562    Raise `nodes.NodeFound` if non-simple list item is encountered.
1563
1564    Here "simple" means a list item containing nothing other than a single
1565    paragraph, a simple list, or a paragraph followed by a simple list.
1566    """
1567
1568    def default_visit(self, node):
1569        raise nodes.NodeFound
1570
1571    def visit_bullet_list(self, node):
1572        pass
1573
1574    def visit_enumerated_list(self, node):
1575        pass
1576
1577    def visit_list_item(self, node):
1578        children = []
1579        for child in node.children:
1580            if not isinstance(child, nodes.Invisible):
1581                children.append(child)
1582        if (children and isinstance(children[0], nodes.paragraph)
1583            and (isinstance(children[-1], nodes.bullet_list)
1584                 or isinstance(children[-1], nodes.enumerated_list))):
1585            children.pop()
1586        if len(children) <= 1:
1587            return
1588        else:
1589            raise nodes.NodeFound
1590
1591    def visit_paragraph(self, node):
1592        raise nodes.SkipNode
1593
1594    def invisible_visit(self, node):
1595        """Invisible nodes should be ignored."""
1596        raise nodes.SkipNode
1597
1598    visit_comment = invisible_visit
1599    visit_substitution_definition = invisible_visit
1600    visit_target = invisible_visit
1601    visit_pending = invisible_visit
Note: リポジトリブラウザについてのヘルプは TracBrowser を参照してください。