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