Fix #338: re.sub() flag argument at wrong position.
[docutils.git] / sandbox / pobrien / WriterTemplate.py
blob868c8434a111e8d38640fb3dc2cc2e3f2ef2d8c6
1 """Template for creating a new writer."""
3 __author__ = "Patrick K. O'Brien <pobrien@orbtech.com>"
4 __cvsid__ = "$Id$"
5 __revision__ = "$Revision$"[11:-2]
8 __docformat__ = 'reStructuredText'
11 import sys
12 import os
13 import time
14 import re
15 from types import ListType
17 import docutils
18 from docutils import nodes, utils, writers, languages
21 class Writer(writers.Writer):
23 supported = ('SomeFormat')
24 """Formats this writer supports."""
26 output = None
27 """Final translated form of `document`."""
29 def __init__(self):
30 writers.Writer.__init__(self)
31 self.translator_class = Translator
33 def translate(self):
34 visitor = self.translator_class(self.document)
35 self.document.walkabout(visitor)
36 self.output = visitor.astext()
39 class Translator(nodes.NodeVisitor):
40 """Modify this to suite your needs."""
42 words_and_spaces = re.compile(r'\S+| +|\n')
44 def __init__(self, document):
45 nodes.NodeVisitor.__init__(self, document)
46 self.settings = settings = document.settings
47 lcode = settings.language_code
48 self.language = languages.get_language(lcode)
49 self.head = []
50 self.body = []
51 self.foot = []
52 self.section_level = 0
53 self.context = []
54 self.topic_class = ''
55 self.colspecs = []
56 self.compact_p = 1
57 self.compact_simple = None
58 self.in_docinfo = None
60 def astext(self):
61 """Return the final formatted document as a string."""
62 raise NotImplementedError
63 return ''.join(self.head + self.body + self.foot)
65 def visit_Text(self, node):
66 raise NotImplementedError, node.astext()
67 self.body.append(node.astext())
69 def depart_Text(self, node):
70 pass
72 def visit_address(self, node):
73 raise NotImplementedError, node.astext()
74 self.visit_docinfo_item(node, 'address', meta=None)
76 def depart_address(self, node):
77 self.depart_docinfo_item()
79 def visit_admonition(self, node, name):
80 raise NotImplementedError, node.astext()
81 self.body.append(self.starttag(node, 'div', CLASS=name))
82 self.body.append('<p class="admonition-title">'
83 + self.language.labels[name] + '</p>\n')
85 def depart_admonition(self):
86 raise NotImplementedError, node.astext()
87 self.body.append('</div>\n')
89 def visit_attention(self, node):
90 self.visit_admonition(node, 'attention')
92 def depart_attention(self, node):
93 self.depart_admonition()
95 def visit_author(self, node):
96 raise NotImplementedError, node.astext()
97 self.visit_docinfo_item(node, 'author')
99 def depart_author(self, node):
100 self.depart_docinfo_item()
102 def visit_authors(self, node):
103 pass
105 def depart_authors(self, node):
106 pass
108 def visit_block_quote(self, node):
109 raise NotImplementedError, node.astext()
110 self.body.append(self.starttag(node, 'blockquote'))
112 def depart_block_quote(self, node):
113 raise NotImplementedError, node.astext()
114 self.body.append('</blockquote>\n')
116 def check_simple_list(self, node):
117 raise NotImplementedError, node.astext()
118 """Check for a simple list that can be rendered compactly."""
119 visitor = SimpleListChecker(self.document)
120 try:
121 node.walk(visitor)
122 except nodes.NodeFound:
123 return None
124 else:
125 return 1
127 def visit_bullet_list(self, node):
128 raise NotImplementedError, node.astext()
129 atts = {}
130 old_compact_simple = self.compact_simple
131 self.context.append((self.compact_simple, self.compact_p))
132 self.compact_p = None
133 self.compact_simple = (self.settings.compact_lists and
134 (self.compact_simple
135 or self.topic_class == 'contents'
136 or self.check_simple_list(node)))
137 if self.compact_simple and not old_compact_simple:
138 atts['class'] = 'simple'
139 self.body.append(self.starttag(node, 'ul', **atts))
141 def depart_bullet_list(self, node):
142 raise NotImplementedError, node.astext()
143 self.compact_simple, self.compact_p = self.context.pop()
144 self.body.append('</ul>\n')
146 def visit_caption(self, node):
147 raise NotImplementedError, node.astext()
148 self.body.append(self.starttag(node, 'p', '', CLASS='caption'))
150 def depart_caption(self, node):
151 raise NotImplementedError, node.astext()
152 self.body.append('</p>\n')
154 def visit_caution(self, node):
155 self.visit_admonition(node, 'caution')
157 def depart_caution(self, node):
158 self.depart_admonition()
160 def visit_citation(self, node):
161 raise NotImplementedError, node.astext()
162 self.body.append(self.starttag(node, 'table', CLASS='citation',
163 frame="void", rules="none"))
164 self.body.append('<colgroup><col class="label" /><col /></colgroup>\n'
165 '<col />\n'
166 '<tbody valign="top">\n'
167 '<tr>')
168 self.footnote_backrefs(node)
170 def depart_citation(self, node):
171 raise NotImplementedError, node.astext()
172 self.body.append('</td></tr>\n'
173 '</tbody>\n</table>\n')
175 def visit_citation_reference(self, node):
176 raise NotImplementedError, node.astext()
177 href = ''
178 if node.has_key('refid'):
179 href = '#' + node['refid']
180 elif node.has_key('refname'):
181 href = '#' + self.document.nameids[node['refname']]
182 self.body.append(self.starttag(node, 'a', '[', href=href,
183 CLASS='citation-reference'))
185 def depart_citation_reference(self, node):
186 raise NotImplementedError, node.astext()
187 self.body.append(']</a>')
189 def visit_classifier(self, node):
190 raise NotImplementedError, node.astext()
191 self.body.append(' <span class="classifier-delimiter">:</span> ')
192 self.body.append(self.starttag(node, 'span', '', CLASS='classifier'))
194 def depart_classifier(self, node):
195 raise NotImplementedError, node.astext()
196 self.body.append('</span>')
198 def visit_colspec(self, node):
199 self.colspecs.append(node)
201 def depart_colspec(self, node):
202 pass
204 def write_colspecs(self):
205 raise NotImplementedError, node.astext()
206 width = 0
207 for node in self.colspecs:
208 width += node['colwidth']
209 for node in self.colspecs:
210 colwidth = int(node['colwidth'] * 100.0 / width + 0.5)
211 self.body.append(self.emptytag(node, 'col',
212 width='%i%%' % colwidth))
213 self.colspecs = []
215 def visit_comment(self, node,
216 sub=re.compile('-(?=-)').sub):
217 raise NotImplementedError, node.astext()
218 """Escape double-dashes in comment text."""
219 self.body.append('<!-- %s -->\n' % sub('- ', node.astext()))
220 # Content already processed:
221 raise nodes.SkipNode
223 def visit_contact(self, node):
224 raise NotImplementedError, node.astext()
225 self.visit_docinfo_item(node, 'contact', meta=None)
227 def depart_contact(self, node):
228 self.depart_docinfo_item()
230 def visit_copyright(self, node):
231 raise NotImplementedError, node.astext()
232 self.visit_docinfo_item(node, 'copyright')
234 def depart_copyright(self, node):
235 self.depart_docinfo_item()
237 def visit_danger(self, node):
238 self.visit_admonition(node, 'danger')
240 def depart_danger(self, node):
241 self.depart_admonition()
243 def visit_date(self, node):
244 raise NotImplementedError, node.astext()
245 self.visit_docinfo_item(node, 'date')
247 def depart_date(self, node):
248 self.depart_docinfo_item()
250 def visit_decoration(self, node):
251 pass
253 def depart_decoration(self, node):
254 pass
256 def visit_definition(self, node):
257 raise NotImplementedError, node.astext()
258 self.body.append('</dt>\n')
259 self.body.append(self.starttag(node, 'dd', ''))
260 if len(node):
261 node[0].set_class('first')
262 node[-1].set_class('last')
264 def depart_definition(self, node):
265 raise NotImplementedError, node.astext()
266 self.body.append('</dd>\n')
268 def visit_definition_list(self, node):
269 raise NotImplementedError, node.astext()
270 self.body.append(self.starttag(node, 'dl'))
272 def depart_definition_list(self, node):
273 raise NotImplementedError, node.astext()
274 self.body.append('</dl>\n')
276 def visit_definition_list_item(self, node):
277 pass
279 def depart_definition_list_item(self, node):
280 pass
282 def visit_description(self, node):
283 raise NotImplementedError, node.astext()
284 self.body.append(self.starttag(node, 'td', ''))
285 if len(node):
286 node[0].set_class('first')
287 node[-1].set_class('last')
289 def depart_description(self, node):
290 raise NotImplementedError, node.astext()
291 self.body.append('</td>')
293 def visit_docinfo(self, node):
294 raise NotImplementedError, node.astext()
295 self.context.append(len(self.body))
296 self.body.append(self.starttag(node, 'table', CLASS='docinfo',
297 frame="void", rules="none"))
298 self.body.append('<col class="docinfo-name" />\n'
299 '<col class="docinfo-content" />\n'
300 '<tbody valign="top">\n')
301 self.in_docinfo = 1
303 def depart_docinfo(self, node):
304 raise NotImplementedError, node.astext()
305 self.body.append('</tbody>\n</table>\n')
306 self.in_docinfo = None
307 start = self.context.pop()
308 self.body_pre_docinfo = self.body[:start]
309 self.docinfo = self.body[start:]
310 self.body = []
312 def visit_docinfo_item(self, node, name, meta=1):
313 raise NotImplementedError, node.astext()
314 if meta:
315 self.head.append('<meta name="%s" content="%s" />\n'
316 % (name, self.attval(node.astext())))
317 self.body.append(self.starttag(node, 'tr', ''))
318 self.body.append('<th class="docinfo-name">%s:</th>\n<td>'
319 % self.language.labels[name])
320 if len(node):
321 if isinstance(node[0], nodes.Element):
322 node[0].set_class('first')
323 if isinstance(node[0], nodes.Element):
324 node[-1].set_class('last')
326 def depart_docinfo_item(self):
327 raise NotImplementedError, node.astext()
328 self.body.append('</td></tr>\n')
330 def visit_doctest_block(self, node):
331 raise NotImplementedError, node.astext()
332 self.body.append(self.starttag(node, 'pre', CLASS='doctest-block'))
334 def depart_doctest_block(self, node):
335 raise NotImplementedError, node.astext()
336 self.body.append('\n</pre>\n')
338 def visit_document(self, node):
339 raise NotImplementedError, node.astext()
340 self.body.append(self.starttag(node, 'div', CLASS='document'))
342 def depart_document(self, node):
343 raise NotImplementedError, node.astext()
344 self.body.append('</div>\n')
346 def visit_emphasis(self, node):
347 raise NotImplementedError, node.astext()
348 self.body.append('<em>')
350 def depart_emphasis(self, node):
351 raise NotImplementedError, node.astext()
352 self.body.append('</em>')
354 def visit_entry(self, node):
355 raise NotImplementedError, node.astext()
356 if isinstance(node.parent.parent, nodes.thead):
357 tagname = 'th'
358 else:
359 tagname = 'td'
360 atts = {}
361 if node.has_key('morerows'):
362 atts['rowspan'] = node['morerows'] + 1
363 if node.has_key('morecols'):
364 atts['colspan'] = node['morecols'] + 1
365 self.body.append(self.starttag(node, tagname, '', **atts))
366 self.context.append('</%s>\n' % tagname.lower())
367 if len(node) == 0: # empty cell
368 self.body.append('&nbsp;')
369 else:
370 node[0].set_class('first')
371 node[-1].set_class('last')
373 def depart_entry(self, node):
374 raise NotImplementedError, node.astext()
375 self.body.append(self.context.pop())
377 def visit_enumerated_list(self, node):
378 raise NotImplementedError, node.astext()
380 The 'start' attribute does not conform to HTML 4.01's strict.dtd, but
381 CSS1 doesn't help. CSS2 isn't widely enough supported yet to be
382 usable.
384 atts = {}
385 if node.has_key('start'):
386 atts['start'] = node['start']
387 if node.has_key('enumtype'):
388 atts['class'] = node['enumtype']
389 # @@@ To do: prefix, suffix. How? Change prefix/suffix to a
390 # single "format" attribute? Use CSS2?
391 old_compact_simple = self.compact_simple
392 self.context.append((self.compact_simple, self.compact_p))
393 self.compact_p = None
394 self.compact_simple = (self.settings.compact_lists and
395 (self.compact_simple
396 or self.topic_class == 'contents'
397 or self.check_simple_list(node)))
398 if self.compact_simple and not old_compact_simple:
399 atts['class'] = (atts.get('class', '') + ' simple').strip()
400 self.body.append(self.starttag(node, 'ol', **atts))
402 def depart_enumerated_list(self, node):
403 raise NotImplementedError, node.astext()
404 self.compact_simple, self.compact_p = self.context.pop()
405 self.body.append('</ol>\n')
407 def visit_error(self, node):
408 self.visit_admonition(node, 'error')
410 def depart_error(self, node):
411 self.depart_admonition()
413 def visit_field(self, node):
414 raise NotImplementedError, node.astext()
415 self.body.append(self.starttag(node, 'tr', '', CLASS='field'))
417 def depart_field(self, node):
418 raise NotImplementedError, node.astext()
419 self.body.append('</tr>\n')
421 def visit_field_body(self, node):
422 raise NotImplementedError, node.astext()
423 self.body.append(self.starttag(node, 'td', '', CLASS='field-body'))
424 if len(node):
425 node[0].set_class('first')
426 node[-1].set_class('last')
428 def depart_field_body(self, node):
429 raise NotImplementedError, node.astext()
430 self.body.append('</td>\n')
432 def visit_field_list(self, node):
433 raise NotImplementedError, node.astext()
434 self.body.append(self.starttag(node, 'table', frame='void',
435 rules='none', CLASS='field-list'))
436 self.body.append('<col class="field-name" />\n'
437 '<col class="field-body" />\n'
438 '<tbody valign="top">\n')
440 def depart_field_list(self, node):
441 raise NotImplementedError, node.astext()
442 self.body.append('</tbody>\n</table>\n')
444 def visit_field_name(self, node):
445 raise NotImplementedError, node.astext()
446 atts = {}
447 if self.in_docinfo:
448 atts['class'] = 'docinfo-name'
449 else:
450 atts['class'] = 'field-name'
451 if len(node.astext()) > 14:
452 atts['colspan'] = 2
453 self.context.append('</tr>\n<tr><td>&nbsp;</td>')
454 else:
455 self.context.append('')
456 self.body.append(self.starttag(node, 'th', '', **atts))
458 def depart_field_name(self, node):
459 raise NotImplementedError, node.astext()
460 self.body.append(':</th>')
461 self.body.append(self.context.pop())
463 def visit_figure(self, node):
464 raise NotImplementedError, node.astext()
465 self.body.append(self.starttag(node, 'div', CLASS='figure'))
467 def depart_figure(self, node):
468 raise NotImplementedError, node.astext()
469 self.body.append('</div>\n')
471 def visit_footer(self, node):
472 raise NotImplementedError, node.astext()
473 self.context.append(len(self.body))
475 def depart_footer(self, node):
476 raise NotImplementedError, node.astext()
477 start = self.context.pop()
478 footer = (['<hr class="footer"/>\n',
479 self.starttag(node, 'div', CLASS='footer')]
480 + self.body[start:] + ['</div>\n'])
481 self.body_suffix[:0] = footer
482 del self.body[start:]
484 def visit_footnote(self, node):
485 raise NotImplementedError, node.astext()
486 self.body.append(self.starttag(node, 'table', CLASS='footnote',
487 frame="void", rules="none"))
488 self.body.append('<colgroup><col class="label" /><col /></colgroup>\n'
489 '<tbody valign="top">\n'
490 '<tr>')
491 self.footnote_backrefs(node)
493 def footnote_backrefs(self, node):
494 raise NotImplementedError, node.astext()
495 if self.settings.footnote_backlinks and node.hasattr('backrefs'):
496 backrefs = node['backrefs']
497 if len(backrefs) == 1:
498 self.context.append('')
499 self.context.append('<a class="fn-backref" href="#%s" '
500 'name="%s">' % (backrefs[0], node['id']))
501 else:
502 i = 1
503 backlinks = []
504 for backref in backrefs:
505 backlinks.append('<a class="fn-backref" href="#%s">%s</a>'
506 % (backref, i))
507 i += 1
508 self.context.append('<em>(%s)</em> ' % ', '.join(backlinks))
509 self.context.append('<a name="%s">' % node['id'])
510 else:
511 self.context.append('')
512 self.context.append('<a name="%s">' % node['id'])
514 def depart_footnote(self, node):
515 raise NotImplementedError, node.astext()
516 self.body.append('</td></tr>\n'
517 '</tbody>\n</table>\n')
519 def visit_footnote_reference(self, node):
520 raise NotImplementedError, node.astext()
521 href = ''
522 if node.has_key('refid'):
523 href = '#' + node['refid']
524 elif node.has_key('refname'):
525 href = '#' + self.document.nameids[node['refname']]
526 format = self.settings.footnote_references
527 if format == 'brackets':
528 suffix = '['
529 self.context.append(']')
530 elif format == 'superscript':
531 suffix = '<sup>'
532 self.context.append('</sup>')
533 else: # shouldn't happen
534 suffix = '???'
535 self.content.append('???')
536 self.body.append(self.starttag(node, 'a', suffix, href=href,
537 CLASS='footnote-reference'))
539 def depart_footnote_reference(self, node):
540 raise NotImplementedError, node.astext()
541 self.body.append(self.context.pop() + '</a>')
543 def visit_generated(self, node):
544 pass
546 def depart_generated(self, node):
547 pass
549 def visit_header(self, node):
550 raise NotImplementedError, node.astext()
551 self.context.append(len(self.body))
553 def depart_header(self, node):
554 raise NotImplementedError, node.astext()
555 start = self.context.pop()
556 self.body_prefix.append(self.starttag(node, 'div', CLASS='header'))
557 self.body_prefix.extend(self.body[start:])
558 self.body_prefix.append('<hr />\n</div>\n')
559 del self.body[start:]
561 def visit_hint(self, node):
562 self.visit_admonition(node, 'hint')
564 def depart_hint(self, node):
565 self.depart_admonition()
567 def visit_image(self, node):
568 raise NotImplementedError, node.astext()
569 atts = node.attributes.copy()
570 atts['src'] = atts['uri']
571 del atts['uri']
572 if not atts.has_key('alt'):
573 atts['alt'] = atts['src']
574 if isinstance(node.parent, nodes.TextElement):
575 self.context.append('')
576 else:
577 self.body.append('<p>')
578 self.context.append('</p>\n')
579 self.body.append(self.emptytag(node, 'img', '', **atts))
581 def depart_image(self, node):
582 raise NotImplementedError, node.astext()
583 self.body.append(self.context.pop())
585 def visit_important(self, node):
586 self.visit_admonition(node, 'important')
588 def depart_important(self, node):
589 self.depart_admonition()
591 def visit_label(self, node):
592 raise NotImplementedError, node.astext()
593 self.body.append(self.starttag(node, 'td', '%s[' % self.context.pop(),
594 CLASS='label'))
596 def depart_label(self, node):
597 raise NotImplementedError, node.astext()
598 self.body.append(']</a></td><td>%s' % self.context.pop())
600 def visit_legend(self, node):
601 raise NotImplementedError, node.astext()
602 self.body.append(self.starttag(node, 'div', CLASS='legend'))
604 def depart_legend(self, node):
605 raise NotImplementedError, node.astext()
606 self.body.append('</div>\n')
608 def visit_line_block(self, node):
609 raise NotImplementedError, node.astext()
610 self.body.append(self.starttag(node, 'pre', CLASS='line-block'))
612 def depart_line_block(self, node):
613 raise NotImplementedError, node.astext()
614 self.body.append('\n</pre>\n')
616 def visit_list_item(self, node):
617 raise NotImplementedError, node.astext()
618 self.body.append(self.starttag(node, 'li', ''))
619 if len(node):
620 node[0].set_class('first')
622 def depart_list_item(self, node):
623 raise NotImplementedError, node.astext()
624 self.body.append('</li>\n')
626 def visit_literal(self, node):
627 raise NotImplementedError, node.astext()
628 """Process text to prevent tokens from wrapping."""
629 self.body.append(self.starttag(node, 'tt', '', CLASS='literal'))
630 text = node.astext()
631 for token in self.words_and_spaces.findall(text):
632 if token.strip():
633 # Protect text like "--an-option" from bad line wrapping:
634 self.body.append('<span class="pre">%s</span>'
635 % self.encode(token))
636 elif token in ('\n', ' '):
637 # Allow breaks at whitespace:
638 self.body.append(token)
639 else:
640 # Protect runs of multiple spaces; the last space can wrap:
641 self.body.append('&nbsp;' * (len(token) - 1) + ' ')
642 self.body.append('</tt>')
643 # Content already processed:
644 raise nodes.SkipNode
646 def visit_literal_block(self, node):
647 raise NotImplementedError, node.astext()
648 self.body.append(self.starttag(node, 'pre', CLASS='literal-block'))
650 def depart_literal_block(self, node):
651 raise NotImplementedError, node.astext()
652 self.body.append('\n</pre>\n')
654 def visit_meta(self, node):
655 raise NotImplementedError, node.astext()
656 self.head.append(self.emptytag(node, 'meta', **node.attributes))
658 def depart_meta(self, node):
659 pass
661 def visit_note(self, node):
662 self.visit_admonition(node, 'note')
664 def depart_note(self, node):
665 self.depart_admonition()
667 def visit_option(self, node):
668 raise NotImplementedError, node.astext()
669 if self.context[-1]:
670 self.body.append(', ')
672 def depart_option(self, node):
673 raise NotImplementedError, node.astext()
674 self.context[-1] += 1
676 def visit_option_argument(self, node):
677 raise NotImplementedError, node.astext()
678 self.body.append(node.get('delimiter', ' '))
679 self.body.append(self.starttag(node, 'var', ''))
681 def depart_option_argument(self, node):
682 raise NotImplementedError, node.astext()
683 self.body.append('</var>')
685 def visit_option_group(self, node):
686 raise NotImplementedError, node.astext()
687 atts = {}
688 if len(node.astext()) > 14:
689 atts['colspan'] = 2
690 self.context.append('</tr>\n<tr><td>&nbsp;</td>')
691 else:
692 self.context.append('')
693 self.body.append(self.starttag(node, 'td', **atts))
694 self.body.append('<kbd>')
695 self.context.append(0) # count number of options
697 def depart_option_group(self, node):
698 raise NotImplementedError, node.astext()
699 self.context.pop()
700 self.body.append('</kbd></td>\n')
701 self.body.append(self.context.pop())
703 def visit_option_list(self, node):
704 raise NotImplementedError, node.astext()
705 self.body.append(
706 self.starttag(node, 'table', CLASS='option-list',
707 frame="void", rules="none"))
708 self.body.append('<col class="option" />\n'
709 '<col class="description" />\n'
710 '<tbody valign="top">\n')
712 def depart_option_list(self, node):
713 raise NotImplementedError, node.astext()
714 self.body.append('</tbody>\n</table>\n')
716 def visit_option_list_item(self, node):
717 raise NotImplementedError, node.astext()
718 self.body.append(self.starttag(node, 'tr', ''))
720 def depart_option_list_item(self, node):
721 raise NotImplementedError, node.astext()
722 self.body.append('</tr>\n')
724 def visit_option_string(self, node):
725 raise NotImplementedError, node.astext()
726 self.body.append(self.starttag(node, 'span', '', CLASS='option'))
728 def depart_option_string(self, node):
729 raise NotImplementedError, node.astext()
730 self.body.append('</span>')
732 def visit_organization(self, node):
733 raise NotImplementedError, node.astext()
734 self.visit_docinfo_item(node, 'organization')
736 def depart_organization(self, node):
737 raise NotImplementedError, node.astext()
738 self.depart_docinfo_item()
740 def visit_paragraph(self, node):
741 raise NotImplementedError, node.astext()
742 # Omit <p> tags if this is an only child and optimizable.
743 if (self.compact_simple or
744 self.compact_p and (len(node.parent) == 1 or
745 len(node.parent) == 2 and
746 isinstance(node.parent[0], nodes.label))):
747 self.context.append('')
748 else:
749 self.body.append(self.starttag(node, 'p', ''))
750 self.context.append('</p>\n')
752 def depart_paragraph(self, node):
753 raise NotImplementedError, node.astext()
754 self.body.append(self.context.pop())
756 def visit_problematic(self, node):
757 raise NotImplementedError, node.astext()
758 if node.hasattr('refid'):
759 self.body.append('<a href="#%s" name="%s">' % (node['refid'],
760 node['id']))
761 self.context.append('</a>')
762 else:
763 self.context.append('')
764 self.body.append(self.starttag(node, 'span', '', CLASS='problematic'))
766 def depart_problematic(self, node):
767 raise NotImplementedError, node.astext()
768 self.body.append('</span>')
769 self.body.append(self.context.pop())
771 def visit_raw(self, node):
772 raise NotImplementedError, node.astext()
773 if node.get('format') == 'html':
774 self.body.append(node.astext())
775 # Keep non-HTML raw text out of output:
776 raise nodes.SkipNode
778 def visit_reference(self, node):
779 raise NotImplementedError, node.astext()
780 if node.has_key('refuri'):
781 href = node['refuri']
782 elif node.has_key('refid'):
783 href = '#' + node['refid']
784 elif node.has_key('refname'):
785 href = '#' + self.document.nameids[node['refname']]
786 self.body.append(self.starttag(node, 'a', '', href=href,
787 CLASS='reference'))
789 def depart_reference(self, node):
790 raise NotImplementedError, node.astext()
791 self.body.append('</a>')
793 def visit_revision(self, node):
794 raise NotImplementedError, node.astext()
795 self.visit_docinfo_item(node, 'revision', meta=None)
797 def depart_revision(self, node):
798 self.depart_docinfo_item()
800 def visit_row(self, node):
801 raise NotImplementedError, node.astext()
802 self.body.append(self.starttag(node, 'tr', ''))
804 def depart_row(self, node):
805 raise NotImplementedError, node.astext()
806 self.body.append('</tr>\n')
808 def visit_section(self, node):
809 raise NotImplementedError, node.astext()
810 self.section_level += 1
811 self.body.append(self.starttag(node, 'div', CLASS='section'))
813 def depart_section(self, node):
814 raise NotImplementedError, node.astext()
815 self.section_level -= 1
816 self.body.append('</div>\n')
818 def visit_status(self, node):
819 raise NotImplementedError, node.astext()
820 self.visit_docinfo_item(node, 'status', meta=None)
822 def depart_status(self, node):
823 self.depart_docinfo_item()
825 def visit_strong(self, node):
826 raise NotImplementedError, node.astext()
827 self.body.append('<strong>')
829 def depart_strong(self, node):
830 raise NotImplementedError, node.astext()
831 self.body.append('</strong>')
833 def visit_substitution_definition(self, node):
834 """Internal only."""
835 raise nodes.SkipNode
837 def visit_substitution_reference(self, node):
838 self.unimplemented_visit(node)
840 def visit_subtitle(self, node):
841 raise NotImplementedError, node.astext()
842 self.body.append(self.starttag(node, 'h2', '', CLASS='subtitle'))
844 def depart_subtitle(self, node):
845 raise NotImplementedError, node.astext()
846 self.body.append('</h2>\n')
848 def visit_system_message(self, node):
849 raise NotImplementedError, node.astext()
850 if node['level'] < self.document.reporter['writer'].report_level:
851 # Level is too low to display:
852 raise nodes.SkipNode
853 self.body.append(self.starttag(node, 'div', CLASS='system-message'))
854 self.body.append('<p class="system-message-title">')
855 attr = {}
856 backref_text = ''
857 if node.hasattr('id'):
858 attr['name'] = node['id']
859 if node.hasattr('backrefs'):
860 backrefs = node['backrefs']
861 if len(backrefs) == 1:
862 backref_text = ('; <em><a href="#%s">backlink</a></em>'
863 % backrefs[0])
864 else:
865 i = 1
866 backlinks = []
867 for backref in backrefs:
868 backlinks.append('<a href="#%s">%s</a>' % (backref, i))
869 i += 1
870 backref_text = ('; <em>backlinks: %s</em>'
871 % ', '.join(backlinks))
872 if node.hasattr('line'):
873 line = ', line %s' % node['line']
874 else:
875 line = ''
876 if attr:
877 a_start = self.starttag({}, 'a', '', **attr)
878 a_end = '</a>'
879 else:
880 a_start = a_end = ''
881 self.body.append('System Message: %s%s/%s%s (<tt>%s</tt>%s)%s</p>\n'
882 % (a_start, node['type'], node['level'], a_end,
883 node['source'], line, backref_text))
885 def depart_system_message(self, node):
886 raise NotImplementedError, node.astext()
887 self.body.append('</div>\n')
889 def visit_table(self, node):
890 raise NotImplementedError, node.astext()
891 self.body.append(
892 self.starttag(node, 'table', CLASS="table",
893 frame='border', rules='all'))
895 def depart_table(self, node):
896 raise NotImplementedError, node.astext()
897 self.body.append('</table>\n')
899 def visit_target(self, node):
900 raise NotImplementedError, node.astext()
901 if not (node.has_key('refuri') or node.has_key('refid')
902 or node.has_key('refname')):
903 self.body.append(self.starttag(node, 'a', '', CLASS='target'))
904 self.context.append('</a>')
905 else:
906 self.context.append('')
908 def depart_target(self, node):
909 raise NotImplementedError, node.astext()
910 self.body.append(self.context.pop())
912 def visit_tbody(self, node):
913 raise NotImplementedError, node.astext()
914 self.write_colspecs()
915 self.body.append(self.context.pop()) # '</colgroup>\n' or ''
916 self.body.append(self.starttag(node, 'tbody', valign='top'))
918 def depart_tbody(self, node):
919 raise NotImplementedError, node.astext()
920 self.body.append('</tbody>\n')
922 def visit_term(self, node):
923 raise NotImplementedError, node.astext()
924 self.body.append(self.starttag(node, 'dt', ''))
926 def depart_term(self, node):
928 Leave the end tag to `self.visit_definition()`, in case there's a
929 classifier.
931 raise NotImplementedError, node.astext()
932 pass
934 def visit_tgroup(self, node):
935 raise NotImplementedError, node.astext()
936 # Mozilla needs <colgroup>:
937 self.body.append(self.starttag(node, 'colgroup'))
938 # Appended by thead or tbody:
939 self.context.append('</colgroup>\n')
941 def depart_tgroup(self, node):
942 pass
944 def visit_thead(self, node):
945 raise NotImplementedError, node.astext()
946 self.write_colspecs()
947 self.body.append(self.context.pop()) # '</colgroup>\n'
948 # There may or may not be a <thead>; this is for <tbody> to use:
949 self.context.append('')
950 self.body.append(self.starttag(node, 'thead', valign='bottom'))
952 def depart_thead(self, node):
953 raise NotImplementedError, node.astext()
954 self.body.append('</thead>\n')
956 def visit_tip(self, node):
957 self.visit_admonition(node, 'tip')
959 def depart_tip(self, node):
960 self.depart_admonition()
962 def visit_title(self, node):
963 raise NotImplementedError, node.astext()
964 """Only 6 section levels are supported by HTML."""
965 if isinstance(node.parent, nodes.topic):
966 self.body.append(
967 self.starttag(node, 'p', '', CLASS='topic-title'))
968 if node.parent.hasattr('id'):
969 self.body.append(
970 self.starttag({}, 'a', '', name=node.parent['id']))
971 self.context.append('</a></p>\n')
972 else:
973 self.context.append('</p>\n')
974 elif self.section_level == 0:
975 # document title
976 self.head.append('<title>%s</title>\n'
977 % self.encode(node.astext()))
978 self.body.append(self.starttag(node, 'h1', '', CLASS='title'))
979 self.context.append('</h1>\n')
980 else:
981 self.body.append(
982 self.starttag(node, 'h%s' % self.section_level, ''))
983 atts = {}
984 if node.parent.hasattr('id'):
985 atts['name'] = node.parent['id']
986 if node.hasattr('refid'):
987 atts['class'] = 'toc-backref'
988 atts['href'] = '#' + node['refid']
989 self.body.append(self.starttag({}, 'a', '', **atts))
990 self.context.append('</a></h%s>\n' % (self.section_level))
992 def depart_title(self, node):
993 raise NotImplementedError, node.astext()
994 self.body.append(self.context.pop())
996 def visit_title_reference(self, node):
997 raise NotImplementedError, node.astext()
998 self.body.append(self.starttag(node, 'cite', ''))
1000 def depart_title_reference(self, node):
1001 raise NotImplementedError, node.astext()
1002 self.body.append('</cite>')
1004 def visit_topic(self, node):
1005 raise NotImplementedError, node.astext()
1006 self.body.append(self.starttag(node, 'div', CLASS='topic'))
1007 self.topic_class = node.get('class')
1009 def depart_topic(self, node):
1010 raise NotImplementedError, node.astext()
1011 self.body.append('</div>\n')
1012 self.topic_class = ''
1014 def visit_transition(self, node):
1015 raise NotImplementedError, node.astext()
1016 self.body.append(self.emptytag(node, 'hr'))
1018 def depart_transition(self, node):
1019 pass
1021 def visit_version(self, node):
1022 raise NotImplementedError, node.astext()
1023 self.visit_docinfo_item(node, 'version', meta=None)
1025 def depart_version(self, node):
1026 self.depart_docinfo_item()
1028 def visit_warning(self, node):
1029 self.visit_admonition(node, 'warning')
1031 def depart_warning(self, node):
1032 self.depart_admonition()
1034 def unimplemented_visit(self, node):
1035 raise NotImplementedError('visiting unimplemented node type: %s'
1036 % node.__class__.__name__)
1039 class SimpleListChecker(nodes.GenericNodeVisitor):
1042 Raise `nodes.SkipNode` if non-simple list item is encountered.
1044 Here "simple" means a list item containing nothing other than a single
1045 paragraph, a simple list, or a paragraph followed by a simple list.
1048 def default_visit(self, node):
1049 raise nodes.NodeFound
1051 def visit_bullet_list(self, node):
1052 pass
1054 def visit_enumerated_list(self, node):
1055 pass
1057 def visit_list_item(self, node):
1058 children = []
1059 for child in node.get_children():
1060 if not isinstance(child, nodes.Invisible):
1061 children.append(child)
1062 if (children and isinstance(children[0], nodes.paragraph)
1063 and (isinstance(children[-1], nodes.bullet_list)
1064 or isinstance(children[-1], nodes.enumerated_list))):
1065 children.pop()
1066 if len(children) <= 1:
1067 return
1068 else:
1069 raise nodes.NodeFound
1071 def visit_paragraph(self, node):
1072 raise nodes.SkipNode
1074 def invisible_visit(self, node):
1075 """Invisible nodes should be ignored."""
1076 pass
1078 visit_comment = invisible_visit
1079 visit_substitution_definition = invisible_visit
1080 visit_target = invisible_visit
1081 visit_pending = invisible_visit