Update documentation of length units.
[docutils.git] / docutils / docutils / writers / odf_odt / __init__.py
blob4a1e8fc137cde87f6d46fb0993cdbcb6834f85dd
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 itertools
27 import docutils
28 try:
29 import locale # module missing in Jython
30 except ImportError:
31 pass
32 from docutils import frontend, nodes, utils, writers, languages
33 from docutils.readers import standalone
34 from docutils.transforms import references
37 IMAGE_NAME_COUNTER = itertools.count()
38 WhichElementTree = ''
39 try:
40 # 1. Try to use lxml.
41 #from lxml import etree
42 #WhichElementTree = 'lxml'
43 raise ImportError('Ignoring lxml')
44 except ImportError, e:
45 try:
46 # 2. Try to use ElementTree from the Python standard library.
47 from xml.etree import ElementTree as etree
48 WhichElementTree = 'elementtree'
49 except ImportError, e:
50 try:
51 # 3. Try to use a version of ElementTree installed as a separate
52 # product.
53 from elementtree import ElementTree as etree
54 WhichElementTree = 'elementtree'
55 except ImportError, e:
56 s1 = 'Must install either a version of Python containing ' \
57 'ElementTree (Python version >=2.5) or install ElementTree.'
58 raise ImportError(s1)
61 # Import pygments and odtwriter pygments formatters if possible.
62 try:
63 import pygments
64 import pygments.lexers
65 from pygmentsformatter import OdtPygmentsProgFormatter, \
66 OdtPygmentsLaTeXFormatter
67 except (ImportError, SyntaxError), exp:
68 pygments = None
70 # check for the Python Imaging Library
71 try:
72 import PIL.Image
73 except ImportError:
74 try: # sometimes PIL modules are put in PYTHONPATH's root
75 import Image
76 class PIL(object): pass # dummy wrapper
77 PIL.Image = Image
78 except ImportError:
79 PIL = None
81 ## import warnings
82 ## warnings.warn('importing IPShellEmbed', UserWarning)
83 ## from IPython.Shell import IPShellEmbed
84 ## args = ['-pdb', '-pi1', 'In <\\#>: ', '-pi2', ' .\\D.: ',
85 ## '-po', 'Out<\\#>: ', '-nosep']
86 ## ipshell = IPShellEmbed(args,
87 ## banner = 'Entering IPython. Press Ctrl-D to exit.',
88 ## exit_msg = 'Leaving Interpreter, back to program.')
92 # ElementTree does not support getparent method (lxml does).
93 # This wrapper class and the following support functions provide
94 # that support for the ability to get the parent of an element.
96 if WhichElementTree == 'elementtree':
97 import weakref
98 _parents = weakref.WeakKeyDictionary()
99 if isinstance(etree.Element, type):
100 _ElementInterface = etree.Element
101 else:
102 _ElementInterface = etree._ElementInterface
103 class _ElementInterfaceWrapper(_ElementInterface):
104 def __init__(self, tag, attrib=None):
105 _ElementInterface.__init__(self, tag, attrib)
106 _parents[self] = None
107 def setparent(self, parent):
108 _parents[self] = parent
109 def getparent(self):
110 return _parents[self]
114 # Constants and globals
116 SPACES_PATTERN = re.compile(r'( +)')
117 TABS_PATTERN = re.compile(r'(\t+)')
118 FILL_PAT1 = re.compile(r'^ +')
119 FILL_PAT2 = re.compile(r' {2,}')
121 TABLESTYLEPREFIX = 'rststyle-table-'
122 TABLENAMEDEFAULT = '%s0' % TABLESTYLEPREFIX
123 TABLEPROPERTYNAMES = ('border', 'border-top', 'border-left',
124 'border-right', 'border-bottom', )
126 GENERATOR_DESC = 'Docutils.org/odf_odt'
128 NAME_SPACE_1 = 'urn:oasis:names:tc:opendocument:xmlns:office:1.0'
130 CONTENT_NAMESPACE_DICT = CNSD = {
131 # 'office:version': '1.0',
132 'chart': 'urn:oasis:names:tc:opendocument:xmlns:chart:1.0',
133 'dc': 'http://purl.org/dc/elements/1.1/',
134 'dom': 'http://www.w3.org/2001/xml-events',
135 'dr3d': 'urn:oasis:names:tc:opendocument:xmlns:dr3d:1.0',
136 'draw': 'urn:oasis:names:tc:opendocument:xmlns:drawing:1.0',
137 'fo': 'urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0',
138 'form': 'urn:oasis:names:tc:opendocument:xmlns:form:1.0',
139 'math': 'http://www.w3.org/1998/Math/MathML',
140 'meta': 'urn:oasis:names:tc:opendocument:xmlns:meta:1.0',
141 'number': 'urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0',
142 'office': NAME_SPACE_1,
143 'ooo': 'http://openoffice.org/2004/office',
144 'oooc': 'http://openoffice.org/2004/calc',
145 'ooow': 'http://openoffice.org/2004/writer',
146 'presentation': 'urn:oasis:names:tc:opendocument:xmlns:presentation:1.0',
148 'script': 'urn:oasis:names:tc:opendocument:xmlns:script:1.0',
149 'style': 'urn:oasis:names:tc:opendocument:xmlns:style:1.0',
150 'svg': 'urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0',
151 'table': 'urn:oasis:names:tc:opendocument:xmlns:table:1.0',
152 'text': 'urn:oasis:names:tc:opendocument:xmlns:text:1.0',
153 'xforms': 'http://www.w3.org/2002/xforms',
154 'xlink': 'http://www.w3.org/1999/xlink',
155 'xsd': 'http://www.w3.org/2001/XMLSchema',
156 'xsi': 'http://www.w3.org/2001/XMLSchema-instance',
159 STYLES_NAMESPACE_DICT = SNSD = {
160 # 'office:version': '1.0',
161 'chart': 'urn:oasis:names:tc:opendocument:xmlns:chart:1.0',
162 'dc': 'http://purl.org/dc/elements/1.1/',
163 'dom': 'http://www.w3.org/2001/xml-events',
164 'dr3d': 'urn:oasis:names:tc:opendocument:xmlns:dr3d:1.0',
165 'draw': 'urn:oasis:names:tc:opendocument:xmlns:drawing:1.0',
166 'fo': 'urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0',
167 'form': 'urn:oasis:names:tc:opendocument:xmlns:form:1.0',
168 'math': 'http://www.w3.org/1998/Math/MathML',
169 'meta': 'urn:oasis:names:tc:opendocument:xmlns:meta:1.0',
170 'number': 'urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0',
171 'office': NAME_SPACE_1,
172 'presentation': 'urn:oasis:names:tc:opendocument:xmlns:presentation:1.0',
173 'ooo': 'http://openoffice.org/2004/office',
174 'oooc': 'http://openoffice.org/2004/calc',
175 'ooow': 'http://openoffice.org/2004/writer',
176 'script': 'urn:oasis:names:tc:opendocument:xmlns:script:1.0',
177 'style': 'urn:oasis:names:tc:opendocument:xmlns:style:1.0',
178 'svg': 'urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0',
179 'table': 'urn:oasis:names:tc:opendocument:xmlns:table:1.0',
180 'text': 'urn:oasis:names:tc:opendocument:xmlns:text:1.0',
181 'xlink': 'http://www.w3.org/1999/xlink',
184 MANIFEST_NAMESPACE_DICT = MANNSD = {
185 'manifest': 'urn:oasis:names:tc:opendocument:xmlns:manifest:1.0',
188 META_NAMESPACE_DICT = METNSD = {
189 # 'office:version': '1.0',
190 'dc': 'http://purl.org/dc/elements/1.1/',
191 'meta': 'urn:oasis:names:tc:opendocument:xmlns:meta:1.0',
192 'office': NAME_SPACE_1,
193 'ooo': 'http://openoffice.org/2004/office',
194 'xlink': 'http://www.w3.org/1999/xlink',
198 # Attribute dictionaries for use with ElementTree (not lxml), which
199 # does not support use of nsmap parameter on Element() and SubElement().
201 CONTENT_NAMESPACE_ATTRIB = {
202 #'office:version': '1.0',
203 'xmlns:chart': 'urn:oasis:names:tc:opendocument:xmlns:chart:1.0',
204 'xmlns:dc': 'http://purl.org/dc/elements/1.1/',
205 'xmlns:dom': 'http://www.w3.org/2001/xml-events',
206 'xmlns:dr3d': 'urn:oasis:names:tc:opendocument:xmlns:dr3d:1.0',
207 'xmlns:draw': 'urn:oasis:names:tc:opendocument:xmlns:drawing:1.0',
208 'xmlns:fo': 'urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0',
209 'xmlns:form': 'urn:oasis:names:tc:opendocument:xmlns:form:1.0',
210 'xmlns:math': 'http://www.w3.org/1998/Math/MathML',
211 'xmlns:meta': 'urn:oasis:names:tc:opendocument:xmlns:meta:1.0',
212 'xmlns:number': 'urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0',
213 'xmlns:office': NAME_SPACE_1,
214 'xmlns:presentation': 'urn:oasis:names:tc:opendocument:xmlns:presentation:1.0',
215 'xmlns:ooo': 'http://openoffice.org/2004/office',
216 'xmlns:oooc': 'http://openoffice.org/2004/calc',
217 'xmlns:ooow': 'http://openoffice.org/2004/writer',
218 'xmlns:script': 'urn:oasis:names:tc:opendocument:xmlns:script:1.0',
219 'xmlns:style': 'urn:oasis:names:tc:opendocument:xmlns:style:1.0',
220 'xmlns:svg': 'urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0',
221 'xmlns:table': 'urn:oasis:names:tc:opendocument:xmlns:table:1.0',
222 'xmlns:text': 'urn:oasis:names:tc:opendocument:xmlns:text:1.0',
223 'xmlns:xforms': 'http://www.w3.org/2002/xforms',
224 'xmlns:xlink': 'http://www.w3.org/1999/xlink',
225 'xmlns:xsd': 'http://www.w3.org/2001/XMLSchema',
226 'xmlns:xsi': 'http://www.w3.org/2001/XMLSchema-instance',
229 STYLES_NAMESPACE_ATTRIB = {
230 #'office:version': '1.0',
231 'xmlns:chart': 'urn:oasis:names:tc:opendocument:xmlns:chart:1.0',
232 'xmlns:dc': 'http://purl.org/dc/elements/1.1/',
233 'xmlns:dom': 'http://www.w3.org/2001/xml-events',
234 'xmlns:dr3d': 'urn:oasis:names:tc:opendocument:xmlns:dr3d:1.0',
235 'xmlns:draw': 'urn:oasis:names:tc:opendocument:xmlns:drawing:1.0',
236 'xmlns:fo': 'urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0',
237 'xmlns:form': 'urn:oasis:names:tc:opendocument:xmlns:form:1.0',
238 'xmlns:math': 'http://www.w3.org/1998/Math/MathML',
239 'xmlns:meta': 'urn:oasis:names:tc:opendocument:xmlns:meta:1.0',
240 'xmlns:number': 'urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0',
241 'xmlns:office': NAME_SPACE_1,
242 'xmlns:presentation': 'urn:oasis:names:tc:opendocument:xmlns:presentation:1.0',
243 'xmlns:ooo': 'http://openoffice.org/2004/office',
244 'xmlns:oooc': 'http://openoffice.org/2004/calc',
245 'xmlns:ooow': 'http://openoffice.org/2004/writer',
246 'xmlns:script': 'urn:oasis:names:tc:opendocument:xmlns:script:1.0',
247 'xmlns:style': 'urn:oasis:names:tc:opendocument:xmlns:style:1.0',
248 'xmlns:svg': 'urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0',
249 'xmlns:table': 'urn:oasis:names:tc:opendocument:xmlns:table:1.0',
250 'xmlns:text': 'urn:oasis:names:tc:opendocument:xmlns:text:1.0',
251 'xmlns:xlink': 'http://www.w3.org/1999/xlink',
254 MANIFEST_NAMESPACE_ATTRIB = {
255 'xmlns:manifest': 'urn:oasis:names:tc:opendocument:xmlns:manifest:1.0',
258 META_NAMESPACE_ATTRIB = {
259 #'office:version': '1.0',
260 'xmlns:dc': 'http://purl.org/dc/elements/1.1/',
261 'xmlns:meta': 'urn:oasis:names:tc:opendocument:xmlns:meta:1.0',
262 'xmlns:office': NAME_SPACE_1,
263 'xmlns:ooo': 'http://openoffice.org/2004/office',
264 'xmlns:xlink': 'http://www.w3.org/1999/xlink',
269 # Functions
273 # ElementTree support functions.
274 # In order to be able to get the parent of elements, must use these
275 # instead of the functions with same name provided by ElementTree.
277 def Element(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.Element(tag, attrib, nsmap=nsmap)
283 else:
284 el = _ElementInterfaceWrapper(tag, attrib)
285 return el
287 def SubElement(parent, tag, attrib=None, nsmap=None, nsdict=CNSD):
288 if attrib is None:
289 attrib = {}
290 tag, attrib = fix_ns(tag, attrib, nsdict)
291 if WhichElementTree == 'lxml':
292 el = etree.SubElement(parent, tag, attrib, nsmap=nsmap)
293 else:
294 el = _ElementInterfaceWrapper(tag, attrib)
295 parent.append(el)
296 el.setparent(parent)
297 return el
299 def fix_ns(tag, attrib, nsdict):
300 nstag = add_ns(tag, nsdict)
301 nsattrib = {}
302 for key, val in attrib.iteritems():
303 nskey = add_ns(key, nsdict)
304 nsattrib[nskey] = val
305 return nstag, nsattrib
307 def add_ns(tag, nsdict=CNSD):
308 if WhichElementTree == 'lxml':
309 nstag, name = tag.split(':')
310 ns = nsdict.get(nstag)
311 if ns is None:
312 raise RuntimeError, 'Invalid namespace prefix: %s' % nstag
313 tag = '{%s}%s' % (ns, name,)
314 return tag
316 def ToString(et):
317 outstream = StringIO.StringIO()
318 if sys.version_info >= (3, 2):
319 et.write(outstream, encoding="unicode")
320 else:
321 et.write(outstream)
322 s1 = outstream.getvalue()
323 outstream.close()
324 return s1
327 def escape_cdata(text):
328 text = text.replace("&", "&amp;")
329 text = text.replace("<", "&lt;")
330 text = text.replace(">", "&gt;")
331 ascii = ''
332 for char in text:
333 if ord(char) >= ord("\x7f"):
334 ascii += "&#x%X;" % ( ord(char), )
335 else:
336 ascii += char
337 return ascii
341 WORD_SPLIT_PAT1 = re.compile(r'\b(\w*)\b\W*')
343 def split_words(line):
344 # We need whitespace at the end of the string for our regexpr.
345 line += ' '
346 words = []
347 pos1 = 0
348 mo = WORD_SPLIT_PAT1.search(line, pos1)
349 while mo is not None:
350 word = mo.groups()[0]
351 words.append(word)
352 pos1 = mo.end()
353 mo = WORD_SPLIT_PAT1.search(line, pos1)
354 return words
358 # Classes
362 class TableStyle(object):
363 def __init__(self, border=None, backgroundcolor=None):
364 self.border = border
365 self.backgroundcolor = backgroundcolor
366 def get_border_(self):
367 return self.border_
368 def set_border_(self, border):
369 self.border_ = border
370 border = property(get_border_, set_border_)
371 def get_backgroundcolor_(self):
372 return self.backgroundcolor_
373 def set_backgroundcolor_(self, backgroundcolor):
374 self.backgroundcolor_ = backgroundcolor
375 backgroundcolor = property(get_backgroundcolor_, set_backgroundcolor_)
377 BUILTIN_DEFAULT_TABLE_STYLE = TableStyle(
378 border = '0.0007in solid #000000')
381 # Information about the indentation level for lists nested inside
382 # other contexts, e.g. dictionary lists.
383 class ListLevel(object):
384 def __init__(self, level, sibling_level=True, nested_level=True):
385 self.level = level
386 self.sibling_level = sibling_level
387 self.nested_level = nested_level
388 def set_sibling(self, sibling_level): self.sibling_level = sibling_level
389 def get_sibling(self): return self.sibling_level
390 def set_nested(self, nested_level): self.nested_level = nested_level
391 def get_nested(self): return self.nested_level
392 def set_level(self, level): self.level = level
393 def get_level(self): return self.level
396 class Writer(writers.Writer):
398 MIME_TYPE = 'application/vnd.oasis.opendocument.text'
399 EXTENSION = '.odt'
401 supported = ('odt', )
402 """Formats this writer supports."""
404 default_stylesheet = 'styles' + EXTENSION
406 default_stylesheet_path = utils.relative_path(
407 os.path.join(os.getcwd(), 'dummy'),
408 os.path.join(os.path.dirname(__file__), default_stylesheet))
410 default_template = 'template.txt'
412 default_template_path = utils.relative_path(
413 os.path.join(os.getcwd(), 'dummy'),
414 os.path.join(os.path.dirname(__file__), default_template))
416 settings_spec = (
417 'ODF-Specific Options',
418 None,
420 ('Specify a stylesheet. '
421 'Default: "%s"' % default_stylesheet_path,
422 ['--stylesheet'],
424 'default': default_stylesheet_path,
425 'dest': 'stylesheet'
427 ('Specify a configuration/mapping file relative to the '
428 'current working '
429 'directory for additional ODF options. '
430 'In particular, this file may contain a section named '
431 '"Formats" that maps default style names to '
432 'names to be used in the resulting output file allowing for '
433 'adhering to external standards. '
434 'For more info and the format of the configuration/mapping file, '
435 'see the odtwriter doc.',
436 ['--odf-config-file'],
437 {'metavar': '<file>'}),
438 ('Obfuscate email addresses to confuse harvesters while still '
439 'keeping email links usable with standards-compliant browsers.',
440 ['--cloak-email-addresses'],
441 {'default': False,
442 'action': 'store_true',
443 'dest': 'cloak_email_addresses',
444 'validator': frontend.validate_boolean}),
445 ('Do not obfuscate email addresses.',
446 ['--no-cloak-email-addresses'],
447 {'default': False,
448 'action': 'store_false',
449 'dest': 'cloak_email_addresses',
450 'validator': frontend.validate_boolean}),
451 ('Specify the thickness of table borders in thousands of a cm. '
452 'Default is 35.',
453 ['--table-border-thickness'],
454 {'default': None,
455 'validator': frontend.validate_nonnegative_int}),
456 ('Add syntax highlighting in literal code blocks.',
457 ['--add-syntax-highlighting'],
458 {'default': False,
459 'action': 'store_true',
460 'dest': 'add_syntax_highlighting',
461 'validator': frontend.validate_boolean}),
462 ('Do not add syntax highlighting in literal code blocks. (default)',
463 ['--no-syntax-highlighting'],
464 {'default': False,
465 'action': 'store_false',
466 'dest': 'add_syntax_highlighting',
467 'validator': frontend.validate_boolean}),
468 ('Create sections for headers. (default)',
469 ['--create-sections'],
470 {'default': True,
471 'action': 'store_true',
472 'dest': 'create_sections',
473 'validator': frontend.validate_boolean}),
474 ('Do not create sections for headers.',
475 ['--no-sections'],
476 {'default': True,
477 'action': 'store_false',
478 'dest': 'create_sections',
479 'validator': frontend.validate_boolean}),
480 ('Create links.',
481 ['--create-links'],
482 {'default': False,
483 'action': 'store_true',
484 'dest': 'create_links',
485 'validator': frontend.validate_boolean}),
486 ('Do not create links. (default)',
487 ['--no-links'],
488 {'default': False,
489 'action': 'store_false',
490 'dest': 'create_links',
491 'validator': frontend.validate_boolean}),
492 ('Generate endnotes at end of document, not footnotes '
493 'at bottom of page.',
494 ['--endnotes-end-doc'],
495 {'default': False,
496 'action': 'store_true',
497 'dest': 'endnotes_end_doc',
498 'validator': frontend.validate_boolean}),
499 ('Generate footnotes at bottom of page, not endnotes '
500 'at end of document. (default)',
501 ['--no-endnotes-end-doc'],
502 {'default': False,
503 'action': 'store_false',
504 'dest': 'endnotes_end_doc',
505 'validator': frontend.validate_boolean}),
506 ('Generate a bullet list table of contents, not '
507 'an ODF/oowriter table of contents.',
508 ['--generate-list-toc'],
509 {'default': True,
510 'action': 'store_false',
511 'dest': 'generate_oowriter_toc',
512 'validator': frontend.validate_boolean}),
513 ('Generate an ODF/oowriter table of contents, not '
514 'a bullet list. (default)',
515 ['--generate-oowriter-toc'],
516 {'default': True,
517 'action': 'store_true',
518 'dest': 'generate_oowriter_toc',
519 'validator': frontend.validate_boolean}),
520 ('Specify the contents of an custom header line. '
521 'See odf_odt writer documentation for details '
522 'about special field character sequences.',
523 ['--custom-odt-header'],
524 { 'default': '',
525 'dest': 'custom_header',
527 ('Specify the contents of an custom footer line. '
528 'See odf_odt writer documentation for details '
529 'about special field character sequences.',
530 ['--custom-odt-footer'],
531 { 'default': '',
532 'dest': 'custom_footer',
537 settings_defaults = {
538 'output_encoding_error_handler': 'xmlcharrefreplace',
541 relative_path_settings = (
542 'stylesheet_path',
545 config_section = 'odf_odt writer'
546 config_section_dependencies = (
547 'writers',
550 def __init__(self):
551 writers.Writer.__init__(self)
552 self.translator_class = ODFTranslator
554 def translate(self):
555 self.settings = self.document.settings
556 self.visitor = self.translator_class(self.document)
557 self.visitor.retrieve_styles(self.EXTENSION)
558 self.document.walkabout(self.visitor)
559 self.visitor.add_doc_title()
560 self.assemble_my_parts()
561 self.output = self.parts['whole']
563 def assemble_my_parts(self):
564 """Assemble the `self.parts` dictionary. Extend in subclasses.
566 writers.Writer.assemble_parts(self)
567 f = tempfile.NamedTemporaryFile()
568 zfile = zipfile.ZipFile(f, 'w', zipfile.ZIP_DEFLATED)
569 self.write_zip_str(zfile, 'mimetype', self.MIME_TYPE,
570 compress_type=zipfile.ZIP_STORED)
571 content = self.visitor.content_astext()
572 self.write_zip_str(zfile, 'content.xml', content)
573 s1 = self.create_manifest()
574 self.write_zip_str(zfile, 'META-INF/manifest.xml', s1)
575 s1 = self.create_meta()
576 self.write_zip_str(zfile, 'meta.xml', s1)
577 s1 = self.get_stylesheet()
578 # Set default language in document to be generated.
579 # Language is specified by the -l/--language command line option.
580 # The format is described in BCP 47. If region is omitted, we use
581 # local.normalize(ll) to obtain a region.
582 language_code = None
583 region_code = None
584 if self.visitor.language_code:
585 language_ids = self.visitor.language_code.replace('_', '-')
586 language_ids = language_ids.split('-')
587 # first tag is primary language tag
588 language_code = language_ids[0].lower()
589 # 2-letter region subtag may follow in 2nd or 3rd position
590 for subtag in language_ids[1:]:
591 if len(subtag) == 2 and subtag.isalpha():
592 region_code = subtag.upper()
593 break
594 elif len(subtag) == 1:
595 break # 1-letter tag is never before valid region tag
596 if region_code is None:
597 try:
598 rcode = locale.normalize(language_code)
599 except NameError:
600 rcode = language_code
601 rcode = rcode.split('_')
602 if len(rcode) > 1:
603 rcode = rcode[1].split('.')
604 region_code = rcode[0]
605 if region_code is None:
606 self.document.reporter.warning(
607 'invalid language-region.\n'
608 ' Could not find region with locale.normalize().\n'
609 ' Please specify both language and region (ll-RR).\n'
610 ' Examples: es-MX (Spanish, Mexico),\n'
611 ' en-AU (English, Australia).')
612 # Update the style ElementTree with the language and region.
613 # Note that we keep a reference to the modified node because
614 # it is possible that ElementTree will throw away the Python
615 # representation of the updated node if we do not.
616 updated, new_dom_styles, updated_node = self.update_stylesheet(
617 self.visitor.get_dom_stylesheet(), language_code, region_code)
618 if updated:
619 s1 = etree.tostring(new_dom_styles)
620 self.write_zip_str(zfile, 'styles.xml', s1)
621 self.store_embedded_files(zfile)
622 self.copy_from_stylesheet(zfile)
623 zfile.close()
624 f.seek(0)
625 whole = f.read()
626 f.close()
627 self.parts['whole'] = whole
628 self.parts['encoding'] = self.document.settings.output_encoding
629 self.parts['version'] = docutils.__version__
631 def update_stylesheet(self, stylesheet_root, language_code, region_code):
632 """Update xml style sheet element with language and region/country."""
633 updated = False
634 modified_nodes = set()
635 if language_code is not None or region_code is not None:
636 n1 = stylesheet_root.find(
637 '{urn:oasis:names:tc:opendocument:xmlns:office:1.0}'
638 'styles')
639 if n1 is None:
640 raise RuntimeError(
641 "Cannot find 'styles' element in styles.odt/styles.xml")
642 n2_nodes = n1.findall(
643 '{urn:oasis:names:tc:opendocument:xmlns:style:1.0}'
644 'default-style')
645 if not n2_nodes:
646 raise RuntimeError(
647 "Cannot find 'default-style' "
648 "element in styles.xml")
649 for node in n2_nodes:
650 family = node.attrib.get(
651 '{urn:oasis:names:tc:opendocument:xmlns:style:1.0}'
652 'family')
653 if family == 'paragraph' or family == 'graphic':
654 n3 = node.find(
655 '{urn:oasis:names:tc:opendocument:xmlns:style:1.0}'
656 'text-properties')
657 if n3 is None:
658 raise RuntimeError(
659 "Cannot find 'text-properties' "
660 "element in styles.xml")
661 if language_code is not None:
662 n3.attrib[
663 '{urn:oasis:names:tc:opendocument:xmlns:'
664 'xsl-fo-compatible:1.0}language'] = language_code
665 n3.attrib[
666 '{urn:oasis:names:tc:opendocument:xmlns:'
667 'style:1.0}language-complex'] = language_code
668 updated = True
669 modified_nodes.add(n3)
670 if region_code is not None:
671 n3.attrib[
672 '{urn:oasis:names:tc:opendocument:xmlns:'
673 'xsl-fo-compatible:1.0}country'] = region_code
674 n3.attrib[
675 '{urn:oasis:names:tc:opendocument:xmlns:'
676 'style:1.0}country-complex'] = region_code
677 updated = True
678 modified_nodes.add(n3)
679 return updated, stylesheet_root, modified_nodes
681 def write_zip_str(
682 self, zfile, name, bytes, compress_type=zipfile.ZIP_DEFLATED):
683 localtime = time.localtime(time.time())
684 zinfo = zipfile.ZipInfo(name, localtime)
685 # Add some standard UNIX file access permissions (-rw-r--r--).
686 zinfo.external_attr = (0x81a4 & 0xFFFF) << 16L
687 zinfo.compress_type = compress_type
688 zfile.writestr(zinfo, bytes)
690 def store_embedded_files(self, zfile):
691 embedded_files = self.visitor.get_embedded_file_list()
692 for source, destination in embedded_files:
693 if source is None:
694 continue
695 try:
696 zfile.write(source, destination)
697 except OSError, e:
698 self.document.reporter.warning(
699 "Can't open file %s." % (source, ))
701 def get_settings(self):
703 modeled after get_stylesheet
705 stylespath = self.settings.stylesheet
706 zfile = zipfile.ZipFile(stylespath, 'r')
707 s1 = zfile.read('settings.xml')
708 zfile.close()
709 return s1
711 def get_stylesheet(self):
712 """Get the stylesheet from the visitor.
713 Ask the visitor to setup the page.
715 s1 = self.visitor.setup_page()
716 return s1
718 def copy_from_stylesheet(self, outzipfile):
719 """Copy images, settings, etc from the stylesheet doc into target doc.
721 stylespath = self.settings.stylesheet
722 inzipfile = zipfile.ZipFile(stylespath, 'r')
723 # Copy the styles.
724 s1 = inzipfile.read('settings.xml')
725 self.write_zip_str(outzipfile, 'settings.xml', s1)
726 # Copy the images.
727 namelist = inzipfile.namelist()
728 for name in namelist:
729 if name.startswith('Pictures/'):
730 imageobj = inzipfile.read(name)
731 outzipfile.writestr(name, imageobj)
732 inzipfile.close()
734 def assemble_parts(self):
735 pass
737 def create_manifest(self):
738 if WhichElementTree == 'lxml':
739 root = Element('manifest:manifest',
740 nsmap=MANIFEST_NAMESPACE_DICT,
741 nsdict=MANIFEST_NAMESPACE_DICT,
743 else:
744 root = Element('manifest:manifest',
745 attrib=MANIFEST_NAMESPACE_ATTRIB,
746 nsdict=MANIFEST_NAMESPACE_DICT,
748 doc = etree.ElementTree(root)
749 SubElement(root, 'manifest:file-entry', attrib={
750 'manifest:media-type': self.MIME_TYPE,
751 'manifest:full-path': '/',
752 }, nsdict=MANNSD)
753 SubElement(root, 'manifest:file-entry', attrib={
754 'manifest:media-type': 'text/xml',
755 'manifest:full-path': 'content.xml',
756 }, nsdict=MANNSD)
757 SubElement(root, 'manifest:file-entry', attrib={
758 'manifest:media-type': 'text/xml',
759 'manifest:full-path': 'styles.xml',
760 }, nsdict=MANNSD)
761 SubElement(root, 'manifest:file-entry', attrib={
762 'manifest:media-type': 'text/xml',
763 'manifest:full-path': 'settings.xml',
764 }, nsdict=MANNSD)
765 SubElement(root, 'manifest:file-entry', attrib={
766 'manifest:media-type': 'text/xml',
767 'manifest:full-path': 'meta.xml',
768 }, nsdict=MANNSD)
769 s1 = ToString(doc)
770 doc = minidom.parseString(s1)
771 s1 = doc.toprettyxml(' ')
772 return s1
774 def create_meta(self):
775 if WhichElementTree == 'lxml':
776 root = Element('office:document-meta',
777 nsmap=META_NAMESPACE_DICT,
778 nsdict=META_NAMESPACE_DICT,
780 else:
781 root = Element('office:document-meta',
782 attrib=META_NAMESPACE_ATTRIB,
783 nsdict=META_NAMESPACE_DICT,
785 doc = etree.ElementTree(root)
786 root = SubElement(root, 'office:meta', nsdict=METNSD)
787 el1 = SubElement(root, 'meta:generator', nsdict=METNSD)
788 el1.text = 'Docutils/rst2odf.py/%s' % (VERSION, )
789 s1 = os.environ.get('USER', '')
790 el1 = SubElement(root, 'meta:initial-creator', nsdict=METNSD)
791 el1.text = s1
792 s2 = time.strftime('%Y-%m-%dT%H:%M:%S', time.localtime())
793 el1 = SubElement(root, 'meta:creation-date', nsdict=METNSD)
794 el1.text = s2
795 el1 = SubElement(root, 'dc:creator', nsdict=METNSD)
796 el1.text = s1
797 el1 = SubElement(root, 'dc:date', nsdict=METNSD)
798 el1.text = s2
799 el1 = SubElement(root, 'dc:language', nsdict=METNSD)
800 el1.text = 'en-US'
801 el1 = SubElement(root, 'meta:editing-cycles', nsdict=METNSD)
802 el1.text = '1'
803 el1 = SubElement(root, 'meta:editing-duration', nsdict=METNSD)
804 el1.text = 'PT00M01S'
805 title = self.visitor.get_title()
806 el1 = SubElement(root, 'dc:title', nsdict=METNSD)
807 if title:
808 el1.text = title
809 else:
810 el1.text = '[no title]'
811 meta_dict = self.visitor.get_meta_dict()
812 keywordstr = meta_dict.get('keywords')
813 if keywordstr is not None:
814 keywords = split_words(keywordstr)
815 for keyword in keywords:
816 el1 = SubElement(root, 'meta:keyword', nsdict=METNSD)
817 el1.text = keyword
818 description = meta_dict.get('description')
819 if description is not None:
820 el1 = SubElement(root, 'dc:description', nsdict=METNSD)
821 el1.text = description
822 s1 = ToString(doc)
823 #doc = minidom.parseString(s1)
824 #s1 = doc.toprettyxml(' ')
825 return s1
828 # class ODFTranslator(nodes.SparseNodeVisitor):
829 class ODFTranslator(nodes.GenericNodeVisitor):
831 used_styles = (
832 'attribution', 'blockindent', 'blockquote', 'blockquote-bulletitem',
833 'blockquote-bulletlist', 'blockquote-enumitem', 'blockquote-enumlist',
834 'bulletitem', 'bulletlist',
835 'caption', 'legend',
836 'centeredtextbody', 'codeblock', 'codeblock-indented',
837 'codeblock-classname', 'codeblock-comment', 'codeblock-functionname',
838 'codeblock-keyword', 'codeblock-name', 'codeblock-number',
839 'codeblock-operator', 'codeblock-string', 'emphasis', 'enumitem',
840 'enumlist', 'epigraph', 'epigraph-bulletitem', 'epigraph-bulletlist',
841 'epigraph-enumitem', 'epigraph-enumlist', 'footer',
842 'footnote', 'citation',
843 'header', 'highlights', 'highlights-bulletitem',
844 'highlights-bulletlist', 'highlights-enumitem', 'highlights-enumlist',
845 'horizontalline', 'inlineliteral', 'quotation', 'rubric',
846 'strong', 'table-title', 'textbody', 'tocbulletlist', 'tocenumlist',
847 'title',
848 'subtitle',
849 'heading1',
850 'heading2',
851 'heading3',
852 'heading4',
853 'heading5',
854 'heading6',
855 'heading7',
856 'admon-attention-hdr',
857 'admon-attention-body',
858 'admon-caution-hdr',
859 'admon-caution-body',
860 'admon-danger-hdr',
861 'admon-danger-body',
862 'admon-error-hdr',
863 'admon-error-body',
864 'admon-generic-hdr',
865 'admon-generic-body',
866 'admon-hint-hdr',
867 'admon-hint-body',
868 'admon-important-hdr',
869 'admon-important-body',
870 'admon-note-hdr',
871 'admon-note-body',
872 'admon-tip-hdr',
873 'admon-tip-body',
874 'admon-warning-hdr',
875 'admon-warning-body',
876 'tableoption',
877 'tableoption.%c', 'tableoption.%c%d', 'Table%d', 'Table%d.%c',
878 'Table%d.%c%d',
879 'lineblock1',
880 'lineblock2',
881 'lineblock3',
882 'lineblock4',
883 'lineblock5',
884 'lineblock6',
885 'image', 'figureframe',
888 def __init__(self, document):
889 #nodes.SparseNodeVisitor.__init__(self, document)
890 nodes.GenericNodeVisitor.__init__(self, document)
891 self.settings = document.settings
892 self.language_code = self.settings.language_code
893 self.language = languages.get_language(
894 self.language_code,
895 document.reporter)
896 self.format_map = {}
897 if self.settings.odf_config_file:
898 from ConfigParser import ConfigParser
900 parser = ConfigParser()
901 parser.read(self.settings.odf_config_file)
902 for rststyle, format in parser.items("Formats"):
903 if rststyle not in self.used_styles:
904 self.document.reporter.warning(
905 'Style "%s" is not a style used by odtwriter.' % (
906 rststyle, ))
907 if sys.version_info.major == 2:
908 self.format_map[rststyle] = format.decode('utf-8')
909 self.section_level = 0
910 self.section_count = 0
911 # Create ElementTree content and styles documents.
912 if WhichElementTree == 'lxml':
913 root = Element(
914 'office:document-content',
915 nsmap=CONTENT_NAMESPACE_DICT,
917 else:
918 root = Element(
919 'office:document-content',
920 attrib=CONTENT_NAMESPACE_ATTRIB,
922 self.content_tree = etree.ElementTree(element=root)
923 self.current_element = root
924 SubElement(root, 'office:scripts')
925 SubElement(root, 'office:font-face-decls')
926 el = SubElement(root, 'office:automatic-styles')
927 self.automatic_styles = el
928 el = SubElement(root, 'office:body')
929 el = self.generate_content_element(el)
930 self.current_element = el
931 self.body_text_element = el
932 self.paragraph_style_stack = [self.rststyle('textbody'), ]
933 self.list_style_stack = []
934 self.table_count = 0
935 self.column_count = ord('A') - 1
936 self.trace_level = -1
937 self.optiontablestyles_generated = False
938 self.field_name = None
939 self.field_element = None
940 self.title = None
941 self.image_count = 0
942 self.image_style_count = 0
943 self.image_dict = {}
944 self.embedded_file_list = []
945 self.syntaxhighlighting = 1
946 self.syntaxhighlight_lexer = 'python'
947 self.header_content = []
948 self.footer_content = []
949 self.in_header = False
950 self.in_footer = False
951 self.blockstyle = ''
952 self.in_table_of_contents = False
953 self.table_of_content_index_body = None
954 self.list_level = 0
955 self.def_list_level = 0
956 self.footnote_ref_dict = {}
957 self.footnote_list = []
958 self.footnote_chars_idx = 0
959 self.footnote_level = 0
960 self.pending_ids = [ ]
961 self.in_paragraph = False
962 self.found_doc_title = False
963 self.bumped_list_level_stack = []
964 self.meta_dict = {}
965 self.line_block_level = 0
966 self.line_indent_level = 0
967 self.citation_id = None
968 self.style_index = 0 # use to form unique style names
969 self.str_stylesheet = ''
970 self.str_stylesheetcontent = ''
971 self.dom_stylesheet = None
972 self.table_styles = None
973 self.in_citation = False
975 # Keep track of nested styling classes
976 self.inline_style_count_stack = []
978 def get_str_stylesheet(self):
979 return self.str_stylesheet
981 def retrieve_styles(self, extension):
982 """Retrieve the stylesheet from either a .xml file or from
983 a .odt (zip) file. Return the content as a string.
985 s2 = None
986 stylespath = self.settings.stylesheet
987 ext = os.path.splitext(stylespath)[1]
988 if ext == '.xml':
989 stylesfile = open(stylespath, 'r')
990 s1 = stylesfile.read()
991 stylesfile.close()
992 elif ext == extension:
993 zfile = zipfile.ZipFile(stylespath, 'r')
994 s1 = zfile.read('styles.xml')
995 s2 = zfile.read('content.xml')
996 zfile.close()
997 else:
998 raise RuntimeError, 'stylesheet path (%s) must be %s or .xml file' %(stylespath, extension)
999 self.str_stylesheet = s1
1000 self.str_stylesheetcontent = s2
1001 self.dom_stylesheet = etree.fromstring(self.str_stylesheet)
1002 self.dom_stylesheetcontent = etree.fromstring(self.str_stylesheetcontent)
1003 self.table_styles = self.extract_table_styles(s2)
1005 def extract_table_styles(self, styles_str):
1006 root = etree.fromstring(styles_str)
1007 table_styles = {}
1008 auto_styles = root.find(
1009 '{%s}automatic-styles' % (CNSD['office'], ))
1010 for stylenode in auto_styles:
1011 name = stylenode.get('{%s}name' % (CNSD['style'], ))
1012 tablename = name.split('.')[0]
1013 family = stylenode.get('{%s}family' % (CNSD['style'], ))
1014 if name.startswith(TABLESTYLEPREFIX):
1015 tablestyle = table_styles.get(tablename)
1016 if tablestyle is None:
1017 tablestyle = TableStyle()
1018 table_styles[tablename] = tablestyle
1019 if family == 'table':
1020 properties = stylenode.find(
1021 '{%s}table-properties' % (CNSD['style'], ))
1022 property = properties.get('{%s}%s' % (CNSD['fo'],
1023 'background-color', ))
1024 if property is not None and property != 'none':
1025 tablestyle.backgroundcolor = property
1026 elif family == 'table-cell':
1027 properties = stylenode.find(
1028 '{%s}table-cell-properties' % (CNSD['style'], ))
1029 if properties is not None:
1030 border = self.get_property(properties)
1031 if border is not None:
1032 tablestyle.border = border
1033 return table_styles
1035 def get_property(self, stylenode):
1036 border = None
1037 for propertyname in TABLEPROPERTYNAMES:
1038 border = stylenode.get('{%s}%s' % (CNSD['fo'], propertyname, ))
1039 if border is not None and border != 'none':
1040 return border
1041 return border
1043 def add_doc_title(self):
1044 text = self.settings.title
1045 if text:
1046 self.title = text
1047 if not self.found_doc_title:
1048 el = Element('text:p', attrib = {
1049 'text:style-name': self.rststyle('title'),
1051 el.text = text
1052 self.body_text_element.insert(0, el)
1053 el = self.find_first_text_p(self.body_text_element)
1054 if el is not None:
1055 self.attach_page_style(el)
1057 def find_first_text_p(self, el):
1058 """Search the generated doc and return the first <text:p> element.
1060 if (
1061 el.tag == 'text:p' or
1062 el.tag == 'text:h'
1064 return el
1065 elif el.getchildren():
1066 for child in el.getchildren():
1067 el1 = self.find_first_text_p(child)
1068 if el1 is not None:
1069 return el1
1070 return None
1071 else:
1072 return None
1074 def attach_page_style(self, el):
1075 """Attach the default page style.
1077 Create an automatic-style that refers to the current style
1078 of this element and that refers to the default page style.
1080 current_style = el.get('text:style-name')
1081 style_name = 'P1003'
1082 el1 = SubElement(
1083 self.automatic_styles, 'style:style', attrib={
1084 'style:name': style_name,
1085 'style:master-page-name': "rststyle-pagedefault",
1086 'style:family': "paragraph",
1087 }, nsdict=SNSD)
1088 if current_style:
1089 el1.set('style:parent-style-name', current_style)
1090 el.set('text:style-name', style_name)
1092 def rststyle(self, name, parameters=()):
1094 Returns the style name to use for the given style.
1096 If `parameters` is given `name` must contain a matching number of
1097 ``%`` and is used as a format expression with `parameters` as
1098 the value.
1100 name1 = name % parameters
1101 stylename = self.format_map.get(name1, 'rststyle-%s' % name1)
1102 return stylename
1104 def generate_content_element(self, root):
1105 return SubElement(root, 'office:text')
1107 def setup_page(self):
1108 self.setup_paper(self.dom_stylesheet)
1109 if (len(self.header_content) > 0 or len(self.footer_content) > 0 or
1110 self.settings.custom_header or self.settings.custom_footer):
1111 self.add_header_footer(self.dom_stylesheet)
1112 new_content = etree.tostring(self.dom_stylesheet)
1113 return new_content
1115 def get_dom_stylesheet(self):
1116 return self.dom_stylesheet
1118 def setup_paper(self, root_el):
1119 try:
1120 fin = os.popen("paperconf -s 2> /dev/null")
1121 w, h = map(float, fin.read().split())
1122 fin.close()
1123 except:
1124 w, h = 612, 792 # default to Letter
1125 def walk(el):
1126 if el.tag == "{%s}page-layout-properties" % SNSD["style"] and \
1127 not el.attrib.has_key("{%s}page-width" % SNSD["fo"]):
1128 el.attrib["{%s}page-width" % SNSD["fo"]] = "%.3fpt" % w
1129 el.attrib["{%s}page-height" % SNSD["fo"]] = "%.3fpt" % h
1130 el.attrib["{%s}margin-left" % SNSD["fo"]] = \
1131 el.attrib["{%s}margin-right" % SNSD["fo"]] = \
1132 "%.3fpt" % (.1 * w)
1133 el.attrib["{%s}margin-top" % SNSD["fo"]] = \
1134 el.attrib["{%s}margin-bottom" % SNSD["fo"]] = \
1135 "%.3fpt" % (.1 * h)
1136 else:
1137 for subel in el.getchildren(): walk(subel)
1138 walk(root_el)
1140 def add_header_footer(self, root_el):
1141 automatic_styles = root_el.find(
1142 '{%s}automatic-styles' % SNSD['office'])
1143 path = '{%s}master-styles' % (NAME_SPACE_1, )
1144 master_el = root_el.find(path)
1145 if master_el is None:
1146 return
1147 path = '{%s}master-page' % (SNSD['style'], )
1148 master_el_container = master_el.findall(path)
1149 master_el = None
1150 target_attrib = '{%s}name' % (SNSD['style'], )
1151 target_name = self.rststyle('pagedefault')
1152 for el in master_el_container:
1153 if el.get(target_attrib) == target_name:
1154 master_el = el
1155 break
1156 if master_el is None:
1157 return
1158 el1 = master_el
1159 if self.header_content or self.settings.custom_header:
1160 if WhichElementTree == 'lxml':
1161 el2 = SubElement(el1, 'style:header', nsdict=SNSD)
1162 else:
1163 el2 = SubElement(el1, 'style:header',
1164 attrib=STYLES_NAMESPACE_ATTRIB,
1165 nsdict=STYLES_NAMESPACE_DICT,
1167 for el in self.header_content:
1168 attrkey = add_ns('text:style-name', nsdict=SNSD)
1169 el.attrib[attrkey] = self.rststyle('header')
1170 el2.append(el)
1171 if self.settings.custom_header:
1172 elcustom = self.create_custom_headfoot(el2,
1173 self.settings.custom_header, 'header', automatic_styles)
1174 if self.footer_content or self.settings.custom_footer:
1175 if WhichElementTree == 'lxml':
1176 el2 = SubElement(el1, 'style:footer', nsdict=SNSD)
1177 else:
1178 el2 = SubElement(el1, 'style:footer',
1179 attrib=STYLES_NAMESPACE_ATTRIB,
1180 nsdict=STYLES_NAMESPACE_DICT,
1182 for el in self.footer_content:
1183 attrkey = add_ns('text:style-name', nsdict=SNSD)
1184 el.attrib[attrkey] = self.rststyle('footer')
1185 el2.append(el)
1186 if self.settings.custom_footer:
1187 elcustom = self.create_custom_headfoot(el2,
1188 self.settings.custom_footer, 'footer', automatic_styles)
1190 code_none, code_field, code_text = range(3)
1191 field_pat = re.compile(r'%(..?)%')
1193 def create_custom_headfoot(self, parent, text, style_name, automatic_styles):
1194 parent = SubElement(parent, 'text:p', attrib={
1195 'text:style-name': self.rststyle(style_name),
1197 current_element = None
1198 field_iter = self.split_field_specifiers_iter(text)
1199 for item in field_iter:
1200 if item[0] == ODFTranslator.code_field:
1201 if item[1] not in ('p', 'P',
1202 't1', 't2', 't3', 't4',
1203 'd1', 'd2', 'd3', 'd4', 'd5',
1204 's', 't', 'a'):
1205 msg = 'bad field spec: %%%s%%' % (item[1], )
1206 raise RuntimeError, msg
1207 el1 = self.make_field_element(parent,
1208 item[1], style_name, automatic_styles)
1209 if el1 is None:
1210 msg = 'bad field spec: %%%s%%' % (item[1], )
1211 raise RuntimeError, msg
1212 else:
1213 current_element = el1
1214 else:
1215 if current_element is None:
1216 parent.text = item[1]
1217 else:
1218 current_element.tail = item[1]
1220 def make_field_element(self, parent, text, style_name, automatic_styles):
1221 if text == 'p':
1222 el1 = SubElement(parent, 'text:page-number', attrib={
1223 #'text:style-name': self.rststyle(style_name),
1224 'text:select-page': 'current',
1226 elif text == 'P':
1227 el1 = SubElement(parent, 'text:page-count', attrib={
1228 #'text:style-name': self.rststyle(style_name),
1230 elif text == 't1':
1231 self.style_index += 1
1232 el1 = SubElement(parent, 'text:time', attrib={
1233 'text:style-name': self.rststyle(style_name),
1234 'text:fixed': 'true',
1235 'style:data-style-name': 'rst-time-style-%d' % self.style_index,
1237 el2 = SubElement(automatic_styles, 'number:time-style', attrib={
1238 'style:name': 'rst-time-style-%d' % self.style_index,
1239 'xmlns:number': SNSD['number'],
1240 'xmlns:style': SNSD['style'],
1242 el3 = SubElement(el2, 'number:hours', attrib={
1243 'number:style': 'long',
1245 el3 = SubElement(el2, 'number:text')
1246 el3.text = ':'
1247 el3 = SubElement(el2, 'number:minutes', attrib={
1248 'number:style': 'long',
1250 elif text == 't2':
1251 self.style_index += 1
1252 el1 = SubElement(parent, 'text:time', attrib={
1253 'text:style-name': self.rststyle(style_name),
1254 'text:fixed': 'true',
1255 'style:data-style-name': 'rst-time-style-%d' % self.style_index,
1257 el2 = SubElement(automatic_styles, 'number:time-style', attrib={
1258 'style:name': 'rst-time-style-%d' % self.style_index,
1259 'xmlns:number': SNSD['number'],
1260 'xmlns:style': SNSD['style'],
1262 el3 = SubElement(el2, 'number:hours', attrib={
1263 'number:style': 'long',
1265 el3 = SubElement(el2, 'number:text')
1266 el3.text = ':'
1267 el3 = SubElement(el2, 'number:minutes', attrib={
1268 'number:style': 'long',
1270 el3 = SubElement(el2, 'number:text')
1271 el3.text = ':'
1272 el3 = SubElement(el2, 'number:seconds', attrib={
1273 'number:style': 'long',
1275 elif text == 't3':
1276 self.style_index += 1
1277 el1 = SubElement(parent, 'text:time', attrib={
1278 'text:style-name': self.rststyle(style_name),
1279 'text:fixed': 'true',
1280 'style:data-style-name': 'rst-time-style-%d' % self.style_index,
1282 el2 = SubElement(automatic_styles, 'number:time-style', attrib={
1283 'style:name': 'rst-time-style-%d' % self.style_index,
1284 'xmlns:number': SNSD['number'],
1285 'xmlns:style': SNSD['style'],
1287 el3 = SubElement(el2, 'number:hours', attrib={
1288 'number:style': 'long',
1290 el3 = SubElement(el2, 'number:text')
1291 el3.text = ':'
1292 el3 = SubElement(el2, 'number:minutes', attrib={
1293 'number:style': 'long',
1295 el3 = SubElement(el2, 'number:text')
1296 el3.text = ' '
1297 el3 = SubElement(el2, 'number:am-pm')
1298 elif text == 't4':
1299 self.style_index += 1
1300 el1 = SubElement(parent, 'text:time', attrib={
1301 'text:style-name': self.rststyle(style_name),
1302 'text:fixed': 'true',
1303 'style:data-style-name': 'rst-time-style-%d' % self.style_index,
1305 el2 = SubElement(automatic_styles, 'number:time-style', attrib={
1306 'style:name': 'rst-time-style-%d' % self.style_index,
1307 'xmlns:number': SNSD['number'],
1308 'xmlns:style': SNSD['style'],
1310 el3 = SubElement(el2, 'number:hours', attrib={
1311 'number:style': 'long',
1313 el3 = SubElement(el2, 'number:text')
1314 el3.text = ':'
1315 el3 = SubElement(el2, 'number:minutes', attrib={
1316 'number:style': 'long',
1318 el3 = SubElement(el2, 'number:text')
1319 el3.text = ':'
1320 el3 = SubElement(el2, 'number:seconds', attrib={
1321 'number:style': 'long',
1323 el3 = SubElement(el2, 'number:text')
1324 el3.text = ' '
1325 el3 = SubElement(el2, 'number:am-pm')
1326 elif text == 'd1':
1327 self.style_index += 1
1328 el1 = SubElement(parent, 'text:date', attrib={
1329 'text:style-name': self.rststyle(style_name),
1330 'style:data-style-name': 'rst-date-style-%d' % self.style_index,
1332 el2 = SubElement(automatic_styles, 'number:date-style', attrib={
1333 'style:name': 'rst-date-style-%d' % self.style_index,
1334 'number:automatic-order': 'true',
1335 'xmlns:number': SNSD['number'],
1336 'xmlns:style': SNSD['style'],
1338 el3 = SubElement(el2, 'number:month', attrib={
1339 'number:style': 'long',
1341 el3 = SubElement(el2, 'number:text')
1342 el3.text = '/'
1343 el3 = SubElement(el2, 'number:day', attrib={
1344 'number:style': 'long',
1346 el3 = SubElement(el2, 'number:text')
1347 el3.text = '/'
1348 el3 = SubElement(el2, 'number:year')
1349 elif text == 'd2':
1350 self.style_index += 1
1351 el1 = SubElement(parent, 'text:date', attrib={
1352 'text:style-name': self.rststyle(style_name),
1353 'style:data-style-name': 'rst-date-style-%d' % self.style_index,
1355 el2 = SubElement(automatic_styles, 'number:date-style', attrib={
1356 'style:name': 'rst-date-style-%d' % self.style_index,
1357 'number:automatic-order': 'true',
1358 'xmlns:number': SNSD['number'],
1359 'xmlns:style': SNSD['style'],
1361 el3 = SubElement(el2, 'number:month', attrib={
1362 'number:style': 'long',
1364 el3 = SubElement(el2, 'number:text')
1365 el3.text = '/'
1366 el3 = SubElement(el2, 'number:day', attrib={
1367 'number:style': 'long',
1369 el3 = SubElement(el2, 'number:text')
1370 el3.text = '/'
1371 el3 = SubElement(el2, 'number:year', attrib={
1372 'number:style': 'long',
1374 elif text == 'd3':
1375 self.style_index += 1
1376 el1 = SubElement(parent, 'text:date', attrib={
1377 'text:style-name': self.rststyle(style_name),
1378 'style:data-style-name': 'rst-date-style-%d' % self.style_index,
1380 el2 = SubElement(automatic_styles, 'number:date-style', attrib={
1381 'style:name': 'rst-date-style-%d' % self.style_index,
1382 'number:automatic-order': 'true',
1383 'xmlns:number': SNSD['number'],
1384 'xmlns:style': SNSD['style'],
1386 el3 = SubElement(el2, 'number:month', attrib={
1387 'number:textual': 'true',
1389 el3 = SubElement(el2, 'number:text')
1390 el3.text = ' '
1391 el3 = SubElement(el2, 'number:day', attrib={
1393 el3 = SubElement(el2, 'number:text')
1394 el3.text = ', '
1395 el3 = SubElement(el2, 'number:year', attrib={
1396 'number:style': 'long',
1398 elif text == 'd4':
1399 self.style_index += 1
1400 el1 = SubElement(parent, 'text:date', attrib={
1401 'text:style-name': self.rststyle(style_name),
1402 'style:data-style-name': 'rst-date-style-%d' % self.style_index,
1404 el2 = SubElement(automatic_styles, 'number:date-style', attrib={
1405 'style:name': 'rst-date-style-%d' % self.style_index,
1406 'number:automatic-order': 'true',
1407 'xmlns:number': SNSD['number'],
1408 'xmlns:style': SNSD['style'],
1410 el3 = SubElement(el2, 'number:month', attrib={
1411 'number:textual': 'true',
1412 'number:style': 'long',
1414 el3 = SubElement(el2, 'number:text')
1415 el3.text = ' '
1416 el3 = SubElement(el2, 'number:day', attrib={
1418 el3 = SubElement(el2, 'number:text')
1419 el3.text = ', '
1420 el3 = SubElement(el2, 'number:year', attrib={
1421 'number:style': 'long',
1423 elif text == 'd5':
1424 self.style_index += 1
1425 el1 = SubElement(parent, 'text:date', attrib={
1426 'text:style-name': self.rststyle(style_name),
1427 'style:data-style-name': 'rst-date-style-%d' % self.style_index,
1429 el2 = SubElement(automatic_styles, 'number:date-style', attrib={
1430 'style:name': 'rst-date-style-%d' % self.style_index,
1431 'xmlns:number': SNSD['number'],
1432 'xmlns:style': SNSD['style'],
1434 el3 = SubElement(el2, 'number:year', attrib={
1435 'number:style': 'long',
1437 el3 = SubElement(el2, 'number:text')
1438 el3.text = '-'
1439 el3 = SubElement(el2, 'number:month', attrib={
1440 'number:style': 'long',
1442 el3 = SubElement(el2, 'number:text')
1443 el3.text = '-'
1444 el3 = SubElement(el2, 'number:day', attrib={
1445 'number:style': 'long',
1447 elif text == 's':
1448 el1 = SubElement(parent, 'text:subject', attrib={
1449 'text:style-name': self.rststyle(style_name),
1451 elif text == 't':
1452 el1 = SubElement(parent, 'text:title', attrib={
1453 'text:style-name': self.rststyle(style_name),
1455 elif text == 'a':
1456 el1 = SubElement(parent, 'text:author-name', attrib={
1457 'text:fixed': 'false',
1459 else:
1460 el1 = None
1461 return el1
1463 def split_field_specifiers_iter(self, text):
1464 pos1 = 0
1465 pos_end = len(text)
1466 while True:
1467 mo = ODFTranslator.field_pat.search(text, pos1)
1468 if mo:
1469 pos2 = mo.start()
1470 if pos2 > pos1:
1471 yield (ODFTranslator.code_text, text[pos1:pos2])
1472 yield (ODFTranslator.code_field, mo.group(1))
1473 pos1 = mo.end()
1474 else:
1475 break
1476 trailing = text[pos1:]
1477 if trailing:
1478 yield (ODFTranslator.code_text, trailing)
1481 def astext(self):
1482 root = self.content_tree.getroot()
1483 et = etree.ElementTree(root)
1484 s1 = ToString(et)
1485 return s1
1487 def content_astext(self):
1488 return self.astext()
1490 def set_title(self, title): self.title = title
1491 def get_title(self): return self.title
1492 def set_embedded_file_list(self, embedded_file_list):
1493 self.embedded_file_list = embedded_file_list
1494 def get_embedded_file_list(self): return self.embedded_file_list
1495 def get_meta_dict(self): return self.meta_dict
1497 def process_footnotes(self):
1498 for node, el1 in self.footnote_list:
1499 backrefs = node.attributes.get('backrefs', [])
1500 first = True
1501 for ref in backrefs:
1502 el2 = self.footnote_ref_dict.get(ref)
1503 if el2 is not None:
1504 if first:
1505 first = False
1506 el3 = copy.deepcopy(el1)
1507 el2.append(el3)
1508 else:
1509 children = el2.getchildren()
1510 if len(children) > 0: # and 'id' in el2.attrib:
1511 child = children[0]
1512 ref1 = child.text
1513 attribkey = add_ns('text:id', nsdict=SNSD)
1514 id1 = el2.get(attribkey, 'footnote-error')
1515 if id1 is None:
1516 id1 = ''
1517 tag = add_ns('text:note-ref', nsdict=SNSD)
1518 el2.tag = tag
1519 if self.settings.endnotes_end_doc:
1520 note_class = 'endnote'
1521 else:
1522 note_class = 'footnote'
1523 el2.attrib.clear()
1524 attribkey = add_ns('text:note-class', nsdict=SNSD)
1525 el2.attrib[attribkey] = note_class
1526 attribkey = add_ns('text:ref-name', nsdict=SNSD)
1527 el2.attrib[attribkey] = id1
1528 attribkey = add_ns('text:reference-format', nsdict=SNSD)
1529 el2.attrib[attribkey] = 'page'
1530 el2.text = ref1
1533 # Utility methods
1535 def append_child(self, tag, attrib=None, parent=None):
1536 if parent is None:
1537 parent = self.current_element
1538 if attrib is None:
1539 el = SubElement(parent, tag)
1540 else:
1541 el = SubElement(parent, tag, attrib)
1542 return el
1544 def append_p(self, style, text=None):
1545 result = self.append_child('text:p', attrib={
1546 'text:style-name': self.rststyle(style)})
1547 self.append_pending_ids(result)
1548 if text is not None:
1549 result.text = text
1550 return result
1552 def append_pending_ids(self, el):
1553 if self.settings.create_links:
1554 for id in self.pending_ids:
1555 SubElement(el, 'text:reference-mark', attrib={
1556 'text:name': id})
1557 self.pending_ids = [ ]
1559 def set_current_element(self, el):
1560 self.current_element = el
1562 def set_to_parent(self):
1563 self.current_element = self.current_element.getparent()
1565 def generate_labeled_block(self, node, label):
1566 label = '%s:' % (self.language.labels[label], )
1567 el = self.append_p('textbody')
1568 el1 = SubElement(el, 'text:span',
1569 attrib={'text:style-name': self.rststyle('strong')})
1570 el1.text = label
1571 el = self.append_p('blockindent')
1572 return el
1574 def generate_labeled_line(self, node, label):
1575 label = '%s:' % (self.language.labels[label], )
1576 el = self.append_p('textbody')
1577 el1 = SubElement(el, 'text:span',
1578 attrib={'text:style-name': self.rststyle('strong')})
1579 el1.text = label
1580 el1.tail = node.astext()
1581 return el
1583 def encode(self, text):
1584 text = text.replace(u'\u00a0', " ")
1585 return text
1588 # Visitor functions
1590 # In alphabetic order, more or less.
1591 # See docutils.docutils.nodes.node_class_names.
1594 def dispatch_visit(self, node):
1595 """Override to catch basic attributes which many nodes have."""
1596 self.handle_basic_atts(node)
1597 nodes.GenericNodeVisitor.dispatch_visit(self, node)
1599 def handle_basic_atts(self, node):
1600 if isinstance(node, nodes.Element) and node['ids']:
1601 self.pending_ids += node['ids']
1603 def default_visit(self, node):
1604 self.document.reporter.warning('missing visit_%s' % (node.tagname, ))
1606 def default_departure(self, node):
1607 self.document.reporter.warning('missing depart_%s' % (node.tagname, ))
1609 def visit_Text(self, node):
1610 # Skip nodes whose text has been processed in parent nodes.
1611 if isinstance(node.parent, docutils.nodes.literal_block):
1612 return
1613 text = node.astext()
1614 # Are we in mixed content? If so, add the text to the
1615 # etree tail of the previous sibling element.
1616 if len(self.current_element.getchildren()) > 0:
1617 if self.current_element.getchildren()[-1].tail:
1618 self.current_element.getchildren()[-1].tail += text
1619 else:
1620 self.current_element.getchildren()[-1].tail = text
1621 else:
1622 if self.current_element.text:
1623 self.current_element.text += text
1624 else:
1625 self.current_element.text = text
1627 def depart_Text(self, node):
1628 pass
1631 # Pre-defined fields
1634 def visit_address(self, node):
1635 el = self.generate_labeled_block(node, 'address')
1636 self.set_current_element(el)
1638 def depart_address(self, node):
1639 self.set_to_parent()
1641 def visit_author(self, node):
1642 if isinstance(node.parent, nodes.authors):
1643 el = self.append_p('blockindent')
1644 else:
1645 el = self.generate_labeled_block(node, 'author')
1646 self.set_current_element(el)
1648 def depart_author(self, node):
1649 self.set_to_parent()
1651 def visit_authors(self, node):
1652 label = '%s:' % (self.language.labels['authors'], )
1653 el = self.append_p('textbody')
1654 el1 = SubElement(el, 'text:span',
1655 attrib={'text:style-name': self.rststyle('strong')})
1656 el1.text = label
1658 def depart_authors(self, node):
1659 pass
1661 def visit_contact(self, node):
1662 el = self.generate_labeled_block(node, 'contact')
1663 self.set_current_element(el)
1665 def depart_contact(self, node):
1666 self.set_to_parent()
1668 def visit_copyright(self, node):
1669 el = self.generate_labeled_block(node, 'copyright')
1670 self.set_current_element(el)
1672 def depart_copyright(self, node):
1673 self.set_to_parent()
1675 def visit_date(self, node):
1676 self.generate_labeled_line(node, 'date')
1678 def depart_date(self, node):
1679 pass
1681 def visit_organization(self, node):
1682 el = self.generate_labeled_block(node, 'organization')
1683 self.set_current_element(el)
1685 def depart_organization(self, node):
1686 self.set_to_parent()
1688 def visit_status(self, node):
1689 el = self.generate_labeled_block(node, 'status')
1690 self.set_current_element(el)
1692 def depart_status(self, node):
1693 self.set_to_parent()
1695 def visit_revision(self, node):
1696 el = self.generate_labeled_line(node, 'revision')
1698 def depart_revision(self, node):
1699 pass
1701 def visit_version(self, node):
1702 el = self.generate_labeled_line(node, 'version')
1703 #self.set_current_element(el)
1705 def depart_version(self, node):
1706 #self.set_to_parent()
1707 pass
1709 def visit_attribution(self, node):
1710 el = self.append_p('attribution', node.astext())
1712 def depart_attribution(self, node):
1713 pass
1715 def visit_block_quote(self, node):
1716 if 'epigraph' in node.attributes['classes']:
1717 self.paragraph_style_stack.append(self.rststyle('epigraph'))
1718 self.blockstyle = self.rststyle('epigraph')
1719 elif 'highlights' in node.attributes['classes']:
1720 self.paragraph_style_stack.append(self.rststyle('highlights'))
1721 self.blockstyle = self.rststyle('highlights')
1722 else:
1723 self.paragraph_style_stack.append(self.rststyle('blockquote'))
1724 self.blockstyle = self.rststyle('blockquote')
1725 self.line_indent_level += 1
1727 def depart_block_quote(self, node):
1728 self.paragraph_style_stack.pop()
1729 self.blockstyle = ''
1730 self.line_indent_level -= 1
1732 def visit_bullet_list(self, node):
1733 self.list_level +=1
1734 if self.in_table_of_contents:
1735 if self.settings.generate_oowriter_toc:
1736 pass
1737 else:
1738 if node.has_key('classes') and \
1739 'auto-toc' in node.attributes['classes']:
1740 el = SubElement(self.current_element, 'text:list', attrib={
1741 'text:style-name': self.rststyle('tocenumlist'),
1743 self.list_style_stack.append(self.rststyle('enumitem'))
1744 else:
1745 el = SubElement(self.current_element, 'text:list', attrib={
1746 'text:style-name': self.rststyle('tocbulletlist'),
1748 self.list_style_stack.append(self.rststyle('bulletitem'))
1749 self.set_current_element(el)
1750 else:
1751 if self.blockstyle == self.rststyle('blockquote'):
1752 el = SubElement(self.current_element, 'text:list', attrib={
1753 'text:style-name': self.rststyle('blockquote-bulletlist'),
1755 self.list_style_stack.append(
1756 self.rststyle('blockquote-bulletitem'))
1757 elif self.blockstyle == self.rststyle('highlights'):
1758 el = SubElement(self.current_element, 'text:list', attrib={
1759 'text:style-name': self.rststyle('highlights-bulletlist'),
1761 self.list_style_stack.append(
1762 self.rststyle('highlights-bulletitem'))
1763 elif self.blockstyle == self.rststyle('epigraph'):
1764 el = SubElement(self.current_element, 'text:list', attrib={
1765 'text:style-name': self.rststyle('epigraph-bulletlist'),
1767 self.list_style_stack.append(
1768 self.rststyle('epigraph-bulletitem'))
1769 else:
1770 el = SubElement(self.current_element, 'text:list', attrib={
1771 'text:style-name': self.rststyle('bulletlist'),
1773 self.list_style_stack.append(self.rststyle('bulletitem'))
1774 self.set_current_element(el)
1776 def depart_bullet_list(self, node):
1777 if self.in_table_of_contents:
1778 if self.settings.generate_oowriter_toc:
1779 pass
1780 else:
1781 self.set_to_parent()
1782 self.list_style_stack.pop()
1783 else:
1784 self.set_to_parent()
1785 self.list_style_stack.pop()
1786 self.list_level -=1
1788 def visit_caption(self, node):
1789 raise nodes.SkipChildren()
1790 pass
1792 def depart_caption(self, node):
1793 pass
1795 def visit_comment(self, node):
1796 el = self.append_p('textbody')
1797 el1 = SubElement(el, 'office:annotation', attrib={})
1798 el2 = SubElement(el1, 'dc:creator', attrib={})
1799 s1 = os.environ.get('USER', '')
1800 el2.text = s1
1801 el2 = SubElement(el1, 'text:p', attrib={})
1802 el2.text = node.astext()
1804 def depart_comment(self, node):
1805 pass
1807 def visit_compound(self, node):
1808 # The compound directive currently receives no special treatment.
1809 pass
1811 def depart_compound(self, node):
1812 pass
1814 def visit_container(self, node):
1815 styles = node.attributes.get('classes', ())
1816 if len(styles) > 0:
1817 self.paragraph_style_stack.append(self.rststyle(styles[0]))
1819 def depart_container(self, node):
1820 styles = node.attributes.get('classes', ())
1821 if len(styles) > 0:
1822 self.paragraph_style_stack.pop()
1824 def visit_decoration(self, node):
1825 pass
1827 def depart_decoration(self, node):
1828 pass
1830 def visit_definition_list(self, node):
1831 self.def_list_level +=1
1832 if self.list_level > 5:
1833 raise RuntimeError(
1834 'max definition list nesting level exceeded')
1836 def depart_definition_list(self, node):
1837 self.def_list_level -=1
1839 def visit_definition_list_item(self, node):
1840 pass
1842 def depart_definition_list_item(self, node):
1843 pass
1845 def visit_term(self, node):
1846 el = self.append_p('deflist-term-%d' % self.def_list_level)
1847 el.text = node.astext()
1848 self.set_current_element(el)
1849 raise nodes.SkipChildren()
1851 def depart_term(self, node):
1852 self.set_to_parent()
1854 def visit_definition(self, node):
1855 self.paragraph_style_stack.append(
1856 self.rststyle('deflist-def-%d' % self.def_list_level))
1857 self.bumped_list_level_stack.append(ListLevel(1))
1859 def depart_definition(self, node):
1860 self.paragraph_style_stack.pop()
1861 self.bumped_list_level_stack.pop()
1863 def visit_classifier(self, node):
1864 els = self.current_element.getchildren()
1865 if len(els) > 0:
1866 el = els[-1]
1867 el1 = SubElement(el, 'text:span',
1868 attrib={'text:style-name': self.rststyle('emphasis')
1870 el1.text = ' (%s)' % (node.astext(), )
1872 def depart_classifier(self, node):
1873 pass
1875 def visit_document(self, node):
1876 pass
1878 def depart_document(self, node):
1879 self.process_footnotes()
1881 def visit_docinfo(self, node):
1882 self.section_level += 1
1883 self.section_count += 1
1884 if self.settings.create_sections:
1885 el = self.append_child('text:section', attrib={
1886 'text:name': 'Section%d' % self.section_count,
1887 'text:style-name': 'Sect%d' % self.section_level,
1889 self.set_current_element(el)
1891 def depart_docinfo(self, node):
1892 self.section_level -= 1
1893 if self.settings.create_sections:
1894 self.set_to_parent()
1896 def visit_emphasis(self, node):
1897 el = SubElement(self.current_element, 'text:span',
1898 attrib={'text:style-name': self.rststyle('emphasis')})
1899 self.set_current_element(el)
1901 def depart_emphasis(self, node):
1902 self.set_to_parent()
1904 def visit_enumerated_list(self, node):
1905 el1 = self.current_element
1906 if self.blockstyle == self.rststyle('blockquote'):
1907 el2 = SubElement(el1, 'text:list', attrib={
1908 'text:style-name': self.rststyle('blockquote-enumlist'),
1910 self.list_style_stack.append(self.rststyle('blockquote-enumitem'))
1911 elif self.blockstyle == self.rststyle('highlights'):
1912 el2 = SubElement(el1, 'text:list', attrib={
1913 'text:style-name': self.rststyle('highlights-enumlist'),
1915 self.list_style_stack.append(self.rststyle('highlights-enumitem'))
1916 elif self.blockstyle == self.rststyle('epigraph'):
1917 el2 = SubElement(el1, 'text:list', attrib={
1918 'text:style-name': self.rststyle('epigraph-enumlist'),
1920 self.list_style_stack.append(self.rststyle('epigraph-enumitem'))
1921 else:
1922 liststylename = 'enumlist-%s' % (node.get('enumtype', 'arabic'), )
1923 el2 = SubElement(el1, 'text:list', attrib={
1924 'text:style-name': self.rststyle(liststylename),
1926 self.list_style_stack.append(self.rststyle('enumitem'))
1927 self.set_current_element(el2)
1929 def depart_enumerated_list(self, node):
1930 self.set_to_parent()
1931 self.list_style_stack.pop()
1933 def visit_list_item(self, node):
1934 # If we are in a "bumped" list level, then wrap this
1935 # list in an outer lists in order to increase the
1936 # indentation level.
1937 if self.in_table_of_contents:
1938 if self.settings.generate_oowriter_toc:
1939 self.paragraph_style_stack.append(
1940 self.rststyle('contents-%d' % (self.list_level, )))
1941 else:
1942 el1 = self.append_child('text:list-item')
1943 self.set_current_element(el1)
1944 else:
1945 el1 = self.append_child('text:list-item')
1946 el3 = el1
1947 if len(self.bumped_list_level_stack) > 0:
1948 level_obj = self.bumped_list_level_stack[-1]
1949 if level_obj.get_sibling():
1950 level_obj.set_nested(False)
1951 for level_obj1 in self.bumped_list_level_stack:
1952 for idx in range(level_obj1.get_level()):
1953 el2 = self.append_child('text:list', parent=el3)
1954 el3 = self.append_child(
1955 'text:list-item', parent=el2)
1956 self.paragraph_style_stack.append(self.list_style_stack[-1])
1957 self.set_current_element(el3)
1959 def depart_list_item(self, node):
1960 if self.in_table_of_contents:
1961 if self.settings.generate_oowriter_toc:
1962 self.paragraph_style_stack.pop()
1963 else:
1964 self.set_to_parent()
1965 else:
1966 if len(self.bumped_list_level_stack) > 0:
1967 level_obj = self.bumped_list_level_stack[-1]
1968 if level_obj.get_sibling():
1969 level_obj.set_nested(True)
1970 for level_obj1 in self.bumped_list_level_stack:
1971 for idx in range(level_obj1.get_level()):
1972 self.set_to_parent()
1973 self.set_to_parent()
1974 self.paragraph_style_stack.pop()
1975 self.set_to_parent()
1977 def visit_header(self, node):
1978 self.in_header = True
1980 def depart_header(self, node):
1981 self.in_header = False
1983 def visit_footer(self, node):
1984 self.in_footer = True
1986 def depart_footer(self, node):
1987 self.in_footer = False
1989 def visit_field(self, node):
1990 pass
1992 def depart_field(self, node):
1993 pass
1995 def visit_field_list(self, node):
1996 pass
1998 def depart_field_list(self, node):
1999 pass
2001 def visit_field_name(self, node):
2002 el = self.append_p('textbody')
2003 el1 = SubElement(el, 'text:span',
2004 attrib={'text:style-name': self.rststyle('strong')})
2005 el1.text = node.astext()
2007 def depart_field_name(self, node):
2008 pass
2010 def visit_field_body(self, node):
2011 self.paragraph_style_stack.append(self.rststyle('blockindent'))
2013 def depart_field_body(self, node):
2014 self.paragraph_style_stack.pop()
2016 def visit_figure(self, node):
2017 pass
2019 def depart_figure(self, node):
2020 pass
2022 def visit_footnote(self, node):
2023 self.footnote_level += 1
2024 self.save_footnote_current = self.current_element
2025 el1 = Element('text:note-body')
2026 self.current_element = el1
2027 self.footnote_list.append((node, el1))
2028 if isinstance(node, docutils.nodes.citation):
2029 self.paragraph_style_stack.append(self.rststyle('citation'))
2030 else:
2031 self.paragraph_style_stack.append(self.rststyle('footnote'))
2033 def depart_footnote(self, node):
2034 self.paragraph_style_stack.pop()
2035 self.current_element = self.save_footnote_current
2036 self.footnote_level -= 1
2038 footnote_chars = [
2039 '*', '**', '***',
2040 '++', '+++',
2041 '##', '###',
2042 '@@', '@@@',
2045 def visit_footnote_reference(self, node):
2046 if self.footnote_level <= 0:
2047 id = node.attributes['ids'][0]
2048 refid = node.attributes.get('refid')
2049 if refid is None:
2050 refid = ''
2051 if self.settings.endnotes_end_doc:
2052 note_class = 'endnote'
2053 else:
2054 note_class = 'footnote'
2055 el1 = self.append_child('text:note', attrib={
2056 'text:id': '%s' % (refid, ),
2057 'text:note-class': note_class,
2059 note_auto = str(node.attributes.get('auto', 1))
2060 if isinstance(node, docutils.nodes.citation_reference):
2061 citation = '[%s]' % node.astext()
2062 el2 = SubElement(el1, 'text:note-citation', attrib={
2063 'text:label': citation,
2065 el2.text = citation
2066 elif note_auto == '1':
2067 el2 = SubElement(el1, 'text:note-citation', attrib={
2068 'text:label': node.astext(),
2070 el2.text = node.astext()
2071 elif note_auto == '*':
2072 if self.footnote_chars_idx >= len(
2073 ODFTranslator.footnote_chars):
2074 self.footnote_chars_idx = 0
2075 footnote_char = ODFTranslator.footnote_chars[
2076 self.footnote_chars_idx]
2077 self.footnote_chars_idx += 1
2078 el2 = SubElement(el1, 'text:note-citation', attrib={
2079 'text:label': footnote_char,
2081 el2.text = footnote_char
2082 self.footnote_ref_dict[id] = el1
2083 raise nodes.SkipChildren()
2085 def depart_footnote_reference(self, node):
2086 pass
2088 def visit_citation(self, node):
2089 self.in_citation = True
2090 for id in node.attributes['ids']:
2091 self.citation_id = id
2092 break
2093 self.paragraph_style_stack.append(self.rststyle('blockindent'))
2094 self.bumped_list_level_stack.append(ListLevel(1))
2096 def depart_citation(self, node):
2097 self.citation_id = None
2098 self.paragraph_style_stack.pop()
2099 self.bumped_list_level_stack.pop()
2100 self.in_citation = False
2102 def visit_citation_reference(self, node):
2103 if self.settings.create_links:
2104 id = node.attributes['refid']
2105 el = self.append_child('text:reference-ref', attrib={
2106 'text:ref-name': '%s' % (id, ),
2107 'text:reference-format': 'text',
2109 el.text = '['
2110 self.set_current_element(el)
2111 elif self.current_element.text is None:
2112 self.current_element.text = '['
2113 else:
2114 self.current_element.text += '['
2116 def depart_citation_reference(self, node):
2117 self.current_element.text += ']'
2118 if self.settings.create_links:
2119 self.set_to_parent()
2121 def visit_label(self, node):
2122 if isinstance(node.parent, docutils.nodes.footnote):
2123 raise nodes.SkipChildren()
2124 elif self.citation_id is not None:
2125 el = self.append_p('textbody')
2126 self.set_current_element(el)
2127 if self.settings.create_links:
2128 el0 = SubElement(el, 'text:span')
2129 el0.text = '['
2130 el1 = self.append_child('text:reference-mark-start', attrib={
2131 'text:name': '%s' % (self.citation_id, ),
2133 else:
2134 el.text = '['
2136 def depart_label(self, node):
2137 if isinstance(node.parent, docutils.nodes.footnote):
2138 pass
2139 elif self.citation_id is not None:
2140 if self.settings.create_links:
2141 el = self.append_child('text:reference-mark-end', attrib={
2142 'text:name': '%s' % (self.citation_id, ),
2144 el0 = SubElement(self.current_element, 'text:span')
2145 el0.text = ']'
2146 else:
2147 self.current_element.text += ']'
2148 self.set_to_parent()
2150 def visit_generated(self, node):
2151 pass
2153 def depart_generated(self, node):
2154 pass
2156 def check_file_exists(self, path):
2157 if os.path.exists(path):
2158 return 1
2159 else:
2160 return 0
2162 def visit_image(self, node):
2163 # Capture the image file.
2164 if 'uri' in node.attributes:
2165 source = node.attributes['uri']
2166 if not (source.startswith('http:') or source.startswith('https:')):
2167 if not source.startswith(os.sep):
2168 docsource, line = utils.get_source_line(node)
2169 if docsource:
2170 dirname = os.path.dirname(docsource)
2171 if dirname:
2172 source = '%s%s%s' % (dirname, os.sep, source, )
2173 if not self.check_file_exists(source):
2174 self.document.reporter.warning(
2175 'Cannot find image file %s.' % (source, ))
2176 return
2177 else:
2178 return
2179 if source in self.image_dict:
2180 filename, destination = self.image_dict[source]
2181 else:
2182 self.image_count += 1
2183 filename = os.path.split(source)[1]
2184 destination = 'Pictures/1%08x%s' % (self.image_count, filename, )
2185 if source.startswith('http:') or source.startswith('https:'):
2186 try:
2187 imgfile = urllib2.urlopen(source)
2188 content = imgfile.read()
2189 imgfile.close()
2190 imgfile2 = tempfile.NamedTemporaryFile('wb', delete=False)
2191 imgfile2.write(content)
2192 imgfile2.close()
2193 imgfilename = imgfile2.name
2194 source = imgfilename
2195 except urllib2.HTTPError, e:
2196 self.document.reporter.warning(
2197 "Can't open image url %s." % (source, ))
2198 spec = (source, destination,)
2199 else:
2200 spec = (os.path.abspath(source), destination,)
2201 self.embedded_file_list.append(spec)
2202 self.image_dict[source] = (source, destination,)
2203 # Is this a figure (containing an image) or just a plain image?
2204 if self.in_paragraph:
2205 el1 = self.current_element
2206 else:
2207 el1 = SubElement(self.current_element, 'text:p',
2208 attrib={'text:style-name': self.rststyle('textbody')})
2209 el2 = el1
2210 if isinstance(node.parent, docutils.nodes.figure):
2211 el3, el4, el5, caption = self.generate_figure(node, source,
2212 destination, el2)
2213 attrib = {}
2214 el6, width = self.generate_image(node, source, destination,
2215 el5, attrib)
2216 if caption is not None:
2217 el6.tail = caption
2218 else: #if isinstance(node.parent, docutils.nodes.image):
2219 el3 = self.generate_image(node, source, destination, el2)
2221 def depart_image(self, node):
2222 pass
2224 def get_image_width_height(self, node, attr):
2225 size = None
2226 unit = None
2227 if attr in node.attributes:
2228 size = node.attributes[attr]
2229 size = size.strip()
2230 # For conversion factors, see:
2231 # http://www.unitconversion.org/unit_converter/typography-ex.html
2232 try:
2233 if size.endswith('%'):
2234 if attr == 'height':
2235 # Percentage allowed for width but not height.
2236 raise ValueError('percentage not allowed for height')
2237 size = size.rstrip(' %')
2238 size = float(size) / 100.0
2239 unit = '%'
2240 else:
2241 size, unit = convert_to_cm(size)
2242 except ValueError, exp:
2243 self.document.reporter.warning(
2244 'Invalid %s for image: "%s". '
2245 'Error: "%s".' % (
2246 attr, node.attributes[attr], exp))
2247 return size, unit
2249 def convert_to_cm(self, size):
2250 """Convert various units to centimeters.
2252 Note that a call to this method should be wrapped in:
2253 try: except ValueError:
2255 size = size.strip()
2256 if size.endswith('px'):
2257 size = float(size[:-2]) * 0.026 # convert px to cm
2258 elif size.endswith('in'):
2259 size = float(size[:-2]) * 2.54 # convert in to cm
2260 elif size.endswith('pt'):
2261 size = float(size[:-2]) * 0.035 # convert pt to cm
2262 elif size.endswith('pc'):
2263 size = float(size[:-2]) * 2.371 # convert pc to cm
2264 elif size.endswith('mm'):
2265 size = float(size[:-2]) * 0.1 # convert mm to cm
2266 elif size.endswith('cm'):
2267 size = float(size[:-2])
2268 else:
2269 raise ValueError('unknown unit type')
2270 unit = 'cm'
2271 return size, unit
2273 def get_image_scale(self, node):
2274 if 'scale' in node.attributes:
2275 scale = node.attributes['scale']
2276 try:
2277 scale = int(scale)
2278 except ValueError:
2279 self.document.reporter.warning(
2280 'Invalid scale for image: "%s"' % (
2281 node.attributes['scale'], ))
2282 if scale < 1: # or scale > 100:
2283 self.document.reporter.warning(
2284 'scale out of range (%s), using 1.' % (scale, ))
2285 scale = 1
2286 scale = scale * 0.01
2287 else:
2288 scale = 1.0
2289 return scale
2291 def get_image_scaled_width_height(self, node, source):
2292 """Return the image size in centimeters adjusted by image attrs."""
2293 scale = self.get_image_scale(node)
2294 width, width_unit = self.get_image_width_height(node, 'width')
2295 height, _ = self.get_image_width_height(node, 'height')
2296 dpi = (72, 72)
2297 if PIL is not None and source in self.image_dict:
2298 filename, destination = self.image_dict[source]
2299 imageobj = PIL.Image.open(filename, 'r')
2300 dpi = imageobj.info.get('dpi', dpi)
2301 # dpi information can be (xdpi, ydpi) or xydpi
2302 try:
2303 iter(dpi)
2304 except:
2305 dpi = (dpi, dpi)
2306 else:
2307 imageobj = None
2308 if width is None or height is None:
2309 if imageobj is None:
2310 raise RuntimeError(
2311 'image size not fully specified and PIL not installed')
2312 if width is None:
2313 width = imageobj.size[0]
2314 width = float(width) * 0.026 # convert px to cm
2315 if height is None:
2316 height = imageobj.size[1]
2317 height = float(height) * 0.026 # convert px to cm
2318 if width_unit == '%':
2319 factor = width
2320 image_width = imageobj.size[0]
2321 image_width = float(image_width) * 0.026 # convert px to cm
2322 image_height = imageobj.size[1]
2323 image_height = float(image_height) * 0.026 # convert px to cm
2324 line_width = self.get_page_width()
2325 width = factor * line_width
2326 factor = (factor * line_width) / image_width
2327 height = factor * image_height
2328 width *= scale
2329 height *= scale
2330 width = '%.2fcm' % width
2331 height = '%.2fcm' % height
2332 return width, height
2334 def get_page_width(self):
2335 """Return the document's page width in centimeters."""
2336 root = self.get_dom_stylesheet()
2337 nodes = root.iterfind(
2338 './/{urn:oasis:names:tc:opendocument:xmlns:style:1.0}'
2339 'page-layout/'
2340 '{urn:oasis:names:tc:opendocument:xmlns:style:1.0}'
2341 'page-layout-properties')
2342 width = None
2343 for node in nodes:
2344 page_width = node.get(
2345 '{urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0}'
2346 'page-width')
2347 margin_left = node.get(
2348 '{urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0}'
2349 'margin-left')
2350 margin_right = node.get(
2351 '{urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0}'
2352 'margin-right')
2353 if (page_width is None or
2354 margin_left is None or
2355 margin_right is None):
2356 continue
2357 try:
2358 page_width, _ = self.convert_to_cm(page_width)
2359 margin_left, _ = self.convert_to_cm(margin_left)
2360 margin_right, _ = self.convert_to_cm(margin_right)
2361 except ValueError, exp:
2362 self.document.reporter.warning(
2363 'Stylesheet file contains invalid page width '
2364 'or margin size.')
2365 width = page_width - margin_left - margin_right
2366 if width is None:
2367 # We can't find the width in styles, so we make a guess.
2368 # Use a width of 6 in = 15.24 cm.
2369 width = 15.24
2370 return width
2372 def generate_figure(self, node, source, destination, current_element):
2373 caption = None
2374 width, height = self.get_image_scaled_width_height(node, source)
2375 for node1 in node.parent.children:
2376 if node1.tagname == 'caption':
2377 caption = node1.astext()
2378 self.image_style_count += 1
2380 # Add the style for the caption.
2381 if caption is not None:
2382 attrib = {
2383 'style:class': 'extra',
2384 'style:family': 'paragraph',
2385 'style:name': 'Caption',
2386 'style:parent-style-name': 'Standard',
2388 el1 = SubElement(self.automatic_styles, 'style:style',
2389 attrib=attrib, nsdict=SNSD)
2390 attrib = {
2391 'fo:margin-bottom': '0.0835in',
2392 'fo:margin-top': '0.0835in',
2393 'text:line-number': '0',
2394 'text:number-lines': 'false',
2396 el2 = SubElement(el1, 'style:paragraph-properties',
2397 attrib=attrib, nsdict=SNSD)
2398 attrib = {
2399 'fo:font-size': '12pt',
2400 'fo:font-style': 'italic',
2401 'style:font-name': 'Times',
2402 'style:font-name-complex': 'Lucidasans1',
2403 'style:font-size-asian': '12pt',
2404 'style:font-size-complex': '12pt',
2405 'style:font-style-asian': 'italic',
2406 'style:font-style-complex': 'italic',
2408 el2 = SubElement(el1, 'style:text-properties',
2409 attrib=attrib, nsdict=SNSD)
2410 style_name = 'rstframestyle%d' % self.image_style_count
2411 draw_name = 'graphics%d' % IMAGE_NAME_COUNTER.next()
2412 # Add the styles
2413 attrib = {
2414 'style:name': style_name,
2415 'style:family': 'graphic',
2416 'style:parent-style-name': self.rststyle('figureframe'),
2418 el1 = SubElement(self.automatic_styles,
2419 'style:style', attrib=attrib, nsdict=SNSD)
2420 halign = 'center'
2421 valign = 'top'
2422 if 'align' in node.attributes:
2423 align = node.attributes['align'].split()
2424 for val in align:
2425 if val in ('left', 'center', 'right'):
2426 halign = val
2427 elif val in ('top', 'middle', 'bottom'):
2428 valign = val
2429 attrib = {}
2430 wrap = False
2431 classes = node.parent.attributes.get('classes')
2432 if classes and 'wrap' in classes:
2433 wrap = True
2434 if wrap:
2435 attrib['style:wrap'] = 'dynamic'
2436 else:
2437 attrib['style:wrap'] = 'none'
2438 el2 = SubElement(el1,
2439 'style:graphic-properties', attrib=attrib, nsdict=SNSD)
2440 attrib = {
2441 'draw:style-name': style_name,
2442 'draw:name': draw_name,
2443 'text:anchor-type': 'paragraph',
2444 'draw:z-index': '0',
2446 attrib['svg:width'] = width
2447 el3 = SubElement(current_element, 'draw:frame', attrib=attrib)
2448 attrib = {}
2449 el4 = SubElement(el3, 'draw:text-box', attrib=attrib)
2450 attrib = {
2451 'text:style-name': self.rststyle('caption'),
2453 el5 = SubElement(el4, 'text:p', attrib=attrib)
2454 return el3, el4, el5, caption
2456 def generate_image(self, node, source, destination, current_element,
2457 frame_attrs=None):
2458 width, height = self.get_image_scaled_width_height(node, source)
2459 self.image_style_count += 1
2460 style_name = 'rstframestyle%d' % self.image_style_count
2461 # Add the style.
2462 attrib = {
2463 'style:name': style_name,
2464 'style:family': 'graphic',
2465 'style:parent-style-name': self.rststyle('image'),
2467 el1 = SubElement(self.automatic_styles,
2468 'style:style', attrib=attrib, nsdict=SNSD)
2469 halign = None
2470 valign = None
2471 if 'align' in node.attributes:
2472 align = node.attributes['align'].split()
2473 for val in align:
2474 if val in ('left', 'center', 'right'):
2475 halign = val
2476 elif val in ('top', 'middle', 'bottom'):
2477 valign = val
2478 if frame_attrs is None:
2479 attrib = {
2480 'style:vertical-pos': 'top',
2481 'style:vertical-rel': 'paragraph',
2482 'style:horizontal-rel': 'paragraph',
2483 'style:mirror': 'none',
2484 'fo:clip': 'rect(0cm 0cm 0cm 0cm)',
2485 'draw:luminance': '0%',
2486 'draw:contrast': '0%',
2487 'draw:red': '0%',
2488 'draw:green': '0%',
2489 'draw:blue': '0%',
2490 'draw:gamma': '100%',
2491 'draw:color-inversion': 'false',
2492 'draw:image-opacity': '100%',
2493 'draw:color-mode': 'standard',
2495 else:
2496 attrib = frame_attrs
2497 if halign is not None:
2498 attrib['style:horizontal-pos'] = halign
2499 if valign is not None:
2500 attrib['style:vertical-pos'] = valign
2501 # If there is a classes/wrap directive or we are
2502 # inside a table, add a no-wrap style.
2503 wrap = False
2504 classes = node.attributes.get('classes')
2505 if classes and 'wrap' in classes:
2506 wrap = True
2507 if wrap:
2508 attrib['style:wrap'] = 'dynamic'
2509 else:
2510 attrib['style:wrap'] = 'none'
2511 # If we are inside a table, add a no-wrap style.
2512 if self.is_in_table(node):
2513 attrib['style:wrap'] = 'none'
2514 el2 = SubElement(el1,
2515 'style:graphic-properties', attrib=attrib, nsdict=SNSD)
2516 draw_name = 'graphics%d' % IMAGE_NAME_COUNTER.next()
2517 # Add the content.
2518 #el = SubElement(current_element, 'text:p',
2519 # attrib={'text:style-name': self.rststyle('textbody')})
2520 attrib={
2521 'draw:style-name': style_name,
2522 'draw:name': draw_name,
2523 'draw:z-index': '1',
2525 if isinstance(node.parent, nodes.TextElement):
2526 attrib['text:anchor-type'] = 'as-char' #vds
2527 else:
2528 attrib['text:anchor-type'] = 'paragraph'
2529 attrib['svg:width'] = width
2530 attrib['svg:height'] = height
2531 el1 = SubElement(current_element, 'draw:frame', attrib=attrib)
2532 el2 = SubElement(el1, 'draw:image', attrib={
2533 'xlink:href': '%s' % (destination, ),
2534 'xlink:type': 'simple',
2535 'xlink:show': 'embed',
2536 'xlink:actuate': 'onLoad',
2538 return el1, width
2540 def is_in_table(self, node):
2541 node1 = node.parent
2542 while node1:
2543 if isinstance(node1, docutils.nodes.entry):
2544 return True
2545 node1 = node1.parent
2546 return False
2548 def visit_legend(self, node):
2549 if isinstance(node.parent, docutils.nodes.figure):
2550 el1 = self.current_element[-1]
2551 el1 = el1[0][0]
2552 self.current_element = el1
2553 self.paragraph_style_stack.append(self.rststyle('legend'))
2555 def depart_legend(self, node):
2556 if isinstance(node.parent, docutils.nodes.figure):
2557 self.paragraph_style_stack.pop()
2558 self.set_to_parent()
2559 self.set_to_parent()
2560 self.set_to_parent()
2562 def visit_line_block(self, node):
2563 self.line_indent_level += 1
2564 self.line_block_level += 1
2566 def depart_line_block(self, node):
2567 self.line_indent_level -= 1
2568 self.line_block_level -= 1
2570 def visit_line(self, node):
2571 style = 'lineblock%d' % self.line_indent_level
2572 el1 = SubElement(self.current_element, 'text:p', attrib={
2573 'text:style-name': self.rststyle(style),
2575 self.current_element = el1
2577 def depart_line(self, node):
2578 self.set_to_parent()
2580 def visit_literal(self, node):
2581 el = SubElement(self.current_element, 'text:span',
2582 attrib={'text:style-name': self.rststyle('inlineliteral')})
2583 self.set_current_element(el)
2585 def depart_literal(self, node):
2586 self.set_to_parent()
2588 def visit_inline(self, node):
2589 styles = node.attributes.get('classes', ())
2590 if styles:
2591 el = self.current_element
2592 for inline_style in styles:
2593 el = SubElement(el, 'text:span',
2594 attrib={'text:style-name':
2595 self.rststyle(inline_style)})
2596 count = len(styles)
2597 else:
2598 # No style was specified so use a default style (old code
2599 # crashed if no style was given)
2600 el = SubElement(self.current_element, 'text:span')
2601 count = 1
2603 self.set_current_element(el)
2604 self.inline_style_count_stack.append(count)
2606 def depart_inline(self, node):
2607 count = self.inline_style_count_stack.pop()
2608 for x in range(count):
2609 self.set_to_parent()
2611 def _calculate_code_block_padding(self, line):
2612 count = 0
2613 matchobj = SPACES_PATTERN.match(line)
2614 if matchobj:
2615 pad = matchobj.group()
2616 count = len(pad)
2617 else:
2618 matchobj = TABS_PATTERN.match(line)
2619 if matchobj:
2620 pad = matchobj.group()
2621 count = len(pad) * 8
2622 return count
2624 def _add_syntax_highlighting(self, insource, language):
2625 lexer = pygments.lexers.get_lexer_by_name(language, stripall=True)
2626 if language in ('latex', 'tex'):
2627 fmtr = OdtPygmentsLaTeXFormatter(lambda name, parameters=():
2628 self.rststyle(name, parameters),
2629 escape_function=escape_cdata)
2630 else:
2631 fmtr = OdtPygmentsProgFormatter(lambda name, parameters=():
2632 self.rststyle(name, parameters),
2633 escape_function=escape_cdata)
2634 outsource = pygments.highlight(insource, lexer, fmtr)
2635 return outsource
2637 def fill_line(self, line):
2638 line = FILL_PAT1.sub(self.fill_func1, line)
2639 line = FILL_PAT2.sub(self.fill_func2, line)
2640 return line
2642 def fill_func1(self, matchobj):
2643 spaces = matchobj.group(0)
2644 repl = '<text:s text:c="%d"/>' % (len(spaces), )
2645 return repl
2647 def fill_func2(self, matchobj):
2648 spaces = matchobj.group(0)
2649 repl = ' <text:s text:c="%d"/>' % (len(spaces) - 1, )
2650 return repl
2652 def visit_literal_block(self, node):
2653 if len(self.paragraph_style_stack) > 1:
2654 wrapper1 = '<text:p text:style-name="%s">%%s</text:p>' % (
2655 self.rststyle('codeblock-indented'), )
2656 else:
2657 wrapper1 = '<text:p text:style-name="%s">%%s</text:p>' % (
2658 self.rststyle('codeblock'), )
2659 source = node.astext()
2660 if (pygments and
2661 self.settings.add_syntax_highlighting
2662 #and
2663 #node.get('hilight', False)
2665 language = node.get('language', 'python')
2666 source = self._add_syntax_highlighting(source, language)
2667 else:
2668 source = escape_cdata(source)
2669 lines = source.split('\n')
2670 # If there is an empty last line, remove it.
2671 if lines[-1] == '':
2672 del lines[-1]
2673 lines1 = ['<wrappertag1 xmlns:text="urn:oasis:names:tc:opendocument:xmlns:text:1.0">']
2675 my_lines = []
2676 for my_line in lines:
2677 my_line = self.fill_line(my_line)
2678 my_line = my_line.replace("&#10;", "\n")
2679 my_lines.append(my_line)
2680 my_lines_str = '<text:line-break/>'.join(my_lines)
2681 my_lines_str2 = wrapper1 % (my_lines_str, )
2682 lines1.append(my_lines_str2)
2683 lines1.append('</wrappertag1>')
2684 s1 = ''.join(lines1)
2685 if WhichElementTree != "lxml":
2686 s1 = s1.encode("utf-8")
2687 el1 = etree.fromstring(s1)
2688 children = el1.getchildren()
2689 for child in children:
2690 self.current_element.append(child)
2692 def depart_literal_block(self, node):
2693 pass
2695 visit_doctest_block = visit_literal_block
2696 depart_doctest_block = depart_literal_block
2698 # placeholder for math (see docs/dev/todo.txt)
2699 def visit_math(self, node):
2700 self.document.reporter.warning('"math" role not supported',
2701 base_node=node)
2702 self.visit_literal(node)
2704 def depart_math(self, node):
2705 self.depart_literal(node)
2707 def visit_math_block(self, node):
2708 self.document.reporter.warning('"math" directive not supported',
2709 base_node=node)
2710 self.visit_literal_block(node)
2712 def depart_math_block(self, node):
2713 self.depart_literal_block(node)
2715 def visit_meta(self, node):
2716 name = node.attributes.get('name')
2717 content = node.attributes.get('content')
2718 if name is not None and content is not None:
2719 self.meta_dict[name] = content
2721 def depart_meta(self, node):
2722 pass
2724 def visit_option_list(self, node):
2725 table_name = 'tableoption'
2727 # Generate automatic styles
2728 if not self.optiontablestyles_generated:
2729 self.optiontablestyles_generated = True
2730 el = SubElement(self.automatic_styles, 'style:style', attrib={
2731 'style:name': self.rststyle(table_name),
2732 'style:family': 'table'}, nsdict=SNSD)
2733 el1 = SubElement(el, 'style:table-properties', attrib={
2734 'style:width': '17.59cm',
2735 'table:align': 'left',
2736 'style:shadow': 'none'}, nsdict=SNSD)
2737 el = SubElement(self.automatic_styles, 'style:style', attrib={
2738 'style:name': self.rststyle('%s.%%c' % table_name, ( 'A', )),
2739 'style:family': 'table-column'}, nsdict=SNSD)
2740 el1 = SubElement(el, 'style:table-column-properties', attrib={
2741 'style:column-width': '4.999cm'}, nsdict=SNSD)
2742 el = SubElement(self.automatic_styles, 'style:style', attrib={
2743 'style:name': self.rststyle('%s.%%c' % table_name, ( 'B', )),
2744 'style:family': 'table-column'}, nsdict=SNSD)
2745 el1 = SubElement(el, 'style:table-column-properties', attrib={
2746 'style:column-width': '12.587cm'}, nsdict=SNSD)
2747 el = SubElement(self.automatic_styles, 'style:style', attrib={
2748 'style:name': self.rststyle(
2749 '%s.%%c%%d' % table_name, ( 'A', 1, )),
2750 'style:family': 'table-cell'}, nsdict=SNSD)
2751 el1 = SubElement(el, 'style:table-cell-properties', attrib={
2752 'fo:background-color': 'transparent',
2753 'fo:padding': '0.097cm',
2754 'fo:border-left': '0.035cm solid #000000',
2755 'fo:border-right': 'none',
2756 'fo:border-top': '0.035cm solid #000000',
2757 'fo:border-bottom': '0.035cm solid #000000'}, nsdict=SNSD)
2758 el2 = SubElement(el1, 'style:background-image', nsdict=SNSD)
2759 el = SubElement(self.automatic_styles, 'style:style', attrib={
2760 'style:name': self.rststyle(
2761 '%s.%%c%%d' % table_name, ( 'B', 1, )),
2762 'style:family': 'table-cell'}, nsdict=SNSD)
2763 el1 = SubElement(el, 'style:table-cell-properties', attrib={
2764 'fo:padding': '0.097cm',
2765 'fo:border': '0.035cm solid #000000'}, nsdict=SNSD)
2766 el = SubElement(self.automatic_styles, 'style:style', attrib={
2767 'style:name': self.rststyle(
2768 '%s.%%c%%d' % table_name, ( 'A', 2, )),
2769 'style:family': 'table-cell'}, nsdict=SNSD)
2770 el1 = SubElement(el, 'style:table-cell-properties', attrib={
2771 'fo:padding': '0.097cm',
2772 'fo:border-left': '0.035cm solid #000000',
2773 'fo:border-right': 'none',
2774 'fo:border-top': 'none',
2775 'fo:border-bottom': '0.035cm solid #000000'}, nsdict=SNSD)
2776 el = SubElement(self.automatic_styles, 'style:style', attrib={
2777 'style:name': self.rststyle(
2778 '%s.%%c%%d' % table_name, ( 'B', 2, )),
2779 'style:family': 'table-cell'}, nsdict=SNSD)
2780 el1 = SubElement(el, 'style:table-cell-properties', attrib={
2781 'fo:padding': '0.097cm',
2782 'fo:border-left': '0.035cm solid #000000',
2783 'fo:border-right': '0.035cm solid #000000',
2784 'fo:border-top': 'none',
2785 'fo:border-bottom': '0.035cm solid #000000'}, nsdict=SNSD)
2787 # Generate table data
2788 el = self.append_child('table:table', attrib={
2789 'table:name': self.rststyle(table_name),
2790 'table:style-name': self.rststyle(table_name),
2792 el1 = SubElement(el, 'table:table-column', attrib={
2793 'table:style-name': self.rststyle(
2794 '%s.%%c' % table_name, ( 'A', ))})
2795 el1 = SubElement(el, 'table:table-column', attrib={
2796 'table:style-name': self.rststyle(
2797 '%s.%%c' % table_name, ( 'B', ))})
2798 el1 = SubElement(el, 'table:table-header-rows')
2799 el2 = SubElement(el1, 'table:table-row')
2800 el3 = SubElement(el2, 'table:table-cell', attrib={
2801 'table:style-name': self.rststyle(
2802 '%s.%%c%%d' % table_name, ( 'A', 1, )),
2803 'office:value-type': 'string'})
2804 el4 = SubElement(el3, 'text:p', attrib={
2805 'text:style-name': 'Table_20_Heading'})
2806 el4.text= 'Option'
2807 el3 = SubElement(el2, 'table:table-cell', attrib={
2808 'table:style-name': self.rststyle(
2809 '%s.%%c%%d' % table_name, ( 'B', 1, )),
2810 'office:value-type': 'string'})
2811 el4 = SubElement(el3, 'text:p', attrib={
2812 'text:style-name': 'Table_20_Heading'})
2813 el4.text= 'Description'
2814 self.set_current_element(el)
2816 def depart_option_list(self, node):
2817 self.set_to_parent()
2819 def visit_option_list_item(self, node):
2820 el = self.append_child('table:table-row')
2821 self.set_current_element(el)
2823 def depart_option_list_item(self, node):
2824 self.set_to_parent()
2826 def visit_option_group(self, node):
2827 el = self.append_child('table:table-cell', attrib={
2828 'table:style-name': 'Table%d.A2' % self.table_count,
2829 'office:value-type': 'string',
2831 self.set_current_element(el)
2833 def depart_option_group(self, node):
2834 self.set_to_parent()
2836 def visit_option(self, node):
2837 el = self.append_child('text:p', attrib={
2838 'text:style-name': 'Table_20_Contents'})
2839 el.text = node.astext()
2841 def depart_option(self, node):
2842 pass
2844 def visit_option_string(self, node):
2845 pass
2847 def depart_option_string(self, node):
2848 pass
2850 def visit_option_argument(self, node):
2851 pass
2853 def depart_option_argument(self, node):
2854 pass
2856 def visit_description(self, node):
2857 el = self.append_child('table:table-cell', attrib={
2858 'table:style-name': 'Table%d.B2' % self.table_count,
2859 'office:value-type': 'string',
2861 el1 = SubElement(el, 'text:p', attrib={
2862 'text:style-name': 'Table_20_Contents'})
2863 el1.text = node.astext()
2864 raise nodes.SkipChildren()
2866 def depart_description(self, node):
2867 pass
2869 def visit_paragraph(self, node):
2870 self.in_paragraph = True
2871 if self.in_header:
2872 el = self.append_p('header')
2873 elif self.in_footer:
2874 el = self.append_p('footer')
2875 else:
2876 style_name = self.paragraph_style_stack[-1]
2877 el = self.append_child('text:p',
2878 attrib={'text:style-name': style_name})
2879 self.append_pending_ids(el)
2880 self.set_current_element(el)
2882 def depart_paragraph(self, node):
2883 self.in_paragraph = False
2884 self.set_to_parent()
2885 if self.in_header:
2886 self.header_content.append(
2887 self.current_element.getchildren()[-1])
2888 self.current_element.remove(
2889 self.current_element.getchildren()[-1])
2890 elif self.in_footer:
2891 self.footer_content.append(
2892 self.current_element.getchildren()[-1])
2893 self.current_element.remove(
2894 self.current_element.getchildren()[-1])
2896 def visit_problematic(self, node):
2897 pass
2899 def depart_problematic(self, node):
2900 pass
2902 def visit_raw(self, node):
2903 if 'format' in node.attributes:
2904 formats = node.attributes['format']
2905 formatlist = formats.split()
2906 if 'odt' in formatlist:
2907 rawstr = node.astext()
2908 attrstr = ' '.join(['%s="%s"' % (k, v, )
2909 for k,v in CONTENT_NAMESPACE_ATTRIB.items()])
2910 contentstr = '<stuff %s>%s</stuff>' % (attrstr, rawstr, )
2911 if WhichElementTree != "lxml":
2912 contentstr = contentstr.encode("utf-8")
2913 content = etree.fromstring(contentstr)
2914 elements = content.getchildren()
2915 if len(elements) > 0:
2916 el1 = elements[0]
2917 if self.in_header:
2918 pass
2919 elif self.in_footer:
2920 pass
2921 else:
2922 self.current_element.append(el1)
2923 raise nodes.SkipChildren()
2925 def depart_raw(self, node):
2926 if self.in_header:
2927 pass
2928 elif self.in_footer:
2929 pass
2930 else:
2931 pass
2933 def visit_reference(self, node):
2934 text = node.astext()
2935 if self.settings.create_links:
2936 if node.has_key('refuri'):
2937 href = node['refuri']
2938 if ( self.settings.cloak_email_addresses
2939 and href.startswith('mailto:')):
2940 href = self.cloak_mailto(href)
2941 el = self.append_child('text:a', attrib={
2942 'xlink:href': '%s' % href,
2943 'xlink:type': 'simple',
2945 self.set_current_element(el)
2946 elif node.has_key('refid'):
2947 if self.settings.create_links:
2948 href = node['refid']
2949 el = self.append_child('text:reference-ref', attrib={
2950 'text:ref-name': '%s' % href,
2951 'text:reference-format': 'text',
2953 else:
2954 self.document.reporter.warning(
2955 'References must have "refuri" or "refid" attribute.')
2956 if (self.in_table_of_contents and
2957 len(node.children) >= 1 and
2958 isinstance(node.children[0], docutils.nodes.generated)):
2959 node.remove(node.children[0])
2961 def depart_reference(self, node):
2962 if self.settings.create_links:
2963 if node.has_key('refuri'):
2964 self.set_to_parent()
2966 def visit_rubric(self, node):
2967 style_name = self.rststyle('rubric')
2968 classes = node.get('classes')
2969 if classes:
2970 class1 = classes[0]
2971 if class1:
2972 style_name = class1
2973 el = SubElement(self.current_element, 'text:h', attrib = {
2974 #'text:outline-level': '%d' % section_level,
2975 #'text:style-name': 'Heading_20_%d' % section_level,
2976 'text:style-name': style_name,
2978 text = node.astext()
2979 el.text = self.encode(text)
2981 def depart_rubric(self, node):
2982 pass
2984 def visit_section(self, node, move_ids=1):
2985 self.section_level += 1
2986 self.section_count += 1
2987 if self.settings.create_sections:
2988 el = self.append_child('text:section', attrib={
2989 'text:name': 'Section%d' % self.section_count,
2990 'text:style-name': 'Sect%d' % self.section_level,
2992 self.set_current_element(el)
2994 def depart_section(self, node):
2995 self.section_level -= 1
2996 if self.settings.create_sections:
2997 self.set_to_parent()
2999 def visit_strong(self, node):
3000 el = SubElement(self.current_element, 'text:span',
3001 attrib={'text:style-name': self.rststyle('strong')})
3002 self.set_current_element(el)
3004 def depart_strong(self, node):
3005 self.set_to_parent()
3007 def visit_substitution_definition(self, node):
3008 raise nodes.SkipChildren()
3010 def depart_substitution_definition(self, node):
3011 pass
3013 def visit_system_message(self, node):
3014 pass
3016 def depart_system_message(self, node):
3017 pass
3019 def get_table_style(self, node):
3020 table_style = None
3021 table_name = None
3022 use_predefined_table_style = False
3023 str_classes = node.get('classes')
3024 if str_classes is not None:
3025 for str_class in str_classes:
3026 if str_class.startswith(TABLESTYLEPREFIX):
3027 table_name = str_class
3028 use_predefined_table_style = True
3029 break
3030 if table_name is not None:
3031 table_style = self.table_styles.get(table_name)
3032 if table_style is None:
3033 # If we can't find the table style, issue warning
3034 # and use the default table style.
3035 self.document.reporter.warning(
3036 'Can\'t find table style "%s". Using default.' % (
3037 table_name, ))
3038 table_name = TABLENAMEDEFAULT
3039 table_style = self.table_styles.get(table_name)
3040 if table_style is None:
3041 # If we can't find the default table style, issue a warning
3042 # and use a built-in default style.
3043 self.document.reporter.warning(
3044 'Can\'t find default table style "%s". Using built-in default.' % (
3045 table_name, ))
3046 table_style = BUILTIN_DEFAULT_TABLE_STYLE
3047 else:
3048 table_name = TABLENAMEDEFAULT
3049 table_style = self.table_styles.get(table_name)
3050 if table_style is None:
3051 # If we can't find the default table style, issue a warning
3052 # and use a built-in default style.
3053 self.document.reporter.warning(
3054 'Can\'t find default table style "%s". Using built-in default.' % (
3055 table_name, ))
3056 table_style = BUILTIN_DEFAULT_TABLE_STYLE
3057 return table_style
3059 def visit_table(self, node):
3060 self.table_count += 1
3061 table_style = self.get_table_style(node)
3062 table_name = '%s%%d' % TABLESTYLEPREFIX
3063 el1 = SubElement(self.automatic_styles, 'style:style', attrib={
3064 'style:name': self.rststyle(
3065 '%s' % table_name, ( self.table_count, )),
3066 'style:family': 'table',
3067 }, nsdict=SNSD)
3068 if table_style.backgroundcolor is None:
3069 el1_1 = SubElement(el1, 'style:table-properties', attrib={
3070 #'style:width': '17.59cm',
3071 #'table:align': 'margins',
3072 'table:align': 'left',
3073 'fo:margin-top': '0in',
3074 'fo:margin-bottom': '0.10in',
3075 }, nsdict=SNSD)
3076 else:
3077 el1_1 = SubElement(el1, 'style:table-properties', attrib={
3078 #'style:width': '17.59cm',
3079 'table:align': 'margins',
3080 'fo:margin-top': '0in',
3081 'fo:margin-bottom': '0.10in',
3082 'fo:background-color': table_style.backgroundcolor,
3083 }, nsdict=SNSD)
3084 # We use a single cell style for all cells in this table.
3085 # That's probably not correct, but seems to work.
3086 el2 = SubElement(self.automatic_styles, 'style:style', attrib={
3087 'style:name': self.rststyle(
3088 '%s.%%c%%d' % table_name, ( self.table_count, 'A', 1, )),
3089 'style:family': 'table-cell',
3090 }, nsdict=SNSD)
3091 thickness = self.settings.table_border_thickness
3092 if thickness is None:
3093 line_style1 = table_style.border
3094 else:
3095 line_style1 = '0.%03dcm solid #000000' % (thickness, )
3096 el2_1 = SubElement(el2, 'style:table-cell-properties', attrib={
3097 'fo:padding': '0.049cm',
3098 'fo:border-left': line_style1,
3099 'fo:border-right': line_style1,
3100 'fo:border-top': line_style1,
3101 'fo:border-bottom': line_style1,
3102 }, nsdict=SNSD)
3103 title = None
3104 for child in node.children:
3105 if child.tagname == 'title':
3106 title = child.astext()
3107 break
3108 if title is not None:
3109 el3 = self.append_p('table-title', title)
3110 else:
3111 pass
3112 el4 = SubElement(self.current_element, 'table:table', attrib={
3113 'table:name': self.rststyle(
3114 '%s' % table_name, ( self.table_count, )),
3115 'table:style-name': self.rststyle(
3116 '%s' % table_name, ( self.table_count, )),
3118 self.set_current_element(el4)
3119 self.current_table_style = el1
3120 self.table_width = 0.0
3122 def depart_table(self, node):
3123 attribkey = add_ns('style:width', nsdict=SNSD)
3124 attribval = '%.4fin' % (self.table_width, )
3125 el1 = self.current_table_style
3126 el2 = el1[0]
3127 el2.attrib[attribkey] = attribval
3128 self.set_to_parent()
3130 def visit_tgroup(self, node):
3131 self.column_count = ord('A') - 1
3133 def depart_tgroup(self, node):
3134 pass
3136 def visit_colspec(self, node):
3137 self.column_count += 1
3138 colspec_name = self.rststyle(
3139 '%s%%d.%%s' % TABLESTYLEPREFIX,
3140 (self.table_count, chr(self.column_count), )
3142 colwidth = node['colwidth'] / 12.0
3143 el1 = SubElement(self.automatic_styles, 'style:style', attrib={
3144 'style:name': colspec_name,
3145 'style:family': 'table-column',
3146 }, nsdict=SNSD)
3147 el1_1 = SubElement(el1, 'style:table-column-properties', attrib={
3148 'style:column-width': '%.4fin' % colwidth
3150 nsdict=SNSD)
3151 el2 = self.append_child('table:table-column', attrib={
3152 'table:style-name': colspec_name,
3154 self.table_width += colwidth
3156 def depart_colspec(self, node):
3157 pass
3159 def visit_thead(self, node):
3160 el = self.append_child('table:table-header-rows')
3161 self.set_current_element(el)
3162 self.in_thead = True
3163 self.paragraph_style_stack.append('Table_20_Heading')
3165 def depart_thead(self, node):
3166 self.set_to_parent()
3167 self.in_thead = False
3168 self.paragraph_style_stack.pop()
3170 def visit_row(self, node):
3171 self.column_count = ord('A') - 1
3172 el = self.append_child('table:table-row')
3173 self.set_current_element(el)
3175 def depart_row(self, node):
3176 self.set_to_parent()
3178 def visit_entry(self, node):
3179 self.column_count += 1
3180 cellspec_name = self.rststyle(
3181 '%s%%d.%%c%%d' % TABLESTYLEPREFIX,
3182 (self.table_count, 'A', 1, )
3184 attrib={
3185 'table:style-name': cellspec_name,
3186 'office:value-type': 'string',
3188 morecols = node.get('morecols', 0)
3189 if morecols > 0:
3190 attrib['table:number-columns-spanned'] = '%d' % (morecols + 1,)
3191 self.column_count += morecols
3192 morerows = node.get('morerows', 0)
3193 if morerows > 0:
3194 attrib['table:number-rows-spanned'] = '%d' % (morerows + 1,)
3195 el1 = self.append_child('table:table-cell', attrib=attrib)
3196 self.set_current_element(el1)
3198 def depart_entry(self, node):
3199 self.set_to_parent()
3201 def visit_tbody(self, node):
3202 pass
3204 def depart_tbody(self, node):
3205 pass
3207 def visit_target(self, node):
3209 # I don't know how to implement targets in ODF.
3210 # How do we create a target in oowriter? A cross-reference?
3211 if not (node.has_key('refuri') or node.has_key('refid')
3212 or node.has_key('refname')):
3213 pass
3214 else:
3215 pass
3217 def depart_target(self, node):
3218 pass
3220 def visit_title(self, node, move_ids=1, title_type='title'):
3221 if isinstance(node.parent, docutils.nodes.section):
3222 section_level = self.section_level
3223 if section_level > 7:
3224 self.document.reporter.warning(
3225 'Heading/section levels greater than 7 not supported.')
3226 self.document.reporter.warning(
3227 ' Reducing to heading level 7 for heading: "%s"' % (
3228 node.astext(), ))
3229 section_level = 7
3230 el1 = self.append_child('text:h', attrib = {
3231 'text:outline-level': '%d' % section_level,
3232 #'text:style-name': 'Heading_20_%d' % section_level,
3233 'text:style-name': self.rststyle(
3234 'heading%d', (section_level, )),
3236 self.append_pending_ids(el1)
3237 self.set_current_element(el1)
3238 elif isinstance(node.parent, docutils.nodes.document):
3239 # text = self.settings.title
3240 #else:
3241 # text = node.astext()
3242 el1 = SubElement(self.current_element, 'text:p', attrib = {
3243 'text:style-name': self.rststyle(title_type),
3245 self.append_pending_ids(el1)
3246 text = node.astext()
3247 self.title = text
3248 self.found_doc_title = True
3249 self.set_current_element(el1)
3251 def depart_title(self, node):
3252 if (isinstance(node.parent, docutils.nodes.section) or
3253 isinstance(node.parent, docutils.nodes.document)):
3254 self.set_to_parent()
3256 def visit_subtitle(self, node, move_ids=1):
3257 self.visit_title(node, move_ids, title_type='subtitle')
3259 def depart_subtitle(self, node):
3260 self.depart_title(node)
3262 def visit_title_reference(self, node):
3263 el = self.append_child('text:span', attrib={
3264 'text:style-name': self.rststyle('quotation')})
3265 el.text = self.encode(node.astext())
3266 raise nodes.SkipChildren()
3268 def depart_title_reference(self, node):
3269 pass
3271 def generate_table_of_content_entry_template(self, el1):
3272 for idx in range(1, 11):
3273 el2 = SubElement(el1,
3274 'text:table-of-content-entry-template',
3275 attrib={
3276 'text:outline-level': "%d" % (idx, ),
3277 'text:style-name': self.rststyle('contents-%d' % (idx, )),
3279 el3 = SubElement(el2, 'text:index-entry-chapter')
3280 el3 = SubElement(el2, 'text:index-entry-text')
3281 el3 = SubElement(el2, 'text:index-entry-tab-stop', attrib={
3282 'style:leader-char': ".",
3283 'style:type': "right",
3285 el3 = SubElement(el2, 'text:index-entry-page-number')
3287 def find_title_label(self, node, class_type, label_key):
3288 label = ''
3289 title_node = None
3290 for child in node.children:
3291 if isinstance(child, class_type):
3292 title_node = child
3293 break
3294 if title_node is not None:
3295 label = title_node.astext()
3296 else:
3297 label = self.language.labels[label_key]
3298 return label
3300 def visit_topic(self, node):
3301 if 'classes' in node.attributes:
3302 if 'contents' in node.attributes['classes']:
3303 label = self.find_title_label(node, docutils.nodes.title,
3304 'contents')
3305 if self.settings.generate_oowriter_toc:
3306 el1 = self.append_child('text:table-of-content', attrib={
3307 'text:name': 'Table of Contents1',
3308 'text:protected': 'true',
3309 'text:style-name': 'Sect1',
3311 el2 = SubElement(el1,
3312 'text:table-of-content-source',
3313 attrib={
3314 'text:outline-level': '10',
3316 el3 =SubElement(el2, 'text:index-title-template', attrib={
3317 'text:style-name': 'Contents_20_Heading',
3319 el3.text = label
3320 self.generate_table_of_content_entry_template(el2)
3321 el4 = SubElement(el1, 'text:index-body')
3322 el5 = SubElement(el4, 'text:index-title')
3323 el6 = SubElement(el5, 'text:p', attrib={
3324 'text:style-name': self.rststyle('contents-heading'),
3326 el6.text = label
3327 self.save_current_element = self.current_element
3328 self.table_of_content_index_body = el4
3329 self.set_current_element(el4)
3330 else:
3331 el = self.append_p('horizontalline')
3332 el = self.append_p('centeredtextbody')
3333 el1 = SubElement(el, 'text:span',
3334 attrib={'text:style-name': self.rststyle('strong')})
3335 el1.text = label
3336 self.in_table_of_contents = True
3337 elif 'abstract' in node.attributes['classes']:
3338 el = self.append_p('horizontalline')
3339 el = self.append_p('centeredtextbody')
3340 el1 = SubElement(el, 'text:span',
3341 attrib={'text:style-name': self.rststyle('strong')})
3342 label = self.find_title_label(node, docutils.nodes.title,
3343 'abstract')
3344 el1.text = label
3345 elif 'dedication' in node.attributes['classes']:
3346 el = self.append_p('horizontalline')
3347 el = self.append_p('centeredtextbody')
3348 el1 = SubElement(el, 'text:span',
3349 attrib={'text:style-name': self.rststyle('strong')})
3350 label = self.find_title_label(node, docutils.nodes.title,
3351 'dedication')
3352 el1.text = label
3354 def depart_topic(self, node):
3355 if 'classes' in node.attributes:
3356 if 'contents' in node.attributes['classes']:
3357 if self.settings.generate_oowriter_toc:
3358 self.update_toc_page_numbers(
3359 self.table_of_content_index_body)
3360 self.set_current_element(self.save_current_element)
3361 else:
3362 el = self.append_p('horizontalline')
3363 self.in_table_of_contents = False
3365 def update_toc_page_numbers(self, el):
3366 collection = []
3367 self.update_toc_collect(el, 0, collection)
3368 self.update_toc_add_numbers(collection)
3370 def update_toc_collect(self, el, level, collection):
3371 collection.append((level, el))
3372 level += 1
3373 for child_el in el.getchildren():
3374 if child_el.tag != 'text:index-body':
3375 self.update_toc_collect(child_el, level, collection)
3377 def update_toc_add_numbers(self, collection):
3378 for level, el1 in collection:
3379 if (el1.tag == 'text:p' and
3380 el1.text != 'Table of Contents'):
3381 el2 = SubElement(el1, 'text:tab')
3382 el2.tail = '9999'
3385 def visit_transition(self, node):
3386 el = self.append_p('horizontalline')
3388 def depart_transition(self, node):
3389 pass
3392 # Admonitions
3394 def visit_warning(self, node):
3395 self.generate_admonition(node, 'warning')
3397 def depart_warning(self, node):
3398 self.paragraph_style_stack.pop()
3400 def visit_attention(self, node):
3401 self.generate_admonition(node, 'attention')
3403 depart_attention = depart_warning
3405 def visit_caution(self, node):
3406 self.generate_admonition(node, 'caution')
3408 depart_caution = depart_warning
3410 def visit_danger(self, node):
3411 self.generate_admonition(node, 'danger')
3413 depart_danger = depart_warning
3415 def visit_error(self, node):
3416 self.generate_admonition(node, 'error')
3418 depart_error = depart_warning
3420 def visit_hint(self, node):
3421 self.generate_admonition(node, 'hint')
3423 depart_hint = depart_warning
3425 def visit_important(self, node):
3426 self.generate_admonition(node, 'important')
3428 depart_important = depart_warning
3430 def visit_note(self, node):
3431 self.generate_admonition(node, 'note')
3433 depart_note = depart_warning
3435 def visit_tip(self, node):
3436 self.generate_admonition(node, 'tip')
3438 depart_tip = depart_warning
3440 def visit_admonition(self, node):
3441 title = None
3442 for child in node.children:
3443 if child.tagname == 'title':
3444 title = child.astext()
3445 if title is None:
3446 classes1 = node.get('classes')
3447 if classes1:
3448 title = classes1[0]
3449 self.generate_admonition(node, 'generic', title)
3451 depart_admonition = depart_warning
3453 def generate_admonition(self, node, label, title=None):
3454 if hasattr(self.language, 'labels'):
3455 translated_label = self.language.labels[label]
3456 else:
3457 translated_label = label
3458 el1 = SubElement(self.current_element, 'text:p', attrib={
3459 'text:style-name': self.rststyle(
3460 'admon-%s-hdr', (label, )),
3462 if title:
3463 el1.text = title
3464 else:
3465 el1.text = '%s!' % (translated_label.capitalize(), )
3466 s1 = self.rststyle('admon-%s-body', (label, ))
3467 self.paragraph_style_stack.append(s1)
3470 # Roles (e.g. subscript, superscript, strong, ...
3472 def visit_subscript(self, node):
3473 el = self.append_child('text:span', attrib={
3474 'text:style-name': 'rststyle-subscript',
3476 self.set_current_element(el)
3478 def depart_subscript(self, node):
3479 self.set_to_parent()
3481 def visit_superscript(self, node):
3482 el = self.append_child('text:span', attrib={
3483 'text:style-name': 'rststyle-superscript',
3485 self.set_current_element(el)
3487 def depart_superscript(self, node):
3488 self.set_to_parent()
3491 # Use an own reader to modify transformations done.
3492 class Reader(standalone.Reader):
3494 def get_transforms(self):
3495 default = standalone.Reader.get_transforms(self)
3496 if self.settings.create_links:
3497 return default
3498 return [ i
3499 for i in default
3500 if i is not references.DanglingReferences ]