2 # Author: Dave Kuhlman <dkuhlman@davekuhlman.org>
3 # Copyright: This module has been placed in the public domain.
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.
12 __docformat__
= 'reStructuredText'
15 from configparser
import ConfigParser
17 from io
import StringIO
22 from pathlib
import Path
29 from xml
.etree
import ElementTree
as etree
30 from xml
.dom
import minidom
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.
42 import pygments
.lexers
43 from .pygmentsformatter
import (OdtPygmentsProgFormatter
,
44 OdtPygmentsLaTeXFormatter
)
45 except (ImportError, SyntaxError):
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.')
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
71 _ElementInterface
= etree
._ElementInterface
74 class _ElementInterfaceWrapper(_ElementInterface
):
75 def __init__(self
, tag
, attrib
=None) -> None:
76 _ElementInterface
.__init
__(self
, tag
, attrib
)
79 def setparent(self
, parent
) -> None:
80 _parents
[self
] = parent
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',
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
):
255 tag
, attrib
= fix_ns(tag
, attrib
, nsdict
)
256 return _ElementInterfaceWrapper(tag
, attrib
)
259 def SubElement(parent
, tag
, attrib
=None, nsmap
=None, nsdict
=CNSD
):
262 tag
, attrib
= fix_ns(tag
, attrib
, nsdict
)
263 el
= _ElementInterfaceWrapper(tag
, attrib
)
269 def fix_ns(tag
, attrib
, nsdict
):
270 nstag
= add_ns(tag
, nsdict
)
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
):
283 outstream
= StringIO()
284 et
.write(outstream
, encoding
="unicode")
285 s1
= outstream
.getvalue()
290 def escape_cdata(text
):
291 text
= text
.replace("&", "&")
292 text
= text
.replace("<", "<")
293 text
= text
.replace(">", ">")
296 if ord(char
) >= ord("\x7f"):
297 ascii_
+= "&#x%X;" % (ord(char
), )
309 def __init__(self
, border
=None, backgroundcolor
=None) -> None:
311 self
.backgroundcolor
= backgroundcolor
313 def get_border_(self
):
316 def set_border_(self
, border
) -> None:
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
) -> None:
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.
337 def __init__(self
, level
, sibling_level
=True, nested_level
=True) -> None:
339 self
.sibling_level
= sibling_level
340 self
.nested_level
= nested_level
342 def set_sibling(self
, sibling_level
) -> None:
343 self
.sibling_level
= sibling_level
345 def get_sibling(self
):
346 return self
.sibling_level
348 def set_nested(self
, nested_level
) -> None:
349 self
.nested_level
= nested_level
351 def get_nested(self
):
352 return self
.nested_level
354 def set_level(self
, level
) -> None:
361 class Writer(writers
.Writer
):
363 MIME_TYPE
= 'application/vnd.oasis.opendocument.text'
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
))
382 'ODF-Specific Options.',
385 ('Specify a stylesheet. '
386 'Default: "%s"' % default_stylesheet_path
,
389 'default': default_stylesheet_path
,
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'],
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'],
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. '
410 ['--table-border-thickness'],
413 'validator': frontend
.validate_nonnegative_int
}),
414 ('Add syntax highlighting in literal code blocks.',
415 ['--add-syntax-highlighting'],
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'],
424 'action': 'store_false',
425 'dest': 'add_syntax_highlighting',
426 'validator': frontend
.validate_boolean
}),
427 ('Create sections for headers. (default)',
428 ['--create-sections'],
430 'action': 'store_true',
431 'dest': 'create_sections',
432 'validator': frontend
.validate_boolean
}),
433 ('Do not create sections for headers.',
436 'action': 'store_false',
437 'dest': 'create_sections',
438 'validator': frontend
.validate_boolean
}),
442 'action': 'store_true',
443 'dest': 'create_links',
444 'validator': frontend
.validate_boolean
}),
445 ('Do not create links. (default)',
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'],
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'],
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'],
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'],
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'],
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
) -> None:
504 writers
.Writer
.__init
__(self
)
505 self
.translator_class
= ODFTranslator
507 def translate(self
) -> None:
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
) -> None:
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
)
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.
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()
548 elif len(subtag
) == 1:
549 break # 1-letter tag is never before valid region tag
550 if region_code
is None:
552 rcode
= locale
.normalize(language_code
)
554 rcode
= language_code
555 rcode
= rcode
.split('_')
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
)
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
)
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."""
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}'
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}'
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}'
607 if family
== 'paragraph' or family
== 'graphic':
609 '{urn:oasis:names:tc:opendocument:xmlns:style:1.0}'
613 "Cannot find 'text-properties' "
614 "element in styles.xml")
615 if language_code
is not None:
617 '{urn:oasis:names:tc:opendocument:xmlns:'
618 'xsl-fo-compatible:1.0}language'] = language_code
620 '{urn:oasis:names:tc:opendocument:xmlns:'
621 'style:1.0}language-complex'] = language_code
623 modified_nodes
.add(n3
)
624 if region_code
is not None:
626 '{urn:oasis:names:tc:opendocument:xmlns:'
627 'xsl-fo-compatible:1.0}country'] = region_code
629 '{urn:oasis:names:tc:opendocument:xmlns:'
630 'style:1.0}country-complex'] = region_code
632 modified_nodes
.add(n3
)
633 return updated
, stylesheet_root
, modified_nodes
636 self
, zfile
, name
, bytes_
, compress_type
=zipfile
.ZIP_DEFLATED
,
638 localtime
= time
.localtime(time
.time())
639 zinfo
= zipfile
.ZipInfo(name
, localtime
)
640 # Add some standard UNIX file access permissions (-rw-r--r--).
641 zinfo
.external_attr
= (0x81a4 & 0xFFFF) << 16
642 zinfo
.compress_type
= compress_type
643 zfile
.writestr(zinfo
, bytes_
)
645 def store_embedded_files(self
, zfile
) -> None:
646 embedded_files
= self
.visitor
.get_embedded_file_list()
647 for source
, destination
in embedded_files
:
651 zfile
.write(source
, destination
)
653 self
.document
.reporter
.warning(
654 "Can't open file %s." % (source
, ))
656 def get_settings(self
):
658 modeled after get_stylesheet
660 stylespath
= self
.settings
.stylesheet
661 zfile
= zipfile
.ZipFile(stylespath
, 'r')
662 s1
= zfile
.read('settings.xml')
666 def get_stylesheet(self
):
667 """Get the stylesheet from the visitor.
668 Ask the visitor to setup the page.
670 return self
.visitor
.setup_page()
672 def copy_from_stylesheet(self
, outzipfile
) -> None:
673 """Copy images, settings, etc from the stylesheet doc into target doc.
675 stylespath
= self
.settings
.stylesheet
676 if not stylespath
.endswith('.odt'):
677 return # an '.xml' stylesheet does not have settings or images
678 inzipfile
= zipfile
.ZipFile(stylespath
, 'r')
680 s1
= inzipfile
.read('settings.xml')
681 self
.write_zip_str(outzipfile
, 'settings.xml', s1
)
683 namelist
= inzipfile
.namelist()
684 for name
in namelist
:
685 if name
.startswith('Pictures/'):
686 imageobj
= inzipfile
.read(name
)
687 outzipfile
.writestr(name
, imageobj
)
690 def assemble_parts(self
) -> None:
693 def create_manifest(self
):
696 attrib
=MANIFEST_NAMESPACE_ATTRIB
,
697 nsdict
=MANIFEST_NAMESPACE_DICT
,
699 doc
= etree
.ElementTree(root
)
700 SubElement(root
, 'manifest:file-entry', attrib
={
701 'manifest:media-type': self
.MIME_TYPE
,
702 'manifest:full-path': '/',
704 SubElement(root
, 'manifest:file-entry', attrib
={
705 'manifest:media-type': 'text/xml',
706 'manifest:full-path': 'content.xml',
708 SubElement(root
, 'manifest:file-entry', attrib
={
709 'manifest:media-type': 'text/xml',
710 'manifest:full-path': 'styles.xml',
712 SubElement(root
, 'manifest:file-entry', attrib
={
713 'manifest:media-type': 'text/xml',
714 'manifest:full-path': 'settings.xml',
716 SubElement(root
, 'manifest:file-entry', attrib
={
717 'manifest:media-type': 'text/xml',
718 'manifest:full-path': 'meta.xml',
721 doc
= minidom
.parseString(s1
)
722 return doc
.toprettyxml(' ')
724 def create_meta(self
):
726 'office:document-meta',
727 attrib
=META_NAMESPACE_ATTRIB
,
728 nsdict
=META_NAMESPACE_DICT
,
730 doc
= etree
.ElementTree(root
)
731 root
= SubElement(root
, 'office:meta', nsdict
=METNSD
)
732 el1
= SubElement(root
, 'meta:generator', nsdict
=METNSD
)
733 el1
.text
= 'Docutils/rst2odf.py/%s' % (VERSION
, )
734 s1
= os
.environ
.get('USER', '')
735 el1
= SubElement(root
, 'meta:initial-creator', nsdict
=METNSD
)
737 s2
= time
.strftime('%Y-%m-%dT%H:%M:%S', time
.localtime())
738 el1
= SubElement(root
, 'meta:creation-date', nsdict
=METNSD
)
740 el1
= SubElement(root
, 'dc:creator', nsdict
=METNSD
)
742 el1
= SubElement(root
, 'dc:date', nsdict
=METNSD
)
744 el1
= SubElement(root
, 'dc:language', nsdict
=METNSD
)
746 el1
= SubElement(root
, 'meta:editing-cycles', nsdict
=METNSD
)
748 el1
= SubElement(root
, 'meta:editing-duration', nsdict
=METNSD
)
749 el1
.text
= 'PT00M01S'
750 title
= self
.visitor
.get_title()
751 el1
= SubElement(root
, 'dc:title', nsdict
=METNSD
)
755 el1
.text
= '[no title]'
756 for prop
, value
in self
.visitor
.get_meta_dict().items():
757 # 'keywords', 'description', and 'subject' have their own fields:
758 if prop
== 'keywords':
759 keywords
= re
.split(', *', value
)
760 for keyword
in keywords
:
761 el1
= SubElement(root
, 'meta:keyword', nsdict
=METNSD
)
763 elif prop
== 'description':
764 el1
= SubElement(root
, 'dc:description', nsdict
=METNSD
)
766 elif prop
== 'subject':
767 el1
= SubElement(root
, 'dc:subject', nsdict
=METNSD
)
769 else: # Store remaining properties as custom/user-defined
770 el1
= SubElement(root
, 'meta:user-defined',
771 attrib
={'meta:name': prop
}, nsdict
=METNSD
)
774 # doc = minidom.parseString(s1)
775 # s1 = doc.toprettyxml(' ')
779 # class ODFTranslator(nodes.SparseNodeVisitor):
780 class ODFTranslator(nodes
.GenericNodeVisitor
):
783 'attribution', 'blockindent', 'blockquote', 'blockquote-bulletitem',
784 'blockquote-bulletlist', 'blockquote-enumitem', 'blockquote-enumlist',
785 'bulletitem', 'bulletlist',
787 'centeredtextbody', 'codeblock', 'codeblock-indented',
788 'codeblock-classname', 'codeblock-comment', 'codeblock-functionname',
789 'codeblock-keyword', 'codeblock-name', 'codeblock-number',
790 'codeblock-operator', 'codeblock-string', 'emphasis', 'enumitem',
791 'enumlist', 'epigraph', 'epigraph-bulletitem', 'epigraph-bulletlist',
792 'epigraph-enumitem', 'epigraph-enumlist', 'footer',
793 'footnote', 'citation',
794 'header', 'highlights', 'highlights-bulletitem',
795 'highlights-bulletlist', 'highlights-enumitem', 'highlights-enumlist',
796 'horizontalline', 'inlineliteral', 'quotation', 'rubric',
797 'strong', 'table-title', 'textbody', 'tocbulletlist', 'tocenumlist',
807 'admon-attention-hdr',
808 'admon-attention-body',
810 'admon-caution-body',
816 'admon-generic-body',
819 'admon-important-hdr',
820 'admon-important-body',
826 'admon-warning-body',
828 'tableoption.%c', 'tableoption.%c%d', 'Table%d', 'Table%d.%c',
836 'image', 'figureframe',
839 def __init__(self
, document
) -> None:
840 # nodes.SparseNodeVisitor.__init__(self, document)
841 nodes
.GenericNodeVisitor
.__init
__(self
, document
)
842 self
.settings
= document
.settings
843 if self
.settings
.output_encoding
== 'unicode':
844 self
.document
.reporter
.severe('The ODT writer returns `bytes` '
845 'that cannot be decoded to `str`')
846 # TODO: return document as "flat" XML?
847 self
.language_code
= self
.settings
.language_code
848 self
.language
= languages
.get_language(
852 if self
.settings
.odf_config_file
:
853 parser
= ConfigParser()
854 parser
.read(self
.settings
.odf_config_file
)
855 for rststyle
, format
in parser
.items("Formats"):
856 if rststyle
not in self
.used_styles
:
857 self
.document
.reporter
.warning(
858 'Style "%s" is not a style used by odtwriter.' % (
860 self
.format_map
[rststyle
] = format
861 self
.section_level
= 0
862 self
.section_count
= 0
863 # Create ElementTree content and styles documents.
865 'office:document-content',
866 attrib
=CONTENT_NAMESPACE_ATTRIB
,
868 self
.content_tree
= etree
.ElementTree(element
=root
)
869 self
.current_element
= root
870 SubElement(root
, 'office:scripts')
871 SubElement(root
, 'office:font-face-decls')
872 el
= SubElement(root
, 'office:automatic-styles')
873 self
.automatic_styles
= el
874 el
= SubElement(root
, 'office:body')
875 el
= self
.generate_content_element(el
)
876 self
.current_element
= el
877 self
.body_text_element
= el
878 self
.paragraph_style_stack
= [self
.rststyle('textbody'), ]
879 self
.list_style_stack
= []
881 self
.column_count
= ord('A') - 1
882 self
.trace_level
= -1
883 self
.optiontablestyles_generated
= False
884 self
.field_name
= None
885 self
.field_element
= None
888 self
.image_style_count
= 0
890 self
.embedded_file_list
= []
891 self
.syntaxhighlighting
= 1
892 self
.syntaxhighlight_lexer
= 'python'
893 self
.header_content
= []
894 self
.footer_content
= []
895 self
.in_header
= False
896 self
.in_footer
= False
898 self
.in_table_of_contents
= False
899 self
.table_of_content_index_body
= None
901 self
.def_list_level
= 0
902 self
.footnote_ref_dict
= {}
903 self
.footnote_list
= []
904 self
.footnote_chars_idx
= 0
905 self
.footnote_level
= 0
906 self
.pending_ids
= []
907 self
.in_paragraph
= False
908 self
.found_doc_title
= False
909 self
.bumped_list_level_stack
= []
911 self
.line_block_level
= 0
912 self
.line_indent_level
= 0
913 self
.citation_id
= None
914 self
.style_index
= 0 # use to form unique style names
915 self
.str_stylesheet
= ''
916 self
.str_stylesheetcontent
= ''
917 self
.dom_stylesheet
= None
918 self
.table_styles
= None
919 self
.in_citation
= False
921 # Keep track of nested styling classes
922 self
.inline_style_count_stack
= []
924 def get_str_stylesheet(self
):
925 return self
.str_stylesheet
927 def retrieve_styles(self
, extension
):
928 """Retrieve the stylesheet from a .xml or .odt (zip) file.
930 Store in `self.*_styles*` attributes.
932 stylespath
= self
.settings
.stylesheet
933 ext
= os
.path
.splitext(stylespath
)[1]
935 s1
= Path(stylespath
).read_text(encoding
='utf-8')
937 elif ext
== extension
:
938 zfile
= zipfile
.ZipFile(stylespath
, 'r')
939 s1
= zfile
.read('styles.xml')
940 s2
= zfile
.read('content.xml')
943 raise RuntimeError('stylesheet path (%s) must be %s or '
944 '.xml file' % (stylespath
, extension
))
945 self
.str_stylesheet
= s1
946 self
.str_stylesheetcontent
= s2
947 self
.dom_stylesheet
= etree
.fromstring(self
.str_stylesheet
)
950 # TODO: dom_stylesheetcontent is never used. Remove?
951 self
.dom_stylesheetcontent
= etree
.fromstring(
952 self
.str_stylesheetcontent
)
953 self
.table_styles
= self
.extract_table_styles(s2
)
955 def extract_table_styles(self
, styles_str
):
956 root
= etree
.fromstring(styles_str
)
958 auto_styles
= root
.find(
959 '{%s}automatic-styles' % (CNSD
['office'], ))
960 for stylenode
in auto_styles
:
961 name
= stylenode
.get('{%s}name' % (CNSD
['style'], ))
962 tablename
= name
.split('.')[0]
963 family
= stylenode
.get('{%s}family' % (CNSD
['style'], ))
964 if name
.startswith(TABLESTYLEPREFIX
):
965 tablestyle
= table_styles
.get(tablename
)
966 if tablestyle
is None:
967 tablestyle
= TableStyle()
968 table_styles
[tablename
] = tablestyle
969 if family
== 'table':
970 properties
= stylenode
.find(
971 '{%s}table-properties' % (CNSD
['style'], ))
972 property_
= properties
.get(
973 '{%s}%s' % (CNSD
['fo'], 'background-color', ))
974 if property_
is not None and property_
!= 'none':
975 tablestyle
.backgroundcolor
= property_
976 elif family
== 'table-cell':
977 properties
= stylenode
.find(
978 '{%s}table-cell-properties' % (CNSD
['style'], ))
979 if properties
is not None:
980 border
= self
.get_property(properties
)
981 if border
is not None:
982 tablestyle
.border
= border
985 def get_property(self
, stylenode
):
987 for propertyname
in TABLEPROPERTYNAMES
:
988 border
= stylenode
.get('{%s}%s' % (CNSD
['fo'], propertyname
, ))
989 if border
is not None and border
!= 'none':
993 def add_doc_title(self
) -> None:
994 text
= self
.settings
.title
997 if not self
.found_doc_title
:
998 el
= Element('text:p', attrib
={
999 'text:style-name': self
.rststyle('title'),
1002 self
.body_text_element
.insert(0, el
)
1003 el
= self
.find_first_text_p(self
.body_text_element
)
1005 self
.attach_page_style(el
)
1007 def find_first_text_p(self
, el
):
1008 """Search the generated doc and return the first <text:p> element.
1010 if el
.tag
== 'text:p' or el
.tag
== 'text:h':
1014 el1
= self
.find_first_text_p(child
)
1019 def attach_page_style(self
, el
) -> None:
1020 """Attach the default page style.
1022 Create an automatic-style that refers to the current style
1023 of this element and that refers to the default page style.
1025 current_style
= el
.get('text:style-name')
1026 style_name
= 'P1003'
1028 self
.automatic_styles
, 'style:style', attrib
={
1029 'style:name': style_name
,
1030 'style:master-page-name': "rststyle-pagedefault",
1031 'style:family': "paragraph",
1034 el1
.set('style:parent-style-name', current_style
)
1035 el
.set('text:style-name', style_name
)
1037 def rststyle(self
, name
, parameters
=()):
1039 Returns the style name to use for the given style.
1041 If `parameters` is given `name` must contain a matching number of
1042 ``%`` and is used as a format expression with `parameters` as
1045 name1
= name
% parameters
1046 return self
.format_map
.get(name1
, 'rststyle-%s' % name1
)
1048 def generate_content_element(self
, root
):
1049 return SubElement(root
, 'office:text')
1051 def setup_page(self
):
1052 self
.setup_paper(self
.dom_stylesheet
)
1053 if (len(self
.header_content
) > 0
1054 or len(self
.footer_content
) > 0
1055 or self
.settings
.custom_header
1056 or self
.settings
.custom_footer
):
1057 self
.add_header_footer(self
.dom_stylesheet
)
1058 return etree
.tostring(self
.dom_stylesheet
)
1060 def get_dom_stylesheet(self
):
1061 return self
.dom_stylesheet
1063 def setup_paper(self
, root_el
) -> None:
1064 # TODO: only call paperconf, if it is actually used
1065 # (i.e. page size removed from "styles.odt" with rst2odt_prepstyles.py
1066 # cf. conditional in walk() below)?
1068 dimensions
= subprocess
.check_output(('paperconf', '-s'),
1069 stderr
=subprocess
.STDOUT
)
1070 w
, h
= (float(s
) for s
in dimensions
.split())
1071 except (subprocess
.CalledProcessError
, FileNotFoundError
, ValueError):
1072 self
.document
.reporter
.info(
1073 'Cannot use `paperconf`, defaulting to Letter.')
1074 w
, h
= 612, 792 # default to Letter
1076 def walk(el
) -> None:
1077 if el
.tag
== "{%s}page-layout-properties" % SNSD
["style"] and \
1078 "{%s}page-width" % SNSD
["fo"] not in el
.attrib
:
1079 el
.attrib
["{%s}page-width" % SNSD
["fo"]] = "%.3fpt" % w
1080 el
.attrib
["{%s}page-height" % SNSD
["fo"]] = "%.3fpt" % h
1081 el
.attrib
["{%s}margin-left" % SNSD
["fo"]] = \
1082 el
.attrib
["{%s}margin-right" % SNSD
["fo"]] = \
1084 el
.attrib
["{%s}margin-top" % SNSD
["fo"]] = \
1085 el
.attrib
["{%s}margin-bottom" % SNSD
["fo"]] = \
1092 def add_header_footer(self
, root_el
) -> None:
1093 automatic_styles
= root_el
.find(
1094 '{%s}automatic-styles' % SNSD
['office'])
1095 path
= '{%s}master-styles' % (NAME_SPACE_1
, )
1096 master_el
= root_el
.find(path
)
1097 if master_el
is None:
1099 path
= '{%s}master-page' % (SNSD
['style'], )
1100 master_el_container
= master_el
.findall(path
)
1102 target_attrib
= '{%s}name' % (SNSD
['style'], )
1103 target_name
= self
.rststyle('pagedefault')
1104 for el
in master_el_container
:
1105 if el
.get(target_attrib
) == target_name
:
1108 if master_el
is None:
1111 if self
.header_content
or self
.settings
.custom_header
:
1113 el1
, 'style:header',
1114 attrib
=STYLES_NAMESPACE_ATTRIB
,
1115 nsdict
=STYLES_NAMESPACE_DICT
,
1117 for el
in self
.header_content
:
1118 attrkey
= add_ns('text:style-name', nsdict
=SNSD
)
1119 el
.attrib
[attrkey
] = self
.rststyle('header')
1121 if self
.settings
.custom_header
:
1122 self
.create_custom_headfoot(
1124 self
.settings
.custom_header
, 'header', automatic_styles
)
1125 if self
.footer_content
or self
.settings
.custom_footer
:
1127 el1
, 'style:footer',
1128 attrib
=STYLES_NAMESPACE_ATTRIB
,
1129 nsdict
=STYLES_NAMESPACE_DICT
,
1131 for el
in self
.footer_content
:
1132 attrkey
= add_ns('text:style-name', nsdict
=SNSD
)
1133 el
.attrib
[attrkey
] = self
.rststyle('footer')
1135 if self
.settings
.custom_footer
:
1136 self
.create_custom_headfoot(
1138 self
.settings
.custom_footer
, 'footer', automatic_styles
)
1140 code_none
, code_field
, code_text
= list(range(3))
1141 field_pat
= re
.compile(r
'%(..?)%')
1143 def create_custom_headfoot(
1144 self
, parent
, text
, style_name
, automatic_styles
):
1145 parent
= SubElement(parent
, 'text:p', attrib
={
1146 'text:style-name': self
.rststyle(style_name
),
1148 current_element
= None
1149 field_iter
= self
.split_field_specifiers_iter(text
)
1150 for item
in field_iter
:
1151 if item
[0] == ODFTranslator
.code_field
:
1154 't1', 't2', 't3', 't4',
1155 'd1', 'd2', 'd3', 'd4', 'd5',
1157 msg
= 'bad field spec: %%%s%%' % (item
[1], )
1158 raise RuntimeError(msg
)
1159 el1
= self
.make_field_element(
1161 item
[1], style_name
, automatic_styles
)
1163 msg
= 'bad field spec: %%%s%%' % (item
[1], )
1164 raise RuntimeError(msg
)
1166 current_element
= el1
1168 if current_element
is None:
1169 parent
.text
= item
[1]
1171 current_element
.tail
= item
[1]
1173 def make_field_element(self
, parent
, text
, style_name
, automatic_styles
):
1175 el1
= SubElement(parent
, 'text:page-number', attrib
={
1176 # 'text:style-name': self.rststyle(style_name),
1177 'text:select-page': 'current',
1180 el1
= SubElement(parent
, 'text:page-count', attrib
={
1181 # 'text:style-name': self.rststyle(style_name),
1184 self
.style_index
+= 1
1185 el1
= SubElement(parent
, 'text:time', attrib
={
1186 'text:style-name': self
.rststyle(style_name
),
1187 'text:fixed': 'true',
1188 'style:data-style-name':
1189 'rst-time-style-%d' % self
.style_index
,
1191 el2
= SubElement(automatic_styles
, 'number:time-style', attrib
={
1192 'style:name': 'rst-time-style-%d' % self
.style_index
,
1193 'xmlns:number': SNSD
['number'],
1194 'xmlns:style': SNSD
['style'],
1196 el3
= SubElement(el2
, 'number:hours', attrib
={
1197 'number:style': 'long',
1199 el3
= SubElement(el2
, 'number:text')
1201 el3
= SubElement(el2
, 'number:minutes', attrib
={
1202 'number:style': 'long',
1205 self
.style_index
+= 1
1206 el1
= SubElement(parent
, 'text:time', attrib
={
1207 'text:style-name': self
.rststyle(style_name
),
1208 'text:fixed': 'true',
1209 'style:data-style-name':
1210 'rst-time-style-%d' % self
.style_index
,
1212 el2
= SubElement(automatic_styles
, 'number:time-style', attrib
={
1213 'style:name': 'rst-time-style-%d' % self
.style_index
,
1214 'xmlns:number': SNSD
['number'],
1215 'xmlns:style': SNSD
['style'],
1217 el3
= SubElement(el2
, 'number:hours', attrib
={
1218 'number:style': 'long',
1220 el3
= SubElement(el2
, 'number:text')
1222 el3
= SubElement(el2
, 'number:minutes', attrib
={
1223 'number:style': 'long',
1225 el3
= SubElement(el2
, 'number:text')
1227 el3
= SubElement(el2
, 'number:seconds', attrib
={
1228 'number:style': 'long',
1231 self
.style_index
+= 1
1232 el1
= SubElement(parent
, 'text:time', attrib
={
1233 'text:style-name': self
.rststyle(style_name
),
1234 'text:fixed': 'true',
1235 'style:data-style-name':
1236 'rst-time-style-%d' % self
.style_index
,
1238 el2
= SubElement(automatic_styles
, 'number:time-style', attrib
={
1239 'style:name': 'rst-time-style-%d' % self
.style_index
,
1240 'xmlns:number': SNSD
['number'],
1241 'xmlns:style': SNSD
['style'],
1243 el3
= SubElement(el2
, 'number:hours', attrib
={
1244 'number:style': 'long',
1246 el3
= SubElement(el2
, 'number:text')
1248 el3
= SubElement(el2
, 'number:minutes', attrib
={
1249 'number:style': 'long',
1251 el3
= SubElement(el2
, 'number:text')
1253 el3
= SubElement(el2
, 'number:am-pm')
1255 self
.style_index
+= 1
1256 el1
= SubElement(parent
, 'text:time', attrib
={
1257 'text:style-name': self
.rststyle(style_name
),
1258 'text:fixed': 'true',
1259 'style:data-style-name':
1260 'rst-time-style-%d' % self
.style_index
,
1262 el2
= SubElement(automatic_styles
, 'number:time-style', attrib
={
1263 'style:name': 'rst-time-style-%d' % self
.style_index
,
1264 'xmlns:number': SNSD
['number'],
1265 'xmlns:style': SNSD
['style'],
1267 el3
= SubElement(el2
, 'number:hours', attrib
={
1268 'number:style': 'long',
1270 el3
= SubElement(el2
, 'number:text')
1272 el3
= SubElement(el2
, 'number:minutes', attrib
={
1273 'number:style': 'long',
1275 el3
= SubElement(el2
, 'number:text')
1277 el3
= SubElement(el2
, 'number:seconds', attrib
={
1278 'number:style': 'long',
1280 el3
= SubElement(el2
, 'number:text')
1282 el3
= SubElement(el2
, 'number:am-pm')
1284 self
.style_index
+= 1
1285 el1
= SubElement(parent
, 'text:date', attrib
={
1286 'text:style-name': self
.rststyle(style_name
),
1287 'style:data-style-name':
1288 'rst-date-style-%d' % self
.style_index
,
1290 el2
= SubElement(automatic_styles
, 'number:date-style', attrib
={
1291 'style:name': 'rst-date-style-%d' % self
.style_index
,
1292 'number:automatic-order': 'true',
1293 'xmlns:number': SNSD
['number'],
1294 'xmlns:style': SNSD
['style'],
1296 el3
= SubElement(el2
, 'number:month', attrib
={
1297 'number:style': 'long',
1299 el3
= SubElement(el2
, 'number:text')
1301 el3
= SubElement(el2
, 'number:day', attrib
={
1302 'number:style': 'long',
1304 el3
= SubElement(el2
, 'number:text')
1306 el3
= SubElement(el2
, 'number:year')
1308 self
.style_index
+= 1
1309 el1
= SubElement(parent
, 'text:date', attrib
={
1310 'text:style-name': self
.rststyle(style_name
),
1311 'style:data-style-name':
1312 'rst-date-style-%d' % self
.style_index
,
1314 el2
= SubElement(automatic_styles
, 'number:date-style', attrib
={
1315 'style:name': 'rst-date-style-%d' % self
.style_index
,
1316 'number:automatic-order': 'true',
1317 'xmlns:number': SNSD
['number'],
1318 'xmlns:style': SNSD
['style'],
1320 el3
= SubElement(el2
, 'number:month', attrib
={
1321 'number:style': 'long',
1323 el3
= SubElement(el2
, 'number:text')
1325 el3
= SubElement(el2
, 'number:day', attrib
={
1326 'number:style': 'long',
1328 el3
= SubElement(el2
, 'number:text')
1330 el3
= SubElement(el2
, 'number:year', attrib
={
1331 'number:style': 'long',
1334 self
.style_index
+= 1
1335 el1
= SubElement(parent
, 'text:date', attrib
={
1336 'text:style-name': self
.rststyle(style_name
),
1337 'style:data-style-name':
1338 'rst-date-style-%d' % self
.style_index
,
1340 el2
= SubElement(automatic_styles
, 'number:date-style', attrib
={
1341 'style:name': 'rst-date-style-%d' % self
.style_index
,
1342 'number:automatic-order': 'true',
1343 'xmlns:number': SNSD
['number'],
1344 'xmlns:style': SNSD
['style'],
1346 el3
= SubElement(el2
, 'number:month', attrib
={
1347 'number:textual': 'true',
1349 el3
= SubElement(el2
, 'number:text')
1351 el3
= SubElement(el2
, 'number:day', attrib
={})
1352 el3
= SubElement(el2
, 'number:text')
1354 el3
= SubElement(el2
, 'number:year', attrib
={
1355 'number:style': 'long',
1358 self
.style_index
+= 1
1359 el1
= SubElement(parent
, 'text:date', attrib
={
1360 'text:style-name': self
.rststyle(style_name
),
1361 'style:data-style-name':
1362 'rst-date-style-%d' % self
.style_index
,
1364 el2
= SubElement(automatic_styles
, 'number:date-style', attrib
={
1365 'style:name': 'rst-date-style-%d' % self
.style_index
,
1366 'number:automatic-order': 'true',
1367 'xmlns:number': SNSD
['number'],
1368 'xmlns:style': SNSD
['style'],
1370 el3
= SubElement(el2
, 'number:month', attrib
={
1371 'number:textual': 'true',
1372 'number:style': 'long',
1374 el3
= SubElement(el2
, 'number:text')
1376 el3
= SubElement(el2
, 'number:day', attrib
={})
1377 el3
= SubElement(el2
, 'number:text')
1379 el3
= SubElement(el2
, 'number:year', attrib
={
1380 'number:style': 'long',
1383 self
.style_index
+= 1
1384 el1
= SubElement(parent
, 'text:date', attrib
={
1385 'text:style-name': self
.rststyle(style_name
),
1386 'style:data-style-name':
1387 'rst-date-style-%d' % self
.style_index
,
1389 el2
= SubElement(automatic_styles
, 'number:date-style', attrib
={
1390 'style:name': 'rst-date-style-%d' % self
.style_index
,
1391 'xmlns:number': SNSD
['number'],
1392 'xmlns:style': SNSD
['style'],
1394 el3
= SubElement(el2
, 'number:year', attrib
={
1395 'number:style': 'long',
1397 el3
= SubElement(el2
, 'number:text')
1399 el3
= SubElement(el2
, 'number:month', attrib
={
1400 'number:style': 'long',
1402 el3
= SubElement(el2
, 'number:text')
1404 el3
= SubElement(el2
, 'number:day', attrib
={
1405 'number:style': 'long',
1408 el1
= SubElement(parent
, 'text:subject', attrib
={
1409 'text:style-name': self
.rststyle(style_name
),
1412 el1
= SubElement(parent
, 'text:title', attrib
={
1413 'text:style-name': self
.rststyle(style_name
),
1416 el1
= SubElement(parent
, 'text:author-name', attrib
={
1417 'text:fixed': 'false',
1423 def split_field_specifiers_iter(self
, text
):
1426 mo
= ODFTranslator
.field_pat
.search(text
, pos1
)
1430 yield ODFTranslator
.code_text
, text
[pos1
:pos2
]
1431 yield ODFTranslator
.code_field
, mo
.group(1)
1435 trailing
= text
[pos1
:]
1437 yield ODFTranslator
.code_text
, trailing
1440 root
= self
.content_tree
.getroot()
1441 et
= etree
.ElementTree(root
)
1444 def content_astext(self
):
1445 return self
.astext()
1447 def set_title(self
, title
) -> None:
1450 def get_title(self
):
1453 def set_embedded_file_list(self
, embedded_file_list
) -> None:
1454 self
.embedded_file_list
= embedded_file_list
1456 def get_embedded_file_list(self
):
1457 return self
.embedded_file_list
1459 def get_meta_dict(self
):
1460 return self
.meta_dict
1462 def process_footnotes(self
) -> None:
1463 for node
, el1
in self
.footnote_list
:
1464 backrefs
= node
.attributes
.get('backrefs', [])
1466 for ref
in backrefs
:
1467 el2
= self
.footnote_ref_dict
.get(ref
)
1471 el3
= copy
.deepcopy(el1
)
1474 if len(el2
) > 0: # and 'id' in el2.attrib:
1477 attribkey
= add_ns('text:id', nsdict
=SNSD
)
1478 id1
= el2
.get(attribkey
, 'footnote-error')
1481 tag
= add_ns('text:note-ref', nsdict
=SNSD
)
1483 if self
.settings
.endnotes_end_doc
:
1484 note_class
= 'endnote'
1486 note_class
= 'footnote'
1488 attribkey
= add_ns('text:note-class', nsdict
=SNSD
)
1489 el2
.attrib
[attribkey
] = note_class
1490 attribkey
= add_ns('text:ref-name', nsdict
=SNSD
)
1491 el2
.attrib
[attribkey
] = id1
1493 'text:reference-format', nsdict
=SNSD
)
1494 el2
.attrib
[attribkey
] = 'page'
1500 def append_child(self
, tag
, attrib
=None, parent
=None):
1502 parent
= self
.current_element
1503 return SubElement(parent
, tag
, attrib
)
1505 def append_p(self
, style
, text
=None):
1506 result
= self
.append_child('text:p', attrib
={
1507 'text:style-name': self
.rststyle(style
)})
1508 self
.append_pending_ids(result
)
1509 if text
is not None:
1513 def append_pending_ids(self
, el
) -> None:
1514 if self
.settings
.create_links
:
1515 for id in self
.pending_ids
:
1516 SubElement(el
, 'text:reference-mark', attrib
={
1518 self
.pending_ids
= []
1520 def set_current_element(self
, el
) -> None:
1521 self
.current_element
= el
1523 def set_to_parent(self
) -> None:
1524 self
.current_element
= self
.current_element
.getparent()
1526 def generate_labeled_block(self
, node
, label
):
1527 label
= '%s:' % (self
.language
.labels
[label
], )
1528 el
= self
.append_p('textbody')
1531 attrib
={'text:style-name': self
.rststyle('strong')})
1533 return self
.append_p('blockindent')
1535 def generate_labeled_line(self
, node
, label
):
1536 label
= '%s:' % (self
.language
.labels
[label
], )
1537 el
= self
.append_p('textbody')
1540 attrib
={'text:style-name': self
.rststyle('strong')})
1542 el1
.tail
= node
.astext()
1545 def encode(self
, text
):
1546 return text
.replace('\n', " ")
1551 # In alphabetic order, more or less.
1552 # See docutils.docutils.nodes.node_class_names.
1555 def dispatch_visit(self
, node
) -> None:
1556 """Override to catch basic attributes which many nodes have."""
1557 self
.handle_basic_atts(node
)
1558 nodes
.GenericNodeVisitor
.dispatch_visit(self
, node
)
1560 def handle_basic_atts(self
, node
) -> None:
1561 if isinstance(node
, nodes
.Element
) and node
['ids']:
1562 self
.pending_ids
+= node
['ids']
1564 def default_visit(self
, node
) -> None:
1565 self
.document
.reporter
.warning(f
'missing visit_{node.tagname}',
1568 def default_departure(self
, node
) -> None:
1569 self
.document
.reporter
.warning(f
'missing depart_{node.tagname}',
1572 def visit_Text(self
, node
) -> None:
1573 # Skip nodes whose text has been processed in parent nodes.
1574 if isinstance(node
.parent
, docutils
.nodes
.literal_block
):
1576 text
= node
.astext()
1577 # Are we in mixed content? If so, add the text to the
1578 # etree tail of the previous sibling element.
1579 if len(self
.current_element
) > 0:
1580 if self
.current_element
[-1].tail
:
1581 self
.current_element
[-1].tail
+= text
1583 self
.current_element
[-1].tail
= text
1585 if self
.current_element
.text
:
1586 self
.current_element
.text
+= text
1588 self
.current_element
.text
= text
1590 def depart_Text(self
, node
) -> None:
1594 # Pre-defined fields
1597 def visit_address(self
, node
) -> None:
1598 el
= self
.generate_labeled_block(node
, 'address')
1599 self
.set_current_element(el
)
1601 def depart_address(self
, node
) -> None:
1602 self
.set_to_parent()
1604 def visit_author(self
, node
) -> None:
1605 if isinstance(node
.parent
, nodes
.authors
):
1606 el
= self
.append_p('blockindent')
1608 el
= self
.generate_labeled_block(node
, 'author')
1609 self
.set_current_element(el
)
1611 def depart_author(self
, node
) -> None:
1612 self
.set_to_parent()
1614 def visit_authors(self
, node
) -> None:
1615 label
= '%s:' % (self
.language
.labels
['authors'], )
1616 el
= self
.append_p('textbody')
1619 attrib
={'text:style-name': self
.rststyle('strong')})
1622 def depart_authors(self
, node
) -> None:
1625 def visit_contact(self
, node
) -> None:
1626 el
= self
.generate_labeled_block(node
, 'contact')
1627 self
.set_current_element(el
)
1629 def depart_contact(self
, node
) -> None:
1630 self
.set_to_parent()
1632 def visit_copyright(self
, node
) -> None:
1633 el
= self
.generate_labeled_block(node
, 'copyright')
1634 self
.set_current_element(el
)
1636 def depart_copyright(self
, node
) -> None:
1637 self
.set_to_parent()
1639 def visit_date(self
, node
) -> None:
1640 self
.generate_labeled_line(node
, 'date')
1642 def depart_date(self
, node
) -> None:
1645 def visit_organization(self
, node
) -> None:
1646 el
= self
.generate_labeled_block(node
, 'organization')
1647 self
.set_current_element(el
)
1649 def depart_organization(self
, node
) -> None:
1650 self
.set_to_parent()
1652 def visit_status(self
, node
) -> None:
1653 el
= self
.generate_labeled_block(node
, 'status')
1654 self
.set_current_element(el
)
1656 def depart_status(self
, node
) -> None:
1657 self
.set_to_parent()
1659 def visit_revision(self
, node
) -> None:
1660 self
.generate_labeled_line(node
, 'revision')
1662 def depart_revision(self
, node
) -> None:
1665 def visit_version(self
, node
) -> None:
1666 self
.generate_labeled_line(node
, 'version')
1667 # self.set_current_element(el)
1669 def depart_version(self
, node
) -> None:
1670 # self.set_to_parent()
1673 def visit_attribution(self
, node
) -> None:
1674 self
.append_p('attribution', node
.astext())
1676 def depart_attribution(self
, node
) -> None:
1679 def visit_block_quote(self
, node
) -> None:
1680 if 'epigraph' in node
.attributes
['classes']:
1681 self
.paragraph_style_stack
.append(self
.rststyle('epigraph'))
1682 self
.blockstyle
= self
.rststyle('epigraph')
1683 elif 'highlights' in node
.attributes
['classes']:
1684 self
.paragraph_style_stack
.append(self
.rststyle('highlights'))
1685 self
.blockstyle
= self
.rststyle('highlights')
1687 self
.paragraph_style_stack
.append(self
.rststyle('blockquote'))
1688 self
.blockstyle
= self
.rststyle('blockquote')
1689 self
.line_indent_level
+= 1
1691 def depart_block_quote(self
, node
) -> None:
1692 self
.paragraph_style_stack
.pop()
1693 self
.blockstyle
= ''
1694 self
.line_indent_level
-= 1
1696 def visit_bullet_list(self
, node
) -> None:
1697 self
.list_level
+= 1
1698 if self
.in_table_of_contents
:
1699 if self
.settings
.generate_oowriter_toc
:
1702 if 'classes' in node
and \
1703 'auto-toc' in node
.attributes
['classes']:
1704 el
= SubElement(self
.current_element
, 'text:list', attrib
={
1705 'text:style-name': self
.rststyle('tocenumlist'),
1707 self
.list_style_stack
.append(self
.rststyle('enumitem'))
1709 el
= SubElement(self
.current_element
, 'text:list', attrib
={
1710 'text:style-name': self
.rststyle('tocbulletlist'),
1712 self
.list_style_stack
.append(self
.rststyle('bulletitem'))
1713 self
.set_current_element(el
)
1715 if self
.blockstyle
== self
.rststyle('blockquote'):
1716 el
= SubElement(self
.current_element
, 'text:list', attrib
={
1717 'text:style-name': self
.rststyle('blockquote-bulletlist'),
1719 self
.list_style_stack
.append(
1720 self
.rststyle('blockquote-bulletitem'))
1721 elif self
.blockstyle
== self
.rststyle('highlights'):
1722 el
= SubElement(self
.current_element
, 'text:list', attrib
={
1723 'text:style-name': self
.rststyle('highlights-bulletlist'),
1725 self
.list_style_stack
.append(
1726 self
.rststyle('highlights-bulletitem'))
1727 elif self
.blockstyle
== self
.rststyle('epigraph'):
1728 el
= SubElement(self
.current_element
, 'text:list', attrib
={
1729 'text:style-name': self
.rststyle('epigraph-bulletlist'),
1731 self
.list_style_stack
.append(
1732 self
.rststyle('epigraph-bulletitem'))
1734 el
= SubElement(self
.current_element
, 'text:list', attrib
={
1735 'text:style-name': self
.rststyle('bulletlist'),
1737 self
.list_style_stack
.append(self
.rststyle('bulletitem'))
1738 self
.set_current_element(el
)
1740 def depart_bullet_list(self
, node
) -> None:
1741 if self
.in_table_of_contents
:
1742 if self
.settings
.generate_oowriter_toc
:
1745 self
.set_to_parent()
1746 self
.list_style_stack
.pop()
1748 self
.set_to_parent()
1749 self
.list_style_stack
.pop()
1750 self
.list_level
-= 1
1752 def visit_caption(self
, node
):
1753 raise nodes
.SkipChildren
1755 def depart_caption(self
, node
) -> None:
1758 def visit_comment(self
, node
) -> None:
1759 el
= self
.append_p('textbody')
1760 el1
= SubElement(el
, 'office:annotation', attrib
={})
1761 el2
= SubElement(el1
, 'dc:creator', attrib
={})
1762 s1
= os
.environ
.get('USER', '')
1764 el2
= SubElement(el1
, 'text:p', attrib
={})
1765 el2
.text
= node
.astext()
1767 def depart_comment(self
, node
) -> None:
1770 def visit_compound(self
, node
) -> None:
1771 # The compound directive currently receives no special treatment.
1774 def depart_compound(self
, node
) -> None:
1777 def visit_container(self
, node
) -> None:
1778 styles
= node
.attributes
.get('classes', ())
1780 self
.paragraph_style_stack
.append(self
.rststyle(styles
[0]))
1782 def depart_container(self
, node
) -> None:
1783 styles
= node
.attributes
.get('classes', ())
1785 self
.paragraph_style_stack
.pop()
1787 def visit_decoration(self
, node
) -> None:
1790 def depart_decoration(self
, node
) -> None:
1793 def visit_definition_list(self
, node
):
1794 self
.def_list_level
+= 1
1795 if self
.list_level
> 5:
1797 'max definition list nesting level exceeded')
1799 def depart_definition_list(self
, node
) -> None:
1800 self
.def_list_level
-= 1
1802 def visit_definition_list_item(self
, node
) -> None:
1805 def depart_definition_list_item(self
, node
) -> None:
1808 def visit_term(self
, node
):
1809 el
= self
.append_p('deflist-term-%d' % self
.def_list_level
)
1810 el
.text
= node
.astext()
1811 self
.set_current_element(el
)
1812 raise nodes
.SkipChildren
1814 def depart_term(self
, node
) -> None:
1815 self
.set_to_parent()
1817 def visit_definition(self
, node
) -> None:
1818 self
.paragraph_style_stack
.append(
1819 self
.rststyle('deflist-def-%d' % self
.def_list_level
))
1820 self
.bumped_list_level_stack
.append(ListLevel(1))
1822 def depart_definition(self
, node
) -> None:
1823 self
.paragraph_style_stack
.pop()
1824 self
.bumped_list_level_stack
.pop()
1826 def visit_classifier(self
, node
) -> None:
1827 if len(self
.current_element
) > 0:
1828 el
= self
.current_element
[-1]
1831 attrib
={'text:style-name': self
.rststyle('emphasis')})
1832 el1
.text
= ' (%s)' % (node
.astext(), )
1834 def depart_classifier(self
, node
) -> None:
1837 def visit_document(self
, node
) -> None:
1840 def depart_document(self
, node
) -> None:
1841 self
.process_footnotes()
1843 def visit_docinfo(self
, node
) -> None:
1844 self
.section_level
+= 1
1845 self
.section_count
+= 1
1846 if self
.settings
.create_sections
:
1847 el
= self
.append_child(
1848 'text:section', attrib
={
1849 'text:name': 'Section%d' % self
.section_count
,
1850 'text:style-name': 'Sect%d' % self
.section_level
,
1853 self
.set_current_element(el
)
1855 def depart_docinfo(self
, node
) -> None:
1856 self
.section_level
-= 1
1857 if self
.settings
.create_sections
:
1858 self
.set_to_parent()
1860 def visit_emphasis(self
, node
) -> None:
1862 self
.current_element
, 'text:span',
1863 attrib
={'text:style-name': self
.rststyle('emphasis')})
1864 self
.set_current_element(el
)
1866 def depart_emphasis(self
, node
) -> None:
1867 self
.set_to_parent()
1869 def visit_enumerated_list(self
, node
) -> None:
1870 el1
= self
.current_element
1871 if self
.blockstyle
== self
.rststyle('blockquote'):
1872 el2
= SubElement(el1
, 'text:list', attrib
={
1873 'text:style-name': self
.rststyle('blockquote-enumlist'),
1875 self
.list_style_stack
.append(self
.rststyle('blockquote-enumitem'))
1876 elif self
.blockstyle
== self
.rststyle('highlights'):
1877 el2
= SubElement(el1
, 'text:list', attrib
={
1878 'text:style-name': self
.rststyle('highlights-enumlist'),
1880 self
.list_style_stack
.append(self
.rststyle('highlights-enumitem'))
1881 elif self
.blockstyle
== self
.rststyle('epigraph'):
1882 el2
= SubElement(el1
, 'text:list', attrib
={
1883 'text:style-name': self
.rststyle('epigraph-enumlist'),
1885 self
.list_style_stack
.append(self
.rststyle('epigraph-enumitem'))
1887 liststylename
= 'enumlist-%s' % (node
.get('enumtype', 'arabic'), )
1888 el2
= SubElement(el1
, 'text:list', attrib
={
1889 'text:style-name': self
.rststyle(liststylename
),
1891 self
.list_style_stack
.append(self
.rststyle('enumitem'))
1892 self
.set_current_element(el2
)
1894 def depart_enumerated_list(self
, node
) -> None:
1895 self
.set_to_parent()
1896 self
.list_style_stack
.pop()
1898 def visit_list_item(self
, node
) -> None:
1899 # If we are in a "bumped" list level, then wrap this
1900 # list in an outer lists in order to increase the
1901 # indentation level.
1902 if self
.in_table_of_contents
:
1903 if self
.settings
.generate_oowriter_toc
:
1904 self
.paragraph_style_stack
.append(
1905 self
.rststyle('contents-%d' % (self
.list_level
, )))
1907 el1
= self
.append_child('text:list-item')
1908 self
.set_current_element(el1
)
1910 el1
= self
.append_child('text:list-item')
1912 if len(self
.bumped_list_level_stack
) > 0:
1913 level_obj
= self
.bumped_list_level_stack
[-1]
1914 if level_obj
.get_sibling():
1915 level_obj
.set_nested(False)
1916 for level_obj1
in self
.bumped_list_level_stack
:
1917 for idx
in range(level_obj1
.get_level()):
1918 el2
= self
.append_child('text:list', parent
=el3
)
1919 el3
= self
.append_child(
1920 'text:list-item', parent
=el2
)
1921 self
.paragraph_style_stack
.append(self
.list_style_stack
[-1])
1922 self
.set_current_element(el3
)
1924 def depart_list_item(self
, node
) -> None:
1925 if self
.in_table_of_contents
:
1926 if self
.settings
.generate_oowriter_toc
:
1927 self
.paragraph_style_stack
.pop()
1929 self
.set_to_parent()
1931 if len(self
.bumped_list_level_stack
) > 0:
1932 level_obj
= self
.bumped_list_level_stack
[-1]
1933 if level_obj
.get_sibling():
1934 level_obj
.set_nested(True)
1935 for level_obj1
in self
.bumped_list_level_stack
:
1936 for idx
in range(level_obj1
.get_level()):
1937 self
.set_to_parent()
1938 self
.set_to_parent()
1939 self
.paragraph_style_stack
.pop()
1940 self
.set_to_parent()
1942 def visit_header(self
, node
) -> None:
1943 self
.in_header
= True
1945 def depart_header(self
, node
) -> None:
1946 self
.in_header
= False
1948 def visit_footer(self
, node
) -> None:
1949 self
.in_footer
= True
1951 def depart_footer(self
, node
) -> None:
1952 self
.in_footer
= False
1954 def visit_field(self
, node
) -> None:
1957 def depart_field(self
, node
) -> None:
1960 def visit_field_list(self
, node
) -> None:
1963 def depart_field_list(self
, node
) -> None:
1966 def visit_field_name(self
, node
) -> None:
1967 el
= self
.append_p('textbody')
1970 attrib
={'text:style-name': self
.rststyle('strong')})
1971 el1
.text
= node
.astext()
1973 def depart_field_name(self
, node
) -> None:
1976 def visit_field_body(self
, node
) -> None:
1977 self
.paragraph_style_stack
.append(self
.rststyle('blockindent'))
1979 def depart_field_body(self
, node
) -> None:
1980 self
.paragraph_style_stack
.pop()
1982 def visit_figure(self
, node
) -> None:
1985 def depart_figure(self
, node
) -> None:
1988 def visit_footnote(self
, node
) -> None:
1989 self
.footnote_level
+= 1
1990 self
.save_footnote_current
= self
.current_element
1991 el1
= Element('text:note-body')
1992 self
.current_element
= el1
1993 self
.footnote_list
.append((node
, el1
))
1994 if isinstance(node
, docutils
.nodes
.citation
):
1995 self
.paragraph_style_stack
.append(self
.rststyle('citation'))
1997 self
.paragraph_style_stack
.append(self
.rststyle('footnote'))
1999 def depart_footnote(self
, node
) -> None:
2000 self
.paragraph_style_stack
.pop()
2001 self
.current_element
= self
.save_footnote_current
2002 self
.footnote_level
-= 1
2011 def visit_footnote_reference(self
, node
):
2012 if self
.footnote_level
<= 0:
2013 id = node
.attributes
['ids'][0]
2014 refid
= node
.attributes
.get('refid')
2017 if self
.settings
.endnotes_end_doc
:
2018 note_class
= 'endnote'
2020 note_class
= 'footnote'
2021 el1
= self
.append_child('text:note', attrib
={
2022 'text:id': '%s' % (refid
, ),
2023 'text:note-class': note_class
,
2025 note_auto
= str(node
.attributes
.get('auto', 1))
2026 if isinstance(node
, docutils
.nodes
.citation_reference
):
2027 citation
= '[%s]' % node
.astext()
2028 el2
= SubElement(el1
, 'text:note-citation', attrib
={
2029 'text:label': citation
,
2032 elif note_auto
== '1':
2033 el2
= SubElement(el1
, 'text:note-citation', attrib
={
2034 'text:label': node
.astext(),
2036 el2
.text
= node
.astext()
2037 elif note_auto
== '*':
2038 if self
.footnote_chars_idx
>= len(
2039 ODFTranslator
.footnote_chars
):
2040 self
.footnote_chars_idx
= 0
2041 footnote_char
= ODFTranslator
.footnote_chars
[
2042 self
.footnote_chars_idx
]
2043 self
.footnote_chars_idx
+= 1
2044 el2
= SubElement(el1
, 'text:note-citation', attrib
={
2045 'text:label': footnote_char
,
2047 el2
.text
= footnote_char
2048 self
.footnote_ref_dict
[id] = el1
2049 raise nodes
.SkipChildren
2051 def depart_footnote_reference(self
, node
) -> None:
2054 def visit_citation(self
, node
) -> None:
2055 self
.in_citation
= True
2056 for id in node
.attributes
['ids']:
2057 self
.citation_id
= id
2059 self
.paragraph_style_stack
.append(self
.rststyle('blockindent'))
2060 self
.bumped_list_level_stack
.append(ListLevel(1))
2062 def depart_citation(self
, node
) -> None:
2063 self
.citation_id
= None
2064 self
.paragraph_style_stack
.pop()
2065 self
.bumped_list_level_stack
.pop()
2066 self
.in_citation
= False
2068 def visit_citation_reference(self
, node
) -> None:
2069 if self
.settings
.create_links
:
2070 id = node
.attributes
['refid']
2071 el
= self
.append_child('text:reference-ref', attrib
={
2072 'text:ref-name': '%s' % (id, ),
2073 'text:reference-format': 'text',
2076 self
.set_current_element(el
)
2077 elif self
.current_element
.text
is None:
2078 self
.current_element
.text
= '['
2080 self
.current_element
.text
+= '['
2082 def depart_citation_reference(self
, node
) -> None:
2083 self
.current_element
.text
+= ']'
2084 if self
.settings
.create_links
:
2085 self
.set_to_parent()
2087 def visit_label(self
, node
):
2088 if isinstance(node
.parent
, docutils
.nodes
.footnote
):
2089 raise nodes
.SkipChildren
2090 elif self
.citation_id
is not None:
2091 el
= self
.append_p('textbody')
2092 self
.set_current_element(el
)
2093 if self
.settings
.create_links
:
2094 el0
= SubElement(el
, 'text:span')
2096 self
.append_child('text:reference-mark-start', attrib
={
2097 'text:name': '%s' % (self
.citation_id
, ),
2102 def depart_label(self
, node
) -> None:
2103 if isinstance(node
.parent
, docutils
.nodes
.footnote
):
2105 elif self
.citation_id
is not None:
2106 if self
.settings
.create_links
:
2107 self
.append_child('text:reference-mark-end', attrib
={
2108 'text:name': '%s' % (self
.citation_id
, ),
2110 el0
= SubElement(self
.current_element
, 'text:span')
2113 self
.current_element
.text
+= ']'
2114 self
.set_to_parent()
2116 def visit_generated(self
, node
) -> None:
2119 def depart_generated(self
, node
) -> None:
2122 def check_file_exists(self
, path
) -> int:
2123 if os
.path
.exists(path
):
2128 def visit_image(self
, node
) -> None:
2129 # Capture the image file.
2130 source
= node
['uri']
2131 uri_parts
= urllib
.parse
.urlparse(source
)
2132 if uri_parts
.scheme
in ('', 'file'):
2133 source
= urllib
.parse
.unquote(uri_parts
.path
)
2134 if self
.settings
.root_prefix
and source
.startswith('/'):
2135 root_prefix
= Path(self
.settings
.root_prefix
)
2136 source
= (root_prefix
/source
[1:]).as_posix()
2138 # adapt relative paths
2139 docsource
, line
= utils
.get_source_line(node
)
2141 dirname
= os
.path
.dirname(docsource
)
2143 source
= os
.path
.join(dirname
, source
)
2144 if not self
.check_file_exists(source
):
2145 self
.document
.reporter
.warning(
2146 f
'Cannot find image file "{source}".', base_node
=node
)
2148 if source
in self
.image_dict
:
2149 filename
, destination
= self
.image_dict
[source
]
2151 self
.image_count
+= 1
2152 filename
= os
.path
.split(source
)[1]
2153 destination
= 'Pictures/1%08x%s' % (self
.image_count
, filename
)
2154 if uri_parts
.scheme
in ('', 'file'):
2155 spec
= (os
.path
.abspath(source
), destination
,)
2158 with urllib
.request
.urlopen(source
) as imgfile
:
2159 content
= imgfile
.read()
2160 except urllib
.error
.URLError
as err
:
2161 self
.document
.reporter
.warning(
2162 f
'Cannot open image URL "{source}". {err}',
2165 with tempfile
.NamedTemporaryFile('wb',
2166 delete
=False) as imgfile2
:
2167 imgfile2
.write(content
)
2168 source
= imgfile2
.name
2169 spec
= (source
, destination
,)
2170 self
.embedded_file_list
.append(spec
)
2171 self
.image_dict
[source
] = (source
, destination
,)
2172 # Is this a figure (containing an image) or just a plain image?
2173 if self
.in_paragraph
:
2174 el1
= self
.current_element
2177 self
.current_element
, 'text:p',
2178 attrib
={'text:style-name': self
.rststyle('textbody')})
2180 if isinstance(node
.parent
, docutils
.nodes
.figure
):
2181 el3
, el4
, el5
, caption
= self
.generate_figure(
2185 el6
, width
= self
.generate_image(
2186 node
, source
, destination
,
2188 if caption
is not None:
2190 else: # if isinstance(node.parent, docutils.nodes.image):
2191 self
.generate_image(node
, source
, destination
, el2
)
2193 def depart_image(self
, node
) -> None:
2196 def get_image_width_height(self
, node
, attr
):
2199 if attr
in node
.attributes
:
2200 size
= node
.attributes
[attr
]
2202 # For conversion factors, see:
2203 # http://www.unitconversion.org/unit_converter/typography-ex.html
2205 if size
.endswith('%'):
2206 if attr
== 'height':
2207 # Percentage allowed for width but not height.
2208 raise ValueError('percentage not allowed for height')
2209 size
= size
.rstrip(' %')
2210 size
= float(size
) / 100.0
2213 size
, unit
= self
.convert_to_cm(size
)
2214 except ValueError as exp
:
2215 self
.document
.reporter
.warning(
2216 f
'Invalid {attr} for image: "{node.attributes[attr]}". '
2217 f
'Error: "{exp}".', base_node
=node
)
2218 size
, unit
= 0.5, 'cm' # fallback to avoid consequential error
2221 def convert_to_cm(self
, size
):
2222 """Convert various units to centimeters.
2224 Note that a call to this method should be wrapped in:
2225 try: except ValueError:
2228 if size
.endswith('px'):
2229 size
= float(size
[:-2]) * 0.026 # convert px to cm
2230 elif size
.endswith('in'):
2231 size
= float(size
[:-2]) * 2.54 # convert in to cm
2232 elif size
.endswith('pt'):
2233 size
= float(size
[:-2]) * 0.035 # convert pt to cm
2234 elif size
.endswith('pc'):
2235 size
= float(size
[:-2]) * 0.423 # convert pc to cm
2236 elif size
.endswith('mm'):
2237 size
= float(size
[:-2]) * 0.1 # convert mm to cm
2238 elif size
.endswith('Q'):
2239 size
= float(size
[:-1]) * 0.25 # convert Q to cm
2240 elif size
.endswith('cm'):
2241 size
= float(size
[:-2])
2242 elif size
[-1:] in '0123456789.': # no unit, use px
2243 size
= float(size
) * 0.026 # convert px to cm
2245 raise ValueError('unit not supported with ODT')
2249 def get_image_scale(self
, node
):
2250 if 'scale' in node
.attributes
:
2251 scale
= node
.attributes
['scale']
2253 self
.document
.reporter
.warning(
2254 f
'scale out of range ({scale}), using 1.', base_node
=node
)
2256 scale
= scale
* 0.01
2261 def get_image_scaled_width_height(self
, node
, source
):
2262 """Return the image size in centimeters adjusted by image attrs."""
2263 scale
= self
.get_image_scale(node
)
2264 width
, width_unit
= self
.get_image_width_height(node
, 'width')
2265 height
, _
= self
.get_image_width_height(node
, 'height')
2266 dpi
= (96, 96) # image resolution in pixel per inch
2267 if width
is None or height
is None:
2269 self
.document
.reporter
.warning(
2270 'image size not fully specified and PIL not installed',
2272 return '0.5cm', '0.5cm' # prevent consequential error
2273 filename
, destination
= self
.image_dict
[source
]
2274 with PIL
.Image
.open(filename
, 'r') as img
:
2276 dpi
= img
.info
.get('dpi', dpi
)
2277 # dpi information can be (xdpi, ydpi) or xydpi
2282 # TODO: use dpi when converting px to cm
2284 width
= img_size
[0] * 0.026 # convert px to cm
2285 if height
is None and width_unit
!= '%':
2286 height
= img_size
[1] * 0.026 # convert px to cm
2287 if width_unit
== '%':
2289 line_width
= self
.get_page_width()
2290 width
= factor
* line_width
2292 # scale proportionally
2293 image_width
= img_size
[0] * 0.026 # convert px to cm
2294 image_height
= img_size
[1] * 0.026 # convert px to cm
2295 factor
= (factor
* line_width
) / image_width
2296 height
= factor
* image_height
2299 width
= '%.2fcm' % width
2300 height
= '%.2fcm' % height
2301 return width
, height
2303 def get_page_width(self
):
2304 """Return the document's page width in centimeters."""
2305 root
= self
.get_dom_stylesheet()
2306 nodes
= root
.iterfind(
2307 './/{urn:oasis:names:tc:opendocument:xmlns:style:1.0}'
2309 '{urn:oasis:names:tc:opendocument:xmlns:style:1.0}'
2310 'page-layout-properties')
2313 page_width
= node
.get(
2314 '{urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0}'
2316 margin_left
= node
.get(
2317 '{urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0}'
2319 margin_right
= node
.get(
2320 '{urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0}'
2322 if (page_width
is None
2323 or margin_left
is None
2324 or margin_right
is None):
2327 page_width
, _
= self
.convert_to_cm(page_width
)
2328 margin_left
, _
= self
.convert_to_cm(margin_left
)
2329 margin_right
, _
= self
.convert_to_cm(margin_right
)
2331 self
.document
.reporter
.warning(
2332 'Stylesheet file contains invalid page width '
2334 width
= page_width
- margin_left
- margin_right
2336 # We can't find the width in styles, so we make a guess.
2337 # Use a width of 6 in = 15.24 cm.
2341 def generate_figure(self
, node
, source
, destination
, current_element
):
2343 width
, height
= self
.get_image_scaled_width_height(node
, source
)
2344 for node1
in node
.parent
.children
:
2345 if node1
.tagname
== 'caption':
2346 caption
= node1
.astext()
2347 self
.image_style_count
+= 1
2349 # Add the style for the caption.
2350 if caption
is not None:
2352 'style:class': 'extra',
2353 'style:family': 'paragraph',
2354 'style:name': 'Caption',
2355 'style:parent-style-name': 'Standard',
2357 el1
= SubElement(self
.automatic_styles
, 'style:style',
2358 attrib
=attrib
, nsdict
=SNSD
)
2360 'fo:margin-bottom': '0.0835in',
2361 'fo:margin-top': '0.0835in',
2362 'text:line-number': '0',
2363 'text:number-lines': 'false',
2365 SubElement(el1
, 'style:paragraph-properties',
2366 attrib
=attrib
, nsdict
=SNSD
)
2368 'fo:font-size': '12pt',
2369 'fo:font-style': 'italic',
2370 'style:font-name': 'Times',
2371 'style:font-name-complex': 'Lucidasans1',
2372 'style:font-size-asian': '12pt',
2373 'style:font-size-complex': '12pt',
2374 'style:font-style-asian': 'italic',
2375 'style:font-style-complex': 'italic',
2377 SubElement(el1
, 'style:text-properties',
2378 attrib
=attrib
, nsdict
=SNSD
)
2379 style_name
= 'rstframestyle%d' % self
.image_style_count
2380 draw_name
= 'graphics%d' % next(IMAGE_NAME_COUNTER
)
2383 'style:name': style_name
,
2384 'style:family': 'graphic',
2385 'style:parent-style-name': self
.rststyle('figureframe'),
2387 el1
= SubElement(self
.automatic_styles
,
2388 'style:style', attrib
=attrib
, nsdict
=SNSD
)
2391 classes
= node
.parent
.attributes
.get('classes')
2392 if classes
and 'wrap' in classes
:
2395 attrib
['style:wrap'] = 'dynamic'
2397 attrib
['style:wrap'] = 'none'
2398 SubElement(el1
, 'style:graphic-properties',
2399 attrib
=attrib
, nsdict
=SNSD
)
2401 'draw:style-name': style_name
,
2402 'draw:name': draw_name
,
2403 'text:anchor-type': 'paragraph',
2404 'draw:z-index': '0',
2406 attrib
['svg:width'] = width
2407 el3
= SubElement(current_element
, 'draw:frame', attrib
=attrib
)
2409 el4
= SubElement(el3
, 'draw:text-box', attrib
=attrib
)
2411 'text:style-name': self
.rststyle('caption'),
2413 el5
= SubElement(el4
, 'text:p', attrib
=attrib
)
2414 return el3
, el4
, el5
, caption
2416 def generate_image(self
, node
, source
, destination
, current_element
,
2418 width
, height
= self
.get_image_scaled_width_height(node
, source
)
2419 self
.image_style_count
+= 1
2420 style_name
= 'rstframestyle%d' % self
.image_style_count
2423 'style:name': style_name
,
2424 'style:family': 'graphic',
2425 'style:parent-style-name': self
.rststyle('image'),
2427 el1
= SubElement(self
.automatic_styles
,
2428 'style:style', attrib
=attrib
, nsdict
=SNSD
)
2431 if 'align' in node
.attributes
:
2432 align
= node
.attributes
['align'].split()
2434 if val
in ('left', 'center', 'right'):
2436 elif val
in ('top', 'middle', 'bottom'):
2438 if frame_attrs
is None:
2440 'style:vertical-pos': 'top',
2441 'style:vertical-rel': 'paragraph',
2442 'style:horizontal-rel': 'paragraph',
2443 'style:mirror': 'none',
2444 'fo:clip': 'rect(0cm 0cm 0cm 0cm)',
2445 'draw:luminance': '0%',
2446 'draw:contrast': '0%',
2450 'draw:gamma': '100%',
2451 'draw:color-inversion': 'false',
2452 'draw:image-opacity': '100%',
2453 'draw:color-mode': 'standard',
2456 attrib
= frame_attrs
2457 if halign
is not None:
2458 attrib
['style:horizontal-pos'] = halign
2459 if valign
is not None:
2460 attrib
['style:vertical-pos'] = valign
2461 # If there is a classes/wrap directive or we are
2462 # inside a table, add a no-wrap style.
2464 classes
= node
.attributes
.get('classes')
2465 if classes
and 'wrap' in classes
:
2468 attrib
['style:wrap'] = 'dynamic'
2470 attrib
['style:wrap'] = 'none'
2471 # If we are inside a table, add a no-wrap style.
2472 if self
.is_in_table(node
):
2473 attrib
['style:wrap'] = 'none'
2474 SubElement(el1
, 'style:graphic-properties',
2475 attrib
=attrib
, nsdict
=SNSD
)
2476 draw_name
= 'graphics%d' % next(IMAGE_NAME_COUNTER
)
2478 # el = SubElement(current_element, 'text:p',
2479 # attrib={'text:style-name': self.rststyle('textbody')})
2481 'draw:style-name': style_name
,
2482 'draw:name': draw_name
,
2483 'draw:z-index': '1',
2485 if isinstance(node
.parent
, nodes
.TextElement
):
2486 attrib
['text:anchor-type'] = 'as-char' # vds
2488 attrib
['text:anchor-type'] = 'paragraph'
2489 attrib
['svg:width'] = width
2490 attrib
['svg:height'] = height
2491 el1
= SubElement(current_element
, 'draw:frame', attrib
=attrib
)
2492 SubElement(el1
, 'draw:image', attrib
={
2493 'xlink:href': '%s' % (destination
, ),
2494 'xlink:type': 'simple',
2495 'xlink:show': 'embed',
2496 'xlink:actuate': 'onLoad',
2500 def is_in_table(self
, node
) -> bool:
2503 if isinstance(node1
, docutils
.nodes
.entry
):
2505 node1
= node1
.parent
2508 def visit_legend(self
, node
) -> None:
2509 if isinstance(node
.parent
, docutils
.nodes
.figure
):
2510 el1
= self
.current_element
[-1]
2512 self
.current_element
= el1
2513 self
.paragraph_style_stack
.append(self
.rststyle('legend'))
2515 def depart_legend(self
, node
) -> None:
2516 if isinstance(node
.parent
, docutils
.nodes
.figure
):
2517 self
.paragraph_style_stack
.pop()
2518 self
.set_to_parent()
2519 self
.set_to_parent()
2520 self
.set_to_parent()
2522 def visit_line_block(self
, node
) -> None:
2523 self
.line_indent_level
+= 1
2524 self
.line_block_level
+= 1
2526 def depart_line_block(self
, node
) -> None:
2527 self
.line_indent_level
-= 1
2528 self
.line_block_level
-= 1
2530 def visit_line(self
, node
) -> None:
2531 style
= 'lineblock%d' % self
.line_indent_level
2532 el1
= SubElement(self
.current_element
, 'text:p',
2533 attrib
={'text:style-name': self
.rststyle(style
), })
2534 self
.current_element
= el1
2536 def depart_line(self
, node
) -> None:
2537 self
.set_to_parent()
2539 def visit_literal(self
, node
) -> None:
2541 self
.current_element
, 'text:span',
2542 attrib
={'text:style-name': self
.rststyle('inlineliteral')})
2543 self
.set_current_element(el
)
2545 def depart_literal(self
, node
) -> None:
2546 self
.set_to_parent()
2548 def visit_inline(self
, node
) -> None:
2549 styles
= node
.attributes
.get('classes', ())
2551 el
= self
.current_element
2552 for inline_style
in styles
:
2553 el
= SubElement(el
, 'text:span',
2554 attrib
={'text:style-name':
2555 self
.rststyle(inline_style
)})
2558 # No style was specified so use a default style (old code
2559 # crashed if no style was given)
2560 el
= SubElement(self
.current_element
, 'text:span')
2563 self
.set_current_element(el
)
2564 self
.inline_style_count_stack
.append(count
)
2566 def depart_inline(self
, node
) -> None:
2567 count
= self
.inline_style_count_stack
.pop()
2568 for x
in range(count
):
2569 self
.set_to_parent()
2571 def _calculate_code_block_padding(self
, line
):
2573 matchobj
= SPACES_PATTERN
.match(line
)
2575 pad
= matchobj
.group()
2578 matchobj
= TABS_PATTERN
.match(line
)
2580 pad
= matchobj
.group()
2581 count
= len(pad
) * 8
2584 def _add_syntax_highlighting(self
, insource
, language
):
2585 lexer
= pygments
.lexers
.get_lexer_by_name(language
, stripall
=True)
2586 if language
in ('latex', 'tex'):
2587 fmtr
= OdtPygmentsLaTeXFormatter(
2588 lambda name
, parameters
=():
2589 self
.rststyle(name
, parameters
),
2590 escape_function
=escape_cdata
)
2592 fmtr
= OdtPygmentsProgFormatter(
2593 lambda name
, parameters
=():
2594 self
.rststyle(name
, parameters
),
2595 escape_function
=escape_cdata
)
2596 return pygments
.highlight(insource
, lexer
, fmtr
)
2598 def fill_line(self
, line
):
2599 line
= FILL_PAT1
.sub(self
.fill_func1
, line
)
2600 return FILL_PAT2
.sub(self
.fill_func2
, line
)
2602 def fill_func1(self
, matchobj
) -> str:
2603 spaces
= matchobj
.group(0)
2604 return '<text:s text:c="%d"/>' % (len(spaces
), )
2606 def fill_func2(self
, matchobj
) -> str:
2607 spaces
= matchobj
.group(0)
2608 return ' <text:s text:c="%d"/>' % (len(spaces
) - 1, )
2610 def visit_literal_block(self
, node
) -> None:
2611 if len(self
.paragraph_style_stack
) > 1:
2612 wrapper1
= '<text:p text:style-name="%s">%%s</text:p>' % (
2613 self
.rststyle('codeblock-indented'), )
2615 wrapper1
= '<text:p text:style-name="%s">%%s</text:p>' % (
2616 self
.rststyle('codeblock'), )
2617 source
= node
.astext()
2618 if (pygments
and self
.settings
.add_syntax_highlighting
):
2619 language
= node
.get('language', 'python')
2620 source
= self
._add
_syntax
_highlighting
(source
, language
)
2622 source
= escape_cdata(source
)
2623 lines
= source
.split('\n')
2624 # If there is an empty last line, remove it.
2627 lines1
= ['<wrappertag1 xmlns:text="urn:oasis:names:tc:'
2628 'opendocument:xmlns:text:1.0">']
2630 for my_line
in lines
:
2631 my_line
= self
.fill_line(my_line
)
2632 my_line
= my_line
.replace(" ", "\n")
2633 my_lines
.append(my_line
)
2634 my_lines_str
= '<text:line-break/>'.join(my_lines
)
2635 my_lines_str2
= wrapper1
% (my_lines_str
, )
2636 lines1
.extend((my_lines_str2
, '</wrappertag1>'))
2637 s1
= ''.join(lines1
)
2638 s1
= s1
.encode("utf-8")
2639 el1
= etree
.fromstring(s1
)
2641 self
.current_element
.append(child
)
2643 def depart_literal_block(self
, node
) -> None:
2646 visit_doctest_block
= visit_literal_block
2647 depart_doctest_block
= depart_literal_block
2649 # placeholder for math (see docs/dev/todo.rst)
2650 def visit_math(self
, node
) -> None:
2651 self
.document
.reporter
.warning('"math" role not supported',
2653 self
.visit_literal(node
)
2655 def depart_math(self
, node
) -> None:
2656 self
.depart_literal(node
)
2658 def visit_math_block(self
, node
) -> None:
2659 self
.document
.reporter
.warning('"math" directive not supported',
2661 self
.visit_literal_block(node
)
2663 def depart_math_block(self
, node
) -> None:
2664 self
.depart_literal_block(node
)
2666 def visit_meta(self
, node
) -> None:
2667 name
= node
.attributes
.get('name')
2668 content
= node
.attributes
.get('content')
2669 if name
is not None and content
is not None:
2670 self
.meta_dict
[name
] = content
2672 def depart_meta(self
, node
) -> None:
2675 def visit_option_list(self
, node
) -> None:
2676 table_name
= 'tableoption'
2678 # Generate automatic styles
2679 if not self
.optiontablestyles_generated
:
2680 self
.optiontablestyles_generated
= True
2681 el
= SubElement(self
.automatic_styles
, 'style:style', attrib
={
2682 'style:name': self
.rststyle(table_name
),
2683 'style:family': 'table'}, nsdict
=SNSD
)
2684 el1
= SubElement(el
, 'style:table-properties', attrib
={
2685 'style:width': '17.59cm',
2686 'table:align': 'left',
2687 'style:shadow': 'none'}, nsdict
=SNSD
)
2688 el
= SubElement(self
.automatic_styles
, 'style:style', attrib
={
2689 'style:name': self
.rststyle('%s.%%c' % table_name
, ('A', )),
2690 'style:family': 'table-column'}, nsdict
=SNSD
)
2691 el1
= SubElement(el
, 'style:table-column-properties', attrib
={
2692 'style:column-width': '4.999cm'}, nsdict
=SNSD
)
2693 el
= SubElement(self
.automatic_styles
, 'style:style', attrib
={
2694 'style:name': self
.rststyle('%s.%%c' % table_name
, ('B', )),
2695 'style:family': 'table-column'}, nsdict
=SNSD
)
2696 el1
= SubElement(el
, 'style:table-column-properties', attrib
={
2697 'style:column-width': '12.587cm'}, nsdict
=SNSD
)
2698 el
= SubElement(self
.automatic_styles
, 'style:style', attrib
={
2699 'style:name': self
.rststyle(
2700 '%s.%%c%%d' % table_name
, ('A', 1, )),
2701 'style:family': 'table-cell'}, nsdict
=SNSD
)
2702 el1
= SubElement(el
, 'style:table-cell-properties', attrib
={
2703 'fo:background-color': 'transparent',
2704 'fo:padding': '0.097cm',
2705 'fo:border-left': '0.035cm solid #000000',
2706 'fo:border-right': 'none',
2707 'fo:border-top': '0.035cm solid #000000',
2708 'fo:border-bottom': '0.035cm solid #000000'}, nsdict
=SNSD
)
2709 el2
= SubElement(el1
, 'style:background-image', nsdict
=SNSD
)
2710 el
= SubElement(self
.automatic_styles
, 'style:style', attrib
={
2711 'style:name': self
.rststyle(
2712 '%s.%%c%%d' % table_name
, ('B', 1, )),
2713 'style:family': 'table-cell'}, nsdict
=SNSD
)
2714 el1
= SubElement(el
, 'style:table-cell-properties', attrib
={
2715 'fo:padding': '0.097cm',
2716 'fo:border': '0.035cm solid #000000'}, nsdict
=SNSD
)
2717 el
= SubElement(self
.automatic_styles
, 'style:style', attrib
={
2718 'style:name': self
.rststyle(
2719 '%s.%%c%%d' % table_name
, ('A', 2, )),
2720 'style:family': 'table-cell'}, nsdict
=SNSD
)
2721 el1
= SubElement(el
, 'style:table-cell-properties', attrib
={
2722 'fo:padding': '0.097cm',
2723 'fo:border-left': '0.035cm solid #000000',
2724 'fo:border-right': 'none',
2725 'fo:border-top': 'none',
2726 'fo:border-bottom': '0.035cm solid #000000'}, nsdict
=SNSD
)
2727 el
= SubElement(self
.automatic_styles
, 'style:style', attrib
={
2728 'style:name': self
.rststyle(
2729 '%s.%%c%%d' % table_name
, ('B', 2, )),
2730 'style:family': 'table-cell'}, nsdict
=SNSD
)
2731 el1
= SubElement(el
, 'style:table-cell-properties', attrib
={
2732 'fo:padding': '0.097cm',
2733 'fo:border-left': '0.035cm solid #000000',
2734 'fo:border-right': '0.035cm solid #000000',
2735 'fo:border-top': 'none',
2736 'fo:border-bottom': '0.035cm solid #000000'}, nsdict
=SNSD
)
2738 # Generate table data
2739 el
= self
.append_child('table:table', attrib
={
2740 'table:name': self
.rststyle(table_name
),
2741 'table:style-name': self
.rststyle(table_name
),
2743 el1
= SubElement(el
, 'table:table-column', attrib
={
2744 'table:style-name': self
.rststyle(
2745 '%s.%%c' % table_name
, ('A', ))})
2746 el1
= SubElement(el
, 'table:table-column', attrib
={
2747 'table:style-name': self
.rststyle(
2748 '%s.%%c' % table_name
, ('B', ))})
2749 el1
= SubElement(el
, 'table:table-header-rows')
2750 el2
= SubElement(el1
, 'table:table-row')
2751 el3
= SubElement(el2
, 'table:table-cell', attrib
={
2752 'table:style-name': self
.rststyle(
2753 '%s.%%c%%d' % table_name
, ('A', 1, )),
2754 'office:value-type': 'string'})
2755 el4
= SubElement(el3
, 'text:p', attrib
={
2756 'text:style-name': 'Table_20_Heading'})
2758 el3
= SubElement(el2
, 'table:table-cell', attrib
={
2759 'table:style-name': self
.rststyle(
2760 '%s.%%c%%d' % table_name
, ('B', 1, )),
2761 'office:value-type': 'string'})
2762 el4
= SubElement(el3
, 'text:p', attrib
={
2763 'text:style-name': 'Table_20_Heading'})
2764 el4
.text
= 'Description'
2765 self
.set_current_element(el
)
2767 def depart_option_list(self
, node
) -> None:
2768 self
.set_to_parent()
2770 def visit_option_list_item(self
, node
) -> None:
2771 el
= self
.append_child('table:table-row')
2772 self
.set_current_element(el
)
2774 def depart_option_list_item(self
, node
) -> None:
2775 self
.set_to_parent()
2777 def visit_option_group(self
, node
) -> None:
2778 el
= self
.append_child('table:table-cell', attrib
={
2779 'table:style-name': 'Table%d.A2' % self
.table_count
,
2780 'office:value-type': 'string',
2782 self
.set_current_element(el
)
2784 def depart_option_group(self
, node
) -> None:
2785 self
.set_to_parent()
2787 def visit_option(self
, node
) -> None:
2788 el
= self
.append_child('text:p', attrib
={
2789 'text:style-name': 'Table_20_Contents'})
2790 el
.text
= node
.astext()
2792 def depart_option(self
, node
) -> None:
2795 def visit_option_string(self
, node
) -> None:
2798 def depart_option_string(self
, node
) -> None:
2801 def visit_option_argument(self
, node
) -> None:
2804 def depart_option_argument(self
, node
) -> None:
2807 def visit_description(self
, node
):
2808 el
= self
.append_child('table:table-cell', attrib
={
2809 'table:style-name': 'Table%d.B2' % self
.table_count
,
2810 'office:value-type': 'string',
2812 el1
= SubElement(el
, 'text:p', attrib
={
2813 'text:style-name': 'Table_20_Contents'})
2814 el1
.text
= node
.astext()
2815 raise nodes
.SkipChildren
2817 def depart_description(self
, node
) -> None:
2820 def visit_paragraph(self
, node
) -> None:
2821 self
.in_paragraph
= True
2823 el
= self
.append_p('header')
2824 elif self
.in_footer
:
2825 el
= self
.append_p('footer')
2827 style_name
= self
.paragraph_style_stack
[-1]
2828 el
= self
.append_child(
2830 attrib
={'text:style-name': style_name
})
2831 self
.append_pending_ids(el
)
2832 self
.set_current_element(el
)
2834 def depart_paragraph(self
, node
) -> None:
2835 self
.in_paragraph
= False
2836 self
.set_to_parent()
2838 self
.header_content
.append(self
.current_element
[-1])
2839 self
.current_element
.remove(self
.current_element
[-1])
2840 elif self
.in_footer
:
2841 self
.footer_content
.append(self
.current_element
[-1])
2842 self
.current_element
.remove(self
.current_element
[-1])
2844 def visit_problematic(self
, node
) -> None:
2847 def depart_problematic(self
, node
) -> None:
2850 def visit_raw(self
, node
):
2851 if 'format' in node
.attributes
:
2852 formats
= node
.attributes
['format']
2853 formatlist
= formats
.split()
2854 if 'odt' in formatlist
:
2855 rawstr
= node
.astext()
2857 '%s="%s"' % (k
, v
, )
2858 for k
, v
in list(CONTENT_NAMESPACE_ATTRIB
.items()))
2859 contentstr
= '<stuff %s>%s</stuff>' % (attrstr
, rawstr
, )
2860 contentstr
= contentstr
.encode("utf-8")
2861 content
= etree
.fromstring(contentstr
)
2862 if len(content
) > 0:
2866 elif self
.in_footer
:
2869 self
.current_element
.append(el1
)
2870 raise nodes
.SkipChildren
2872 def depart_raw(self
, node
) -> None:
2875 elif self
.in_footer
:
2880 def visit_reference(self
, node
) -> None:
2881 # text = node.astext()
2882 if self
.settings
.create_links
:
2883 if 'refuri' in node
:
2884 href
= node
['refuri']
2885 if (self
.settings
.cloak_email_addresses
2886 and href
.startswith('mailto:')):
2887 href
= self
.cloak_mailto(href
)
2888 el
= self
.append_child('text:a', attrib
={
2889 'xlink:href': '%s' % href
,
2890 'xlink:type': 'simple',
2892 self
.set_current_element(el
)
2893 elif 'refid' in node
:
2894 if self
.settings
.create_links
:
2895 href
= node
['refid']
2896 el
= self
.append_child('text:reference-ref', attrib
={
2897 'text:ref-name': '%s' % href
,
2898 'text:reference-format': 'text',
2901 self
.document
.reporter
.warning(
2902 'References must have "refuri" or "refid" attribute.',
2904 if (self
.in_table_of_contents
2905 and len(node
.children
) >= 1
2906 and isinstance(node
.children
[0], docutils
.nodes
.generated
)):
2907 node
.remove(node
.children
[0])
2909 def depart_reference(self
, node
) -> None:
2910 if self
.settings
.create_links
:
2911 if 'refuri' in node
:
2912 self
.set_to_parent()
2914 def visit_rubric(self
, node
) -> None:
2915 style_name
= self
.rststyle('rubric')
2916 classes
= node
.get('classes')
2921 el
= SubElement(self
.current_element
, 'text:h', attrib
={
2922 # 'text:outline-level': '%d' % section_level,
2923 # 'text:style-name': 'Heading_20_%d' % section_level,
2924 'text:style-name': style_name
,
2926 text
= node
.astext()
2927 el
.text
= self
.encode(text
)
2929 def depart_rubric(self
, node
) -> None:
2932 def visit_section(self
, node
, move_ids
=1) -> None:
2933 self
.section_level
+= 1
2934 self
.section_count
+= 1
2935 if self
.settings
.create_sections
:
2936 el
= self
.append_child('text:section', attrib
={
2937 'text:name': 'Section%d' % self
.section_count
,
2938 'text:style-name': 'Sect%d' % self
.section_level
,
2940 self
.set_current_element(el
)
2942 def depart_section(self
, node
) -> None:
2943 self
.section_level
-= 1
2944 if self
.settings
.create_sections
:
2945 self
.set_to_parent()
2947 def visit_strong(self
, node
) -> None:
2948 el
= SubElement(self
.current_element
, 'text:span',
2949 attrib
={'text:style-name': self
.rststyle('strong')})
2950 self
.set_current_element(el
)
2952 def depart_strong(self
, node
) -> None:
2953 self
.set_to_parent()
2955 def visit_substitution_definition(self
, node
):
2956 raise nodes
.SkipChildren
2958 def depart_substitution_definition(self
, node
) -> None:
2961 def visit_system_message(self
, node
) -> None:
2964 def depart_system_message(self
, node
) -> None:
2967 def get_table_style(self
, node
):
2970 str_classes
= node
.get('classes')
2971 if str_classes
is not None:
2972 for str_class
in str_classes
:
2973 if str_class
.startswith(TABLESTYLEPREFIX
):
2974 table_name
= str_class
2976 if table_name
is not None:
2977 table_style
= self
.table_styles
.get(table_name
)
2978 if table_style
is None:
2979 # If we can't find the table style, issue warning
2980 # and use the default table style.
2981 self
.document
.reporter
.warning(
2982 f
'Can\'t find table style "{table_name}". Using default.',
2984 table_name
= TABLENAMEDEFAULT
2985 table_style
= self
.table_styles
.get(table_name
)
2986 if table_style
is None:
2987 # If we can't find the default table style, issue a warning
2988 # and use a built-in default style.
2989 self
.document
.reporter
.warning(
2990 f
'Can\'t find default table style "{table_name}". '
2991 'Using built-in default.', base_node
=node
)
2992 table_style
= BUILTIN_DEFAULT_TABLE_STYLE
2994 table_name
= TABLENAMEDEFAULT
2995 table_style
= self
.table_styles
.get(table_name
)
2996 if table_style
is None:
2997 # If we can't find the default table style, issue a warning
2998 # and use a built-in default style.
2999 self
.document
.reporter
.warning(
3000 'Can\'t find default table style "{table_name}". '
3001 'Using built-in default.', base_node
=node
)
3002 table_style
= BUILTIN_DEFAULT_TABLE_STYLE
3005 def visit_table(self
, node
) -> None:
3006 self
.table_count
+= 1
3007 table_style
= self
.get_table_style(node
)
3008 table_name
= '%s%%d' % TABLESTYLEPREFIX
3009 el1
= SubElement(self
.automatic_styles
, 'style:style', attrib
={
3010 'style:name': self
.rststyle(
3011 '%s' % table_name
, (self
.table_count
, )),
3012 'style:family': 'table',
3014 if table_style
.backgroundcolor
is None:
3015 SubElement(el1
, 'style:table-properties', attrib
={
3016 # 'style:width': '17.59cm',
3017 # 'table:align': 'margins',
3018 'table:align': 'left',
3019 'fo:margin-top': '0in',
3020 'fo:margin-bottom': '0.10in',
3023 SubElement(el1
, 'style:table-properties', attrib
={
3024 # 'style:width': '17.59cm',
3025 'table:align': 'margins',
3026 'fo:margin-top': '0in',
3027 'fo:margin-bottom': '0.10in',
3028 'fo:background-color': table_style
.backgroundcolor
,
3030 # We use a single cell style for all cells in this table.
3031 # That's probably not correct, but seems to work.
3032 el2
= SubElement(self
.automatic_styles
, 'style:style', attrib
={
3033 'style:name': self
.rststyle(
3034 '%s.%%c%%d' % table_name
, (self
.table_count
, 'A', 1, )),
3035 'style:family': 'table-cell',
3037 thickness
= self
.settings
.table_border_thickness
3038 if thickness
is None:
3039 line_style1
= table_style
.border
3041 line_style1
= '0.%03dcm solid #000000' % (thickness
, )
3042 SubElement(el2
, 'style:table-cell-properties', attrib
={
3043 'fo:padding': '0.049cm',
3044 'fo:border-left': line_style1
,
3045 'fo:border-right': line_style1
,
3046 'fo:border-top': line_style1
,
3047 'fo:border-bottom': line_style1
,
3050 for child
in node
.children
:
3051 if child
.tagname
== 'title':
3052 title
= child
.astext()
3054 if title
is not None:
3055 self
.append_p('table-title', title
)
3058 el4
= SubElement(self
.current_element
, 'table:table', attrib
={
3059 'table:name': self
.rststyle(
3060 '%s' % table_name
, (self
.table_count
, )),
3061 'table:style-name': self
.rststyle(
3062 '%s' % table_name
, (self
.table_count
, )),
3064 self
.set_current_element(el4
)
3065 self
.current_table_style
= el1
3066 self
.table_width
= 0.0
3068 def depart_table(self
, node
) -> None:
3069 attribkey
= add_ns('style:width', nsdict
=SNSD
)
3070 attribval
= '%.4fin' % (self
.table_width
, )
3071 el1
= self
.current_table_style
3073 el2
.attrib
[attribkey
] = attribval
3074 self
.set_to_parent()
3076 def visit_tgroup(self
, node
) -> None:
3077 self
.column_count
= ord('A') - 1
3079 def depart_tgroup(self
, node
) -> None:
3082 def visit_colspec(self
, node
) -> None:
3083 self
.column_count
+= 1
3084 colspec_name
= self
.rststyle(
3085 '%s%%d.%%s' % TABLESTYLEPREFIX
,
3086 (self
.table_count
, chr(self
.column_count
), )
3088 colwidth
= node
.propwidth() / 12.0
3089 el1
= SubElement(self
.automatic_styles
, 'style:style', attrib
={
3090 'style:name': colspec_name
,
3091 'style:family': 'table-column',
3093 SubElement(el1
, 'style:table-column-properties',
3094 attrib
={'style:column-width': '%.4fin' % colwidth
},
3096 self
.append_child('table:table-column',
3097 attrib
={'table:style-name': colspec_name
, })
3098 self
.table_width
+= colwidth
3100 def depart_colspec(self
, node
) -> None:
3103 def visit_thead(self
, node
) -> None:
3104 el
= self
.append_child('table:table-header-rows')
3105 self
.set_current_element(el
)
3106 self
.in_thead
= True
3107 self
.paragraph_style_stack
.append('Table_20_Heading')
3109 def depart_thead(self
, node
) -> None:
3110 self
.set_to_parent()
3111 self
.in_thead
= False
3112 self
.paragraph_style_stack
.pop()
3114 def visit_row(self
, node
) -> None:
3115 self
.column_count
= ord('A') - 1
3116 el
= self
.append_child('table:table-row')
3117 self
.set_current_element(el
)
3119 def depart_row(self
, node
) -> None:
3120 self
.set_to_parent()
3122 def visit_entry(self
, node
) -> None:
3123 self
.column_count
+= 1
3124 cellspec_name
= self
.rststyle(
3125 '%s%%d.%%c%%d' % TABLESTYLEPREFIX
,
3126 (self
.table_count
, 'A', 1, )
3129 'table:style-name': cellspec_name
,
3130 'office:value-type': 'string',
3132 morecols
= node
.get('morecols', 0)
3134 attrib
['table:number-columns-spanned'] = '%d' % (morecols
+ 1,)
3135 self
.column_count
+= morecols
3136 morerows
= node
.get('morerows', 0)
3138 attrib
['table:number-rows-spanned'] = '%d' % (morerows
+ 1,)
3139 el1
= self
.append_child('table:table-cell', attrib
=attrib
)
3140 self
.set_current_element(el1
)
3142 def depart_entry(self
, node
) -> None:
3143 self
.set_to_parent()
3145 def visit_tbody(self
, node
) -> None:
3148 def depart_tbody(self
, node
) -> None:
3151 def visit_target(self
, node
) -> None:
3153 # I don't know how to implement targets in ODF.
3154 # How do we create a target in oowriter? A cross-reference?
3155 if ('refuri' not in node
3156 and 'refid' not in node
3157 and 'refname' not in node
):
3162 def depart_target(self
, node
) -> None:
3165 def visit_title(self
, node
, move_ids
=1, title_type
='title') -> None:
3166 if isinstance(node
.parent
, docutils
.nodes
.section
):
3167 section_level
= self
.section_level
3168 if section_level
> 7:
3169 self
.document
.reporter
.warning(
3170 'Heading/section levels greater than 7 not supported.'
3171 ' Reducing to heading level 7 for heading: "%s"' % (
3172 node
.astext(), ), base_node
=node
)
3174 el1
= self
.append_child(
3176 'text:outline-level': '%d' % section_level
,
3177 # 'text:style-name': 'Heading_20_%d' % section_level,
3178 'text:style-name': self
.rststyle(
3179 'heading%d', (section_level
, )),
3181 self
.append_pending_ids(el1
)
3182 self
.set_current_element(el1
)
3183 elif isinstance(node
.parent
, docutils
.nodes
.document
):
3184 # text = self.settings.title
3186 # text = node.astext()
3187 el1
= SubElement(self
.current_element
, 'text:p', attrib
={
3188 'text:style-name': self
.rststyle(title_type
),
3190 self
.append_pending_ids(el1
)
3191 text
= node
.astext()
3193 self
.found_doc_title
= True
3194 self
.set_current_element(el1
)
3196 def depart_title(self
, node
) -> None:
3197 if (isinstance(node
.parent
, docutils
.nodes
.section
)
3198 or isinstance(node
.parent
, docutils
.nodes
.document
)):
3199 self
.set_to_parent()
3201 def visit_subtitle(self
, node
, move_ids
=1) -> None:
3202 self
.visit_title(node
, move_ids
, title_type
='subtitle')
3204 def depart_subtitle(self
, node
) -> None:
3205 self
.depart_title(node
)
3207 def visit_title_reference(self
, node
):
3208 el
= self
.append_child('text:span', attrib
={
3209 'text:style-name': self
.rststyle('quotation')})
3210 el
.text
= self
.encode(node
.astext())
3211 raise nodes
.SkipChildren
3213 def depart_title_reference(self
, node
) -> None:
3216 def generate_table_of_content_entry_template(self
, el1
) -> None:
3217 for idx
in range(1, 11):
3220 'text:table-of-content-entry-template',
3222 'text:outline-level': "%d" % (idx
, ),
3223 'text:style-name': self
.rststyle('contents-%d' % (idx
, )),
3225 SubElement(el2
, 'text:index-entry-chapter')
3226 SubElement(el2
, 'text:index-entry-text')
3227 SubElement(el2
, 'text:index-entry-tab-stop', attrib
={
3228 'style:leader-char': ".",
3229 'style:type': "right",
3231 SubElement(el2
, 'text:index-entry-page-number')
3233 def find_title_label(self
, node
, class_type
, label_key
):
3236 for child
in node
.children
:
3237 if isinstance(child
, class_type
):
3240 if title_node
is not None:
3241 label
= title_node
.astext()
3243 label
= self
.language
.labels
[label_key
]
3246 def visit_topic(self
, node
) -> None:
3247 if 'classes' in node
.attributes
:
3248 if 'contents' in node
.attributes
['classes']:
3249 label
= self
.find_title_label(
3250 node
, docutils
.nodes
.title
, 'contents')
3251 if self
.settings
.generate_oowriter_toc
:
3252 el1
= self
.append_child('text:table-of-content', attrib
={
3253 'text:name': 'Table of Contents1',
3254 'text:protected': 'true',
3255 'text:style-name': 'Sect1',
3259 'text:table-of-content-source',
3261 'text:outline-level': '10',
3263 el3
= SubElement(el2
, 'text:index-title-template', attrib
={
3264 'text:style-name': 'Contents_20_Heading',
3267 self
.generate_table_of_content_entry_template(el2
)
3268 el4
= SubElement(el1
, 'text:index-body')
3269 el5
= SubElement(el4
, 'text:index-title')
3270 el6
= SubElement(el5
, 'text:p', attrib
={
3271 'text:style-name': self
.rststyle('contents-heading'),
3274 self
.save_current_element
= self
.current_element
3275 self
.table_of_content_index_body
= el4
3276 self
.set_current_element(el4
)
3278 el
= self
.append_p('horizontalline')
3279 el
= self
.append_p('centeredtextbody')
3282 attrib
={'text:style-name': self
.rststyle('strong')})
3284 self
.in_table_of_contents
= True
3285 elif 'abstract' in node
.attributes
['classes']:
3286 el
= self
.append_p('horizontalline')
3287 el
= self
.append_p('centeredtextbody')
3290 attrib
={'text:style-name': self
.rststyle('strong')})
3291 label
= self
.find_title_label(
3292 node
, docutils
.nodes
.title
,
3295 elif 'dedication' in node
.attributes
['classes']:
3296 el
= self
.append_p('horizontalline')
3297 el
= self
.append_p('centeredtextbody')
3300 attrib
={'text:style-name': self
.rststyle('strong')})
3301 label
= self
.find_title_label(
3302 node
, docutils
.nodes
.title
,
3306 def depart_topic(self
, node
) -> None:
3307 if 'classes' in node
.attributes
:
3308 if 'contents' in node
.attributes
['classes']:
3309 if self
.settings
.generate_oowriter_toc
:
3310 self
.update_toc_page_numbers(
3311 self
.table_of_content_index_body
)
3312 self
.set_current_element(self
.save_current_element
)
3314 self
.append_p('horizontalline')
3315 self
.in_table_of_contents
= False
3317 def update_toc_page_numbers(self
, el
) -> None:
3319 self
.update_toc_collect(el
, 0, collection
)
3320 self
.update_toc_add_numbers(collection
)
3322 def update_toc_collect(self
, el
, level
, collection
) -> None:
3323 collection
.append((level
, el
))
3326 if child_el
.tag
!= 'text:index-body':
3327 self
.update_toc_collect(child_el
, level
, collection
)
3329 def update_toc_add_numbers(self
, collection
) -> None:
3330 for level
, el1
in collection
:
3331 if (el1
.tag
== 'text:p'
3332 and el1
.text
!= 'Table of Contents'):
3333 el2
= SubElement(el1
, 'text:tab')
3336 def visit_transition(self
, node
) -> None:
3337 self
.append_p('horizontalline')
3339 def depart_transition(self
, node
) -> None:
3345 def visit_warning(self
, node
) -> None:
3346 self
.generate_admonition(node
, 'warning')
3348 def depart_warning(self
, node
) -> None:
3349 self
.paragraph_style_stack
.pop()
3351 def visit_attention(self
, node
) -> None:
3352 self
.generate_admonition(node
, 'attention')
3354 depart_attention
= depart_warning
3356 def visit_caution(self
, node
) -> None:
3357 self
.generate_admonition(node
, 'caution')
3359 depart_caution
= depart_warning
3361 def visit_danger(self
, node
) -> None:
3362 self
.generate_admonition(node
, 'danger')
3364 depart_danger
= depart_warning
3366 def visit_error(self
, node
) -> None:
3367 self
.generate_admonition(node
, 'error')
3369 depart_error
= depart_warning
3371 def visit_hint(self
, node
) -> None:
3372 self
.generate_admonition(node
, 'hint')
3374 depart_hint
= depart_warning
3376 def visit_important(self
, node
) -> None:
3377 self
.generate_admonition(node
, 'important')
3379 depart_important
= depart_warning
3381 def visit_note(self
, node
) -> None:
3382 self
.generate_admonition(node
, 'note')
3384 depart_note
= depart_warning
3386 def visit_tip(self
, node
) -> None:
3387 self
.generate_admonition(node
, 'tip')
3389 depart_tip
= depart_warning
3391 def visit_admonition(self
, node
) -> None:
3393 for child
in node
.children
:
3394 if child
.tagname
== 'title':
3395 title
= child
.astext()
3397 classes1
= node
.get('classes')
3400 self
.generate_admonition(node
, 'generic', title
)
3402 depart_admonition
= depart_warning
3404 def generate_admonition(self
, node
, label
, title
=None) -> None:
3405 if hasattr(self
.language
, 'labels'):
3406 translated_label
= self
.language
.labels
.get(label
, label
)
3408 translated_label
= label
3409 el1
= SubElement(self
.current_element
, 'text:p', attrib
={
3410 'text:style-name': self
.rststyle(
3411 'admon-%s-hdr', (label
, )),
3416 el1
.text
= '%s!' % (translated_label
.capitalize(), )
3417 s1
= self
.rststyle('admon-%s-body', (label
, ))
3418 self
.paragraph_style_stack
.append(s1
)
3421 # Roles (e.g. subscript, superscript, strong, ...
3423 def visit_subscript(self
, node
) -> None:
3424 el
= self
.append_child('text:span', attrib
={
3425 'text:style-name': 'rststyle-subscript',
3427 self
.set_current_element(el
)
3429 def depart_subscript(self
, node
) -> None:
3430 self
.set_to_parent()
3432 def visit_superscript(self
, node
) -> None:
3433 el
= self
.append_child('text:span', attrib
={
3434 'text:style-name': 'rststyle-superscript',
3436 self
.set_current_element(el
)
3438 def depart_superscript(self
, node
) -> None:
3439 self
.set_to_parent()
3441 def visit_abbreviation(self
, node
) -> None:
3444 def depart_abbreviation(self
, node
) -> None:
3447 def visit_acronym(self
, node
) -> None:
3450 def depart_acronym(self
, node
) -> None:
3453 def visit_sidebar(self
, node
) -> None:
3456 def depart_sidebar(self
, node
) -> None:
3460 # Use an own reader to modify transformations done.
3461 class Reader(standalone
.Reader
):
3463 def get_transforms(self
):
3464 transforms
= super().get_transforms()
3465 if not self
.settings
.create_links
:
3466 transforms
.remove(references
.DanglingReferences
)