removed obsolete issues (many of them fixed with AE)
[docutils.git] / sandbox / oliverr / docbook / writer / docbook.py
blob1a5a0a68565fd412140a2fa99fb68e51a572f8d4
1 #!/usr/bin/env python
3 """
4 :Author: Ollie Rutherfurd
5 :Contact: oliver@rutherfurd.net
6 :Revision: $Revision$
7 :Date: $Date$
8 :Copyright: This module has been placed in the public domain.
10 DocBook XML document tree Writer.
12 This Writer converts a reST document tree to a subset
13 of DocBook.
15 **This is an unfinished work in progress.**
16 """
18 __docformat__ = 'reStructuredText'
20 import re
21 import string
22 from docutils import writers, nodes, languages
23 from types import ListType
25 class Writer(writers.Writer):
27 settings_spec = (
28 'DocBook-Specific Options',
29 None,
30 (('Set DocBook document type. '
31 'Choices are "article", "book", and "chapter". '
32 'Default is "article".',
33 ['--doctype'],
34 {'default': 'article',
35 'metavar': '<name>',
36 'type': 'choice',
37 'choices': ('article', 'book', 'chapter',)
44 """DocBook does it's own section numbering"""
45 settings_default_overrides = {'enable_section_numbering': 0}
47 output = None
48 """Final translated form of `document`."""
50 def translate(self):
51 visitor = DocBookTranslator(self.document)
52 self.document.walkabout(visitor)
53 self.output = visitor.astext()
56 class DocBookTranslator(nodes.NodeVisitor):
58 XML_DECL = '<?xml version="1.0" encoding="%s"?>\n'
60 DOCTYPE_DECL = """<!DOCTYPE %s
61 PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN"
62 "http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">\n"""
64 def __init__(self, document):
65 nodes.NodeVisitor.__init__(self, document)
66 self.language = languages.get_language(
67 document.settings.language_code)
68 self.doctype = document.settings.doctype
69 self.doc_header = [
70 self.XML_DECL % (document.settings.output_encoding,),
71 self.DOCTYPE_DECL % (self.doctype,),
72 '<%s>\n' % (self.doctype,),
74 self.doc_footer = [
75 '</%s>\n' % (self.doctype,)
77 self.body = []
78 self.section = 0
79 self.context = []
80 self.colnames = []
81 self.footnotes = {}
82 self.footnote_map = {}
83 self.docinfo = []
84 self.title = ''
85 self.subtitle = ''
87 def astext(self):
88 return ''.join(self.doc_header
89 + self.docinfo
90 + self.body
91 + self.doc_footer)
93 def encode(self, text):
94 """Encode special characters in `text` & return."""
95 # @@@ A codec to do these and all other
96 # HTML entities would be nice.
97 text = text.replace("&", "&amp;")
98 text = text.replace("<", "&lt;")
99 text = text.replace('"', "&quot;")
100 text = text.replace(">", "&gt;")
101 return text
103 def encodeattr(self, text):
104 """Encode attributes characters > 128 as &#XXX;"""
105 buff = []
106 for c in text:
107 if ord(c) >= 128:
108 buff.append('&#%d;' % ord(c))
109 else:
110 buff.append(c)
111 return ''.join(buff)
113 def rearrange_footnotes(self):
115 Replaces ``foonote_reference`` placeholders with
116 ``footnote`` element content as DocBook and reST
117 handle footnotes differently.
119 DocBook defines footnotes inline, whereas they
120 may be anywere in reST. This function replaces the
121 first instance of a ``footnote_reference`` with
122 the ``footnote`` element itself, and later
123 references of the same a footnote with
124 ``footnoteref`` elements.
126 for (footnote_id,refs) in self.footnote_map.items():
127 ref_id, context, pos = refs[0]
128 context[pos] = ''.join(self.footnotes[footnote_id])
129 for ref_id, context, pos in refs[1:]:
130 context[pos] = '<footnoteref linkend="%s"/>' \
131 % (footnote_id,)
133 def attval(self, text,
134 transtable=string.maketrans('\n\r\t\v\f', ' ')):
135 """Cleanse, encode, and return attribute value text."""
136 return self.encode(text.translate(transtable))
138 def starttag(self, node, tagname, suffix='\n', infix='', **attributes):
140 Construct and return a start tag given a node
141 (id & class attributes are extracted), tag name,
142 and optional attributes.
144 atts = {}
145 for (name, value) in attributes.items():
146 atts[name.lower()] = value
148 for att in ('id',): # node attribute overrides
149 if node.has_key(att):
150 atts[att] = node[att]
152 attlist = atts.items()
153 attlist.sort()
154 parts = [tagname.lower()]
155 for name, value in attlist:
156 if value is None: # boolean attribute
157 # this came from the html writer, but shouldn't
158 # apply here, as an element with no attribute
159 # isn't well-formed XML.
160 parts.append(name.lower())
161 elif isinstance(value, ListType):
162 values = [str(v) for v in value]
163 parts.append('%s="%s"' % (name.lower(),
164 self.attval(' '.join(values))))
165 else:
166 name = self.encodeattr(name.lower())
167 value = str(self.encodeattr(unicode(value)))
168 value = self.attval(value)
169 parts.append('%s="%s"' % (name,value))
171 return '<%s%s>%s' % (' '.join(parts), infix, suffix)
173 def emptytag(self, node, tagname, suffix='\n', **attributes):
174 """Construct and return an XML-compatible empty tag."""
175 return self.starttag(node, tagname, suffix, infix=' /', **attributes)
177 def visit_Text(self, node):
178 self.body.append(self.encode(node.astext()))
180 def depart_Text(self, node):
181 pass
183 def visit_address(self, node):
184 # handled by visit_docinfo
185 pass
187 def depart_address(self, node):
188 # handled by visit_docinfo
189 pass
191 def visit_admonition(self, node, name=''):
192 self.body.append(self.starttag(node, 'note'))
194 def depart_admonition(self, node=None):
195 self.body.append('</note>\n')
197 def visit_attention(self, node):
198 self.body.append(self.starttag(node, 'note'))
199 self.body.append('\n<title>%s</title>\n'
200 % (self.language.labels[node.tagname],))
202 def depart_attention(self, node):
203 self.body.append('</note>\n')
205 def visit_attribution(self, node):
206 # attribution must precede blockquote content
207 if isinstance(node.parent, nodes.block_quote):
208 raise nodes.SkipNode
209 self.body.append(self.starttag(node, 'attribution', ''))
211 def depart_attribution(self, node):
212 # attribution must precede blockquote content
213 if not isinstance(node.parent, nodes.block_quote):
214 self.body.append('</attribution>\n')
216 # author is handled in ``visit_docinfo()``
217 def visit_author(self, node):
218 raise nodes.SkipNode
220 # authors is handled in ``visit_docinfo()``
221 def visit_authors(self, node):
222 raise nodes.SkipNode
224 def visit_block_quote(self, node):
225 self.body.append(self.starttag(node, 'blockquote'))
226 if isinstance(node[-1], nodes.attribution):
227 self.body.append('<attribution>%s</attribution>\n' % node[-1].astext())
229 def depart_block_quote(self, node):
230 self.body.append('</blockquote>\n')
232 def visit_bullet_list(self, node):
233 self.body.append(self.starttag(node, 'itemizedlist'))
235 def depart_bullet_list(self, node):
236 self.body.append('</itemizedlist>\n')
238 def visit_caption(self, node):
239 # NOTE: ideally, this should probably be stuffed into
240 # the mediaobject as a "caption" element
241 self.body.append(self.starttag(node, 'para'))
243 def depart_caption(self, node):
244 self.body.append('</para>')
246 def visit_caution(self, node):
247 self.body.append(self.starttag(node, 'caution'))
248 self.body.append('\n<title>%s</title>\n'
249 % (self.language.labels[node.tagname],))
251 def depart_caution(self, node):
252 self.body.append('</caution>\n')
254 # reST & DocBook ciations are somewhat
255 # different creatures.
257 # reST seems to handle citations as a labled
258 # footnotes, whereas DocBook doesn't from what
259 # I can tell. In DocBook, it looks like they're
260 # an abbreviation for a published work, which
261 # might be in the bibliography.
263 # Quote:
265 # The content of a Citation is assumed to be a reference
266 # string, perhaps identical to an abbreviation in an entry
267 # in a Bibliography.
269 # I hoped to have citations behave look footnotes,
270 # using the citation label as the footnote label,
271 # which would seem functionally equivlent, however
272 # the DocBook stylesheets for generating HTML & FO
273 # output don't seem to be using the label for foonotes
274 # so this doesn't work very well.
276 # Any ideas or suggestions would be welcome.
278 def visit_citation(self, node):
279 self.visit_footnote(node)
281 def depart_citation(self, node):
282 self.depart_footnote(node)
284 def visit_citation_reference(self, node):
285 self.visit_footnote_reference(node)
287 def depart_citation_reference(self, node):
288 # there isn't a a depart_footnote_reference
289 pass
291 def visit_classifier(self, node):
292 self.body.append(self.starttag(node, 'type'))
294 def depart_classifier(self, node):
295 self.body.append('</type>\n')
297 def visit_colspec(self, node):
298 self.colnames.append('col_%d' % (len(self.colnames) + 1,))
299 atts = {'colname': self.colnames[-1]}
300 self.body.append(self.emptytag(node, 'colspec', **atts))
302 def depart_colspec(self, node):
303 pass
305 def visit_comment(self, node, sub=re.compile('-(?=-)').sub):
306 """Escape double-dashes in comment text."""
307 self.body.append('<!-- %s -->\n' % sub('- ', node.astext()))
308 raise nodes.SkipNode
310 # contact is handled in ``visit_docinfo()``
311 def visit_contact(self, node):
312 raise nodes.SkipNode
314 # copyright is handled in ``visit_docinfo()``
315 def visit_copyright(self, node):
316 raise nodes.SkipNode
318 def visit_danger(self, node):
319 self.body.append(self.starttag(node, 'caution'))
320 self.body.append('\n<title>%s</title>\n'
321 % (self.language.labels[node.tagname],))
323 def depart_danger(self, node):
324 self.body.append('</caution>\n')
326 # date is handled in ``visit_docinfo()``
327 def visit_date(self, node):
328 raise nodes.SkipNode
330 def visit_decoration(self, node):
331 pass
332 def depart_decoration(self, node):
333 pass
335 def visit_definition(self, node):
336 # "term" is not closed in depart_term
337 self.body.append('</term>\n')
338 self.body.append(self.starttag(node, 'listitem'))
340 def depart_definition(self, node):
341 self.body.append('</listitem>\n')
343 def visit_definition_list(self, node):
344 self.body.append(self.starttag(node, 'variablelist'))
346 def depart_definition_list(self, node):
347 self.body.append('</variablelist>\n')
349 def visit_definition_list_item(self, node):
350 self.body.append(self.starttag(node, 'varlistentry'))
352 def depart_definition_list_item(self, node):
353 self.body.append('</varlistentry>\n')
355 def visit_description(self, node):
356 self.body.append(self.starttag(node, 'entry'))
358 def depart_description(self, node):
359 self.body.append('</entry>\n')
361 def visit_docinfo(self, node):
363 Collects all docinfo elements for the document.
365 Since reST's bibliography elements don't map very
366 cleanly to DocBook, rather than maintain state and
367 check dependencies within the different visitor
368 fuctions all processing of bibliography elements
369 is dont within this function.
371 .. NOTE:: Skips processing of all child nodes as
372 everything should be collected here.
375 # XXX There are a number of fields in docinfo elements
376 # which don't map nicely to docbook elements and
377 # reST allows one to insert arbitrary fields into
378 # the header, We need to be able to handle fields
379 # which either don't map or nicely or are unexpected.
380 # I'm thinking of just using DocBook to display these
381 # elements in some sort of tabular format -- but
382 # to collecting them is not straight-forward.
383 # Paragraphs, links, lists, etc... can all live within
384 # the values so we either need a separate visitor
385 # to translate these elements, or to maintain state
386 # in any possible child elements (not something I
387 # want to do).
389 docinfo = ['<%sinfo>\n' % self.doctype]
391 address = ''
392 authors = []
393 author = ''
394 contact = ''
395 date = ''
396 legalnotice = ''
397 orgname = ''
398 releaseinfo = ''
399 revision,version = '',''
401 docinfo.append('<title>%s</title>\n' % self.title)
402 if self.subtitle:
403 docinfo.append('<subtitle>%s</subtitle>\n' % self.subtitle)
405 for n in node:
406 if isinstance(n, nodes.address):
407 address = n.astext()
408 elif isinstance(n, nodes.author):
409 author = n.astext()
410 elif isinstance(n, nodes.authors):
411 for a in n:
412 authors.append(a.astext())
413 elif isinstance(n, nodes.contact):
414 contact = n.astext()
415 elif isinstance(n, nodes.copyright):
416 legalnotice = n.astext()
417 elif isinstance(n, nodes.date):
418 date = n.astext()
419 elif isinstance(n, nodes.organization):
420 orgname = n.astext()
421 elif isinstance(n, nodes.revision):
422 # XXX yuck
423 revision = 'Revision ' + n.astext()
424 elif isinstance(n, nodes.status):
425 releaseinfo = n.astext()
426 elif isinstance(n, nodes.version):
427 # XXX yuck
428 version = 'Version ' + n.astext()
429 elif isinstance(n, nodes.field):
430 # XXX
431 import sys
432 print >> sys.stderr, "I don't do 'field' yet"
433 print n.astext()
434 # since all child nodes are handled here raise an exception
435 # if node is not handled, so it doesn't silently slip through.
436 else:
437 print dir(n)
438 print n.astext()
439 raise self.unimplemented_visit(n)
441 # can only add author if name is present
442 # since contact is associate with author, the contact
443 # can also only be added if an author name is given.
444 if author:
445 docinfo.append('<author>\n')
446 docinfo.append('<othername>%s</othername>\n' % author)
447 if contact:
448 docinfo.append('<email>%s</email>\n' % contact)
449 docinfo.append('</author>\n')
451 if authors:
452 docinfo.append('<authorgroup>\n')
453 for name in authors:
454 docinfo.append(
455 '<author><othername>%s</othername></author>\n' % name)
456 docinfo.append('</authorgroup>\n')
458 if revision or version:
459 edition = version
460 if edition and revision:
461 edition += ', ' + revision
462 elif revision:
463 edition = revision
464 docinfo.append('<edition>%s</edition>\n' % edition)
466 if date:
467 docinfo.append('<date>%s</date>\n' % date)
469 if orgname:
470 docinfo.append('<orgname>%s</orgname>\n' % orgname)
472 if releaseinfo:
473 docinfo.append('<releaseinfo>%s</releaseinfo>\n' % releaseinfo)
475 if legalnotice:
476 docinfo.append('<legalnotice>\n')
477 docinfo.append('<para>%s</para>\n' % legalnotice)
478 docinfo.append('</legalnotice>\n')
480 if address:
481 docinfo.append('<address xml:space="preserve">' +
482 address + '</address>\n')
484 if len(docinfo) > 1:
485 docinfo.append('</%sinfo>\n' % self.doctype)
487 self.docinfo = docinfo
489 raise nodes.SkipChildren
491 def depart_docinfo(self, node):
492 pass
494 def visit_doctest_block(self, node):
495 self.body.append('<informalexample>\n')
496 self.body.append(self.starttag(node, 'programlisting'))
498 def depart_doctest_block(self, node):
499 self.body.append('</programlisting>\n')
500 self.body.append('</informalexample>\n')
502 def visit_document(self, node):
503 pass
505 def depart_document(self, node):
506 self.rearrange_footnotes()
508 def visit_emphasis(self, node):
509 self.body.append('<emphasis>')
511 def depart_emphasis(self, node):
512 self.body.append('</emphasis>')
514 def visit_entry(self, node):
515 tagname = 'entry'
516 atts = {}
517 if node.has_key('morerows'):
518 atts['morerows'] = node['morerows']
519 if node.has_key('morecols'):
520 atts['namest'] = self.colnames[self.entry_level]
521 atts['nameend'] = self.colnames[self.entry_level \
522 + node['morecols']]
523 self.entry_level += 1 # for tracking what namest and nameend are
524 self.body.append(self.starttag(node, tagname, '', **atts))
526 def depart_entry(self, node):
527 self.body.append('</entry>\n')
529 def visit_enumerated_list(self, node):
530 # TODO: need to specify "mark" type used for list items
531 self.body.append(self.starttag(node, 'orderedlist'))
533 def depart_enumerated_list(self, node):
534 self.body.append('</orderedlist>\n')
536 def visit_error(self, node):
537 self.body.append(self.starttag(node, 'caution'))
538 self.body.append('\n<title>%s</title>\n'
539 % (self.language.labels[node.tagname],))
541 def depart_error(self, node):
542 self.body.append('</caution>\n')
544 # TODO: wrap with some element (filename used in DocBook example)
545 def visit_field(self, node):
546 self.body.append(self.starttag(node, 'varlistentry'))
548 def depart_field(self, node):
549 self.body.append('</varlistentry>\n')
551 # TODO: see if this should be wrapped with some element
552 def visit_field_argument(self, node):
553 self.body.append(' ')
555 def depart_field_argument(self, node):
556 pass
558 def visit_field_body(self, node):
559 # NOTE: this requires that a field body always
560 # be present, which looks like the case
561 # (from docutils.dtd)
562 self.body.append(self.context.pop())
563 self.body.append(self.starttag(node, 'listitem'))
565 def depart_field_body(self, node):
566 self.body.append('</listitem>\n')
568 def visit_field_list(self, node):
569 self.body.append(self.starttag(node, 'variablelist'))
571 def depart_field_list(self, node):
572 self.body.append('</variablelist>\n')
574 def visit_field_name(self, node):
575 self.body.append(self.starttag(node, 'term'))
576 # popped by visit_field_body, so "field_argument" is
577 # content within "term"
578 self.context.append('</term>\n')
580 def depart_field_name(self, node):
581 pass
583 def visit_figure(self, node):
584 self.body.append(self.starttag(node, 'informalfigure'))
585 self.body.append('<blockquote>')
587 def depart_figure(self, node):
588 self.body.append('</blockquote>')
589 self.body.append('</informalfigure>\n')
591 # TODO: footer (this is where 'generated by docutils' arrives)
592 # if that's all that will be there, it could map to "colophon"
593 def visit_footer(self, node):
594 raise nodes.SkipChildren
596 def depart_footer(self, node):
597 pass
599 def visit_footnote(self, node):
600 self.footnotes[node['id']] = []
601 atts = {'id': node['id']}
602 if isinstance(node[0], nodes.label):
603 atts['label'] = node[0].astext()
604 self.footnotes[node['id']].append(
605 self.starttag(node, 'footnote', **atts))
607 # replace body with this with a footnote collector list
608 # which will hold all the contents for this footnote.
609 # This needs to be kept separate so it can be used to replace
610 # the first ``footnote_reference`` as DocBook defines
611 # ``footnote`` elements inline.
612 self._body = self.body
613 self.body = self.footnotes[node['id']]
615 def depart_footnote(self, node):
616 # finish footnote and then replace footnote collector
617 # with real body list.
618 self.footnotes[node['id']].append('</footnote>')
619 self.body = self._body
620 self._body = None
622 def visit_footnote_reference(self, node):
623 if node.has_key('refid'):
624 refid = node['refid']
625 else:
626 refid = self.document.nameids[node['refname']]
628 # going to replace this footnote reference with the actual
629 # footnote later on, so store the footnote id to replace
630 # this reference with and the list and position to replace it
631 # in. Both list and position are stored in case a footnote
632 # reference is within a footnote, in which case ``self.body``
633 # won't really be ``self.body`` but a footnote collector
634 # list.
635 refs = self.footnote_map.get(refid, [])
636 refs.append((node['id'], self.body, len(self.body),))
637 self.footnote_map[refid] = refs
639 # add place holder list item which should later be
640 # replaced with the contents of the footnote element
641 # and it's child elements
642 self.body.append('<!-- REPLACE WITH FOOTNOTE -->')
644 raise nodes.SkipNode
646 def visit_header(self, node):
647 pass
648 def depart_header(self, node):
649 pass
651 # ??? does anything need to be done for generated?
652 def visit_generated(self, node):
653 pass
654 def depart_generated(self, node):
655 pass
657 def visit_hint(self, node):
658 self.body.append(self.starttag(node, 'note'))
659 self.body.append('\n<title>%s</title>\n'
660 % (self.language.labels[node.tagname],))
662 def depart_hint(self, node):
663 self.body.append('</note>\n')
665 def visit_image(self, node):
666 if isinstance(node.parent, nodes.paragraph):
667 element = 'inlinemediaobject'
668 elif isinstance(node.parent, nodes.reference):
669 element = 'inlinemediaobject'
670 else:
671 element = 'mediaobject'
672 atts = node.attributes.copy()
673 atts['fileref'] = atts['uri']
674 alt = None
675 del atts['uri']
676 if atts.has_key('alt'):
677 alt = atts['alt']
678 del atts['alt']
679 if atts.has_key('height'):
680 atts['depth'] = atts['height']
681 del atts['height']
682 self.body.append('<%s>' % element)
683 self.body.append('<imageobject>')
684 self.body.append(self.emptytag(node, 'imagedata', **atts))
685 self.body.append('</imageobject>')
686 if alt:
687 self.body.append('<textobject><phrase>' \
688 '%s</phrase></textobject>\n' % alt)
689 self.body.append('</%s>' % element)
691 def depart_image(self, node):
692 pass
694 def visit_important(self, node):
695 self.body.append(self.starttag(node, 'important'))
697 def depart_important(self, node):
698 self.body.append('</important>')
700 # @@@ Incomplete, pending a proper implementation on the
701 # Parser/Reader end.
702 # XXX see if the default for interpreted should be ``citetitle``
703 def visit_interpreted(self, node):
704 self.body.append('<constant>\n')
706 def depart_interpreted(self, node):
707 self.body.append('</constant>\n')
709 def visit_label(self, node):
710 # getting label for "footnote" in ``visit_footnote``
711 # because label is an attribute for the ``footnote``
712 # element.
713 if isinstance(node.parent, nodes.footnote):
714 raise nodes.SkipNode
715 # citations are currently treated as footnotes
716 elif isinstance(node.parent, nodes.citation):
717 raise nodes.SkipNode
719 def depart_label(self, node):
720 pass
722 def visit_legend(self, node):
723 # legend is placed inside the figure's ``blockquote``
724 # so there's nothing special to be done for it
725 pass
727 def depart_legend(self, node):
728 pass
730 def visit_line_block(self, node):
731 self.body.append(self.starttag(node, 'literallayout'))
733 def depart_line_block(self, node):
734 self.body.append('</literallayout>\n')
736 def visit_list_item(self, node):
737 self.body.append(self.starttag(node, 'listitem'))
739 def depart_list_item(self, node):
740 self.body.append('</listitem>\n')
742 def visit_literal(self, node):
743 self.body.append('<literal>')
745 def depart_literal(self, node):
746 self.body.append('</literal>')
748 def visit_literal_block(self, node):
749 self.body.append(self.starttag(node, 'programlisting'))
751 def depart_literal_block(self, node):
752 self.body.append('</programlisting>\n')
754 def visit_note(self, node):
755 self.body.append(self.starttag(node, 'note'))
756 self.body.append('\n<title>%s</title>\n'
757 % (self.language.labels[node.tagname],))
759 def depart_note(self, node):
760 self.body.append('</note>\n')
762 def visit_option(self, node):
763 self.body.append(self.starttag(node, 'command'))
764 if self.context[-1]:
765 self.body.append(', ')
767 def depart_option(self, node):
768 self.context[-1] += 1
769 self.body.append('</command>')
771 def visit_option_argument(self, node):
772 self.body.append(node.get('delimiter', ' '))
773 self.body.append(self.starttag(node, 'replaceable', ''))
775 def depart_option_argument(self, node):
776 self.body.append('</replaceable>')
778 def visit_option_group(self, node):
779 self.body.append(self.starttag(node, 'entry'))
780 self.context.append(0)
782 def depart_option_group(self, node):
783 self.context.pop()
784 self.body.append('</entry>\n')
786 def visit_option_list(self, node):
787 self.body.append(self.starttag(node, 'informaltable', frame='all'))
788 self.body.append('<tgroup cols="2">\n')
789 self.body.append('<colspec colname="option_col"/>\n')
790 self.body.append('<colspec colname="description_col"/>\n')
791 self.body.append('<tbody>\n')
793 def depart_option_list(self, node):
794 self.body.append('</tbody>')
795 self.body.append('</tgroup>\n')
796 self.body.append('</informaltable>\n')
798 def visit_option_list_item(self, node):
799 self.body.append(self.starttag(node, 'row'))
801 def depart_option_list_item(self, node):
802 self.body.append('</row>\n')
804 def visit_option_string(self, node):
805 pass
807 def depart_option_string(self, node):
808 pass
810 # organization is handled in ``visit_docinfo()``
811 def visit_organization(self, node):
812 raise nodes.SkipNode
814 def visit_paragraph(self, node):
815 self.body.append(self.starttag(node, 'para', ''))
817 def depart_paragraph(self, node):
818 self.body.append('</para>')
820 # TODO: problematic
821 visit_problematic = depart_problematic = lambda self, node: None
823 def visit_raw(self, node):
824 if node.has_key('format') and node['format'] == 'docbook':
825 self.body.append(node.astext())
826 raise node.SkipNode
828 def visit_reference(self, node):
829 atts = {}
830 if node.has_key('refuri'):
831 atts['url'] = node['refuri']
832 self.context.append('ulink')
833 elif node.has_key('refid'):
834 atts['linkend'] = node['refid']
835 self.context.append('link')
836 elif node.has_key('refname'):
837 atts['linkend'] = self.document.nameids[node['refname']]
838 self.context.append('link')
839 # if parent is a section,
840 # wrap link in a para
841 if isinstance(node.parent, nodes.section):
842 self.body.append('<para>')
843 self.body.append(self.starttag(node, self.context[-1], '', **atts))
845 def depart_reference(self, node):
846 self.body.append('</%s>' % (self.context.pop(),))
847 # if parent is a section,
848 # wrap link in a para
849 if isinstance(node.parent, nodes.section):
850 self.body.append('</para>')
852 # revision is handled in ``visit_docinfo()``
853 def visit_revision(self, node):
854 raise nodes.SkipNode
856 def visit_row(self, node):
857 self.entry_level = 0
858 self.body.append(self.starttag(node, 'row'))
860 def depart_row(self, node):
861 self.body.append('</row>\n')
863 def visit_rubric(self, node):
864 self.body.append(self.starttag(node, 'bridgehead'))
866 def depart_rubric(self, node):
867 self.body.append('</bridgehead>')
869 def visit_section(self, node):
870 if self.section == 0 and self.doctype == 'book':
871 self.body.append(self.starttag(node, 'chapter'))
872 else:
873 self.body.append(self.starttag(node, 'section'))
874 self.section += 1
876 def depart_section(self, node):
877 self.section -= 1
878 if self.section == 0 and self.doctype == 'book':
879 self.body.append('</chapter>\n')
880 else:
881 self.body.append('</section>\n')
883 def visit_sidebar(self, node):
884 self.body.append(self.starttag(node, 'sidebar'))
885 if isinstance(node[0], nodes.title):
886 self.body.append('<sidebarinfo>\n')
887 self.body.append('<title>%s</title>\n' % node[0].astext())
888 if isinstance(node[1], nodes.subtitle):
889 self.body.append('<subtitle>%s</subtitle>\n' % node[1].astext())
890 self.body.append('</sidebarinfo>\n')
892 def depart_sidebar(self, node):
893 self.body.append('</sidebar>\n')
895 # author is handled in ``visit_docinfo()``
896 def visit_status(self, node):
897 raise nodes.SkipNode
899 def visit_strong(self, node):
900 self.body.append('<emphasis role="strong">')
902 def depart_strong(self, node):
903 self.body.append('</emphasis>')
905 def visit_subscript(self, node):
906 self.body.append(self.starttag(node, 'subscript', ''))
908 def depart_subscript(self, node):
909 self.body.append('</subscript>')
911 def visit_substitution_definition(self, node):
912 raise nodes.SkipNode
914 def visit_substitution_reference(self, node):
915 self.unimplemented_visit(node)
917 def visit_subtitle(self, node):
918 # document title needs to go into
919 # <type>info/subtitle, so save it for
920 # when we do visit_docinfo
921 if isinstance(node.parent, nodes.document):
922 self.subtitle = node.astext()
923 raise nodes.SkipNode
924 else:
925 # sidebar subtitle needs to go into a sidebarinfo element
926 #if isinstance(node.parent, nodes.sidebar):
927 # self.body.append('<sidebarinfo>')
928 if isinstance(node.parent, nodes.sidebar):
929 raise nodes.SkipNode
930 self.body.append(self.starttag(node, 'subtitle', ''))
932 def depart_subtitle(self, node):
933 if not isinstance(node.parent, nodes.document):
934 self.body.append('</subtitle>\n')
935 #if isinstance(node.parent, nodes.sidebar):
936 # self.body.append('</sidebarinfo>\n')
938 def visit_superscript(self, node):
939 self.body.append(self.starttag(node, 'superscript', ''))
941 def depart_superscript(self, node):
942 self.body.append('</superscript>')
944 # TODO: system_message
945 visit_system_message = depart_system_message = lambda self, node: None
947 def visit_table(self, node):
948 self.body.append(
949 self.starttag(node, 'informaltable', frame='all')
952 def depart_table(self, node):
953 self.body.append('</informaltable>\n')
955 # don't think anything is needed for targets
956 def visit_target(self, node):
957 # XXX this would like to be a transform!
958 # XXX comment this mess!
959 handled = 0
960 siblings = node.parent.children
961 for i in range(len(siblings)):
962 if siblings[i] is node:
963 if i+1 < len(siblings):
964 next = siblings[i+1]
965 if isinstance(next,nodes.Text):
966 pass
967 elif not next.attributes.has_key('id'):
968 next['id'] = node['id']
969 handled = 1
970 if not handled:
971 if not node.parent.attributes.has_key('id'):
972 node.parent.attributes['id'] = node['id']
973 handled = 1
974 # might need to do more...
975 # (if not handled, update the referrer to refer to the parent's id)
977 def depart_target(self, node):
978 pass
980 def visit_tbody(self, node):
981 self.body.append(self.starttag(node, 'tbody'))
983 def depart_tbody(self, node):
984 self.body.append('</tbody>\n')
986 def visit_term(self, node):
987 self.body.append(self.starttag(node, 'term'))
988 self.body.append('<varname>')
990 def depart_term(self, node):
991 # Leave the end tag "term" to ``visit_definition()``,
992 # in case there's a classifier.
993 self.body.append('</varname>')
995 def visit_tgroup(self, node):
996 self.colnames = []
997 atts = {'cols': node['cols']}
998 self.body.append(self.starttag(node, 'tgroup', **atts))
1000 def depart_tgroup(self, node):
1001 self.body.append('</tgroup>\n')
1003 def visit_thead(self, node):
1004 self.body.append(self.starttag(node, 'thead'))
1006 def depart_thead(self, node):
1007 self.body.append('</thead>\n')
1009 def visit_tip(self, node):
1010 self.body.append(self.starttag(node, 'tip'))
1012 def depart_tip(self, node):
1013 self.body.append('</tip>\n')
1015 def visit_title(self, node):
1016 # document title needs to go inside
1017 # <type>info/title
1018 if isinstance(node.parent, nodes.document):
1019 self.title = node.astext()
1020 raise nodes.SkipNode
1021 elif isinstance(node.parent, nodes.sidebar):
1022 # sidebar title and subtitle are collected in visit_sidebar
1023 raise nodes.SkipNode
1024 else:
1025 self.body.append(self.starttag(node, 'title', ''))
1027 def depart_title(self, node):
1028 if not isinstance(node.parent, nodes.document):
1029 self.body.append('</title>\n')
1031 def visit_title_reference(self, node):
1032 self.body.append('<citetitle>')
1034 def depart_title_reference(self, node):
1035 self.body.append('</citetitle>')
1037 def visit_topic(self, node):
1038 # let DocBook handle Table of Contents generation
1039 if node.get('class') == 'contents':
1040 raise nodes.SkipChildren
1041 elif node.get('class') == 'abstract':
1042 self.body.append(self.starttag(node, 'abstract'))
1043 self.context.append('abstract')
1044 elif node.get('class') == 'dedication':
1045 # docbook only supports dedication in a book,
1046 # so we're faking it for article & chapter
1047 if self.doctype == 'book':
1048 self.body.append(self.starttag(node, 'dedication'))
1049 self.context.append('dedication')
1050 else:
1051 self.body.append(self.starttag(node, 'section'))
1052 self.context.append('section')
1054 # generic "topic" element treated as a section
1055 elif node.get('class','') == '':
1056 self.body.append(self.starttag(node, 'section'))
1057 self.context.append('section')
1058 else:
1059 # XXX DEBUG CODE
1060 print 'class:', node.get('class')
1061 print node.__class__.__name__
1062 print node
1063 print `node`
1064 print dir(node)
1065 self.unimplemented_visit(node)
1067 def depart_topic(self, node):
1068 if len(self.context):
1069 self.body.append('</%s>\n' % (self.context.pop(),))
1071 def visit_transition(self, node):
1072 pass
1073 def depart_transition(self, node):
1074 pass
1076 # author is handled in ``visit_docinfo()``
1077 def visit_version(self, node):
1078 raise nodes.SkipNode
1080 def visit_warning(self, node):
1081 self.body.append(self.starttag(node, 'warning'))
1083 def depart_warning(self, node):
1084 self.body.append('</warning>\n')
1086 def unimplemented_visit(self, node):
1087 raise NotImplementedError('visiting unimplemented node type: %s'
1088 % node.__class__.__name__)
1090 # :collapseFolds=0:folding=indent:indentSize=4:
1091 # :lineSeparator=\n:noTabs=true:tabSize=4: