odtwriter: import the PIL Image module via ``import PIL``.
[docutils.git] / docutils / writers / odf_odt / __init__.py
blob81f84b10b44db9230797e00af94e8fe87dd6feb8
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 urllib2
28 import docutils
29 from docutils import frontend, nodes, utils, writers, languages
30 from docutils.parsers import rst
31 from docutils.readers import standalone
32 from docutils.transforms import references
35 WhichElementTree = ''
36 try:
37 # 1. Try to use lxml.
38 #from lxml import etree
39 #WhichElementTree = 'lxml'
40 raise ImportError('Ignoring lxml')
41 except ImportError, e:
42 try:
43 # 2. Try to use ElementTree from the Python standard library.
44 from xml.etree import ElementTree as etree
45 WhichElementTree = 'elementtree'
46 except ImportError, e:
47 try:
48 # 3. Try to use a version of ElementTree installed as a separate
49 # product.
50 from elementtree import ElementTree as etree
51 WhichElementTree = 'elementtree'
52 except ImportError, e:
53 s1 = 'Must install either a version of Python containing ' \
54 'ElementTree (Python version >=2.5) or install ElementTree.'
55 raise ImportError(s1)
58 # Import pygments and odtwriter pygments formatters if possible.
59 try:
60 import pygments
61 import pygments.lexers
62 from pygmentsformatter import OdtPygmentsProgFormatter, \
63 OdtPygmentsLaTeXFormatter
64 except ImportError, exp:
65 pygments = None
67 try: # check for the Python Imaging Library
68 import PIL
69 except ImportError:
70 try: # sometimes PIL modules are put in PYTHONPATH's root
71 import Image
72 class PIL(object): pass # dummy wrapper
73 PIL.Image = Image
74 except ImportError:
75 PIL = None
77 ## import warnings
78 ## warnings.warn('importing IPShellEmbed', UserWarning)
79 ## from IPython.Shell import IPShellEmbed
80 ## args = ['-pdb', '-pi1', 'In <\\#>: ', '-pi2', ' .\\D.: ',
81 ## '-po', 'Out<\\#>: ', '-nosep']
82 ## ipshell = IPShellEmbed(args,
83 ## banner = 'Entering IPython. Press Ctrl-D to exit.',
84 ## exit_msg = 'Leaving Interpreter, back to program.')
88 # ElementTree does not support getparent method (lxml does).
89 # This wrapper class and the following support functions provide
90 # that support for the ability to get the parent of an element.
92 if WhichElementTree == 'elementtree':
93 class _ElementInterfaceWrapper(etree._ElementInterface):
94 def __init__(self, tag, attrib=None):
95 etree._ElementInterface.__init__(self, tag, attrib)
96 if attrib is None:
97 attrib = {}
98 self.parent = None
99 def setparent(self, parent):
100 self.parent = parent
101 def getparent(self):
102 return self.parent
106 # Constants and globals
108 SPACES_PATTERN = re.compile(r'( +)')
109 TABS_PATTERN = re.compile(r'(\t+)')
110 FILL_PAT1 = re.compile(r'^ +')
111 FILL_PAT2 = re.compile(r' {2,}')
113 TABLESTYLEPREFIX = 'rststyle-table-'
114 TABLENAMEDEFAULT = '%s0' % TABLESTYLEPREFIX
115 TABLEPROPERTYNAMES = ('border', 'border-top', 'border-left',
116 'border-right', 'border-bottom', )
118 GENERATOR_DESC = 'Docutils.org/odf_odt'
120 NAME_SPACE_1 = 'urn:oasis:names:tc:opendocument:xmlns:office:1.0'
122 CONTENT_NAMESPACE_DICT = CNSD = {
123 # 'office:version': '1.0',
124 'chart': 'urn:oasis:names:tc:opendocument:xmlns:chart:1.0',
125 'dc': 'http://purl.org/dc/elements/1.1/',
126 'dom': 'http://www.w3.org/2001/xml-events',
127 'dr3d': 'urn:oasis:names:tc:opendocument:xmlns:dr3d:1.0',
128 'draw': 'urn:oasis:names:tc:opendocument:xmlns:drawing:1.0',
129 'fo': 'urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0',
130 'form': 'urn:oasis:names:tc:opendocument:xmlns:form:1.0',
131 'math': 'http://www.w3.org/1998/Math/MathML',
132 'meta': 'urn:oasis:names:tc:opendocument:xmlns:meta:1.0',
133 'number': 'urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0',
134 'office': NAME_SPACE_1,
135 'ooo': 'http://openoffice.org/2004/office',
136 'oooc': 'http://openoffice.org/2004/calc',
137 'ooow': 'http://openoffice.org/2004/writer',
138 'presentation': 'urn:oasis:names:tc:opendocument:xmlns:presentation:1.0',
140 'script': 'urn:oasis:names:tc:opendocument:xmlns:script:1.0',
141 'style': 'urn:oasis:names:tc:opendocument:xmlns:style:1.0',
142 'svg': 'urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0',
143 'table': 'urn:oasis:names:tc:opendocument:xmlns:table:1.0',
144 'text': 'urn:oasis:names:tc:opendocument:xmlns:text:1.0',
145 'xforms': 'http://www.w3.org/2002/xforms',
146 'xlink': 'http://www.w3.org/1999/xlink',
147 'xsd': 'http://www.w3.org/2001/XMLSchema',
148 'xsi': 'http://www.w3.org/2001/XMLSchema-instance',
151 STYLES_NAMESPACE_DICT = SNSD = {
152 # 'office:version': '1.0',
153 'chart': 'urn:oasis:names:tc:opendocument:xmlns:chart:1.0',
154 'dc': 'http://purl.org/dc/elements/1.1/',
155 'dom': 'http://www.w3.org/2001/xml-events',
156 'dr3d': 'urn:oasis:names:tc:opendocument:xmlns:dr3d:1.0',
157 'draw': 'urn:oasis:names:tc:opendocument:xmlns:drawing:1.0',
158 'fo': 'urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0',
159 'form': 'urn:oasis:names:tc:opendocument:xmlns:form:1.0',
160 'math': 'http://www.w3.org/1998/Math/MathML',
161 'meta': 'urn:oasis:names:tc:opendocument:xmlns:meta:1.0',
162 'number': 'urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0',
163 'office': NAME_SPACE_1,
164 'presentation': 'urn:oasis:names:tc:opendocument:xmlns:presentation:1.0',
165 'ooo': 'http://openoffice.org/2004/office',
166 'oooc': 'http://openoffice.org/2004/calc',
167 'ooow': 'http://openoffice.org/2004/writer',
168 'script': 'urn:oasis:names:tc:opendocument:xmlns:script:1.0',
169 'style': 'urn:oasis:names:tc:opendocument:xmlns:style:1.0',
170 'svg': 'urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0',
171 'table': 'urn:oasis:names:tc:opendocument:xmlns:table:1.0',
172 'text': 'urn:oasis:names:tc:opendocument:xmlns:text:1.0',
173 'xlink': 'http://www.w3.org/1999/xlink',
176 MANIFEST_NAMESPACE_DICT = MANNSD = {
177 'manifest': 'urn:oasis:names:tc:opendocument:xmlns:manifest:1.0',
180 META_NAMESPACE_DICT = METNSD = {
181 # 'office:version': '1.0',
182 'dc': 'http://purl.org/dc/elements/1.1/',
183 'meta': 'urn:oasis:names:tc:opendocument:xmlns:meta:1.0',
184 'office': NAME_SPACE_1,
185 'ooo': 'http://openoffice.org/2004/office',
186 'xlink': 'http://www.w3.org/1999/xlink',
190 # Attribute dictionaries for use with ElementTree (not lxml), which
191 # does not support use of nsmap parameter on Element() and SubElement().
193 CONTENT_NAMESPACE_ATTRIB = {
194 #'office:version': '1.0',
195 'xmlns:chart': 'urn:oasis:names:tc:opendocument:xmlns:chart:1.0',
196 'xmlns:dc': 'http://purl.org/dc/elements/1.1/',
197 'xmlns:dom': 'http://www.w3.org/2001/xml-events',
198 'xmlns:dr3d': 'urn:oasis:names:tc:opendocument:xmlns:dr3d:1.0',
199 'xmlns:draw': 'urn:oasis:names:tc:opendocument:xmlns:drawing:1.0',
200 'xmlns:fo': 'urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0',
201 'xmlns:form': 'urn:oasis:names:tc:opendocument:xmlns:form:1.0',
202 'xmlns:math': 'http://www.w3.org/1998/Math/MathML',
203 'xmlns:meta': 'urn:oasis:names:tc:opendocument:xmlns:meta:1.0',
204 'xmlns:number': 'urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0',
205 'xmlns:office': NAME_SPACE_1,
206 'xmlns:presentation': 'urn:oasis:names:tc:opendocument:xmlns:presentation:1.0',
207 'xmlns:ooo': 'http://openoffice.org/2004/office',
208 'xmlns:oooc': 'http://openoffice.org/2004/calc',
209 'xmlns:ooow': 'http://openoffice.org/2004/writer',
210 'xmlns:script': 'urn:oasis:names:tc:opendocument:xmlns:script:1.0',
211 'xmlns:style': 'urn:oasis:names:tc:opendocument:xmlns:style:1.0',
212 'xmlns:svg': 'urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0',
213 'xmlns:table': 'urn:oasis:names:tc:opendocument:xmlns:table:1.0',
214 'xmlns:text': 'urn:oasis:names:tc:opendocument:xmlns:text:1.0',
215 'xmlns:xforms': 'http://www.w3.org/2002/xforms',
216 'xmlns:xlink': 'http://www.w3.org/1999/xlink',
217 'xmlns:xsd': 'http://www.w3.org/2001/XMLSchema',
218 'xmlns:xsi': 'http://www.w3.org/2001/XMLSchema-instance',
221 STYLES_NAMESPACE_ATTRIB = {
222 #'office:version': '1.0',
223 'xmlns:chart': 'urn:oasis:names:tc:opendocument:xmlns:chart:1.0',
224 'xmlns:dc': 'http://purl.org/dc/elements/1.1/',
225 'xmlns:dom': 'http://www.w3.org/2001/xml-events',
226 'xmlns:dr3d': 'urn:oasis:names:tc:opendocument:xmlns:dr3d:1.0',
227 'xmlns:draw': 'urn:oasis:names:tc:opendocument:xmlns:drawing:1.0',
228 'xmlns:fo': 'urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0',
229 'xmlns:form': 'urn:oasis:names:tc:opendocument:xmlns:form:1.0',
230 'xmlns:math': 'http://www.w3.org/1998/Math/MathML',
231 'xmlns:meta': 'urn:oasis:names:tc:opendocument:xmlns:meta:1.0',
232 'xmlns:number': 'urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0',
233 'xmlns:office': NAME_SPACE_1,
234 'xmlns:presentation': 'urn:oasis:names:tc:opendocument:xmlns:presentation:1.0',
235 'xmlns:ooo': 'http://openoffice.org/2004/office',
236 'xmlns:oooc': 'http://openoffice.org/2004/calc',
237 'xmlns:ooow': 'http://openoffice.org/2004/writer',
238 'xmlns:script': 'urn:oasis:names:tc:opendocument:xmlns:script:1.0',
239 'xmlns:style': 'urn:oasis:names:tc:opendocument:xmlns:style:1.0',
240 'xmlns:svg': 'urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0',
241 'xmlns:table': 'urn:oasis:names:tc:opendocument:xmlns:table:1.0',
242 'xmlns:text': 'urn:oasis:names:tc:opendocument:xmlns:text:1.0',
243 'xmlns:xlink': 'http://www.w3.org/1999/xlink',
246 MANIFEST_NAMESPACE_ATTRIB = {
247 'xmlns:manifest': 'urn:oasis:names:tc:opendocument:xmlns:manifest:1.0',
250 META_NAMESPACE_ATTRIB = {
251 #'office:version': '1.0',
252 'xmlns:dc': 'http://purl.org/dc/elements/1.1/',
253 'xmlns:meta': 'urn:oasis:names:tc:opendocument:xmlns:meta:1.0',
254 'xmlns:office': NAME_SPACE_1,
255 'xmlns:ooo': 'http://openoffice.org/2004/office',
256 'xmlns:xlink': 'http://www.w3.org/1999/xlink',
261 # Functions
265 # ElementTree support functions.
266 # In order to be able to get the parent of elements, must use these
267 # instead of the functions with same name provided by ElementTree.
269 def Element(tag, attrib=None, nsmap=None, nsdict=CNSD):
270 if attrib is None:
271 attrib = {}
272 tag, attrib = fix_ns(tag, attrib, nsdict)
273 if WhichElementTree == 'lxml':
274 el = etree.Element(tag, attrib, nsmap=nsmap)
275 else:
276 el = _ElementInterfaceWrapper(tag, attrib)
277 return el
279 def SubElement(parent, tag, attrib=None, nsmap=None, nsdict=CNSD):
280 if attrib is None:
281 attrib = {}
282 tag, attrib = fix_ns(tag, attrib, nsdict)
283 if WhichElementTree == 'lxml':
284 el = etree.SubElement(parent, tag, attrib, nsmap=nsmap)
285 else:
286 el = _ElementInterfaceWrapper(tag, attrib)
287 parent.append(el)
288 el.setparent(parent)
289 return el
291 def fix_ns(tag, attrib, nsdict):
292 nstag = add_ns(tag, nsdict)
293 nsattrib = {}
294 for key, val in attrib.iteritems():
295 nskey = add_ns(key, nsdict)
296 nsattrib[nskey] = val
297 return nstag, nsattrib
299 def add_ns(tag, nsdict=CNSD):
300 if WhichElementTree == 'lxml':
301 nstag, name = tag.split(':')
302 ns = nsdict.get(nstag)
303 if ns is None:
304 raise RuntimeError, 'Invalid namespace prefix: %s' % nstag
305 tag = '{%s}%s' % (ns, name,)
306 return tag
308 def ToString(et):
309 outstream = StringIO.StringIO()
310 if sys.version_info >= (3, 2):
311 et.write(outstream, encoding="unicode")
312 else:
313 et.write(outstream)
314 s1 = outstream.getvalue()
315 outstream.close()
316 return s1
319 def escape_cdata(text):
320 text = text.replace("&", "&amp;")
321 text = text.replace("<", "&lt;")
322 text = text.replace(">", "&gt;")
323 ascii = ''
324 for char in text:
325 if ord(char) >= ord("\x7f"):
326 ascii += "&#x%X;" % ( ord(char), )
327 else:
328 ascii += char
329 return ascii
333 WORD_SPLIT_PAT1 = re.compile(r'\b(\w*)\b\W*')
335 def split_words(line):
336 # We need whitespace at the end of the string for our regexpr.
337 line += ' '
338 words = []
339 pos1 = 0
340 mo = WORD_SPLIT_PAT1.search(line, pos1)
341 while mo is not None:
342 word = mo.groups()[0]
343 words.append(word)
344 pos1 = mo.end()
345 mo = WORD_SPLIT_PAT1.search(line, pos1)
346 return words
350 # Classes
354 class TableStyle(object):
355 def __init__(self, border=None, backgroundcolor=None):
356 self.border = border
357 self.backgroundcolor = backgroundcolor
358 def get_border_(self):
359 return self.border_
360 def set_border_(self, border):
361 self.border_ = border
362 border = property(get_border_, set_border_)
363 def get_backgroundcolor_(self):
364 return self.backgroundcolor_
365 def set_backgroundcolor_(self, backgroundcolor):
366 self.backgroundcolor_ = backgroundcolor
367 backgroundcolor = property(get_backgroundcolor_, set_backgroundcolor_)
369 BUILTIN_DEFAULT_TABLE_STYLE = TableStyle(
370 border = '0.0007in solid #000000')
373 # Information about the indentation level for lists nested inside
374 # other contexts, e.g. dictionary lists.
375 class ListLevel(object):
376 def __init__(self, level, sibling_level=True, nested_level=True):
377 self.level = level
378 self.sibling_level = sibling_level
379 self.nested_level = nested_level
380 def set_sibling(self, sibling_level): self.sibling_level = sibling_level
381 def get_sibling(self): return self.sibling_level
382 def set_nested(self, nested_level): self.nested_level = nested_level
383 def get_nested(self): return self.nested_level
384 def set_level(self, level): self.level = level
385 def get_level(self): return self.level
388 class Writer(writers.Writer):
390 MIME_TYPE = 'application/vnd.oasis.opendocument.text'
391 EXTENSION = '.odt'
393 supported = ('odt', )
394 """Formats this writer supports."""
396 default_stylesheet = 'styles' + EXTENSION
398 default_stylesheet_path = utils.relative_path(
399 os.path.join(os.getcwd(), 'dummy'),
400 os.path.join(os.path.dirname(__file__), default_stylesheet))
402 default_template = 'template.txt'
404 default_template_path = utils.relative_path(
405 os.path.join(os.getcwd(), 'dummy'),
406 os.path.join(os.path.dirname(__file__), default_template))
408 settings_spec = (
409 'ODF-Specific Options',
410 None,
412 ('Specify a stylesheet. '
413 'Default: "%s"' % default_stylesheet_path,
414 ['--stylesheet'],
416 'default': default_stylesheet_path,
417 'dest': 'stylesheet'
419 ('Specify a configuration/mapping file relative to the '
420 'current working '
421 'directory for additional ODF options. '
422 'In particular, this file may contain a section named '
423 '"Formats" that maps default style names to '
424 'names to be used in the resulting output file allowing for '
425 'adhering to external standards. '
426 'For more info and the format of the configuration/mapping file, '
427 'see the odtwriter doc.',
428 ['--odf-config-file'],
429 {'metavar': '<file>'}),
430 ('Obfuscate email addresses to confuse harvesters while still '
431 'keeping email links usable with standards-compliant browsers.',
432 ['--cloak-email-addresses'],
433 {'default': False,
434 'action': 'store_true',
435 'dest': 'cloak_email_addresses',
436 'validator': frontend.validate_boolean}),
437 ('Do not obfuscate email addresses.',
438 ['--no-cloak-email-addresses'],
439 {'default': False,
440 'action': 'store_false',
441 'dest': 'cloak_email_addresses',
442 'validator': frontend.validate_boolean}),
443 ('Specify the thickness of table borders in thousands of a cm. '
444 'Default is 35.',
445 ['--table-border-thickness'],
446 {'default': None,
447 'validator': frontend.validate_nonnegative_int}),
448 ('Add syntax highlighting in literal code blocks.',
449 ['--add-syntax-highlighting'],
450 {'default': False,
451 'action': 'store_true',
452 'dest': 'add_syntax_highlighting',
453 'validator': frontend.validate_boolean}),
454 ('Do not add syntax highlighting in literal code blocks. (default)',
455 ['--no-syntax-highlighting'],
456 {'default': False,
457 'action': 'store_false',
458 'dest': 'add_syntax_highlighting',
459 'validator': frontend.validate_boolean}),
460 ('Create sections for headers. (default)',
461 ['--create-sections'],
462 {'default': True,
463 'action': 'store_true',
464 'dest': 'create_sections',
465 'validator': frontend.validate_boolean}),
466 ('Do not create sections for headers.',
467 ['--no-sections'],
468 {'default': True,
469 'action': 'store_false',
470 'dest': 'create_sections',
471 'validator': frontend.validate_boolean}),
472 ('Create links.',
473 ['--create-links'],
474 {'default': False,
475 'action': 'store_true',
476 'dest': 'create_links',
477 'validator': frontend.validate_boolean}),
478 ('Do not create links. (default)',
479 ['--no-links'],
480 {'default': False,
481 'action': 'store_false',
482 'dest': 'create_links',
483 'validator': frontend.validate_boolean}),
484 ('Generate endnotes at end of document, not footnotes '
485 'at bottom of page.',
486 ['--endnotes-end-doc'],
487 {'default': False,
488 'action': 'store_true',
489 'dest': 'endnotes_end_doc',
490 'validator': frontend.validate_boolean}),
491 ('Generate footnotes at bottom of page, not endnotes '
492 'at end of document. (default)',
493 ['--no-endnotes-end-doc'],
494 {'default': False,
495 'action': 'store_false',
496 'dest': 'endnotes_end_doc',
497 'validator': frontend.validate_boolean}),
498 ('Generate a bullet list table of contents, not '
499 'an ODF/oowriter table of contents.',
500 ['--generate-list-toc'],
501 {'default': True,
502 'action': 'store_false',
503 'dest': 'generate_oowriter_toc',
504 'validator': frontend.validate_boolean}),
505 ('Generate an ODF/oowriter table of contents, not '
506 'a bullet list. (default)',
507 ['--generate-oowriter-toc'],
508 {'default': True,
509 'action': 'store_true',
510 'dest': 'generate_oowriter_toc',
511 'validator': frontend.validate_boolean}),
512 ('Specify the contents of an custom header line. '
513 'See odf_odt writer documentation for details '
514 'about special field character sequences.',
515 ['--custom-odt-header'],
516 { 'default': '',
517 'dest': 'custom_header',
519 ('Specify the contents of an custom footer line. '
520 'See odf_odt writer documentation for details '
521 'about special field character sequences.',
522 ['--custom-odt-footer'],
523 { 'default': '',
524 'dest': 'custom_footer',
529 settings_defaults = {
530 'output_encoding_error_handler': 'xmlcharrefreplace',
533 relative_path_settings = (
534 'stylesheet_path',
537 config_section = 'opendocument odf writer'
538 config_section_dependencies = (
539 'writers',
542 def __init__(self):
543 writers.Writer.__init__(self)
544 self.translator_class = ODFTranslator
546 def translate(self):
547 self.settings = self.document.settings
548 self.visitor = self.translator_class(self.document)
549 self.visitor.retrieve_styles(self.EXTENSION)
550 self.document.walkabout(self.visitor)
551 self.visitor.add_doc_title()
552 self.assemble_my_parts()
553 self.output = self.parts['whole']
555 def assemble_my_parts(self):
556 """Assemble the `self.parts` dictionary. Extend in subclasses.
558 writers.Writer.assemble_parts(self)
559 f = tempfile.NamedTemporaryFile()
560 zfile = zipfile.ZipFile(f, 'w', zipfile.ZIP_DEFLATED)
561 self.write_zip_str(zfile, 'mimetype', self.MIME_TYPE,
562 compress_type=zipfile.ZIP_STORED)
563 content = self.visitor.content_astext()
564 self.write_zip_str(zfile, 'content.xml', content)
565 s1 = self.create_manifest()
566 self.write_zip_str(zfile, 'META-INF/manifest.xml', s1)
567 s1 = self.create_meta()
568 self.write_zip_str(zfile, 'meta.xml', s1)
569 s1 = self.get_stylesheet()
570 self.write_zip_str(zfile, 'styles.xml', s1)
571 self.store_embedded_files(zfile)
572 self.copy_from_stylesheet(zfile)
573 zfile.close()
574 f.seek(0)
575 whole = f.read()
576 f.close()
577 self.parts['whole'] = whole
578 self.parts['encoding'] = self.document.settings.output_encoding
579 self.parts['version'] = docutils.__version__
581 def write_zip_str(self, zfile, name, bytes, compress_type=zipfile.ZIP_DEFLATED):
582 localtime = time.localtime(time.time())
583 zinfo = zipfile.ZipInfo(name, localtime)
584 # Add some standard UNIX file access permissions (-rw-r--r--).
585 zinfo.external_attr = (0x81a4 & 0xFFFF) << 16L
586 zinfo.compress_type = compress_type
587 zfile.writestr(zinfo, bytes)
589 def store_embedded_files(self, zfile):
590 embedded_files = self.visitor.get_embedded_file_list()
591 for source, destination in embedded_files:
592 if source is None:
593 continue
594 try:
595 # encode/decode
596 destination1 = destination.decode('latin-1').encode('utf-8')
597 zfile.write(source, destination1)
598 except OSError, e:
599 self.document.reporter.warning(
600 "Can't open file %s." % (source, ))
602 def get_settings(self):
604 modeled after get_stylesheet
606 stylespath = self.settings.stylesheet
607 zfile = zipfile.ZipFile(stylespath, 'r')
608 s1 = zfile.read('settings.xml')
609 zfile.close()
610 return s1
612 def get_stylesheet(self):
613 """Get the stylesheet from the visitor.
614 Ask the visitor to setup the page.
616 s1 = self.visitor.setup_page()
617 return s1
619 def copy_from_stylesheet(self, outzipfile):
620 """Copy images, settings, etc from the stylesheet doc into target doc.
622 stylespath = self.settings.stylesheet
623 inzipfile = zipfile.ZipFile(stylespath, 'r')
624 # Copy the styles.
625 s1 = inzipfile.read('settings.xml')
626 self.write_zip_str(outzipfile, 'settings.xml', s1)
627 # Copy the images.
628 namelist = inzipfile.namelist()
629 for name in namelist:
630 if name.startswith('Pictures/'):
631 imageobj = inzipfile.read(name)
632 outzipfile.writestr(name, imageobj)
633 inzipfile.close()
635 def assemble_parts(self):
636 pass
638 def create_manifest(self):
639 if WhichElementTree == 'lxml':
640 root = Element('manifest:manifest',
641 nsmap=MANIFEST_NAMESPACE_DICT,
642 nsdict=MANIFEST_NAMESPACE_DICT,
644 else:
645 root = Element('manifest:manifest',
646 attrib=MANIFEST_NAMESPACE_ATTRIB,
647 nsdict=MANIFEST_NAMESPACE_DICT,
649 doc = etree.ElementTree(root)
650 SubElement(root, 'manifest:file-entry', attrib={
651 'manifest:media-type': self.MIME_TYPE,
652 'manifest:full-path': '/',
653 }, nsdict=MANNSD)
654 SubElement(root, 'manifest:file-entry', attrib={
655 'manifest:media-type': 'text/xml',
656 'manifest:full-path': 'content.xml',
657 }, nsdict=MANNSD)
658 SubElement(root, 'manifest:file-entry', attrib={
659 'manifest:media-type': 'text/xml',
660 'manifest:full-path': 'styles.xml',
661 }, nsdict=MANNSD)
662 SubElement(root, 'manifest:file-entry', attrib={
663 'manifest:media-type': 'text/xml',
664 'manifest:full-path': 'settings.xml',
665 }, nsdict=MANNSD)
666 SubElement(root, 'manifest:file-entry', attrib={
667 'manifest:media-type': 'text/xml',
668 'manifest:full-path': 'meta.xml',
669 }, nsdict=MANNSD)
670 s1 = ToString(doc)
671 doc = minidom.parseString(s1)
672 s1 = doc.toprettyxml(' ')
673 return s1
675 def create_meta(self):
676 if WhichElementTree == 'lxml':
677 root = Element('office:document-meta',
678 nsmap=META_NAMESPACE_DICT,
679 nsdict=META_NAMESPACE_DICT,
681 else:
682 root = Element('office:document-meta',
683 attrib=META_NAMESPACE_ATTRIB,
684 nsdict=META_NAMESPACE_DICT,
686 doc = etree.ElementTree(root)
687 root = SubElement(root, 'office:meta', nsdict=METNSD)
688 el1 = SubElement(root, 'meta:generator', nsdict=METNSD)
689 el1.text = 'Docutils/rst2odf.py/%s' % (VERSION, )
690 s1 = os.environ.get('USER', '')
691 el1 = SubElement(root, 'meta:initial-creator', nsdict=METNSD)
692 el1.text = s1
693 s2 = time.strftime('%Y-%m-%dT%H:%M:%S', time.localtime())
694 el1 = SubElement(root, 'meta:creation-date', nsdict=METNSD)
695 el1.text = s2
696 el1 = SubElement(root, 'dc:creator', nsdict=METNSD)
697 el1.text = s1
698 el1 = SubElement(root, 'dc:date', nsdict=METNSD)
699 el1.text = s2
700 el1 = SubElement(root, 'dc:language', nsdict=METNSD)
701 el1.text = 'en-US'
702 el1 = SubElement(root, 'meta:editing-cycles', nsdict=METNSD)
703 el1.text = '1'
704 el1 = SubElement(root, 'meta:editing-duration', nsdict=METNSD)
705 el1.text = 'PT00M01S'
706 title = self.visitor.get_title()
707 el1 = SubElement(root, 'dc:title', nsdict=METNSD)
708 if title:
709 el1.text = title
710 else:
711 el1.text = '[no title]'
712 meta_dict = self.visitor.get_meta_dict()
713 keywordstr = meta_dict.get('keywords')
714 if keywordstr is not None:
715 keywords = split_words(keywordstr)
716 for keyword in keywords:
717 el1 = SubElement(root, 'meta:keyword', nsdict=METNSD)
718 el1.text = keyword
719 description = meta_dict.get('description')
720 if description is not None:
721 el1 = SubElement(root, 'dc:description', nsdict=METNSD)
722 el1.text = description
723 s1 = ToString(doc)
724 #doc = minidom.parseString(s1)
725 #s1 = doc.toprettyxml(' ')
726 return s1
728 # class ODFTranslator(nodes.SparseNodeVisitor):
730 class ODFTranslator(nodes.GenericNodeVisitor):
732 used_styles = (
733 'attribution', 'blockindent', 'blockquote', 'blockquote-bulletitem',
734 'blockquote-bulletlist', 'blockquote-enumitem', 'blockquote-enumlist',
735 'bulletitem', 'bulletlist',
736 'caption', 'legend',
737 'centeredtextbody', 'codeblock', 'codeblock-indented',
738 'codeblock-classname', 'codeblock-comment', 'codeblock-functionname',
739 'codeblock-keyword', 'codeblock-name', 'codeblock-number',
740 'codeblock-operator', 'codeblock-string', 'emphasis', 'enumitem',
741 'enumlist', 'epigraph', 'epigraph-bulletitem', 'epigraph-bulletlist',
742 'epigraph-enumitem', 'epigraph-enumlist', 'footer',
743 'footnote', 'citation',
744 'header', 'highlights', 'highlights-bulletitem',
745 'highlights-bulletlist', 'highlights-enumitem', 'highlights-enumlist',
746 'horizontalline', 'inlineliteral', 'quotation', 'rubric',
747 'strong', 'table-title', 'textbody', 'tocbulletlist', 'tocenumlist',
748 'title',
749 'subtitle',
750 'heading1',
751 'heading2',
752 'heading3',
753 'heading4',
754 'heading5',
755 'heading6',
756 'heading7',
757 'admon-attention-hdr',
758 'admon-attention-body',
759 'admon-caution-hdr',
760 'admon-caution-body',
761 'admon-danger-hdr',
762 'admon-danger-body',
763 'admon-error-hdr',
764 'admon-error-body',
765 'admon-generic-hdr',
766 'admon-generic-body',
767 'admon-hint-hdr',
768 'admon-hint-body',
769 'admon-important-hdr',
770 'admon-important-body',
771 'admon-note-hdr',
772 'admon-note-body',
773 'admon-tip-hdr',
774 'admon-tip-body',
775 'admon-warning-hdr',
776 'admon-warning-body',
777 'tableoption',
778 'tableoption.%c', 'tableoption.%c%d', 'Table%d', 'Table%d.%c',
779 'Table%d.%c%d',
780 'lineblock1',
781 'lineblock2',
782 'lineblock3',
783 'lineblock4',
784 'lineblock5',
785 'lineblock6',
786 'image', 'figureframe',
789 def __init__(self, document):
790 #nodes.SparseNodeVisitor.__init__(self, document)
791 nodes.GenericNodeVisitor.__init__(self, document)
792 self.settings = document.settings
793 lcode = self.settings.language_code
794 self.language = languages.get_language(lcode, document.reporter)
795 self.format_map = { }
796 if self.settings.odf_config_file:
797 from ConfigParser import ConfigParser
799 parser = ConfigParser()
800 parser.read(self.settings.odf_config_file)
801 for rststyle, format in parser.items("Formats"):
802 if rststyle not in self.used_styles:
803 self.document.reporter.warning(
804 'Style "%s" is not a style used by odtwriter.' % (
805 rststyle, ))
806 self.format_map[rststyle] = format.decode('utf-8')
807 self.section_level = 0
808 self.section_count = 0
809 # Create ElementTree content and styles documents.
810 if WhichElementTree == 'lxml':
811 root = Element(
812 'office:document-content',
813 nsmap=CONTENT_NAMESPACE_DICT,
815 else:
816 root = Element(
817 'office:document-content',
818 attrib=CONTENT_NAMESPACE_ATTRIB,
820 self.content_tree = etree.ElementTree(element=root)
821 self.current_element = root
822 SubElement(root, 'office:scripts')
823 SubElement(root, 'office:font-face-decls')
824 el = SubElement(root, 'office:automatic-styles')
825 self.automatic_styles = el
826 el = SubElement(root, 'office:body')
827 el = self.generate_content_element(el)
828 self.current_element = el
829 self.body_text_element = el
830 self.paragraph_style_stack = [self.rststyle('textbody'), ]
831 self.list_style_stack = []
832 self.table_count = 0
833 self.column_count = ord('A') - 1
834 self.trace_level = -1
835 self.optiontablestyles_generated = False
836 self.field_name = None
837 self.field_element = None
838 self.title = None
839 self.image_count = 0
840 self.image_style_count = 0
841 self.image_dict = {}
842 self.embedded_file_list = []
843 self.syntaxhighlighting = 1
844 self.syntaxhighlight_lexer = 'python'
845 self.header_content = []
846 self.footer_content = []
847 self.in_header = False
848 self.in_footer = False
849 self.blockstyle = ''
850 self.in_table_of_contents = False
851 self.table_of_content_index_body = None
852 self.list_level = 0
853 self.def_list_level = 0
854 self.footnote_ref_dict = {}
855 self.footnote_list = []
856 self.footnote_chars_idx = 0
857 self.footnote_level = 0
858 self.pending_ids = [ ]
859 self.in_paragraph = False
860 self.found_doc_title = False
861 self.bumped_list_level_stack = []
862 self.meta_dict = {}
863 self.line_block_level = 0
864 self.line_indent_level = 0
865 self.citation_id = None
866 self.style_index = 0 # use to form unique style names
867 self.str_stylesheet = ''
868 self.str_stylesheetcontent = ''
869 self.dom_stylesheet = None
870 self.table_styles = None
871 self.in_citation = False
874 def get_str_stylesheet(self):
875 return self.str_stylesheet
877 def retrieve_styles(self, extension):
878 """Retrieve the stylesheet from either a .xml file or from
879 a .odt (zip) file. Return the content as a string.
881 s2 = None
882 stylespath = self.settings.stylesheet
883 ext = os.path.splitext(stylespath)[1]
884 if ext == '.xml':
885 stylesfile = open(stylespath, 'r')
886 s1 = stylesfile.read()
887 stylesfile.close()
888 elif ext == extension:
889 zfile = zipfile.ZipFile(stylespath, 'r')
890 s1 = zfile.read('styles.xml')
891 s2 = zfile.read('content.xml')
892 zfile.close()
893 else:
894 raise RuntimeError, 'stylesheet path (%s) must be %s or .xml file' %(stylespath, extension)
895 self.str_stylesheet = s1
896 self.str_stylesheetcontent = s2
897 self.dom_stylesheet = etree.fromstring(self.str_stylesheet)
898 self.dom_stylesheetcontent = etree.fromstring(self.str_stylesheetcontent)
899 self.table_styles = self.extract_table_styles(s2)
901 def extract_table_styles(self, styles_str):
902 root = etree.fromstring(styles_str)
903 table_styles = {}
904 auto_styles = root.find(
905 '{%s}automatic-styles' % (CNSD['office'], ))
906 for stylenode in auto_styles:
907 name = stylenode.get('{%s}name' % (CNSD['style'], ))
908 tablename = name.split('.')[0]
909 family = stylenode.get('{%s}family' % (CNSD['style'], ))
910 if name.startswith(TABLESTYLEPREFIX):
911 tablestyle = table_styles.get(tablename)
912 if tablestyle is None:
913 tablestyle = TableStyle()
914 table_styles[tablename] = tablestyle
915 if family == 'table':
916 properties = stylenode.find(
917 '{%s}table-properties' % (CNSD['style'], ))
918 property = properties.get('{%s}%s' % (CNSD['fo'],
919 'background-color', ))
920 if property is not None and property != 'none':
921 tablestyle.backgroundcolor = property
922 elif family == 'table-cell':
923 properties = stylenode.find(
924 '{%s}table-cell-properties' % (CNSD['style'], ))
925 if properties is not None:
926 border = self.get_property(properties)
927 if border is not None:
928 tablestyle.border = border
929 return table_styles
931 def get_property(self, stylenode):
932 border = None
933 for propertyname in TABLEPROPERTYNAMES:
934 border = stylenode.get('{%s}%s' % (CNSD['fo'], propertyname, ))
935 if border is not None and border != 'none':
936 return border
937 return border
939 def add_doc_title(self):
940 text = self.settings.title
941 if text:
942 self.title = text
943 if not self.found_doc_title:
944 el = Element('text:p', attrib = {
945 'text:style-name': self.rststyle('title'),
947 el.text = text
948 self.body_text_element.insert(0, el)
950 def rststyle(self, name, parameters=( )):
952 Returns the style name to use for the given style.
954 If `parameters` is given `name` must contain a matching number of ``%`` and
955 is used as a format expression with `parameters` as the value.
957 name1 = name % parameters
958 stylename = self.format_map.get(name1, 'rststyle-%s' % name1)
959 return stylename
961 def generate_content_element(self, root):
962 return SubElement(root, 'office:text')
964 def setup_page(self):
965 self.setup_paper(self.dom_stylesheet)
966 if (len(self.header_content) > 0 or len(self.footer_content) > 0 or
967 self.settings.custom_header or self.settings.custom_footer):
968 self.add_header_footer(self.dom_stylesheet)
969 new_content = etree.tostring(self.dom_stylesheet)
970 return new_content
972 def setup_paper(self, root_el):
973 try:
974 fin = os.popen("paperconf -s 2> /dev/null")
975 w, h = map(float, fin.read().split())
976 fin.close()
977 except:
978 w, h = 612, 792 # default to Letter
979 def walk(el):
980 if el.tag == "{%s}page-layout-properties" % SNSD["style"] and \
981 not el.attrib.has_key("{%s}page-width" % SNSD["fo"]):
982 el.attrib["{%s}page-width" % SNSD["fo"]] = "%.3fpt" % w
983 el.attrib["{%s}page-height" % SNSD["fo"]] = "%.3fpt" % h
984 el.attrib["{%s}margin-left" % SNSD["fo"]] = \
985 el.attrib["{%s}margin-right" % SNSD["fo"]] = \
986 "%.3fpt" % (.1 * w)
987 el.attrib["{%s}margin-top" % SNSD["fo"]] = \
988 el.attrib["{%s}margin-bottom" % SNSD["fo"]] = \
989 "%.3fpt" % (.1 * h)
990 else:
991 for subel in el.getchildren(): walk(subel)
992 walk(root_el)
994 def add_header_footer(self, root_el):
995 automatic_styles = root_el.find(
996 '{%s}automatic-styles' % SNSD['office'])
997 path = '{%s}master-styles' % (NAME_SPACE_1, )
998 master_el = root_el.find(path)
999 if master_el is None:
1000 return
1001 path = '{%s}master-page' % (SNSD['style'], )
1002 master_el = master_el.find(path)
1003 if master_el is None:
1004 return
1005 el1 = master_el
1006 if self.header_content or self.settings.custom_header:
1007 if WhichElementTree == 'lxml':
1008 el2 = SubElement(el1, 'style:header', nsdict=SNSD)
1009 else:
1010 el2 = SubElement(el1, 'style:header',
1011 attrib=STYLES_NAMESPACE_ATTRIB,
1012 nsdict=STYLES_NAMESPACE_DICT,
1014 for el in self.header_content:
1015 attrkey = add_ns('text:style-name', nsdict=SNSD)
1016 el.attrib[attrkey] = self.rststyle('header')
1017 el2.append(el)
1018 if self.settings.custom_header:
1019 elcustom = self.create_custom_headfoot(el2,
1020 self.settings.custom_header, 'header', automatic_styles)
1021 if self.footer_content or self.settings.custom_footer:
1022 if WhichElementTree == 'lxml':
1023 el2 = SubElement(el1, 'style:footer', nsdict=SNSD)
1024 else:
1025 el2 = SubElement(el1, 'style:footer',
1026 attrib=STYLES_NAMESPACE_ATTRIB,
1027 nsdict=STYLES_NAMESPACE_DICT,
1029 for el in self.footer_content:
1030 attrkey = add_ns('text:style-name', nsdict=SNSD)
1031 el.attrib[attrkey] = self.rststyle('footer')
1032 el2.append(el)
1033 if self.settings.custom_footer:
1034 elcustom = self.create_custom_headfoot(el2,
1035 self.settings.custom_footer, 'footer', automatic_styles)
1037 code_none, code_field, code_text = range(3)
1038 field_pat = re.compile(r'%(..?)%')
1040 def create_custom_headfoot(self, parent, text, style_name, automatic_styles):
1041 parent = SubElement(parent, 'text:p', attrib={
1042 'text:style-name': self.rststyle(style_name),
1044 current_element = None
1045 field_iter = self.split_field_specifiers_iter(text)
1046 for item in field_iter:
1047 if item[0] == ODFTranslator.code_field:
1048 if item[1] not in ('p', 'P',
1049 't1', 't2', 't3', 't4',
1050 'd1', 'd2', 'd3', 'd4', 'd5',
1051 's', 't', 'a'):
1052 msg = 'bad field spec: %%%s%%' % (item[1], )
1053 raise RuntimeError, msg
1054 el1 = self.make_field_element(parent,
1055 item[1], style_name, automatic_styles)
1056 if el1 is None:
1057 msg = 'bad field spec: %%%s%%' % (item[1], )
1058 raise RuntimeError, msg
1059 else:
1060 current_element = el1
1061 else:
1062 if current_element is None:
1063 parent.text = item[1]
1064 else:
1065 current_element.tail = item[1]
1067 def make_field_element(self, parent, text, style_name, automatic_styles):
1068 if text == 'p':
1069 el1 = SubElement(parent, 'text:page-number', attrib={
1070 #'text:style-name': self.rststyle(style_name),
1071 'text:select-page': 'current',
1073 elif text == 'P':
1074 el1 = SubElement(parent, 'text:page-count', attrib={
1075 #'text:style-name': self.rststyle(style_name),
1077 elif text == 't1':
1078 self.style_index += 1
1079 el1 = SubElement(parent, 'text:time', attrib={
1080 'text:style-name': self.rststyle(style_name),
1081 'text:fixed': 'true',
1082 'style:data-style-name': 'rst-time-style-%d' % self.style_index,
1084 el2 = SubElement(automatic_styles, 'number:time-style', attrib={
1085 'style:name': 'rst-time-style-%d' % self.style_index,
1086 'xmlns:number': SNSD['number'],
1087 'xmlns:style': SNSD['style'],
1089 el3 = SubElement(el2, 'number:hours', attrib={
1090 'number:style': 'long',
1092 el3 = SubElement(el2, 'number:text')
1093 el3.text = ':'
1094 el3 = SubElement(el2, 'number:minutes', attrib={
1095 'number:style': 'long',
1097 elif text == 't2':
1098 self.style_index += 1
1099 el1 = SubElement(parent, 'text:time', attrib={
1100 'text:style-name': self.rststyle(style_name),
1101 'text:fixed': 'true',
1102 'style:data-style-name': 'rst-time-style-%d' % self.style_index,
1104 el2 = SubElement(automatic_styles, 'number:time-style', attrib={
1105 'style:name': 'rst-time-style-%d' % self.style_index,
1106 'xmlns:number': SNSD['number'],
1107 'xmlns:style': SNSD['style'],
1109 el3 = SubElement(el2, 'number:hours', attrib={
1110 'number:style': 'long',
1112 el3 = SubElement(el2, 'number:text')
1113 el3.text = ':'
1114 el3 = SubElement(el2, 'number:minutes', attrib={
1115 'number:style': 'long',
1117 el3 = SubElement(el2, 'number:text')
1118 el3.text = ':'
1119 el3 = SubElement(el2, 'number:seconds', attrib={
1120 'number:style': 'long',
1122 elif text == 't3':
1123 self.style_index += 1
1124 el1 = SubElement(parent, 'text:time', attrib={
1125 'text:style-name': self.rststyle(style_name),
1126 'text:fixed': 'true',
1127 'style:data-style-name': 'rst-time-style-%d' % self.style_index,
1129 el2 = SubElement(automatic_styles, 'number:time-style', attrib={
1130 'style:name': 'rst-time-style-%d' % self.style_index,
1131 'xmlns:number': SNSD['number'],
1132 'xmlns:style': SNSD['style'],
1134 el3 = SubElement(el2, 'number:hours', attrib={
1135 'number:style': 'long',
1137 el3 = SubElement(el2, 'number:text')
1138 el3.text = ':'
1139 el3 = SubElement(el2, 'number:minutes', attrib={
1140 'number:style': 'long',
1142 el3 = SubElement(el2, 'number:text')
1143 el3.text = ' '
1144 el3 = SubElement(el2, 'number:am-pm')
1145 elif text == 't4':
1146 self.style_index += 1
1147 el1 = SubElement(parent, 'text:time', attrib={
1148 'text:style-name': self.rststyle(style_name),
1149 'text:fixed': 'true',
1150 'style:data-style-name': 'rst-time-style-%d' % self.style_index,
1152 el2 = SubElement(automatic_styles, 'number:time-style', attrib={
1153 'style:name': 'rst-time-style-%d' % self.style_index,
1154 'xmlns:number': SNSD['number'],
1155 'xmlns:style': SNSD['style'],
1157 el3 = SubElement(el2, 'number:hours', attrib={
1158 'number:style': 'long',
1160 el3 = SubElement(el2, 'number:text')
1161 el3.text = ':'
1162 el3 = SubElement(el2, 'number:minutes', attrib={
1163 'number:style': 'long',
1165 el3 = SubElement(el2, 'number:text')
1166 el3.text = ':'
1167 el3 = SubElement(el2, 'number:seconds', attrib={
1168 'number:style': 'long',
1170 el3 = SubElement(el2, 'number:text')
1171 el3.text = ' '
1172 el3 = SubElement(el2, 'number:am-pm')
1173 elif text == 'd1':
1174 self.style_index += 1
1175 el1 = SubElement(parent, 'text:date', attrib={
1176 'text:style-name': self.rststyle(style_name),
1177 'style:data-style-name': 'rst-date-style-%d' % self.style_index,
1179 el2 = SubElement(automatic_styles, 'number:date-style', attrib={
1180 'style:name': 'rst-date-style-%d' % self.style_index,
1181 'number:automatic-order': 'true',
1182 'xmlns:number': SNSD['number'],
1183 'xmlns:style': SNSD['style'],
1185 el3 = SubElement(el2, 'number:month', attrib={
1186 'number:style': 'long',
1188 el3 = SubElement(el2, 'number:text')
1189 el3.text = '/'
1190 el3 = SubElement(el2, 'number:day', attrib={
1191 'number:style': 'long',
1193 el3 = SubElement(el2, 'number:text')
1194 el3.text = '/'
1195 el3 = SubElement(el2, 'number:year')
1196 elif text == 'd2':
1197 self.style_index += 1
1198 el1 = SubElement(parent, 'text:date', attrib={
1199 'text:style-name': self.rststyle(style_name),
1200 'style:data-style-name': 'rst-date-style-%d' % self.style_index,
1202 el2 = SubElement(automatic_styles, 'number:date-style', attrib={
1203 'style:name': 'rst-date-style-%d' % self.style_index,
1204 'number:automatic-order': 'true',
1205 'xmlns:number': SNSD['number'],
1206 'xmlns:style': SNSD['style'],
1208 el3 = SubElement(el2, 'number:month', attrib={
1209 'number:style': 'long',
1211 el3 = SubElement(el2, 'number:text')
1212 el3.text = '/'
1213 el3 = SubElement(el2, 'number:day', attrib={
1214 'number:style': 'long',
1216 el3 = SubElement(el2, 'number:text')
1217 el3.text = '/'
1218 el3 = SubElement(el2, 'number:year', attrib={
1219 'number:style': 'long',
1221 elif text == 'd3':
1222 self.style_index += 1
1223 el1 = SubElement(parent, 'text:date', attrib={
1224 'text:style-name': self.rststyle(style_name),
1225 'style:data-style-name': 'rst-date-style-%d' % self.style_index,
1227 el2 = SubElement(automatic_styles, 'number:date-style', attrib={
1228 'style:name': 'rst-date-style-%d' % self.style_index,
1229 'number:automatic-order': 'true',
1230 'xmlns:number': SNSD['number'],
1231 'xmlns:style': SNSD['style'],
1233 el3 = SubElement(el2, 'number:month', attrib={
1234 'number:textual': 'true',
1236 el3 = SubElement(el2, 'number:text')
1237 el3.text = ' '
1238 el3 = SubElement(el2, 'number:day', attrib={
1240 el3 = SubElement(el2, 'number:text')
1241 el3.text = ', '
1242 el3 = SubElement(el2, 'number:year', attrib={
1243 'number:style': 'long',
1245 elif text == 'd4':
1246 self.style_index += 1
1247 el1 = SubElement(parent, 'text:date', attrib={
1248 'text:style-name': self.rststyle(style_name),
1249 'style:data-style-name': 'rst-date-style-%d' % self.style_index,
1251 el2 = SubElement(automatic_styles, 'number:date-style', attrib={
1252 'style:name': 'rst-date-style-%d' % self.style_index,
1253 'number:automatic-order': 'true',
1254 'xmlns:number': SNSD['number'],
1255 'xmlns:style': SNSD['style'],
1257 el3 = SubElement(el2, 'number:month', attrib={
1258 'number:textual': 'true',
1259 'number:style': 'long',
1261 el3 = SubElement(el2, 'number:text')
1262 el3.text = ' '
1263 el3 = SubElement(el2, 'number:day', attrib={
1265 el3 = SubElement(el2, 'number:text')
1266 el3.text = ', '
1267 el3 = SubElement(el2, 'number:year', attrib={
1268 'number:style': 'long',
1270 elif text == 'd5':
1271 self.style_index += 1
1272 el1 = SubElement(parent, 'text:date', attrib={
1273 'text:style-name': self.rststyle(style_name),
1274 'style:data-style-name': 'rst-date-style-%d' % self.style_index,
1276 el2 = SubElement(automatic_styles, 'number:date-style', attrib={
1277 'style:name': 'rst-date-style-%d' % self.style_index,
1278 'xmlns:number': SNSD['number'],
1279 'xmlns:style': SNSD['style'],
1281 el3 = SubElement(el2, 'number:year', attrib={
1282 'number:style': 'long',
1284 el3 = SubElement(el2, 'number:text')
1285 el3.text = '-'
1286 el3 = SubElement(el2, 'number:month', attrib={
1287 'number:style': 'long',
1289 el3 = SubElement(el2, 'number:text')
1290 el3.text = '-'
1291 el3 = SubElement(el2, 'number:day', attrib={
1292 'number:style': 'long',
1294 elif text == 's':
1295 el1 = SubElement(parent, 'text:subject', attrib={
1296 'text:style-name': self.rststyle(style_name),
1298 elif text == 't':
1299 el1 = SubElement(parent, 'text:title', attrib={
1300 'text:style-name': self.rststyle(style_name),
1302 elif text == 'a':
1303 el1 = SubElement(parent, 'text:author-name', attrib={
1304 'text:fixed': 'false',
1306 else:
1307 el1 = None
1308 return el1
1310 def split_field_specifiers_iter(self, text):
1311 pos1 = 0
1312 pos_end = len(text)
1313 while True:
1314 mo = ODFTranslator.field_pat.search(text, pos1)
1315 if mo:
1316 pos2 = mo.start()
1317 if pos2 > pos1:
1318 yield (ODFTranslator.code_text, text[pos1:pos2])
1319 yield (ODFTranslator.code_field, mo.group(1))
1320 pos1 = mo.end()
1321 else:
1322 break
1323 trailing = text[pos1:]
1324 if trailing:
1325 yield (ODFTranslator.code_text, trailing)
1328 def astext(self):
1329 root = self.content_tree.getroot()
1330 et = etree.ElementTree(root)
1331 s1 = ToString(et)
1332 return s1
1334 def content_astext(self):
1335 return self.astext()
1337 def set_title(self, title): self.title = title
1338 def get_title(self): return self.title
1339 def set_embedded_file_list(self, embedded_file_list):
1340 self.embedded_file_list = embedded_file_list
1341 def get_embedded_file_list(self): return self.embedded_file_list
1342 def get_meta_dict(self): return self.meta_dict
1344 def process_footnotes(self):
1345 for node, el1 in self.footnote_list:
1346 backrefs = node.attributes.get('backrefs', [])
1347 first = True
1348 for ref in backrefs:
1349 el2 = self.footnote_ref_dict.get(ref)
1350 if el2 is not None:
1351 if first:
1352 first = False
1353 el3 = copy.deepcopy(el1)
1354 el2.append(el3)
1355 else:
1356 children = el2.getchildren()
1357 if len(children) > 0: # and 'id' in el2.attrib:
1358 child = children[0]
1359 ref1 = child.text
1360 attribkey = add_ns('text:id', nsdict=SNSD)
1361 id1 = el2.get(attribkey, 'footnote-error')
1362 if id1 is None:
1363 id1 = ''
1364 tag = add_ns('text:note-ref', nsdict=SNSD)
1365 el2.tag = tag
1366 if self.settings.endnotes_end_doc:
1367 note_class = 'endnote'
1368 else:
1369 note_class = 'footnote'
1370 el2.attrib.clear()
1371 attribkey = add_ns('text:note-class', nsdict=SNSD)
1372 el2.attrib[attribkey] = note_class
1373 attribkey = add_ns('text:ref-name', nsdict=SNSD)
1374 el2.attrib[attribkey] = id1
1375 attribkey = add_ns('text:reference-format', nsdict=SNSD)
1376 el2.attrib[attribkey] = 'page'
1377 el2.text = ref1
1380 # Utility methods
1382 def append_child(self, tag, attrib=None, parent=None):
1383 if parent is None:
1384 parent = self.current_element
1385 if attrib is None:
1386 el = SubElement(parent, tag)
1387 else:
1388 el = SubElement(parent, tag, attrib)
1389 return el
1391 def append_p(self, style, text=None):
1392 result = self.append_child('text:p', attrib={
1393 'text:style-name': self.rststyle(style)})
1394 self.append_pending_ids(result)
1395 if text is not None:
1396 result.text = text
1397 return result
1399 def append_pending_ids(self, el):
1400 if self.settings.create_links:
1401 for id in self.pending_ids:
1402 SubElement(el, 'text:reference-mark', attrib={
1403 'text:name': id})
1404 self.pending_ids = [ ]
1406 def set_current_element(self, el):
1407 self.current_element = el
1409 def set_to_parent(self):
1410 self.current_element = self.current_element.getparent()
1412 def generate_labeled_block(self, node, label):
1413 label = '%s:' % (self.language.labels[label], )
1414 el = self.append_p('textbody')
1415 el1 = SubElement(el, 'text:span',
1416 attrib={'text:style-name': self.rststyle('strong')})
1417 el1.text = label
1418 el = self.append_p('blockindent')
1419 return el
1421 def generate_labeled_line(self, node, label):
1422 label = '%s:' % (self.language.labels[label], )
1423 el = self.append_p('textbody')
1424 el1 = SubElement(el, 'text:span',
1425 attrib={'text:style-name': self.rststyle('strong')})
1426 el1.text = label
1427 el1.tail = node.astext()
1428 return el
1430 def encode(self, text):
1431 text = text.replace(u'\u00a0', " ")
1432 return text
1435 # Visitor functions
1437 # In alphabetic order, more or less.
1438 # See docutils.docutils.nodes.node_class_names.
1441 def dispatch_visit(self, node):
1442 """Override to catch basic attributes which many nodes have."""
1443 self.handle_basic_atts(node)
1444 nodes.GenericNodeVisitor.dispatch_visit(self, node)
1446 def handle_basic_atts(self, node):
1447 if isinstance(node, nodes.Element) and node['ids']:
1448 self.pending_ids += node['ids']
1450 def default_visit(self, node):
1451 self.document.reporter.warning('missing visit_%s' % (node.tagname, ))
1453 def default_departure(self, node):
1454 self.document.reporter.warning('missing depart_%s' % (node.tagname, ))
1456 ## def add_text_to_element(self, text):
1457 ## # Are we in a citation. If so, add text to current element, not
1458 ## # to children.
1459 ## # Are we in mixed content? If so, add the text to the
1460 ## # etree tail of the previous sibling element.
1461 ## if not self.in_citation and len(self.current_element.getchildren()) > 0:
1462 ## if self.current_element.getchildren()[-1].tail:
1463 ## self.current_element.getchildren()[-1].tail += text
1464 ## else:
1465 ## self.current_element.getchildren()[-1].tail = text
1466 ## else:
1467 ## if self.current_element.text:
1468 ## self.current_element.text += text
1469 ## else:
1470 ## self.current_element.text = text
1472 ## def visit_Text(self, node):
1473 ## # Skip nodes whose text has been processed in parent nodes.
1474 ## if isinstance(node.parent, docutils.nodes.literal_block):
1475 ## return
1476 ## text = node.astext()
1477 ## self.add_text_to_element(text)
1479 def visit_Text(self, node):
1480 # Skip nodes whose text has been processed in parent nodes.
1481 if isinstance(node.parent, docutils.nodes.literal_block):
1482 return
1483 text = node.astext()
1484 # Are we in mixed content? If so, add the text to the
1485 # etree tail of the previous sibling element.
1486 if len(self.current_element.getchildren()) > 0:
1487 if self.current_element.getchildren()[-1].tail:
1488 self.current_element.getchildren()[-1].tail += text
1489 else:
1490 self.current_element.getchildren()[-1].tail = text
1491 else:
1492 if self.current_element.text:
1493 self.current_element.text += text
1494 else:
1495 self.current_element.text = text
1497 def depart_Text(self, node):
1498 pass
1501 # Pre-defined fields
1504 def visit_address(self, node):
1505 el = self.generate_labeled_block(node, 'address')
1506 self.set_current_element(el)
1508 def depart_address(self, node):
1509 self.set_to_parent()
1511 def visit_author(self, node):
1512 if isinstance(node.parent, nodes.authors):
1513 el = self.append_p('blockindent')
1514 else:
1515 el = self.generate_labeled_block(node, 'author')
1516 self.set_current_element(el)
1518 def depart_author(self, node):
1519 self.set_to_parent()
1521 def visit_authors(self, node):
1522 label = '%s:' % (self.language.labels['authors'], )
1523 el = self.append_p('textbody')
1524 el1 = SubElement(el, 'text:span',
1525 attrib={'text:style-name': self.rststyle('strong')})
1526 el1.text = label
1528 def depart_authors(self, node):
1529 pass
1531 def visit_contact(self, node):
1532 el = self.generate_labeled_block(node, 'contact')
1533 self.set_current_element(el)
1535 def depart_contact(self, node):
1536 self.set_to_parent()
1538 def visit_copyright(self, node):
1539 el = self.generate_labeled_block(node, 'copyright')
1540 self.set_current_element(el)
1542 def depart_copyright(self, node):
1543 self.set_to_parent()
1545 def visit_date(self, node):
1546 self.generate_labeled_line(node, 'date')
1548 def depart_date(self, node):
1549 pass
1551 def visit_organization(self, node):
1552 el = self.generate_labeled_block(node, 'organization')
1553 self.set_current_element(el)
1555 def depart_organization(self, node):
1556 self.set_to_parent()
1558 def visit_status(self, node):
1559 el = self.generate_labeled_block(node, 'status')
1560 self.set_current_element(el)
1562 def depart_status(self, node):
1563 self.set_to_parent()
1565 def visit_revision(self, node):
1566 el = self.generate_labeled_line(node, 'revision')
1568 def depart_revision(self, node):
1569 pass
1571 def visit_version(self, node):
1572 el = self.generate_labeled_line(node, 'version')
1573 #self.set_current_element(el)
1575 def depart_version(self, node):
1576 #self.set_to_parent()
1577 pass
1579 def visit_attribution(self, node):
1580 el = self.append_p('attribution', node.astext())
1582 def depart_attribution(self, node):
1583 pass
1585 def visit_block_quote(self, node):
1586 if 'epigraph' in node.attributes['classes']:
1587 self.paragraph_style_stack.append(self.rststyle('epigraph'))
1588 self.blockstyle = self.rststyle('epigraph')
1589 elif 'highlights' in node.attributes['classes']:
1590 self.paragraph_style_stack.append(self.rststyle('highlights'))
1591 self.blockstyle = self.rststyle('highlights')
1592 else:
1593 self.paragraph_style_stack.append(self.rststyle('blockquote'))
1594 self.blockstyle = self.rststyle('blockquote')
1595 self.line_indent_level += 1
1597 def depart_block_quote(self, node):
1598 self.paragraph_style_stack.pop()
1599 self.blockstyle = ''
1600 self.line_indent_level -= 1
1602 def visit_bullet_list(self, node):
1603 self.list_level +=1
1604 if self.in_table_of_contents:
1605 if self.settings.generate_oowriter_toc:
1606 pass
1607 else:
1608 if node.has_key('classes') and \
1609 'auto-toc' in node.attributes['classes']:
1610 el = SubElement(self.current_element, 'text:list', attrib={
1611 'text:style-name': self.rststyle('tocenumlist'),
1613 self.list_style_stack.append(self.rststyle('enumitem'))
1614 else:
1615 el = SubElement(self.current_element, 'text:list', attrib={
1616 'text:style-name': self.rststyle('tocbulletlist'),
1618 self.list_style_stack.append(self.rststyle('bulletitem'))
1619 self.set_current_element(el)
1620 else:
1621 if self.blockstyle == self.rststyle('blockquote'):
1622 el = SubElement(self.current_element, 'text:list', attrib={
1623 'text:style-name': self.rststyle('blockquote-bulletlist'),
1625 self.list_style_stack.append(
1626 self.rststyle('blockquote-bulletitem'))
1627 elif self.blockstyle == self.rststyle('highlights'):
1628 el = SubElement(self.current_element, 'text:list', attrib={
1629 'text:style-name': self.rststyle('highlights-bulletlist'),
1631 self.list_style_stack.append(
1632 self.rststyle('highlights-bulletitem'))
1633 elif self.blockstyle == self.rststyle('epigraph'):
1634 el = SubElement(self.current_element, 'text:list', attrib={
1635 'text:style-name': self.rststyle('epigraph-bulletlist'),
1637 self.list_style_stack.append(
1638 self.rststyle('epigraph-bulletitem'))
1639 else:
1640 el = SubElement(self.current_element, 'text:list', attrib={
1641 'text:style-name': self.rststyle('bulletlist'),
1643 self.list_style_stack.append(self.rststyle('bulletitem'))
1644 self.set_current_element(el)
1646 def depart_bullet_list(self, node):
1647 if self.in_table_of_contents:
1648 if self.settings.generate_oowriter_toc:
1649 pass
1650 else:
1651 self.set_to_parent()
1652 self.list_style_stack.pop()
1653 else:
1654 self.set_to_parent()
1655 self.list_style_stack.pop()
1656 self.list_level -=1
1658 def visit_caption(self, node):
1659 raise nodes.SkipChildren()
1660 pass
1662 def depart_caption(self, node):
1663 pass
1665 def visit_comment(self, node):
1666 el = self.append_p('textbody')
1667 el1 = SubElement(el, 'office:annotation', attrib={})
1668 el2 = SubElement(el1, 'dc:creator', attrib={})
1669 s1 = os.environ.get('USER', '')
1670 el2.text = s1
1671 el2 = SubElement(el1, 'text:p', attrib={})
1672 el2.text = node.astext()
1674 def depart_comment(self, node):
1675 pass
1677 def visit_compound(self, node):
1678 # The compound directive currently receives no special treatment.
1679 pass
1681 def depart_compound(self, node):
1682 pass
1684 def visit_container(self, node):
1685 styles = node.attributes.get('classes', ())
1686 if len(styles) > 0:
1687 self.paragraph_style_stack.append(self.rststyle(styles[0]))
1689 def depart_container(self, node):
1690 styles = node.attributes.get('classes', ())
1691 if len(styles) > 0:
1692 self.paragraph_style_stack.pop()
1694 def visit_decoration(self, node):
1695 pass
1697 def depart_decoration(self, node):
1698 pass
1700 def visit_definition_list(self, node):
1701 self.def_list_level +=1
1702 if self.list_level > 5:
1703 raise RuntimeError(
1704 'max definition list nesting level exceeded')
1706 def depart_definition_list(self, node):
1707 self.def_list_level -=1
1709 def visit_definition_list_item(self, node):
1710 pass
1712 def depart_definition_list_item(self, node):
1713 pass
1715 def visit_term(self, node):
1716 el = self.append_p('deflist-term-%d' % self.def_list_level)
1717 el.text = node.astext()
1718 self.set_current_element(el)
1719 raise nodes.SkipChildren()
1721 def depart_term(self, node):
1722 self.set_to_parent()
1724 def visit_definition(self, node):
1725 self.paragraph_style_stack.append(
1726 self.rststyle('deflist-def-%d' % self.def_list_level))
1727 self.bumped_list_level_stack.append(ListLevel(1))
1729 def depart_definition(self, node):
1730 self.paragraph_style_stack.pop()
1731 self.bumped_list_level_stack.pop()
1733 def visit_classifier(self, node):
1734 els = self.current_element.getchildren()
1735 if len(els) > 0:
1736 el = els[-1]
1737 el1 = SubElement(el, 'text:span',
1738 attrib={'text:style-name': self.rststyle('emphasis')
1740 el1.text = ' (%s)' % (node.astext(), )
1742 def depart_classifier(self, node):
1743 pass
1745 def visit_document(self, node):
1746 pass
1748 def depart_document(self, node):
1749 self.process_footnotes()
1751 def visit_docinfo(self, node):
1752 self.section_level += 1
1753 self.section_count += 1
1754 if self.settings.create_sections:
1755 el = self.append_child('text:section', attrib={
1756 'text:name': 'Section%d' % self.section_count,
1757 'text:style-name': 'Sect%d' % self.section_level,
1759 self.set_current_element(el)
1761 def depart_docinfo(self, node):
1762 self.section_level -= 1
1763 if self.settings.create_sections:
1764 self.set_to_parent()
1766 def visit_emphasis(self, node):
1767 el = SubElement(self.current_element, 'text:span',
1768 attrib={'text:style-name': self.rststyle('emphasis')})
1769 self.set_current_element(el)
1771 def depart_emphasis(self, node):
1772 self.set_to_parent()
1774 def visit_enumerated_list(self, node):
1775 el1 = self.current_element
1776 if self.blockstyle == self.rststyle('blockquote'):
1777 el2 = SubElement(el1, 'text:list', attrib={
1778 'text:style-name': self.rststyle('blockquote-enumlist'),
1780 self.list_style_stack.append(self.rststyle('blockquote-enumitem'))
1781 elif self.blockstyle == self.rststyle('highlights'):
1782 el2 = SubElement(el1, 'text:list', attrib={
1783 'text:style-name': self.rststyle('highlights-enumlist'),
1785 self.list_style_stack.append(self.rststyle('highlights-enumitem'))
1786 elif self.blockstyle == self.rststyle('epigraph'):
1787 el2 = SubElement(el1, 'text:list', attrib={
1788 'text:style-name': self.rststyle('epigraph-enumlist'),
1790 self.list_style_stack.append(self.rststyle('epigraph-enumitem'))
1791 else:
1792 liststylename = 'enumlist-%s' % (node.get('enumtype', 'arabic'), )
1793 el2 = SubElement(el1, 'text:list', attrib={
1794 'text:style-name': self.rststyle(liststylename),
1796 self.list_style_stack.append(self.rststyle('enumitem'))
1797 self.set_current_element(el2)
1799 def depart_enumerated_list(self, node):
1800 self.set_to_parent()
1801 self.list_style_stack.pop()
1803 def visit_list_item(self, node):
1804 # If we are in a "bumped" list level, then wrap this
1805 # list in an outer lists in order to increase the
1806 # indentation level.
1807 if self.in_table_of_contents:
1808 if self.settings.generate_oowriter_toc:
1809 self.paragraph_style_stack.append(
1810 self.rststyle('contents-%d' % (self.list_level, )))
1811 else:
1812 el1 = self.append_child('text:list-item')
1813 self.set_current_element(el1)
1814 else:
1815 el1 = self.append_child('text:list-item')
1816 el3 = el1
1817 if len(self.bumped_list_level_stack) > 0:
1818 level_obj = self.bumped_list_level_stack[-1]
1819 if level_obj.get_sibling():
1820 level_obj.set_nested(False)
1821 for level_obj1 in self.bumped_list_level_stack:
1822 for idx in range(level_obj1.get_level()):
1823 el2 = self.append_child('text:list', parent=el3)
1824 el3 = self.append_child(
1825 'text:list-item', parent=el2)
1826 self.paragraph_style_stack.append(self.list_style_stack[-1])
1827 self.set_current_element(el3)
1829 def depart_list_item(self, node):
1830 if self.in_table_of_contents:
1831 if self.settings.generate_oowriter_toc:
1832 self.paragraph_style_stack.pop()
1833 else:
1834 self.set_to_parent()
1835 else:
1836 if len(self.bumped_list_level_stack) > 0:
1837 level_obj = self.bumped_list_level_stack[-1]
1838 if level_obj.get_sibling():
1839 level_obj.set_nested(True)
1840 for level_obj1 in self.bumped_list_level_stack:
1841 for idx in range(level_obj1.get_level()):
1842 self.set_to_parent()
1843 self.set_to_parent()
1844 self.paragraph_style_stack.pop()
1845 self.set_to_parent()
1847 def visit_header(self, node):
1848 self.in_header = True
1850 def depart_header(self, node):
1851 self.in_header = False
1853 def visit_footer(self, node):
1854 self.in_footer = True
1856 def depart_footer(self, node):
1857 self.in_footer = False
1859 def visit_field(self, node):
1860 pass
1862 def depart_field(self, node):
1863 pass
1865 def visit_field_list(self, node):
1866 pass
1868 def depart_field_list(self, node):
1869 pass
1871 def visit_field_name(self, node):
1872 el = self.append_p('textbody')
1873 el1 = SubElement(el, 'text:span',
1874 attrib={'text:style-name': self.rststyle('strong')})
1875 el1.text = node.astext()
1877 def depart_field_name(self, node):
1878 pass
1880 def visit_field_body(self, node):
1881 self.paragraph_style_stack.append(self.rststyle('blockindent'))
1883 def depart_field_body(self, node):
1884 self.paragraph_style_stack.pop()
1886 def visit_figure(self, node):
1887 pass
1889 def depart_figure(self, node):
1890 pass
1892 def visit_footnote(self, node):
1893 self.footnote_level += 1
1894 self.save_footnote_current = self.current_element
1895 el1 = Element('text:note-body')
1896 self.current_element = el1
1897 self.footnote_list.append((node, el1))
1898 if isinstance(node, docutils.nodes.citation):
1899 self.paragraph_style_stack.append(self.rststyle('citation'))
1900 else:
1901 self.paragraph_style_stack.append(self.rststyle('footnote'))
1903 def depart_footnote(self, node):
1904 self.paragraph_style_stack.pop()
1905 self.current_element = self.save_footnote_current
1906 self.footnote_level -= 1
1908 footnote_chars = [
1909 '*', '**', '***',
1910 '++', '+++',
1911 '##', '###',
1912 '@@', '@@@',
1915 def visit_footnote_reference(self, node):
1916 if self.footnote_level <= 0:
1917 id = node.attributes['ids'][0]
1918 refid = node.attributes.get('refid')
1919 if refid is None:
1920 refid = ''
1921 if self.settings.endnotes_end_doc:
1922 note_class = 'endnote'
1923 else:
1924 note_class = 'footnote'
1925 el1 = self.append_child('text:note', attrib={
1926 'text:id': '%s' % (refid, ),
1927 'text:note-class': note_class,
1929 note_auto = str(node.attributes.get('auto', 1))
1930 if isinstance(node, docutils.nodes.citation_reference):
1931 citation = '[%s]' % node.astext()
1932 el2 = SubElement(el1, 'text:note-citation', attrib={
1933 'text:label': citation,
1935 el2.text = citation
1936 elif note_auto == '1':
1937 el2 = SubElement(el1, 'text:note-citation', attrib={
1938 'text:label': node.astext(),
1940 el2.text = node.astext()
1941 elif note_auto == '*':
1942 if self.footnote_chars_idx >= len(
1943 ODFTranslator.footnote_chars):
1944 self.footnote_chars_idx = 0
1945 footnote_char = ODFTranslator.footnote_chars[
1946 self.footnote_chars_idx]
1947 self.footnote_chars_idx += 1
1948 el2 = SubElement(el1, 'text:note-citation', attrib={
1949 'text:label': footnote_char,
1951 el2.text = footnote_char
1952 self.footnote_ref_dict[id] = el1
1953 raise nodes.SkipChildren()
1955 def depart_footnote_reference(self, node):
1956 pass
1958 def visit_citation(self, node):
1959 self.in_citation = True
1960 for id in node.attributes['ids']:
1961 self.citation_id = id
1962 break
1963 self.paragraph_style_stack.append(self.rststyle('blockindent'))
1964 self.bumped_list_level_stack.append(ListLevel(1))
1966 def depart_citation(self, node):
1967 self.citation_id = None
1968 self.paragraph_style_stack.pop()
1969 self.bumped_list_level_stack.pop()
1970 self.in_citation = False
1972 def visit_citation_reference(self, node):
1973 if self.settings.create_links:
1974 id = node.attributes['refid']
1975 el = self.append_child('text:reference-ref', attrib={
1976 'text:ref-name': '%s' % (id, ),
1977 'text:reference-format': 'text',
1979 el.text = '['
1980 self.set_current_element(el)
1981 elif self.current_element.text is None:
1982 self.current_element.text = '['
1983 else:
1984 self.current_element.text += '['
1986 def depart_citation_reference(self, node):
1987 self.current_element.text += ']'
1988 if self.settings.create_links:
1989 self.set_to_parent()
1991 def visit_label(self, node):
1992 if isinstance(node.parent, docutils.nodes.footnote):
1993 raise nodes.SkipChildren()
1994 elif self.citation_id is not None:
1995 el = self.append_p('textbody')
1996 self.set_current_element(el)
1997 el.text = '['
1998 if self.settings.create_links:
1999 el1 = self.append_child('text:reference-mark-start', attrib={
2000 'text:name': '%s' % (self.citation_id, ),
2003 def depart_label(self, node):
2004 if isinstance(node.parent, docutils.nodes.footnote):
2005 pass
2006 elif self.citation_id is not None:
2007 self.current_element.text += ']'
2008 if self.settings.create_links:
2009 el = self.append_child('text:reference-mark-end', attrib={
2010 'text:name': '%s' % (self.citation_id, ),
2012 self.set_to_parent()
2014 def visit_generated(self, node):
2015 pass
2017 def depart_generated(self, node):
2018 pass
2020 def check_file_exists(self, path):
2021 if os.path.exists(path):
2022 return 1
2023 else:
2024 return 0
2026 def visit_image(self, node):
2027 # Capture the image file.
2028 if 'uri' in node.attributes:
2029 source = node.attributes['uri']
2030 if not source.startswith('http:'):
2031 if not source.startswith(os.sep):
2032 docsource, line = utils.get_source_line(node)
2033 if docsource:
2034 dirname = os.path.dirname(docsource)
2035 if dirname:
2036 source = '%s%s%s' % (dirname, os.sep, source, )
2037 if not self.check_file_exists(source):
2038 self.document.reporter.warning(
2039 'Cannot find image file %s.' % (source, ))
2040 return
2041 else:
2042 return
2043 if source in self.image_dict:
2044 filename, destination = self.image_dict[source]
2045 else:
2046 self.image_count += 1
2047 filename = os.path.split(source)[1]
2048 destination = 'Pictures/1%08x%s' % (self.image_count, filename, )
2049 if source.startswith('http:'):
2050 try:
2051 imgfile = urllib2.urlopen(source)
2052 content = imgfile.read()
2053 imgfile.close()
2054 imgfile2 = tempfile.NamedTemporaryFile('wb', delete=False)
2055 imgfile2.write(content)
2056 imgfile2.close()
2057 imgfilename = imgfile2.name
2058 source = imgfilename
2059 except urllib2.HTTPError, e:
2060 self.document.reporter.warning(
2061 "Can't open image url %s." % (source, ))
2062 spec = (source, destination,)
2063 else:
2064 spec = (os.path.abspath(source), destination,)
2065 self.embedded_file_list.append(spec)
2066 self.image_dict[source] = (source, destination,)
2067 # Is this a figure (containing an image) or just a plain image?
2068 if self.in_paragraph:
2069 el1 = self.current_element
2070 else:
2071 el1 = SubElement(self.current_element, 'text:p',
2072 attrib={'text:style-name': self.rststyle('textbody')})
2073 el2 = el1
2074 if isinstance(node.parent, docutils.nodes.figure):
2075 el3, el4, el5, caption = self.generate_figure(node, source,
2076 destination, el2)
2077 attrib = {}
2078 el6, width = self.generate_image(node, source, destination,
2079 el5, attrib)
2080 if caption is not None:
2081 el6.tail = caption
2082 else: #if isinstance(node.parent, docutils.nodes.image):
2083 el3 = self.generate_image(node, source, destination, el2)
2085 def depart_image(self, node):
2086 pass
2088 def get_image_width_height(self, node, attr):
2089 size = None
2090 if attr in node.attributes:
2091 size = node.attributes[attr]
2092 unit = size[-2:]
2093 if unit.isalpha():
2094 size = size[:-2]
2095 else:
2096 unit = 'px'
2097 try:
2098 size = float(size)
2099 except ValueError, e:
2100 self.document.reporter.warning(
2101 'Invalid %s for image: "%s"' % (
2102 attr, node.attributes[attr]))
2103 size = [size, unit]
2104 return size
2106 def get_image_scale(self, node):
2107 if 'scale' in node.attributes:
2108 try:
2109 scale = int(node.attributes['scale'])
2110 if scale < 1: # or scale > 100:
2111 self.document.reporter.warning(
2112 'scale out of range (%s), using 1.' % (scale, ))
2113 scale = 1
2114 scale = scale * 0.01
2115 except ValueError, e:
2116 self.document.reporter.warning(
2117 'Invalid scale for image: "%s"' % (
2118 node.attributes['scale'], ))
2119 else:
2120 scale = 1.0
2121 return scale
2123 def get_image_scaled_width_height(self, node, source):
2124 scale = self.get_image_scale(node)
2125 width = self.get_image_width_height(node, 'width')
2126 height = self.get_image_width_height(node, 'height')
2128 dpi = (72, 72)
2129 if PIL is not None and source in self.image_dict:
2130 filename, destination = self.image_dict[source]
2131 imageobj = PIL.Image.open(filename, 'r')
2132 dpi = imageobj.info.get('dpi', dpi)
2133 # dpi information can be (xdpi, ydpi) or xydpi
2134 try: iter(dpi)
2135 except: dpi = (dpi, dpi)
2136 else:
2137 imageobj = None
2139 if width is None or height is None:
2140 if imageobj is None:
2141 raise RuntimeError(
2142 'image size not fully specified and PIL not installed')
2143 if width is None: width = [imageobj.size[0], 'px']
2144 if height is None: height = [imageobj.size[1], 'px']
2146 width[0] *= scale
2147 height[0] *= scale
2148 if width[1] == 'px': width = [width[0] / dpi[0], 'in']
2149 if height[1] == 'px': height = [height[0] / dpi[1], 'in']
2151 width[0] = str(width[0])
2152 height[0] = str(height[0])
2153 return ''.join(width), ''.join(height)
2155 def generate_figure(self, node, source, destination, current_element):
2156 caption = None
2157 width, height = self.get_image_scaled_width_height(node, source)
2158 for node1 in node.parent.children:
2159 if node1.tagname == 'caption':
2160 caption = node1.astext()
2161 self.image_style_count += 1
2163 # Add the style for the caption.
2164 if caption is not None:
2165 attrib = {
2166 'style:class': 'extra',
2167 'style:family': 'paragraph',
2168 'style:name': 'Caption',
2169 'style:parent-style-name': 'Standard',
2171 el1 = SubElement(self.automatic_styles, 'style:style',
2172 attrib=attrib, nsdict=SNSD)
2173 attrib = {
2174 'fo:margin-bottom': '0.0835in',
2175 'fo:margin-top': '0.0835in',
2176 'text:line-number': '0',
2177 'text:number-lines': 'false',
2179 el2 = SubElement(el1, 'style:paragraph-properties',
2180 attrib=attrib, nsdict=SNSD)
2181 attrib = {
2182 'fo:font-size': '12pt',
2183 'fo:font-style': 'italic',
2184 'style:font-name': 'Times',
2185 'style:font-name-complex': 'Lucidasans1',
2186 'style:font-size-asian': '12pt',
2187 'style:font-size-complex': '12pt',
2188 'style:font-style-asian': 'italic',
2189 'style:font-style-complex': 'italic',
2191 el2 = SubElement(el1, 'style:text-properties',
2192 attrib=attrib, nsdict=SNSD)
2193 style_name = 'rstframestyle%d' % self.image_style_count
2194 # Add the styles
2195 attrib = {
2196 'style:name': style_name,
2197 'style:family': 'graphic',
2198 'style:parent-style-name': self.rststyle('figureframe'),
2200 el1 = SubElement(self.automatic_styles,
2201 'style:style', attrib=attrib, nsdict=SNSD)
2202 halign = 'center'
2203 valign = 'top'
2204 if 'align' in node.attributes:
2205 align = node.attributes['align'].split()
2206 for val in align:
2207 if val in ('left', 'center', 'right'):
2208 halign = val
2209 elif val in ('top', 'middle', 'bottom'):
2210 valign = val
2211 attrib = {}
2212 wrap = False
2213 classes = node.parent.attributes.get('classes')
2214 if classes and 'wrap' in classes:
2215 wrap = True
2216 if wrap:
2217 attrib['style:wrap'] = 'dynamic'
2218 else:
2219 attrib['style:wrap'] = 'none'
2220 el2 = SubElement(el1,
2221 'style:graphic-properties', attrib=attrib, nsdict=SNSD)
2222 attrib = {
2223 'draw:style-name': style_name,
2224 'draw:name': 'Frame1',
2225 'text:anchor-type': 'paragraph',
2226 'draw:z-index': '0',
2228 attrib['svg:width'] = width
2229 # dbg
2230 #attrib['svg:height'] = height
2231 el3 = SubElement(current_element, 'draw:frame', attrib=attrib)
2232 attrib = {}
2233 el4 = SubElement(el3, 'draw:text-box', attrib=attrib)
2234 attrib = {
2235 'text:style-name': self.rststyle('caption'),
2237 el5 = SubElement(el4, 'text:p', attrib=attrib)
2238 return el3, el4, el5, caption
2240 def generate_image(self, node, source, destination, current_element,
2241 frame_attrs=None):
2242 width, height = self.get_image_scaled_width_height(node, source)
2243 self.image_style_count += 1
2244 style_name = 'rstframestyle%d' % self.image_style_count
2245 # Add the style.
2246 attrib = {
2247 'style:name': style_name,
2248 'style:family': 'graphic',
2249 'style:parent-style-name': self.rststyle('image'),
2251 el1 = SubElement(self.automatic_styles,
2252 'style:style', attrib=attrib, nsdict=SNSD)
2253 halign = None
2254 valign = None
2255 if 'align' in node.attributes:
2256 align = node.attributes['align'].split()
2257 for val in align:
2258 if val in ('left', 'center', 'right'):
2259 halign = val
2260 elif val in ('top', 'middle', 'bottom'):
2261 valign = val
2262 if frame_attrs is None:
2263 attrib = {
2264 'style:vertical-pos': 'top',
2265 'style:vertical-rel': 'paragraph',
2266 'style:horizontal-rel': 'paragraph',
2267 'style:mirror': 'none',
2268 'fo:clip': 'rect(0cm 0cm 0cm 0cm)',
2269 'draw:luminance': '0%',
2270 'draw:contrast': '0%',
2271 'draw:red': '0%',
2272 'draw:green': '0%',
2273 'draw:blue': '0%',
2274 'draw:gamma': '100%',
2275 'draw:color-inversion': 'false',
2276 'draw:image-opacity': '100%',
2277 'draw:color-mode': 'standard',
2279 else:
2280 attrib = frame_attrs
2281 if halign is not None:
2282 attrib['style:horizontal-pos'] = halign
2283 if valign is not None:
2284 attrib['style:vertical-pos'] = valign
2285 # If there is a classes/wrap directive or we are
2286 # inside a table, add a no-wrap style.
2287 wrap = False
2288 classes = node.attributes.get('classes')
2289 if classes and 'wrap' in classes:
2290 wrap = True
2291 if wrap:
2292 attrib['style:wrap'] = 'dynamic'
2293 else:
2294 attrib['style:wrap'] = 'none'
2295 # If we are inside a table, add a no-wrap style.
2296 if self.is_in_table(node):
2297 attrib['style:wrap'] = 'none'
2298 el2 = SubElement(el1,
2299 'style:graphic-properties', attrib=attrib, nsdict=SNSD)
2300 # Add the content.
2301 #el = SubElement(current_element, 'text:p',
2302 # attrib={'text:style-name': self.rststyle('textbody')})
2303 attrib={
2304 'draw:style-name': style_name,
2305 'draw:name': 'graphics2',
2306 'draw:z-index': '1',
2308 if isinstance(node.parent, nodes.TextElement):
2309 attrib['text:anchor-type'] = 'as-char' #vds
2310 else:
2311 attrib['text:anchor-type'] = 'paragraph'
2312 attrib['svg:width'] = width
2313 attrib['svg:height'] = height
2314 el1 = SubElement(current_element, 'draw:frame', attrib=attrib)
2315 el2 = SubElement(el1, 'draw:image', attrib={
2316 'xlink:href': '%s' % (destination, ),
2317 'xlink:type': 'simple',
2318 'xlink:show': 'embed',
2319 'xlink:actuate': 'onLoad',
2321 return el1, width
2323 def is_in_table(self, node):
2324 node1 = node.parent
2325 while node1:
2326 if isinstance(node1, docutils.nodes.entry):
2327 return True
2328 node1 = node1.parent
2329 return False
2331 def visit_legend(self, node):
2332 if isinstance(node.parent, docutils.nodes.figure):
2333 el1 = self.current_element[-1]
2334 el1 = el1[0][0]
2335 self.current_element = el1
2336 self.paragraph_style_stack.append(self.rststyle('legend'))
2338 def depart_legend(self, node):
2339 if isinstance(node.parent, docutils.nodes.figure):
2340 self.paragraph_style_stack.pop()
2341 self.set_to_parent()
2342 self.set_to_parent()
2343 self.set_to_parent()
2345 def visit_line_block(self, node):
2346 self.line_indent_level += 1
2347 self.line_block_level += 1
2349 def depart_line_block(self, node):
2350 self.line_indent_level -= 1
2351 self.line_block_level -= 1
2353 def visit_line(self, node):
2354 style = 'lineblock%d' % self.line_indent_level
2355 el1 = SubElement(self.current_element, 'text:p', attrib={
2356 'text:style-name': self.rststyle(style),
2358 self.current_element = el1
2360 def depart_line(self, node):
2361 self.set_to_parent()
2363 def visit_literal(self, node):
2364 el = SubElement(self.current_element, 'text:span',
2365 attrib={'text:style-name': self.rststyle('inlineliteral')})
2366 self.set_current_element(el)
2368 def depart_literal(self, node):
2369 self.set_to_parent()
2371 def visit_inline(self, node):
2372 styles = node.attributes.get('classes', ())
2373 if len(styles) > 0:
2374 inline_style = styles[0]
2375 el = SubElement(self.current_element, 'text:span',
2376 attrib={'text:style-name': self.rststyle(inline_style)})
2377 self.set_current_element(el)
2379 def depart_inline(self, node):
2380 self.set_to_parent()
2382 def _calculate_code_block_padding(self, line):
2383 count = 0
2384 matchobj = SPACES_PATTERN.match(line)
2385 if matchobj:
2386 pad = matchobj.group()
2387 count = len(pad)
2388 else:
2389 matchobj = TABS_PATTERN.match(line)
2390 if matchobj:
2391 pad = matchobj.group()
2392 count = len(pad) * 8
2393 return count
2395 def _add_syntax_highlighting(self, insource, language):
2396 lexer = pygments.lexers.get_lexer_by_name(language, stripall=True)
2397 if language in ('latex', 'tex'):
2398 fmtr = OdtPygmentsLaTeXFormatter(lambda name, parameters=():
2399 self.rststyle(name, parameters),
2400 escape_function=escape_cdata)
2401 else:
2402 fmtr = OdtPygmentsProgFormatter(lambda name, parameters=():
2403 self.rststyle(name, parameters),
2404 escape_function=escape_cdata)
2405 outsource = pygments.highlight(insource, lexer, fmtr)
2406 return outsource
2408 def fill_line(self, line):
2409 line = FILL_PAT1.sub(self.fill_func1, line)
2410 line = FILL_PAT2.sub(self.fill_func2, line)
2411 return line
2413 def fill_func1(self, matchobj):
2414 spaces = matchobj.group(0)
2415 repl = '<text:s text:c="%d"/>' % (len(spaces), )
2416 return repl
2418 def fill_func2(self, matchobj):
2419 spaces = matchobj.group(0)
2420 repl = ' <text:s text:c="%d"/>' % (len(spaces) - 1, )
2421 return repl
2423 def visit_literal_block(self, node):
2424 if len(self.paragraph_style_stack) > 1:
2425 wrapper1 = '<text:p text:style-name="%s">%%s</text:p>' % (
2426 self.rststyle('codeblock-indented'), )
2427 else:
2428 wrapper1 = '<text:p text:style-name="%s">%%s</text:p>' % (
2429 self.rststyle('codeblock'), )
2430 source = node.astext()
2431 if (pygments and
2432 self.settings.add_syntax_highlighting
2433 #and
2434 #node.get('hilight', False)
2436 language = node.get('language', 'python')
2437 source = self._add_syntax_highlighting(source, language)
2438 else:
2439 source = escape_cdata(source)
2440 lines = source.split('\n')
2441 # If there is an empty last line, remove it.
2442 if lines[-1] == '':
2443 del lines[-1]
2444 lines1 = ['<wrappertag1 xmlns:text="urn:oasis:names:tc:opendocument:xmlns:text:1.0">']
2446 my_lines = []
2447 for my_line in lines:
2448 my_line = self.fill_line(my_line)
2449 my_line = my_line.replace("&#10;", "\n")
2450 my_lines.append(my_line)
2451 my_lines_str = '<text:line-break/>'.join(my_lines)
2452 my_lines_str2 = wrapper1 % (my_lines_str, )
2453 lines1.append(my_lines_str2)
2454 lines1.append('</wrappertag1>')
2455 s1 = ''.join(lines1)
2456 if WhichElementTree != "lxml":
2457 s1 = s1.encode("utf-8")
2458 el1 = etree.fromstring(s1)
2459 children = el1.getchildren()
2460 for child in children:
2461 self.current_element.append(child)
2463 def depart_literal_block(self, node):
2464 pass
2466 visit_doctest_block = visit_literal_block
2467 depart_doctest_block = depart_literal_block
2469 # placeholder for math (see docs/dev/todo.txt)
2470 def visit_math(self, node):
2471 self.document.reporter.warning('"math" role not supported',
2472 base_node=node)
2473 self.visit_literal(node)
2475 def depart_math(self, node):
2476 self.depart_literal(node)
2478 def visit_math_block(self, node):
2479 self.document.reporter.warning('"math" directive not supported',
2480 base_node=node)
2481 self.visit_literal_block(node)
2483 def depart_math_block(self, node):
2484 self.depart_literal_block(node)
2486 def visit_meta(self, node):
2487 name = node.attributes.get('name')
2488 content = node.attributes.get('content')
2489 if name is not None and content is not None:
2490 self.meta_dict[name] = content
2492 def depart_meta(self, node):
2493 pass
2495 def visit_option_list(self, node):
2496 table_name = 'tableoption'
2498 # Generate automatic styles
2499 if not self.optiontablestyles_generated:
2500 self.optiontablestyles_generated = True
2501 el = SubElement(self.automatic_styles, 'style:style', attrib={
2502 'style:name': self.rststyle(table_name),
2503 'style:family': 'table'}, nsdict=SNSD)
2504 el1 = SubElement(el, 'style:table-properties', attrib={
2505 'style:width': '17.59cm',
2506 'table:align': 'left',
2507 'style:shadow': 'none'}, nsdict=SNSD)
2508 el = SubElement(self.automatic_styles, 'style:style', attrib={
2509 'style:name': self.rststyle('%s.%%c' % table_name, ( 'A', )),
2510 'style:family': 'table-column'}, nsdict=SNSD)
2511 el1 = SubElement(el, 'style:table-column-properties', attrib={
2512 'style:column-width': '4.999cm'}, nsdict=SNSD)
2513 el = SubElement(self.automatic_styles, 'style:style', attrib={
2514 'style:name': self.rststyle('%s.%%c' % table_name, ( 'B', )),
2515 'style:family': 'table-column'}, nsdict=SNSD)
2516 el1 = SubElement(el, 'style:table-column-properties', attrib={
2517 'style:column-width': '12.587cm'}, nsdict=SNSD)
2518 el = SubElement(self.automatic_styles, 'style:style', attrib={
2519 'style:name': self.rststyle(
2520 '%s.%%c%%d' % table_name, ( 'A', 1, )),
2521 'style:family': 'table-cell'}, nsdict=SNSD)
2522 el1 = SubElement(el, 'style:table-cell-properties', attrib={
2523 'fo:background-color': 'transparent',
2524 'fo:padding': '0.097cm',
2525 'fo:border-left': '0.035cm solid #000000',
2526 'fo:border-right': 'none',
2527 'fo:border-top': '0.035cm solid #000000',
2528 'fo:border-bottom': '0.035cm solid #000000'}, nsdict=SNSD)
2529 el2 = SubElement(el1, 'style:background-image', nsdict=SNSD)
2530 el = SubElement(self.automatic_styles, 'style:style', attrib={
2531 'style:name': self.rststyle(
2532 '%s.%%c%%d' % table_name, ( 'B', 1, )),
2533 'style:family': 'table-cell'}, nsdict=SNSD)
2534 el1 = SubElement(el, 'style:table-cell-properties', attrib={
2535 'fo:padding': '0.097cm',
2536 'fo:border': '0.035cm solid #000000'}, nsdict=SNSD)
2537 el = SubElement(self.automatic_styles, 'style:style', attrib={
2538 'style:name': self.rststyle(
2539 '%s.%%c%%d' % table_name, ( 'A', 2, )),
2540 'style:family': 'table-cell'}, nsdict=SNSD)
2541 el1 = SubElement(el, 'style:table-cell-properties', attrib={
2542 'fo:padding': '0.097cm',
2543 'fo:border-left': '0.035cm solid #000000',
2544 'fo:border-right': 'none',
2545 'fo:border-top': 'none',
2546 'fo:border-bottom': '0.035cm solid #000000'}, nsdict=SNSD)
2547 el = SubElement(self.automatic_styles, 'style:style', attrib={
2548 'style:name': self.rststyle(
2549 '%s.%%c%%d' % table_name, ( 'B', 2, )),
2550 'style:family': 'table-cell'}, nsdict=SNSD)
2551 el1 = SubElement(el, 'style:table-cell-properties', attrib={
2552 'fo:padding': '0.097cm',
2553 'fo:border-left': '0.035cm solid #000000',
2554 'fo:border-right': '0.035cm solid #000000',
2555 'fo:border-top': 'none',
2556 'fo:border-bottom': '0.035cm solid #000000'}, nsdict=SNSD)
2558 # Generate table data
2559 el = self.append_child('table:table', attrib={
2560 'table:name': self.rststyle(table_name),
2561 'table:style-name': self.rststyle(table_name),
2563 el1 = SubElement(el, 'table:table-column', attrib={
2564 'table:style-name': self.rststyle(
2565 '%s.%%c' % table_name, ( 'A', ))})
2566 el1 = SubElement(el, 'table:table-column', attrib={
2567 'table:style-name': self.rststyle(
2568 '%s.%%c' % table_name, ( 'B', ))})
2569 el1 = SubElement(el, 'table:table-header-rows')
2570 el2 = SubElement(el1, 'table:table-row')
2571 el3 = SubElement(el2, 'table:table-cell', attrib={
2572 'table:style-name': self.rststyle(
2573 '%s.%%c%%d' % table_name, ( 'A', 1, )),
2574 'office:value-type': 'string'})
2575 el4 = SubElement(el3, 'text:p', attrib={
2576 'text:style-name': 'Table_20_Heading'})
2577 el4.text= 'Option'
2578 el3 = SubElement(el2, 'table:table-cell', attrib={
2579 'table:style-name': self.rststyle(
2580 '%s.%%c%%d' % table_name, ( 'B', 1, )),
2581 'office:value-type': 'string'})
2582 el4 = SubElement(el3, 'text:p', attrib={
2583 'text:style-name': 'Table_20_Heading'})
2584 el4.text= 'Description'
2585 self.set_current_element(el)
2587 def depart_option_list(self, node):
2588 self.set_to_parent()
2590 def visit_option_list_item(self, node):
2591 el = self.append_child('table:table-row')
2592 self.set_current_element(el)
2594 def depart_option_list_item(self, node):
2595 self.set_to_parent()
2597 def visit_option_group(self, node):
2598 el = self.append_child('table:table-cell', attrib={
2599 'table:style-name': 'Table%d.A2' % self.table_count,
2600 'office:value-type': 'string',
2602 self.set_current_element(el)
2604 def depart_option_group(self, node):
2605 self.set_to_parent()
2607 def visit_option(self, node):
2608 el = self.append_child('text:p', attrib={
2609 'text:style-name': 'Table_20_Contents'})
2610 el.text = node.astext()
2612 def depart_option(self, node):
2613 pass
2615 def visit_option_string(self, node):
2616 pass
2618 def depart_option_string(self, node):
2619 pass
2621 def visit_option_argument(self, node):
2622 pass
2624 def depart_option_argument(self, node):
2625 pass
2627 def visit_description(self, node):
2628 el = self.append_child('table:table-cell', attrib={
2629 'table:style-name': 'Table%d.B2' % self.table_count,
2630 'office:value-type': 'string',
2632 el1 = SubElement(el, 'text:p', attrib={
2633 'text:style-name': 'Table_20_Contents'})
2634 el1.text = node.astext()
2635 raise nodes.SkipChildren()
2637 def depart_description(self, node):
2638 pass
2640 def visit_paragraph(self, node):
2641 self.in_paragraph = True
2642 if self.in_header:
2643 el = self.append_p('header')
2644 elif self.in_footer:
2645 el = self.append_p('footer')
2646 else:
2647 style_name = self.paragraph_style_stack[-1]
2648 el = self.append_child('text:p',
2649 attrib={'text:style-name': style_name})
2650 self.append_pending_ids(el)
2651 self.set_current_element(el)
2653 def depart_paragraph(self, node):
2654 self.in_paragraph = False
2655 self.set_to_parent()
2656 if self.in_header:
2657 self.header_content.append(
2658 self.current_element.getchildren()[-1])
2659 self.current_element.remove(
2660 self.current_element.getchildren()[-1])
2661 elif self.in_footer:
2662 self.footer_content.append(
2663 self.current_element.getchildren()[-1])
2664 self.current_element.remove(
2665 self.current_element.getchildren()[-1])
2667 def visit_problematic(self, node):
2668 pass
2670 def depart_problematic(self, node):
2671 pass
2673 def visit_raw(self, node):
2674 if 'format' in node.attributes:
2675 formats = node.attributes['format']
2676 formatlist = formats.split()
2677 if 'odt' in formatlist:
2678 rawstr = node.astext()
2679 attrstr = ' '.join(['%s="%s"' % (k, v, )
2680 for k,v in CONTENT_NAMESPACE_ATTRIB.items()])
2681 contentstr = '<stuff %s>%s</stuff>' % (attrstr, rawstr, )
2682 if WhichElementTree != "lxml":
2683 contentstr = contentstr.encode("utf-8")
2684 content = etree.fromstring(contentstr)
2685 elements = content.getchildren()
2686 if len(elements) > 0:
2687 el1 = elements[0]
2688 if self.in_header:
2689 pass
2690 elif self.in_footer:
2691 pass
2692 else:
2693 self.current_element.append(el1)
2694 raise nodes.SkipChildren()
2696 def depart_raw(self, node):
2697 if self.in_header:
2698 pass
2699 elif self.in_footer:
2700 pass
2701 else:
2702 pass
2704 def visit_reference(self, node):
2705 text = node.astext()
2706 if self.settings.create_links:
2707 if node.has_key('refuri'):
2708 href = node['refuri']
2709 if ( self.settings.cloak_email_addresses
2710 and href.startswith('mailto:')):
2711 href = self.cloak_mailto(href)
2712 el = self.append_child('text:a', attrib={
2713 'xlink:href': '%s' % href,
2714 'xlink:type': 'simple',
2716 self.set_current_element(el)
2717 elif node.has_key('refid'):
2718 if self.settings.create_links:
2719 href = node['refid']
2720 el = self.append_child('text:reference-ref', attrib={
2721 'text:ref-name': '%s' % href,
2722 'text:reference-format': 'text',
2724 else:
2725 self.document.reporter.warning(
2726 'References must have "refuri" or "refid" attribute.')
2727 if (self.in_table_of_contents and
2728 len(node.children) >= 1 and
2729 isinstance(node.children[0], docutils.nodes.generated)):
2730 node.remove(node.children[0])
2732 def depart_reference(self, node):
2733 if self.settings.create_links:
2734 if node.has_key('refuri'):
2735 self.set_to_parent()
2737 def visit_rubric(self, node):
2738 style_name = self.rststyle('rubric')
2739 classes = node.get('classes')
2740 if classes:
2741 class1 = classes[0]
2742 if class1:
2743 style_name = class1
2744 el = SubElement(self.current_element, 'text:h', attrib = {
2745 #'text:outline-level': '%d' % section_level,
2746 #'text:style-name': 'Heading_20_%d' % section_level,
2747 'text:style-name': style_name,
2749 text = node.astext()
2750 el.text = self.encode(text)
2752 def depart_rubric(self, node):
2753 pass
2755 def visit_section(self, node, move_ids=1):
2756 self.section_level += 1
2757 self.section_count += 1
2758 if self.settings.create_sections:
2759 el = self.append_child('text:section', attrib={
2760 'text:name': 'Section%d' % self.section_count,
2761 'text:style-name': 'Sect%d' % self.section_level,
2763 self.set_current_element(el)
2765 def depart_section(self, node):
2766 self.section_level -= 1
2767 if self.settings.create_sections:
2768 self.set_to_parent()
2770 def visit_strong(self, node):
2771 el = SubElement(self.current_element, 'text:span',
2772 attrib={'text:style-name': self.rststyle('strong')})
2773 self.set_current_element(el)
2775 def depart_strong(self, node):
2776 self.set_to_parent()
2778 def visit_substitution_definition(self, node):
2779 raise nodes.SkipChildren()
2781 def depart_substitution_definition(self, node):
2782 pass
2784 def visit_system_message(self, node):
2785 pass
2787 def depart_system_message(self, node):
2788 pass
2790 def get_table_style(self, node):
2791 table_style = None
2792 table_name = None
2793 use_predefined_table_style = False
2794 str_classes = node.get('classes')
2795 if str_classes is not None:
2796 for str_class in str_classes:
2797 if str_class.startswith(TABLESTYLEPREFIX):
2798 table_name = str_class
2799 use_predefined_table_style = True
2800 break
2801 if table_name is not None:
2802 table_style = self.table_styles.get(table_name)
2803 if table_style is None:
2804 # If we can't find the table style, issue warning
2805 # and use the default table style.
2806 self.document.reporter.warning(
2807 'Can\'t find table style "%s". Using default.' % (
2808 table_name, ))
2809 table_name = TABLENAMEDEFAULT
2810 table_style = self.table_styles.get(table_name)
2811 if table_style is None:
2812 # If we can't find the default table style, issue a warning
2813 # and use a built-in default style.
2814 self.document.reporter.warning(
2815 'Can\'t find default table style "%s". Using built-in default.' % (
2816 table_name, ))
2817 table_style = BUILTIN_DEFAULT_TABLE_STYLE
2818 else:
2819 table_name = TABLENAMEDEFAULT
2820 table_style = self.table_styles.get(table_name)
2821 if table_style is None:
2822 # If we can't find the default table style, issue a warning
2823 # and use a built-in default style.
2824 self.document.reporter.warning(
2825 'Can\'t find default table style "%s". Using built-in default.' % (
2826 table_name, ))
2827 table_style = BUILTIN_DEFAULT_TABLE_STYLE
2828 return table_style
2830 def visit_table(self, node):
2831 self.table_count += 1
2832 table_style = self.get_table_style(node)
2833 table_name = '%s%%d' % TABLESTYLEPREFIX
2834 el1 = SubElement(self.automatic_styles, 'style:style', attrib={
2835 'style:name': self.rststyle(
2836 '%s' % table_name, ( self.table_count, )),
2837 'style:family': 'table',
2838 }, nsdict=SNSD)
2839 if table_style.backgroundcolor is None:
2840 el1_1 = SubElement(el1, 'style:table-properties', attrib={
2841 #'style:width': '17.59cm',
2842 #'table:align': 'margins',
2843 'table:align': 'left',
2844 'fo:margin-top': '0in',
2845 'fo:margin-bottom': '0.10in',
2846 }, nsdict=SNSD)
2847 else:
2848 el1_1 = SubElement(el1, 'style:table-properties', attrib={
2849 #'style:width': '17.59cm',
2850 'table:align': 'margins',
2851 'fo:margin-top': '0in',
2852 'fo:margin-bottom': '0.10in',
2853 'fo:background-color': table_style.backgroundcolor,
2854 }, nsdict=SNSD)
2855 # We use a single cell style for all cells in this table.
2856 # That's probably not correct, but seems to work.
2857 el2 = SubElement(self.automatic_styles, 'style:style', attrib={
2858 'style:name': self.rststyle(
2859 '%s.%%c%%d' % table_name, ( self.table_count, 'A', 1, )),
2860 'style:family': 'table-cell',
2861 }, nsdict=SNSD)
2862 thickness = self.settings.table_border_thickness
2863 if thickness is None:
2864 line_style1 = table_style.border
2865 else:
2866 line_style1 = '0.%03dcm solid #000000' % (thickness, )
2867 el2_1 = SubElement(el2, 'style:table-cell-properties', attrib={
2868 'fo:padding': '0.049cm',
2869 'fo:border-left': line_style1,
2870 'fo:border-right': line_style1,
2871 'fo:border-top': line_style1,
2872 'fo:border-bottom': line_style1,
2873 }, nsdict=SNSD)
2874 title = None
2875 for child in node.children:
2876 if child.tagname == 'title':
2877 title = child.astext()
2878 break
2879 if title is not None:
2880 el3 = self.append_p('table-title', title)
2881 else:
2882 pass
2883 el4 = SubElement(self.current_element, 'table:table', attrib={
2884 'table:name': self.rststyle(
2885 '%s' % table_name, ( self.table_count, )),
2886 'table:style-name': self.rststyle(
2887 '%s' % table_name, ( self.table_count, )),
2889 self.set_current_element(el4)
2890 self.current_table_style = el1
2891 self.table_width = 0.0
2893 def depart_table(self, node):
2894 attribkey = add_ns('style:width', nsdict=SNSD)
2895 attribval = '%.4fin' % (self.table_width, )
2896 el1 = self.current_table_style
2897 el2 = el1[0]
2898 el2.attrib[attribkey] = attribval
2899 self.set_to_parent()
2901 def visit_tgroup(self, node):
2902 self.column_count = ord('A') - 1
2904 def depart_tgroup(self, node):
2905 pass
2907 def visit_colspec(self, node):
2908 self.column_count += 1
2909 colspec_name = self.rststyle(
2910 '%s%%d.%%s' % TABLESTYLEPREFIX,
2911 (self.table_count, chr(self.column_count), )
2913 colwidth = node['colwidth'] / 12.0
2914 el1 = SubElement(self.automatic_styles, 'style:style', attrib={
2915 'style:name': colspec_name,
2916 'style:family': 'table-column',
2917 }, nsdict=SNSD)
2918 el1_1 = SubElement(el1, 'style:table-column-properties', attrib={
2919 'style:column-width': '%.4fin' % colwidth
2921 nsdict=SNSD)
2922 el2 = self.append_child('table:table-column', attrib={
2923 'table:style-name': colspec_name,
2925 self.table_width += colwidth
2927 def depart_colspec(self, node):
2928 pass
2930 def visit_thead(self, node):
2931 el = self.append_child('table:table-header-rows')
2932 self.set_current_element(el)
2933 self.in_thead = True
2934 self.paragraph_style_stack.append('Table_20_Heading')
2936 def depart_thead(self, node):
2937 self.set_to_parent()
2938 self.in_thead = False
2939 self.paragraph_style_stack.pop()
2941 def visit_row(self, node):
2942 self.column_count = ord('A') - 1
2943 el = self.append_child('table:table-row')
2944 self.set_current_element(el)
2946 def depart_row(self, node):
2947 self.set_to_parent()
2949 def visit_entry(self, node):
2950 self.column_count += 1
2951 cellspec_name = self.rststyle(
2952 '%s%%d.%%c%%d' % TABLESTYLEPREFIX,
2953 (self.table_count, 'A', 1, )
2955 attrib={
2956 'table:style-name': cellspec_name,
2957 'office:value-type': 'string',
2959 morecols = node.get('morecols', 0)
2960 if morecols > 0:
2961 attrib['table:number-columns-spanned'] = '%d' % (morecols + 1,)
2962 self.column_count += morecols
2963 morerows = node.get('morerows', 0)
2964 if morerows > 0:
2965 attrib['table:number-rows-spanned'] = '%d' % (morerows + 1,)
2966 el1 = self.append_child('table:table-cell', attrib=attrib)
2967 self.set_current_element(el1)
2969 def depart_entry(self, node):
2970 self.set_to_parent()
2972 def visit_tbody(self, node):
2973 pass
2975 def depart_tbody(self, node):
2976 pass
2978 def visit_target(self, node):
2980 # I don't know how to implement targets in ODF.
2981 # How do we create a target in oowriter? A cross-reference?
2982 if not (node.has_key('refuri') or node.has_key('refid')
2983 or node.has_key('refname')):
2984 pass
2985 else:
2986 pass
2988 def depart_target(self, node):
2989 pass
2991 def visit_title(self, node, move_ids=1, title_type='title'):
2992 if isinstance(node.parent, docutils.nodes.section):
2993 section_level = self.section_level
2994 if section_level > 7:
2995 self.document.reporter.warning(
2996 'Heading/section levels greater than 7 not supported.')
2997 self.document.reporter.warning(
2998 ' Reducing to heading level 7 for heading: "%s"' % (
2999 node.astext(), ))
3000 section_level = 7
3001 el1 = self.append_child('text:h', attrib = {
3002 'text:outline-level': '%d' % section_level,
3003 #'text:style-name': 'Heading_20_%d' % section_level,
3004 'text:style-name': self.rststyle(
3005 'heading%d', (section_level, )),
3007 self.append_pending_ids(el1)
3008 self.set_current_element(el1)
3009 elif isinstance(node.parent, docutils.nodes.document):
3010 # text = self.settings.title
3011 #else:
3012 # text = node.astext()
3013 el1 = SubElement(self.current_element, 'text:p', attrib = {
3014 'text:style-name': self.rststyle(title_type),
3016 self.append_pending_ids(el1)
3017 text = node.astext()
3018 self.title = text
3019 self.found_doc_title = True
3020 self.set_current_element(el1)
3022 def depart_title(self, node):
3023 if (isinstance(node.parent, docutils.nodes.section) or
3024 isinstance(node.parent, docutils.nodes.document)):
3025 self.set_to_parent()
3027 def visit_subtitle(self, node, move_ids=1):
3028 self.visit_title(node, move_ids, title_type='subtitle')
3030 def depart_subtitle(self, node):
3031 self.depart_title(node)
3033 def visit_title_reference(self, node):
3034 el = self.append_child('text:span', attrib={
3035 'text:style-name': self.rststyle('quotation')})
3036 el.text = self.encode(node.astext())
3037 raise nodes.SkipChildren()
3039 def depart_title_reference(self, node):
3040 pass
3042 def generate_table_of_content_entry_template(self, el1):
3043 for idx in range(1, 11):
3044 el2 = SubElement(el1,
3045 'text:table-of-content-entry-template',
3046 attrib={
3047 'text:outline-level': "%d" % (idx, ),
3048 'text:style-name': self.rststyle('contents-%d' % (idx, )),
3050 el3 = SubElement(el2, 'text:index-entry-chapter')
3051 el3 = SubElement(el2, 'text:index-entry-text')
3052 el3 = SubElement(el2, 'text:index-entry-tab-stop', attrib={
3053 'style:leader-char': ".",
3054 'style:type': "right",
3056 el3 = SubElement(el2, 'text:index-entry-page-number')
3058 def find_title_label(self, node, class_type, label_key):
3059 label = ''
3060 title_node = None
3061 for child in node.children:
3062 if isinstance(child, class_type):
3063 title_node = child
3064 break
3065 if title_node is not None:
3066 label = title_node.astext()
3067 else:
3068 label = self.language.labels[label_key]
3069 return label
3071 def visit_topic(self, node):
3072 if 'classes' in node.attributes:
3073 if 'contents' in node.attributes['classes']:
3074 label = self.find_title_label(node, docutils.nodes.title,
3075 'contents')
3076 if self.settings.generate_oowriter_toc:
3077 el1 = self.append_child('text:table-of-content', attrib={
3078 'text:name': 'Table of Contents1',
3079 'text:protected': 'true',
3080 'text:style-name': 'Sect1',
3082 el2 = SubElement(el1,
3083 'text:table-of-content-source',
3084 attrib={
3085 'text:outline-level': '10',
3087 el3 =SubElement(el2, 'text:index-title-template', attrib={
3088 'text:style-name': 'Contents_20_Heading',
3090 el3.text = label
3091 self.generate_table_of_content_entry_template(el2)
3092 el4 = SubElement(el1, 'text:index-body')
3093 el5 = SubElement(el4, 'text:index-title')
3094 el6 = SubElement(el5, 'text:p', attrib={
3095 'text:style-name': self.rststyle('contents-heading'),
3097 el6.text = label
3098 self.save_current_element = self.current_element
3099 self.table_of_content_index_body = el4
3100 self.set_current_element(el4)
3101 else:
3102 el = self.append_p('horizontalline')
3103 el = self.append_p('centeredtextbody')
3104 el1 = SubElement(el, 'text:span',
3105 attrib={'text:style-name': self.rststyle('strong')})
3106 el1.text = label
3107 self.in_table_of_contents = True
3108 elif 'abstract' in node.attributes['classes']:
3109 el = self.append_p('horizontalline')
3110 el = self.append_p('centeredtextbody')
3111 el1 = SubElement(el, 'text:span',
3112 attrib={'text:style-name': self.rststyle('strong')})
3113 label = self.find_title_label(node, docutils.nodes.title,
3114 'abstract')
3115 el1.text = label
3116 elif 'dedication' in node.attributes['classes']:
3117 el = self.append_p('horizontalline')
3118 el = self.append_p('centeredtextbody')
3119 el1 = SubElement(el, 'text:span',
3120 attrib={'text:style-name': self.rststyle('strong')})
3121 label = self.find_title_label(node, docutils.nodes.title,
3122 'dedication')
3123 el1.text = label
3125 def depart_topic(self, node):
3126 if 'classes' in node.attributes:
3127 if 'contents' in node.attributes['classes']:
3128 if self.settings.generate_oowriter_toc:
3129 self.update_toc_page_numbers(
3130 self.table_of_content_index_body)
3131 self.set_current_element(self.save_current_element)
3132 else:
3133 el = self.append_p('horizontalline')
3134 self.in_table_of_contents = False
3136 def update_toc_page_numbers(self, el):
3137 collection = []
3138 self.update_toc_collect(el, 0, collection)
3139 self.update_toc_add_numbers(collection)
3141 def update_toc_collect(self, el, level, collection):
3142 collection.append((level, el))
3143 level += 1
3144 for child_el in el.getchildren():
3145 if child_el.tag != 'text:index-body':
3146 self.update_toc_collect(child_el, level, collection)
3148 def update_toc_add_numbers(self, collection):
3149 for level, el1 in collection:
3150 if (el1.tag == 'text:p' and
3151 el1.text != 'Table of Contents'):
3152 el2 = SubElement(el1, 'text:tab')
3153 el2.tail = '9999'
3156 def visit_transition(self, node):
3157 el = self.append_p('horizontalline')
3159 def depart_transition(self, node):
3160 pass
3163 # Admonitions
3165 def visit_warning(self, node):
3166 self.generate_admonition(node, 'warning')
3168 def depart_warning(self, node):
3169 self.paragraph_style_stack.pop()
3171 def visit_attention(self, node):
3172 self.generate_admonition(node, 'attention')
3174 depart_attention = depart_warning
3176 def visit_caution(self, node):
3177 self.generate_admonition(node, 'caution')
3179 depart_caution = depart_warning
3181 def visit_danger(self, node):
3182 self.generate_admonition(node, 'danger')
3184 depart_danger = depart_warning
3186 def visit_error(self, node):
3187 self.generate_admonition(node, 'error')
3189 depart_error = depart_warning
3191 def visit_hint(self, node):
3192 self.generate_admonition(node, 'hint')
3194 depart_hint = depart_warning
3196 def visit_important(self, node):
3197 self.generate_admonition(node, 'important')
3199 depart_important = depart_warning
3201 def visit_note(self, node):
3202 self.generate_admonition(node, 'note')
3204 depart_note = depart_warning
3206 def visit_tip(self, node):
3207 self.generate_admonition(node, 'tip')
3209 depart_tip = depart_warning
3211 def visit_admonition(self, node):
3212 title = None
3213 for child in node.children:
3214 if child.tagname == 'title':
3215 title = child.astext()
3216 if title is None:
3217 classes1 = node.get('classes')
3218 if classes1:
3219 title = classes1[0]
3220 self.generate_admonition(node, 'generic', title)
3222 depart_admonition = depart_warning
3224 def generate_admonition(self, node, label, title=None):
3225 el1 = SubElement(self.current_element, 'text:p', attrib = {
3226 'text:style-name': self.rststyle('admon-%s-hdr', ( label, )),
3228 if title:
3229 el1.text = title
3230 else:
3231 el1.text = '%s!' % (label.capitalize(), )
3232 s1 = self.rststyle('admon-%s-body', ( label, ))
3233 self.paragraph_style_stack.append(s1)
3236 # Roles (e.g. subscript, superscript, strong, ...
3238 def visit_subscript(self, node):
3239 el = self.append_child('text:span', attrib={
3240 'text:style-name': 'rststyle-subscript',
3242 self.set_current_element(el)
3244 def depart_subscript(self, node):
3245 self.set_to_parent()
3247 def visit_superscript(self, node):
3248 el = self.append_child('text:span', attrib={
3249 'text:style-name': 'rststyle-superscript',
3251 self.set_current_element(el)
3253 def depart_superscript(self, node):
3254 self.set_to_parent()
3257 # Use an own reader to modify transformations done.
3258 class Reader(standalone.Reader):
3260 def get_transforms(self):
3261 default = standalone.Reader.get_transforms(self)
3262 if self.settings.create_links:
3263 return default
3264 return [ i
3265 for i in default
3266 if i is not references.DanglingReferences ]