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