Fixed display of error message if ``paperconf`` is not available as a
[docutils/kirr.git] / sandbox / OpenDocument / odtwriter / __init__.py
blobd7b4f0f08c0834cea95a11905658d3e7d2dd1689
2 """
3 Open Document Format (ODF) Writer.
5 """
7 VERSION = '1.0a'
9 __docformat__ = 'reStructuredText'
12 import sys
13 import os
14 import os.path
15 import tempfile
16 import zipfile
17 from xml.dom import minidom
18 import time
19 import re
20 import StringIO
21 import inspect
22 import imp
23 import copy
24 import docutils
25 from docutils import frontend, nodes, utils, writers, languages
26 from docutils.parsers import rst
27 from docutils.readers import standalone
28 from docutils.transforms import references
31 WhichElementTree = ''
32 try:
33 # 1. Try to use lxml.
34 from lxml import etree
35 WhichElementTree = 'lxml'
36 except ImportError, e:
37 try:
38 # 2. Try to use ElementTree from the Python standard library.
39 from xml.etree import ElementTree as etree
40 WhichElementTree = 'elementtree'
41 except ImportError, e:
42 try:
43 # 3. Try to use a version of ElementTree installed as a separate
44 # product.
45 from elementtree import ElementTree as etree
46 WhichElementTree = 'elementtree'
47 except ImportError, e:
48 print '***'
49 print '*** Error: Must install either ElementTree or lxml or'
50 print '*** a version of Python containing ElementTree.'
51 print '***'
52 raise
54 try:
55 import pygments
56 import pygments.formatter
57 import pygments.lexers
58 class OdtPygmentsFormatter(pygments.formatter.Formatter):
59 def __init__(self, rststyle_function):
60 pygments.formatter.Formatter.__init__(self)
61 self.rststyle_function = rststyle_function
63 def rststyle(self, name, parameters=( )):
64 return self.rststyle_function(name, parameters)
66 class OdtPygmentsProgFormatter(OdtPygmentsFormatter):
67 def format(self, tokensource, outfile):
68 tokenclass = pygments.token.Token
69 for ttype, value in tokensource:
70 value = escape_cdata(value)
71 if ttype == tokenclass.Keyword:
72 s2 = self.rststyle('codeblock-keyword')
73 s1 = '<text:span text:style-name="%s">%s</text:span>' % \
74 (s2, value, )
75 elif ttype == tokenclass.Literal.String:
76 s2 = self.rststyle('codeblock-string')
77 s1 = '<text:span text:style-name="%s">%s</text:span>' % \
78 (s2, value, )
79 elif ttype in (
80 tokenclass.Literal.Number.Integer,
81 tokenclass.Literal.Number.Integer.Long,
82 tokenclass.Literal.Number.Float,
83 tokenclass.Literal.Number.Hex,
84 tokenclass.Literal.Number.Oct,
85 tokenclass.Literal.Number,
87 s2 = self.rststyle('codeblock-number')
88 s1 = '<text:span text:style-name="%s">%s</text:span>' % \
89 (s2, value, )
90 elif ttype == tokenclass.Operator:
91 s2 = self.rststyle('codeblock-operator')
92 s1 = '<text:span text:style-name="%s">%s</text:span>' % \
93 (s2, value, )
94 elif ttype == tokenclass.Comment:
95 s2 = self.rststyle('codeblock-comment')
96 s1 = '<text:span text:style-name="%s">%s</text:span>' % \
97 (s2, value, )
98 elif ttype == tokenclass.Name.Class:
99 s2 = self.rststyle('codeblock-classname')
100 s1 = '<text:span text:style-name="%s">%s</text:span>' % \
101 (s2, value, )
102 elif ttype == tokenclass.Name.Function:
103 s2 = self.rststyle('codeblock-functionname')
104 s1 = '<text:span text:style-name="%s">%s</text:span>' % \
105 (s2, value, )
106 elif ttype == tokenclass.Name:
107 s2 = self.rststyle('codeblock-name')
108 s1 = '<text:span text:style-name="%s">%s</text:span>' % \
109 (s2, value, )
110 else:
111 s1 = value
112 outfile.write(s1)
113 class OdtPygmentsLaTeXFormatter(OdtPygmentsFormatter):
114 def format(self, tokensource, outfile):
115 tokenclass = pygments.token.Token
116 for ttype, value in tokensource:
117 value = escape_cdata(value)
118 if ttype == tokenclass.Keyword:
119 s2 = self.rststyle('codeblock-keyword')
120 s1 = '<text:span text:style-name="%s">%s</text:span>' % \
121 (s2, value, )
122 elif ttype in (tokenclass.Literal.String,
123 tokenclass.Literal.String.Backtick,
125 s2 = self.rststyle('codeblock-string')
126 s1 = '<text:span text:style-name="%s">%s</text:span>' % \
127 (s2, value, )
128 elif ttype == tokenclass.Name.Attribute:
129 s2 = self.rststyle('codeblock-operator')
130 s1 = '<text:span text:style-name="%s">%s</text:span>' % \
131 (s2, value, )
132 elif ttype == tokenclass.Comment:
133 if value[-1] == '\n':
134 s2 = self.rststyle('codeblock-comment')
135 s1 = '<text:span text:style-name="%s">%s</text:span>\n' % \
136 (s2, value[:-1], )
137 else:
138 s2 = self.rststyle('codeblock-comment')
139 s1 = '<text:span text:style-name="%s">%s</text:span>' % \
140 (s2, value, )
141 elif ttype == tokenclass.Name.Builtin:
142 s2 = self.rststyle('codeblock-name')
143 s1 = '<text:span text:style-name="%s">%s</text:span>' % \
144 (s2, value, )
145 else:
146 s1 = value
147 outfile.write(s1)
149 except ImportError, e:
150 pygments = None
153 # Is the PIL imaging library installed?
154 try:
155 import Image
156 except ImportError, exp:
157 Image = None
159 ## from IPython.Shell import IPShellEmbed
160 ## args = ['-pdb', '-pi1', 'In <\\#>: ', '-pi2', ' .\\D.: ',
161 ## '-po', 'Out<\\#>: ', '-nosep']
162 ## ipshell = IPShellEmbed(args,
163 ## banner = 'Entering IPython. Press Ctrl-D to exit.',
164 ## exit_msg = 'Leaving Interpreter, back to program.')
168 # ElementTree does not support getparent method (lxml does).
169 # This wrapper class and the following support functions provide
170 # that support for the ability to get the parent of an element.
172 if WhichElementTree == 'elementtree':
173 class _ElementInterfaceWrapper(etree._ElementInterface):
174 def __init__(self, tag, attrib=None):
175 etree._ElementInterface.__init__(self, tag, attrib)
176 if attrib is None:
177 attrib = {}
178 self.parent = None
179 def setparent(self, parent):
180 self.parent = parent
181 def getparent(self):
182 return self.parent
186 # Constants and globals
188 # Turn tracing on/off. See methods trace_visit_node/trace_depart_node.
189 DEBUG = 0
190 SPACES_PATTERN = re.compile(r'( +)')
191 TABS_PATTERN = re.compile(r'(\t+)')
192 FILL_PAT1 = re.compile(r'^ +')
193 FILL_PAT2 = re.compile(r' {2,}')
195 TableStylePrefix = 'Table'
197 GENERATOR_DESC = 'Docutils.org/odtwriter'
199 NAME_SPACE_1 = 'urn:oasis:names:tc:opendocument:xmlns:office:1.0'
201 CONTENT_NAMESPACE_DICT = CNSD = {
202 # 'office:version': '1.0',
203 'chart': 'urn:oasis:names:tc:opendocument:xmlns:chart:1.0',
204 'dc': 'http://purl.org/dc/elements/1.1/',
205 'dom': 'http://www.w3.org/2001/xml-events',
206 'dr3d': 'urn:oasis:names:tc:opendocument:xmlns:dr3d:1.0',
207 'draw': 'urn:oasis:names:tc:opendocument:xmlns:drawing:1.0',
208 'fo': 'urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0',
209 'form': 'urn:oasis:names:tc:opendocument:xmlns:form:1.0',
210 'math': 'http://www.w3.org/1998/Math/MathML',
211 'meta': 'urn:oasis:names:tc:opendocument:xmlns:meta:1.0',
212 'number': 'urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0',
213 'office': NAME_SPACE_1,
214 'ooo': 'http://openoffice.org/2004/office',
215 'oooc': 'http://openoffice.org/2004/calc',
216 'ooow': 'http://openoffice.org/2004/writer',
217 'presentation': 'urn:oasis:names:tc:opendocument:xmlns:presentation:1.0',
219 'script': 'urn:oasis:names:tc:opendocument:xmlns:script:1.0',
220 'style': 'urn:oasis:names:tc:opendocument:xmlns:style:1.0',
221 'svg': 'urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0',
222 'table': 'urn:oasis:names:tc:opendocument:xmlns:table:1.0',
223 'text': 'urn:oasis:names:tc:opendocument:xmlns:text:1.0',
224 'xforms': 'http://www.w3.org/2002/xforms',
225 'xlink': 'http://www.w3.org/1999/xlink',
226 'xsd': 'http://www.w3.org/2001/XMLSchema',
227 'xsi': 'http://www.w3.org/2001/XMLSchema-instance',
230 STYLES_NAMESPACE_DICT = SNSD = {
231 # 'office:version': '1.0',
232 'chart': 'urn:oasis:names:tc:opendocument:xmlns:chart:1.0',
233 'dc': 'http://purl.org/dc/elements/1.1/',
234 'dom': 'http://www.w3.org/2001/xml-events',
235 'dr3d': 'urn:oasis:names:tc:opendocument:xmlns:dr3d:1.0',
236 'draw': 'urn:oasis:names:tc:opendocument:xmlns:drawing:1.0',
237 'fo': 'urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0',
238 'form': 'urn:oasis:names:tc:opendocument:xmlns:form:1.0',
239 'math': 'http://www.w3.org/1998/Math/MathML',
240 'meta': 'urn:oasis:names:tc:opendocument:xmlns:meta:1.0',
241 'number': 'urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0',
242 'office': NAME_SPACE_1,
243 'presentation': 'urn:oasis:names:tc:opendocument:xmlns:presentation:1.0',
244 'ooo': 'http://openoffice.org/2004/office',
245 'oooc': 'http://openoffice.org/2004/calc',
246 'ooow': 'http://openoffice.org/2004/writer',
247 'script': 'urn:oasis:names:tc:opendocument:xmlns:script:1.0',
248 'style': 'urn:oasis:names:tc:opendocument:xmlns:style:1.0',
249 'svg': 'urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0',
250 'table': 'urn:oasis:names:tc:opendocument:xmlns:table:1.0',
251 'text': 'urn:oasis:names:tc:opendocument:xmlns:text:1.0',
252 'xlink': 'http://www.w3.org/1999/xlink',
255 MANIFEST_NAMESPACE_DICT = MANNSD = {
256 'manifest': 'urn:oasis:names:tc:opendocument:xmlns:manifest:1.0',
259 META_NAMESPACE_DICT = METNSD = {
260 # 'office:version': '1.0',
261 'dc': 'http://purl.org/dc/elements/1.1/',
262 'meta': 'urn:oasis:names:tc:opendocument:xmlns:meta:1.0',
263 'office': NAME_SPACE_1,
264 'ooo': 'http://openoffice.org/2004/office',
265 'xlink': 'http://www.w3.org/1999/xlink',
269 # Attribute dictionaries for use with ElementTree (not lxml), which
270 # does not support use of nsmap parameter on Element() and SubElement().
272 CONTENT_NAMESPACE_ATTRIB = {
273 'office:version': '1.0',
274 'xmlns:chart': 'urn:oasis:names:tc:opendocument:xmlns:chart:1.0',
275 'xmlns:dc': 'http://purl.org/dc/elements/1.1/',
276 'xmlns:dom': 'http://www.w3.org/2001/xml-events',
277 'xmlns:dr3d': 'urn:oasis:names:tc:opendocument:xmlns:dr3d:1.0',
278 'xmlns:draw': 'urn:oasis:names:tc:opendocument:xmlns:drawing:1.0',
279 'xmlns:fo': 'urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0',
280 'xmlns:form': 'urn:oasis:names:tc:opendocument:xmlns:form:1.0',
281 'xmlns:math': 'http://www.w3.org/1998/Math/MathML',
282 'xmlns:meta': 'urn:oasis:names:tc:opendocument:xmlns:meta:1.0',
283 'xmlns:number': 'urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0',
284 'xmlns:office': NAME_SPACE_1,
285 'xmlns:presentation': 'urn:oasis:names:tc:opendocument:xmlns:presentation:1.0',
286 'xmlns:ooo': 'http://openoffice.org/2004/office',
287 'xmlns:oooc': 'http://openoffice.org/2004/calc',
288 'xmlns:ooow': 'http://openoffice.org/2004/writer',
289 'xmlns:script': 'urn:oasis:names:tc:opendocument:xmlns:script:1.0',
290 'xmlns:style': 'urn:oasis:names:tc:opendocument:xmlns:style:1.0',
291 'xmlns:svg': 'urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0',
292 'xmlns:table': 'urn:oasis:names:tc:opendocument:xmlns:table:1.0',
293 'xmlns:text': 'urn:oasis:names:tc:opendocument:xmlns:text:1.0',
294 'xmlns:xforms': 'http://www.w3.org/2002/xforms',
295 'xmlns:xlink': 'http://www.w3.org/1999/xlink',
296 'xmlns:xsd': 'http://www.w3.org/2001/XMLSchema',
297 'xmlns:xsi': 'http://www.w3.org/2001/XMLSchema-instance',
300 STYLES_NAMESPACE_ATTRIB = {
301 'office:version': '1.0',
302 'xmlns:chart': 'urn:oasis:names:tc:opendocument:xmlns:chart:1.0',
303 'xmlns:dc': 'http://purl.org/dc/elements/1.1/',
304 'xmlns:dom': 'http://www.w3.org/2001/xml-events',
305 'xmlns:dr3d': 'urn:oasis:names:tc:opendocument:xmlns:dr3d:1.0',
306 'xmlns:draw': 'urn:oasis:names:tc:opendocument:xmlns:drawing:1.0',
307 'xmlns:fo': 'urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0',
308 'xmlns:form': 'urn:oasis:names:tc:opendocument:xmlns:form:1.0',
309 'xmlns:math': 'http://www.w3.org/1998/Math/MathML',
310 'xmlns:meta': 'urn:oasis:names:tc:opendocument:xmlns:meta:1.0',
311 'xmlns:number': 'urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0',
312 'xmlns:office': NAME_SPACE_1,
313 'xmlns:presentation': 'urn:oasis:names:tc:opendocument:xmlns:presentation:1.0',
314 'xmlns:ooo': 'http://openoffice.org/2004/office',
315 'xmlns:oooc': 'http://openoffice.org/2004/calc',
316 'xmlns:ooow': 'http://openoffice.org/2004/writer',
317 'xmlns:script': 'urn:oasis:names:tc:opendocument:xmlns:script:1.0',
318 'xmlns:style': 'urn:oasis:names:tc:opendocument:xmlns:style:1.0',
319 'xmlns:svg': 'urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0',
320 'xmlns:table': 'urn:oasis:names:tc:opendocument:xmlns:table:1.0',
321 'xmlns:text': 'urn:oasis:names:tc:opendocument:xmlns:text:1.0',
322 'xmlns:xlink': 'http://www.w3.org/1999/xlink',
325 MANIFEST_NAMESPACE_ATTRIB = {
326 'xmlns:manifest': 'urn:oasis:names:tc:opendocument:xmlns:manifest:1.0',
329 META_NAMESPACE_ATTRIB = {
330 'office:version': '1.0',
331 'xmlns:dc': 'http://purl.org/dc/elements/1.1/',
332 'xmlns:meta': 'urn:oasis:names:tc:opendocument:xmlns:meta:1.0',
333 'xmlns:office': NAME_SPACE_1,
334 'xmlns:ooo': 'http://openoffice.org/2004/office',
335 'xmlns:xlink': 'http://www.w3.org/1999/xlink',
340 # Functions
344 # ElementTree support functions.
345 # In order to be able to get the parent of elements, must use these
346 # instead of the functions with same name provided by ElementTree.
348 def Element(tag, attrib=None, nsmap=None, nsdict=CNSD):
349 if attrib is None:
350 attrib = {}
351 tag, attrib = fix_ns(tag, attrib, nsdict)
352 if WhichElementTree == 'lxml':
353 el = etree.Element(tag, attrib, nsmap=nsmap)
354 else:
355 el = _ElementInterfaceWrapper(tag, attrib)
356 return el
358 def SubElement(parent, tag, attrib=None, nsmap=None, nsdict=CNSD):
359 if attrib is None:
360 attrib = {}
361 tag, attrib = fix_ns(tag, attrib, nsdict)
362 if WhichElementTree == 'lxml':
363 el = etree.SubElement(parent, tag, attrib, nsmap=nsmap)
364 else:
365 el = _ElementInterfaceWrapper(tag, attrib)
366 parent.append(el)
367 el.setparent(parent)
368 return el
370 def fix_ns(tag, attrib, nsdict):
371 nstag = add_ns(tag, nsdict)
372 nsattrib = {}
373 for key, val in attrib.iteritems():
374 nskey = add_ns(key, nsdict)
375 nsattrib[nskey] = val
376 return nstag, nsattrib
378 def add_ns(tag, nsdict=CNSD):
379 if WhichElementTree == 'lxml':
380 nstag, name = tag.split(':')
381 ns = nsdict.get(nstag)
382 if ns is None:
383 raise RuntimeError, 'Invalid namespace prefix: %s' % nstag
384 tag = '{%s}%s' % (ns, name,)
385 return tag
387 def ToString(et):
388 outstream = StringIO.StringIO()
389 et.write(outstream)
390 s1 = outstream.getvalue()
391 outstream.close()
392 return s1
394 def escape_cdata(text):
395 text = text.replace("&", "&amp;")
396 text = text.replace("<", "&lt;")
397 text = text.replace(">", "&gt;")
398 ascii = ''
399 for char in text:
400 if ord(char) >= ord("\x7f"):
401 ascii += "&#x%X;" % ( ord(char), )
402 else:
403 ascii += char
404 return ascii
408 # Classes
411 # Does this version of Docutils has Directive support?
412 if hasattr(rst, 'Directive'):
414 # Class to control syntax highlighting.
415 class SyntaxHighlightCodeBlock(rst.Directive):
416 required_arguments = 1
417 optional_arguments = 0
418 has_content = True
420 # See visit_literal_block for code that processes the node
421 # created here.
422 def run(self):
423 language = self.arguments[0]
424 code_block = nodes.literal_block(classes=["code-block", language],
425 language=language)
426 lines = self.content
427 content = '\n'.join(lines)
428 text_node = nodes.Text(content)
429 code_block.append(text_node)
430 # Mark this node for high-lighting so that visit_literal_block
431 # will be able to hight-light those produced here and
432 # *not* high-light regular literal blocks (:: in reST).
433 code_block['hilight'] = True
434 #import pdb; pdb.set_trace()
435 return [code_block]
437 rst.directives.register_directive('sourcecode', SyntaxHighlightCodeBlock)
438 rst.directives.register_directive('code', SyntaxHighlightCodeBlock)
440 rst.directives.register_directive('code-block', SyntaxHighlightCodeBlock)
443 # Register directives defined in a module named "odtwriter_plugins".
445 def load_plugins():
446 plugin_mod = None
447 count = 0
448 try:
449 name = 'odtwriter_plugins'
450 fp, pathname, description = imp.find_module(name)
451 plugin_mod = imp.load_module(name, fp, pathname, description)
452 #import odtwriter_plugins
453 #plugin_mod = odtwriter_plugins
454 except ImportError, e:
455 pass
456 if plugin_mod is None:
457 return count
458 klasses = inspect.getmembers(plugin_mod, inspect.isclass)
459 for klass in klasses:
460 if register_plugin(*klass):
461 count += 1
462 return count
464 def register_plugin(name, klass):
465 plugin_name = getattr(klass, 'plugin_name', None)
466 if plugin_name is not None:
467 rst.directives.register_directive(plugin_name, klass)
469 load_plugins()
472 WORD_SPLIT_PAT1 = re.compile(r'\b(\w*)\b\W*')
474 def split_words(line):
475 # We need whitespace at the end of the string for our regexpr.
476 line += ' '
477 words = []
478 pos1 = 0
479 mo = WORD_SPLIT_PAT1.search(line, pos1)
480 while mo is not None:
481 word = mo.groups()[0]
482 words.append(word)
483 pos1 = mo.end()
484 mo = WORD_SPLIT_PAT1.search(line, pos1)
485 return words
489 # Information about the indentation level for lists nested inside
490 # other contexts, e.g. dictionary lists.
491 class ListLevel(object):
492 def __init__(self, level, sibling_level=True, nested_level=True):
493 self.level = level
494 self.sibling_level = sibling_level
495 self.nested_level = nested_level
496 def set_sibling(self, sibling_level): self.sibling_level = sibling_level
497 def get_sibling(self): return self.sibling_level
498 def set_nested(self, nested_level): self.nested_level = nested_level
499 def get_nested(self): return self.nested_level
500 def set_level(self, level): self.level = level
501 def get_level(self): return self.level
504 class Writer(writers.Writer):
506 MIME_TYPE = 'application/vnd.oasis.opendocument.text'
507 EXTENSION = '.odt'
509 supported = ('html', 'html4css1', 'xhtml')
510 """Formats this writer supports."""
512 default_stylesheet = 'styles' + EXTENSION
514 default_stylesheet_path = utils.relative_path(
515 os.path.join(os.getcwd(), 'dummy'),
516 os.path.join(os.path.dirname(__file__), default_stylesheet))
518 default_template = 'template.txt'
520 default_template_path = utils.relative_path(
521 os.path.join(os.getcwd(), 'dummy'),
522 os.path.join(os.path.dirname(__file__), default_template))
524 settings_spec = (
525 'ODF-Specific Options',
526 None,
528 ('Specify a stylesheet URL, used verbatim. Overrides '
529 '--stylesheet-path.',
530 ['--stylesheet'],
531 {'metavar': '<URL>', 'overrides': 'stylesheet_path'}),
532 ('Specify a stylesheet file, relative to the current working '
533 'directory. The path is adjusted relative to the output ODF '
534 'file. Overrides --stylesheet. Default: "%s"'
535 % default_stylesheet_path,
536 ['--stylesheet-path'],
537 {'metavar': '<file>', 'overrides': 'stylesheet',
538 'default': default_stylesheet_path}),
539 ('Specify a configuration/mapping file relative to the '
540 'current working '
541 'directory for additional ODF options. '
542 'In particular, this file may contain a section named '
543 '"Formats" that maps default style names to '
544 'names to be used in the resulting output file allowing for '
545 'adhering to external standards. '
546 'For more info and the format of the configuration/mapping file, '
547 'see the odtwriter doc.',
548 ['--odf-config-file'],
549 {'metavar': '<file>'}),
550 ('Obfuscate email addresses to confuse harvesters while still '
551 'keeping email links usable with standards-compliant browsers.',
552 ['--cloak-email-addresses'],
553 {'default': False,
554 'action': 'store_true',
555 'dest': 'cloak_email_addresses',
556 'validator': frontend.validate_boolean}),
557 ('Do not obfuscate email addresses.',
558 ['--no-cloak-email-addresses'],
559 {'default': False,
560 'action': 'store_false',
561 'dest': 'cloak_email_addresses',
562 'validator': frontend.validate_boolean}),
563 ('Specify the thickness of table borders in thousands of a cm. '
564 'Default is 35.',
565 ['--table-border-thickness'],
566 {'default': 35,
567 'validator': frontend.validate_nonnegative_int}),
568 ('Add syntax highlighting in literal code blocks.',
569 ['--add-syntax-highlighting'],
570 {'default': False,
571 'action': 'store_true',
572 'dest': 'add_syntax_highlighting',
573 'validator': frontend.validate_boolean}),
574 ('Do not add syntax highlighting in literal code blocks. (default)',
575 ['--no-add-syntax-highlighting'],
576 {'default': False,
577 'action': 'store_false',
578 'dest': 'add_syntax_highlighting',
579 'validator': frontend.validate_boolean}),
580 ('Create sections for headers. (default)',
581 ['--create-sections'],
582 {'default': True,
583 'action': 'store_true',
584 'dest': 'create_sections',
585 'validator': frontend.validate_boolean}),
586 ('Do not create sections for headers.',
587 ['--no-create-sections'],
588 {'default': True,
589 'action': 'store_false',
590 'dest': 'create_sections',
591 'validator': frontend.validate_boolean}),
592 ('Create links.',
593 ['--create-links'],
594 {'default': False,
595 'action': 'store_true',
596 'dest': 'create_links',
597 'validator': frontend.validate_boolean}),
598 ('Do not create links. (default)',
599 ['--no-create-links'],
600 {'default': False,
601 'action': 'store_false',
602 'dest': 'create_links',
603 'validator': frontend.validate_boolean}),
604 ('Generate endnotes at end of document, not footnotes '
605 'at bottom of page.',
606 ['--endnotes-end-doc'],
607 {'default': False,
608 'action': 'store_true',
609 'dest': 'endnotes_end_doc',
610 'validator': frontend.validate_boolean}),
611 ('Generate footnotes at bottom of page, not endnotes '
612 'at end of document. (default)',
613 ['--no-endnotes-end-doc'],
614 {'default': False,
615 'action': 'store_false',
616 'dest': 'endnotes_end_doc',
617 'validator': frontend.validate_boolean}),
620 settings_defaults = {
621 'output_encoding_error_handler': 'xmlcharrefreplace',
624 relative_path_settings = (
625 'stylesheet_path',
628 config_section = 'opendocument odf writer'
629 config_section_dependencies = (
630 'writers',
633 def __init__(self):
634 writers.Writer.__init__(self)
635 self.translator_class = ODFTranslator
637 def translate(self):
638 self.settings = self.document.settings
639 self.visitor = self.translator_class(self.document)
640 self.document.walkabout(self.visitor)
641 self.visitor.add_doc_title()
642 self.assemble_my_parts()
643 self.output = self.parts['whole']
645 def assemble_my_parts(self):
646 """Assemble the `self.parts` dictionary. Extend in subclasses.
648 #ipshell('At assemble_parts')
649 writers.Writer.assemble_parts(self)
650 f = tempfile.NamedTemporaryFile()
651 zfile = zipfile.ZipFile(f, 'w', zipfile.ZIP_DEFLATED)
652 content = self.visitor.content_astext()
653 self.write_zip_str(zfile, 'content.xml', content)
654 self.write_zip_str(zfile, 'mimetype', self.MIME_TYPE)
655 s1 = self.create_manifest()
656 self.write_zip_str(zfile, 'META-INF/manifest.xml', s1)
657 s1 = self.create_meta()
658 self.write_zip_str(zfile, 'meta.xml', s1)
659 s1 = self.get_stylesheet()
660 self.write_zip_str(zfile, 'styles.xml', s1)
661 s1 = self.get_settings()
662 self.write_zip_str(zfile, 'settings.xml', s1)
663 self.store_embedded_files(zfile)
664 zfile.close()
665 f.seek(0)
666 whole = f.read()
667 f.close()
668 self.parts['whole'] = whole
669 self.parts['encoding'] = self.document.settings.output_encoding
670 self.parts['version'] = docutils.__version__
672 def write_zip_str(self, zfile, name, bytes):
673 localtime = time.localtime(time.time())
674 zinfo = zipfile.ZipInfo(name, localtime)
675 # Add some standard UNIX file access permissions (-rw-r--r--).
676 zinfo.external_attr = (0x81a4 & 0xFFFF) << 16L
677 zinfo.compress_type = zipfile.ZIP_DEFLATED
678 zfile.writestr(zinfo, bytes)
680 def store_embedded_files(self, zfile):
681 embedded_files = self.visitor.get_embedded_file_list()
682 for source, destination in embedded_files:
683 if source is None:
684 continue
685 try:
686 # encode/decode
687 destination1 = destination.decode('latin-1').encode('utf-8')
688 zfile.write(source, destination1, zipfile.ZIP_STORED)
689 except OSError, e:
690 print "Error: Can't open file %s." % (source, )
692 def get_settings(self):
694 modeled after get_stylesheet
696 stylespath = utils.get_stylesheet_reference(self.settings,
697 os.path.join(os.getcwd(), 'dummy'))
698 zfile = zipfile.ZipFile(stylespath, 'r')
699 s1 = zfile.read('settings.xml')
700 zfile.close()
701 return s1
703 def get_stylesheet(self):
704 """Retrieve the stylesheet from either a .xml file or from
705 a .odt (zip) file. Return the content as a string.
707 stylespath = utils.get_stylesheet_reference(self.settings,
708 os.path.join(os.getcwd(), 'dummy'))
709 ext = os.path.splitext(stylespath)[1]
710 if ext == '.xml':
711 stylesfile = open(stylespath, 'r')
712 s1 = stylesfile.read()
713 stylesfile.close()
714 elif ext == self.EXTENSION:
715 zfile = zipfile.ZipFile(stylespath, 'r')
716 s1 = zfile.read('styles.xml')
717 zfile.close()
718 else:
719 raise RuntimeError, 'stylesheet path (%s) must be %s or .xml file' %(stylespath, self.EXTENSION)
720 s1 = self.visitor.setup_page(s1)
721 return s1
723 def assemble_parts(self):
724 pass
726 def create_manifest(self):
727 if WhichElementTree == 'lxml':
728 root = Element('manifest:manifest',
729 nsmap=MANIFEST_NAMESPACE_DICT,
730 nsdict=MANIFEST_NAMESPACE_DICT,
732 else:
733 root = Element('manifest:manifest',
734 attrib=MANIFEST_NAMESPACE_ATTRIB,
735 nsdict=MANIFEST_NAMESPACE_DICT,
737 doc = etree.ElementTree(root)
738 SubElement(root, 'manifest:file-entry', attrib={
739 'manifest:media-type': self.MIME_TYPE,
740 'manifest:full-path': '/',
741 }, nsdict=MANNSD)
742 SubElement(root, 'manifest:file-entry', attrib={
743 'manifest:media-type': 'text/xml',
744 'manifest:full-path': 'content.xml',
745 }, nsdict=MANNSD)
746 SubElement(root, 'manifest:file-entry', attrib={
747 'manifest:media-type': 'text/xml',
748 'manifest:full-path': 'styles.xml',
749 }, nsdict=MANNSD)
750 SubElement(root, 'manifest:file-entry', attrib={
751 'manifest:media-type': 'text/xml',
752 'manifest:full-path': 'meta.xml',
753 }, nsdict=MANNSD)
754 s1 = ToString(doc)
755 doc = minidom.parseString(s1)
756 s1 = doc.toprettyxml(' ')
757 return s1
759 def create_meta(self):
760 if WhichElementTree == 'lxml':
761 root = Element('office:document-meta',
762 nsmap=META_NAMESPACE_DICT,
763 nsdict=META_NAMESPACE_DICT,
765 else:
766 root = Element('office:document-meta',
767 attrib=META_NAMESPACE_ATTRIB,
768 nsdict=META_NAMESPACE_DICT,
770 doc = etree.ElementTree(root)
771 root = SubElement(root, 'office:meta', nsdict=METNSD)
772 el1 = SubElement(root, 'meta:generator', nsdict=METNSD)
773 el1.text = 'Docutils/rst2odf.py/%s' % (VERSION, )
774 s1 = os.environ.get('USER', '')
775 el1 = SubElement(root, 'meta:initial-creator', nsdict=METNSD)
776 el1.text = s1
777 s2 = time.strftime('%Y-%m-%dT%H:%M:%S', time.localtime())
778 el1 = SubElement(root, 'meta:creation-date', nsdict=METNSD)
779 el1.text = s2
780 el1 = SubElement(root, 'dc:creator', nsdict=METNSD)
781 el1.text = s1
782 el1 = SubElement(root, 'dc:date', nsdict=METNSD)
783 el1.text = s2
784 el1 = SubElement(root, 'dc:language', nsdict=METNSD)
785 el1.text = 'en-US'
786 el1 = SubElement(root, 'meta:editing-cycles', nsdict=METNSD)
787 el1.text = '1'
788 el1 = SubElement(root, 'meta:editing-duration', nsdict=METNSD)
789 el1.text = 'PT00M01S'
790 title = self.visitor.get_title()
791 el1 = SubElement(root, 'dc:title', nsdict=METNSD)
792 if title:
793 el1.text = title
794 else:
795 el1.text = '[no title]'
796 meta_dict = self.visitor.get_meta_dict()
797 keywordstr = meta_dict.get('keywords')
798 if keywordstr is not None:
799 keywords = split_words(keywordstr)
800 for keyword in keywords:
801 el1 = SubElement(root, 'meta:keyword', nsdict=METNSD)
802 el1.text = keyword
803 description = meta_dict.get('description')
804 if description is not None:
805 el1 = SubElement(root, 'dc:description', nsdict=METNSD)
806 el1.text = description
807 s1 = ToString(doc)
808 #doc = minidom.parseString(s1)
809 #s1 = doc.toprettyxml(' ')
810 return s1
812 # class ODFTranslator(nodes.SparseNodeVisitor):
814 class ODFTranslator(nodes.GenericNodeVisitor):
816 used_styles = (
817 'attribution', 'blockindent', 'blockquote', 'blockquote-bulletitem',
818 'blockquote-bulletlist', 'blockquote-enumitem', 'blockquote-enumlist',
819 'bulletitem', 'bulletlist', 'caption', 'centeredtextbody', 'codeblock',
820 'codeblock-classname', 'codeblock-comment', 'codeblock-functionname',
821 'codeblock-keyword', 'codeblock-name', 'codeblock-number',
822 'codeblock-operator', 'codeblock-string', 'emphasis', 'enumitem',
823 'enumlist', 'epigraph', 'epigraph-bulletitem', 'epigraph-bulletlist',
824 'epigraph-enumitem', 'epigraph-enumlist', 'footer',
825 'footnote', 'citation',
826 'header', 'highlights', 'highlights-bulletitem',
827 'highlights-bulletlist', 'highlights-enumitem', 'highlights-enumlist',
828 'horizontalline', 'inlineliteral', 'quotation', 'rubric',
829 'strong', 'table-title', 'textbody', 'tocbulletlist', 'tocenumlist',
830 'title',
831 'subtitle',
832 'heading1',
833 'heading2',
834 'heading3',
835 'heading4',
836 'heading5',
837 'heading6',
838 'heading7',
839 'admon-attention-hdr',
840 'admon-attention-body',
841 'admon-caution-hdr',
842 'admon-caution-body',
843 'admon-danger-hdr',
844 'admon-danger-body',
845 'admon-error-hdr',
846 'admon-error-body',
847 'admon-generic-hdr',
848 'admon-generic-body',
849 'admon-hint-hdr',
850 'admon-hint-body',
851 'admon-important-hdr',
852 'admon-important-body',
853 'admon-note-hdr',
854 'admon-note-body',
855 'admon-tip-hdr',
856 'admon-tip-body',
857 'admon-warning-hdr',
858 'admon-warning-body',
859 'tableoption',
860 'tableoption.%c', 'tableoption.%c%d', 'Table%d', 'Table%d.%c',
861 'Table%d.%c%d',
862 'lineblock1',
863 'lineblock2',
864 'lineblock3',
865 'lineblock4',
866 'lineblock5',
867 'lineblock6',
870 def __init__(self, document):
871 #nodes.SparseNodeVisitor.__init__(self, document)
872 nodes.GenericNodeVisitor.__init__(self, document)
873 self.settings = document.settings
874 self.format_map = { }
875 if self.settings.odf_config_file:
876 from ConfigParser import ConfigParser
878 parser = ConfigParser()
879 parser.read(self.settings.odf_config_file)
880 for rststyle, format in parser.items("Formats"):
881 if rststyle not in self.used_styles:
882 print '***'
883 print ('*** Warning: Style "%s" '
884 'is not a style used by odtwriter.' % (
885 rststyle, ))
886 print '***'
887 #raise RuntimeError, 'Unused style "%s"' % ( rststyle, )
888 self.format_map[rststyle] = format
889 self.section_level = 0
890 self.section_count = 0
891 # Create ElementTree content and styles documents.
892 if WhichElementTree == 'lxml':
893 root = Element(
894 'office:document-content',
895 nsmap=CONTENT_NAMESPACE_DICT,
897 else:
898 root = Element(
899 'office:document-content',
900 attrib=CONTENT_NAMESPACE_ATTRIB,
902 self.content_tree = etree.ElementTree(element=root)
903 self.current_element = root
904 SubElement(root, 'office:scripts')
905 SubElement(root, 'office:font-face-decls')
906 el = SubElement(root, 'office:automatic-styles')
907 self.automatic_styles = el
908 el = SubElement(root, 'office:body')
909 el = self.generate_content_element(el)
910 self.current_element = el
911 self.body_text_element = el
912 self.paragraph_style_stack = [self.rststyle('textbody'), ]
913 self.list_style_stack = []
914 self.table_count = 0
915 self.column_count = ord('A') - 1
916 self.trace_level = -1
917 self.optiontablestyles_generated = False
918 self.field_name = None
919 self.field_element = None
920 self.title = None
921 self.image_count = 0
922 self.image_style_count = 0
923 self.image_dict = {}
924 self.embedded_file_list = []
925 self.syntaxhighlighting = 1
926 self.syntaxhighlight_lexer = 'python'
927 self.header_content = []
928 self.footer_content = []
929 self.in_header = False
930 self.in_footer = False
931 self.blockstyle = ''
932 self.in_table_of_contents = False
933 self.footnote_ref_dict = {}
934 self.footnote_list = []
935 self.footnote_chars_idx = 0
936 self.footnote_level = 0
937 self.pending_ids = [ ]
938 self.in_paragraph = False
939 self.found_doc_title = False
940 self.bumped_list_level_stack = []
941 self.meta_dict = {}
942 self.line_block_level = 0
943 self.line_indent_level = 0
944 self.citation_id = None
946 def add_doc_title(self):
947 text = self.settings.title
948 if text:
949 self.title = text
950 if not self.found_doc_title:
951 el = Element('text:p', attrib = {
952 'text:style-name': self.rststyle('title'),
954 el.text = text
955 self.body_text_element.insert(0, el)
957 def rststyle(self, name, parameters=( )):
959 Returns the style name to use for the given style.
961 If `parameters` is given `name` must contain a matching number of ``%`` and
962 is used as a format expression with `parameters` as the value.
964 name1 = name % parameters
965 stylename = self.format_map.get(name1, 'rststyle-%s' % name1)
966 return stylename
968 def generate_content_element(self, root):
969 return SubElement(root, 'office:text')
971 def setup_page(self, content):
972 root_el = etree.fromstring(content)
973 self.setup_paper(root_el)
974 if len(self.header_content) > 0 or len(self.footer_content) > 0:
975 self.add_header_footer(root_el)
976 new_content = etree.tostring(root_el)
977 return new_content
979 def setup_paper(self, root_el):
980 try:
981 fin = os.popen("paperconf -s 2> /dev/null")
982 w, h = map(float, fin.read().split())
983 fin.close()
984 except:
985 w, h = 612, 792 # default to Letter
986 def walk(el):
987 if el.tag == "{%s}page-layout-properties" % SNSD["style"] and \
988 not el.attrib.has_key("{%s}page-width" % SNSD["fo"]):
989 el.attrib["{%s}page-width" % SNSD["fo"]] = "%.3fpt" % w
990 el.attrib["{%s}page-height" % SNSD["fo"]] = "%.3fpt" % h
991 el.attrib["{%s}margin-left" % SNSD["fo"]] = \
992 el.attrib["{%s}margin-right" % SNSD["fo"]] = \
993 "%.3fpt" % (.1 * w)
994 el.attrib["{%s}margin-top" % SNSD["fo"]] = \
995 el.attrib["{%s}margin-bottom" % SNSD["fo"]] = \
996 "%.3fpt" % (.1 * h)
997 else:
998 for subel in el.getchildren(): walk(subel)
999 walk(root_el)
1001 def add_header_footer(self, root_el):
1002 path = '{%s}master-styles' % (NAME_SPACE_1, )
1003 master_el = root_el.find(path)
1004 if master_el is None:
1005 return
1006 path = '{%s}master-page' % (SNSD['style'], )
1007 master_el = master_el.find(path)
1008 if master_el is None:
1009 return
1010 el1 = master_el
1011 if len(self.header_content) > 0:
1012 if WhichElementTree == 'lxml':
1013 el2 = SubElement(el1, 'style:header', nsdict=SNSD)
1014 else:
1015 el2 = SubElement(el1, 'style:header',
1016 attrib=STYLES_NAMESPACE_ATTRIB,
1017 nsdict=STYLES_NAMESPACE_DICT,
1019 for el in self.header_content:
1020 attrkey = add_ns('text:style-name', nsdict=SNSD)
1021 el.attrib[attrkey] = self.rststyle('header')
1022 el2.append(el)
1023 if len(self.footer_content) > 0:
1024 if WhichElementTree == 'lxml':
1025 el2 = SubElement(el1, 'style:footer', nsdict=SNSD)
1026 else:
1027 el2 = SubElement(el1, 'style:footer',
1028 attrib=STYLES_NAMESPACE_ATTRIB,
1029 nsdict=STYLES_NAMESPACE_DICT,
1031 for el in self.footer_content:
1032 attrkey = add_ns('text:style-name', nsdict=SNSD)
1033 el.attrib[attrkey] = self.rststyle('footer')
1034 el2.append(el)
1035 #new_tree = etree.ElementTree(root_el)
1036 #new_content = ToString(new_tree)
1038 def astext(self):
1039 root = self.content_tree.getroot()
1040 et = etree.ElementTree(root)
1041 s1 = ToString(et)
1042 return s1
1044 def content_astext(self):
1045 return self.astext()
1047 def set_title(self, title): self.title = title
1048 def get_title(self): return self.title
1049 def set_embedded_file_list(self, embedded_file_list):
1050 self.embedded_file_list = embedded_file_list
1051 def get_embedded_file_list(self): return self.embedded_file_list
1052 def get_meta_dict(self): return self.meta_dict
1054 def process_footnotes(self):
1055 for node, el1 in self.footnote_list:
1056 backrefs = node.attributes.get('backrefs', [])
1057 first = True
1058 for ref in backrefs:
1059 el2 = self.footnote_ref_dict.get(ref)
1060 if el2 is not None:
1061 if first:
1062 first = False
1063 el3 = copy.deepcopy(el1)
1064 el2.append(el3)
1065 else:
1066 children = el2.getchildren()
1067 if len(children) > 0: # and 'id' in el2.attrib:
1068 child = children[0]
1069 ref1 = child.text
1070 attribkey = add_ns('text:id', nsdict=SNSD)
1071 id1 = el2.get(attribkey, 'footnote-error')
1072 if id1 is None:
1073 id1 = ''
1074 tag = add_ns('text:note-ref', nsdict=SNSD)
1075 el2.tag = tag
1076 if self.settings.endnotes_end_doc:
1077 note_class = 'endnote'
1078 else:
1079 note_class = 'footnote'
1080 el2.attrib.clear()
1081 attribkey = add_ns('text:note-class', nsdict=SNSD)
1082 el2.attrib[attribkey] = note_class
1083 attribkey = add_ns('text:ref-name', nsdict=SNSD)
1084 el2.attrib[attribkey] = id1
1085 attribkey = add_ns('text:reference-format', nsdict=SNSD)
1086 el2.attrib[attribkey] = 'page'
1087 el2.text = ref1
1090 # Utility methods
1092 def append_child(self, tag, attrib=None, parent=None):
1093 if parent is None:
1094 parent = self.current_element
1095 if attrib is None:
1096 el = SubElement(parent, tag)
1097 else:
1098 el = SubElement(parent, tag, attrib)
1099 return el
1101 def append_p(self, style, text=None):
1102 result = self.append_child('text:p', attrib={
1103 'text:style-name': self.rststyle(style)})
1104 self.append_pending_ids(result)
1105 if text is not None:
1106 result.text = text
1107 return result
1109 def append_pending_ids(self, el):
1110 if self.settings.create_links:
1111 for id in self.pending_ids:
1112 SubElement(el, 'text:reference-mark', attrib={
1113 'text:name': id})
1114 self.pending_ids = [ ]
1116 def set_current_element(self, el):
1117 self.current_element = el
1119 def set_to_parent(self):
1120 self.current_element = self.current_element.getparent()
1122 def generate_labeled_block(self, node, label):
1123 el = self.append_p('textbody')
1124 el1 = SubElement(el, 'text:span',
1125 attrib={'text:style-name': self.rststyle('strong')})
1126 el1.text = label
1127 el = self.append_p('blockindent')
1128 return el
1130 def generate_labeled_line(self, node, label):
1131 el = self.append_p('textbody')
1132 el1 = SubElement(el, 'text:span',
1133 attrib={'text:style-name': self.rststyle('strong')})
1134 el1.text = label
1135 el1.tail = node.astext()
1136 return el
1138 def encode(self, text):
1139 text = text.replace(u'\u00a0', " ")
1140 return text
1142 def trace_visit_node(self, node):
1143 if DEBUG >= 1:
1144 self.trace_level += 1
1145 self._trace_show_level(self.trace_level)
1146 if DEBUG >= 2:
1147 print '(visit_%s) node: %s' % (node.tagname, node.astext(), )
1148 else:
1149 print '(visit_%s)' % node.tagname
1151 def trace_depart_node(self, node):
1152 if not DEBUG:
1153 return
1154 self._trace_show_level(self.trace_level)
1155 print '(depart_%s)' % node.tagname
1156 self.trace_level -= 1
1158 def _trace_show_level(self, level):
1159 for idx in range(level):
1160 print ' ',
1163 # Visitor functions
1165 # In alphabetic order, more or less.
1166 # See docutils.docutils.nodes.node_class_names.
1169 def dispatch_visit(self, node):
1170 """Override to catch basic attributes which many nodes have."""
1171 self.handle_basic_atts(node)
1172 nodes.GenericNodeVisitor.dispatch_visit(self, node)
1174 def handle_basic_atts(self, node):
1175 if isinstance(node, nodes.Element) and node['ids']:
1176 self.pending_ids += node['ids']
1178 def default_visit(self, node):
1179 #ipshell('At default_visit')
1180 print 'missing visit_%s' % (node.tagname, )
1182 def default_departure(self, node):
1183 print 'missing depart_%s' % (node.tagname, )
1185 def visit_Text(self, node):
1186 #ipshell('At visit_Text')
1187 # Skip nodes whose text has been processed in parent nodes.
1188 if isinstance(node.parent, docutils.nodes.literal_block):
1189 #isinstance(node.parent, docutils.nodes.term) or \
1190 #isinstance(node.parent, docutils.nodes.definition):
1191 return
1192 text = node.astext()
1193 # Are we in mixed content? If so, add the text to the
1194 # etree tail of the previous sibling element.
1195 if len(self.current_element.getchildren()) > 0:
1196 if self.current_element.getchildren()[-1].tail:
1197 self.current_element.getchildren()[-1].tail += text
1198 else:
1199 self.current_element.getchildren()[-1].tail = text
1200 else:
1201 if self.current_element.text:
1202 self.current_element.text += text
1203 else:
1204 self.current_element.text = text
1206 def depart_Text(self, node):
1207 pass
1210 # Pre-defined fields
1213 def visit_address(self, node):
1214 #ipshell('At visit_address')
1215 el = self.generate_labeled_block(node, 'Address: ')
1216 self.set_current_element(el)
1218 def depart_address(self, node):
1219 self.set_to_parent()
1221 def visit_author(self, node):
1222 if isinstance(node.parent, nodes.authors):
1223 el = self.append_p('blockindent')
1224 else:
1225 el = self.generate_labeled_block(node, 'Author: ')
1226 self.set_current_element(el)
1228 def depart_author(self, node):
1229 self.set_to_parent()
1231 def visit_authors(self, node):
1232 #ipshell('At visit_authors')
1233 #self.trace_visit_node(node)
1234 label = 'Authors:'
1235 el = self.append_p('textbody')
1236 el1 = SubElement(el, 'text:span',
1237 attrib={'text:style-name': self.rststyle('strong')})
1238 el1.text = label
1240 def depart_authors(self, node):
1241 #self.trace_depart_node(node)
1242 pass
1244 def visit_contact(self, node):
1245 el = self.generate_labeled_block(node, 'Contact: ')
1246 self.set_current_element(el)
1248 def depart_contact(self, node):
1249 self.set_to_parent()
1251 def visit_copyright(self, node):
1252 el = self.generate_labeled_block(node, 'Copyright: ')
1253 self.set_current_element(el)
1255 def depart_copyright(self, node):
1256 self.set_to_parent()
1258 def visit_date(self, node):
1259 self.generate_labeled_line(node, 'Date: ')
1261 def depart_date(self, node):
1262 pass
1264 def visit_organization(self, node):
1265 el = self.generate_labeled_block(node, 'Organization: ')
1266 self.set_current_element(el)
1268 def depart_organization(self, node):
1269 self.set_to_parent()
1271 def visit_status(self, node):
1272 el = self.generate_labeled_block(node, 'Status: ')
1273 self.set_current_element(el)
1275 def depart_status(self, node):
1276 self.set_to_parent()
1278 def visit_revision(self, node):
1279 self.generate_labeled_line(node, 'Revision: ')
1281 def depart_revision(self, node):
1282 pass
1284 def visit_version(self, node):
1285 el = self.generate_labeled_line(node, 'Version: ')
1286 #self.set_current_element(el)
1288 def depart_version(self, node):
1289 #self.set_to_parent()
1290 pass
1292 def visit_attribution(self, node):
1293 #ipshell('At visit_attribution')
1294 el = self.append_p('attribution', node.astext())
1296 def depart_attribution(self, node):
1297 #ipshell('At depart_attribution')
1298 pass
1300 def visit_block_quote(self, node):
1301 #ipshell('At visit_block_quote')
1302 if 'epigraph' in node.attributes['classes']:
1303 self.paragraph_style_stack.append(self.rststyle('epigraph'))
1304 self.blockstyle = self.rststyle('epigraph')
1305 elif 'highlights' in node.attributes['classes']:
1306 self.paragraph_style_stack.append(self.rststyle('highlights'))
1307 self.blockstyle = self.rststyle('highlights')
1308 else:
1309 self.paragraph_style_stack.append(self.rststyle('blockquote'))
1310 self.blockstyle = self.rststyle('blockquote')
1311 self.line_indent_level += 1
1313 def depart_block_quote(self, node):
1314 self.paragraph_style_stack.pop()
1315 self.blockstyle = ''
1316 self.line_indent_level -= 1
1318 def visit_bullet_list(self, node):
1319 #ipshell('At visit_bullet_list')
1320 if self.in_table_of_contents:
1321 if node.has_key('classes') and \
1322 'auto-toc' in node.attributes['classes']:
1323 el = SubElement(self.current_element, 'text:list', attrib={
1324 'text:style-name': self.rststyle('tocenumlist'),
1326 self.list_style_stack.append(self.rststyle('enumitem'))
1327 else:
1328 el = SubElement(self.current_element, 'text:list', attrib={
1329 'text:style-name': self.rststyle('tocbulletlist'),
1331 self.list_style_stack.append(self.rststyle('bulletitem'))
1332 else:
1333 if self.blockstyle == self.rststyle('blockquote'):
1334 el = SubElement(self.current_element, 'text:list', attrib={
1335 'text:style-name': self.rststyle('blockquote-bulletlist'),
1337 self.list_style_stack.append(self.rststyle('blockquote-bulletitem'))
1338 elif self.blockstyle == self.rststyle('highlights'):
1339 el = SubElement(self.current_element, 'text:list', attrib={
1340 'text:style-name': self.rststyle('highlights-bulletlist'),
1342 self.list_style_stack.append(self.rststyle('highlights-bulletitem'))
1343 elif self.blockstyle == self.rststyle('epigraph'):
1344 el = SubElement(self.current_element, 'text:list', attrib={
1345 'text:style-name': self.rststyle('epigraph-bulletlist'),
1347 self.list_style_stack.append(self.rststyle('epigraph-bulletitem'))
1348 else:
1349 el = SubElement(self.current_element, 'text:list', attrib={
1350 'text:style-name': self.rststyle('bulletlist'),
1352 self.list_style_stack.append(self.rststyle('bulletitem'))
1353 self.set_current_element(el)
1355 def depart_bullet_list(self, node):
1356 self.set_to_parent()
1357 self.list_style_stack.pop()
1359 def visit_caption(self, node):
1360 raise nodes.SkipChildren()
1361 pass
1363 def depart_caption(self, node):
1364 pass
1366 def visit_comment(self, node):
1367 #ipshell('At visit_comment')
1368 el = self.append_p('textbody')
1369 el1 = SubElement(el, 'office:annotation', attrib={})
1370 el2 = SubElement(el1, 'text:p', attrib={})
1371 el2.text = node.astext()
1373 def depart_comment(self, node):
1374 pass
1376 def visit_compound(self, node):
1377 # The compound directive currently receives no special treatment.
1378 pass
1380 def depart_compound(self, node):
1381 pass
1383 def visit_container(self, node):
1384 styles = node.attributes.get('classes', ())
1385 if len(styles) > 0:
1386 self.paragraph_style_stack.append(self.rststyle(styles[0]))
1388 def depart_container(self, node):
1389 #ipshell('At depart_container')
1390 styles = node.attributes.get('classes', ())
1391 if len(styles) > 0:
1392 self.paragraph_style_stack.pop()
1394 def visit_decoration(self, node):
1395 #global DEBUG
1396 #ipshell('At visit_decoration')
1397 #DEBUG = 1
1398 #self.trace_visit_node(node)
1399 pass
1401 def depart_decoration(self, node):
1402 #ipshell('At depart_decoration')
1403 pass
1405 def visit_definition(self, node):
1406 self.paragraph_style_stack.append(self.rststyle('blockindent'))
1407 self.bumped_list_level_stack.append(ListLevel(1))
1409 def depart_definition(self, node):
1410 self.paragraph_style_stack.pop()
1411 self.bumped_list_level_stack.pop()
1413 def visit_definition_list(self, node):
1414 pass
1416 def depart_definition_list(self, node):
1417 pass
1419 def visit_definition_list_item(self, node):
1420 pass
1422 def depart_definition_list_item(self, node):
1423 pass
1425 def visit_term(self, node):
1426 #ipshell('At visit_term')
1427 el = self.append_p('textbody')
1428 el1 = SubElement(el, 'text:span',
1429 attrib={'text:style-name': self.rststyle('strong')})
1430 #el1.text = node.astext()
1431 self.set_current_element(el1)
1433 def depart_term(self, node):
1434 #ipshell('At depart_term')
1435 self.set_to_parent()
1436 self.set_to_parent()
1438 def visit_classifier(self, node):
1439 #ipshell('At visit_classifier')
1440 els = self.current_element.getchildren()
1441 if len(els) > 0:
1442 el = els[-1]
1443 el1 = SubElement(el, 'text:span',
1444 attrib={'text:style-name': self.rststyle('emphasis')
1446 el1.text = ' (%s)' % (node.astext(), )
1448 def depart_classifier(self, node):
1449 pass
1451 def visit_document(self, node):
1452 #ipshell('At visit_document')
1453 pass
1455 def depart_document(self, node):
1456 self.process_footnotes()
1458 def visit_docinfo(self, node):
1459 #self.trace_visit_node(node)
1460 self.section_level += 1
1461 self.section_count += 1
1462 if self.settings.create_sections:
1463 el = self.append_child('text:section', attrib={
1464 'text:name': 'Section%d' % self.section_count,
1465 'text:style-name': 'Sect%d' % self.section_level,
1467 self.set_current_element(el)
1469 def depart_docinfo(self, node):
1470 #self.trace_depart_node(node)
1471 self.section_level -= 1
1472 if self.settings.create_sections:
1473 self.set_to_parent()
1475 def visit_emphasis(self, node):
1476 el = SubElement(self.current_element, 'text:span',
1477 attrib={'text:style-name': self.rststyle('emphasis')})
1478 self.set_current_element(el)
1480 def depart_emphasis(self, node):
1481 self.set_to_parent()
1483 def visit_enumerated_list(self, node):
1484 el1 = self.current_element
1485 if self.blockstyle == self.rststyle('blockquote'):
1486 el2 = SubElement(el1, 'text:list', attrib={
1487 'text:style-name': self.rststyle('blockquote-enumlist'),
1489 self.list_style_stack.append(self.rststyle('blockquote-enumitem'))
1490 elif self.blockstyle == self.rststyle('highlights'):
1491 el2 = SubElement(el1, 'text:list', attrib={
1492 'text:style-name': self.rststyle('highlights-enumlist'),
1494 self.list_style_stack.append(self.rststyle('highlights-enumitem'))
1495 elif self.blockstyle == self.rststyle('epigraph'):
1496 el2 = SubElement(el1, 'text:list', attrib={
1497 'text:style-name': self.rststyle('epigraph-enumlist'),
1499 self.list_style_stack.append(self.rststyle('epigraph-enumitem'))
1500 else:
1501 el2 = SubElement(el1, 'text:list', attrib={
1502 'text:style-name': self.rststyle('enumlist'),
1504 self.list_style_stack.append(self.rststyle('enumitem'))
1505 self.set_current_element(el2)
1507 def depart_enumerated_list(self, node):
1508 self.set_to_parent()
1509 self.list_style_stack.pop()
1511 def visit_list_item(self, node):
1512 #ipshell('At visit_list_item')
1513 el1 = self.append_child('text:list-item')
1514 # If we are in a "bumped" list level, then wrap this
1515 # list in an outer lists in order to increase the
1516 # indentation level.
1517 el3 = el1
1518 if len(self.bumped_list_level_stack) > 0:
1519 level_obj = self.bumped_list_level_stack[-1]
1520 if level_obj.get_sibling():
1521 level_obj.set_nested(False)
1522 for level_obj1 in self.bumped_list_level_stack:
1523 for idx in range(level_obj1.get_level()):
1524 el2 = self.append_child('text:list', parent=el3)
1525 el3 = self.append_child('text:list-item', parent=el2)
1526 self.paragraph_style_stack.append(self.list_style_stack[-1])
1527 self.set_current_element(el3)
1529 def depart_list_item(self, node):
1530 if len(self.bumped_list_level_stack) > 0:
1531 level_obj = self.bumped_list_level_stack[-1]
1532 if level_obj.get_sibling():
1533 level_obj.set_nested(True)
1534 for level_obj1 in self.bumped_list_level_stack:
1535 for idx in range(level_obj1.get_level()):
1536 self.set_to_parent()
1537 self.set_to_parent()
1538 self.paragraph_style_stack.pop()
1539 self.set_to_parent()
1541 def visit_header(self, node):
1542 #ipshell('At visit_header')
1543 self.in_header = True
1545 def depart_header(self, node):
1546 #ipshell('At depart_header')
1547 self.in_header = False
1549 def visit_footer(self, node):
1550 #ipshell('At visit_footer')
1551 self.in_footer = True
1553 def depart_footer(self, node):
1554 #ipshell('At depart_footer')
1555 self.in_footer = False
1557 def visit_field(self, node):
1558 pass
1560 def depart_field(self, node):
1561 pass
1563 def visit_field_list(self, node):
1564 #ipshell('At visit_field_list')
1565 pass
1567 def depart_field_list(self, node):
1568 #ipshell('At depart_field_list')
1569 pass
1571 def visit_field_name(self, node):
1572 #ipshell('At visit_field_name')
1573 #self.trace_visit_node(node)
1574 el = self.append_p('textbody')
1575 el1 = SubElement(el, 'text:span',
1576 attrib={'text:style-name': self.rststyle('strong')})
1577 el1.text = node.astext()
1579 def depart_field_name(self, node):
1580 #self.trace_depart_node(node)
1581 pass
1583 def visit_field_body(self, node):
1584 #ipshell('At visit_field_body')
1585 #self.trace_visit_node(node)
1586 self.paragraph_style_stack.append(self.rststyle('blockindent'))
1588 def depart_field_body(self, node):
1589 #self.trace_depart_node(node)
1590 self.paragraph_style_stack.pop()
1592 def visit_figure(self, node):
1593 #ipshell('At visit_figure')
1594 #self.trace_visit_node(node)
1595 pass
1597 def depart_figure(self, node):
1598 #self.trace_depart_node(node)
1599 pass
1601 def visit_footnote(self, node):
1602 #ipshell('At visit_footnote')
1603 self.footnote_level += 1
1604 self.save_footnote_current = self.current_element
1605 el1 = Element('text:note-body')
1606 self.current_element = el1
1607 self.footnote_list.append((node, el1))
1608 if isinstance(node, docutils.nodes.citation):
1609 self.paragraph_style_stack.append(self.rststyle('citation'))
1610 else:
1611 self.paragraph_style_stack.append(self.rststyle('footnote'))
1613 def depart_footnote(self, node):
1614 #ipshell('At depart_footnote')
1615 self.paragraph_style_stack.pop()
1616 self.current_element = self.save_footnote_current
1617 self.footnote_level -= 1
1619 footnote_chars = [
1620 '*', '**', '***',
1621 '++', '+++',
1622 '##', '###',
1623 '@@', '@@@',
1626 def visit_footnote_reference(self, node):
1627 #ipshell('At visit_footnote_reference')
1628 if self.footnote_level <= 0:
1629 id = node.attributes['ids'][0]
1630 refid = node.attributes.get('refid')
1631 if refid is None:
1632 refid = ''
1633 if self.settings.endnotes_end_doc:
1634 note_class = 'endnote'
1635 else:
1636 note_class = 'footnote'
1637 el1 = self.append_child('text:note', attrib={
1638 'text:id': '%s' % (refid, ),
1639 'text:note-class': note_class,
1641 note_auto = str(node.attributes.get('auto', 1))
1642 if isinstance(node, docutils.nodes.citation_reference):
1643 citation = '[%s]' % node.astext()
1644 el2 = SubElement(el1, 'text:note-citation', attrib={
1645 'text:label': citation,
1647 el2.text = citation
1648 elif note_auto == '1':
1649 el2 = SubElement(el1, 'text:note-citation')
1650 el2.text = node.astext()
1651 elif note_auto == '*':
1652 if self.footnote_chars_idx >= len(
1653 ODFTranslator.footnote_chars):
1654 self.footnote_chars_idx = 0
1655 footnote_char = ODFTranslator.footnote_chars[
1656 self.footnote_chars_idx]
1657 self.footnote_chars_idx += 1
1658 el2 = SubElement(el1, 'text:note-citation', attrib={
1659 'text:label': footnote_char,
1661 el2.text = footnote_char
1662 self.footnote_ref_dict[id] = el1
1663 raise nodes.SkipChildren()
1665 def depart_footnote_reference(self, node):
1666 #ipshell('At depart_footnote_reference')
1667 pass
1669 def visit_citation(self, node):
1670 #ipshell('At visit_citation')
1671 for id in node.attributes['ids']:
1672 self.citation_id = id
1673 break
1674 self.paragraph_style_stack.append(self.rststyle('blockindent'))
1675 self.bumped_list_level_stack.append(ListLevel(1))
1677 def depart_citation(self, node):
1678 #ipshell('At depart_citation')
1679 self.citation_id = None
1680 self.paragraph_style_stack.pop()
1681 self.bumped_list_level_stack.pop()
1683 def visit_citation_reference(self, node):
1684 #ipshell('At visit_citation_reference')
1685 if self.settings.create_links:
1686 id = node.attributes['refid']
1687 el = self.append_child('text:reference-ref', attrib={
1688 'text:ref-name': '%s' % (id, ),
1689 'text:reference-format': 'text',
1691 el.text = '['
1692 self.set_current_element(el)
1693 elif self.current_element.text is None:
1694 self.current_element.text = '['
1695 else:
1696 self.current_element.text += '['
1698 def depart_citation_reference(self, node):
1699 #ipshell('At depart_citation_reference')
1700 self.current_element.text += ']'
1701 if self.settings.create_links:
1702 self.set_to_parent()
1704 # visit_citation = visit_footnote
1705 # depart_citation = depart_footnote
1706 # visit_citation_reference = visit_footnote_reference
1707 # depart_citation_reference = depart_footnote_reference
1709 def visit_label(self, node):
1710 #ipshell('At visit_label')
1711 if isinstance(node.parent, docutils.nodes.footnote):
1712 raise nodes.SkipChildren()
1713 elif self.citation_id is not None:
1714 el = self.append_p('textbody')
1715 self.set_current_element(el)
1716 el.text = '['
1717 if self.settings.create_links:
1718 el1 = self.append_child('text:reference-mark-start', attrib={
1719 'text:name': '%s' % (self.citation_id, ),
1722 def depart_label(self, node):
1723 #ipshell('At depart_label')
1724 if isinstance(node.parent, docutils.nodes.footnote):
1725 pass
1726 elif self.citation_id is not None:
1727 self.current_element.text += ']'
1728 if self.settings.create_links:
1729 el = self.append_child('text:reference-mark-end', attrib={
1730 'text:name': '%s' % (self.citation_id, ),
1732 self.set_to_parent()
1734 def visit_generated(self, node):
1735 pass
1737 def depart_generated(self, node):
1738 pass
1740 def check_file_exists(self, path):
1741 if os.path.exists(path):
1742 return 1
1743 else:
1744 return 0
1746 def visit_image(self, node):
1747 #ipshell('At visit_image')
1748 #self.trace_visit_node(node)
1749 # Capture the image file.
1750 if 'uri' in node.attributes:
1751 source = node.attributes['uri']
1752 if not self.check_file_exists(source):
1753 print 'Error: Cannot find image file %s.' % (source, )
1754 return
1755 else:
1756 return
1757 if source in self.image_dict:
1758 filename, destination = self.image_dict[source]
1759 else:
1760 self.image_count += 1
1761 filename = os.path.split(source)[1]
1762 destination = 'Pictures/1%08x%s' % (self.image_count, filename, )
1763 spec = (os.path.abspath(source), destination,)
1765 self.embedded_file_list.append(spec)
1766 self.image_dict[source] = (source, destination,)
1767 # Is this a figure (containing an image) or just a plain image?
1768 if self.in_paragraph:
1769 el1 = self.current_element
1770 else:
1771 el1 = SubElement(self.current_element, 'text:p',
1772 attrib={'text:style-name': self.rststyle('textbody')})
1773 el2 = el1
1774 if isinstance(node.parent, docutils.nodes.figure):
1775 el3, el4, caption = self.generate_figure(node, source,
1776 destination, el2)
1777 attrib = {
1778 'draw:blue': '0%',
1779 'draw:color-inversion': 'false',
1780 'draw:color-mode': 'standard',
1781 'draw:contrast': '0%',
1782 'draw:gamma': '100%',
1783 'draw:green': '0%',
1784 'draw:image-opacity': '100%',
1785 'draw:luminance': '0%',
1786 'draw:red': '0%',
1787 'fo:border': 'none',
1788 'fo:clip': 'rect(0in 0in 0in 0in)',
1789 'fo:margin-bottom': '0in',
1790 'fo:margin-left': '0in',
1791 'fo:margin-right': '0in',
1792 'fo:margin-top': '0in',
1793 'fo:padding': '0in',
1794 'style:horizontal-pos': 'from-left',
1795 'style:horizontal-rel': 'paragraph-content',
1796 'style:mirror': 'none',
1797 'style:run-through': 'foreground',
1798 'style:shadow': 'none',
1799 'style:vertical-pos': 'from-top',
1800 'style:vertical-rel': 'paragraph-content',
1801 'style:wrap': 'none',
1803 el5, width = self.generate_image(node, source, destination,
1804 el4, attrib)
1805 if caption is not None:
1806 el5.tail = caption
1807 else: #if isinstance(node.parent, docutils.nodes.image):
1808 el3 = self.generate_image(node, source, destination, el2)
1810 def depart_image(self, node):
1811 pass
1813 def get_image_width_height(self, node, attr):
1814 size = None
1815 if attr in node.attributes:
1816 size = node.attributes[attr]
1817 unit = size[-2:]
1818 if unit.isalpha():
1819 size = size[:-2]
1820 else:
1821 unit = 'px'
1822 try:
1823 size = float(size)
1824 except ValueError, e:
1825 print 'Error: Invalid %s for image: "%s"' % (
1826 attr, node.attributes[attr])
1827 size = [size, unit]
1828 return size
1830 def get_image_scale(self, node):
1831 if 'scale' in node.attributes:
1832 try:
1833 scale = int(node.attributes['scale'])
1834 if scale < 1: # or scale > 100:
1835 raise ValueError
1836 scale = scale * 0.01
1837 except ValueError, e:
1838 print 'Error: Invalid scale for image: "%s"' % (
1839 node.attributes['scale'], )
1840 else:
1841 scale = 1.0
1842 return scale
1844 def get_image_scaled_width_height(self, node, source):
1845 scale = self.get_image_scale(node)
1846 width = self.get_image_width_height(node, 'width')
1847 height = self.get_image_width_height(node, 'height')
1849 dpi = (72, 72)
1850 if Image is not None and source in self.image_dict:
1851 filename, destination = self.image_dict[source]
1852 imageobj = Image.open(filename, 'r')
1853 dpi = imageobj.info.get('dpi', dpi)
1854 # dpi information can be (xdpi, ydpi) or xydpi
1855 try: iter(dpi)
1856 except: dpi = (dpi, dpi)
1857 else:
1858 imageobj = None
1860 if width is None or height is None:
1861 if imageobj is None:
1862 raise RuntimeError, 'image size not fully specified and PIL not installed'
1863 if width is None: width = [imageobj.size[0], 'px']
1864 if height is None: height = [imageobj.size[1], 'px']
1866 width[0] *= scale
1867 height[0] *= scale
1868 if width[1] == 'px': width = [width[0] / dpi[0], 'in']
1869 if height[1] == 'px': height = [height[0] / dpi[1], 'in']
1871 width[0] = str(width[0])
1872 height[0] = str(height[0])
1873 return ''.join(width), ''.join(height)
1875 def generate_figure(self, node, source, destination, current_element):
1876 #ipshell('At generate_figure')
1877 caption = None
1878 width, height = self.get_image_scaled_width_height(node, source)
1879 for node1 in node.parent.children:
1880 if node1.tagname == 'caption':
1881 caption = node1.astext()
1882 self.image_style_count += 1
1884 # Add the style for the caption.
1885 if caption is not None:
1886 attrib = {
1887 'style:class': 'extra',
1888 'style:family': 'paragraph',
1889 'style:name': 'Caption',
1890 'style:parent-style-name': 'Standard',
1892 el1 = SubElement(self.automatic_styles, 'style:style',
1893 attrib=attrib, nsdict=SNSD)
1894 attrib = {
1895 'fo:margin-bottom': '0.0835in',
1896 'fo:margin-top': '0.0835in',
1897 'text:line-number': '0',
1898 'text:number-lines': 'false',
1900 el2 = SubElement(el1, 'style:paragraph-properties',
1901 attrib=attrib, nsdict=SNSD)
1902 attrib = {
1903 'fo:font-size': '12pt',
1904 'fo:font-style': 'italic',
1905 'style:font-name': 'Times',
1906 'style:font-name-complex': 'Lucidasans1',
1907 'style:font-size-asian': '12pt',
1908 'style:font-size-complex': '12pt',
1909 'style:font-style-asian': 'italic',
1910 'style:font-style-complex': 'italic',
1912 el2 = SubElement(el1, 'style:text-properties',
1913 attrib=attrib, nsdict=SNSD)
1914 style_name = 'rstframestyle%d' % self.image_style_count
1915 # Add the styles
1916 attrib = {
1917 'style:name': style_name,
1918 'style:family': 'graphic',
1919 'style:parent-style-name': 'Frame',
1921 el1 = SubElement(self.automatic_styles,
1922 'style:style', attrib=attrib, nsdict=SNSD)
1923 halign = 'center'
1924 valign = 'top'
1925 if 'align' in node.attributes:
1926 align = node.attributes['align'].split()
1927 for val in align:
1928 if val in ('left', 'center', 'right'):
1929 halign = val
1930 elif val in ('top', 'middle', 'bottom'):
1931 valign = val
1932 attrib = {
1933 'fo:margin-left': '0cm',
1934 'fo:margin-right': '0cm',
1935 'fo:margin-top': '0cm',
1936 'fo:margin-bottom': '0cm',
1937 'style:wrap': 'dynamic',
1938 'style:number-wrapped-paragraphs': 'no-limit',
1939 'style:vertical-pos': valign,
1940 'style:vertical-rel': 'paragraph',
1941 'style:horizontal-pos': halign,
1942 'style:horizontal-rel': 'paragraph',
1943 'fo:padding': '0cm',
1944 'fo:border': 'none',
1946 el2 = SubElement(el1,
1947 'style:graphic-properties', attrib=attrib, nsdict=SNSD)
1948 attrib = {
1949 'draw:style-name': style_name,
1950 'draw:name': 'Frame1',
1951 'text:anchor-type': 'paragraph',
1952 'draw:z-index': '1',
1954 attrib['svg:width'] = width
1955 # dbg
1956 #attrib['svg:height'] = height
1957 el3 = SubElement(current_element, 'draw:frame', attrib=attrib)
1958 attrib = {}
1959 el4 = SubElement(el3, 'draw:text-box', attrib=attrib)
1960 attrib = {
1961 'text:style-name': self.rststyle('caption'),
1963 el5 = SubElement(el4, 'text:p', attrib=attrib)
1964 return el3, el5, caption
1966 def generate_image(self, node, source, destination, current_element,
1967 #ipshell('At generate_image')
1968 frame_attrs=None):
1969 width, height = self.get_image_scaled_width_height(node, source)
1970 self.image_style_count += 1
1971 style_name = 'rstframestyle%d' % self.image_style_count
1972 # Add the style.
1973 attrib = {
1974 'style:name': style_name,
1975 'style:family': 'graphic',
1976 'style:parent-style-name': 'Graphics',
1978 el1 = SubElement(self.automatic_styles,
1979 'style:style', attrib=attrib, nsdict=SNSD)
1980 halign = None
1981 valign = None
1982 if 'align' in node.attributes:
1983 align = node.attributes['align'].split()
1984 for val in align:
1985 if val in ('left', 'center', 'right'):
1986 halign = val
1987 elif val in ('top', 'middle', 'bottom'):
1988 valign = val
1989 if frame_attrs is None:
1990 attrib = {
1991 'style:vertical-pos': 'top',
1992 'style:vertical-rel': 'paragraph',
1993 #'style:horizontal-pos': halign,
1994 #'style:vertical-pos': valign,
1995 'style:horizontal-rel': 'paragraph',
1996 'style:mirror': 'none',
1997 'fo:clip': 'rect(0cm 0cm 0cm 0cm)',
1998 'draw:luminance': '0%',
1999 'draw:contrast': '0%',
2000 'draw:red': '0%',
2001 'draw:green': '0%',
2002 'draw:blue': '0%',
2003 'draw:gamma': '100%',
2004 'draw:color-inversion': 'false',
2005 'draw:image-opacity': '100%',
2006 'draw:color-mode': 'standard',
2008 else:
2009 attrib = frame_attrs
2010 if halign is not None:
2011 attrib['style:horizontal-pos'] = halign
2012 if valign is not None:
2013 attrib['style:vertical-pos'] = valign
2014 #ipshell('At generate_image')
2015 # If we are inside a table, add a no-wrap style.
2016 if self.is_in_table(node):
2017 attrib['style:wrap'] = 'none'
2018 el2 = SubElement(el1,
2019 'style:graphic-properties', attrib=attrib, nsdict=SNSD)
2020 # Add the content.
2021 #el = SubElement(current_element, 'text:p',
2022 # attrib={'text:style-name': self.rststyle('textbody')})
2023 attrib={
2024 'draw:style-name': style_name,
2025 'draw:name': 'graphics2',
2026 #'text:anchor-type': 'paragraph',
2027 #'svg:width': '%fcm' % (width, ),
2028 #'svg:height': '%fcm' % (height, ),
2029 'draw:z-index': '1',
2031 if isinstance(node.parent, nodes.TextElement):
2032 attrib['text:anchor-type'] = 'char'
2033 else:
2034 attrib['text:anchor-type'] = 'paragraph'
2035 attrib['svg:width'] = width
2036 attrib['svg:height'] = height
2037 el1 = SubElement(current_element, 'draw:frame', attrib=attrib)
2038 el2 = SubElement(el1, 'draw:image', attrib={
2039 'xlink:href': '%s' % (destination, ),
2040 'xlink:type': 'simple',
2041 'xlink:show': 'embed',
2042 'xlink:actuate': 'onLoad',
2044 return el1, width
2046 def is_in_table(self, node):
2047 node1 = node.parent
2048 while node1:
2049 if isinstance(node1, docutils.nodes.entry):
2050 return True
2051 node1 = node1.parent
2052 return False
2054 def visit_legend(self, node):
2055 # Currently, the legend receives *no* special treatment.
2056 #ipshell('At visit_legend')
2057 pass
2059 def depart_legend(self, node):
2060 pass
2062 def visit_line_block(self, node):
2063 #ipshell('At visit_line_block')
2064 self.line_indent_level += 1
2065 self.line_block_level += 1
2067 def depart_line_block(self, node):
2068 #ipshell('At depart_line_block')
2069 if self.line_block_level <= 1:
2070 el1 = SubElement(self.current_element, 'text:p', attrib={
2071 'text:style-name': self.rststyle('lineblock1'),
2073 self.line_indent_level -= 1
2074 self.line_block_level -= 1
2076 def visit_line(self, node):
2077 #ipshell('At visit_line')
2078 style = 'lineblock%d' % self.line_indent_level
2079 el1 = SubElement(self.current_element, 'text:p', attrib={
2080 'text:style-name': self.rststyle(style),
2082 self.current_element = el1
2084 def depart_line(self, node):
2085 #ipshell('At depart_line')
2086 self.set_to_parent()
2088 def visit_literal(self, node):
2089 #ipshell('At visit_literal')
2090 el = SubElement(self.current_element, 'text:span',
2091 attrib={'text:style-name': self.rststyle('inlineliteral')})
2092 self.set_current_element(el)
2094 def depart_literal(self, node):
2095 self.set_to_parent()
2097 def _calculate_code_block_padding(self, line):
2098 count = 0
2099 matchobj = SPACES_PATTERN.match(line)
2100 if matchobj:
2101 pad = matchobj.group()
2102 count = len(pad)
2103 else:
2104 matchobj = TABS_PATTERN.match(line)
2105 if matchobj:
2106 pad = matchobj.group()
2107 count = len(pad) * 8
2108 return count
2110 def _add_syntax_highlighting(self, insource, language):
2111 lexer = pygments.lexers.get_lexer_by_name(language, stripall=True)
2112 if language in ('latex', 'tex'):
2113 fmtr = OdtPygmentsLaTeXFormatter(lambda name, parameters=():
2114 self.rststyle(name, parameters))
2115 else:
2116 fmtr = OdtPygmentsProgFormatter(lambda name, parameters=():
2117 self.rststyle(name, parameters))
2118 outsource = pygments.highlight(insource, lexer, fmtr)
2119 return outsource
2121 def fill_line(self, line):
2122 line = FILL_PAT1.sub(self.fill_func1, line)
2123 line = FILL_PAT2.sub(self.fill_func2, line)
2124 return line
2126 def fill_func1(self, matchobj):
2127 spaces = matchobj.group(0)
2128 repl = '<text:s text:c="%d"/>' % (len(spaces), )
2129 return repl
2131 def fill_func2(self, matchobj):
2132 spaces = matchobj.group(0)
2133 repl = ' <text:s text:c="%d"/>' % (len(spaces) - 1, )
2134 return repl
2136 def visit_literal_block(self, node):
2137 #ipshell('At visit_literal_block')
2138 wrapper1 = '<text:p text:style-name="%s">%%s</text:p>' % (
2139 self.rststyle('codeblock'), )
2140 source = node.astext()
2141 if (pygments and
2142 self.settings.add_syntax_highlighting and
2143 node.get('hilight', False)):
2144 language = node.get('language', 'python')
2145 source = self._add_syntax_highlighting(source, language)
2146 else:
2147 source = escape_cdata(source)
2148 lines = source.split('\n')
2149 lines1 = ['<wrappertag1 xmlns:text="urn:oasis:names:tc:opendocument:xmlns:text:1.0">']
2151 my_lines = []
2152 for my_line in lines:
2153 my_line = self.fill_line(my_line)
2154 my_line = my_line.replace("&#10;", "\n")
2155 my_lines.append(my_line)
2156 my_lines_str = '<text:line-break/>'.join(my_lines)
2157 my_lines_str2 = wrapper1 % (my_lines_str, )
2158 lines1.append(my_lines_str2)
2159 lines1.append('</wrappertag1>')
2160 s1 = ''.join(lines1)
2161 if WhichElementTree != "lxml":
2162 s1 = s1.encode("utf-8")
2163 el1 = etree.fromstring(s1)
2164 children = el1.getchildren()
2165 for child in children:
2166 self.current_element.append(child)
2168 def depart_literal_block(self, node):
2169 pass
2171 visit_doctest_block = visit_literal_block
2172 depart_doctest_block = depart_literal_block
2174 def visit_meta(self, node):
2175 #ipshell('At visit_meta')
2176 name = node.attributes.get('name')
2177 content = node.attributes.get('content')
2178 if name is not None and content is not None:
2179 self.meta_dict[name] = content
2181 def depart_meta(self, node):
2182 pass
2184 def visit_option_list(self, node):
2185 table_name = 'tableoption'
2187 # Generate automatic styles
2188 if not self.optiontablestyles_generated:
2189 self.optiontablestyles_generated = True
2190 el = SubElement(self.automatic_styles, 'style:style', attrib={
2191 'style:name': self.rststyle(table_name),
2192 'style:family': 'table'}, nsdict=SNSD)
2193 el1 = SubElement(el, 'style:table-properties', attrib={
2194 'style:width': '17.59cm',
2195 'table:align': 'left',
2196 'style:shadow': 'none'}, nsdict=SNSD)
2197 el = SubElement(self.automatic_styles, 'style:style', attrib={
2198 'style:name': self.rststyle('%s.%%c' % table_name, ( 'A', )),
2199 'style:family': 'table-column'}, nsdict=SNSD)
2200 el1 = SubElement(el, 'style:table-column-properties', attrib={
2201 'style:column-width': '4.999cm'}, nsdict=SNSD)
2202 el = SubElement(self.automatic_styles, 'style:style', attrib={
2203 'style:name': self.rststyle('%s.%%c' % table_name, ( 'B', )),
2204 'style:family': 'table-column'}, nsdict=SNSD)
2205 el1 = SubElement(el, 'style:table-column-properties', attrib={
2206 'style:column-width': '12.587cm'}, nsdict=SNSD)
2207 el = SubElement(self.automatic_styles, 'style:style', attrib={
2208 'style:name': self.rststyle('%s.%%c%%d' % table_name, ( 'A', 1, )),
2209 'style:family': 'table-cell'}, nsdict=SNSD)
2210 el1 = SubElement(el, 'style:table-cell-properties', attrib={
2211 'fo:background-color': 'transparent',
2212 'fo:padding': '0.097cm',
2213 'fo:border-left': '0.035cm solid #000000',
2214 'fo:border-right': 'none',
2215 'fo:border-top': '0.035cm solid #000000',
2216 'fo:border-bottom': '0.035cm solid #000000'}, nsdict=SNSD)
2217 el2 = SubElement(el1, 'style:background-image', nsdict=SNSD)
2218 el = SubElement(self.automatic_styles, 'style:style', attrib={
2219 'style:name': self.rststyle('%s.%%c%%d' % table_name, ( 'B', 1, )),
2220 'style:family': 'table-cell'}, nsdict=SNSD)
2221 el1 = SubElement(el, 'style:table-cell-properties', attrib={
2222 'fo:padding': '0.097cm',
2223 'fo:border': '0.035cm solid #000000'}, nsdict=SNSD)
2224 el = SubElement(self.automatic_styles, 'style:style', attrib={
2225 'style:name': self.rststyle('%s.%%c%%d' % table_name, ( 'A', 2, )),
2226 'style:family': 'table-cell'}, nsdict=SNSD)
2227 el1 = SubElement(el, 'style:table-cell-properties', attrib={
2228 'fo:padding': '0.097cm',
2229 'fo:border-left': '0.035cm solid #000000',
2230 'fo:border-right': 'none',
2231 'fo:border-top': 'none',
2232 'fo:border-bottom': '0.035cm solid #000000'}, nsdict=SNSD)
2233 el = SubElement(self.automatic_styles, 'style:style', attrib={
2234 'style:name': self.rststyle('%s.%%c%%d' % table_name, ( 'B', 2, )),
2235 'style:family': 'table-cell'}, nsdict=SNSD)
2236 el1 = SubElement(el, 'style:table-cell-properties', attrib={
2237 'fo:padding': '0.097cm',
2238 'fo:border-left': '0.035cm solid #000000',
2239 'fo:border-right': '0.035cm solid #000000',
2240 'fo:border-top': 'none',
2241 'fo:border-bottom': '0.035cm solid #000000'}, nsdict=SNSD)
2243 # Generate table data
2244 el = self.append_child('table:table', attrib={
2245 'table:name': self.rststyle(table_name),
2246 'table:style-name': self.rststyle(table_name),
2248 el1 = SubElement(el, 'table:table-column', attrib={
2249 'table:style-name': self.rststyle('%s.%%c' % table_name, ( 'A', ))})
2250 el1 = SubElement(el, 'table:table-column', attrib={
2251 'table:style-name': self.rststyle('%s.%%c' % table_name, ( 'B', ))})
2252 el1 = SubElement(el, 'table:table-header-rows')
2253 el2 = SubElement(el1, 'table:table-row')
2254 el3 = SubElement(el2, 'table:table-cell', attrib={
2255 'table:style-name': self.rststyle('%s.%%c%%d' % table_name, ( 'A', 1, )),
2256 'office:value-type': 'string'})
2257 el4 = SubElement(el3, 'text:p', attrib={
2258 'text:style-name': 'Table_20_Heading'})
2259 el4.text= 'Option'
2260 el3 = SubElement(el2, 'table:table-cell', attrib={
2261 'table:style-name': self.rststyle('%s.%%c%%d' % table_name, ( 'B', 1, )),
2262 'office:value-type': 'string'})
2263 el4 = SubElement(el3, 'text:p', attrib={
2264 'text:style-name': 'Table_20_Heading'})
2265 el4.text= 'Description'
2266 self.set_current_element(el)
2268 def depart_option_list(self, node):
2269 self.set_to_parent()
2271 def visit_option_list_item(self, node):
2272 el = self.append_child('table:table-row')
2273 self.set_current_element(el)
2275 def depart_option_list_item(self, node):
2276 self.set_to_parent()
2278 def visit_option_group(self, node):
2279 el = self.append_child('table:table-cell', attrib={
2280 'table:style-name': 'Table%d.A2' % self.table_count,
2281 'office:value-type': 'string',
2283 self.set_current_element(el)
2285 def depart_option_group(self, node):
2286 self.set_to_parent()
2288 def visit_option(self, node):
2289 el = self.append_child('text:p', attrib={
2290 'text:style-name': 'Table_20_Contents'})
2291 el.text = node.astext()
2293 def depart_option(self, node):
2294 pass
2296 def visit_option_string(self, node):
2297 pass
2299 def depart_option_string(self, node):
2300 pass
2302 def visit_option_argument(self, node):
2303 #ipshell('At visit_option_argument')
2304 pass
2306 def depart_option_argument(self, node):
2307 pass
2309 def visit_description(self, node):
2310 el = self.append_child('table:table-cell', attrib={
2311 'table:style-name': 'Table%d.B2' % self.table_count,
2312 'office:value-type': 'string',
2314 el1 = SubElement(el, 'text:p', attrib={
2315 'text:style-name': 'Table_20_Contents'})
2316 el1.text = node.astext()
2317 raise nodes.SkipChildren()
2319 def depart_description(self, node):
2320 pass
2322 def visit_paragraph(self, node):
2323 #ipshell('At visit_paragraph')
2324 #self.trace_visit_node(node)
2325 self.in_paragraph = True
2326 if self.in_header:
2327 el = self.append_p('header')
2328 elif self.in_footer:
2329 el = self.append_p('footer')
2330 else:
2331 style_name = self.paragraph_style_stack[-1]
2332 el = self.append_child('text:p',
2333 attrib={'text:style-name': style_name})
2334 self.append_pending_ids(el)
2335 self.set_current_element(el)
2337 def depart_paragraph(self, node):
2338 #ipshell('At depart_paragraph')
2339 #self.trace_depart_node(node)
2340 self.in_paragraph = False
2341 self.set_to_parent()
2342 if self.in_header:
2343 self.header_content.append(self.current_element.getchildren()[-1])
2344 self.current_element.remove(self.current_element.getchildren()[-1])
2345 elif self.in_footer:
2346 self.footer_content.append(self.current_element.getchildren()[-1])
2347 self.current_element.remove(self.current_element.getchildren()[-1])
2349 def visit_problematic(self, node):
2350 #print '(visit_problematic) node: %s' % (node.astext(), )
2351 pass
2353 def depart_problematic(self, node):
2354 pass
2356 def visit_raw(self, node):
2357 #ipshell('At visit_raw')
2358 if 'format' in node.attributes:
2359 formats = node.attributes['format']
2360 formatlist = formats.split()
2361 if 'odt' in formatlist:
2362 rawstr = node.astext()
2363 attrstr = ' '.join(['%s="%s"' % (k, v, )
2364 for k,v in CONTENT_NAMESPACE_ATTRIB.items()])
2365 contentstr = '<stuff %s>%s</stuff>' % (attrstr, rawstr, )
2366 if WhichElementTree != "lxml":
2367 contentstr = contentstr.encode("utf-8")
2368 content = etree.fromstring(contentstr)
2369 elements = content.getchildren()
2370 if len(elements) > 0:
2371 el1 = elements[0]
2372 if self.in_header:
2373 pass
2374 elif self.in_footer:
2375 pass
2376 else:
2377 self.current_element.append(el1)
2378 raise nodes.SkipChildren()
2380 def depart_raw(self, node):
2381 if self.in_header:
2382 pass
2383 elif self.in_footer:
2384 pass
2385 else:
2386 pass
2388 def visit_reference(self, node):
2389 #self.trace_visit_node(node)
2390 text = node.astext()
2391 if self.settings.create_links:
2392 if node.has_key('refuri'):
2393 href = node['refuri']
2394 if ( self.settings.cloak_email_addresses
2395 and href.startswith('mailto:')):
2396 href = self.cloak_mailto(href)
2397 el = self.append_child('text:a', attrib={
2398 'xlink:href': '%s' % href,
2399 'xlink:type': 'simple',
2401 self.set_current_element(el)
2402 elif node.has_key('refid'):
2403 if self.settings.create_links:
2404 href = node['refid']
2405 el = self.append_child('text:reference-ref', attrib={
2406 'text:ref-name': '%s' % href,
2407 'text:reference-format': 'text',
2409 else:
2410 raise RuntimeError, 'References must have "refuri" or "refid" attribute.'
2411 if (self.in_table_of_contents and
2412 len(node.children) >= 1 and
2413 isinstance(node.children[0], docutils.nodes.generated)):
2414 node.remove(node.children[0])
2416 def depart_reference(self, node):
2417 #self.trace_depart_node(node)
2418 if self.settings.create_links:
2419 if node.has_key('refuri'):
2420 self.set_to_parent()
2422 def visit_rubric(self, node):
2423 style_name = self.rststyle('rubric')
2424 classes = node.get('classes')
2425 if classes:
2426 class1 = classes[0]
2427 if class1:
2428 style_name = class1
2429 el = SubElement(self.current_element, 'text:h', attrib = {
2430 #'text:outline-level': '%d' % section_level,
2431 #'text:style-name': 'Heading_20_%d' % section_level,
2432 'text:style-name': style_name,
2434 text = node.astext()
2435 el.text = self.encode(text)
2437 def depart_rubric(self, node):
2438 pass
2440 def visit_section(self, node, move_ids=1):
2441 #ipshell('At visit_section')
2442 self.section_level += 1
2443 self.section_count += 1
2444 if self.settings.create_sections:
2445 el = self.append_child('text:section', attrib={
2446 'text:name': 'Section%d' % self.section_count,
2447 'text:style-name': 'Sect%d' % self.section_level,
2449 self.set_current_element(el)
2451 def depart_section(self, node):
2452 self.section_level -= 1
2453 if self.settings.create_sections:
2454 self.set_to_parent()
2456 def visit_strong(self, node):
2457 #ipshell('At visit_strong')
2458 el = SubElement(self.current_element, 'text:span',
2459 attrib={'text:style-name': self.rststyle('strong')})
2460 self.set_current_element(el)
2462 def depart_strong(self, node):
2463 self.set_to_parent()
2465 def visit_substitution_definition(self, node):
2466 #ipshell('At visit_substitution_definition')
2467 raise nodes.SkipChildren()
2469 def depart_substitution_definition(self, node):
2470 #ipshell('At depart_substitution_definition')
2471 pass
2473 def visit_system_message(self, node):
2474 #print '(visit_system_message) node: %s' % (node.astext(), )
2475 pass
2477 def depart_system_message(self, node):
2478 pass
2480 def visit_table(self, node):
2481 #self.trace_visit_node(node)
2482 #ipshell('At visit_table')
2483 self.table_count += 1
2484 table_name = '%s%%d' % TableStylePrefix
2485 el1 = SubElement(self.automatic_styles, 'style:style', attrib={
2486 'style:name': self.rststyle('%s' % table_name, ( self.table_count, )),
2487 'style:family': 'table',
2488 }, nsdict=SNSD)
2489 el1_1 = SubElement(el1, 'style:table-properties', attrib={
2490 #'style:width': '17.59cm',
2491 'table:align': 'margins',
2492 'fo:margin-top': '0in',
2493 'fo:margin-bottom': '0.10in',
2494 }, nsdict=SNSD)
2495 # We use a single cell style for all cells in this table.
2496 # That's probably not correct, but seems to work.
2497 el2 = SubElement(self.automatic_styles, 'style:style', attrib={
2498 'style:name': self.rststyle('%s.%%c%%d' % table_name, ( self.table_count, 'A', 1, )),
2499 'style:family': 'table-cell',
2500 }, nsdict=SNSD)
2501 line_style1 = '0.%03dcm solid #000000' % self.settings.table_border_thickness
2502 el2_1 = SubElement(el2, 'style:table-cell-properties', attrib={
2503 'fo:padding': '0.049cm',
2504 'fo:border-left': line_style1,
2505 'fo:border-right': line_style1,
2506 'fo:border-top': line_style1,
2507 'fo:border-bottom': line_style1,
2508 }, nsdict=SNSD)
2509 title = None
2510 for child in node.children:
2511 if child.tagname == 'title':
2512 title = child.astext()
2513 break
2514 if title is not None:
2515 el3 = self.append_p('table-title', title)
2516 else:
2517 #print 'no table title'
2518 pass
2519 el4 = SubElement(self.current_element, 'table:table', attrib={
2520 'table:name': self.rststyle('%s' % table_name, ( self.table_count, )),
2521 'table:style-name': self.rststyle('%s' % table_name, ( self.table_count, )),
2523 self.set_current_element(el4)
2524 self.current_table_style = el1
2525 self.table_width = 0
2527 def depart_table(self, node):
2528 #self.trace_depart_node(node)
2529 #ipshell('At depart_table')
2530 attribkey = add_ns('style:width', nsdict=SNSD)
2531 attribval = '%dcm' % self.table_width
2532 self.current_table_style.attrib[attribkey] = attribval
2533 self.set_to_parent()
2535 def visit_tgroup(self, node):
2536 #self.trace_visit_node(node)
2537 #ipshell('At visit_tgroup')
2538 self.column_count = ord('A') - 1
2540 def depart_tgroup(self, node):
2541 #self.trace_depart_node(node)
2542 pass
2544 def visit_colspec(self, node):
2545 #self.trace_visit_node(node)
2546 #ipshell('At visit_colspec')
2547 self.column_count += 1
2548 colspec_name = self.rststyle('%s%%d.%%s' % TableStylePrefix, ( self.table_count, chr(self.column_count), ))
2549 colwidth = node['colwidth']
2550 el1 = SubElement(self.automatic_styles, 'style:style', attrib={
2551 'style:name': colspec_name,
2552 'style:family': 'table-column',
2553 }, nsdict=SNSD)
2554 el1_1 = SubElement(el1, 'style:table-column-properties', attrib={
2555 'style:column-width': '%dcm' % colwidth }, nsdict=SNSD)
2556 el2 = self.append_child('table:table-column', attrib={
2557 'table:style-name': colspec_name,
2559 self.table_width += colwidth
2561 def depart_colspec(self, node):
2562 #self.trace_depart_node(node)
2563 pass
2565 def visit_thead(self, node):
2566 #self.trace_visit_node(node)
2567 #ipshell('At visit_thead')
2568 el = self.append_child('table:table-header-rows')
2569 self.set_current_element(el)
2570 self.in_thead = True
2571 self.paragraph_style_stack.append('Table_20_Heading')
2573 def depart_thead(self, node):
2574 #self.trace_depart_node(node)
2575 self.set_to_parent()
2576 self.in_thead = False
2577 self.paragraph_style_stack.pop()
2579 def visit_row(self, node):
2580 #self.trace_visit_node(node)
2581 #ipshell('At visit_row')
2582 self.column_count = ord('A') - 1
2583 el = self.append_child('table:table-row')
2584 self.set_current_element(el)
2586 def depart_row(self, node):
2587 #self.trace_depart_node(node)
2588 self.set_to_parent()
2590 def visit_entry(self, node):
2591 #self.trace_visit_node(node)
2592 #ipshell('At visit_entry')
2593 self.column_count += 1
2594 cellspec_name = self.rststyle('%s%%d.%%c%%d' % TableStylePrefix, ( self.table_count, 'A', 1, ))
2595 attrib={
2596 'table:style-name': cellspec_name,
2597 'office:value-type': 'string',
2599 morecols = node.get('morecols', 0)
2600 if morecols > 0:
2601 attrib['table:number-columns-spanned'] = '%d' % (morecols + 1,)
2602 self.column_count += morecols
2603 el1 = self.append_child('table:table-cell', attrib=attrib)
2604 self.set_current_element(el1)
2606 def depart_entry(self, node):
2607 #self.trace_depart_node(node)
2608 self.set_to_parent()
2610 def visit_tbody(self, node):
2611 #self.trace_visit_node(node)
2612 #ipshell('At visit_')
2613 pass
2615 def depart_tbody(self, node):
2616 #self.trace_depart_node(node)
2617 pass
2619 def visit_target(self, node):
2621 # I don't know how to implement targets in ODF.
2622 # How do we create a target in oowriter? A cross-reference?
2623 if not (node.has_key('refuri') or node.has_key('refid')
2624 or node.has_key('refname')):
2625 pass
2626 else:
2627 pass
2629 def depart_target(self, node):
2630 pass
2632 def visit_title(self, node, move_ids=1, title_type='title'):
2633 #ipshell('At visit_title')
2634 if isinstance(node.parent, docutils.nodes.section):
2635 section_level = self.section_level
2636 if section_level > 7:
2637 print 'Warning: Heading/section levels greater than 7 not supported.'
2638 print ' Reducing to heading level 7 for heading:'
2639 print ' "%s"' % node.astext()
2640 section_level = 7
2641 el1 = self.append_child('text:h', attrib = {
2642 'text:outline-level': '%d' % section_level,
2643 #'text:style-name': 'Heading_20_%d' % section_level,
2644 'text:style-name': self.rststyle('heading%d', (section_level, )),
2646 self.append_pending_ids(el1)
2647 self.set_current_element(el1)
2648 elif isinstance(node.parent, docutils.nodes.document):
2649 # text = self.settings.title
2650 #else:
2651 # text = node.astext()
2652 el1 = SubElement(self.current_element, 'text:p', attrib = {
2653 'text:style-name': self.rststyle(title_type),
2655 self.append_pending_ids(el1)
2656 text = node.astext()
2657 self.title = text
2658 self.found_doc_title = True
2659 self.set_current_element(el1)
2661 def depart_title(self, node):
2662 if (isinstance(node.parent, docutils.nodes.section) or
2663 isinstance(node.parent, docutils.nodes.document)):
2664 self.set_to_parent()
2666 def visit_subtitle(self, node, move_ids=1):
2667 self.visit_title(node, move_ids, title_type='subtitle')
2669 def depart_subtitle(self, node):
2670 self.depart_title(node)
2672 def visit_title_reference(self, node):
2673 #ipshell('At visit_title_reference')
2674 el = self.append_child('text:span', attrib={
2675 'text:style-name': self.rststyle('quotation')})
2676 el.text = self.encode(node.astext())
2678 def depart_title_reference(self, node):
2679 pass
2681 def visit_topic(self, node):
2682 #ipshell('At visit_topic')
2683 if 'classes' in node.attributes:
2684 if 'contents' in node.attributes['classes']:
2685 el = self.append_p('horizontalline')
2686 el = self.append_p('centeredtextbody')
2687 el1 = SubElement(el, 'text:span',
2688 attrib={'text:style-name': self.rststyle('strong')})
2689 el1.text = 'Contents'
2690 self.in_table_of_contents = True
2691 elif 'abstract' in node.attributes['classes']:
2692 el = self.append_p('horizontalline')
2693 el = self.append_p('centeredtextbody')
2694 el1 = SubElement(el, 'text:span',
2695 attrib={'text:style-name': self.rststyle('strong')})
2696 el1.text = 'Abstract'
2698 def depart_topic(self, node):
2699 #ipshell('At depart_topic')
2700 if 'classes' in node.attributes:
2701 if 'contents' in node.attributes['classes']:
2702 el = self.append_p('horizontalline')
2703 self.in_table_of_contents = False
2705 def visit_transition(self, node):
2706 el = self.append_p('horizontalline')
2708 def depart_transition(self, node):
2709 pass
2712 # Admonitions
2714 def visit_warning(self, node):
2715 self.generate_admonition(node, 'warning')
2717 def depart_warning(self, node):
2718 self.paragraph_style_stack.pop()
2720 def visit_attention(self, node):
2721 self.generate_admonition(node, 'attention')
2723 depart_attention = depart_warning
2725 def visit_caution(self, node):
2726 self.generate_admonition(node, 'caution')
2728 depart_caution = depart_warning
2730 def visit_danger(self, node):
2731 self.generate_admonition(node, 'danger')
2733 depart_danger = depart_warning
2735 def visit_error(self, node):
2736 self.generate_admonition(node, 'error')
2738 depart_error = depart_warning
2740 def visit_hint(self, node):
2741 self.generate_admonition(node, 'hint')
2743 depart_hint = depart_warning
2745 def visit_important(self, node):
2746 self.generate_admonition(node, 'important')
2748 depart_important = depart_warning
2750 def visit_note(self, node):
2751 self.generate_admonition(node, 'note')
2753 depart_note = depart_warning
2755 def visit_tip(self, node):
2756 self.generate_admonition(node, 'tip')
2758 depart_tip = depart_warning
2760 def visit_admonition(self, node):
2761 #import pdb; pdb.set_trace()
2762 title = None
2763 for child in node.children:
2764 if child.tagname == 'title':
2765 title = child.astext()
2766 if title is None:
2767 classes1 = node.get('classes')
2768 if classes1:
2769 title = classes1[0]
2770 self.generate_admonition(node, 'generic', title)
2772 depart_admonition = depart_warning
2774 def generate_admonition(self, node, label, title=None):
2775 el1 = SubElement(self.current_element, 'text:p', attrib = {
2776 'text:style-name': self.rststyle('admon-%s-hdr', ( label, )),
2778 if title:
2779 el1.text = title
2780 else:
2781 el1.text = '%s!' % (label.capitalize(), )
2782 s1 = self.rststyle('admon-%s-body', ( label, ))
2783 self.paragraph_style_stack.append(s1)
2786 # Roles (e.g. subscript, superscript, strong, ...
2788 def visit_subscript(self, node):
2789 el = self.append_child('text:span', attrib={
2790 'text:style-name': 'rststyle-subscript',
2792 self.set_current_element(el)
2794 def depart_subscript(self, node):
2795 self.set_to_parent()
2797 def visit_superscript(self, node):
2798 el = self.append_child('text:span', attrib={
2799 'text:style-name': 'rststyle-superscript',
2801 self.set_current_element(el)
2803 def depart_superscript(self, node):
2804 self.set_to_parent()
2807 # Use an own reader to modify transformations done.
2808 class Reader(standalone.Reader):
2810 def get_transforms(self):
2811 default = standalone.Reader.get_transforms(self)
2812 if self.settings.create_links:
2813 return default
2814 return [ i
2815 for i in default
2816 if i is not references.DanglingReferences ]