SEESenv/scripts/docbook.py
changeset 48 c9990f63505d
equal deleted inserted replaced
47:e50530e32ac0 48:c9990f63505d
       
     1 #!/usr/bin/env python
       
     2 
       
     3 """
       
     4 :Author: Ollie Rutherfurd
       
     5 :Contact: oliver@rutherfurd.net
       
     6 :Revision: $Revision: 5918 $
       
     7 :Date: $Date: 2009-04-23 04:45:57 +0200 (Don, 23 Apr 2009) $
       
     8 :Copyright: This module has been placed in the public domain.
       
     9 
       
    10 DocBook XML document tree Writer.
       
    11 
       
    12 This Writer converts a reST document tree to a subset
       
    13 of DocBook.
       
    14 
       
    15 **This is an unfinished work in progress.**
       
    16 """
       
    17 
       
    18 __docformat__ = 'reStructuredText'
       
    19 
       
    20 import re
       
    21 import string
       
    22 from docutils import writers, nodes, languages
       
    23 from types import ListType
       
    24 
       
    25 class Writer(writers.Writer):
       
    26 
       
    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': 'chapter', 
       
    35              'metavar': '<name>',
       
    36              'type': 'choice', 
       
    37              'choices': ('article', 'book', 'chapter',)
       
    38             }
       
    39          ),
       
    40         )
       
    41     )
       
    42 
       
    43 
       
    44     """DocBook does it's own section numbering"""
       
    45     settings_default_overrides = {'enable_section_numbering': 0}
       
    46 
       
    47     output = None
       
    48     """Final translated form of `document`."""
       
    49 
       
    50     def translate(self):
       
    51         visitor = DocBookTranslator(self.document)
       
    52         self.document.walkabout(visitor)
       
    53         self.output = visitor.astext()
       
    54 
       
    55 
       
    56 class DocBookTranslator(nodes.NodeVisitor):
       
    57 
       
    58     XML_DECL = '<?xml version="1.0" encoding="%s"?>\n'
       
    59 
       
    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"""
       
    63 
       
    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,),
       
    73         ]
       
    74         self.doc_footer = [
       
    75             '</%s>\n' % (self.doctype,)
       
    76         ]
       
    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 = ''
       
    86 
       
    87     def astext(self):
       
    88         return ''.join(self.doc_header
       
    89                     + self.docinfo
       
    90                     + self.body
       
    91                     + self.doc_footer)
       
    92 
       
    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
       
   102 
       
   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)
       
   112 
       
   113     def rearrange_footnotes(self):
       
   114         """
       
   115         Replaces ``foonote_reference`` placeholders with
       
   116         ``footnote`` element content as DocBook and reST
       
   117         handle footnotes differently.
       
   118 
       
   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.
       
   125         """
       
   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,)
       
   132 
       
   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))
       
   137 
       
   138     def starttag(self, node, tagname, suffix='\n', infix='', **attributes):
       
   139         """
       
   140         Construct and return a start tag given a node 
       
   141         (id & class attributes are extracted), tag name, 
       
   142         and optional attributes.
       
   143         """
       
   144         atts = {}
       
   145         for (name, value) in attributes.items():
       
   146             atts[name.lower()] = value
       
   147 
       
   148         for att in ('id',):             # node attribute overrides
       
   149             if node.has_key(att):
       
   150                 atts[att] = node[att]
       
   151 
       
   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))
       
   170 
       
   171         return '<%s%s>%s' % (' '.join(parts), infix, suffix)
       
   172 
       
   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)
       
   176 
       
   177     def visit_Text(self, node):
       
   178         self.body.append(self.encode(node.astext()))
       
   179 
       
   180     def depart_Text(self, node):
       
   181         pass
       
   182 
       
   183     def visit_address(self, node):
       
   184         # handled by visit_docinfo
       
   185         pass
       
   186 
       
   187     def depart_address(self, node):
       
   188         # handled by visit_docinfo
       
   189         pass
       
   190 
       
   191     def visit_admonition(self, node, name=''):
       
   192         self.body.append(self.starttag(node, 'note'))
       
   193 
       
   194     def depart_admonition(self, node=None):
       
   195         self.body.append('</note>\n')
       
   196 
       
   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],))
       
   201 
       
   202     def depart_attention(self, node):
       
   203         self.body.append('</note>\n')
       
   204 
       
   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', ''))
       
   210 
       
   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')
       
   215 
       
   216     # author is handled in ``visit_docinfo()``
       
   217     def visit_author(self, node):
       
   218         raise nodes.SkipNode
       
   219 
       
   220     # authors is handled in ``visit_docinfo()``
       
   221     def visit_authors(self, node):
       
   222         raise nodes.SkipNode
       
   223 
       
   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())
       
   228 
       
   229     def depart_block_quote(self, node):
       
   230         self.body.append('</blockquote>\n')
       
   231 
       
   232     def visit_bullet_list(self, node):
       
   233         self.body.append(self.starttag(node, 'itemizedlist'))
       
   234 
       
   235     def depart_bullet_list(self, node):
       
   236         self.body.append('</itemizedlist>\n')
       
   237 
       
   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'))
       
   242 
       
   243     def depart_caption(self, node):
       
   244         self.body.append('</para>')
       
   245 
       
   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],))
       
   250 
       
   251     def depart_caution(self, node):
       
   252         self.body.append('</caution>\n')
       
   253 
       
   254     # reST & DocBook ciations are somewhat 
       
   255     # different creatures.
       
   256     #
       
   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.
       
   262     #
       
   263     # Quote:
       
   264     #
       
   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. 
       
   268     #
       
   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.
       
   275     #
       
   276     # Any ideas or suggestions would be welcome.
       
   277 
       
   278     def visit_citation(self, node):
       
   279         self.visit_footnote(node)
       
   280 
       
   281     def depart_citation(self, node):
       
   282         self.depart_footnote(node)
       
   283 
       
   284     def visit_citation_reference(self, node):
       
   285         self.visit_footnote_reference(node)
       
   286 
       
   287     def depart_citation_reference(self, node):
       
   288         # there isn't a a depart_footnote_reference
       
   289         pass
       
   290 
       
   291     def visit_classifier(self, node):
       
   292         self.body.append(self.starttag(node, 'type'))
       
   293 
       
   294     def depart_classifier(self, node):
       
   295         self.body.append('</type>\n')
       
   296 
       
   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))
       
   301 
       
   302     def depart_colspec(self, node):
       
   303         pass
       
   304 
       
   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
       
   309 
       
   310     # contact is handled in ``visit_docinfo()``
       
   311     def visit_contact(self, node):
       
   312         raise nodes.SkipNode
       
   313 
       
   314     # copyright is handled in ``visit_docinfo()``
       
   315     def visit_copyright(self, node):
       
   316         raise nodes.SkipNode
       
   317 
       
   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],))
       
   322 
       
   323     def depart_danger(self, node):
       
   324         self.body.append('</caution>\n')
       
   325 
       
   326     # date is handled in ``visit_docinfo()``
       
   327     def visit_date(self, node):
       
   328         raise nodes.SkipNode
       
   329 
       
   330     def visit_decoration(self, node):
       
   331         pass
       
   332     def depart_decoration(self, node):
       
   333         pass
       
   334 
       
   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'))
       
   339 
       
   340     def depart_definition(self, node):
       
   341         self.body.append('</listitem>\n')
       
   342 
       
   343     def visit_definition_list(self, node):
       
   344         self.body.append(self.starttag(node, 'variablelist'))
       
   345 
       
   346     def depart_definition_list(self, node):
       
   347         self.body.append('</variablelist>\n')
       
   348 
       
   349     def visit_definition_list_item(self, node):
       
   350         self.body.append(self.starttag(node, 'varlistentry'))
       
   351 
       
   352     def depart_definition_list_item(self, node):
       
   353         self.body.append('</varlistentry>\n')
       
   354 
       
   355     def visit_description(self, node):
       
   356         self.body.append(self.starttag(node, 'entry'))
       
   357 
       
   358     def depart_description(self, node):
       
   359         self.body.append('</entry>\n')
       
   360 
       
   361     def visit_docinfo(self, node):
       
   362         """
       
   363         Collects all docinfo elements for the document.
       
   364 
       
   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.
       
   370 
       
   371         .. NOTE:: Skips processing of all child nodes as
       
   372                   everything should be collected here.
       
   373         """
       
   374 
       
   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).
       
   388 
       
   389         docinfo = ['<%sinfo>\n' % self.doctype]
       
   390 
       
   391         address = ''
       
   392         authors = []
       
   393         author = ''
       
   394         contact = ''
       
   395         date = ''
       
   396         legalnotice = ''
       
   397         orgname = ''
       
   398         releaseinfo = ''
       
   399         revision,version = '',''
       
   400  
       
   401         docinfo.append('<title>%s</title>\n' % self.title)
       
   402         if self.subtitle:
       
   403             docinfo.append('<subtitle>%s</subtitle>\n' % self.subtitle)
       
   404 
       
   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)
       
   440 
       
   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')
       
   450 
       
   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')
       
   457 
       
   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)
       
   465 
       
   466         if date:
       
   467             docinfo.append('<date>%s</date>\n' % date)
       
   468 
       
   469         if orgname:
       
   470             docinfo.append('<orgname>%s</orgname>\n' % orgname)
       
   471 
       
   472         if releaseinfo:
       
   473             docinfo.append('<releaseinfo>%s</releaseinfo>\n' % releaseinfo)
       
   474 
       
   475         if legalnotice:
       
   476             docinfo.append('<legalnotice>\n')
       
   477             docinfo.append('<para>%s</para>\n' % legalnotice)
       
   478             docinfo.append('</legalnotice>\n')
       
   479 
       
   480         if address:
       
   481             docinfo.append('<address xml:space="preserve">' + 
       
   482                 address + '</address>\n')
       
   483 
       
   484         if len(docinfo) > 1:
       
   485             docinfo.append('</%sinfo>\n' % self.doctype)
       
   486 
       
   487         self.docinfo = docinfo
       
   488 
       
   489         raise nodes.SkipChildren
       
   490 
       
   491     def depart_docinfo(self, node):
       
   492         pass
       
   493 
       
   494     def visit_doctest_block(self, node):
       
   495         self.body.append('<informalexample>\n')
       
   496         self.body.append(self.starttag(node, 'programlisting'))
       
   497 
       
   498     def depart_doctest_block(self, node):
       
   499         self.body.append('</programlisting>\n')
       
   500         self.body.append('</informalexample>\n')
       
   501 
       
   502     def visit_document(self, node):
       
   503         pass
       
   504 
       
   505     def depart_document(self, node):
       
   506         self.rearrange_footnotes()
       
   507 
       
   508     def visit_emphasis(self, node):
       
   509         self.body.append('<emphasis>')
       
   510 
       
   511     def depart_emphasis(self, node):
       
   512         self.body.append('</emphasis>')
       
   513 
       
   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))
       
   525 
       
   526     def depart_entry(self, node):
       
   527         self.body.append('</entry>\n')
       
   528 
       
   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'))
       
   532 
       
   533     def depart_enumerated_list(self, node):
       
   534         self.body.append('</orderedlist>\n')
       
   535 
       
   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],))
       
   540 
       
   541     def depart_error(self, node):
       
   542         self.body.append('</caution>\n')
       
   543 
       
   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'))
       
   547 
       
   548     def depart_field(self, node):
       
   549         self.body.append('</varlistentry>\n')
       
   550 
       
   551     # TODO: see if this should be wrapped with some element
       
   552     def visit_field_argument(self, node):
       
   553         self.body.append(' ')
       
   554 
       
   555     def depart_field_argument(self, node):
       
   556         pass
       
   557 
       
   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'))
       
   564 
       
   565     def depart_field_body(self, node):
       
   566         self.body.append('</listitem>\n')
       
   567 
       
   568     def visit_field_list(self, node):
       
   569         self.body.append(self.starttag(node, 'variablelist'))
       
   570 
       
   571     def depart_field_list(self, node):
       
   572         self.body.append('</variablelist>\n')
       
   573 
       
   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')
       
   579 
       
   580     def depart_field_name(self, node):
       
   581         pass
       
   582 
       
   583     def visit_figure(self, node):
       
   584         self.body.append(self.starttag(node, 'informalfigure'))
       
   585         self.body.append('<blockquote>')
       
   586 
       
   587     def depart_figure(self, node):
       
   588         self.body.append('</blockquote>')
       
   589         self.body.append('</informalfigure>\n')
       
   590 
       
   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
       
   595 
       
   596     def depart_footer(self, node):
       
   597         pass
       
   598 
       
   599     def visit_footnote(self, node):
       
   600         self.footnotes[node['ids'][0]] = []
       
   601         atts = {'id': node['ids'][0]}
       
   602         if isinstance(node[0], nodes.label):
       
   603             atts['label'] = node[0].astext()
       
   604         self.footnotes[node['ids'][0]].append(
       
   605             self.starttag(node, 'footnote', **atts))
       
   606 
       
   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['ids'][0]]
       
   614 
       
   615     def depart_footnote(self, node):
       
   616         # finish footnote and then replace footnote collector
       
   617         # with real body list.
       
   618         self.footnotes[node['ids'][0]].append('</footnote>')
       
   619         self.body = self._body
       
   620         self._body = None
       
   621 
       
   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']]
       
   627 
       
   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['ids'][0], self.body, len(self.body),))
       
   637         self.footnote_map[refid] = refs
       
   638 
       
   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 -->')
       
   643 
       
   644         raise nodes.SkipNode
       
   645 
       
   646     def visit_header(self, node):
       
   647         pass
       
   648     def depart_header(self, node):
       
   649         pass
       
   650 
       
   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
       
   656 
       
   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],))
       
   661 
       
   662     def depart_hint(self, node):
       
   663         self.body.append('</note>\n')
       
   664 
       
   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)
       
   690 
       
   691     def depart_image(self, node):
       
   692         pass
       
   693 
       
   694     def visit_important(self, node):
       
   695         self.body.append(self.starttag(node, 'important'))
       
   696 
       
   697     def depart_important(self, node):
       
   698         self.body.append('</important>')
       
   699 
       
   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')
       
   705 
       
   706     def depart_interpreted(self, node):
       
   707         self.body.append('</constant>\n')
       
   708 
       
   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
       
   718 
       
   719     def depart_label(self, node):
       
   720         pass
       
   721 
       
   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
       
   726 
       
   727     def depart_legend(self, node):
       
   728         pass
       
   729 
       
   730     def visit_line(self,node):
       
   731         pass
       
   732 
       
   733     def depart_line(self,node):
       
   734         pass
       
   735         
       
   736     def visit_line_block(self, node):
       
   737         self.body.append(self.starttag(node, 'literallayout'))
       
   738 
       
   739     def depart_line_block(self, node):
       
   740         self.body.append('</literallayout>\n')
       
   741 
       
   742     def visit_list_item(self, node):
       
   743         self.body.append(self.starttag(node, 'listitem'))
       
   744 
       
   745     def depart_list_item(self, node):
       
   746         self.body.append('</listitem>\n')
       
   747 
       
   748     def visit_literal(self, node):
       
   749          self.body.append('<literal>')
       
   750 
       
   751     def depart_literal(self, node):
       
   752         self.body.append('</literal>')
       
   753 
       
   754     def visit_literal_block(self, node):
       
   755         self.body.append(self.starttag(node, 'programlisting'))
       
   756 
       
   757     def depart_literal_block(self, node):
       
   758         self.body.append('</programlisting>\n')
       
   759 
       
   760     def visit_note(self, node):
       
   761         self.body.append(self.starttag(node, 'note'))
       
   762         self.body.append('\n<title>%s</title>\n' 
       
   763             % (self.language.labels[node.tagname],))
       
   764 
       
   765     def depart_note(self, node):
       
   766         self.body.append('</note>\n')
       
   767 
       
   768     def visit_option(self, node):
       
   769         self.body.append(self.starttag(node, 'command'))
       
   770         if self.context[-1]:
       
   771             self.body.append(', ')
       
   772 
       
   773     def depart_option(self, node):
       
   774         self.context[-1] += 1
       
   775         self.body.append('</command>')
       
   776 
       
   777     def visit_option_argument(self, node):
       
   778         self.body.append(node.get('delimiter', ' '))
       
   779         self.body.append(self.starttag(node, 'replaceable', ''))
       
   780 
       
   781     def depart_option_argument(self, node):
       
   782         self.body.append('</replaceable>')
       
   783 
       
   784     def visit_option_group(self, node):
       
   785         self.body.append(self.starttag(node, 'entry'))
       
   786         self.context.append(0)
       
   787 
       
   788     def depart_option_group(self, node):
       
   789         self.context.pop()
       
   790         self.body.append('</entry>\n')
       
   791 
       
   792     def visit_option_list(self, node):
       
   793         self.body.append(self.starttag(node, 'informaltable', frame='all'))
       
   794         self.body.append('<tgroup cols="2">\n')
       
   795         self.body.append('<colspec colname="option_col"/>\n')
       
   796         self.body.append('<colspec colname="description_col"/>\n')
       
   797         self.body.append('<tbody>\n')
       
   798 
       
   799     def depart_option_list(self, node):
       
   800         self.body.append('</tbody>')
       
   801         self.body.append('</tgroup>\n')
       
   802         self.body.append('</informaltable>\n')
       
   803 
       
   804     def visit_option_list_item(self, node):
       
   805         self.body.append(self.starttag(node, 'row'))
       
   806 
       
   807     def depart_option_list_item(self, node):
       
   808         self.body.append('</row>\n')
       
   809 
       
   810     def visit_option_string(self, node):
       
   811         pass
       
   812 
       
   813     def depart_option_string(self, node):
       
   814         pass
       
   815 
       
   816     # organization is handled in ``visit_docinfo()``
       
   817     def visit_organization(self, node):
       
   818         raise nodes.SkipNode
       
   819 
       
   820     def visit_paragraph(self, node):
       
   821         self.body.append(self.starttag(node, 'para', ''))
       
   822 
       
   823     def depart_paragraph(self, node):
       
   824         self.body.append('</para>')
       
   825 
       
   826     # TODO: problematic
       
   827     visit_problematic = depart_problematic = lambda self, node: None
       
   828 
       
   829     def visit_raw(self, node):
       
   830         if node.has_key('format') and node['format'] == 'docbook':
       
   831             self.body.append(node.astext())
       
   832         raise nodes.SkipNode
       
   833 
       
   834     def visit_reference(self, node):
       
   835         atts = {}
       
   836         if node.has_key('refuri'):
       
   837             atts['url'] = node['refuri']
       
   838             self.context.append('ulink')
       
   839         elif node.has_key('refid'):
       
   840             atts['linkend'] = node['refid']
       
   841             self.context.append('link')
       
   842         elif node.has_key('refname'):
       
   843             atts['linkend'] = self.document.nameids[node['refname']]
       
   844             self.context.append('link')
       
   845         # if parent is a section, 
       
   846         # wrap link in a para
       
   847         if isinstance(node.parent, nodes.section):
       
   848             self.body.append('<para>')
       
   849         self.body.append(self.starttag(node, self.context[-1], '', **atts))
       
   850 
       
   851     def depart_reference(self, node):
       
   852         self.body.append('</%s>' % (self.context.pop(),))
       
   853         # if parent is a section, 
       
   854         # wrap link in a para
       
   855         if isinstance(node.parent, nodes.section):
       
   856             self.body.append('</para>')
       
   857 
       
   858     # revision is handled in ``visit_docinfo()``
       
   859     def visit_revision(self, node):
       
   860         raise nodes.SkipNode
       
   861 
       
   862     def visit_row(self, node):
       
   863         self.entry_level = 0
       
   864         self.body.append(self.starttag(node, 'row'))
       
   865 
       
   866     def depart_row(self, node):
       
   867         self.body.append('</row>\n')
       
   868 
       
   869     def visit_rubric(self, node):
       
   870         self.body.append(self.starttag(node, 'bridgehead'))
       
   871 
       
   872     def depart_rubric(self, node):
       
   873         self.body.append('</bridgehead>')
       
   874 
       
   875     def visit_section(self, node):
       
   876         if self.section == 0 and self.doctype == 'book':
       
   877             self.body.append(self.starttag(node, 'chapter'))
       
   878         else:
       
   879             self.body.append(self.starttag(node, 'section'))
       
   880         self.section += 1
       
   881 
       
   882     def depart_section(self, node):
       
   883         self.section -= 1
       
   884         if self.section == 0 and self.doctype == 'book':
       
   885             self.body.append('</chapter>\n')
       
   886         else:
       
   887             self.body.append('</section>\n')
       
   888 
       
   889     def visit_sidebar(self, node):
       
   890         self.body.append(self.starttag(node, 'sidebar'))
       
   891         if isinstance(node[0], nodes.title):
       
   892             self.body.append('<sidebarinfo>\n')
       
   893             self.body.append('<title>%s</title>\n' % node[0].astext())
       
   894             if isinstance(node[1], nodes.subtitle):
       
   895                 self.body.append('<subtitle>%s</subtitle>\n' % node[1].astext())
       
   896             self.body.append('</sidebarinfo>\n')
       
   897 
       
   898     def depart_sidebar(self, node):
       
   899         self.body.append('</sidebar>\n')
       
   900 
       
   901     # author is handled in ``visit_docinfo()``
       
   902     def visit_status(self, node):
       
   903         raise nodes.SkipNode
       
   904 
       
   905     def visit_strong(self, node):
       
   906         self.body.append('<emphasis role="strong">')
       
   907 
       
   908     def depart_strong(self, node):
       
   909         self.body.append('</emphasis>')
       
   910 
       
   911     def visit_subscript(self, node):
       
   912         self.body.append(self.starttag(node, 'subscript', ''))
       
   913 
       
   914     def depart_subscript(self, node):
       
   915         self.body.append('</subscript>')
       
   916 
       
   917     def visit_substitution_definition(self, node):
       
   918         raise nodes.SkipNode
       
   919 
       
   920     def visit_substitution_reference(self, node):
       
   921         self.unimplemented_visit(node)
       
   922 
       
   923     def visit_subtitle(self, node):
       
   924         # document title needs to go into
       
   925         # <type>info/subtitle, so save it for
       
   926         # when we do visit_docinfo
       
   927         if isinstance(node.parent, nodes.document):
       
   928             self.subtitle = node.astext()
       
   929             raise nodes.SkipNode
       
   930         else:
       
   931             # sidebar subtitle needs to go into a sidebarinfo element
       
   932             #if isinstance(node.parent, nodes.sidebar):
       
   933             #    self.body.append('<sidebarinfo>')
       
   934             if isinstance(node.parent, nodes.sidebar):
       
   935                 raise nodes.SkipNode
       
   936             self.body.append(self.starttag(node, 'subtitle', ''))
       
   937 
       
   938     def depart_subtitle(self, node):
       
   939         if not isinstance(node.parent, nodes.document):
       
   940             self.body.append('</subtitle>\n')
       
   941         #if isinstance(node.parent, nodes.sidebar):
       
   942         #    self.body.append('</sidebarinfo>\n')
       
   943 
       
   944     def visit_superscript(self, node):
       
   945         self.body.append(self.starttag(node, 'superscript', ''))
       
   946 
       
   947     def depart_superscript(self, node):
       
   948         self.body.append('</superscript>')
       
   949 
       
   950     # TODO: system_message
       
   951     visit_system_message = depart_system_message = lambda self, node: None
       
   952 
       
   953     def visit_table(self, node):
       
   954         self.body.append(
       
   955             self.starttag(node, 'informaltable', frame='all')
       
   956         )
       
   957 
       
   958     def depart_table(self, node):
       
   959         self.body.append('</informaltable>\n')
       
   960 
       
   961     # don't think anything is needed for targets
       
   962     def visit_target(self, node):
       
   963         # XXX this would like to be a transform!
       
   964         # XXX comment this mess!
       
   965         handled = 0
       
   966         siblings = node.parent.children
       
   967         for i in range(len(siblings)):
       
   968             if siblings[i] is node:
       
   969                 if i+1 < len(siblings):
       
   970                     next = siblings[i+1]
       
   971                     if isinstance(next,nodes.Text):
       
   972                         pass
       
   973                     elif not next.attributes.has_key('id'):
       
   974                         next['id'] = node['ids'][0]
       
   975                         handled = 1
       
   976         if not handled:
       
   977             if not node.parent.attributes.has_key('id'):
       
   978                 # TODO node["ids"] 
       
   979                 node.parent.attributes['id'] = node['ids'][0]
       
   980                 handled = 1
       
   981         # might need to do more...
       
   982         # (if not handled, update the referrer to refer to the parent's id)
       
   983 
       
   984     def depart_target(self, node):
       
   985         pass
       
   986 
       
   987     def visit_tbody(self, node):
       
   988         self.body.append(self.starttag(node, 'tbody'))
       
   989 
       
   990     def depart_tbody(self, node):
       
   991         self.body.append('</tbody>\n')
       
   992 
       
   993     def visit_term(self, node):
       
   994         self.body.append(self.starttag(node, 'term'))
       
   995         self.body.append('<varname>')
       
   996 
       
   997     def depart_term(self, node):
       
   998         # Leave the end tag "term" to ``visit_definition()``,
       
   999         # in case there's a classifier.
       
  1000         self.body.append('</varname>')
       
  1001 
       
  1002     def visit_tgroup(self, node):
       
  1003         self.colnames = []
       
  1004         atts = {'cols': node['cols']}
       
  1005         self.body.append(self.starttag(node, 'tgroup', **atts))
       
  1006 
       
  1007     def depart_tgroup(self, node):
       
  1008         self.body.append('</tgroup>\n')
       
  1009 
       
  1010     def visit_thead(self, node):
       
  1011         self.body.append(self.starttag(node, 'thead'))
       
  1012 
       
  1013     def depart_thead(self, node):
       
  1014         self.body.append('</thead>\n')
       
  1015 
       
  1016     def visit_tip(self, node):
       
  1017         self.body.append(self.starttag(node, 'tip'))
       
  1018 
       
  1019     def depart_tip(self, node):
       
  1020         self.body.append('</tip>\n')
       
  1021 
       
  1022     def visit_title(self, node):
       
  1023         # document title needs to go inside
       
  1024         # <type>info/title
       
  1025         if isinstance(node.parent, nodes.document):
       
  1026             self.title = node.astext()
       
  1027             raise nodes.SkipNode
       
  1028         elif isinstance(node.parent, nodes.sidebar):
       
  1029             # sidebar title and subtitle are collected in visit_sidebar
       
  1030             raise nodes.SkipNode
       
  1031         else:
       
  1032             self.body.append(self.starttag(node, 'title', ''))
       
  1033 
       
  1034     def depart_title(self, node):
       
  1035         if not isinstance(node.parent, nodes.document):
       
  1036             self.body.append('</title>\n')
       
  1037 
       
  1038     def visit_title_reference(self, node):
       
  1039         self.body.append('<citetitle>')
       
  1040 
       
  1041     def depart_title_reference(self, node):
       
  1042         self.body.append('</citetitle>')
       
  1043 
       
  1044     def visit_topic(self, node):
       
  1045         # let DocBook handle Table of Contents generation
       
  1046         if node.get('class') == 'contents':
       
  1047             raise nodes.SkipChildren
       
  1048         elif node.get('class') == 'abstract':
       
  1049             self.body.append(self.starttag(node, 'abstract'))
       
  1050             self.context.append('abstract')
       
  1051         elif node.get('class') == 'dedication':
       
  1052             # docbook only supports dedication in a book,
       
  1053             # so we're faking it for article & chapter
       
  1054             if self.doctype == 'book':
       
  1055                 self.body.append(self.starttag(node, 'dedication'))
       
  1056                 self.context.append('dedication')
       
  1057             else:
       
  1058                 self.body.append(self.starttag(node, 'section'))
       
  1059                 self.context.append('section')
       
  1060 
       
  1061         # generic "topic" element treated as a section
       
  1062         elif node.get('class','') == '':
       
  1063             self.body.append(self.starttag(node, 'section'))
       
  1064             self.context.append('section')
       
  1065         else:
       
  1066             # XXX DEBUG CODE
       
  1067             print 'class:', node.get('class')
       
  1068             print node.__class__.__name__
       
  1069             print node
       
  1070             print `node`
       
  1071             print dir(node)
       
  1072             self.unimplemented_visit(node)
       
  1073 
       
  1074     def depart_topic(self, node):
       
  1075         if len(self.context):
       
  1076             self.body.append('</%s>\n' % (self.context.pop(),))
       
  1077 
       
  1078     def visit_transition(self, node):
       
  1079         pass
       
  1080     def depart_transition(self, node):
       
  1081         pass
       
  1082 
       
  1083     # author is handled in ``visit_docinfo()``
       
  1084     def visit_version(self, node):
       
  1085         raise nodes.SkipNode
       
  1086 
       
  1087     def visit_warning(self, node):
       
  1088         self.body.append(self.starttag(node, 'warning'))
       
  1089 
       
  1090     def depart_warning(self, node):
       
  1091         self.body.append('</warning>\n')
       
  1092 
       
  1093     def unimplemented_visit(self, node):
       
  1094         raise NotImplementedError('visiting unimplemented node type: %s'
       
  1095                 % node.__class__.__name__)
       
  1096 
       
  1097 # :collapseFolds=0:folding=indent:indentSize=4:
       
  1098 # :lineSeparator=\n:noTabs=true:tabSize=4: