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