PageRenderTime 34ms CodeModel.GetById 13ms RepoModel.GetById 0ms app.codeStats 1ms

/python/helpers/py3only/docutils/writers/odf_odt/__init__.py

http://github.com/JetBrains/intellij-community
Python | 3297 lines | 3131 code | 101 blank | 65 comment | 77 complexity | 93563875264b030ebfef20b0d0a830de MD5 | raw file
Possible License(s): BSD-3-Clause, Apache-2.0, MPL-2.0-no-copyleft-exception, MIT, EPL-1.0, AGPL-1.0

Large files files are truncated, but you can click here to view the full file

  1. # $Id: __init__.py 7717 2013-08-21 22:01:21Z milde $
  2. # Author: Dave Kuhlman <dkuhlman@rexx.com>
  3. # Copyright: This module has been placed in the public domain.
  4. """
  5. Open Document Format (ODF) Writer.
  6. """
  7. VERSION = '1.0a'
  8. __docformat__ = 'reStructuredText'
  9. import copy
  10. import io
  11. import os
  12. import os.path
  13. import re
  14. import sys
  15. import tempfile
  16. import time
  17. import zipfile
  18. from xml.dom import minidom
  19. import urllib.error
  20. import urllib.parse
  21. import urllib.request
  22. import docutils
  23. from docutils import frontend, nodes, utils, writers, languages
  24. from docutils.readers import standalone
  25. from docutils.transforms import references
  26. WhichElementTree = ''
  27. try:
  28. # 1. Try to use lxml.
  29. #from lxml import etree
  30. #WhichElementTree = 'lxml'
  31. raise ImportError('Ignoring lxml')
  32. except ImportError as e:
  33. try:
  34. # 2. Try to use ElementTree from the Python standard library.
  35. from xml.etree import ElementTree as etree
  36. WhichElementTree = 'elementtree'
  37. except ImportError as e:
  38. try:
  39. # 3. Try to use a version of ElementTree installed as a separate
  40. # product.
  41. from elementtree import ElementTree as etree
  42. WhichElementTree = 'elementtree'
  43. except ImportError as e:
  44. s1 = 'Must install either a version of Python containing ' \
  45. 'ElementTree (Python version >=2.5) or install ElementTree.'
  46. raise ImportError(s1)
  47. #
  48. # Import pygments and odtwriter pygments formatters if possible.
  49. try:
  50. import pygments
  51. import pygments.lexers
  52. from .pygmentsformatter import OdtPygmentsProgFormatter, \
  53. OdtPygmentsLaTeXFormatter
  54. except ImportError as exp:
  55. pygments = None
  56. # check for the Python Imaging Library
  57. try:
  58. import PIL.Image
  59. except ImportError:
  60. try: # sometimes PIL modules are put in PYTHONPATH's root
  61. import Image
  62. class PIL(object): pass # dummy wrapper
  63. PIL.Image = Image
  64. except ImportError:
  65. PIL = None
  66. ## import warnings
  67. ## warnings.warn('importing IPShellEmbed', UserWarning)
  68. ## from IPython.Shell import IPShellEmbed
  69. ## args = ['-pdb', '-pi1', 'In <\\#>: ', '-pi2', ' .\\D.: ',
  70. ## '-po', 'Out<\\#>: ', '-nosep']
  71. ## ipshell = IPShellEmbed(args,
  72. ## banner = 'Entering IPython. Press Ctrl-D to exit.',
  73. ## exit_msg = 'Leaving Interpreter, back to program.')
  74. #
  75. # ElementTree does not support getparent method (lxml does).
  76. # This wrapper class and the following support functions provide
  77. # that support for the ability to get the parent of an element.
  78. #
  79. if WhichElementTree == 'elementtree':
  80. import weakref
  81. _parents = weakref.WeakKeyDictionary()
  82. if isinstance(etree.Element, type):
  83. _ElementInterface = etree.Element
  84. else:
  85. _ElementInterface = etree._ElementInterface
  86. class _ElementInterfaceWrapper(_ElementInterface):
  87. def __init__(self, tag, attrib=None):
  88. _ElementInterface.__init__(self, tag, attrib)
  89. _parents[self] = None
  90. def setparent(self, parent):
  91. _parents[self] = parent
  92. def getparent(self):
  93. return _parents[self]
  94. #
  95. # Constants and globals
  96. SPACES_PATTERN = re.compile(r'( +)')
  97. TABS_PATTERN = re.compile(r'(\t+)')
  98. FILL_PAT1 = re.compile(r'^ +')
  99. FILL_PAT2 = re.compile(r' {2,}')
  100. TABLESTYLEPREFIX = 'rststyle-table-'
  101. TABLENAMEDEFAULT = '%s0' % TABLESTYLEPREFIX
  102. TABLEPROPERTYNAMES = ('border', 'border-top', 'border-left',
  103. 'border-right', 'border-bottom', )
  104. GENERATOR_DESC = 'Docutils.org/odf_odt'
  105. NAME_SPACE_1 = 'urn:oasis:names:tc:opendocument:xmlns:office:1.0'
  106. CONTENT_NAMESPACE_DICT = CNSD = {
  107. # 'office:version': '1.0',
  108. 'chart': 'urn:oasis:names:tc:opendocument:xmlns:chart:1.0',
  109. 'dc': 'http://purl.org/dc/elements/1.1/',
  110. 'dom': 'http://www.w3.org/2001/xml-events',
  111. 'dr3d': 'urn:oasis:names:tc:opendocument:xmlns:dr3d:1.0',
  112. 'draw': 'urn:oasis:names:tc:opendocument:xmlns:drawing:1.0',
  113. 'fo': 'urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0',
  114. 'form': 'urn:oasis:names:tc:opendocument:xmlns:form:1.0',
  115. 'math': 'http://www.w3.org/1998/Math/MathML',
  116. 'meta': 'urn:oasis:names:tc:opendocument:xmlns:meta:1.0',
  117. 'number': 'urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0',
  118. 'office': NAME_SPACE_1,
  119. 'ooo': 'http://openoffice.org/2004/office',
  120. 'oooc': 'http://openoffice.org/2004/calc',
  121. 'ooow': 'http://openoffice.org/2004/writer',
  122. 'presentation': 'urn:oasis:names:tc:opendocument:xmlns:presentation:1.0',
  123. 'script': 'urn:oasis:names:tc:opendocument:xmlns:script:1.0',
  124. 'style': 'urn:oasis:names:tc:opendocument:xmlns:style:1.0',
  125. 'svg': 'urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0',
  126. 'table': 'urn:oasis:names:tc:opendocument:xmlns:table:1.0',
  127. 'text': 'urn:oasis:names:tc:opendocument:xmlns:text:1.0',
  128. 'xforms': 'http://www.w3.org/2002/xforms',
  129. 'xlink': 'http://www.w3.org/1999/xlink',
  130. 'xsd': 'http://www.w3.org/2001/XMLSchema',
  131. 'xsi': 'http://www.w3.org/2001/XMLSchema-instance',
  132. }
  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',
  156. }
  157. MANIFEST_NAMESPACE_DICT = MANNSD = {
  158. 'manifest': 'urn:oasis:names:tc:opendocument:xmlns:manifest:1.0',
  159. }
  160. META_NAMESPACE_DICT = METNSD = {
  161. # 'office:version': '1.0',
  162. 'dc': 'http://purl.org/dc/elements/1.1/',
  163. 'meta': 'urn:oasis:names:tc:opendocument:xmlns:meta:1.0',
  164. 'office': NAME_SPACE_1,
  165. 'ooo': 'http://openoffice.org/2004/office',
  166. 'xlink': 'http://www.w3.org/1999/xlink',
  167. }
  168. #
  169. # Attribute dictionaries for use with ElementTree (not lxml), which
  170. # does not support use of nsmap parameter on Element() and SubElement().
  171. CONTENT_NAMESPACE_ATTRIB = {
  172. #'office:version': '1.0',
  173. 'xmlns:chart': 'urn:oasis:names:tc:opendocument:xmlns:chart:1.0',
  174. 'xmlns:dc': 'http://purl.org/dc/elements/1.1/',
  175. 'xmlns:dom': 'http://www.w3.org/2001/xml-events',
  176. 'xmlns:dr3d': 'urn:oasis:names:tc:opendocument:xmlns:dr3d:1.0',
  177. 'xmlns:draw': 'urn:oasis:names:tc:opendocument:xmlns:drawing:1.0',
  178. 'xmlns:fo': 'urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0',
  179. 'xmlns:form': 'urn:oasis:names:tc:opendocument:xmlns:form:1.0',
  180. 'xmlns:math': 'http://www.w3.org/1998/Math/MathML',
  181. 'xmlns:meta': 'urn:oasis:names:tc:opendocument:xmlns:meta:1.0',
  182. 'xmlns:number': 'urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0',
  183. 'xmlns:office': NAME_SPACE_1,
  184. 'xmlns:presentation': 'urn:oasis:names:tc:opendocument:xmlns:presentation:1.0',
  185. 'xmlns:ooo': 'http://openoffice.org/2004/office',
  186. 'xmlns:oooc': 'http://openoffice.org/2004/calc',
  187. 'xmlns:ooow': 'http://openoffice.org/2004/writer',
  188. 'xmlns:script': 'urn:oasis:names:tc:opendocument:xmlns:script:1.0',
  189. 'xmlns:style': 'urn:oasis:names:tc:opendocument:xmlns:style:1.0',
  190. 'xmlns:svg': 'urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0',
  191. 'xmlns:table': 'urn:oasis:names:tc:opendocument:xmlns:table:1.0',
  192. 'xmlns:text': 'urn:oasis:names:tc:opendocument:xmlns:text:1.0',
  193. 'xmlns:xforms': 'http://www.w3.org/2002/xforms',
  194. 'xmlns:xlink': 'http://www.w3.org/1999/xlink',
  195. 'xmlns:xsd': 'http://www.w3.org/2001/XMLSchema',
  196. 'xmlns:xsi': 'http://www.w3.org/2001/XMLSchema-instance',
  197. }
  198. STYLES_NAMESPACE_ATTRIB = {
  199. #'office:version': '1.0',
  200. 'xmlns:chart': 'urn:oasis:names:tc:opendocument:xmlns:chart:1.0',
  201. 'xmlns:dc': 'http://purl.org/dc/elements/1.1/',
  202. 'xmlns:dom': 'http://www.w3.org/2001/xml-events',
  203. 'xmlns:dr3d': 'urn:oasis:names:tc:opendocument:xmlns:dr3d:1.0',
  204. 'xmlns:draw': 'urn:oasis:names:tc:opendocument:xmlns:drawing:1.0',
  205. 'xmlns:fo': 'urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0',
  206. 'xmlns:form': 'urn:oasis:names:tc:opendocument:xmlns:form:1.0',
  207. 'xmlns:math': 'http://www.w3.org/1998/Math/MathML',
  208. 'xmlns:meta': 'urn:oasis:names:tc:opendocument:xmlns:meta:1.0',
  209. 'xmlns:number': 'urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0',
  210. 'xmlns:office': NAME_SPACE_1,
  211. 'xmlns:presentation': 'urn:oasis:names:tc:opendocument:xmlns:presentation:1.0',
  212. 'xmlns:ooo': 'http://openoffice.org/2004/office',
  213. 'xmlns:oooc': 'http://openoffice.org/2004/calc',
  214. 'xmlns:ooow': 'http://openoffice.org/2004/writer',
  215. 'xmlns:script': 'urn:oasis:names:tc:opendocument:xmlns:script:1.0',
  216. 'xmlns:style': 'urn:oasis:names:tc:opendocument:xmlns:style:1.0',
  217. 'xmlns:svg': 'urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0',
  218. 'xmlns:table': 'urn:oasis:names:tc:opendocument:xmlns:table:1.0',
  219. 'xmlns:text': 'urn:oasis:names:tc:opendocument:xmlns:text:1.0',
  220. 'xmlns:xlink': 'http://www.w3.org/1999/xlink',
  221. }
  222. MANIFEST_NAMESPACE_ATTRIB = {
  223. 'xmlns:manifest': 'urn:oasis:names:tc:opendocument:xmlns:manifest:1.0',
  224. }
  225. META_NAMESPACE_ATTRIB = {
  226. #'office:version': '1.0',
  227. 'xmlns:dc': 'http://purl.org/dc/elements/1.1/',
  228. 'xmlns:meta': 'urn:oasis:names:tc:opendocument:xmlns:meta:1.0',
  229. 'xmlns:office': NAME_SPACE_1,
  230. 'xmlns:ooo': 'http://openoffice.org/2004/office',
  231. 'xmlns:xlink': 'http://www.w3.org/1999/xlink',
  232. }
  233. #
  234. # Functions
  235. #
  236. #
  237. # ElementTree support functions.
  238. # In order to be able to get the parent of elements, must use these
  239. # instead of the functions with same name provided by ElementTree.
  240. #
  241. def Element(tag, attrib=None, nsmap=None, nsdict=CNSD):
  242. if attrib is None:
  243. attrib = {}
  244. tag, attrib = fix_ns(tag, attrib, nsdict)
  245. if WhichElementTree == 'lxml':
  246. el = etree.Element(tag, attrib, nsmap=nsmap)
  247. else:
  248. el = _ElementInterfaceWrapper(tag, attrib)
  249. return el
  250. def SubElement(parent, tag, attrib=None, nsmap=None, nsdict=CNSD):
  251. if attrib is None:
  252. attrib = {}
  253. tag, attrib = fix_ns(tag, attrib, nsdict)
  254. if WhichElementTree == 'lxml':
  255. el = etree.SubElement(parent, tag, attrib, nsmap=nsmap)
  256. else:
  257. el = _ElementInterfaceWrapper(tag, attrib)
  258. parent.append(el)
  259. el.setparent(parent)
  260. return el
  261. def fix_ns(tag, attrib, nsdict):
  262. nstag = add_ns(tag, nsdict)
  263. nsattrib = {}
  264. for key, val in attrib.items():
  265. nskey = add_ns(key, nsdict)
  266. nsattrib[nskey] = val
  267. return nstag, nsattrib
  268. def add_ns(tag, nsdict=CNSD):
  269. if WhichElementTree == 'lxml':
  270. nstag, name = tag.split(':')
  271. ns = nsdict.get(nstag)
  272. if ns is None:
  273. raise RuntimeError('Invalid namespace prefix: %s' % nstag)
  274. tag = '{%s}%s' % (ns, name,)
  275. return tag
  276. def ToString(et):
  277. outstream = io.StringIO()
  278. if sys.version_info >= (3, 2):
  279. et.write(outstream, encoding="unicode")
  280. else:
  281. et.write(outstream)
  282. s1 = outstream.getvalue()
  283. outstream.close()
  284. return s1
  285. def escape_cdata(text):
  286. text = text.replace("&", "&amp;")
  287. text = text.replace("<", "&lt;")
  288. text = text.replace(">", "&gt;")
  289. ascii = ''
  290. for char in text:
  291. if ord(char) >= ord("\x7f"):
  292. ascii += "&#x%X;" % ( ord(char), )
  293. else:
  294. ascii += char
  295. return ascii
  296. WORD_SPLIT_PAT1 = re.compile(r'\b(\w*)\b\W*')
  297. def split_words(line):
  298. # We need whitespace at the end of the string for our regexpr.
  299. line += ' '
  300. words = []
  301. pos1 = 0
  302. mo = WORD_SPLIT_PAT1.search(line, pos1)
  303. while mo is not None:
  304. word = mo.groups()[0]
  305. words.append(word)
  306. pos1 = mo.end()
  307. mo = WORD_SPLIT_PAT1.search(line, pos1)
  308. return words
  309. #
  310. # Classes
  311. #
  312. class TableStyle(object):
  313. def __init__(self, border=None, backgroundcolor=None):
  314. self.border = border
  315. self.backgroundcolor = backgroundcolor
  316. def get_border_(self):
  317. return self.border_
  318. def set_border_(self, border):
  319. self.border_ = border
  320. border = property(get_border_, set_border_)
  321. def get_backgroundcolor_(self):
  322. return self.backgroundcolor_
  323. def set_backgroundcolor_(self, backgroundcolor):
  324. self.backgroundcolor_ = backgroundcolor
  325. backgroundcolor = property(get_backgroundcolor_, set_backgroundcolor_)
  326. BUILTIN_DEFAULT_TABLE_STYLE = TableStyle(
  327. border = '0.0007in solid #000000')
  328. #
  329. # Information about the indentation level for lists nested inside
  330. # other contexts, e.g. dictionary lists.
  331. class ListLevel(object):
  332. def __init__(self, level, sibling_level=True, nested_level=True):
  333. self.level = level
  334. self.sibling_level = sibling_level
  335. self.nested_level = nested_level
  336. def set_sibling(self, sibling_level): self.sibling_level = sibling_level
  337. def get_sibling(self): return self.sibling_level
  338. def set_nested(self, nested_level): self.nested_level = nested_level
  339. def get_nested(self): return self.nested_level
  340. def set_level(self, level): self.level = level
  341. def get_level(self): return self.level
  342. class Writer(writers.Writer):
  343. MIME_TYPE = 'application/vnd.oasis.opendocument.text'
  344. EXTENSION = '.odt'
  345. supported = ('odt', )
  346. """Formats this writer supports."""
  347. default_stylesheet = 'styles' + EXTENSION
  348. default_stylesheet_path = utils.relative_path(
  349. os.path.join(os.getcwd(), 'dummy'),
  350. os.path.join(os.path.dirname(__file__), default_stylesheet))
  351. default_template = 'template.txt'
  352. default_template_path = utils.relative_path(
  353. os.path.join(os.getcwd(), 'dummy'),
  354. os.path.join(os.path.dirname(__file__), default_template))
  355. settings_spec = (
  356. 'ODF-Specific Options',
  357. None,
  358. (
  359. ('Specify a stylesheet. '
  360. 'Default: "%s"' % default_stylesheet_path,
  361. ['--stylesheet'],
  362. {
  363. 'default': default_stylesheet_path,
  364. 'dest': 'stylesheet'
  365. }),
  366. ('Specify a configuration/mapping file relative to the '
  367. 'current working '
  368. 'directory for additional ODF options. '
  369. 'In particular, this file may contain a section named '
  370. '"Formats" that maps default style names to '
  371. 'names to be used in the resulting output file allowing for '
  372. 'adhering to external standards. '
  373. 'For more info and the format of the configuration/mapping file, '
  374. 'see the odtwriter doc.',
  375. ['--odf-config-file'],
  376. {'metavar': '<file>'}),
  377. ('Obfuscate email addresses to confuse harvesters while still '
  378. 'keeping email links usable with standards-compliant browsers.',
  379. ['--cloak-email-addresses'],
  380. {'default': False,
  381. 'action': 'store_true',
  382. 'dest': 'cloak_email_addresses',
  383. 'validator': frontend.validate_boolean}),
  384. ('Do not obfuscate email addresses.',
  385. ['--no-cloak-email-addresses'],
  386. {'default': False,
  387. 'action': 'store_false',
  388. 'dest': 'cloak_email_addresses',
  389. 'validator': frontend.validate_boolean}),
  390. ('Specify the thickness of table borders in thousands of a cm. '
  391. 'Default is 35.',
  392. ['--table-border-thickness'],
  393. {'default': None,
  394. 'validator': frontend.validate_nonnegative_int}),
  395. ('Add syntax highlighting in literal code blocks.',
  396. ['--add-syntax-highlighting'],
  397. {'default': False,
  398. 'action': 'store_true',
  399. 'dest': 'add_syntax_highlighting',
  400. 'validator': frontend.validate_boolean}),
  401. ('Do not add syntax highlighting in literal code blocks. (default)',
  402. ['--no-syntax-highlighting'],
  403. {'default': False,
  404. 'action': 'store_false',
  405. 'dest': 'add_syntax_highlighting',
  406. 'validator': frontend.validate_boolean}),
  407. ('Create sections for headers. (default)',
  408. ['--create-sections'],
  409. {'default': True,
  410. 'action': 'store_true',
  411. 'dest': 'create_sections',
  412. 'validator': frontend.validate_boolean}),
  413. ('Do not create sections for headers.',
  414. ['--no-sections'],
  415. {'default': True,
  416. 'action': 'store_false',
  417. 'dest': 'create_sections',
  418. 'validator': frontend.validate_boolean}),
  419. ('Create links.',
  420. ['--create-links'],
  421. {'default': False,
  422. 'action': 'store_true',
  423. 'dest': 'create_links',
  424. 'validator': frontend.validate_boolean}),
  425. ('Do not create links. (default)',
  426. ['--no-links'],
  427. {'default': False,
  428. 'action': 'store_false',
  429. 'dest': 'create_links',
  430. 'validator': frontend.validate_boolean}),
  431. ('Generate endnotes at end of document, not footnotes '
  432. 'at bottom of page.',
  433. ['--endnotes-end-doc'],
  434. {'default': False,
  435. 'action': 'store_true',
  436. 'dest': 'endnotes_end_doc',
  437. 'validator': frontend.validate_boolean}),
  438. ('Generate footnotes at bottom of page, not endnotes '
  439. 'at end of document. (default)',
  440. ['--no-endnotes-end-doc'],
  441. {'default': False,
  442. 'action': 'store_false',
  443. 'dest': 'endnotes_end_doc',
  444. 'validator': frontend.validate_boolean}),
  445. ('Generate a bullet list table of contents, not '
  446. 'an ODF/oowriter table of contents.',
  447. ['--generate-list-toc'],
  448. {'default': True,
  449. 'action': 'store_false',
  450. 'dest': 'generate_oowriter_toc',
  451. 'validator': frontend.validate_boolean}),
  452. ('Generate an ODF/oowriter table of contents, not '
  453. 'a bullet list. (default)',
  454. ['--generate-oowriter-toc'],
  455. {'default': True,
  456. 'action': 'store_true',
  457. 'dest': 'generate_oowriter_toc',
  458. 'validator': frontend.validate_boolean}),
  459. ('Specify the contents of an custom header line. '
  460. 'See odf_odt writer documentation for details '
  461. 'about special field character sequences.',
  462. ['--custom-odt-header'],
  463. { 'default': '',
  464. 'dest': 'custom_header',
  465. }),
  466. ('Specify the contents of an custom footer line. '
  467. 'See odf_odt writer documentation for details '
  468. 'about special field character sequences.',
  469. ['--custom-odt-footer'],
  470. { 'default': '',
  471. 'dest': 'custom_footer',
  472. }),
  473. )
  474. )
  475. settings_defaults = {
  476. 'output_encoding_error_handler': 'xmlcharrefreplace',
  477. }
  478. relative_path_settings = (
  479. 'stylesheet_path',
  480. )
  481. config_section = 'odf_odt writer'
  482. config_section_dependencies = (
  483. 'writers',
  484. )
  485. def __init__(self):
  486. writers.Writer.__init__(self)
  487. self.translator_class = ODFTranslator
  488. def translate(self):
  489. self.settings = self.document.settings
  490. self.visitor = self.translator_class(self.document)
  491. self.visitor.retrieve_styles(self.EXTENSION)
  492. self.document.walkabout(self.visitor)
  493. self.visitor.add_doc_title()
  494. self.assemble_my_parts()
  495. self.output = self.parts['whole']
  496. def assemble_my_parts(self):
  497. """Assemble the `self.parts` dictionary. Extend in subclasses.
  498. """
  499. writers.Writer.assemble_parts(self)
  500. f = tempfile.NamedTemporaryFile()
  501. zfile = zipfile.ZipFile(f, 'w', zipfile.ZIP_DEFLATED)
  502. self.write_zip_str(zfile, 'mimetype', self.MIME_TYPE,
  503. compress_type=zipfile.ZIP_STORED)
  504. content = self.visitor.content_astext()
  505. self.write_zip_str(zfile, 'content.xml', content)
  506. s1 = self.create_manifest()
  507. self.write_zip_str(zfile, 'META-INF/manifest.xml', s1)
  508. s1 = self.create_meta()
  509. self.write_zip_str(zfile, 'meta.xml', s1)
  510. s1 = self.get_stylesheet()
  511. self.write_zip_str(zfile, 'styles.xml', s1)
  512. self.store_embedded_files(zfile)
  513. self.copy_from_stylesheet(zfile)
  514. zfile.close()
  515. f.seek(0)
  516. whole = f.read()
  517. f.close()
  518. self.parts['whole'] = whole
  519. self.parts['encoding'] = self.document.settings.output_encoding
  520. self.parts['version'] = docutils.__version__
  521. def write_zip_str(self, zfile, name, bytes, compress_type=zipfile.ZIP_DEFLATED):
  522. localtime = time.localtime(time.time())
  523. zinfo = zipfile.ZipInfo(name, localtime)
  524. # Add some standard UNIX file access permissions (-rw-r--r--).
  525. zinfo.external_attr = (0x81a4 & 0xFFFF) << 16
  526. zinfo.compress_type = compress_type
  527. zfile.writestr(zinfo, bytes)
  528. def store_embedded_files(self, zfile):
  529. embedded_files = self.visitor.get_embedded_file_list()
  530. for source, destination in embedded_files:
  531. if source is None:
  532. continue
  533. try:
  534. # encode/decode
  535. destination1 = destination.decode('latin-1').encode('utf-8')
  536. zfile.write(source, destination1)
  537. except OSError as e:
  538. self.document.reporter.warning(
  539. "Can't open file %s." % (source, ))
  540. def get_settings(self):
  541. """
  542. modeled after get_stylesheet
  543. """
  544. stylespath = self.settings.stylesheet
  545. zfile = zipfile.ZipFile(stylespath, 'r')
  546. s1 = zfile.read('settings.xml')
  547. zfile.close()
  548. return s1
  549. def get_stylesheet(self):
  550. """Get the stylesheet from the visitor.
  551. Ask the visitor to setup the page.
  552. """
  553. s1 = self.visitor.setup_page()
  554. return s1
  555. def copy_from_stylesheet(self, outzipfile):
  556. """Copy images, settings, etc from the stylesheet doc into target doc.
  557. """
  558. stylespath = self.settings.stylesheet
  559. inzipfile = zipfile.ZipFile(stylespath, 'r')
  560. # Copy the styles.
  561. s1 = inzipfile.read('settings.xml')
  562. self.write_zip_str(outzipfile, 'settings.xml', s1)
  563. # Copy the images.
  564. namelist = inzipfile.namelist()
  565. for name in namelist:
  566. if name.startswith('Pictures/'):
  567. imageobj = inzipfile.read(name)
  568. outzipfile.writestr(name, imageobj)
  569. inzipfile.close()
  570. def assemble_parts(self):
  571. pass
  572. def create_manifest(self):
  573. if WhichElementTree == 'lxml':
  574. root = Element('manifest:manifest',
  575. nsmap=MANIFEST_NAMESPACE_DICT,
  576. nsdict=MANIFEST_NAMESPACE_DICT,
  577. )
  578. else:
  579. root = Element('manifest:manifest',
  580. attrib=MANIFEST_NAMESPACE_ATTRIB,
  581. nsdict=MANIFEST_NAMESPACE_DICT,
  582. )
  583. doc = etree.ElementTree(root)
  584. SubElement(root, 'manifest:file-entry', attrib={
  585. 'manifest:media-type': self.MIME_TYPE,
  586. 'manifest:full-path': '/',
  587. }, nsdict=MANNSD)
  588. SubElement(root, 'manifest:file-entry', attrib={
  589. 'manifest:media-type': 'text/xml',
  590. 'manifest:full-path': 'content.xml',
  591. }, nsdict=MANNSD)
  592. SubElement(root, 'manifest:file-entry', attrib={
  593. 'manifest:media-type': 'text/xml',
  594. 'manifest:full-path': 'styles.xml',
  595. }, nsdict=MANNSD)
  596. SubElement(root, 'manifest:file-entry', attrib={
  597. 'manifest:media-type': 'text/xml',
  598. 'manifest:full-path': 'settings.xml',
  599. }, nsdict=MANNSD)
  600. SubElement(root, 'manifest:file-entry', attrib={
  601. 'manifest:media-type': 'text/xml',
  602. 'manifest:full-path': 'meta.xml',
  603. }, nsdict=MANNSD)
  604. s1 = ToString(doc)
  605. doc = minidom.parseString(s1)
  606. s1 = doc.toprettyxml(' ')
  607. return s1
  608. def create_meta(self):
  609. if WhichElementTree == 'lxml':
  610. root = Element('office:document-meta',
  611. nsmap=META_NAMESPACE_DICT,
  612. nsdict=META_NAMESPACE_DICT,
  613. )
  614. else:
  615. root = Element('office:document-meta',
  616. attrib=META_NAMESPACE_ATTRIB,
  617. nsdict=META_NAMESPACE_DICT,
  618. )
  619. doc = etree.ElementTree(root)
  620. root = SubElement(root, 'office:meta', nsdict=METNSD)
  621. el1 = SubElement(root, 'meta:generator', nsdict=METNSD)
  622. el1.text = 'Docutils/rst2odf.py/%s' % (VERSION, )
  623. s1 = os.environ.get('USER', '')
  624. el1 = SubElement(root, 'meta:initial-creator', nsdict=METNSD)
  625. el1.text = s1
  626. s2 = time.strftime('%Y-%m-%dT%H:%M:%S', time.localtime())
  627. el1 = SubElement(root, 'meta:creation-date', nsdict=METNSD)
  628. el1.text = s2
  629. el1 = SubElement(root, 'dc:creator', nsdict=METNSD)
  630. el1.text = s1
  631. el1 = SubElement(root, 'dc:date', nsdict=METNSD)
  632. el1.text = s2
  633. el1 = SubElement(root, 'dc:language', nsdict=METNSD)
  634. el1.text = 'en-US'
  635. el1 = SubElement(root, 'meta:editing-cycles', nsdict=METNSD)
  636. el1.text = '1'
  637. el1 = SubElement(root, 'meta:editing-duration', nsdict=METNSD)
  638. el1.text = 'PT00M01S'
  639. title = self.visitor.get_title()
  640. el1 = SubElement(root, 'dc:title', nsdict=METNSD)
  641. if title:
  642. el1.text = title
  643. else:
  644. el1.text = '[no title]'
  645. meta_dict = self.visitor.get_meta_dict()
  646. keywordstr = meta_dict.get('keywords')
  647. if keywordstr is not None:
  648. keywords = split_words(keywordstr)
  649. for keyword in keywords:
  650. el1 = SubElement(root, 'meta:keyword', nsdict=METNSD)
  651. el1.text = keyword
  652. description = meta_dict.get('description')
  653. if description is not None:
  654. el1 = SubElement(root, 'dc:description', nsdict=METNSD)
  655. el1.text = description
  656. s1 = ToString(doc)
  657. #doc = minidom.parseString(s1)
  658. #s1 = doc.toprettyxml(' ')
  659. return s1
  660. # class ODFTranslator(nodes.SparseNodeVisitor):
  661. class ODFTranslator(nodes.GenericNodeVisitor):
  662. used_styles = (
  663. 'attribution', 'blockindent', 'blockquote', 'blockquote-bulletitem',
  664. 'blockquote-bulletlist', 'blockquote-enumitem', 'blockquote-enumlist',
  665. 'bulletitem', 'bulletlist',
  666. 'caption', 'legend',
  667. 'centeredtextbody', 'codeblock', 'codeblock-indented',
  668. 'codeblock-classname', 'codeblock-comment', 'codeblock-functionname',
  669. 'codeblock-keyword', 'codeblock-name', 'codeblock-number',
  670. 'codeblock-operator', 'codeblock-string', 'emphasis', 'enumitem',
  671. 'enumlist', 'epigraph', 'epigraph-bulletitem', 'epigraph-bulletlist',
  672. 'epigraph-enumitem', 'epigraph-enumlist', 'footer',
  673. 'footnote', 'citation',
  674. 'header', 'highlights', 'highlights-bulletitem',
  675. 'highlights-bulletlist', 'highlights-enumitem', 'highlights-enumlist',
  676. 'horizontalline', 'inlineliteral', 'quotation', 'rubric',
  677. 'strong', 'table-title', 'textbody', 'tocbulletlist', 'tocenumlist',
  678. 'title',
  679. 'subtitle',
  680. 'heading1',
  681. 'heading2',
  682. 'heading3',
  683. 'heading4',
  684. 'heading5',
  685. 'heading6',
  686. 'heading7',
  687. 'admon-attention-hdr',
  688. 'admon-attention-body',
  689. 'admon-caution-hdr',
  690. 'admon-caution-body',
  691. 'admon-danger-hdr',
  692. 'admon-danger-body',
  693. 'admon-error-hdr',
  694. 'admon-error-body',
  695. 'admon-generic-hdr',
  696. 'admon-generic-body',
  697. 'admon-hint-hdr',
  698. 'admon-hint-body',
  699. 'admon-important-hdr',
  700. 'admon-important-body',
  701. 'admon-note-hdr',
  702. 'admon-note-body',
  703. 'admon-tip-hdr',
  704. 'admon-tip-body',
  705. 'admon-warning-hdr',
  706. 'admon-warning-body',
  707. 'tableoption',
  708. 'tableoption.%c', 'tableoption.%c%d', 'Table%d', 'Table%d.%c',
  709. 'Table%d.%c%d',
  710. 'lineblock1',
  711. 'lineblock2',
  712. 'lineblock3',
  713. 'lineblock4',
  714. 'lineblock5',
  715. 'lineblock6',
  716. 'image', 'figureframe',
  717. )
  718. def __init__(self, document):
  719. #nodes.SparseNodeVisitor.__init__(self, document)
  720. nodes.GenericNodeVisitor.__init__(self, document)
  721. self.settings = document.settings
  722. lcode = self.settings.language_code
  723. self.language = languages.get_language(lcode, document.reporter)
  724. self.format_map = { }
  725. if self.settings.odf_config_file:
  726. from configparser import ConfigParser
  727. parser = ConfigParser()
  728. parser.read(self.settings.odf_config_file)
  729. for rststyle, format in parser.items("Formats"):
  730. if rststyle not in self.used_styles:
  731. self.document.reporter.warning(
  732. 'Style "%s" is not a style used by odtwriter.' % (
  733. rststyle, ))
  734. self.format_map[rststyle] = format.decode('utf-8')
  735. self.section_level = 0
  736. self.section_count = 0
  737. # Create ElementTree content and styles documents.
  738. if WhichElementTree == 'lxml':
  739. root = Element(
  740. 'office:document-content',
  741. nsmap=CONTENT_NAMESPACE_DICT,
  742. )
  743. else:
  744. root = Element(
  745. 'office:document-content',
  746. attrib=CONTENT_NAMESPACE_ATTRIB,
  747. )
  748. self.content_tree = etree.ElementTree(element=root)
  749. self.current_element = root
  750. SubElement(root, 'office:scripts')
  751. SubElement(root, 'office:font-face-decls')
  752. el = SubElement(root, 'office:automatic-styles')
  753. self.automatic_styles = el
  754. el = SubElement(root, 'office:body')
  755. el = self.generate_content_element(el)
  756. self.current_element = el
  757. self.body_text_element = el
  758. self.paragraph_style_stack = [self.rststyle('textbody'), ]
  759. self.list_style_stack = []
  760. self.table_count = 0
  761. self.column_count = ord('A') - 1
  762. self.trace_level = -1
  763. self.optiontablestyles_generated = False
  764. self.field_name = None
  765. self.field_element = None
  766. self.title = None
  767. self.image_count = 0
  768. self.image_style_count = 0
  769. self.image_dict = {}
  770. self.embedded_file_list = []
  771. self.syntaxhighlighting = 1
  772. self.syntaxhighlight_lexer = 'python'
  773. self.header_content = []
  774. self.footer_content = []
  775. self.in_header = False
  776. self.in_footer = False
  777. self.blockstyle = ''
  778. self.in_table_of_contents = False
  779. self.table_of_content_index_body = None
  780. self.list_level = 0
  781. self.def_list_level = 0
  782. self.footnote_ref_dict = {}
  783. self.footnote_list = []
  784. self.footnote_chars_idx = 0
  785. self.footnote_level = 0
  786. self.pending_ids = [ ]
  787. self.in_paragraph = False
  788. self.found_doc_title = False
  789. self.bumped_list_level_stack = []
  790. self.meta_dict = {}
  791. self.line_block_level = 0
  792. self.line_indent_level = 0
  793. self.citation_id = None
  794. self.style_index = 0 # use to form unique style names
  795. self.str_stylesheet = ''
  796. self.str_stylesheetcontent = ''
  797. self.dom_stylesheet = None
  798. self.table_styles = None
  799. self.in_citation = False
  800. def get_str_stylesheet(self):
  801. return self.str_stylesheet
  802. def retrieve_styles(self, extension):
  803. """Retrieve the stylesheet from either a .xml file or from
  804. a .odt (zip) file. Return the content as a string.
  805. """
  806. s2 = None
  807. stylespath = self.settings.stylesheet
  808. ext = os.path.splitext(stylespath)[1]
  809. if ext == '.xml':
  810. stylesfile = open(stylespath, 'r')
  811. s1 = stylesfile.read()
  812. stylesfile.close()
  813. elif ext == extension:
  814. zfile = zipfile.ZipFile(stylespath, 'r')
  815. s1 = zfile.read('styles.xml')
  816. s2 = zfile.read('content.xml')
  817. zfile.close()
  818. else:
  819. raise RuntimeError('stylesheet path (%s) must be %s or .xml file' %(stylespath, extension))
  820. self.str_stylesheet = s1
  821. self.str_stylesheetcontent = s2
  822. self.dom_stylesheet = etree.fromstring(self.str_stylesheet)
  823. self.dom_stylesheetcontent = etree.fromstring(self.str_stylesheetcontent)
  824. self.table_styles = self.extract_table_styles(s2)
  825. def extract_table_styles(self, styles_str):
  826. root = etree.fromstring(styles_str)
  827. table_styles = {}
  828. auto_styles = root.find(
  829. '{%s}automatic-styles' % (CNSD['office'], ))
  830. for stylenode in auto_styles:
  831. name = stylenode.get('{%s}name' % (CNSD['style'], ))
  832. tablename = name.split('.')[0]
  833. family = stylenode.get('{%s}family' % (CNSD['style'], ))
  834. if name.startswith(TABLESTYLEPREFIX):
  835. tablestyle = table_styles.get(tablename)
  836. if tablestyle is None:
  837. tablestyle = TableStyle()
  838. table_styles[tablename] = tablestyle
  839. if family == 'table':
  840. properties = stylenode.find(
  841. '{%s}table-properties' % (CNSD['style'], ))
  842. property = properties.get('{%s}%s' % (CNSD['fo'],
  843. 'background-color', ))
  844. if property is not None and property != 'none':
  845. tablestyle.backgroundcolor = property
  846. elif family == 'table-cell':
  847. properties = stylenode.find(
  848. '{%s}table-cell-properties' % (CNSD['style'], ))
  849. if properties is not None:
  850. border = self.get_property(properties)
  851. if border is not None:
  852. tablestyle.border = border
  853. return table_styles
  854. def get_property(self, stylenode):
  855. border = None
  856. for propertyname in TABLEPROPERTYNAMES:
  857. border = stylenode.get('{%s}%s' % (CNSD['fo'], propertyname, ))
  858. if border is not None and border != 'none':
  859. return border
  860. return border
  861. def add_doc_title(self):
  862. text = self.settings.title
  863. if text:
  864. self.title = text
  865. if not self.found_doc_title:
  866. el = Element('text:p', attrib = {
  867. 'text:style-name': self.rststyle('title'),
  868. })
  869. el.text = text
  870. self.body_text_element.insert(0, el)
  871. el = self.find_first_text_p(self.body_text_element)
  872. if el is not None:
  873. self.attach_page_style(el)
  874. def find_first_text_p(self, el):
  875. """Search the generated doc and return the first <text:p> element.
  876. """
  877. if (
  878. el.tag == 'text:p' or
  879. el.tag == 'text:h'
  880. ):
  881. return el
  882. elif el.getchildren():
  883. for child in el.getchildren():
  884. el1 = self.find_first_text_p(child)
  885. if el1 is not None:
  886. return el1
  887. return None
  888. else:
  889. return None
  890. def attach_page_style(self, el):
  891. """Attach the default page style.
  892. Create an automatic-style that refers to the current style
  893. of this element and that refers to the default page style.
  894. """
  895. current_style = el.get('text:style-name')
  896. style_name = 'P1003'
  897. el1 = SubElement(
  898. self.automatic_styles, 'style:style', attrib={
  899. 'style:name': style_name,
  900. 'style:master-page-name': "rststyle-pagedefault",
  901. 'style:family': "paragraph",
  902. }, nsdict=SNSD)
  903. if current_style:
  904. el1.set('style:parent-style-name', current_style)
  905. el.set('text:style-name', style_name)
  906. def rststyle(self, name, parameters=( )):
  907. """
  908. Returns the style name to use for the given style.
  909. If `parameters` is given `name` must contain a matching number of ``%`` and
  910. is used as a format expression with `parameters` as the value.
  911. """
  912. name1 = name % parameters
  913. stylename = self.format_map.get(name1, 'rststyle-%s' % name1)
  914. return stylename
  915. def generate_content_element(self, root):
  916. return SubElement(root, 'office:text')
  917. def setup_page(self):
  918. self.setup_paper(self.dom_stylesheet)
  919. if (len(self.header_content) > 0 or len(self.footer_content) > 0 or
  920. self.settings.custom_header or self.settings.custom_footer):
  921. self.add_header_footer(self.dom_stylesheet)
  922. new_content = etree.tostring(self.dom_stylesheet)
  923. return new_content
  924. def setup_paper(self, root_el):
  925. try:
  926. fin = os.popen("paperconf -s 2> /dev/null")
  927. w, h = list(map(float, fin.read().split()))
  928. fin.close()
  929. except:
  930. w, h = 612, 792 # default to Letter
  931. def walk(el):
  932. if el.tag == "{%s}page-layout-properties" % SNSD["style"] and \
  933. "{%s}page-width" % SNSD["fo"] not in el.attrib:
  934. el.attrib["{%s}page-width" % SNSD["fo"]] = "%.3fpt" % w
  935. el.attrib["{%s}page-height" % SNSD["fo"]] = "%.3fpt" % h
  936. el.attrib["{%s}margin-left" % SNSD["fo"]] = \
  937. el.attrib["{%s}margin-right" % SNSD["fo"]] = \
  938. "%.3fpt" % (.1 * w)
  939. el.attrib["{%s}margin-top" % SNSD["fo"]] = \
  940. el.attrib["{%s}margin-bottom" % SNSD["fo"]] = \
  941. "%.3fpt" % (.1 * h)
  942. else:
  943. for subel in el.getchildren(): walk(subel)
  944. walk(root_el)
  945. def add_header_footer(self, root_el):
  946. automatic_styles = root_el.find(
  947. '{%s}automatic-styles' % SNSD['office'])
  948. path = '{%s}master-styles' % (NAME_SPACE_1, )
  949. master_el = root_el.find(path)
  950. if master_el is None:
  951. return
  952. path = '{%s}master-page' % (SNSD['style'], )
  953. master_el_container = master_el.findall(path)
  954. master_el = None
  955. target_attrib = '{%s}name' % (SNSD['style'], )
  956. target_name = self.rststyle('pagedefault')
  957. for el in master_el_container:
  958. if el.get(target_attrib) == target_name:
  959. master_el = el
  960. break
  961. if master_el is None:
  962. return
  963. el1 = master_el
  964. if self.header_content or self.settings.custom_header:
  965. if WhichElementTree == 'lxml':
  966. el2 = SubElement(el1, 'style:header', nsdict=SNSD)
  967. else:
  968. el2 = SubElement(el1, 'style:header',
  969. attrib=STYLES_NAMESPACE_ATTRIB,
  970. nsdict=STYLES_NAMESPACE_DICT,
  971. )
  972. for el in self.header_content:
  973. attrkey = add_ns('text:style-name', nsdict=SNSD)
  974. el.attrib[attrkey] = self.rststyle('header')
  975. el2.append(el)
  976. if self.settings.custom_header:
  977. elcustom = self.create_custom_headfoot(el2,
  978. self.settings.custom_header, 'header', automatic_styles)
  979. if self.footer_content or self.settings.custom_footer:
  980. if WhichElementTree == 'lxml':
  981. el2 = SubElement(el1, 'style:footer', nsdict=SNSD)
  982. else:
  983. el2 = SubElement(el1, 'style:footer',
  984. attrib=STYLES_NAMESPACE_ATTRIB,
  985. nsdict=STYLES_NAMESPACE_DICT,
  986. )
  987. for el in self.footer_content:
  988. attrkey = add_ns('text:style-name', nsdict=SNSD)
  989. el.attrib[attrkey] = self.rststyle('footer')
  990. el2.append(el)
  991. if self.settings.custom_footer:
  992. elcustom = self.create_custom_headfoot(el2,
  993. self.settings.custom_footer, 'footer', automatic_styles)
  994. code_none, code_field, code_text = list(range(3))
  995. field_pat = re.compile(r'%(..?)%')
  996. def create_custom_headfoot(self, parent, text, style_name, automatic_styles):
  997. parent = SubElement(parent, 'text:p', attrib={
  998. 'text:style-name': self.rststyle(style_name),
  999. })
  1000. current_element = None
  1001. field_iter = self.split_field_specifiers_iter(text)
  1002. for item in field_iter:
  1003. if item[0] == ODFTranslator.code_field:
  1004. if item[1] not in ('p', 'P',
  1005. 't1', 't2', 't3', 't4',
  1006. 'd1', 'd2', 'd3', 'd4', 'd5',
  1007. 's', 't', 'a'):
  1008. msg = 'bad field spec: %%%s%%' % (item[1], )
  1009. raise RuntimeError(msg)
  1010. el1 = self.make_field_element(parent,
  1011. item[1], style_name, automatic_styles)
  1012. if el1 is None:
  1013. msg = 'bad field spec: %%%s%%' % (item[1], )
  1014. raise RuntimeError(msg)
  1015. else:
  1016. current_element = el1
  1017. else:
  1018. if current_element is None:
  1019. parent.text = item[1]
  1020. else:
  1021. current_element.tail = item[1]
  1022. def make_field_element(self, parent, text, style_name, automatic_styles):
  1023. if text == 'p':
  1024. el1 = SubElement(parent, 'text:page-number', attrib={
  1025. #'text:style-name': self.rststyle(style_name),
  1026. 'text:select-page': 'current',
  1027. })
  1028. elif text == 'P':
  1029. el1 = SubElement(parent, 'text:page-count', attrib={
  1030. #'text:style-name': self.rststyle(style_name),
  1031. })
  1032. elif text == 't1':
  1033. self.style_index += 1
  1034. el1 = SubElement(parent, 'text:time', attrib={
  1035. 'text:style-name': self.rststyle(style_name),
  1036. 'text:fixed': 'true',
  1037. 'style:data-style-name': 'rst-time-style-%d' % self.style_index,
  1038. })
  1039. el2 = SubElement(automatic_styles, 'number:time-style', attrib={
  1040. 'style:name': 'rst-time-style-%d' % self.style_index,
  1041. 'xmlns:number': SNSD['number'],
  1042. 'xmlns:style': SNSD['style'],
  1043. })
  1044. el3 = SubElement(el2, 'number:hours', attrib={
  1045. 'number:style': 'long',
  1046. })
  1047. el3 = SubElement(el2, 'number:text')
  1048. el3.text = ':'
  1049. el3 = SubElement(el2, 'number:minutes', attrib={
  1050. 'number:style': 'long',
  1051. })
  1052. elif text == 't2':
  1053. self.style_index += 1
  1054. el1 = SubElement(parent, 'text:time', attrib={
  1055. 'text:style-name': self.rststyle(style_name),
  1056. 'text:fixed': 'true',
  1057. 'style:data-style-name': 'rst-time-style-%d' % self.style_index,
  1058. })
  1059. el2 = SubElement(automatic_styles, 'number:time-style', attrib={
  1060. 'style:name': 'rst-time-style-%d' % self.style_index,
  1061. 'xmlns:number': SNSD['number'],
  1062. 'xmlns:style': SNSD['style'],
  1063. })
  1064. el3 = SubElement(el2, 'number:hours', attrib={
  1065. 'number:style': 'long',
  1066. })
  1067. el3 = SubElement(el2, 'number:text')
  1068. el3.text = ':'
  1069. el3 = SubElement(el2, 'number:minutes', attrib={
  1070. 'number:style': 'long',
  1071. })
  1072. el3 = SubElement(el2, 'number:text')
  1073. el3.text = ':'
  1074. el3 = SubElement(el2, 'number:seconds', attrib={
  1075. 'number:style': 'long',
  1076. })
  1077. elif text == 't3':
  1078. self.style_index += 1
  1079. el1 = SubElement(parent, 'text:time', attrib={
  1080. 'text:style-name': self.rststyle(style_name),
  1081. 'text:fixed': 'true',
  1082. 'style:data-style-name': 'rst-time-style-%d' % self.style_index,
  1083. })
  1084. el2 = SubElement(automatic_styles, 'number:time-style', attrib={
  1085. 'style:name': 'rst-time-style-%d' % self.style_index,
  1086. 'xmlns:number': SNSD['number'],
  1087. 'xmlns:style': SNSD['style'],
  1088. })
  1089. el3 = SubElement(el2, 'number:hours', attrib={
  1090. 'number:style': 'long',
  1091. })
  1092. el3 = SubElement(el2, 'number:text')
  1093. el3.text = ':'
  1094. el3 = SubElement(el2, 'number:minutes', attrib={
  1095. 'number:style': 'long',
  1096. })
  1097. el3 = SubElement(el2, 'number:text')
  1098. el3.text = ' '
  1099. el3 = SubElement(el2, 'number:am-pm')
  1100. elif text == 't4':
  1101. self.style_index += 1
  1102. el1 = SubElement(parent, 'text:time', attrib={
  1103. 'text:style-name': self.rststyle(style_name),
  1104. 'text:fixed': 'true',
  1105. 'style:data-style-name': 'rst-time-style-%d' % self.style_index,
  1106. })
  1107. el2 = SubElement(automatic_styles, 'number:time-style', attrib={
  1108. 'style:name': 'rst-time-style-%d' % self.style_index,
  1109. 'xmlns:number': SNSD['number'],
  1110. 'xmlns:style': SNSD['style'],
  1111. })
  1112. el3 = SubElement(el2, 'number:hours', attrib={
  1113. 'number:style': 'long',
  1114. })
  1115. el3 = SubElement(el2, 'number:text')
  1116. el3.text = ':'
  1117. el3 = SubElement(el2, 'number:minutes', attrib={
  1118. 'number:style': 'long',
  1119. })
  1120. el3 = SubElement(el2, 'number:text')
  1121. el3.text = ':'
  1122. el3 = SubElement(el2, 'number:seconds', attrib={
  1123. 'number:style': 'long',
  1124. })
  1125. el3 = SubElement(el2, 'number:text')
  1126. el3.text = ' '
  1127. el3 = SubElement(el2, 'number:am-pm')
  1128. elif text == 'd1':
  1129. self.style_index += 1
  1130. el1 = SubElement(parent, 'text:date', attrib={
  1131. 'text:style-name': self.rststyle(style_name),
  1132. 'style:data-style-name': 'rst-date-style-%d' % self.style_index,
  1133. })
  1134. el2 = SubElement(automatic_styles, 'number:date-style', attrib={
  1135. 'style:name': 'rst-date-style-%d' % self.style_index,
  1136. 'number:automatic-order': 'true',
  1137. 'xmlns:number': SNSD['number'],
  1138. 'xmlns:style': SNSD['style'],
  1139. })
  1140. el3 = SubElement(el2, 'number:month', attrib={
  1141. 'number:style': 'long',
  1142. })
  1143. el3 = SubElement(el2, 'number:text')
  1144. el3.text = '/'
  1145. el3 = SubElement(el2, 'number:day', attrib={
  1146. 'number:style': 'long',
  1147. })
  1148. el3 = SubElement(el2, 'number:text')
  1149. el3.text = '/'
  1150. el3 = SubElement(el2, 'number:year')
  1151. elif text == 'd2':
  1152. self.style_index += 1
  1153. el1 = SubElement(parent, 'text:date', attrib={
  1154. 'text:style-name': self.rststyle(style_name),
  1155. 'style:data-style-name': 'rst-date-style-%d' % self.style_index,
  1156. })
  1157. el2 = SubElement(automatic_styles, 'number:date-style', attrib={
  1158. 'style:name': 'rst-date-style-%d' % self.style_index,
  1159. 'number:automatic-order': 'true',
  1160. 'xmlns:number': SNSD['number'],
  1161. 'xmlns:style': SNSD['style'],
  1162. })
  1163. el3 = SubElement(el2, 'number:month', attrib={
  1164. 'number:style': 'long',
  1165. })
  1166. el3 = SubElement(el2, 'number:text')
  1167. el3.text = '/'
  1168. el3 = SubElement(el2, 'number:day', attrib={
  1169. 'number:style': 'long',
  1170. })
  1171. el3 = SubElement(el2, 'number:text')
  1172. el3.text = '/'
  1173. el3 = SubElement(el2, 'number:year', attrib={
  1174. 'number:style': 'long',
  1175. })
  1176. elif text == 'd3':
  1177. self.style_index += 1
  1178. el1 = SubElement(parent, 'text:date', attrib={
  1179. 'text:style-name': self.rststyle(style_name),
  1180. 'style:data-style-name': 'rst-date-style-%d' % self.style_index,
  1181. })
  1182. el2 = SubElement(automatic_styles, 'number…

Large files files are truncated, but you can click here to view the full file