logilab/doctools

view rest_docbook.py @ 0:cc367abb080e

forget the past. forget the past.
author root
date Wed, 26 Apr 2006 10:48:09 +0000
parents
children fdd104723a75
line source
1 #!/usr/bin/env python
2 # pylint: disable-msg=W0131
4 """
5 :Author: Ollie Rutherfurd
6 :Contact: oliver@rutherfurd.net
7 :Revision: $Revision: 1.7 $
8 :Date: $Date: 2006-02-08 15:21:17 $
9 :Copyright: This module has been placed in the public domain.
11 DocBook document tree Writer.
13 This Writer converts a reST document tree to a subset
14 of DocBook.
16 .. Note:: This is an unfinished work in progress.
18 Document Types
19 ==============
21 This writer can create 3 types of DocBook documents:
23 1. "article" *(default)*
24 2. "book"
25 3. "chapter"
27 .. Note:: When creating a "book" document, all first-level
28 sections are output as "chapter" elements instead
29 of "section" as in "article" and "chapter".
31 Mappings
32 ========
34 Option List
35 -----------
37 As there is no direct equivlent for a listing of program options
38 in DocBook_, as defined in reST_, a table containing the
39 option list contents is generated.
41 Field List
42 ----------
44 Like `Option List`_, there is not direct equivlent for
45 a Field List in DocBook, so this is done using a
46 "variablelist".
48 .. NOTE:: It might be better to switch Definition List
49 to glossary or something similar, so Field List
50 and Definition List are generating the same type
51 of output.
53 Bibliography Elements
54 ---------------------
56 Here's how reST's bibliography elements are mapped
57 to DocBook elements:
59 +--------------+---------------------------------------------+
60 | reST Element | DocBook Element |
61 +==============+=============================================+
62 | author | {doctype}info/author/othername |
63 | | or |
64 | | {doctype}info/authorgroup/author/othername |
65 | | if nested under ``authors`` |
66 +--------------+---------------------------------------------+
67 | authors | {doctype}info/authorgroup/ |
68 +--------------+---------------------------------------------+
69 | contact | {doctype}info/author/email |
70 +--------------+---------------------------------------------+
71 | copyright | {doctype}info/legalnotice |
72 +--------------+---------------------------------------------+
73 | date | {doctype}info/date |
74 +--------------+---------------------------------------------+
75 | organization | {doctype}info/orgname |
76 +--------------+---------------------------------------------+
77 | revision | concatenated with ``version`` into |
78 | | {doctype}info/edition |
79 +--------------+---------------------------------------------+
80 | status | {doctype}info/releaseinfo |
81 +--------------+---------------------------------------------+
82 | version | concatenated with ``revision`` into |
83 | | {doctype}info/edition |
84 +--------------+---------------------------------------------+
86 Note: ``{doctype}`` is the type of the DocBook document
87 being generated, one of the following: ``article``,
88 ``book``, or ``chapter``.
90 Todo
91 ====
93 - Inline images -- need to figure out how to identify an inline image
94 - list item marks are not guarenteed to be what was specified (if they
95 are it is be coincidence, however unless one starts out of order
96 they should match most of the time).
97 - sidebar subtitle needs to go into sidebarinfo
99 Should para, note, etc... not in a section at the start
100 of the document be stuffed into an untitled ``section``?
102 """
104 __docformat__ = 'reStructuredText'
106 import re
107 import string
108 from docutils import writers, nodes, languages
109 from types import ListType
111 class Writer(writers.Writer):
113 settings_spec = (
114 'DocBook-Specific Options',
115 None,
116 (('Set DocBook document type. '
117 'Choices are "article", "book", and "chapter". '
118 'Default is "article".',
119 ['--doctype'],
120 {'default': 'article',
121 'metavar': '<name>',
122 'type': 'choice',
123 'choices': ('article', 'book', 'chapter',)
124 }
125 ),
126 )
127 )
129 output = None
130 """Final translated form of `document`."""
132 def translate(self):
133 visitor = DocBookTranslator(self.document)
134 self.document.walkabout(visitor)
135 self.output = visitor.astext()
138 class FragmentWriter(Writer):
140 def translate(self):
141 visitor = DocBookTranslator(self.document)
142 self.document.walkabout(visitor)
143 self.output = u''.join(visitor.body)
145 class DocBookTranslator(nodes.NodeVisitor):
147 XML_DECL = '<?xml version="1.0" encoding="%s"?>\n'
149 DOCTYPE_DECL = """<!DOCTYPE %s
150 PUBLIC "-//OASIS//DTD DocBook XML V4.1.2//EN"
151 "http://www.oasis-open.org/docbook/xml/4.0/docbookx.dtd">\n"""
153 def __init__(self, document):
154 nodes.NodeVisitor.__init__(self, document)
155 self.language = languages.get_language(
156 document.settings.language_code)
157 self.doctype = document.settings.doctype
158 self.doc_header = [
159 self.XML_DECL % (document.settings.output_encoding,),
160 self.DOCTYPE_DECL % (self.doctype,),
161 '<%s>\n' % (self.doctype,),
162 ]
163 self.doc_footer = [
164 '</%s>\n' % (self.doctype,)
165 ]
166 self.body = []
167 self.section = 0
168 self.context = []
169 self.colnames = []
170 self.footnotes = {}
171 self.footnote_map = {}
172 self.docinfo = []
174 def astext(self):
175 return ''.join(self.doc_header
176 + self.docinfo
177 + self.body
178 + self.doc_footer)
180 def encode(self, text):
181 """Encode special characters in `text` & return."""
182 # @@@ A codec to do these and all other
183 # HTML entities would be nice.
184 text = text.replace("&", "&amp;")
185 text = text.replace("<", "&lt;")
186 text = text.replace('"', "&quot;")
187 text = text.replace(">", "&gt;")
188 return text
190 def rearrange_footnotes(self):
191 """
192 Replaces ``foonote_reference`` placeholders with
193 ``footnote`` element content as DocBook and reST
194 handle footnotes differently.
196 DocBook defines footnotes inline, whereas they
197 may be anywere in reST. This function replaces the
198 first instance of a ``footnote_reference`` with
199 the ``footnote`` element itself, and later
200 references of the same a footnote with
201 ``footnoteref`` elements.
202 """
203 for (footnote_id,refs) in self.footnote_map.items():
204 ref_id, context, pos = refs[0]
205 context[pos] = ''.join(self.footnotes[footnote_id])
206 for ref_id, context, pos in refs[1:]:
207 context[pos] = '<footnoteref linkend="%s"/>' \
208 % (footnote_id,)
210 def attval(self, text,
211 transtable=string.maketrans('\n\r\t\v\f', ' ')):
212 """Cleanse, encode, and return attribute value text."""
213 return self.encode(text.translate(transtable))
215 def starttag(self, node, tagname, suffix='\n', infix='', **attributes):
216 """
217 Construct and return a start tag given a node
218 (id & class attributes are extracted), tag name,
219 and optional attributes.
220 """
221 atts = {}
222 for (name, value) in attributes.items():
223 atts[name.lower()] = value
225 for att in ('id',): # node attribute overrides
226 if node.has_key(att):
227 atts[att] = node[att]
229 attlist = atts.items()
230 attlist.sort()
231 parts = [tagname.lower()]
232 for name, value in attlist:
233 if value is None: # boolean attribute
234 # According to the HTML spec, ``<element boolean>`` is good,
235 # ``<element boolean="boolean">`` is bad.
236 # (But the XHTML (XML) spec says the opposite. <sigh>)
237 parts.append(name.lower())
238 elif isinstance(value, ListType):
239 values = [str(v) for v in value]
240 parts.append('%s="%s"' % (name.lower(),
241 self.attval(' '.join(values))))
242 else:
243 parts.append('%s="%s"' % (name.lower(),
244 self.attval(str(value))))
245 return '<%s%s>%s' % (' '.join(parts), infix, suffix)
247 def emptytag(self, node, tagname, suffix='\n', **attributes):
248 """Construct and return an XML-compatible empty tag."""
249 return self.starttag(node, tagname, suffix, infix=' /', **attributes)
251 def visit_Text(self, node):
252 self.body.append(self.encode(node.astext()))
254 def depart_Text(self, node):
255 pass
257 def visit_attention(self, node):
258 self.body.append(self.starttag(node, 'note'))
259 self.body.append('\n<title>%s</title>\n'
260 % (self.language.labels[node.tagname],))
262 def depart_attention(self, node):
263 self.body.append('</note>\n')
265 # author is handled in ``visit_docinfo()``
266 def visit_author(self, node):
267 raise nodes.SkipNode
269 # authors is handled in ``visit_docinfo()``
270 def visit_authors(self, node):
271 raise nodes.SkipNode
273 def visit_block_quote(self, node):
274 self.body.append(self.starttag(node, 'blockquote'))
276 def depart_block_quote(self, node):
277 self.body.append('</blockquote>\n')
279 def visit_bullet_list(self, node):
280 self.body.append(self.starttag(node, 'itemizedlist'))
282 def depart_bullet_list(self, node):
283 self.body.append('</itemizedlist>\n')
285 def visit_caption(self, node):
286 # NOTE: ideally, this should probably be stuffed into
287 # the mediaobject as a "caption" element
288 self.body.append(self.starttag(node, 'para'))
290 def depart_caption(self, node):
291 self.body.append('</para>')
293 def visit_caution(self, node):
294 self.body.append(self.starttag(node, 'caution'))
295 self.body.append('\n<title>%s</title>\n'
296 % (self.language.labels[node.tagname],))
298 def depart_caution(self, node):
299 self.body.append('</caution>\n')
301 # reST seems to handle citations as a labled
302 # footnotes, whereas DocBook doesn't from what
303 # I can tell, so I'm not sure how to give DocBook
304 # citations that result in equivlent output
305 # as the docutils html writer.
306 #
307 # Currently, citations are handled as footnotes,
308 # using the citation label as the footnote label
309 # which seems functionally equivlent, but the
310 # DocBook stylesheets for generating HTML output
311 # don't seem to be using the label for foonotes
312 # so this doesn't work.
313 #
314 # So I'm at a bit of a loss as to how to
315 # handle citations. Any ideas or suggestions would
316 # be welcome.
318 # TODO: citation
319 def visit_citation(self, node):
320 self.visit_footnote(node)
322 def depart_citation(self, node):
323 self.depart_footnote(node)
325 # TODO: citation_reference
326 def visit_citation_reference(self, node):
327 self.visit_footnote_reference(node)
329 def depart_citation_reference(self, node):
330 pass
332 def visit_classifier(self, node):
333 self.body.append(' : ')
334 self.body.append(self.starttag(node, 'type'))
336 def depart_classifier(self, node):
337 self.body.append('</type>\n')
339 def visit_colspec(self, node):
340 self.colnames.append('col_%d' % (len(self.colnames) + 1,))
341 atts = {'colname': self.colnames[-1]}
342 self.body.append(self.emptytag(node, 'colspec', **atts))
344 def depart_colspec(self, node):
345 pass
347 def visit_comment(self, node, sub=re.compile('-(?=-)').sub):
348 """Escape double-dashes in comment text."""
349 self.body.append('<!-- %s -->\n' % sub('- ', node.astext()))
350 raise nodes.SkipNode
352 # contact is handled in ``visit_docinfo()``
353 def visit_contact(self, node):
354 raise nodes.SkipNode
356 # copyright is handled in ``visit_docinfo()``
357 def visit_copyright(self, node):
358 raise nodes.SkipNode
360 def visit_danger(self, node):
361 self.body.append(self.starttag(node, 'caution'))
362 self.body.append('\n<title>%s</title>\n'
363 % (self.language.labels[node.tagname],))
365 def depart_danger(self, node):
366 self.body.append('</caution>\n')
368 # date is handled in ``visit_docinfo()``
369 def visit_date(self, node):
370 raise nodes.SkipNode
372 # TODO: decoration
373 def visit_decoration(self, node):
374 pass
376 def depart_decoration(self, node):
377 pass
379 def visit_definition(self, node):
380 # "term" is not closed in depart_term
381 self.body.append('</term>\n')
382 self.body.append(self.starttag(node, 'listitem'))
384 def depart_definition(self, node):
385 self.body.append('</listitem>\n')
387 def visit_definition_list(self, node):
388 self.body.append(self.starttag(node, 'variablelist'))
390 def depart_definition_list(self, node):
391 self.body.append('</variablelist>\n')
393 def visit_definition_list_item(self, node):
394 self.body.append(self.starttag(node, 'varlistentry'))
396 def depart_definition_list_item(self, node):
397 self.body.append('</varlistentry>\n')
399 def visit_description(self, node):
400 self.body.append(self.starttag(node, 'entry'))
402 def depart_description(self, node):
403 self.body.append('</entry>\n')
405 def visit_docinfo(self, node):
406 """
407 Collects all docinfo elements for the document.
409 Since reST's bibliography elements don't map very
410 cleanly to DocBook, rather than maintain state and
411 check dependencies within the different visitor
412 fuctions all processing of bibliography elements
413 is dont within this function.
415 .. NOTE:: Skips processing of all child nodes as
416 everything should be collected here.
417 """
419 # XXX There are a number of fields in docinfo elements
420 # which don't map nicely to docbook elements and
421 # reST allows one to insert arbitrary fields into
422 # the header, We need to be able to handle fields
423 # which either don't map or nicely or are unexpected.
424 # I'm thinking of just using DocBook to display these
425 # elements in some sort of tabular format -- but
426 # to collecting them is not straight-forward.
427 # Paragraphs, links, lists, etc... can all live within
428 # the values so we either need a separate visitor
429 # to translate these elements, or to maintain state
430 # in any possible child elements (not something I
431 # want to do).
433 docinfo = ['<%sinfo>\n' % self.doctype]
435 authors = []
436 author = ''
437 contact = ''
438 date = ''
439 legalnotice = ''
440 orgname = ''
441 releaseinfo = ''
442 revision, version = '', ''
444 for n in node:
445 if isinstance(n, nodes.author):
446 author = n.astext()
447 elif isinstance(n, nodes.authors):
448 for a in n:
449 authors.append(a.astext())
450 elif isinstance(n, nodes.contact):
451 contact = n.astext()
452 elif isinstance(n, nodes.copyright):
453 legalnotice = n.astext()
454 elif isinstance(n, nodes.date):
455 date = n.astext()
456 elif isinstance(n, nodes.organization):
457 orgname = n.astext()
458 elif isinstance(n, nodes.revision):
459 revision = 'Revision ' + n.astext()
460 elif isinstance(n, nodes.status):
461 releaseinfo = n.astext()
462 elif isinstance(n, nodes.version):
463 version = 'Version ' + n.astext()
464 elif isinstance(n, nodes.field):
465 # XXX
466 import sys
467 print >> sys.stderr, "I don't do 'field' yet"
468 # since all child nodes are handled here raise an exception
469 # if node is not handled, so it doesn't silently slip through.
470 else:
471 print dir(n)
472 print n.astext()
473 raise self.unimplemented_visit(n)
475 # can only add author if name is present
476 # since contact is associate with author, the contact
477 # can also only be added if an author name is given.
478 if author:
479 docinfo.append('<author>\n')
480 docinfo.append('<othername>%s</othername>\n' % author)
481 if contact:
482 docinfo.append('<email>%s</email>\n' % contact)
483 docinfo.append('</author>\n')
485 if authors:
486 docinfo.append('<authorgroup>\n')
487 for name in authors:
488 docinfo.append(
489 '<author><othername>%s</othername></author>\n' % name)
490 docinfo.append('</authorgroup>\n')
492 if revision or version:
493 edition = version
494 if edition and revision:
495 edition += ', ' + revision
496 elif revision:
497 edition = revision
498 docinfo.append('<edition>%s</edition>\n' % edition)
500 if date:
501 docinfo.append('<date>%s</date>\n' % date)
503 if orgname:
504 docinfo.append('<orgname>%s</orgname>\n' % orgname)
506 if releaseinfo:
507 docinfo.append('<releaseinfo>%s</releaseinfo>\n' % releaseinfo)
509 if legalnotice:
510 docinfo.append('<legalnotice>\n')
511 docinfo.append('<para>%s</para>\n' % legalnotice)
512 docinfo.append('</legalnotice>\n')
514 if len(docinfo) > 1:
515 docinfo.append('</%sinfo>\n' % self.doctype)
517 self.docinfo = docinfo
519 raise nodes.SkipChildren
521 def depart_docinfo(self, node):
522 pass
524 def visit_doctest_block(self, node):
525 self.body.append('<informalexample>\n')
526 self.body.append(self.starttag(node, 'programlisting'))
528 def depart_doctest_block(self, node):
529 self.body.append('</programlisting>\n')
530 self.body.append('</informalexample>\n')
532 def visit_document(self, node):
533 pass
535 def depart_document(self, node):
536 self.rearrange_footnotes()
538 def visit_emphasis(self, node):
539 #self.body.append(self.starttag(node, 'emphasis')) # XXX
540 self.body.append('<emphasis>')
542 def depart_emphasis(self, node):
543 self.body.append('</emphasis>')
545 def visit_entry(self, node):
546 tagname = 'entry'
547 atts = {}
548 if node.has_key('morerows'):
549 atts['morerows'] = node['morerows']
550 if node.has_key('morecols'):
551 atts['namest'] = self.colnames[self.entry_level]
552 atts['nameend'] = self.colnames[self.entry_level \
553 + node['morecols']]
554 self.entry_level += 1 # for tracking what namest and nameend are
555 self.body.append(self.starttag(node, tagname, '', **atts))
557 def depart_entry(self, node):
558 self.body.append('</entry>\n')
560 def visit_enumerated_list(self, node):
561 # TODO: need to specify "mark" type used for list items
562 self.body.append(self.starttag(node, 'orderedlist'))
564 def depart_enumerated_list(self, node):
565 self.body.append('</orderedlist>\n')
567 def visit_error(self, node):
568 self.body.append(self.starttag(node, 'caution'))
569 self.body.append('\n<title>%s</title>\n'
570 % (self.language.labels[node.tagname],))
572 def depart_error(self, node):
573 self.body.append('</caution>\n')
575 # TODO: wrap with some element (filename used in DocBook example)
576 def visit_field(self, node):
577 self.body.append(self.starttag(node, 'varlistentry'))
579 def depart_field(self, node):
580 self.body.append('</varlistentry>\n')
582 # TODO: see if this should be wrapped with some element
583 def visit_field_argument(self, node):
584 self.body.append(' ')
586 def depart_field_argument(self, node):
587 pass
589 def visit_field_body(self, node):
590 # NOTE: this requires that a field body always
591 # be present, which looks like the case
592 # (from docutils.dtd)
593 self.body.append(self.context.pop())
594 self.body.append(self.starttag(node, 'listitem'))
596 def depart_field_body(self, node):
597 self.body.append('</listitem>\n')
599 def visit_field_list(self, node):
600 self.body.append(self.starttag(node, 'variablelist'))
602 def depart_field_list(self, node):
603 self.body.append('</variablelist>\n')
605 def visit_field_name(self, node):
606 self.body.append(self.starttag(node, 'term'))
607 # popped by visit_field_body, so "field_argument" is
608 # content within "term"
609 self.context.append('</term>\n')
611 def depart_field_name(self, node):
612 pass
614 def visit_figure(self, node):
615 self.body.append(self.starttag(node, 'informalfigure'))
616 self.body.append('<blockquote>')
618 def depart_figure(self, node):
619 self.body.append('</blockquote>')
620 self.body.append('</informalfigure>\n')
622 # TODO: footer (this is where 'generated by docutils' arrives)
623 # if that's all that will be there, it could map to "colophon"
624 def visit_footer(self, node):
625 raise nodes.SkipChildren
627 def depart_footer(self, node):
628 pass
630 def visit_footnote(self, node):
631 self.footnotes[node['id']] = []
632 atts = {'id': node['id']}
633 if isinstance(node[0], nodes.label):
634 # FIXME: this fails with the second auto-sequenece character
635 # used in the test document ``test.txt``.
636 atts['label'] = node[0].astext()
637 self.footnotes[node['id']].append(
638 self.starttag(node, 'footnote', **atts))
640 # replace body with this with a footnote collector list
641 # which will hold all the contents for this footnote.
642 # This needs to be kept separate so it can be used to replace
643 # the first ``footnote_reference`` as DocBook defines
644 # ``footnote`` elements inline.
645 self._body = self.body
646 self.body = self.footnotes[node['id']]
648 def depart_footnote(self, node):
649 # finish footnote and then replace footnote collector
650 # with real body list.
651 self.footnotes[node['id']].append('</footnote>\n')
652 self.body = self._body
653 self._body = None
655 def visit_footnote_reference(self, node):
656 if node.has_key('refid'):
657 refid = node['refid']
658 else:
659 refid = self.document.nameids[node['refname']]
661 # going to replace this footnote reference with the actual
662 # footnote later on, so store the footnote id to replace
663 # this reference with and the list and position to replace it
664 # in. Both list and position are stored in case a footnote
665 # reference is within a footnote, in which case ``self.body``
666 # won't really be ``self.body`` but a footnote collector
667 # list.
668 refs = self.footnote_map.get(refid, [])
669 refs.append((node['id'], self.body, len(self.body),))
670 self.footnote_map[refid] = refs
672 # add place holder list item which should later be
673 # replaced with the contents of the footnote element
674 # and it's child elements
675 self.body.append('<!-- REPLACE WITH FOOTNOTE -->')
677 raise nodes.SkipNode
679 # TODO: header
681 # ??? does anything need to be done for generated?
682 def visit_generated(self, node):
683 pass
685 def depart_generated(self, node):
686 pass
688 def visit_hint(self, node):
689 self.body.append(self.starttag(node, 'note'))
690 self.body.append('\n<title>%s</title>\n'
691 % (self.language.labels[node.tagname],))
693 def depart_hint(self, node):
694 self.body.append('</note>\n')
696 def visit_image(self, node):
697 atts = node.attributes.copy()
698 atts['fileref'] = atts['uri']
699 alt = None
700 del atts['uri']
701 if atts.has_key('alt'):
702 alt = atts['alt']
703 del atts['alt']
704 if atts.has_key('height'):
705 atts['depth'] = atts['height']
706 del atts['height']
707 # NOTE: using win32 port of xsltproc and docbook-stylesheets-1.51.1
708 # I'm getting the following error when transforming:
709 # Error C:\home\igor\src\gnome-xml\xpath.c:8023: Undefined
710 # namespace prefix xmlXPathCompiledEval: evaluation failed
711 # When I switched to version 1.49 of the docbook-stylesheets
712 # I didn't have this problem.
713 self.body.append('<mediaobject>')
714 self.body.append('<imageobject>')
715 self.body.append(self.emptytag(node, 'imagedata', **atts))
716 self.body.append('</imageobject>')
717 if alt:
718 self.body.append('<textobject><phrase>' \
719 '%s</phrase></textobject>\n' % alt)
720 self.body.append('</mediaobject>')
722 def depart_image(self, node):
723 pass
725 def visit_important(self, node):
726 self.body.append(self.starttag(node, 'important'))
728 def depart_important(self, node):
729 self.body.append('</important>')
731 # @@@ Incomplete, pending a proper implementation on the
732 # Parser/Reader end.
733 def visit_interpreted(self, node):
734 self.body.append('<constant>\n')
736 def depart_interpreted(self, node):
737 self.body.append('</constant>\n')
739 def visit_label(self, node):
740 # getting label for "footnote" in ``visit_footnote``
741 # because label is an attribute for the ``footnote``
742 # element.
743 if isinstance(node.parent, nodes.footnote):
744 raise nodes.SkipNode
745 # TODO: handle citation label
746 elif isinstance(node.parent, nodes.citation):
747 raise nodes.SkipNode
749 def depart_label(self, node):
750 pass
752 def visit_legend(self, node):
753 # TODO: explain why this is empty....
754 pass
756 def depart_legend(self, node):
757 pass
759 def visit_line_block(self, node):
760 self.body.append(self.starttag(node, 'literallayout'))
762 def depart_line_block(self, node):
763 self.body.append('</literallayout>\n')
765 def visit_list_item(self, node):
766 self.body.append(self.starttag(node, 'listitem'))
768 def depart_list_item(self, node):
769 self.body.append('</listitem>\n')
771 def visit_literal(self, node):
772 self.body.append('<literal>')
774 def depart_literal(self, node):
775 self.body.append('</literal>')
777 def visit_literal_block(self, node):
778 self.body.append(self.starttag(node, 'programlisting'))
780 def depart_literal_block(self, node):
781 self.body.append('\n</programlisting>\n')
783 def visit_note(self, node):
784 self.body.append(self.starttag(node, 'note'))
785 self.body.append('\n<title>%s</title>\n'
786 % (self.language.labels[node.tagname],))
788 def depart_note(self, node):
789 self.body.append('</note>\n')
791 def visit_option(self, node):
792 self.body.append(self.starttag(node, 'command'))
793 if self.context[-1]:
794 self.body.append(', ')
796 def depart_option(self, node):
797 self.context[-1] += 1
798 self.body.append('</command>')
800 def visit_option_argument(self, node):
801 self.body.append(node.get('delimiter', ' '))
802 self.body.append(self.starttag(node, 'replaceable', ''))
804 def depart_option_argument(self, node):
805 self.body.append('</replaceable>')
807 def visit_option_group(self, node):
808 self.body.append(self.starttag(node, 'entry'))
809 self.context.append(0)
811 def depart_option_group(self, node):
812 self.context.pop()
813 self.body.append('</entry>\n')
815 def visit_option_list(self, node):
816 self.body.append(self.starttag(node, 'informaltable', frame='all'))
817 self.body.append('<tgroup cols="2">\n')
818 self.body.append('<colspec colname="option_col"/>\n')
819 self.body.append('<colspec colname="description_col"/>\n')
820 self.body.append('<thead>\n')
821 self.body.append('<row>\n')
822 # FIXME: shouldn't hardcode everything...
823 self.body.append('<entry align="center">Option</entry>\n')
824 self.body.append('<entry align="center">Description</entry>\n')
825 self.body.append('</row>\n')
826 self.body.append('</thead>\n')
827 self.body.append('<tbody>\n')
829 def depart_option_list(self, node):
830 self.body.append('</tbody>')
831 self.body.append('</tgroup>\n')
832 self.body.append('</informaltable>\n')
834 def visit_option_list_item(self, node):
835 self.body.append(self.starttag(node, 'row'))
837 def depart_option_list_item(self, node):
838 self.body.append('</row>\n')
840 def visit_option_string(self, node):
841 pass
843 def depart_option_string(self, node):
844 pass
846 # organization is handled in ``visit_docinfo()``
847 def visit_organization(self, node):
848 raise nodes.SkipNode
850 def visit_paragraph(self, node):
851 self.body.append(self.starttag(node, 'para', ''))
853 def depart_paragraph(self, node):
854 self.body.append('</para>\n')
856 # TODO: problematic
857 visit_problematic = depart_problematic = lambda self, node: None
859 def visit_raw(self, node):
860 if node.has_key('format') and node['format'] == 'docbook':
861 self.body.append(node.astext())
862 raise nodes.SkipNode
864 def visit_reference(self, node):
865 atts = {}
866 if node.has_key('refuri'):
867 atts['url'] = node['refuri']
868 self.context.append('ulink')
869 elif node.has_key('refid'):
870 atts['linkend'] = node['refid']
871 self.context.append('link')
872 elif node.has_key('refname'):
873 atts['linkend'] = self.document.nameids[node['refname']]
874 self.context.append('link')
875 self.body.append(self.starttag(node, self.context[-1], '', **atts))
877 def depart_reference(self, node):
878 self.body.append('</%s>' % (self.context.pop(),))
880 # revision is handled in ``visit_docinfo()``
881 def visit_revision(self, node):
882 raise nodes.SkipNode
884 def visit_row(self, node):
885 self.entry_level = 0
886 self.body.append(self.starttag(node, 'row'))
888 def depart_row(self, node):
889 self.body.append('</row>\n')
891 def visit_section(self, node):
892 if self.section == 0 and self.doctype == 'book':
893 self.body.append(self.starttag(node, 'chapter'))
894 else:
895 self.body.append(self.starttag(node, 'section'))
896 self.section += 1
898 def depart_section(self, node):
899 self.section -= 1
900 if self.section == 0 and self.doctype == 'book':
901 self.body.append('</chapter>\n')
902 else:
903 self.body.append('</section>\n')
905 def visit_sidebar(self, node):
906 self.body.append(self.starttag(node, 'sidebar'))
908 def depart_sidebar(self, node):
909 self.body.append('</sidebar>\n')
911 # author is handled in ``visit_docinfo()``
912 def visit_status(self, node):
913 raise nodes.SkipNode
915 def visit_strong(self, node):
916 # self.body.append(self.starttag(node, 'emphasis', role='strong')) # XXX
917 self.body.append('<emphasis role="strong">')
919 def depart_strong(self, node):
920 self.body.append('</emphasis>')
922 def visit_substitution_definition(self, node):
923 raise nodes.SkipNode
925 def visit_substitution_reference(self, node):
926 self.unimplemented_visit(node)
928 def visit_subtitle(self, node):
929 self.body.append(self.starttag(node, 'subtitle'))
931 def depart_subtitle(self, node):
932 self.body.append('</subtitle>\n')
933 if isinstance(node.parent, nodes.sidebar):
934 self.body.append('</sidebarinfo>\n')
936 # TODO: system_message
937 visit_system_message = depart_system_message = lambda self, node: None
939 def visit_table(self, node):
940 self.body.append(
941 self.starttag(node, 'informaltable', frame='all')
942 )
944 def depart_table(self, node):
945 self.body.append('</informaltable>\n')
947 # TODO: target
948 visit_target = depart_target = lambda self,node: None
950 def visit_tbody(self, node):
951 self.body.append(self.starttag(node, 'tbody'))
953 def depart_tbody(self, node):
954 self.body.append('</tbody>\n')
956 def visit_term(self, node):
957 self.body.append(self.starttag(node, 'term'))
958 self.body.append('<varname>')
960 def depart_term(self, node):
961 # Leave the end tag "term" to ``visit_definition()``,
962 # in case there's a classifier.
963 self.body.append('</varname>')
965 def visit_tgroup(self, node):
966 self.colnames = []
967 atts = {'cols': node['cols']}
968 self.body.append(self.starttag(node, 'tgroup', **atts))
970 def depart_tgroup(self, node):
971 self.body.append('</tgroup>\n')
973 def visit_thead(self, node):
974 self.body.append(self.starttag(node, 'thead'))
976 def depart_thead(self, node):
977 self.body.append('</thead>\n')
979 def visit_tip(self, node):
980 self.body.append(self.starttag(node, 'tip'))
982 def depart_tip(self, node):
983 self.body.append('</tip>\n')
985 def visit_title(self, node):
986 # HACK: sidebar subtitle needs to go into sidebarinfo
987 # so it's easier to put the title in there too
988 if isinstance(node.parent, nodes.sidebar):
989 if isinstance(node.parent.children[1], nodes.subtitle):
990 self.body.append('<sidebarinfo>')
991 self.body.append(self.starttag(node, 'title', ''))
993 def depart_title(self, node):
994 self.body.append('</title>\n')
996 # XXX just a hack for now
997 def visit_title_reference(self, node):
998 #self.body.append(self.starttag(node, 'emphasis')) # XXX
999 self.body.append('<emphasis>')
1001 # XXX just a hack for now
1002 def depart_title_reference(self, node):
1003 self.body.append('</emphasis>')
1005 def visit_topic(self, node):
1006 # TODO: map dedication to dedication
1008 # Table of Contents generation handled by DocBook
1009 if node.get('class') == 'contents':
1010 raise nodes.SkipChildren
1011 elif node.get('class') == 'abstract':
1012 self.body.append(self.starttag(node, 'abstract'))
1013 self.context.append('abstract')
1014 # generic "topic" element
1015 # XXX I don't really know what else to do with it.
1016 elif node.get('class','') == '':
1017 self.body.append(self.starttag(node, 'section'))
1018 self.context.append('section')
1019 else:
1020 # XXX debug code
1021 print 'class:', node.get('class')
1022 print node.__class__.__name__
1023 print node
1024 print `node`
1025 print dir(node)
1026 self.unimplemented_visit(node)
1028 def depart_topic(self, node):
1029 if len(self.context):
1030 self.body.append('</%s>\n' % (self.context.pop(),))
1032 # QUESTION: what to do for "transition"?
1033 def visit_transition(self, node):
1034 pass
1036 def depart_transition(self, node):
1037 pass
1039 # author is handled in ``visit_docinfo()``
1040 def visit_version(self, node):
1041 raise nodes.SkipNode
1043 def visit_warning(self, node):
1044 self.body.append(self.starttag(node, 'warning'))
1046 def depart_warning(self, node):
1047 self.body.append('</warning>\n')
1049 def unimplemented_visit(self, node):
1050 raise NotImplementedError('visiting unimplemented node type: %s'
1051 % node.__class__.__name__)
1053 # :collapseFolds=0:folding=sidekick:indentSize=4:
1054 # :lineSeparator=\n:noTabs=true:tabSize=4: