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