Fix to custom-odt-header/footer, esp. for MS Windows
[docutils.git] / docutils / writers / odf_odt / __init__.py
blob996947e28c680e6c2ff2cef58d13deb99a4bad88
1 # $Id$
2 # Author: Dave Kuhlman <dkuhlman@rexx.com>
3 # Copyright: This module has been placed in the public domain.
5 """
6 Open Document Format (ODF) Writer.
8 """
10 VERSION = '1.0a'
12 __docformat__ = 'reStructuredText'
15 import sys
16 import os
17 import os.path
18 import tempfile
19 import zipfile
20 from xml.dom import minidom
21 import time
22 import re
23 import StringIO
24 import inspect
25 import imp
26 import copy
27 import docutils
28 from docutils import frontend, nodes, utils, writers, languages
29 from docutils.parsers import rst
30 from docutils.readers import standalone
31 from docutils.transforms import references
34 WhichElementTree = ''
35 try:
36 # 1. Try to use lxml.
37 #from lxml import etree
38 #WhichElementTree = 'lxml'
39 raise ImportError('Ignoring lxml')
40 except ImportError, e:
41 try:
42 # 2. Try to use ElementTree from the Python standard library.
43 from xml.etree import ElementTree as etree
44 WhichElementTree = 'elementtree'
45 except ImportError, e:
46 try:
47 # 3. Try to use a version of ElementTree installed as a separate
48 # product.
49 from elementtree import ElementTree as etree
50 WhichElementTree = 'elementtree'
51 except ImportError, e:
52 s1 = 'Must install either a version of Python containing ' \
53 'ElementTree (Python version >=2.5) or install ElementTree.'
54 raise ImportError(s1)
57 # Import pygments and odtwriter pygments formatters if possible.
58 try:
59 import pygments
60 import pygments.lexers
61 from pygmentsformatter import OdtPygmentsProgFormatter, \
62 OdtPygmentsLaTeXFormatter
63 except ImportError, exp:
64 pygments = None
67 # Is the PIL imaging library installed?
68 try:
69 import Image
70 except ImportError, exp:
71 Image = None
73 ## import warnings
74 ## warnings.warn('importing IPShellEmbed', UserWarning)
75 ## from IPython.Shell import IPShellEmbed
76 ## args = ['-pdb', '-pi1', 'In <\\#>: ', '-pi2', ' .\\D.: ',
77 ## '-po', 'Out<\\#>: ', '-nosep']
78 ## ipshell = IPShellEmbed(args,
79 ## banner = 'Entering IPython. Press Ctrl-D to exit.',
80 ## exit_msg = 'Leaving Interpreter, back to program.')
84 # ElementTree does not support getparent method (lxml does).
85 # This wrapper class and the following support functions provide
86 # that support for the ability to get the parent of an element.
88 if WhichElementTree == 'elementtree':
89 class _ElementInterfaceWrapper(etree._ElementInterface):
90 def __init__(self, tag, attrib=None):
91 etree._ElementInterface.__init__(self, tag, attrib)
92 if attrib is None:
93 attrib = {}
94 self.parent = None
95 def setparent(self, parent):
96 self.parent = parent
97 def getparent(self):
98 return self.parent
102 # Constants and globals
104 SPACES_PATTERN = re.compile(r'( +)')
105 TABS_PATTERN = re.compile(r'(\t+)')
106 FILL_PAT1 = re.compile(r'^ +')
107 FILL_PAT2 = re.compile(r' {2,}')
109 TABLESTYLEPREFIX = 'rststyle-table-'
110 TABLENAMEDEFAULT = '%s0' % TABLESTYLEPREFIX
111 TABLEPROPERTYNAMES = ('border', 'border-top', 'border-left',
112 'border-right', 'border-bottom', )
114 GENERATOR_DESC = 'Docutils.org/odf_odt'
116 NAME_SPACE_1 = 'urn:oasis:names:tc:opendocument:xmlns:office:1.0'
118 CONTENT_NAMESPACE_DICT = CNSD = {
119 # 'office:version': '1.0',
120 'chart': 'urn:oasis:names:tc:opendocument:xmlns:chart:1.0',
121 'dc': 'http://purl.org/dc/elements/1.1/',
122 'dom': 'http://www.w3.org/2001/xml-events',
123 'dr3d': 'urn:oasis:names:tc:opendocument:xmlns:dr3d:1.0',
124 'draw': 'urn:oasis:names:tc:opendocument:xmlns:drawing:1.0',
125 'fo': 'urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0',
126 'form': 'urn:oasis:names:tc:opendocument:xmlns:form:1.0',
127 'math': 'http://www.w3.org/1998/Math/MathML',
128 'meta': 'urn:oasis:names:tc:opendocument:xmlns:meta:1.0',
129 'number': 'urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0',
130 'office': NAME_SPACE_1,
131 'ooo': 'http://openoffice.org/2004/office',
132 'oooc': 'http://openoffice.org/2004/calc',
133 'ooow': 'http://openoffice.org/2004/writer',
134 'presentation': 'urn:oasis:names:tc:opendocument:xmlns:presentation:1.0',
136 'script': 'urn:oasis:names:tc:opendocument:xmlns:script:1.0',
137 'style': 'urn:oasis:names:tc:opendocument:xmlns:style:1.0',
138 'svg': 'urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0',
139 'table': 'urn:oasis:names:tc:opendocument:xmlns:table:1.0',
140 'text': 'urn:oasis:names:tc:opendocument:xmlns:text:1.0',
141 'xforms': 'http://www.w3.org/2002/xforms',
142 'xlink': 'http://www.w3.org/1999/xlink',
143 'xsd': 'http://www.w3.org/2001/XMLSchema',
144 'xsi': 'http://www.w3.org/2001/XMLSchema-instance',
147 STYLES_NAMESPACE_DICT = SNSD = {
148 # 'office:version': '1.0',
149 'chart': 'urn:oasis:names:tc:opendocument:xmlns:chart:1.0',
150 'dc': 'http://purl.org/dc/elements/1.1/',
151 'dom': 'http://www.w3.org/2001/xml-events',
152 'dr3d': 'urn:oasis:names:tc:opendocument:xmlns:dr3d:1.0',
153 'draw': 'urn:oasis:names:tc:opendocument:xmlns:drawing:1.0',
154 'fo': 'urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0',
155 'form': 'urn:oasis:names:tc:opendocument:xmlns:form:1.0',
156 'math': 'http://www.w3.org/1998/Math/MathML',
157 'meta': 'urn:oasis:names:tc:opendocument:xmlns:meta:1.0',
158 'number': 'urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0',
159 'office': NAME_SPACE_1,
160 'presentation': 'urn:oasis:names:tc:opendocument:xmlns:presentation:1.0',
161 'ooo': 'http://openoffice.org/2004/office',
162 'oooc': 'http://openoffice.org/2004/calc',
163 'ooow': 'http://openoffice.org/2004/writer',
164 'script': 'urn:oasis:names:tc:opendocument:xmlns:script:1.0',
165 'style': 'urn:oasis:names:tc:opendocument:xmlns:style:1.0',
166 'svg': 'urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0',
167 'table': 'urn:oasis:names:tc:opendocument:xmlns:table:1.0',
168 'text': 'urn:oasis:names:tc:opendocument:xmlns:text:1.0',
169 'xlink': 'http://www.w3.org/1999/xlink',
172 MANIFEST_NAMESPACE_DICT = MANNSD = {
173 'manifest': 'urn:oasis:names:tc:opendocument:xmlns:manifest:1.0',
176 META_NAMESPACE_DICT = METNSD = {
177 # 'office:version': '1.0',
178 'dc': 'http://purl.org/dc/elements/1.1/',
179 'meta': 'urn:oasis:names:tc:opendocument:xmlns:meta:1.0',
180 'office': NAME_SPACE_1,
181 'ooo': 'http://openoffice.org/2004/office',
182 'xlink': 'http://www.w3.org/1999/xlink',
186 # Attribute dictionaries for use with ElementTree (not lxml), which
187 # does not support use of nsmap parameter on Element() and SubElement().
189 CONTENT_NAMESPACE_ATTRIB = {
190 'office:version': '1.0',
191 'xmlns:chart': 'urn:oasis:names:tc:opendocument:xmlns:chart:1.0',
192 'xmlns:dc': 'http://purl.org/dc/elements/1.1/',
193 'xmlns:dom': 'http://www.w3.org/2001/xml-events',
194 'xmlns:dr3d': 'urn:oasis:names:tc:opendocument:xmlns:dr3d:1.0',
195 'xmlns:draw': 'urn:oasis:names:tc:opendocument:xmlns:drawing:1.0',
196 'xmlns:fo': 'urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0',
197 'xmlns:form': 'urn:oasis:names:tc:opendocument:xmlns:form:1.0',
198 'xmlns:math': 'http://www.w3.org/1998/Math/MathML',
199 'xmlns:meta': 'urn:oasis:names:tc:opendocument:xmlns:meta:1.0',
200 'xmlns:number': 'urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0',
201 'xmlns:office': NAME_SPACE_1,
202 'xmlns:presentation': 'urn:oasis:names:tc:opendocument:xmlns:presentation:1.0',
203 'xmlns:ooo': 'http://openoffice.org/2004/office',
204 'xmlns:oooc': 'http://openoffice.org/2004/calc',
205 'xmlns:ooow': 'http://openoffice.org/2004/writer',
206 'xmlns:script': 'urn:oasis:names:tc:opendocument:xmlns:script:1.0',
207 'xmlns:style': 'urn:oasis:names:tc:opendocument:xmlns:style:1.0',
208 'xmlns:svg': 'urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0',
209 'xmlns:table': 'urn:oasis:names:tc:opendocument:xmlns:table:1.0',
210 'xmlns:text': 'urn:oasis:names:tc:opendocument:xmlns:text:1.0',
211 'xmlns:xforms': 'http://www.w3.org/2002/xforms',
212 'xmlns:xlink': 'http://www.w3.org/1999/xlink',
213 'xmlns:xsd': 'http://www.w3.org/2001/XMLSchema',
214 'xmlns:xsi': 'http://www.w3.org/2001/XMLSchema-instance',
217 STYLES_NAMESPACE_ATTRIB = {
218 'office:version': '1.0',
219 'xmlns:chart': 'urn:oasis:names:tc:opendocument:xmlns:chart:1.0',
220 'xmlns:dc': 'http://purl.org/dc/elements/1.1/',
221 'xmlns:dom': 'http://www.w3.org/2001/xml-events',
222 'xmlns:dr3d': 'urn:oasis:names:tc:opendocument:xmlns:dr3d:1.0',
223 'xmlns:draw': 'urn:oasis:names:tc:opendocument:xmlns:drawing:1.0',
224 'xmlns:fo': 'urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0',
225 'xmlns:form': 'urn:oasis:names:tc:opendocument:xmlns:form:1.0',
226 'xmlns:math': 'http://www.w3.org/1998/Math/MathML',
227 'xmlns:meta': 'urn:oasis:names:tc:opendocument:xmlns:meta:1.0',
228 'xmlns:number': 'urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0',
229 'xmlns:office': NAME_SPACE_1,
230 'xmlns:presentation': 'urn:oasis:names:tc:opendocument:xmlns:presentation:1.0',
231 'xmlns:ooo': 'http://openoffice.org/2004/office',
232 'xmlns:oooc': 'http://openoffice.org/2004/calc',
233 'xmlns:ooow': 'http://openoffice.org/2004/writer',
234 'xmlns:script': 'urn:oasis:names:tc:opendocument:xmlns:script:1.0',
235 'xmlns:style': 'urn:oasis:names:tc:opendocument:xmlns:style:1.0',
236 'xmlns:svg': 'urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0',
237 'xmlns:table': 'urn:oasis:names:tc:opendocument:xmlns:table:1.0',
238 'xmlns:text': 'urn:oasis:names:tc:opendocument:xmlns:text:1.0',
239 'xmlns:xlink': 'http://www.w3.org/1999/xlink',
242 MANIFEST_NAMESPACE_ATTRIB = {
243 'xmlns:manifest': 'urn:oasis:names:tc:opendocument:xmlns:manifest:1.0',
246 META_NAMESPACE_ATTRIB = {
247 'office:version': '1.0',
248 'xmlns:dc': 'http://purl.org/dc/elements/1.1/',
249 'xmlns:meta': 'urn:oasis:names:tc:opendocument:xmlns:meta:1.0',
250 'xmlns:office': NAME_SPACE_1,
251 'xmlns:ooo': 'http://openoffice.org/2004/office',
252 'xmlns:xlink': 'http://www.w3.org/1999/xlink',
257 # Functions
261 # ElementTree support functions.
262 # In order to be able to get the parent of elements, must use these
263 # instead of the functions with same name provided by ElementTree.
265 def Element(tag, attrib=None, nsmap=None, nsdict=CNSD):
266 if attrib is None:
267 attrib = {}
268 tag, attrib = fix_ns(tag, attrib, nsdict)
269 if WhichElementTree == 'lxml':
270 el = etree.Element(tag, attrib, nsmap=nsmap)
271 else:
272 el = _ElementInterfaceWrapper(tag, attrib)
273 return el
275 def SubElement(parent, tag, attrib=None, nsmap=None, nsdict=CNSD):
276 if attrib is None:
277 attrib = {}
278 tag, attrib = fix_ns(tag, attrib, nsdict)
279 if WhichElementTree == 'lxml':
280 el = etree.SubElement(parent, tag, attrib, nsmap=nsmap)
281 else:
282 el = _ElementInterfaceWrapper(tag, attrib)
283 parent.append(el)
284 el.setparent(parent)
285 return el
287 def fix_ns(tag, attrib, nsdict):
288 nstag = add_ns(tag, nsdict)
289 nsattrib = {}
290 for key, val in attrib.iteritems():
291 nskey = add_ns(key, nsdict)
292 nsattrib[nskey] = val
293 return nstag, nsattrib
295 def add_ns(tag, nsdict=CNSD):
296 if WhichElementTree == 'lxml':
297 nstag, name = tag.split(':')
298 ns = nsdict.get(nstag)
299 if ns is None:
300 raise RuntimeError, 'Invalid namespace prefix: %s' % nstag
301 tag = '{%s}%s' % (ns, name,)
302 return tag
304 def ToString(et):
305 outstream = StringIO.StringIO()
306 et.write(outstream)
307 s1 = outstream.getvalue()
308 outstream.close()
309 return s1
312 def escape_cdata(text):
313 text = text.replace("&", "&amp;")
314 text = text.replace("<", "&lt;")
315 text = text.replace(">", "&gt;")
316 ascii = ''
317 for char in text:
318 if ord(char) >= ord("\x7f"):
319 ascii += "&#x%X;" % ( ord(char), )
320 else:
321 ascii += char
322 return ascii
326 WORD_SPLIT_PAT1 = re.compile(r'\b(\w*)\b\W*')
328 def split_words(line):
329 # We need whitespace at the end of the string for our regexpr.
330 line += ' '
331 words = []
332 pos1 = 0
333 mo = WORD_SPLIT_PAT1.search(line, pos1)
334 while mo is not None:
335 word = mo.groups()[0]
336 words.append(word)
337 pos1 = mo.end()
338 mo = WORD_SPLIT_PAT1.search(line, pos1)
339 return words
343 # Classes
347 class TableStyle(object):
348 def __init__(self, border=None, backgroundcolor=None):
349 self.border = border
350 self.backgroundcolor = backgroundcolor
351 def get_border_(self):
352 return self.border_
353 def set_border_(self, border):
354 self.border_ = border
355 border = property(get_border_, set_border_)
356 def get_backgroundcolor_(self):
357 return self.backgroundcolor_
358 def set_backgroundcolor_(self, backgroundcolor):
359 self.backgroundcolor_ = backgroundcolor
360 backgroundcolor = property(get_backgroundcolor_, set_backgroundcolor_)
362 BUILTIN_DEFAULT_TABLE_STYLE = TableStyle(
363 border = '0.0007in solid #000000')
366 # Information about the indentation level for lists nested inside
367 # other contexts, e.g. dictionary lists.
368 class ListLevel(object):
369 def __init__(self, level, sibling_level=True, nested_level=True):
370 self.level = level
371 self.sibling_level = sibling_level
372 self.nested_level = nested_level
373 def set_sibling(self, sibling_level): self.sibling_level = sibling_level
374 def get_sibling(self): return self.sibling_level
375 def set_nested(self, nested_level): self.nested_level = nested_level
376 def get_nested(self): return self.nested_level
377 def set_level(self, level): self.level = level
378 def get_level(self): return self.level
381 class Writer(writers.Writer):
383 MIME_TYPE = 'application/vnd.oasis.opendocument.text'
384 EXTENSION = '.odt'
386 supported = ('odt', )
387 """Formats this writer supports."""
389 default_stylesheet = 'styles' + EXTENSION
391 default_stylesheet_path = utils.relative_path(
392 os.path.join(os.getcwd(), 'dummy'),
393 os.path.join(os.path.dirname(__file__), default_stylesheet))
395 default_template = 'template.txt'
397 default_template_path = utils.relative_path(
398 os.path.join(os.getcwd(), 'dummy'),
399 os.path.join(os.path.dirname(__file__), default_template))
401 settings_spec = (
402 'ODF-Specific Options',
403 None,
405 ('Specify a stylesheet. '
406 'Default: "%s"' % default_stylesheet_path,
407 ['--stylesheet'],
409 'default': default_stylesheet_path,
410 'dest': 'stylesheet'
412 ('Specify a configuration/mapping file relative to the '
413 'current working '
414 'directory for additional ODF options. '
415 'In particular, this file may contain a section named '
416 '"Formats" that maps default style names to '
417 'names to be used in the resulting output file allowing for '
418 'adhering to external standards. '
419 'For more info and the format of the configuration/mapping file, '
420 'see the odtwriter doc.',
421 ['--odf-config-file'],
422 {'metavar': '<file>'}),
423 ('Obfuscate email addresses to confuse harvesters while still '
424 'keeping email links usable with standards-compliant browsers.',
425 ['--cloak-email-addresses'],
426 {'default': False,
427 'action': 'store_true',
428 'dest': 'cloak_email_addresses',
429 'validator': frontend.validate_boolean}),
430 ('Do not obfuscate email addresses.',
431 ['--no-cloak-email-addresses'],
432 {'default': False,
433 'action': 'store_false',
434 'dest': 'cloak_email_addresses',
435 'validator': frontend.validate_boolean}),
436 ('Specify the thickness of table borders in thousands of a cm. '
437 'Default is 35.',
438 ['--table-border-thickness'],
439 {'default': None,
440 'validator': frontend.validate_nonnegative_int}),
441 ('Add syntax highlighting in literal code blocks.',
442 ['--add-syntax-highlighting'],
443 {'default': False,
444 'action': 'store_true',
445 'dest': 'add_syntax_highlighting',
446 'validator': frontend.validate_boolean}),
447 ('Do not add syntax highlighting in literal code blocks. (default)',
448 ['--no-syntax-highlighting'],
449 {'default': False,
450 'action': 'store_false',
451 'dest': 'add_syntax_highlighting',
452 'validator': frontend.validate_boolean}),
453 ('Create sections for headers. (default)',
454 ['--create-sections'],
455 {'default': True,
456 'action': 'store_true',
457 'dest': 'create_sections',
458 'validator': frontend.validate_boolean}),
459 ('Do not create sections for headers.',
460 ['--no-sections'],
461 {'default': True,
462 'action': 'store_false',
463 'dest': 'create_sections',
464 'validator': frontend.validate_boolean}),
465 ('Create links.',
466 ['--create-links'],
467 {'default': False,
468 'action': 'store_true',
469 'dest': 'create_links',
470 'validator': frontend.validate_boolean}),
471 ('Do not create links. (default)',
472 ['--no-links'],
473 {'default': False,
474 'action': 'store_false',
475 'dest': 'create_links',
476 'validator': frontend.validate_boolean}),
477 ('Generate endnotes at end of document, not footnotes '
478 'at bottom of page.',
479 ['--endnotes-end-doc'],
480 {'default': False,
481 'action': 'store_true',
482 'dest': 'endnotes_end_doc',
483 'validator': frontend.validate_boolean}),
484 ('Generate footnotes at bottom of page, not endnotes '
485 'at end of document. (default)',
486 ['--no-endnotes-end-doc'],
487 {'default': False,
488 'action': 'store_false',
489 'dest': 'endnotes_end_doc',
490 'validator': frontend.validate_boolean}),
491 ('Generate a bullet list table of contents, not '
492 'an ODF/oowriter table of contents.',
493 ['--generate-list-toc'],
494 {'default': True,
495 'action': 'store_false',
496 'dest': 'generate_oowriter_toc',
497 'validator': frontend.validate_boolean}),
498 ('Generate an ODF/oowriter table of contents, not '
499 'a bullet list. (default)',
500 ['--generate-oowriter-toc'],
501 {'default': True,
502 'action': 'store_true',
503 'dest': 'generate_oowriter_toc',
504 'validator': frontend.validate_boolean}),
505 ('Specify the contents of an custom header line. '
506 'See odf_odt writer documentation for details '
507 'about special field character sequences.',
508 ['--custom-odt-header'],
509 { 'default': '',
510 'dest': 'custom_header',
512 ('Specify the contents of an custom footer line. '
513 'See odf_odt writer documentation for details '
514 'about special field character sequences.',
515 ['--custom-odt-footer'],
516 { 'default': '',
517 'dest': 'custom_footer',
522 settings_defaults = {
523 'output_encoding_error_handler': 'xmlcharrefreplace',
526 relative_path_settings = (
527 'stylesheet_path',
530 config_section = 'opendocument odf writer'
531 config_section_dependencies = (
532 'writers',
535 def __init__(self):
536 writers.Writer.__init__(self)
537 self.translator_class = ODFTranslator
539 def translate(self):
540 self.settings = self.document.settings
541 self.visitor = self.translator_class(self.document)
542 self.visitor.retrieve_styles(self.EXTENSION)
543 self.document.walkabout(self.visitor)
544 self.visitor.add_doc_title()
545 self.assemble_my_parts()
546 self.output = self.parts['whole']
548 def assemble_my_parts(self):
549 """Assemble the `self.parts` dictionary. Extend in subclasses.
551 writers.Writer.assemble_parts(self)
552 f = tempfile.NamedTemporaryFile()
553 zfile = zipfile.ZipFile(f, 'w', zipfile.ZIP_DEFLATED)
554 content = self.visitor.content_astext()
555 self.write_zip_str(zfile, 'content.xml', content)
556 self.write_zip_str(zfile, 'mimetype', self.MIME_TYPE)
557 s1 = self.create_manifest()
558 self.write_zip_str(zfile, 'META-INF/manifest.xml', s1)
559 s1 = self.create_meta()
560 self.write_zip_str(zfile, 'meta.xml', s1)
561 s1 = self.get_stylesheet()
562 self.write_zip_str(zfile, 'styles.xml', s1)
563 s1 = self.get_settings()
564 self.write_zip_str(zfile, 'settings.xml', s1)
565 self.store_embedded_files(zfile)
566 zfile.close()
567 f.seek(0)
568 whole = f.read()
569 f.close()
570 self.parts['whole'] = whole
571 self.parts['encoding'] = self.document.settings.output_encoding
572 self.parts['version'] = docutils.__version__
574 def write_zip_str(self, zfile, name, bytes):
575 localtime = time.localtime(time.time())
576 zinfo = zipfile.ZipInfo(name, localtime)
577 # Add some standard UNIX file access permissions (-rw-r--r--).
578 zinfo.external_attr = (0x81a4 & 0xFFFF) << 16L
579 zinfo.compress_type = zipfile.ZIP_DEFLATED
580 zfile.writestr(zinfo, bytes)
582 def store_embedded_files(self, zfile):
583 embedded_files = self.visitor.get_embedded_file_list()
584 for source, destination in embedded_files:
585 if source is None:
586 continue
587 try:
588 # encode/decode
589 destination1 = destination.decode('latin-1').encode('utf-8')
590 zfile.write(source, destination1, zipfile.ZIP_STORED)
591 except OSError, e:
592 self.document.reporter.warning(
593 "Can't open file %s." % (source, ))
595 def get_settings(self):
597 modeled after get_stylesheet
599 stylespath = self.settings.stylesheet
600 zfile = zipfile.ZipFile(stylespath, 'r')
601 s1 = zfile.read('settings.xml')
602 zfile.close()
603 return s1
605 def get_stylesheet(self):
606 """Get the stylesheet from the visitor.
607 Ask the visitor to setup the page.
609 s1 = self.visitor.setup_page()
610 return s1
612 def assemble_parts(self):
613 pass
615 def create_manifest(self):
616 if WhichElementTree == 'lxml':
617 root = Element('manifest:manifest',
618 nsmap=MANIFEST_NAMESPACE_DICT,
619 nsdict=MANIFEST_NAMESPACE_DICT,
621 else:
622 root = Element('manifest:manifest',
623 attrib=MANIFEST_NAMESPACE_ATTRIB,
624 nsdict=MANIFEST_NAMESPACE_DICT,
626 doc = etree.ElementTree(root)
627 SubElement(root, 'manifest:file-entry', attrib={
628 'manifest:media-type': self.MIME_TYPE,
629 'manifest:full-path': '/',
630 }, nsdict=MANNSD)
631 SubElement(root, 'manifest:file-entry', attrib={
632 'manifest:media-type': 'text/xml',
633 'manifest:full-path': 'content.xml',
634 }, nsdict=MANNSD)
635 SubElement(root, 'manifest:file-entry', attrib={
636 'manifest:media-type': 'text/xml',
637 'manifest:full-path': 'styles.xml',
638 }, nsdict=MANNSD)
639 SubElement(root, 'manifest:file-entry', attrib={
640 'manifest:media-type': 'text/xml',
641 'manifest:full-path': 'meta.xml',
642 }, nsdict=MANNSD)
643 s1 = ToString(doc)
644 doc = minidom.parseString(s1)
645 s1 = doc.toprettyxml(' ')
646 return s1
648 def create_meta(self):
649 if WhichElementTree == 'lxml':
650 root = Element('office:document-meta',
651 nsmap=META_NAMESPACE_DICT,
652 nsdict=META_NAMESPACE_DICT,
654 else:
655 root = Element('office:document-meta',
656 attrib=META_NAMESPACE_ATTRIB,
657 nsdict=META_NAMESPACE_DICT,
659 doc = etree.ElementTree(root)
660 root = SubElement(root, 'office:meta', nsdict=METNSD)
661 el1 = SubElement(root, 'meta:generator', nsdict=METNSD)
662 el1.text = 'Docutils/rst2odf.py/%s' % (VERSION, )
663 s1 = os.environ.get('USER', '')
664 el1 = SubElement(root, 'meta:initial-creator', nsdict=METNSD)
665 el1.text = s1
666 s2 = time.strftime('%Y-%m-%dT%H:%M:%S', time.localtime())
667 el1 = SubElement(root, 'meta:creation-date', nsdict=METNSD)
668 el1.text = s2
669 el1 = SubElement(root, 'dc:creator', nsdict=METNSD)
670 el1.text = s1
671 el1 = SubElement(root, 'dc:date', nsdict=METNSD)
672 el1.text = s2
673 el1 = SubElement(root, 'dc:language', nsdict=METNSD)
674 el1.text = 'en-US'
675 el1 = SubElement(root, 'meta:editing-cycles', nsdict=METNSD)
676 el1.text = '1'
677 el1 = SubElement(root, 'meta:editing-duration', nsdict=METNSD)
678 el1.text = 'PT00M01S'
679 title = self.visitor.get_title()
680 el1 = SubElement(root, 'dc:title', nsdict=METNSD)
681 if title:
682 el1.text = title
683 else:
684 el1.text = '[no title]'
685 meta_dict = self.visitor.get_meta_dict()
686 keywordstr = meta_dict.get('keywords')
687 if keywordstr is not None:
688 keywords = split_words(keywordstr)
689 for keyword in keywords:
690 el1 = SubElement(root, 'meta:keyword', nsdict=METNSD)
691 el1.text = keyword
692 description = meta_dict.get('description')
693 if description is not None:
694 el1 = SubElement(root, 'dc:description', nsdict=METNSD)
695 el1.text = description
696 s1 = ToString(doc)
697 #doc = minidom.parseString(s1)
698 #s1 = doc.toprettyxml(' ')
699 return s1
701 # class ODFTranslator(nodes.SparseNodeVisitor):
703 class ODFTranslator(nodes.GenericNodeVisitor):
705 used_styles = (
706 'attribution', 'blockindent', 'blockquote', 'blockquote-bulletitem',
707 'blockquote-bulletlist', 'blockquote-enumitem', 'blockquote-enumlist',
708 'bulletitem', 'bulletlist',
709 'caption', 'legend',
710 'centeredtextbody', 'codeblock',
711 'codeblock-classname', 'codeblock-comment', 'codeblock-functionname',
712 'codeblock-keyword', 'codeblock-name', 'codeblock-number',
713 'codeblock-operator', 'codeblock-string', 'emphasis', 'enumitem',
714 'enumlist', 'epigraph', 'epigraph-bulletitem', 'epigraph-bulletlist',
715 'epigraph-enumitem', 'epigraph-enumlist', 'footer',
716 'footnote', 'citation',
717 'header', 'highlights', 'highlights-bulletitem',
718 'highlights-bulletlist', 'highlights-enumitem', 'highlights-enumlist',
719 'horizontalline', 'inlineliteral', 'quotation', 'rubric',
720 'strong', 'table-title', 'textbody', 'tocbulletlist', 'tocenumlist',
721 'title',
722 'subtitle',
723 'heading1',
724 'heading2',
725 'heading3',
726 'heading4',
727 'heading5',
728 'heading6',
729 'heading7',
730 'admon-attention-hdr',
731 'admon-attention-body',
732 'admon-caution-hdr',
733 'admon-caution-body',
734 'admon-danger-hdr',
735 'admon-danger-body',
736 'admon-error-hdr',
737 'admon-error-body',
738 'admon-generic-hdr',
739 'admon-generic-body',
740 'admon-hint-hdr',
741 'admon-hint-body',
742 'admon-important-hdr',
743 'admon-important-body',
744 'admon-note-hdr',
745 'admon-note-body',
746 'admon-tip-hdr',
747 'admon-tip-body',
748 'admon-warning-hdr',
749 'admon-warning-body',
750 'tableoption',
751 'tableoption.%c', 'tableoption.%c%d', 'Table%d', 'Table%d.%c',
752 'Table%d.%c%d',
753 'lineblock1',
754 'lineblock2',
755 'lineblock3',
756 'lineblock4',
757 'lineblock5',
758 'lineblock6',
759 'image', 'figureframe',
762 def __init__(self, document):
763 #nodes.SparseNodeVisitor.__init__(self, document)
764 nodes.GenericNodeVisitor.__init__(self, document)
765 self.settings = document.settings
766 self.format_map = { }
767 if self.settings.odf_config_file:
768 from ConfigParser import ConfigParser
770 parser = ConfigParser()
771 parser.read(self.settings.odf_config_file)
772 for rststyle, format in parser.items("Formats"):
773 if rststyle not in self.used_styles:
774 self.document.reporter.warning(
775 'Style "%s" is not a style used by odtwriter.' % (
776 rststyle, ))
777 self.format_map[rststyle] = format
778 self.section_level = 0
779 self.section_count = 0
780 # Create ElementTree content and styles documents.
781 if WhichElementTree == 'lxml':
782 root = Element(
783 'office:document-content',
784 nsmap=CONTENT_NAMESPACE_DICT,
786 else:
787 root = Element(
788 'office:document-content',
789 attrib=CONTENT_NAMESPACE_ATTRIB,
791 self.content_tree = etree.ElementTree(element=root)
792 self.current_element = root
793 SubElement(root, 'office:scripts')
794 SubElement(root, 'office:font-face-decls')
795 el = SubElement(root, 'office:automatic-styles')
796 self.automatic_styles = el
797 el = SubElement(root, 'office:body')
798 el = self.generate_content_element(el)
799 self.current_element = el
800 self.body_text_element = el
801 self.paragraph_style_stack = [self.rststyle('textbody'), ]
802 self.list_style_stack = []
803 self.table_count = 0
804 self.column_count = ord('A') - 1
805 self.trace_level = -1
806 self.optiontablestyles_generated = False
807 self.field_name = None
808 self.field_element = None
809 self.title = None
810 self.image_count = 0
811 self.image_style_count = 0
812 self.image_dict = {}
813 self.embedded_file_list = []
814 self.syntaxhighlighting = 1
815 self.syntaxhighlight_lexer = 'python'
816 self.header_content = []
817 self.footer_content = []
818 self.in_header = False
819 self.in_footer = False
820 self.blockstyle = ''
821 self.in_table_of_contents = False
822 self.table_of_content_index_body = None
823 self.list_level = 0
824 self.footnote_ref_dict = {}
825 self.footnote_list = []
826 self.footnote_chars_idx = 0
827 self.footnote_level = 0
828 self.pending_ids = [ ]
829 self.in_paragraph = False
830 self.found_doc_title = False
831 self.bumped_list_level_stack = []
832 self.meta_dict = {}
833 self.line_block_level = 0
834 self.line_indent_level = 0
835 self.citation_id = None
836 self.style_index = 0 # use to form unique style names
837 self.str_stylesheet = ''
838 self.str_stylesheetcontent = ''
839 self.dom_stylesheet = None
840 self.table_styles = None
842 def get_str_stylesheet(self):
843 return self.str_stylesheet
845 def retrieve_styles(self, extension):
846 """Retrieve the stylesheet from either a .xml file or from
847 a .odt (zip) file. Return the content as a string.
849 s2 = None
850 stylespath = self.settings.stylesheet
851 ext = os.path.splitext(stylespath)[1]
852 if ext == '.xml':
853 stylesfile = open(stylespath, 'r')
854 s1 = stylesfile.read()
855 stylesfile.close()
856 elif ext == extension:
857 zfile = zipfile.ZipFile(stylespath, 'r')
858 s1 = zfile.read('styles.xml')
859 s2 = zfile.read('content.xml')
860 zfile.close()
861 else:
862 raise RuntimeError, 'stylesheet path (%s) must be %s or .xml file' %(stylespath, extension)
863 self.str_stylesheet = s1
864 self.str_stylesheetcontent = s2
865 self.dom_stylesheet = etree.fromstring(self.str_stylesheet)
866 self.dom_stylesheetcontent = etree.fromstring(self.str_stylesheetcontent)
867 self.table_styles = self.extract_table_styles(s2)
869 def extract_table_styles(self, styles_str):
870 root = etree.fromstring(styles_str)
871 table_styles = {}
872 auto_styles = root.find(
873 '{%s}automatic-styles' % (CNSD['office'], ))
874 for stylenode in auto_styles:
875 name = stylenode.get('{%s}name' % (CNSD['style'], ))
876 tablename = name.split('.')[0]
877 family = stylenode.get('{%s}family' % (CNSD['style'], ))
878 if name.startswith(TABLESTYLEPREFIX):
879 tablestyle = table_styles.get(tablename)
880 if tablestyle is None:
881 tablestyle = TableStyle()
882 table_styles[tablename] = tablestyle
883 if family == 'table':
884 properties = stylenode.find(
885 '{%s}table-properties' % (CNSD['style'], ))
886 property = properties.get('{%s}%s' % (CNSD['fo'],
887 'background-color', ))
888 if property is not None and property != 'none':
889 tablestyle.backgroundcolor = property
890 elif family == 'table-cell':
891 properties = stylenode.find(
892 '{%s}table-cell-properties' % (CNSD['style'], ))
893 if properties is not None:
894 border = self.get_property(properties)
895 if border is not None:
896 tablestyle.border = border
897 return table_styles
899 def get_property(self, stylenode):
900 border = None
901 for propertyname in TABLEPROPERTYNAMES:
902 border = stylenode.get('{%s}%s' % (CNSD['fo'], propertyname, ))
903 if border is not None and border != 'none':
904 return border
905 return border
907 def add_doc_title(self):
908 text = self.settings.title
909 if text:
910 self.title = text
911 if not self.found_doc_title:
912 el = Element('text:p', attrib = {
913 'text:style-name': self.rststyle('title'),
915 el.text = text
916 self.body_text_element.insert(0, el)
918 def rststyle(self, name, parameters=( )):
920 Returns the style name to use for the given style.
922 If `parameters` is given `name` must contain a matching number of ``%`` and
923 is used as a format expression with `parameters` as the value.
925 name1 = name % parameters
926 stylename = self.format_map.get(name1, 'rststyle-%s' % name1)
927 return stylename
929 def generate_content_element(self, root):
930 return SubElement(root, 'office:text')
932 def setup_page(self):
933 self.setup_paper(self.dom_stylesheet)
934 if (len(self.header_content) > 0 or len(self.footer_content) > 0 or
935 self.settings.custom_header or self.settings.custom_footer):
936 self.add_header_footer(self.dom_stylesheet)
937 new_content = etree.tostring(self.dom_stylesheet)
938 return new_content
940 def setup_paper(self, root_el):
941 try:
942 fin = os.popen("paperconf -s 2> /dev/null")
943 w, h = map(float, fin.read().split())
944 fin.close()
945 except:
946 w, h = 612, 792 # default to Letter
947 def walk(el):
948 if el.tag == "{%s}page-layout-properties" % SNSD["style"] and \
949 not el.attrib.has_key("{%s}page-width" % SNSD["fo"]):
950 el.attrib["{%s}page-width" % SNSD["fo"]] = "%.3fpt" % w
951 el.attrib["{%s}page-height" % SNSD["fo"]] = "%.3fpt" % h
952 el.attrib["{%s}margin-left" % SNSD["fo"]] = \
953 el.attrib["{%s}margin-right" % SNSD["fo"]] = \
954 "%.3fpt" % (.1 * w)
955 el.attrib["{%s}margin-top" % SNSD["fo"]] = \
956 el.attrib["{%s}margin-bottom" % SNSD["fo"]] = \
957 "%.3fpt" % (.1 * h)
958 else:
959 for subel in el.getchildren(): walk(subel)
960 walk(root_el)
962 def add_header_footer(self, root_el):
963 automatic_styles = root_el.find(
964 '{%s}automatic-styles' % SNSD['office'])
965 path = '{%s}master-styles' % (NAME_SPACE_1, )
966 master_el = root_el.find(path)
967 if master_el is None:
968 return
969 path = '{%s}master-page' % (SNSD['style'], )
970 master_el = master_el.find(path)
971 if master_el is None:
972 return
973 el1 = master_el
974 if self.header_content or self.settings.custom_header:
975 if WhichElementTree == 'lxml':
976 el2 = SubElement(el1, 'style:header', nsdict=SNSD)
977 else:
978 el2 = SubElement(el1, 'style:header',
979 attrib=STYLES_NAMESPACE_ATTRIB,
980 nsdict=STYLES_NAMESPACE_DICT,
982 for el in self.header_content:
983 attrkey = add_ns('text:style-name', nsdict=SNSD)
984 el.attrib[attrkey] = self.rststyle('header')
985 el2.append(el)
986 if self.settings.custom_header:
987 elcustom = self.create_custom_headfoot(el2,
988 self.settings.custom_header, 'header', automatic_styles)
989 if self.footer_content or self.settings.custom_footer:
990 if WhichElementTree == 'lxml':
991 el2 = SubElement(el1, 'style:footer', nsdict=SNSD)
992 else:
993 el2 = SubElement(el1, 'style:footer',
994 attrib=STYLES_NAMESPACE_ATTRIB,
995 nsdict=STYLES_NAMESPACE_DICT,
997 for el in self.footer_content:
998 attrkey = add_ns('text:style-name', nsdict=SNSD)
999 el.attrib[attrkey] = self.rststyle('footer')
1000 el2.append(el)
1001 if self.settings.custom_footer:
1002 elcustom = self.create_custom_headfoot(el2,
1003 self.settings.custom_footer, 'footer', automatic_styles)
1005 code_none, code_field, code_text = range(3)
1006 field_pat = re.compile(r'%(..?)%')
1008 def create_custom_headfoot(self, parent, text, style_name, automatic_styles):
1009 parent = SubElement(parent, 'text:p', attrib={
1010 'text:style-name': self.rststyle(style_name),
1012 current_element = None
1013 field_iter = self.split_field_specifiers_iter(text)
1014 for item in field_iter:
1015 if item[0] == ODFTranslator.code_field:
1016 if item[1] not in ('p', 'P',
1017 't1', 't2', 't3', 't4',
1018 'd1', 'd2', 'd3', 'd4', 'd5',
1019 's', 't', 'a'):
1020 msg = 'bad field spec: %%%s%%' % (item[1], )
1021 raise RuntimeError, msg
1022 el1 = self.make_field_element(parent,
1023 item[1], style_name, automatic_styles)
1024 if el1 is None:
1025 msg = 'bad field spec: %%%s%%' % (item[1], )
1026 raise RuntimeError, msg
1027 else:
1028 current_element = el1
1029 else:
1030 if current_element is None:
1031 parent.text = item[1]
1032 else:
1033 current_element.tail = item[1]
1035 def make_field_element(self, parent, text, style_name, automatic_styles):
1036 if text == 'p':
1037 el1 = SubElement(parent, 'text:page-number', attrib={
1038 #'text:style-name': self.rststyle(style_name),
1039 'text:select-page': 'current',
1041 elif text == 'P':
1042 el1 = SubElement(parent, 'text:page-count', attrib={
1043 #'text:style-name': self.rststyle(style_name),
1045 elif text == 't1':
1046 self.style_index += 1
1047 el1 = SubElement(parent, 'text:time', attrib={
1048 'text:style-name': self.rststyle(style_name),
1049 'text:fixed': 'true',
1050 'style:data-style-name': 'rst-time-style-%d' % self.style_index,
1052 el2 = SubElement(automatic_styles, 'number:time-style', attrib={
1053 'style:name': 'rst-time-style-%d' % self.style_index,
1054 'xmlns:number': SNSD['number'],
1055 'xmlns:style': SNSD['style'],
1057 el3 = SubElement(el2, 'number:hours', attrib={
1058 'number:style': 'long',
1060 el3 = SubElement(el2, 'number:text')
1061 el3.text = ':'
1062 el3 = SubElement(el2, 'number:minutes', attrib={
1063 'number:style': 'long',
1065 elif text == 't2':
1066 self.style_index += 1
1067 el1 = SubElement(parent, 'text:time', attrib={
1068 'text:style-name': self.rststyle(style_name),
1069 'text:fixed': 'true',
1070 'style:data-style-name': 'rst-time-style-%d' % self.style_index,
1072 el2 = SubElement(automatic_styles, 'number:time-style', attrib={
1073 'style:name': 'rst-time-style-%d' % self.style_index,
1074 'xmlns:number': SNSD['number'],
1075 'xmlns:style': SNSD['style'],
1077 el3 = SubElement(el2, 'number:hours', attrib={
1078 'number:style': 'long',
1080 el3 = SubElement(el2, 'number:text')
1081 el3.text = ':'
1082 el3 = SubElement(el2, 'number:minutes', attrib={
1083 'number:style': 'long',
1085 el3 = SubElement(el2, 'number:text')
1086 el3.text = ':'
1087 el3 = SubElement(el2, 'number:seconds', attrib={
1088 'number:style': 'long',
1090 elif text == 't3':
1091 self.style_index += 1
1092 el1 = SubElement(parent, 'text:time', attrib={
1093 'text:style-name': self.rststyle(style_name),
1094 'text:fixed': 'true',
1095 'style:data-style-name': 'rst-time-style-%d' % self.style_index,
1097 el2 = SubElement(automatic_styles, 'number:time-style', attrib={
1098 'style:name': 'rst-time-style-%d' % self.style_index,
1099 'xmlns:number': SNSD['number'],
1100 'xmlns:style': SNSD['style'],
1102 el3 = SubElement(el2, 'number:hours', attrib={
1103 'number:style': 'long',
1105 el3 = SubElement(el2, 'number:text')
1106 el3.text = ':'
1107 el3 = SubElement(el2, 'number:minutes', attrib={
1108 'number:style': 'long',
1110 el3 = SubElement(el2, 'number:text')
1111 el3.text = ' '
1112 el3 = SubElement(el2, 'number:am-pm')
1113 elif text == 't4':
1114 self.style_index += 1
1115 el1 = SubElement(parent, 'text:time', attrib={
1116 'text:style-name': self.rststyle(style_name),
1117 'text:fixed': 'true',
1118 'style:data-style-name': 'rst-time-style-%d' % self.style_index,
1120 el2 = SubElement(automatic_styles, 'number:time-style', attrib={
1121 'style:name': 'rst-time-style-%d' % self.style_index,
1122 'xmlns:number': SNSD['number'],
1123 'xmlns:style': SNSD['style'],
1125 el3 = SubElement(el2, 'number:hours', attrib={
1126 'number:style': 'long',
1128 el3 = SubElement(el2, 'number:text')
1129 el3.text = ':'
1130 el3 = SubElement(el2, 'number:minutes', attrib={
1131 'number:style': 'long',
1133 el3 = SubElement(el2, 'number:text')
1134 el3.text = ':'
1135 el3 = SubElement(el2, 'number:seconds', attrib={
1136 'number:style': 'long',
1138 el3 = SubElement(el2, 'number:text')
1139 el3.text = ' '
1140 el3 = SubElement(el2, 'number:am-pm')
1141 elif text == 'd1':
1142 self.style_index += 1
1143 el1 = SubElement(parent, 'text:date', attrib={
1144 'text:style-name': self.rststyle(style_name),
1145 'style:data-style-name': 'rst-date-style-%d' % self.style_index,
1147 el2 = SubElement(automatic_styles, 'number:date-style', attrib={
1148 'style:name': 'rst-date-style-%d' % self.style_index,
1149 'number:automatic-order': 'true',
1150 'xmlns:number': SNSD['number'],
1151 'xmlns:style': SNSD['style'],
1153 el3 = SubElement(el2, 'number:month', attrib={
1154 'number:style': 'long',
1156 el3 = SubElement(el2, 'number:text')
1157 el3.text = '/'
1158 el3 = SubElement(el2, 'number:day', attrib={
1159 'number:style': 'long',
1161 el3 = SubElement(el2, 'number:text')
1162 el3.text = '/'
1163 el3 = SubElement(el2, 'number:year')
1164 elif text == 'd2':
1165 self.style_index += 1
1166 el1 = SubElement(parent, 'text:date', attrib={
1167 'text:style-name': self.rststyle(style_name),
1168 'style:data-style-name': 'rst-date-style-%d' % self.style_index,
1170 el2 = SubElement(automatic_styles, 'number:date-style', attrib={
1171 'style:name': 'rst-date-style-%d' % self.style_index,
1172 'number:automatic-order': 'true',
1173 'xmlns:number': SNSD['number'],
1174 'xmlns:style': SNSD['style'],
1176 el3 = SubElement(el2, 'number:month', attrib={
1177 'number:style': 'long',
1179 el3 = SubElement(el2, 'number:text')
1180 el3.text = '/'
1181 el3 = SubElement(el2, 'number:day', attrib={
1182 'number:style': 'long',
1184 el3 = SubElement(el2, 'number:text')
1185 el3.text = '/'
1186 el3 = SubElement(el2, 'number:year', attrib={
1187 'number:style': 'long',
1189 elif text == 'd3':
1190 self.style_index += 1
1191 el1 = SubElement(parent, 'text:date', attrib={
1192 'text:style-name': self.rststyle(style_name),
1193 'style:data-style-name': 'rst-date-style-%d' % self.style_index,
1195 el2 = SubElement(automatic_styles, 'number:date-style', attrib={
1196 'style:name': 'rst-date-style-%d' % self.style_index,
1197 'number:automatic-order': 'true',
1198 'xmlns:number': SNSD['number'],
1199 'xmlns:style': SNSD['style'],
1201 el3 = SubElement(el2, 'number:month', attrib={
1202 'number:textual': 'true',
1204 el3 = SubElement(el2, 'number:text')
1205 el3.text = ' '
1206 el3 = SubElement(el2, 'number:day', attrib={
1208 el3 = SubElement(el2, 'number:text')
1209 el3.text = ', '
1210 el3 = SubElement(el2, 'number:year', attrib={
1211 'number:style': 'long',
1213 elif text == 'd4':
1214 self.style_index += 1
1215 el1 = SubElement(parent, 'text:date', attrib={
1216 'text:style-name': self.rststyle(style_name),
1217 'style:data-style-name': 'rst-date-style-%d' % self.style_index,
1219 el2 = SubElement(automatic_styles, 'number:date-style', attrib={
1220 'style:name': 'rst-date-style-%d' % self.style_index,
1221 'number:automatic-order': 'true',
1222 'xmlns:number': SNSD['number'],
1223 'xmlns:style': SNSD['style'],
1225 el3 = SubElement(el2, 'number:month', attrib={
1226 'number:textual': 'true',
1227 'number:style': 'long',
1229 el3 = SubElement(el2, 'number:text')
1230 el3.text = ' '
1231 el3 = SubElement(el2, 'number:day', attrib={
1233 el3 = SubElement(el2, 'number:text')
1234 el3.text = ', '
1235 el3 = SubElement(el2, 'number:year', attrib={
1236 'number:style': 'long',
1238 elif text == 'd5':
1239 self.style_index += 1
1240 el1 = SubElement(parent, 'text:date', attrib={
1241 'text:style-name': self.rststyle(style_name),
1242 'style:data-style-name': 'rst-date-style-%d' % self.style_index,
1244 el2 = SubElement(automatic_styles, 'number:date-style', attrib={
1245 'style:name': 'rst-date-style-%d' % self.style_index,
1246 'xmlns:number': SNSD['number'],
1247 'xmlns:style': SNSD['style'],
1249 el3 = SubElement(el2, 'number:year', attrib={
1250 'number:style': 'long',
1252 el3 = SubElement(el2, 'number:text')
1253 el3.text = '-'
1254 el3 = SubElement(el2, 'number:month', attrib={
1255 'number:style': 'long',
1257 el3 = SubElement(el2, 'number:text')
1258 el3.text = '-'
1259 el3 = SubElement(el2, 'number:day', attrib={
1260 'number:style': 'long',
1262 elif text == 's':
1263 el1 = SubElement(parent, 'text:subject', attrib={
1264 'text:style-name': self.rststyle(style_name),
1266 elif text == 't':
1267 el1 = SubElement(parent, 'text:title', attrib={
1268 'text:style-name': self.rststyle(style_name),
1270 elif text == 'a':
1271 el1 = SubElement(parent, 'text:author-name', attrib={
1272 'text:fixed': 'false',
1274 else:
1275 el1 = None
1276 return el1
1278 def split_field_specifiers_iter(self, text):
1279 pos1 = 0
1280 pos_end = len(text)
1281 while True:
1282 mo = ODFTranslator.field_pat.search(text, pos1)
1283 if mo:
1284 pos2 = mo.start()
1285 if pos2 > pos1:
1286 yield (ODFTranslator.code_text, text[pos1:pos2])
1287 yield (ODFTranslator.code_field, mo.group(1))
1288 pos1 = mo.end()
1289 else:
1290 break
1291 trailing = text[pos1:]
1292 if trailing:
1293 yield (ODFTranslator.code_text, trailing)
1296 def astext(self):
1297 root = self.content_tree.getroot()
1298 et = etree.ElementTree(root)
1299 s1 = ToString(et)
1300 return s1
1302 def content_astext(self):
1303 return self.astext()
1305 def set_title(self, title): self.title = title
1306 def get_title(self): return self.title
1307 def set_embedded_file_list(self, embedded_file_list):
1308 self.embedded_file_list = embedded_file_list
1309 def get_embedded_file_list(self): return self.embedded_file_list
1310 def get_meta_dict(self): return self.meta_dict
1312 def process_footnotes(self):
1313 for node, el1 in self.footnote_list:
1314 backrefs = node.attributes.get('backrefs', [])
1315 first = True
1316 for ref in backrefs:
1317 el2 = self.footnote_ref_dict.get(ref)
1318 if el2 is not None:
1319 if first:
1320 first = False
1321 el3 = copy.deepcopy(el1)
1322 el2.append(el3)
1323 else:
1324 children = el2.getchildren()
1325 if len(children) > 0: # and 'id' in el2.attrib:
1326 child = children[0]
1327 ref1 = child.text
1328 attribkey = add_ns('text:id', nsdict=SNSD)
1329 id1 = el2.get(attribkey, 'footnote-error')
1330 if id1 is None:
1331 id1 = ''
1332 tag = add_ns('text:note-ref', nsdict=SNSD)
1333 el2.tag = tag
1334 if self.settings.endnotes_end_doc:
1335 note_class = 'endnote'
1336 else:
1337 note_class = 'footnote'
1338 el2.attrib.clear()
1339 attribkey = add_ns('text:note-class', nsdict=SNSD)
1340 el2.attrib[attribkey] = note_class
1341 attribkey = add_ns('text:ref-name', nsdict=SNSD)
1342 el2.attrib[attribkey] = id1
1343 attribkey = add_ns('text:reference-format', nsdict=SNSD)
1344 el2.attrib[attribkey] = 'page'
1345 el2.text = ref1
1348 # Utility methods
1350 def append_child(self, tag, attrib=None, parent=None):
1351 if parent is None:
1352 parent = self.current_element
1353 if attrib is None:
1354 el = SubElement(parent, tag)
1355 else:
1356 el = SubElement(parent, tag, attrib)
1357 return el
1359 def append_p(self, style, text=None):
1360 result = self.append_child('text:p', attrib={
1361 'text:style-name': self.rststyle(style)})
1362 self.append_pending_ids(result)
1363 if text is not None:
1364 result.text = text
1365 return result
1367 def append_pending_ids(self, el):
1368 if self.settings.create_links:
1369 for id in self.pending_ids:
1370 SubElement(el, 'text:reference-mark', attrib={
1371 'text:name': id})
1372 self.pending_ids = [ ]
1374 def set_current_element(self, el):
1375 self.current_element = el
1377 def set_to_parent(self):
1378 self.current_element = self.current_element.getparent()
1380 def generate_labeled_block(self, node, label):
1381 el = self.append_p('textbody')
1382 el1 = SubElement(el, 'text:span',
1383 attrib={'text:style-name': self.rststyle('strong')})
1384 el1.text = label
1385 el = self.append_p('blockindent')
1386 return el
1388 def generate_labeled_line(self, node, label):
1389 el = self.append_p('textbody')
1390 el1 = SubElement(el, 'text:span',
1391 attrib={'text:style-name': self.rststyle('strong')})
1392 el1.text = label
1393 el1.tail = node.astext()
1394 return el
1396 def encode(self, text):
1397 text = text.replace(u'\u00a0', " ")
1398 return text
1401 # Visitor functions
1403 # In alphabetic order, more or less.
1404 # See docutils.docutils.nodes.node_class_names.
1407 def dispatch_visit(self, node):
1408 """Override to catch basic attributes which many nodes have."""
1409 self.handle_basic_atts(node)
1410 nodes.GenericNodeVisitor.dispatch_visit(self, node)
1412 def handle_basic_atts(self, node):
1413 if isinstance(node, nodes.Element) and node['ids']:
1414 self.pending_ids += node['ids']
1416 def default_visit(self, node):
1417 self.document.reporter.warning('missing visit_%s' % (node.tagname, ))
1419 def default_departure(self, node):
1420 self.document.reporter.warning('missing depart_%s' % (node.tagname, ))
1422 def visit_Text(self, node):
1423 # Skip nodes whose text has been processed in parent nodes.
1424 if isinstance(node.parent, docutils.nodes.literal_block):
1425 return
1426 text = node.astext()
1427 # Are we in mixed content? If so, add the text to the
1428 # etree tail of the previous sibling element.
1429 if len(self.current_element.getchildren()) > 0:
1430 if self.current_element.getchildren()[-1].tail:
1431 self.current_element.getchildren()[-1].tail += text
1432 else:
1433 self.current_element.getchildren()[-1].tail = text
1434 else:
1435 if self.current_element.text:
1436 self.current_element.text += text
1437 else:
1438 self.current_element.text = text
1440 def depart_Text(self, node):
1441 pass
1444 # Pre-defined fields
1447 def visit_address(self, node):
1448 el = self.generate_labeled_block(node, 'Address: ')
1449 self.set_current_element(el)
1451 def depart_address(self, node):
1452 self.set_to_parent()
1454 def visit_author(self, node):
1455 if isinstance(node.parent, nodes.authors):
1456 el = self.append_p('blockindent')
1457 else:
1458 el = self.generate_labeled_block(node, 'Author: ')
1459 self.set_current_element(el)
1461 def depart_author(self, node):
1462 self.set_to_parent()
1464 def visit_authors(self, node):
1465 label = 'Authors:'
1466 el = self.append_p('textbody')
1467 el1 = SubElement(el, 'text:span',
1468 attrib={'text:style-name': self.rststyle('strong')})
1469 el1.text = label
1471 def depart_authors(self, node):
1472 pass
1474 def visit_contact(self, node):
1475 el = self.generate_labeled_block(node, 'Contact: ')
1476 self.set_current_element(el)
1478 def depart_contact(self, node):
1479 self.set_to_parent()
1481 def visit_copyright(self, node):
1482 el = self.generate_labeled_block(node, 'Copyright: ')
1483 self.set_current_element(el)
1485 def depart_copyright(self, node):
1486 self.set_to_parent()
1488 def visit_date(self, node):
1489 self.generate_labeled_line(node, 'Date: ')
1491 def depart_date(self, node):
1492 pass
1494 def visit_organization(self, node):
1495 el = self.generate_labeled_block(node, 'Organization: ')
1496 self.set_current_element(el)
1498 def depart_organization(self, node):
1499 self.set_to_parent()
1501 def visit_status(self, node):
1502 el = self.generate_labeled_block(node, 'Status: ')
1503 self.set_current_element(el)
1505 def depart_status(self, node):
1506 self.set_to_parent()
1508 def visit_revision(self, node):
1509 self.generate_labeled_line(node, 'Revision: ')
1511 def depart_revision(self, node):
1512 pass
1514 def visit_version(self, node):
1515 el = self.generate_labeled_line(node, 'Version: ')
1516 #self.set_current_element(el)
1518 def depart_version(self, node):
1519 #self.set_to_parent()
1520 pass
1522 def visit_attribution(self, node):
1523 el = self.append_p('attribution', node.astext())
1525 def depart_attribution(self, node):
1526 pass
1528 def visit_block_quote(self, node):
1529 if 'epigraph' in node.attributes['classes']:
1530 self.paragraph_style_stack.append(self.rststyle('epigraph'))
1531 self.blockstyle = self.rststyle('epigraph')
1532 elif 'highlights' in node.attributes['classes']:
1533 self.paragraph_style_stack.append(self.rststyle('highlights'))
1534 self.blockstyle = self.rststyle('highlights')
1535 else:
1536 self.paragraph_style_stack.append(self.rststyle('blockquote'))
1537 self.blockstyle = self.rststyle('blockquote')
1538 self.line_indent_level += 1
1540 def depart_block_quote(self, node):
1541 self.paragraph_style_stack.pop()
1542 self.blockstyle = ''
1543 self.line_indent_level -= 1
1545 def visit_bullet_list(self, node):
1546 self.list_level +=1
1547 if self.in_table_of_contents:
1548 if self.settings.generate_oowriter_toc:
1549 pass
1550 else:
1551 if node.has_key('classes') and \
1552 'auto-toc' in node.attributes['classes']:
1553 el = SubElement(self.current_element, 'text:list', attrib={
1554 'text:style-name': self.rststyle('tocenumlist'),
1556 self.list_style_stack.append(self.rststyle('enumitem'))
1557 else:
1558 el = SubElement(self.current_element, 'text:list', attrib={
1559 'text:style-name': self.rststyle('tocbulletlist'),
1561 self.list_style_stack.append(self.rststyle('bulletitem'))
1562 self.set_current_element(el)
1563 else:
1564 if self.blockstyle == self.rststyle('blockquote'):
1565 el = SubElement(self.current_element, 'text:list', attrib={
1566 'text:style-name': self.rststyle('blockquote-bulletlist'),
1568 self.list_style_stack.append(
1569 self.rststyle('blockquote-bulletitem'))
1570 elif self.blockstyle == self.rststyle('highlights'):
1571 el = SubElement(self.current_element, 'text:list', attrib={
1572 'text:style-name': self.rststyle('highlights-bulletlist'),
1574 self.list_style_stack.append(
1575 self.rststyle('highlights-bulletitem'))
1576 elif self.blockstyle == self.rststyle('epigraph'):
1577 el = SubElement(self.current_element, 'text:list', attrib={
1578 'text:style-name': self.rststyle('epigraph-bulletlist'),
1580 self.list_style_stack.append(
1581 self.rststyle('epigraph-bulletitem'))
1582 else:
1583 el = SubElement(self.current_element, 'text:list', attrib={
1584 'text:style-name': self.rststyle('bulletlist'),
1586 self.list_style_stack.append(self.rststyle('bulletitem'))
1587 self.set_current_element(el)
1589 def depart_bullet_list(self, node):
1590 if self.in_table_of_contents:
1591 if self.settings.generate_oowriter_toc:
1592 pass
1593 else:
1594 self.set_to_parent()
1595 self.list_style_stack.pop()
1596 else:
1597 self.set_to_parent()
1598 self.list_style_stack.pop()
1599 self.list_level -=1
1601 def visit_caption(self, node):
1602 raise nodes.SkipChildren()
1603 pass
1605 def depart_caption(self, node):
1606 pass
1608 def visit_comment(self, node):
1609 el = self.append_p('textbody')
1610 el1 = SubElement(el, 'office:annotation', attrib={})
1611 el2 = SubElement(el1, 'text:p', attrib={})
1612 el2.text = node.astext()
1614 def depart_comment(self, node):
1615 pass
1617 def visit_compound(self, node):
1618 # The compound directive currently receives no special treatment.
1619 pass
1621 def depart_compound(self, node):
1622 pass
1624 def visit_container(self, node):
1625 styles = node.attributes.get('classes', ())
1626 if len(styles) > 0:
1627 self.paragraph_style_stack.append(self.rststyle(styles[0]))
1629 def depart_container(self, node):
1630 styles = node.attributes.get('classes', ())
1631 if len(styles) > 0:
1632 self.paragraph_style_stack.pop()
1634 def visit_decoration(self, node):
1635 pass
1637 def depart_decoration(self, node):
1638 pass
1640 def visit_definition(self, node):
1641 self.paragraph_style_stack.append(self.rststyle('blockindent'))
1642 self.bumped_list_level_stack.append(ListLevel(1))
1644 def depart_definition(self, node):
1645 self.paragraph_style_stack.pop()
1646 self.bumped_list_level_stack.pop()
1648 def visit_definition_list(self, node):
1649 pass
1651 def depart_definition_list(self, node):
1652 pass
1654 def visit_definition_list_item(self, node):
1655 pass
1657 def depart_definition_list_item(self, node):
1658 pass
1660 def visit_term(self, node):
1661 el = self.append_p('textbody')
1662 el1 = SubElement(el, 'text:span',
1663 attrib={'text:style-name': self.rststyle('strong')})
1664 #el1.text = node.astext()
1665 self.set_current_element(el1)
1667 def depart_term(self, node):
1668 self.set_to_parent()
1669 self.set_to_parent()
1671 def visit_classifier(self, node):
1672 els = self.current_element.getchildren()
1673 if len(els) > 0:
1674 el = els[-1]
1675 el1 = SubElement(el, 'text:span',
1676 attrib={'text:style-name': self.rststyle('emphasis')
1678 el1.text = ' (%s)' % (node.astext(), )
1680 def depart_classifier(self, node):
1681 pass
1683 def visit_document(self, node):
1684 pass
1686 def depart_document(self, node):
1687 self.process_footnotes()
1689 def visit_docinfo(self, node):
1690 self.section_level += 1
1691 self.section_count += 1
1692 if self.settings.create_sections:
1693 el = self.append_child('text:section', attrib={
1694 'text:name': 'Section%d' % self.section_count,
1695 'text:style-name': 'Sect%d' % self.section_level,
1697 self.set_current_element(el)
1699 def depart_docinfo(self, node):
1700 self.section_level -= 1
1701 if self.settings.create_sections:
1702 self.set_to_parent()
1704 def visit_emphasis(self, node):
1705 el = SubElement(self.current_element, 'text:span',
1706 attrib={'text:style-name': self.rststyle('emphasis')})
1707 self.set_current_element(el)
1709 def depart_emphasis(self, node):
1710 self.set_to_parent()
1712 def visit_enumerated_list(self, node):
1713 el1 = self.current_element
1714 if self.blockstyle == self.rststyle('blockquote'):
1715 el2 = SubElement(el1, 'text:list', attrib={
1716 'text:style-name': self.rststyle('blockquote-enumlist'),
1718 self.list_style_stack.append(self.rststyle('blockquote-enumitem'))
1719 elif self.blockstyle == self.rststyle('highlights'):
1720 el2 = SubElement(el1, 'text:list', attrib={
1721 'text:style-name': self.rststyle('highlights-enumlist'),
1723 self.list_style_stack.append(self.rststyle('highlights-enumitem'))
1724 elif self.blockstyle == self.rststyle('epigraph'):
1725 el2 = SubElement(el1, 'text:list', attrib={
1726 'text:style-name': self.rststyle('epigraph-enumlist'),
1728 self.list_style_stack.append(self.rststyle('epigraph-enumitem'))
1729 else:
1730 liststylename = 'enumlist-%s' % (node.get('enumtype', 'arabic'), )
1731 el2 = SubElement(el1, 'text:list', attrib={
1732 'text:style-name': self.rststyle(liststylename),
1734 self.list_style_stack.append(self.rststyle('enumitem'))
1735 self.set_current_element(el2)
1737 def depart_enumerated_list(self, node):
1738 self.set_to_parent()
1739 self.list_style_stack.pop()
1741 def visit_list_item(self, node):
1742 # If we are in a "bumped" list level, then wrap this
1743 # list in an outer lists in order to increase the
1744 # indentation level.
1745 if self.in_table_of_contents:
1746 if self.settings.generate_oowriter_toc:
1747 self.paragraph_style_stack.append(
1748 self.rststyle('contents-%d' % (self.list_level, )))
1749 else:
1750 el1 = self.append_child('text:list-item')
1751 self.set_current_element(el1)
1752 else:
1753 el1 = self.append_child('text:list-item')
1754 el3 = el1
1755 if len(self.bumped_list_level_stack) > 0:
1756 level_obj = self.bumped_list_level_stack[-1]
1757 if level_obj.get_sibling():
1758 level_obj.set_nested(False)
1759 for level_obj1 in self.bumped_list_level_stack:
1760 for idx in range(level_obj1.get_level()):
1761 el2 = self.append_child('text:list', parent=el3)
1762 el3 = self.append_child(
1763 'text:list-item', parent=el2)
1764 self.paragraph_style_stack.append(self.list_style_stack[-1])
1765 self.set_current_element(el3)
1767 def depart_list_item(self, node):
1768 if self.in_table_of_contents:
1769 if self.settings.generate_oowriter_toc:
1770 self.paragraph_style_stack.pop()
1771 else:
1772 self.set_to_parent()
1773 else:
1774 if len(self.bumped_list_level_stack) > 0:
1775 level_obj = self.bumped_list_level_stack[-1]
1776 if level_obj.get_sibling():
1777 level_obj.set_nested(True)
1778 for level_obj1 in self.bumped_list_level_stack:
1779 for idx in range(level_obj1.get_level()):
1780 self.set_to_parent()
1781 self.set_to_parent()
1782 self.paragraph_style_stack.pop()
1783 self.set_to_parent()
1785 def visit_header(self, node):
1786 self.in_header = True
1788 def depart_header(self, node):
1789 self.in_header = False
1791 def visit_footer(self, node):
1792 self.in_footer = True
1794 def depart_footer(self, node):
1795 self.in_footer = False
1797 def visit_field(self, node):
1798 pass
1800 def depart_field(self, node):
1801 pass
1803 def visit_field_list(self, node):
1804 pass
1806 def depart_field_list(self, node):
1807 pass
1809 def visit_field_name(self, node):
1810 el = self.append_p('textbody')
1811 el1 = SubElement(el, 'text:span',
1812 attrib={'text:style-name': self.rststyle('strong')})
1813 el1.text = node.astext()
1815 def depart_field_name(self, node):
1816 pass
1818 def visit_field_body(self, node):
1819 self.paragraph_style_stack.append(self.rststyle('blockindent'))
1821 def depart_field_body(self, node):
1822 self.paragraph_style_stack.pop()
1824 def visit_figure(self, node):
1825 pass
1827 def depart_figure(self, node):
1828 pass
1830 def visit_footnote(self, node):
1831 self.footnote_level += 1
1832 self.save_footnote_current = self.current_element
1833 el1 = Element('text:note-body')
1834 self.current_element = el1
1835 self.footnote_list.append((node, el1))
1836 if isinstance(node, docutils.nodes.citation):
1837 self.paragraph_style_stack.append(self.rststyle('citation'))
1838 else:
1839 self.paragraph_style_stack.append(self.rststyle('footnote'))
1841 def depart_footnote(self, node):
1842 self.paragraph_style_stack.pop()
1843 self.current_element = self.save_footnote_current
1844 self.footnote_level -= 1
1846 footnote_chars = [
1847 '*', '**', '***',
1848 '++', '+++',
1849 '##', '###',
1850 '@@', '@@@',
1853 def visit_footnote_reference(self, node):
1854 if self.footnote_level <= 0:
1855 id = node.attributes['ids'][0]
1856 refid = node.attributes.get('refid')
1857 if refid is None:
1858 refid = ''
1859 if self.settings.endnotes_end_doc:
1860 note_class = 'endnote'
1861 else:
1862 note_class = 'footnote'
1863 el1 = self.append_child('text:note', attrib={
1864 'text:id': '%s' % (refid, ),
1865 'text:note-class': note_class,
1867 note_auto = str(node.attributes.get('auto', 1))
1868 if isinstance(node, docutils.nodes.citation_reference):
1869 citation = '[%s]' % node.astext()
1870 el2 = SubElement(el1, 'text:note-citation', attrib={
1871 'text:label': citation,
1873 el2.text = citation
1874 elif note_auto == '1':
1875 el2 = SubElement(el1, 'text:note-citation', attrib={
1876 'text:label': node.astext(),
1878 el2.text = node.astext()
1879 elif note_auto == '*':
1880 if self.footnote_chars_idx >= len(
1881 ODFTranslator.footnote_chars):
1882 self.footnote_chars_idx = 0
1883 footnote_char = ODFTranslator.footnote_chars[
1884 self.footnote_chars_idx]
1885 self.footnote_chars_idx += 1
1886 el2 = SubElement(el1, 'text:note-citation', attrib={
1887 'text:label': footnote_char,
1889 el2.text = footnote_char
1890 self.footnote_ref_dict[id] = el1
1891 raise nodes.SkipChildren()
1893 def depart_footnote_reference(self, node):
1894 pass
1896 def visit_citation(self, node):
1897 for id in node.attributes['ids']:
1898 self.citation_id = id
1899 break
1900 self.paragraph_style_stack.append(self.rststyle('blockindent'))
1901 self.bumped_list_level_stack.append(ListLevel(1))
1903 def depart_citation(self, node):
1904 self.citation_id = None
1905 self.paragraph_style_stack.pop()
1906 self.bumped_list_level_stack.pop()
1908 def visit_citation_reference(self, node):
1909 if self.settings.create_links:
1910 id = node.attributes['refid']
1911 el = self.append_child('text:reference-ref', attrib={
1912 'text:ref-name': '%s' % (id, ),
1913 'text:reference-format': 'text',
1915 el.text = '['
1916 self.set_current_element(el)
1917 elif self.current_element.text is None:
1918 self.current_element.text = '['
1919 else:
1920 self.current_element.text += '['
1922 def depart_citation_reference(self, node):
1923 self.current_element.text += ']'
1924 if self.settings.create_links:
1925 self.set_to_parent()
1927 def visit_label(self, node):
1928 if isinstance(node.parent, docutils.nodes.footnote):
1929 raise nodes.SkipChildren()
1930 elif self.citation_id is not None:
1931 el = self.append_p('textbody')
1932 self.set_current_element(el)
1933 el.text = '['
1934 if self.settings.create_links:
1935 el1 = self.append_child('text:reference-mark-start', attrib={
1936 'text:name': '%s' % (self.citation_id, ),
1939 def depart_label(self, node):
1940 if isinstance(node.parent, docutils.nodes.footnote):
1941 pass
1942 elif self.citation_id is not None:
1943 self.current_element.text += ']'
1944 if self.settings.create_links:
1945 el = self.append_child('text:reference-mark-end', attrib={
1946 'text:name': '%s' % (self.citation_id, ),
1948 self.set_to_parent()
1950 def visit_generated(self, node):
1951 pass
1953 def depart_generated(self, node):
1954 pass
1956 def check_file_exists(self, path):
1957 if os.path.exists(path):
1958 return 1
1959 else:
1960 return 0
1962 def visit_image(self, node):
1963 # Capture the image file.
1964 if 'uri' in node.attributes:
1965 source = node.attributes['uri']
1966 if not self.check_file_exists(source):
1967 self.document.reporter.warning(
1968 'Cannot find image file %s.' % (source, ))
1969 return
1970 else:
1971 return
1972 if source in self.image_dict:
1973 filename, destination = self.image_dict[source]
1974 else:
1975 self.image_count += 1
1976 filename = os.path.split(source)[1]
1977 destination = 'Pictures/1%08x%s' % (self.image_count, filename, )
1978 spec = (os.path.abspath(source), destination,)
1980 self.embedded_file_list.append(spec)
1981 self.image_dict[source] = (source, destination,)
1982 # Is this a figure (containing an image) or just a plain image?
1983 if self.in_paragraph:
1984 el1 = self.current_element
1985 else:
1986 el1 = SubElement(self.current_element, 'text:p',
1987 attrib={'text:style-name': self.rststyle('textbody')})
1988 el2 = el1
1989 if isinstance(node.parent, docutils.nodes.figure):
1990 el3, el4, el5, caption = self.generate_figure(node, source,
1991 destination, el2)
1992 attrib = {}
1993 el6, width = self.generate_image(node, source, destination,
1994 el5, attrib)
1995 if caption is not None:
1996 el6.tail = caption
1997 else: #if isinstance(node.parent, docutils.nodes.image):
1998 el3 = self.generate_image(node, source, destination, el2)
2000 def depart_image(self, node):
2001 pass
2003 def get_image_width_height(self, node, attr):
2004 size = None
2005 if attr in node.attributes:
2006 size = node.attributes[attr]
2007 unit = size[-2:]
2008 if unit.isalpha():
2009 size = size[:-2]
2010 else:
2011 unit = 'px'
2012 try:
2013 size = float(size)
2014 except ValueError, e:
2015 self.document.reporter.warning(
2016 'Invalid %s for image: "%s"' % (
2017 attr, node.attributes[attr]))
2018 size = [size, unit]
2019 return size
2021 def get_image_scale(self, node):
2022 if 'scale' in node.attributes:
2023 try:
2024 scale = int(node.attributes['scale'])
2025 if scale < 1: # or scale > 100:
2026 self.document.reporter.warning(
2027 'scale out of range (%s), using 1.' % (scale, ))
2028 scale = 1
2029 scale = scale * 0.01
2030 except ValueError, e:
2031 self.document.reporter.warning(
2032 'Invalid scale for image: "%s"' % (
2033 node.attributes['scale'], ))
2034 else:
2035 scale = 1.0
2036 return scale
2038 def get_image_scaled_width_height(self, node, source):
2039 scale = self.get_image_scale(node)
2040 width = self.get_image_width_height(node, 'width')
2041 height = self.get_image_width_height(node, 'height')
2043 dpi = (72, 72)
2044 if Image is not None and source in self.image_dict:
2045 filename, destination = self.image_dict[source]
2046 imageobj = Image.open(filename, 'r')
2047 dpi = imageobj.info.get('dpi', dpi)
2048 # dpi information can be (xdpi, ydpi) or xydpi
2049 try: iter(dpi)
2050 except: dpi = (dpi, dpi)
2051 else:
2052 imageobj = None
2054 if width is None or height is None:
2055 if imageobj is None:
2056 raise RuntimeError(
2057 'image size not fully specified and PIL not installed')
2058 if width is None: width = [imageobj.size[0], 'px']
2059 if height is None: height = [imageobj.size[1], 'px']
2061 width[0] *= scale
2062 height[0] *= scale
2063 if width[1] == 'px': width = [width[0] / dpi[0], 'in']
2064 if height[1] == 'px': height = [height[0] / dpi[1], 'in']
2066 width[0] = str(width[0])
2067 height[0] = str(height[0])
2068 return ''.join(width), ''.join(height)
2070 def generate_figure(self, node, source, destination, current_element):
2071 caption = None
2072 width, height = self.get_image_scaled_width_height(node, source)
2073 for node1 in node.parent.children:
2074 if node1.tagname == 'caption':
2075 caption = node1.astext()
2076 self.image_style_count += 1
2078 # Add the style for the caption.
2079 if caption is not None:
2080 attrib = {
2081 'style:class': 'extra',
2082 'style:family': 'paragraph',
2083 'style:name': 'Caption',
2084 'style:parent-style-name': 'Standard',
2086 el1 = SubElement(self.automatic_styles, 'style:style',
2087 attrib=attrib, nsdict=SNSD)
2088 attrib = {
2089 'fo:margin-bottom': '0.0835in',
2090 'fo:margin-top': '0.0835in',
2091 'text:line-number': '0',
2092 'text:number-lines': 'false',
2094 el2 = SubElement(el1, 'style:paragraph-properties',
2095 attrib=attrib, nsdict=SNSD)
2096 attrib = {
2097 'fo:font-size': '12pt',
2098 'fo:font-style': 'italic',
2099 'style:font-name': 'Times',
2100 'style:font-name-complex': 'Lucidasans1',
2101 'style:font-size-asian': '12pt',
2102 'style:font-size-complex': '12pt',
2103 'style:font-style-asian': 'italic',
2104 'style:font-style-complex': 'italic',
2106 el2 = SubElement(el1, 'style:text-properties',
2107 attrib=attrib, nsdict=SNSD)
2108 style_name = 'rstframestyle%d' % self.image_style_count
2109 # Add the styles
2110 attrib = {
2111 'style:name': style_name,
2112 'style:family': 'graphic',
2113 'style:parent-style-name': self.rststyle('figureframe'),
2115 el1 = SubElement(self.automatic_styles,
2116 'style:style', attrib=attrib, nsdict=SNSD)
2117 halign = 'center'
2118 valign = 'top'
2119 if 'align' in node.attributes:
2120 align = node.attributes['align'].split()
2121 for val in align:
2122 if val in ('left', 'center', 'right'):
2123 halign = val
2124 elif val in ('top', 'middle', 'bottom'):
2125 valign = val
2126 attrib = {}
2127 wrap = False
2128 classes = node.parent.attributes.get('classes')
2129 if classes and 'wrap' in classes:
2130 wrap = True
2131 if wrap:
2132 attrib['style:wrap'] = 'dynamic'
2133 else:
2134 attrib['style:wrap'] = 'none'
2135 el2 = SubElement(el1,
2136 'style:graphic-properties', attrib=attrib, nsdict=SNSD)
2137 attrib = {
2138 'draw:style-name': style_name,
2139 'draw:name': 'Frame1',
2140 'text:anchor-type': 'paragraph',
2141 'draw:z-index': '0',
2143 attrib['svg:width'] = width
2144 # dbg
2145 #attrib['svg:height'] = height
2146 el3 = SubElement(current_element, 'draw:frame', attrib=attrib)
2147 attrib = {}
2148 el4 = SubElement(el3, 'draw:text-box', attrib=attrib)
2149 attrib = {
2150 'text:style-name': self.rststyle('caption'),
2152 el5 = SubElement(el4, 'text:p', attrib=attrib)
2153 return el3, el4, el5, caption
2155 def generate_image(self, node, source, destination, current_element,
2156 frame_attrs=None):
2157 width, height = self.get_image_scaled_width_height(node, source)
2158 self.image_style_count += 1
2159 style_name = 'rstframestyle%d' % self.image_style_count
2160 # Add the style.
2161 attrib = {
2162 'style:name': style_name,
2163 'style:family': 'graphic',
2164 'style:parent-style-name': self.rststyle('image'),
2166 el1 = SubElement(self.automatic_styles,
2167 'style:style', attrib=attrib, nsdict=SNSD)
2168 halign = None
2169 valign = None
2170 if 'align' in node.attributes:
2171 align = node.attributes['align'].split()
2172 for val in align:
2173 if val in ('left', 'center', 'right'):
2174 halign = val
2175 elif val in ('top', 'middle', 'bottom'):
2176 valign = val
2177 if frame_attrs is None:
2178 attrib = {
2179 'style:vertical-pos': 'top',
2180 'style:vertical-rel': 'paragraph',
2181 'style:horizontal-rel': 'paragraph',
2182 'style:mirror': 'none',
2183 'fo:clip': 'rect(0cm 0cm 0cm 0cm)',
2184 'draw:luminance': '0%',
2185 'draw:contrast': '0%',
2186 'draw:red': '0%',
2187 'draw:green': '0%',
2188 'draw:blue': '0%',
2189 'draw:gamma': '100%',
2190 'draw:color-inversion': 'false',
2191 'draw:image-opacity': '100%',
2192 'draw:color-mode': 'standard',
2194 else:
2195 attrib = frame_attrs
2196 if halign is not None:
2197 attrib['style:horizontal-pos'] = halign
2198 if valign is not None:
2199 attrib['style:vertical-pos'] = valign
2200 # If there is a classes/wrap directive or we are
2201 # inside a table, add a no-wrap style.
2202 wrap = False
2203 classes = node.attributes.get('classes')
2204 if classes and 'wrap' in classes:
2205 wrap = True
2206 if wrap:
2207 attrib['style:wrap'] = 'dynamic'
2208 else:
2209 attrib['style:wrap'] = 'none'
2210 # If we are inside a table, add a no-wrap style.
2211 if self.is_in_table(node):
2212 attrib['style:wrap'] = 'none'
2213 el2 = SubElement(el1,
2214 'style:graphic-properties', attrib=attrib, nsdict=SNSD)
2215 # Add the content.
2216 #el = SubElement(current_element, 'text:p',
2217 # attrib={'text:style-name': self.rststyle('textbody')})
2218 attrib={
2219 'draw:style-name': style_name,
2220 'draw:name': 'graphics2',
2221 'draw:z-index': '1',
2223 if isinstance(node.parent, nodes.TextElement):
2224 attrib['text:anchor-type'] = 'as-char' #vds
2225 else:
2226 attrib['text:anchor-type'] = 'paragraph'
2227 attrib['svg:width'] = width
2228 attrib['svg:height'] = height
2229 el1 = SubElement(current_element, 'draw:frame', attrib=attrib)
2230 el2 = SubElement(el1, 'draw:image', attrib={
2231 'xlink:href': '%s' % (destination, ),
2232 'xlink:type': 'simple',
2233 'xlink:show': 'embed',
2234 'xlink:actuate': 'onLoad',
2236 return el1, width
2238 def is_in_table(self, node):
2239 node1 = node.parent
2240 while node1:
2241 if isinstance(node1, docutils.nodes.entry):
2242 return True
2243 node1 = node1.parent
2244 return False
2246 def visit_legend(self, node):
2247 if isinstance(node.parent, docutils.nodes.figure):
2248 el1 = self.current_element[-1]
2249 el1 = el1[0][0]
2250 self.current_element = el1
2251 self.paragraph_style_stack.append(self.rststyle('legend'))
2253 def depart_legend(self, node):
2254 if isinstance(node.parent, docutils.nodes.figure):
2255 self.paragraph_style_stack.pop()
2256 self.set_to_parent()
2257 self.set_to_parent()
2258 self.set_to_parent()
2260 def visit_line_block(self, node):
2261 self.line_indent_level += 1
2262 self.line_block_level += 1
2264 def depart_line_block(self, node):
2265 self.line_indent_level -= 1
2266 self.line_block_level -= 1
2268 def visit_line(self, node):
2269 style = 'lineblock%d' % self.line_indent_level
2270 el1 = SubElement(self.current_element, 'text:p', attrib={
2271 'text:style-name': self.rststyle(style),
2273 self.current_element = el1
2275 def depart_line(self, node):
2276 self.set_to_parent()
2278 def visit_literal(self, node):
2279 el = SubElement(self.current_element, 'text:span',
2280 attrib={'text:style-name': self.rststyle('inlineliteral')})
2281 self.set_current_element(el)
2283 def depart_literal(self, node):
2284 self.set_to_parent()
2286 def visit_inline(self, node):
2287 styles = node.attributes.get('classes', ())
2288 if len(styles) > 0:
2289 inline_style = styles[0]
2290 el = SubElement(self.current_element, 'text:span',
2291 attrib={'text:style-name': self.rststyle(inline_style)})
2292 self.set_current_element(el)
2294 def depart_inline(self, node):
2295 self.set_to_parent()
2297 def _calculate_code_block_padding(self, line):
2298 count = 0
2299 matchobj = SPACES_PATTERN.match(line)
2300 if matchobj:
2301 pad = matchobj.group()
2302 count = len(pad)
2303 else:
2304 matchobj = TABS_PATTERN.match(line)
2305 if matchobj:
2306 pad = matchobj.group()
2307 count = len(pad) * 8
2308 return count
2310 def _add_syntax_highlighting(self, insource, language):
2311 lexer = pygments.lexers.get_lexer_by_name(language, stripall=True)
2312 if language in ('latex', 'tex'):
2313 fmtr = OdtPygmentsLaTeXFormatter(lambda name, parameters=():
2314 self.rststyle(name, parameters),
2315 escape_function=escape_cdata)
2316 else:
2317 fmtr = OdtPygmentsProgFormatter(lambda name, parameters=():
2318 self.rststyle(name, parameters),
2319 escape_function=escape_cdata)
2320 outsource = pygments.highlight(insource, lexer, fmtr)
2321 return outsource
2323 def fill_line(self, line):
2324 line = FILL_PAT1.sub(self.fill_func1, line)
2325 line = FILL_PAT2.sub(self.fill_func2, line)
2326 return line
2328 def fill_func1(self, matchobj):
2329 spaces = matchobj.group(0)
2330 repl = '<text:s text:c="%d"/>' % (len(spaces), )
2331 return repl
2333 def fill_func2(self, matchobj):
2334 spaces = matchobj.group(0)
2335 repl = ' <text:s text:c="%d"/>' % (len(spaces) - 1, )
2336 return repl
2338 def visit_literal_block(self, node):
2339 wrapper1 = '<text:p text:style-name="%s">%%s</text:p>' % (
2340 self.rststyle('codeblock'), )
2341 source = node.astext()
2342 if (pygments and
2343 self.settings.add_syntax_highlighting
2344 #and
2345 #node.get('hilight', False)
2347 language = node.get('language', 'python')
2348 source = self._add_syntax_highlighting(source, language)
2349 else:
2350 source = escape_cdata(source)
2351 lines = source.split('\n')
2352 lines1 = ['<wrappertag1 xmlns:text="urn:oasis:names:tc:opendocument:xmlns:text:1.0">']
2354 my_lines = []
2355 for my_line in lines:
2356 my_line = self.fill_line(my_line)
2357 my_line = my_line.replace("&#10;", "\n")
2358 my_lines.append(my_line)
2359 my_lines_str = '<text:line-break/>'.join(my_lines)
2360 my_lines_str2 = wrapper1 % (my_lines_str, )
2361 lines1.append(my_lines_str2)
2362 lines1.append('</wrappertag1>')
2363 s1 = ''.join(lines1)
2364 if WhichElementTree != "lxml":
2365 s1 = s1.encode("utf-8")
2366 el1 = etree.fromstring(s1)
2367 children = el1.getchildren()
2368 for child in children:
2369 self.current_element.append(child)
2371 def depart_literal_block(self, node):
2372 pass
2374 visit_doctest_block = visit_literal_block
2375 depart_doctest_block = depart_literal_block
2377 def visit_meta(self, node):
2378 name = node.attributes.get('name')
2379 content = node.attributes.get('content')
2380 if name is not None and content is not None:
2381 self.meta_dict[name] = content
2383 def depart_meta(self, node):
2384 pass
2386 def visit_option_list(self, node):
2387 table_name = 'tableoption'
2389 # Generate automatic styles
2390 if not self.optiontablestyles_generated:
2391 self.optiontablestyles_generated = True
2392 el = SubElement(self.automatic_styles, 'style:style', attrib={
2393 'style:name': self.rststyle(table_name),
2394 'style:family': 'table'}, nsdict=SNSD)
2395 el1 = SubElement(el, 'style:table-properties', attrib={
2396 'style:width': '17.59cm',
2397 'table:align': 'left',
2398 'style:shadow': 'none'}, nsdict=SNSD)
2399 el = SubElement(self.automatic_styles, 'style:style', attrib={
2400 'style:name': self.rststyle('%s.%%c' % table_name, ( 'A', )),
2401 'style:family': 'table-column'}, nsdict=SNSD)
2402 el1 = SubElement(el, 'style:table-column-properties', attrib={
2403 'style:column-width': '4.999cm'}, nsdict=SNSD)
2404 el = SubElement(self.automatic_styles, 'style:style', attrib={
2405 'style:name': self.rststyle('%s.%%c' % table_name, ( 'B', )),
2406 'style:family': 'table-column'}, nsdict=SNSD)
2407 el1 = SubElement(el, 'style:table-column-properties', attrib={
2408 'style:column-width': '12.587cm'}, nsdict=SNSD)
2409 el = SubElement(self.automatic_styles, 'style:style', attrib={
2410 'style:name': self.rststyle(
2411 '%s.%%c%%d' % table_name, ( 'A', 1, )),
2412 'style:family': 'table-cell'}, nsdict=SNSD)
2413 el1 = SubElement(el, 'style:table-cell-properties', attrib={
2414 'fo:background-color': 'transparent',
2415 'fo:padding': '0.097cm',
2416 'fo:border-left': '0.035cm solid #000000',
2417 'fo:border-right': 'none',
2418 'fo:border-top': '0.035cm solid #000000',
2419 'fo:border-bottom': '0.035cm solid #000000'}, nsdict=SNSD)
2420 el2 = SubElement(el1, 'style:background-image', nsdict=SNSD)
2421 el = SubElement(self.automatic_styles, 'style:style', attrib={
2422 'style:name': self.rststyle(
2423 '%s.%%c%%d' % table_name, ( 'B', 1, )),
2424 'style:family': 'table-cell'}, nsdict=SNSD)
2425 el1 = SubElement(el, 'style:table-cell-properties', attrib={
2426 'fo:padding': '0.097cm',
2427 'fo:border': '0.035cm solid #000000'}, nsdict=SNSD)
2428 el = SubElement(self.automatic_styles, 'style:style', attrib={
2429 'style:name': self.rststyle(
2430 '%s.%%c%%d' % table_name, ( 'A', 2, )),
2431 'style:family': 'table-cell'}, nsdict=SNSD)
2432 el1 = SubElement(el, 'style:table-cell-properties', attrib={
2433 'fo:padding': '0.097cm',
2434 'fo:border-left': '0.035cm solid #000000',
2435 'fo:border-right': 'none',
2436 'fo:border-top': 'none',
2437 'fo:border-bottom': '0.035cm solid #000000'}, nsdict=SNSD)
2438 el = SubElement(self.automatic_styles, 'style:style', attrib={
2439 'style:name': self.rststyle(
2440 '%s.%%c%%d' % table_name, ( 'B', 2, )),
2441 'style:family': 'table-cell'}, nsdict=SNSD)
2442 el1 = SubElement(el, 'style:table-cell-properties', attrib={
2443 'fo:padding': '0.097cm',
2444 'fo:border-left': '0.035cm solid #000000',
2445 'fo:border-right': '0.035cm solid #000000',
2446 'fo:border-top': 'none',
2447 'fo:border-bottom': '0.035cm solid #000000'}, nsdict=SNSD)
2449 # Generate table data
2450 el = self.append_child('table:table', attrib={
2451 'table:name': self.rststyle(table_name),
2452 'table:style-name': self.rststyle(table_name),
2454 el1 = SubElement(el, 'table:table-column', attrib={
2455 'table:style-name': self.rststyle(
2456 '%s.%%c' % table_name, ( 'A', ))})
2457 el1 = SubElement(el, 'table:table-column', attrib={
2458 'table:style-name': self.rststyle(
2459 '%s.%%c' % table_name, ( 'B', ))})
2460 el1 = SubElement(el, 'table:table-header-rows')
2461 el2 = SubElement(el1, 'table:table-row')
2462 el3 = SubElement(el2, 'table:table-cell', attrib={
2463 'table:style-name': self.rststyle(
2464 '%s.%%c%%d' % table_name, ( 'A', 1, )),
2465 'office:value-type': 'string'})
2466 el4 = SubElement(el3, 'text:p', attrib={
2467 'text:style-name': 'Table_20_Heading'})
2468 el4.text= 'Option'
2469 el3 = SubElement(el2, 'table:table-cell', attrib={
2470 'table:style-name': self.rststyle(
2471 '%s.%%c%%d' % table_name, ( 'B', 1, )),
2472 'office:value-type': 'string'})
2473 el4 = SubElement(el3, 'text:p', attrib={
2474 'text:style-name': 'Table_20_Heading'})
2475 el4.text= 'Description'
2476 self.set_current_element(el)
2478 def depart_option_list(self, node):
2479 self.set_to_parent()
2481 def visit_option_list_item(self, node):
2482 el = self.append_child('table:table-row')
2483 self.set_current_element(el)
2485 def depart_option_list_item(self, node):
2486 self.set_to_parent()
2488 def visit_option_group(self, node):
2489 el = self.append_child('table:table-cell', attrib={
2490 'table:style-name': 'Table%d.A2' % self.table_count,
2491 'office:value-type': 'string',
2493 self.set_current_element(el)
2495 def depart_option_group(self, node):
2496 self.set_to_parent()
2498 def visit_option(self, node):
2499 el = self.append_child('text:p', attrib={
2500 'text:style-name': 'Table_20_Contents'})
2501 el.text = node.astext()
2503 def depart_option(self, node):
2504 pass
2506 def visit_option_string(self, node):
2507 pass
2509 def depart_option_string(self, node):
2510 pass
2512 def visit_option_argument(self, node):
2513 pass
2515 def depart_option_argument(self, node):
2516 pass
2518 def visit_description(self, node):
2519 el = self.append_child('table:table-cell', attrib={
2520 'table:style-name': 'Table%d.B2' % self.table_count,
2521 'office:value-type': 'string',
2523 el1 = SubElement(el, 'text:p', attrib={
2524 'text:style-name': 'Table_20_Contents'})
2525 el1.text = node.astext()
2526 raise nodes.SkipChildren()
2528 def depart_description(self, node):
2529 pass
2531 def visit_paragraph(self, node):
2532 self.in_paragraph = True
2533 if self.in_header:
2534 el = self.append_p('header')
2535 elif self.in_footer:
2536 el = self.append_p('footer')
2537 else:
2538 style_name = self.paragraph_style_stack[-1]
2539 el = self.append_child('text:p',
2540 attrib={'text:style-name': style_name})
2541 self.append_pending_ids(el)
2542 self.set_current_element(el)
2544 def depart_paragraph(self, node):
2545 self.in_paragraph = False
2546 self.set_to_parent()
2547 if self.in_header:
2548 self.header_content.append(
2549 self.current_element.getchildren()[-1])
2550 self.current_element.remove(
2551 self.current_element.getchildren()[-1])
2552 elif self.in_footer:
2553 self.footer_content.append(
2554 self.current_element.getchildren()[-1])
2555 self.current_element.remove(
2556 self.current_element.getchildren()[-1])
2558 def visit_problematic(self, node):
2559 pass
2561 def depart_problematic(self, node):
2562 pass
2564 def visit_raw(self, node):
2565 if 'format' in node.attributes:
2566 formats = node.attributes['format']
2567 formatlist = formats.split()
2568 if 'odt' in formatlist:
2569 rawstr = node.astext()
2570 attrstr = ' '.join(['%s="%s"' % (k, v, )
2571 for k,v in CONTENT_NAMESPACE_ATTRIB.items()])
2572 contentstr = '<stuff %s>%s</stuff>' % (attrstr, rawstr, )
2573 if WhichElementTree != "lxml":
2574 contentstr = contentstr.encode("utf-8")
2575 content = etree.fromstring(contentstr)
2576 elements = content.getchildren()
2577 if len(elements) > 0:
2578 el1 = elements[0]
2579 if self.in_header:
2580 pass
2581 elif self.in_footer:
2582 pass
2583 else:
2584 self.current_element.append(el1)
2585 raise nodes.SkipChildren()
2587 def depart_raw(self, node):
2588 if self.in_header:
2589 pass
2590 elif self.in_footer:
2591 pass
2592 else:
2593 pass
2595 def visit_reference(self, node):
2596 text = node.astext()
2597 if self.settings.create_links:
2598 if node.has_key('refuri'):
2599 href = node['refuri']
2600 if ( self.settings.cloak_email_addresses
2601 and href.startswith('mailto:')):
2602 href = self.cloak_mailto(href)
2603 el = self.append_child('text:a', attrib={
2604 'xlink:href': '%s' % href,
2605 'xlink:type': 'simple',
2607 self.set_current_element(el)
2608 elif node.has_key('refid'):
2609 if self.settings.create_links:
2610 href = node['refid']
2611 el = self.append_child('text:reference-ref', attrib={
2612 'text:ref-name': '%s' % href,
2613 'text:reference-format': 'text',
2615 else:
2616 self.document.reporter.warning(
2617 'References must have "refuri" or "refid" attribute.')
2618 if (self.in_table_of_contents and
2619 len(node.children) >= 1 and
2620 isinstance(node.children[0], docutils.nodes.generated)):
2621 node.remove(node.children[0])
2623 def depart_reference(self, node):
2624 if self.settings.create_links:
2625 if node.has_key('refuri'):
2626 self.set_to_parent()
2628 def visit_rubric(self, node):
2629 style_name = self.rststyle('rubric')
2630 classes = node.get('classes')
2631 if classes:
2632 class1 = classes[0]
2633 if class1:
2634 style_name = class1
2635 el = SubElement(self.current_element, 'text:h', attrib = {
2636 #'text:outline-level': '%d' % section_level,
2637 #'text:style-name': 'Heading_20_%d' % section_level,
2638 'text:style-name': style_name,
2640 text = node.astext()
2641 el.text = self.encode(text)
2643 def depart_rubric(self, node):
2644 pass
2646 def visit_section(self, node, move_ids=1):
2647 self.section_level += 1
2648 self.section_count += 1
2649 if self.settings.create_sections:
2650 el = self.append_child('text:section', attrib={
2651 'text:name': 'Section%d' % self.section_count,
2652 'text:style-name': 'Sect%d' % self.section_level,
2654 self.set_current_element(el)
2656 def depart_section(self, node):
2657 self.section_level -= 1
2658 if self.settings.create_sections:
2659 self.set_to_parent()
2661 def visit_strong(self, node):
2662 el = SubElement(self.current_element, 'text:span',
2663 attrib={'text:style-name': self.rststyle('strong')})
2664 self.set_current_element(el)
2666 def depart_strong(self, node):
2667 self.set_to_parent()
2669 def visit_substitution_definition(self, node):
2670 raise nodes.SkipChildren()
2672 def depart_substitution_definition(self, node):
2673 pass
2675 def visit_system_message(self, node):
2676 pass
2678 def depart_system_message(self, node):
2679 pass
2681 def get_table_style(self, node):
2682 table_style = None
2683 table_name = None
2684 use_predefined_table_style = False
2685 str_classes = node.get('classes')
2686 if str_classes is not None:
2687 for str_class in str_classes:
2688 if str_class.startswith(TABLESTYLEPREFIX):
2689 table_name = str_class
2690 use_predefined_table_style = True
2691 break
2692 if table_name is not None:
2693 table_style = self.table_styles.get(table_name)
2694 if table_style is None:
2695 # If we can't find the table style, issue warning
2696 # and use the default table style.
2697 self.document.reporter.warning(
2698 'Can\'t find table style "%s". Using default.' % (
2699 table_name, ))
2700 table_name = TABLENAMEDEFAULT
2701 table_style = self.table_styles.get(table_name)
2702 if table_style is None:
2703 # If we can't find the default table style, issue a warning
2704 # and use a built-in default style.
2705 self.document.reporter.warning(
2706 'Can\'t find default table style "%s". Using built-in default.' % (
2707 table_name, ))
2708 table_style = BUILTIN_DEFAULT_TABLE_STYLE
2709 else:
2710 table_name = TABLENAMEDEFAULT
2711 table_style = self.table_styles.get(table_name)
2712 if table_style is None:
2713 # If we can't find the default table style, issue a warning
2714 # and use a built-in default style.
2715 self.document.reporter.warning(
2716 'Can\'t find default table style "%s". Using built-in default.' % (
2717 table_name, ))
2718 table_style = BUILTIN_DEFAULT_TABLE_STYLE
2719 return table_style
2721 def visit_table(self, node):
2722 self.table_count += 1
2723 table_style = self.get_table_style(node)
2724 table_name = '%s%%d' % TABLESTYLEPREFIX
2725 el1 = SubElement(self.automatic_styles, 'style:style', attrib={
2726 'style:name': self.rststyle(
2727 '%s' % table_name, ( self.table_count, )),
2728 'style:family': 'table',
2729 }, nsdict=SNSD)
2730 if table_style.backgroundcolor is None:
2731 el1_1 = SubElement(el1, 'style:table-properties', attrib={
2732 #'style:width': '17.59cm',
2733 'table:align': 'margins',
2734 'fo:margin-top': '0in',
2735 'fo:margin-bottom': '0.10in',
2736 }, nsdict=SNSD)
2737 else:
2738 el1_1 = SubElement(el1, 'style:table-properties', attrib={
2739 #'style:width': '17.59cm',
2740 'table:align': 'margins',
2741 'fo:margin-top': '0in',
2742 'fo:margin-bottom': '0.10in',
2743 'fo:background-color': table_style.backgroundcolor,
2744 }, nsdict=SNSD)
2745 # We use a single cell style for all cells in this table.
2746 # That's probably not correct, but seems to work.
2747 el2 = SubElement(self.automatic_styles, 'style:style', attrib={
2748 'style:name': self.rststyle(
2749 '%s.%%c%%d' % table_name, ( self.table_count, 'A', 1, )),
2750 'style:family': 'table-cell',
2751 }, nsdict=SNSD)
2752 thickness = self.settings.table_border_thickness
2753 if thickness is None:
2754 line_style1 = table_style.border
2755 else:
2756 line_style1 = '0.%03dcm solid #000000' % (thickness, )
2757 el2_1 = SubElement(el2, 'style:table-cell-properties', attrib={
2758 'fo:padding': '0.049cm',
2759 'fo:border-left': line_style1,
2760 'fo:border-right': line_style1,
2761 'fo:border-top': line_style1,
2762 'fo:border-bottom': line_style1,
2763 }, nsdict=SNSD)
2764 title = None
2765 for child in node.children:
2766 if child.tagname == 'title':
2767 title = child.astext()
2768 break
2769 if title is not None:
2770 el3 = self.append_p('table-title', title)
2771 else:
2772 pass
2773 el4 = SubElement(self.current_element, 'table:table', attrib={
2774 'table:name': self.rststyle(
2775 '%s' % table_name, ( self.table_count, )),
2776 'table:style-name': self.rststyle(
2777 '%s' % table_name, ( self.table_count, )),
2779 self.set_current_element(el4)
2780 self.current_table_style = el1
2781 self.table_width = 0
2783 def depart_table(self, node):
2784 attribkey = add_ns('style:width', nsdict=SNSD)
2785 attribval = '%dcm' % self.table_width
2786 self.current_table_style.attrib[attribkey] = attribval
2787 self.set_to_parent()
2789 def visit_tgroup(self, node):
2790 self.column_count = ord('A') - 1
2792 def depart_tgroup(self, node):
2793 pass
2795 def visit_colspec(self, node):
2796 self.column_count += 1
2797 colspec_name = self.rststyle(
2798 '%s%%d.%%s' % TABLESTYLEPREFIX,
2799 (self.table_count, chr(self.column_count), )
2801 colwidth = node['colwidth']
2802 el1 = SubElement(self.automatic_styles, 'style:style', attrib={
2803 'style:name': colspec_name,
2804 'style:family': 'table-column',
2805 }, nsdict=SNSD)
2806 el1_1 = SubElement(el1, 'style:table-column-properties', attrib={
2807 'style:column-width': '%dcm' % colwidth }, nsdict=SNSD)
2808 el2 = self.append_child('table:table-column', attrib={
2809 'table:style-name': colspec_name,
2811 self.table_width += colwidth
2813 def depart_colspec(self, node):
2814 pass
2816 def visit_thead(self, node):
2817 el = self.append_child('table:table-header-rows')
2818 self.set_current_element(el)
2819 self.in_thead = True
2820 self.paragraph_style_stack.append('Table_20_Heading')
2822 def depart_thead(self, node):
2823 self.set_to_parent()
2824 self.in_thead = False
2825 self.paragraph_style_stack.pop()
2827 def visit_row(self, node):
2828 self.column_count = ord('A') - 1
2829 el = self.append_child('table:table-row')
2830 self.set_current_element(el)
2832 def depart_row(self, node):
2833 self.set_to_parent()
2835 def visit_entry(self, node):
2836 self.column_count += 1
2837 cellspec_name = self.rststyle(
2838 '%s%%d.%%c%%d' % TABLESTYLEPREFIX,
2839 (self.table_count, 'A', 1, )
2841 attrib={
2842 'table:style-name': cellspec_name,
2843 'office:value-type': 'string',
2845 morecols = node.get('morecols', 0)
2846 if morecols > 0:
2847 attrib['table:number-columns-spanned'] = '%d' % (morecols + 1,)
2848 self.column_count += morecols
2849 morerows = node.get('morerows', 0)
2850 if morerows > 0:
2851 attrib['table:number-rows-spanned'] = '%d' % (morerows + 1,)
2852 el1 = self.append_child('table:table-cell', attrib=attrib)
2853 self.set_current_element(el1)
2855 def depart_entry(self, node):
2856 self.set_to_parent()
2858 def visit_tbody(self, node):
2859 pass
2861 def depart_tbody(self, node):
2862 pass
2864 def visit_target(self, node):
2866 # I don't know how to implement targets in ODF.
2867 # How do we create a target in oowriter? A cross-reference?
2868 if not (node.has_key('refuri') or node.has_key('refid')
2869 or node.has_key('refname')):
2870 pass
2871 else:
2872 pass
2874 def depart_target(self, node):
2875 pass
2877 def visit_title(self, node, move_ids=1, title_type='title'):
2878 if isinstance(node.parent, docutils.nodes.section):
2879 section_level = self.section_level
2880 if section_level > 7:
2881 self.document.reporter.warning(
2882 'Heading/section levels greater than 7 not supported.')
2883 self.document.reporter.warning(
2884 ' Reducing to heading level 7 for heading: "%s"' % (
2885 node.astext(), ))
2886 section_level = 7
2887 el1 = self.append_child('text:h', attrib = {
2888 'text:outline-level': '%d' % section_level,
2889 #'text:style-name': 'Heading_20_%d' % section_level,
2890 'text:style-name': self.rststyle(
2891 'heading%d', (section_level, )),
2893 self.append_pending_ids(el1)
2894 self.set_current_element(el1)
2895 elif isinstance(node.parent, docutils.nodes.document):
2896 # text = self.settings.title
2897 #else:
2898 # text = node.astext()
2899 el1 = SubElement(self.current_element, 'text:p', attrib = {
2900 'text:style-name': self.rststyle(title_type),
2902 self.append_pending_ids(el1)
2903 text = node.astext()
2904 self.title = text
2905 self.found_doc_title = True
2906 self.set_current_element(el1)
2908 def depart_title(self, node):
2909 if (isinstance(node.parent, docutils.nodes.section) or
2910 isinstance(node.parent, docutils.nodes.document)):
2911 self.set_to_parent()
2913 def visit_subtitle(self, node, move_ids=1):
2914 self.visit_title(node, move_ids, title_type='subtitle')
2916 def depart_subtitle(self, node):
2917 self.depart_title(node)
2919 def visit_title_reference(self, node):
2920 el = self.append_child('text:span', attrib={
2921 'text:style-name': self.rststyle('quotation')})
2922 el.text = self.encode(node.astext())
2923 raise nodes.SkipChildren()
2925 def depart_title_reference(self, node):
2926 pass
2928 def generate_table_of_content_entry_template(self, el1):
2929 for idx in range(1, 11):
2930 el2 = SubElement(el1,
2931 'text:table-of-content-entry-template',
2932 attrib={
2933 'text:outline-level': "%d" % (idx, ),
2934 'text:style-name': self.rststyle('contents-%d' % (idx, )),
2936 el3 = SubElement(el2, 'text:index-entry-chapter')
2937 el3 = SubElement(el2, 'text:index-entry-text')
2938 el3 = SubElement(el2, 'text:index-entry-tab-stop', attrib={
2939 'style:leader-char': ".",
2940 'style:type': "right",
2942 el3 = SubElement(el2, 'text:index-entry-page-number')
2944 def visit_topic(self, node):
2945 if 'classes' in node.attributes:
2946 if 'contents' in node.attributes['classes']:
2947 if self.settings.generate_oowriter_toc:
2948 el1 = self.append_child('text:table-of-content', attrib={
2949 'text:name': 'Table of Contents1',
2950 'text:protected': 'true',
2951 'text:style-name': 'Sect1',
2953 el2 = SubElement(el1,
2954 'text:table-of-content-source',
2955 attrib={
2956 'text:outline-level': '10',
2958 el3 =SubElement(el2, 'text:index-title-template', attrib={
2959 'text:style-name': 'Contents_20_Heading',
2961 el3.text = 'Table of Contents'
2962 self.generate_table_of_content_entry_template(el2)
2963 el4 = SubElement(el1, 'text:index-body')
2964 el5 = SubElement(el4, 'text:index-title')
2965 el6 = SubElement(el5, 'text:p', attrib={
2966 'text:style-name': self.rststyle('contents-heading'),
2968 el6.text = 'Table of Contents'
2969 self.save_current_element = self.current_element
2970 self.table_of_content_index_body = el4
2971 self.set_current_element(el4)
2972 else:
2973 el = self.append_p('horizontalline')
2974 el = self.append_p('centeredtextbody')
2975 el1 = SubElement(el, 'text:span',
2976 attrib={'text:style-name': self.rststyle('strong')})
2977 el1.text = 'Contents'
2978 self.in_table_of_contents = True
2979 elif 'abstract' in node.attributes['classes']:
2980 el = self.append_p('horizontalline')
2981 el = self.append_p('centeredtextbody')
2982 el1 = SubElement(el, 'text:span',
2983 attrib={'text:style-name': self.rststyle('strong')})
2984 el1.text = 'Abstract'
2986 def depart_topic(self, node):
2987 if 'classes' in node.attributes:
2988 if 'contents' in node.attributes['classes']:
2989 if self.settings.generate_oowriter_toc:
2990 self.update_toc_page_numbers(
2991 self.table_of_content_index_body)
2992 self.set_current_element(self.save_current_element)
2993 else:
2994 el = self.append_p('horizontalline')
2995 self.in_table_of_contents = False
2997 def update_toc_page_numbers(self, el):
2998 collection = []
2999 self.update_toc_collect(el, 0, collection)
3000 self.update_toc_add_numbers(collection)
3002 def update_toc_collect(self, el, level, collection):
3003 collection.append((level, el))
3004 level += 1
3005 for child_el in el.getchildren():
3006 if child_el.tag != 'text:index-body':
3007 self.update_toc_collect(child_el, level, collection)
3009 def update_toc_add_numbers(self, collection):
3010 for level, el1 in collection:
3011 if (el1.tag == 'text:p' and
3012 el1.text != 'Table of Contents'):
3013 el2 = SubElement(el1, 'text:tab')
3014 el2.tail = '9999'
3017 def visit_transition(self, node):
3018 el = self.append_p('horizontalline')
3020 def depart_transition(self, node):
3021 pass
3024 # Admonitions
3026 def visit_warning(self, node):
3027 self.generate_admonition(node, 'warning')
3029 def depart_warning(self, node):
3030 self.paragraph_style_stack.pop()
3032 def visit_attention(self, node):
3033 self.generate_admonition(node, 'attention')
3035 depart_attention = depart_warning
3037 def visit_caution(self, node):
3038 self.generate_admonition(node, 'caution')
3040 depart_caution = depart_warning
3042 def visit_danger(self, node):
3043 self.generate_admonition(node, 'danger')
3045 depart_danger = depart_warning
3047 def visit_error(self, node):
3048 self.generate_admonition(node, 'error')
3050 depart_error = depart_warning
3052 def visit_hint(self, node):
3053 self.generate_admonition(node, 'hint')
3055 depart_hint = depart_warning
3057 def visit_important(self, node):
3058 self.generate_admonition(node, 'important')
3060 depart_important = depart_warning
3062 def visit_note(self, node):
3063 self.generate_admonition(node, 'note')
3065 depart_note = depart_warning
3067 def visit_tip(self, node):
3068 self.generate_admonition(node, 'tip')
3070 depart_tip = depart_warning
3072 def visit_admonition(self, node):
3073 title = None
3074 for child in node.children:
3075 if child.tagname == 'title':
3076 title = child.astext()
3077 if title is None:
3078 classes1 = node.get('classes')
3079 if classes1:
3080 title = classes1[0]
3081 self.generate_admonition(node, 'generic', title)
3083 depart_admonition = depart_warning
3085 def generate_admonition(self, node, label, title=None):
3086 el1 = SubElement(self.current_element, 'text:p', attrib = {
3087 'text:style-name': self.rststyle('admon-%s-hdr', ( label, )),
3089 if title:
3090 el1.text = title
3091 else:
3092 el1.text = '%s!' % (label.capitalize(), )
3093 s1 = self.rststyle('admon-%s-body', ( label, ))
3094 self.paragraph_style_stack.append(s1)
3097 # Roles (e.g. subscript, superscript, strong, ...
3099 def visit_subscript(self, node):
3100 el = self.append_child('text:span', attrib={
3101 'text:style-name': 'rststyle-subscript',
3103 self.set_current_element(el)
3105 def depart_subscript(self, node):
3106 self.set_to_parent()
3108 def visit_superscript(self, node):
3109 el = self.append_child('text:span', attrib={
3110 'text:style-name': 'rststyle-superscript',
3112 self.set_current_element(el)
3114 def depart_superscript(self, node):
3115 self.set_to_parent()
3118 # Use an own reader to modify transformations done.
3119 class Reader(standalone.Reader):
3121 def get_transforms(self):
3122 default = standalone.Reader.get_transforms(self)
3123 if self.settings.create_links:
3124 return default
3125 return [ i
3126 for i in default
3127 if i is not references.DanglingReferences ]