~ubuntu-branches/ubuntu/jaunty/python-docutils/jaunty

« back to all changes in this revision

Viewing changes to docutils/writers/html4css1.py

  • Committer: Bazaar Package Importer
  • Author(s): martin f. krafft
  • Date: 2006-07-10 11:45:05 UTC
  • mfrom: (2.1.4 edgy)
  • Revision ID: james.westby@ubuntu.com-20060710114505-otkhqcslevewxmz5
Tags: 0.4-3
Added build dependency on python-central (closes: #377580).

Show diffs side-by-side

added added

removed removed

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