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