Add <target> to one more testcase (see r8206).
[docutils.git] / sandbox / rst2odp / bin / rst2odp.py
blob3f61c7daca4847c24249a876d858777c713dcb90
1 #!/usr/bin/env python
2 # -*- coding: utf-8 -*-
3 # Copyright 2008-2009 Matt Harrison
4 # Licensed under Apache License, Version 2.0 (current)
5 import sys
6 import os
8 import docutils
9 from docutils import io, writers, nodes
10 from docutils.readers import standalone
11 from docutils.core import Publisher, default_description, \
12 default_usage
13 from docutils.parsers import rst
15 import odplib.preso as preso
18 S5_COLORS = dict(
19 black='#000000',
20 gray='#545454',
21 silver='#c0c0c0',
22 white='#ffffff',
23 maroon='#b03060',
24 red='#ff0000',
25 magenta='#ff00ff',
26 fuchsia='#ff00ff', # FIX
27 pink='#ff1493',
28 orange='#ffa500',
29 yellow='#ffff00',
30 lime='#32cd32',
31 green='#00ff00',
32 olive='#6b8e23',
33 teal='#008080',
34 cyan='#00ffff',
35 aqua='#00ffff', # FIX
36 blue='#0000ff',
37 navy='#000080',
38 purple='#a020f0'
41 S5_SIZES = dict(
42 huge='66pt',
43 big='44pt',
44 normal='28pt',
45 small='22pt',
46 tiny='18pt'
49 class SyntaxHighlightCodeBlock(rst.Directive):
50 required_arguments = 1
51 optional_arguments = 0
52 has_content = True
54 # See visit_literal_block for code that processes the node
55 # created here.
56 def run(self):
57 language = self.arguments[0]
58 code_block = nodes.literal_block(classes=["code-block", language],
59 language=language)
60 lines = self.content
61 content = '\n'.join(lines)
62 text_node = nodes.Text(content)
63 code_block.append(text_node)
64 # Mark this node for high-lighting so that visit_literal_block
65 # will be able to hight-light those produced here and
66 # *not* high-light regular literal blocks (:: in reST).
67 code_block['hilight'] = True
68 return [code_block]
70 rst.directives.register_directive('code-block', SyntaxHighlightCodeBlock)
72 class ImportNode(nodes.General, nodes.Inline, nodes.Element): pass
74 class ImportSlideBlock(rst.Directive):
75 required_arguments = 2
76 optional_arguments = 0
77 has_content = False
78 node_class = ImportNode
80 # See visit_literal_block for code that processes the node
81 # created here.
82 def run(self):
83 odp_path = self.arguments[0]
84 page_num = self.arguments[1]
85 node = ImportNode(odp_path=odp_path, page_num=page_num)
86 return [node]
88 rst.directives.register_directive('importslide', ImportSlideBlock)
90 class Writer(writers.Writer):
91 settings_spec = (
92 'ODP Specific Options', # option group title
93 None, # Description
94 ( # options (help string, list of options, dictions of OptionParser.add_option dicts)
95 ('Specify a template (.otp) to use for styling',
96 ['--template-file'],
97 {'action': 'store',
98 'dest': 'template_file'}),
99 ('Specify a monospace font to use ("Courier New" default)',
100 ['--mono-font'],
101 {'action': 'store',
102 'dest': 'mono_font'}),
103 ('Specify a normal font to use ("Arial" default)',
104 ['--font'],
105 {'action': 'store',
106 'dest': 'font'}),
107 ('Specify pages to export (2,3,9-10)',
108 ['--pages-to-output'],
109 {'action': 'store',
110 'dest': 'pages_to_output'})
113 def __init__(self):
114 writers.Writer.__init__(self)
115 self.translator_class = ODPTranslator
117 def translate(self):
118 self.visitor = self.translator_class(self.document)
119 self.document.walkabout(self.visitor)
120 self.parts['whole'] = self.visitor.get_whole()
121 self.output = self.parts['whole']
122 self.parts['encoding'] = self.document.settings.output_encoding
123 self.parts['version'] = docutils.__version__
125 class ODPTranslator(nodes.GenericNodeVisitor):
126 def __init__(self, document):
127 nodes.GenericNodeVisitor.__init__(self, document)
128 self.settings = document.settings
129 self.preso = preso.Preso()
131 if self.settings.pages_to_output:
132 self.preso.limit_pages = num_string_to_list(self.settings.pages_to_output)
134 if self.settings.mono_font:
135 preso.MONO_FONT = self.settings.mono_font
137 if self.settings.font:
138 preso.NORMAL_FONT = self.settings.font
140 self.in_node = {} # map of tagname to True if we are in/under this
141 self._reset()
143 def _reset(self):
144 # state we keep track of
145 self.cur_slide = None
146 self.bullet_list = None
147 self.bullet_depth = 0
148 self.footer = None
150 def _init_slide(self, force=False):
151 if force or self.cur_slide is None:
152 self._reset()
153 self.cur_slide = self.preso.add_slide()
155 def at(self, nodename):
157 shortcut for at/under this node
159 return self.in_node.get(nodename, False)
161 def get_whole(self):
162 return self.preso.get_data(self.settings.template_file)
164 def dispatch_visit(self, node):
165 # Easier just to throw nodes I'm in in a dict, than keeping
166 # state for each one
167 count = self.in_node.setdefault(node.tagname, 0)
168 self.in_node[node.tagname] += 1
169 nodes.GenericNodeVisitor.dispatch_visit(self, node)
171 def dispatch_departure(self, node):
172 self.in_node[node.tagname] -= 1
173 nodes.GenericNodeVisitor.dispatch_departure(self, node)
175 def default_visit(self, node):
176 if self.settings.report_level >= 3:
177 print "ERR! NODE", node, node.tagname
178 raise NotImplementedError('node is %r, tag is %s' % (node, node.tagname))
181 def default_departure(self, node):
182 if self.settings.report_level >= 3:
183 print "NODE", node, node.tagname
184 raise NotImplementedError
187 def _dumb_visit(self, node):
188 pass
189 _dumb_depart = _dumb_visit
192 def visit_document(self, node):
193 if self.settings.report_level >= 3:
194 print "DOC", node
196 depart_document = _dumb_depart
198 def visit_title(self, node):
199 if self.at('section') < 2:
200 self._init_slide()
201 if self.at('topic'):
202 return
203 elif self.at('sidebar'):
204 return
205 elif self.at('section') < 2:
206 self.cur_slide.add_title_frame()
208 def depart_title(self, node):
209 if self.at('topic'):
210 return
211 elif self.at('sidebar'):
212 return
213 elif self.at('section') < 2:
214 # not in a title element anymore
215 self.cur_slide.cur_element = None
216 else:
217 pass
220 def visit_Text(self, node):
221 if self.bullet_list and not self.at('handout'): # !!!need to deal w/ bullets in handout
222 self.bullet_list.write(node.astext())
223 elif self.at('footer'):
224 self.footer.write(node.astext())
225 elif self.at('comment'):
226 pass
227 elif self.at('topic'):
228 pass
229 elif self.at('literal_block'):
230 pass
231 elif self.at('reference'):
232 # FirstClown - if we have link text, we need to make sure the text
233 # doesn't get any styles applied or it sometimes doesn't show.
234 self.cur_slide.write(node.astext(), add_p_style=False, add_t_style=False)
235 elif self.at('doctest_block'):
236 pass
237 elif self.at('field_name'):
238 pass
239 else:
240 self.cur_slide.write(node.astext())
241 depart_Text = _dumb_depart
243 def _push_handout(self, classes):
244 if 'handout' in classes:
245 self.in_node['handout'] = True
246 self.cur_slide.push_element()
247 if not self.cur_slide.notes_frame:
248 self.cur_slide.add_notes_frame()
249 else:
250 self.cur_slide.cur_element = self.cur_slide.notes_frame
251 self.cur_slide.insert_line_break += 1
252 self.cur_slide.insert_line_breaks()
255 def visit_paragraph(self, node):
256 classes = node.attributes.get('classes', [])
257 self._push_handout(classes)
259 ## if 'center' in classes:
260 ## attribs = {'fo:text-align':'center',
261 ## 'fo:margin-left':'1.2cm',
262 ## 'fo:margin-right':'-.9cm',
263 ## }
264 ## style = preso.ParagraphStyle(**attribs)
265 ## self.cur_slide.push_style(style)
267 ## elif 'left' in classes:
268 ## pass # default
269 ## elif 'right' in classes:
270 ## attribs = {'fo:text-align':'end',
271 ## }
272 ## style = preso.ParagraphStyle(**attribs)
273 ## self.cur_slide.push_style(style)
274 p_attribs = self._get_para_attribs(node)
275 if p_attribs:
276 self.cur_slide.push_style(preso.ParagraphStyle(**p_attribs))
278 # text styles
279 attribs = self._get_text_attribs(node)
280 if attribs:
281 style = preso.TextStyle(**attribs)
282 self.cur_slide.push_style(style)
284 if self.bullet_list:
285 pass
286 elif self.at('topic'):
287 return
288 elif self.at('block_quote'):
289 return # block quote adds paragraph style
290 elif self.at('doctest_block'):
291 pass
294 def depart_paragraph(self, node):
295 # add newline
296 if not self.at('list_item'):
297 self.depart_line(node)
299 classes = node.attributes.get('classes', [])
300 if 'center' in classes or 'right' in classes:
301 self.cur_slide.pop_node()
302 if 'handout' in classes:
303 self.in_node['handout'] = False
304 self.cur_slide.pop_element()
306 if self._get_text_attribs(node):
307 # pop off text:span
308 self.cur_slide.pop_node()
309 self.cur_slide.pop_style()
311 elif self.at('topic'):
312 return
313 elif self.at('block_quote'):
314 return # block quote adds paragraph style
315 else:
316 self.cur_slide.parent_of('text:p')
318 visit_definition = _dumb_visit
319 depart_definition = _dumb_depart
321 def visit_bullet_list(self, node):
322 if self.at('topic'):
323 return
324 p_attribs = self._get_para_attribs(node)
325 if p_attribs:
326 self.cur_slide.push_style(preso.ParagraphStyle(**p_attribs))
328 attribs = self._get_text_attribs(node)
329 if attribs:
330 style = preso.TextStyle(**attribs)
331 self.cur_slide.push_style(style)
333 classes = node.attributes.get('classes', [])
334 if 'handout' in classes:
335 self._push_handout(classes)
337 self.bullet_depth += 1
338 if not self.bullet_list:
339 # start a new list
340 self.bullet_list = preso.OutlineList(self.cur_slide)
341 self.cur_slide.add_list(self.bullet_list)
342 else:
343 # indent one more
344 self.bullet_list.indent()
345 if 'incremental' in node.attributes.get('classes', []):
346 self.in_node['incremental'] = True
348 def depart_bullet_list(self, node):
349 if self.at('topic'):
350 return
352 classes = node.attributes.get('classes', [])
353 if 'handout' in classes:
354 self.in_node['handout'] = False
355 self.cur_slide.pop_element()
357 self.bullet_depth -= 1
358 if self.bullet_depth == 0:
359 # done with list
360 self.bullet_list = None
361 self.cur_slide.pop_element()
362 self.cur_slide.insert_line_break += 1
363 else:
364 self.bullet_list.dedent()
365 if 'incremental' in node.attributes.get('classes', []):
366 self.in_node['incremental'] = False
368 visit_definition_list = visit_bullet_list
369 depart_definition_list = depart_bullet_list
372 def visit_list_item(self, node):
373 if self.at('topic'):
374 return
375 if self.at('incremental'):
376 self.cur_slide.start_animation(preso.Animation())
377 self.bullet_list.new_item()
379 def depart_list_item(self, node):
380 if self.at('topic'):
381 return
382 if self.at('incremental'):
383 self.cur_slide.end_animation()
385 visit_definition_list_item = visit_list_item
386 depart_definition_list_item = depart_list_item
389 visit_decoration = _dumb_visit
390 depart_decoration = _dumb_depart
392 def visit_footer(self, node):
393 self.footer = preso.Footer(self.cur_slide)
395 def depart_footer(self, node):
396 self.preso.add_footer(self.footer)
397 self.footer = None
399 visit_docinfo = _dumb_visit
400 depart_docinfo = _dumb_depart
402 # bibliographic elements
403 def visit_author(self, node):
404 self.visit_attribution(node)
406 def depart_author(self, node):
407 self.depart_line(node) # add new-line
408 self.depart_attribution(node)
410 visit_copyright = visit_author
411 depart_copyright = depart_author
413 def visit_date(self, node):
414 self.visit_attribution(node)
416 def depart_date(self, node):
417 self.depart_line(node) # add new-line
418 self.depart_attribution(node)
420 def visit_field(self, node):
421 pass
423 def depart_field(self, node):
424 pass
426 def visit_field_name(self, node):
427 pass # maybe put this somewhere
429 def depart_field_name(self, node):
430 pass
432 def visit_field_body(self, node):
433 self.visit_attribution(node)
435 def depart_field_body(self, node):
436 self.depart_attribution(node)
438 def visit_comment(self, node):
439 pass
440 def depart_comment(self, node):
441 pass
443 visit_topic = _dumb_visit
444 depart_topic = _dumb_depart
446 def visit_reference(self, node):
448 <draw:text-box>
449 <text:p text:style-name="P4">
450 <text:a xlink:href="http://www.yahoo.com/">Yahoo corp</text:a>
451 </text:p>
452 </draw:text-box>
454 if node.has_key('refid'):
455 return
456 elif self.at('topic'):
457 return
458 elif self.at('field_body'):
459 self.visit_attribution(node)
460 self.cur_slide.push_pending_node('text:a', {'xlink:href': '%s' % node['refuri'],
461 'xlink:type': 'simple'})
462 self.cur_slide.write(node.astext())
463 else:
464 # needs to be in a p
465 # need to hack a bit, since .write usually inserts text:p and text:span
466 if not self.cur_slide.cur_element or not self.cur_slide.cur_element._in_p():
467 #self.cur_slide.add_node('text:p', {})
468 # pyohio code
469 # we write an empty string since write() creates the paragraph
470 # we need, with the style needed to reset from a possible Title
471 # P0 style. This was most apparent when a link was first word
472 # in a section after a title.
473 self.cur_slide.write("")
475 self.cur_slide.add_node('text:a', attrib={'xlink:href': '%s' % node['refuri'],
476 'xlink:type': 'simple'})
478 def depart_reference(self, node):
479 if node.has_key('refid'):
480 return
481 elif self.at('topic'):
482 return
483 elif self.at('field_body'):
484 self.depart_attribution(node)
485 self.cur_slide.parent_of('text:a')
486 else:
487 self.cur_slide.parent_of('text:a')
489 def visit_target(self,node):
490 # Skip the target element since the <reference> of the target is
491 # responsible for writing out the content
492 pass
494 def depart_target(self, node):
495 pass
497 def visit_container(self, node):
498 classes = node.attributes.get('classes', [])
499 self._push_handout(classes)
501 def depart_container(self, node):
502 if self.in_node.get('handout', False):
503 self.cur_slide.pop_element()
504 self.in_node['handout'] = False
506 visit_substitution_definition = _dumb_visit
507 depart_substitution_definition = _dumb_depart
509 def visit_section(self, node):
511 # first page has no section
512 if self.at('section') < 2:
513 # don't create slide for subsections
514 self._init_slide(force=True)
516 def depart_section(self, node):
517 if self.at('section') < 1:
518 self._reset()
520 def visit_transition(self, node):
521 # hack to have titleless slides (transitions come in between sections)
522 self._reset()
523 self._init_slide(force=True)
525 depart_transition = _dumb_depart
527 def visit_literal(self, node):
528 style = preso.TextStyle(**{
529 'fo:font-family':preso.MONO_FONT,
530 'style:font-family-generic':"swiss",
531 'style:font-pitch':"fixed"})
532 self.cur_slide.push_style(style)
534 def depart_literal(self, node):
535 # pop off the text:span
536 self.cur_slide.pop_style()
537 self.cur_slide.pop_node()
539 def visit_inline(self,node):
540 attribs = self._get_text_attribs(node)
541 if attribs:
542 style = preso.TextStyle(**attribs)
544 self.cur_slide.push_style(style)
545 if 'incremental' in node.attributes.get('classes', []):
546 self.cur_slide.start_animation(preso.Animation())
548 def depart_inline(self, node):
549 # pop off the text:span
550 attribs = self._get_text_attribs(node)
551 if attribs:
552 self.cur_slide.pop_style()
553 self.cur_slide.pop_node()
554 if 'incremental' in node.attributes.get('classes', []):
555 self.cur_slide.end_animation()
557 def visit_emphasis(self, node):
558 attribs = {'fo:font-style':'italic'}
559 style = preso.TextStyle(**attribs)
560 self.cur_slide.push_style(style)
562 def depart_emphasis(self, node):
563 # pop off the text:span
564 self.cur_slide.pop_style()
565 self.cur_slide.pop_node()
567 visit_title_reference = visit_emphasis
568 depart_title_reference = depart_emphasis
571 def visit_strong(self, node):
572 attribs = {'fo:font-weight':'bold'}
573 style = preso.TextStyle(**attribs)
574 self.cur_slide.push_style(style)
576 def depart_strong(self, node):
577 # pop off the text:span
578 self.cur_slide.pop_style()
579 self.cur_slide.pop_node()
581 visit_term = visit_strong
582 depart_term = depart_strong
584 def visit_superscript(self, node):
585 attribs = {'style:text-position':'super 58%'}
586 style = preso.TextStyle(**attribs)
587 self.cur_slide.push_style(style)
589 def depart_superscript(self, node):
590 # pop off the text:span
591 self.cur_slide.pop_style()
592 self.cur_slide.pop_node()
594 def visit_subscript(self, node):
595 attribs = {'style:text-position':'sub 58%'}
596 style = preso.TextStyle(**attribs)
597 self.cur_slide.push_style(style)
599 def depart_subscript(self, node):
600 # pop off the text:span
601 self.cur_slide.pop_style()
602 self.cur_slide.pop_node()
604 def visit_block_quote(self, node):
605 attribs = {'fo:text-align':'start',
606 'fo:margin-left':'1.2cm',
607 'fo:margin-right':'-.9cm',
609 style = preso.ParagraphStyle(**attribs)
610 self.cur_slide.push_style(style)
612 def depart_block_quote(self, node):
613 # pop off the text:p
614 self.cur_slide.pop_style()
615 self.cur_slide.pop_node()
617 def visit_attribution(self, node):
618 # right justify
619 attribs = {'fo:text-align':'end',
620 'fo:margin-right':'.9cm'
622 style = preso.ParagraphStyle(**attribs)
623 self.cur_slide.push_style(style)
625 # italics
626 style = preso.TextStyle(**{'fo:font-size':S5_SIZES['small'],
627 'fo:font-style':'italic'})
628 self.cur_slide.push_style(style)
630 def depart_attribution(self, node):
631 # pop off the text:p and text:span
632 self.cur_slide.pop_style()
633 self.cur_slide.pop_node()
634 self.cur_slide.pop_style()
635 self.cur_slide.pop_node()
637 def visit_line_block(self, node):
638 #jump out of current paragraph
639 self.cur_slide.parent_of('text:p')
641 p_attribs = self._get_para_attribs(node)
642 if p_attribs:
643 self.cur_slide.push_style(preso.ParagraphStyle(**p_attribs))
644 else:
645 # right justify
646 P_attribs = {'fo:text-align':'end',
647 'fo:margin-right':'.9cm'
649 style = preso.ParagraphStyle(**p_attribs)
650 self.cur_slide.push_style(preso.ParagraphStyle(**p_attribs))
651 #self.cur_slide.push_style(style)
652 attribs = self._get_text_attribs(node)
653 if attribs:
654 style = preso.TextStyle(**attribs)
655 self.cur_slide.push_style(style)
657 def depart_line_block(self, node):
658 attribs = self._get_text_attribs(node)
659 if attribs:
660 #self.cur_slide.pop_style()
661 self.cur_slide.pop_node()
662 # pop off text:p
663 self.cur_slide.pop_style()
664 self.cur_slide.pop_node()
667 def visit_line(self, node):
668 pass
670 def depart_line(self, node):
671 self.cur_slide.insert_line_break += 1
672 self.cur_slide.insert_line_breaks()
674 @preso.cwd_decorator
675 def visit_image(self, node):
676 classes = node.attributes.get('classes', [])
678 source = node.attributes['uri']
679 p = preso.Picture(os.path.abspath(source), **node.attributes)
680 self.cur_slide.add_picture(p)
682 def depart_image(self, node):
683 pass
685 visit_figure = _dumb_visit
686 depart_figure = _dumb_depart
688 def visit_caption(self, node):
689 #!!! fix
690 pass
692 def depart_caption(self, node):
693 pass
695 def visit_literal_block(self, node):
696 attributes = node.attributes
697 # left align
698 style = preso.ParagraphStyle(**{'fo:text-align':'start'})
699 self.cur_slide.push_style(style)
700 # text styles
701 attribs = self._get_text_attribs(node)
702 if attribs:
703 style = preso.TextStyle(**attribs)
704 self.cur_slide.push_style(style)
706 if attributes['classes'] and 'code-block' in attributes['classes']:
707 node_input = node.astext()
708 language = node.attributes['language']
709 self.cur_slide.add_code(node_input, language)
710 # insert a new line after
711 self.cur_slide.insert_line_break += 1
713 else:
714 style = preso.TextStyle(**{
715 'fo:font-family':preso.MONO_FONT,
716 'style:font-family-generic':"swiss",
717 'style:font-pitch':"fixed"})
718 self.cur_slide.push_style(style)
719 node_input = node.astext()
720 chunks = node_input.split('\n')
721 for chunk in chunks:
722 self.cur_slide.write(chunk)
723 self.cur_slide.insert_line_break += 1
724 self.cur_slide.insert_line_breaks()
728 def depart_literal_block(self, node):
729 # pop text-align
730 self.cur_slide.pop_style()
731 self.cur_slide.pop_node()
732 attributes = node.attributes
733 if attributes['classes'] and 'code-block' in attributes['classes']:
734 pass
735 else:
736 self.cur_slide.pop_style()
737 self.cur_slide.pop_node()
739 def visit_footnote(self, node):
740 # shift to bottom of page?
741 pass
743 def depart_footnote(self, node):
744 pass
746 def visit_footnote_reference(self, node):
747 self.visit_superscript(node)
748 self.cur_slide.write('[')
750 def depart_footnote_reference(self, node):
751 self.cur_slide.write(']')
752 self.depart_superscript(node)
755 def visit_label(self, node):
756 # part of footnote
757 if self.at('footnote'):
758 self.cur_slide.write('[')
760 def depart_label(self, node):
761 if self.at('footnote'):
762 self.cur_slide.write('] ')
765 def visit_enumerated_list(self, node):
766 if self.at('topic'):
767 return
768 self.bullet_depth += 1
769 if not self.bullet_list:
770 self.bullet_list = preso.NumberList(self.cur_slide)
771 self.cur_slide.add_list(self.bullet_list)
772 else:
773 # indent one more
774 self.bullet_list.indent()
775 if 'incremental' in node.attributes.get('classes', []):
776 self.in_node['incremental'] = True
779 def depart_enumerated_list(self, node):
780 self.depart_bullet_list(node)
782 def visit_doctest_block(self, node):
783 node_input = node.astext()
784 language = 'pycon'
785 self.cur_slide.add_code(node_input, language)
786 # insert a new line after
787 self.cur_slide.insert_line_break += 1
790 def depart_doctest_block(self, node):
791 pass
793 def visit_importslide(self, node):
794 pass
797 def visit_table(self, node):
798 table = preso.TableFrame(self.cur_slide)
799 self.cur_slide.add_table(table)
801 def depart_table(self, node):
802 self.cur_slide.pop_element()
804 def visit_row(self, node):
805 self.cur_slide.cur_element.add_row()
807 def depart_row(self, node):
808 pass
810 def visit_entry(self, node):
811 self.cur_slide.cur_element.add_cell()
813 def depart_entry(self, node):
814 pass
816 visit_tgroup = _dumb_visit
817 depart_tgroup = _dumb_depart
819 visit_colspec = _dumb_visit
820 depart_colspec = _dumb_depart
822 visit_thead = _dumb_visit
823 depart_thead = _dumb_depart
825 visit_tbody = _dumb_visit
826 depart_tbody = _dumb_depart
828 def visit_hint(self, node):
829 return self._visit_hint(node, 'Hint')
832 def _visit_hint(self, node, name):
833 if self.cur_slide.text_frames:
834 # should adjust width of other frame
835 node = self.cur_slide.text_frames[-1].get_node()
836 node.attrib['svg:width'] = '12.296cm'
837 else:
838 self.cur_slide.add_text_frame()
840 self.cur_slide.push_element()
841 #put the hint on the right side
842 attrib = {
843 'presentation:style-name':'pr2',
844 'draw:layer':'layout',
845 'svg:width':'12.296cm',
846 'svg:height':'13.86cm',
847 'svg:x':'14.311cm',
848 'svg:y':'4.577cm',
849 'presentation:class':'subtitle'
851 self.cur_slide.add_text_frame(attrib)
852 self.cur_slide.write(name)
853 self.cur_slide.insert_line_break = 2
854 self.cur_slide.insert_line_breaks()
856 def depart_hint(self, node):
857 self.cur_slide.pop_element()
859 def visit_sidebar(self, node):
860 return self._visit_hint(node, 'Sidebar')
862 depart_sidebar = depart_hint
865 def _get_para_attribs(self, node):
866 classes = node.attributes.get('classes', [])
867 attribs = {}
868 for c in classes:
869 if c == 'center':
870 attribs.update({'fo:text-align':'center',
871 'fo:margin-left':'1.2cm',
872 'fo:margin-right':'.9cm',
874 elif c == 'right':
875 attribs.update({'fo:text-align':'end',
876 'fo:margin-right':'.9cm'
878 elif c == 'left':
879 attribs.update({'fo:text-align':'start',
880 'fo:margin-left':'1.2cm',
881 'fo:margin-right':'5.9cm',
883 #pass # default
884 return attribs
887 def _get_text_attribs(self, node):
888 classes = node.attributes.get('classes', [])
889 attribs = {}
890 for c in classes:
891 if c in S5_COLORS:
892 attribs['fo:color'] = S5_COLORS[c]
893 elif c in S5_SIZES:
894 attribs['fo:font-size'] = S5_SIZES[c]
895 return attribs
897 def num_string_to_list(numstr):
899 >>> num_string_to_list('2,5-7')
900 [2, 5, 6, 7]
901 >>> num_string_to_list('1')
905 nums = []
906 if ',' in numstr:
907 comma_delim = numstr.split(',')
908 for part in comma_delim:
909 if '-' in part:
910 start, end = [int(x) for x in part.split('-')]
911 for num in range(start, end+1):
912 nums.append(num)
913 else:
914 nums.append(int(part))
915 elif '-' in numstr:
916 start, end = [int(x) for x in numstr.split('-')]
917 for num in range(start, end+1):
918 nums.append(num)
919 else:
920 nums.append(int(numstr))
921 return nums
924 class BinaryFileOutput(io.FileOutput):
926 A version of docutils.io.FileOutput which writes to a binary file.
928 def open(self):
929 try:
930 self.destination = open(self.destination_path, 'wb')
932 except IOError, error:
933 if not self.handle_io_errors:
934 raise
935 print >>sys.stderr, '%s: %s' % (error.__class__.__name__,
936 error)
937 print >>sys.stderr, ('Unable to open destination file for writing '
938 '(%r). Exiting.' % self.destination_path)
939 sys.exit(1)
940 self.opened = 1
943 def main(prog_args):
944 argv = None
945 reader = standalone.Reader()
946 reader_name = 'standalone'
947 writer = Writer()
948 writer_name = 'pseudoxml'
949 parser = None
950 parser_name = 'restructuredtext'
951 settings = None
952 settings_spec = None
953 settings_overrides = None
954 config_section = None
955 enable_exit_status = 1
956 usage = default_usage
957 publisher = Publisher(reader, parser, writer, settings,
958 destination_class=BinaryFileOutput)
959 publisher.set_components(reader_name, parser_name, writer_name)
960 description = ('Generates OpenDocument/OpenOffice/ODF slides from '
961 'standalone reStructuredText sources. ' + default_description)
963 output = publisher.publish(argv, usage, description,
964 settings_spec, settings_overrides,
965 config_section=config_section,
966 enable_exit_status=enable_exit_status)
969 def _test():
970 import doctest
971 doctest.testmod()
973 if __name__ == "__main__":
974 if '--doctest' in sys.argv:
975 _test()
976 else:
977 sys.exit(main(sys.argv) or 0)