Website: add template and basic stylesheet with menu. Update buildhtml.py (and theref...
[docutils.git] / sandbox / gitpull / web_stylesheet_and_menu / docutils / writers / odf_odt / __init__.py
blobeba6361d17cce7493b246ba5c26b32b8a3a87639
1 # $Id$
2 # Author: Dave Kuhlman <dkuhlman@rexx.com>
3 # Copyright: This module has been placed in the public domain.
5 """
6 Open Document Format (ODF) Writer.
8 """
10 VERSION = '1.0a'
12 __docformat__ = 'reStructuredText'
15 import sys
16 import os
17 import os.path
18 import tempfile
19 import zipfile
20 from xml.dom import minidom
21 import time
22 import re
23 import StringIO
24 import copy
25 import urllib2
26 import docutils
27 from docutils import frontend, nodes, utils, writers, languages
28 from docutils.readers import standalone
29 from docutils.transforms import references
32 WhichElementTree = ''
33 try:
34 # 1. Try to use lxml.
35 #from lxml import etree
36 #WhichElementTree = 'lxml'
37 raise ImportError('Ignoring lxml')
38 except ImportError, e:
39 try:
40 # 2. Try to use ElementTree from the Python standard library.
41 from xml.etree import ElementTree as etree
42 WhichElementTree = 'elementtree'
43 except ImportError, e:
44 try:
45 # 3. Try to use a version of ElementTree installed as a separate
46 # product.
47 from elementtree import ElementTree as etree
48 WhichElementTree = 'elementtree'
49 except ImportError, e:
50 s1 = 'Must install either a version of Python containing ' \
51 'ElementTree (Python version >=2.5) or install ElementTree.'
52 raise ImportError(s1)
55 # Import pygments and odtwriter pygments formatters if possible.
56 try:
57 import pygments
58 import pygments.lexers
59 from pygmentsformatter import OdtPygmentsProgFormatter, \
60 OdtPygmentsLaTeXFormatter
61 except ImportError, exp:
62 pygments = None
64 # check for the Python Imaging Library
65 try:
66 import PIL.Image
67 except ImportError:
68 try: # sometimes PIL modules are put in PYTHONPATH's root
69 import Image
70 class PIL(object): pass # dummy wrapper
71 PIL.Image = Image
72 except ImportError:
73 PIL = None
75 ## import warnings
76 ## warnings.warn('importing IPShellEmbed', UserWarning)
77 ## from IPython.Shell import IPShellEmbed
78 ## args = ['-pdb', '-pi1', 'In <\\#>: ', '-pi2', ' .\\D.: ',
79 ## '-po', 'Out<\\#>: ', '-nosep']
80 ## ipshell = IPShellEmbed(args,
81 ## banner = 'Entering IPython. Press Ctrl-D to exit.',
82 ## exit_msg = 'Leaving Interpreter, back to program.')
86 # ElementTree does not support getparent method (lxml does).
87 # This wrapper class and the following support functions provide
88 # that support for the ability to get the parent of an element.
90 if WhichElementTree == 'elementtree':
91 import weakref
92 _parents = weakref.WeakKeyDictionary()
93 if isinstance(etree.Element, type):
94 _ElementInterface = etree.Element
95 else:
96 _ElementInterface = etree._ElementInterface
97 class _ElementInterfaceWrapper(_ElementInterface):
98 def __init__(self, tag, attrib=None):
99 _ElementInterface.__init__(self, tag, attrib)
100 _parents[self] = None
101 def setparent(self, parent):
102 _parents[self] = parent
103 def getparent(self):
104 return _parents[self]
108 # Constants and globals
110 SPACES_PATTERN = re.compile(r'( +)')
111 TABS_PATTERN = re.compile(r'(\t+)')
112 FILL_PAT1 = re.compile(r'^ +')
113 FILL_PAT2 = re.compile(r' {2,}')
115 TABLESTYLEPREFIX = 'rststyle-table-'
116 TABLENAMEDEFAULT = '%s0' % TABLESTYLEPREFIX
117 TABLEPROPERTYNAMES = ('border', 'border-top', 'border-left',
118 'border-right', 'border-bottom', )
120 GENERATOR_DESC = 'Docutils.org/odf_odt'
122 NAME_SPACE_1 = 'urn:oasis:names:tc:opendocument:xmlns:office:1.0'
124 CONTENT_NAMESPACE_DICT = CNSD = {
125 # 'office:version': '1.0',
126 'chart': 'urn:oasis:names:tc:opendocument:xmlns:chart:1.0',
127 'dc': 'http://purl.org/dc/elements/1.1/',
128 'dom': 'http://www.w3.org/2001/xml-events',
129 'dr3d': 'urn:oasis:names:tc:opendocument:xmlns:dr3d:1.0',
130 'draw': 'urn:oasis:names:tc:opendocument:xmlns:drawing:1.0',
131 'fo': 'urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0',
132 'form': 'urn:oasis:names:tc:opendocument:xmlns:form:1.0',
133 'math': 'http://www.w3.org/1998/Math/MathML',
134 'meta': 'urn:oasis:names:tc:opendocument:xmlns:meta:1.0',
135 'number': 'urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0',
136 'office': NAME_SPACE_1,
137 'ooo': 'http://openoffice.org/2004/office',
138 'oooc': 'http://openoffice.org/2004/calc',
139 'ooow': 'http://openoffice.org/2004/writer',
140 'presentation': 'urn:oasis:names:tc:opendocument:xmlns:presentation:1.0',
142 'script': 'urn:oasis:names:tc:opendocument:xmlns:script:1.0',
143 'style': 'urn:oasis:names:tc:opendocument:xmlns:style:1.0',
144 'svg': 'urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0',
145 'table': 'urn:oasis:names:tc:opendocument:xmlns:table:1.0',
146 'text': 'urn:oasis:names:tc:opendocument:xmlns:text:1.0',
147 'xforms': 'http://www.w3.org/2002/xforms',
148 'xlink': 'http://www.w3.org/1999/xlink',
149 'xsd': 'http://www.w3.org/2001/XMLSchema',
150 'xsi': 'http://www.w3.org/2001/XMLSchema-instance',
153 STYLES_NAMESPACE_DICT = SNSD = {
154 # 'office:version': '1.0',
155 'chart': 'urn:oasis:names:tc:opendocument:xmlns:chart:1.0',
156 'dc': 'http://purl.org/dc/elements/1.1/',
157 'dom': 'http://www.w3.org/2001/xml-events',
158 'dr3d': 'urn:oasis:names:tc:opendocument:xmlns:dr3d:1.0',
159 'draw': 'urn:oasis:names:tc:opendocument:xmlns:drawing:1.0',
160 'fo': 'urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0',
161 'form': 'urn:oasis:names:tc:opendocument:xmlns:form:1.0',
162 'math': 'http://www.w3.org/1998/Math/MathML',
163 'meta': 'urn:oasis:names:tc:opendocument:xmlns:meta:1.0',
164 'number': 'urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0',
165 'office': NAME_SPACE_1,
166 'presentation': 'urn:oasis:names:tc:opendocument:xmlns:presentation:1.0',
167 'ooo': 'http://openoffice.org/2004/office',
168 'oooc': 'http://openoffice.org/2004/calc',
169 'ooow': 'http://openoffice.org/2004/writer',
170 'script': 'urn:oasis:names:tc:opendocument:xmlns:script:1.0',
171 'style': 'urn:oasis:names:tc:opendocument:xmlns:style:1.0',
172 'svg': 'urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0',
173 'table': 'urn:oasis:names:tc:opendocument:xmlns:table:1.0',
174 'text': 'urn:oasis:names:tc:opendocument:xmlns:text:1.0',
175 'xlink': 'http://www.w3.org/1999/xlink',
178 MANIFEST_NAMESPACE_DICT = MANNSD = {
179 'manifest': 'urn:oasis:names:tc:opendocument:xmlns:manifest:1.0',
182 META_NAMESPACE_DICT = METNSD = {
183 # 'office:version': '1.0',
184 'dc': 'http://purl.org/dc/elements/1.1/',
185 'meta': 'urn:oasis:names:tc:opendocument:xmlns:meta:1.0',
186 'office': NAME_SPACE_1,
187 'ooo': 'http://openoffice.org/2004/office',
188 'xlink': 'http://www.w3.org/1999/xlink',
192 # Attribute dictionaries for use with ElementTree (not lxml), which
193 # does not support use of nsmap parameter on Element() and SubElement().
195 CONTENT_NAMESPACE_ATTRIB = {
196 #'office:version': '1.0',
197 'xmlns:chart': 'urn:oasis:names:tc:opendocument:xmlns:chart:1.0',
198 'xmlns:dc': 'http://purl.org/dc/elements/1.1/',
199 'xmlns:dom': 'http://www.w3.org/2001/xml-events',
200 'xmlns:dr3d': 'urn:oasis:names:tc:opendocument:xmlns:dr3d:1.0',
201 'xmlns:draw': 'urn:oasis:names:tc:opendocument:xmlns:drawing:1.0',
202 'xmlns:fo': 'urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0',
203 'xmlns:form': 'urn:oasis:names:tc:opendocument:xmlns:form:1.0',
204 'xmlns:math': 'http://www.w3.org/1998/Math/MathML',
205 'xmlns:meta': 'urn:oasis:names:tc:opendocument:xmlns:meta:1.0',
206 'xmlns:number': 'urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0',
207 'xmlns:office': NAME_SPACE_1,
208 'xmlns:presentation': 'urn:oasis:names:tc:opendocument:xmlns:presentation:1.0',
209 'xmlns:ooo': 'http://openoffice.org/2004/office',
210 'xmlns:oooc': 'http://openoffice.org/2004/calc',
211 'xmlns:ooow': 'http://openoffice.org/2004/writer',
212 'xmlns:script': 'urn:oasis:names:tc:opendocument:xmlns:script:1.0',
213 'xmlns:style': 'urn:oasis:names:tc:opendocument:xmlns:style:1.0',
214 'xmlns:svg': 'urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0',
215 'xmlns:table': 'urn:oasis:names:tc:opendocument:xmlns:table:1.0',
216 'xmlns:text': 'urn:oasis:names:tc:opendocument:xmlns:text:1.0',
217 'xmlns:xforms': 'http://www.w3.org/2002/xforms',
218 'xmlns:xlink': 'http://www.w3.org/1999/xlink',
219 'xmlns:xsd': 'http://www.w3.org/2001/XMLSchema',
220 'xmlns:xsi': 'http://www.w3.org/2001/XMLSchema-instance',
223 STYLES_NAMESPACE_ATTRIB = {
224 #'office:version': '1.0',
225 'xmlns:chart': 'urn:oasis:names:tc:opendocument:xmlns:chart:1.0',
226 'xmlns:dc': 'http://purl.org/dc/elements/1.1/',
227 'xmlns:dom': 'http://www.w3.org/2001/xml-events',
228 'xmlns:dr3d': 'urn:oasis:names:tc:opendocument:xmlns:dr3d:1.0',
229 'xmlns:draw': 'urn:oasis:names:tc:opendocument:xmlns:drawing:1.0',
230 'xmlns:fo': 'urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0',
231 'xmlns:form': 'urn:oasis:names:tc:opendocument:xmlns:form:1.0',
232 'xmlns:math': 'http://www.w3.org/1998/Math/MathML',
233 'xmlns:meta': 'urn:oasis:names:tc:opendocument:xmlns:meta:1.0',
234 'xmlns:number': 'urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0',
235 'xmlns:office': NAME_SPACE_1,
236 'xmlns:presentation': 'urn:oasis:names:tc:opendocument:xmlns:presentation:1.0',
237 'xmlns:ooo': 'http://openoffice.org/2004/office',
238 'xmlns:oooc': 'http://openoffice.org/2004/calc',
239 'xmlns:ooow': 'http://openoffice.org/2004/writer',
240 'xmlns:script': 'urn:oasis:names:tc:opendocument:xmlns:script:1.0',
241 'xmlns:style': 'urn:oasis:names:tc:opendocument:xmlns:style:1.0',
242 'xmlns:svg': 'urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0',
243 'xmlns:table': 'urn:oasis:names:tc:opendocument:xmlns:table:1.0',
244 'xmlns:text': 'urn:oasis:names:tc:opendocument:xmlns:text:1.0',
245 'xmlns:xlink': 'http://www.w3.org/1999/xlink',
248 MANIFEST_NAMESPACE_ATTRIB = {
249 'xmlns:manifest': 'urn:oasis:names:tc:opendocument:xmlns:manifest:1.0',
252 META_NAMESPACE_ATTRIB = {
253 #'office:version': '1.0',
254 'xmlns:dc': 'http://purl.org/dc/elements/1.1/',
255 'xmlns:meta': 'urn:oasis:names:tc:opendocument:xmlns:meta:1.0',
256 'xmlns:office': NAME_SPACE_1,
257 'xmlns:ooo': 'http://openoffice.org/2004/office',
258 'xmlns:xlink': 'http://www.w3.org/1999/xlink',
263 # Functions
267 # ElementTree support functions.
268 # In order to be able to get the parent of elements, must use these
269 # instead of the functions with same name provided by ElementTree.
271 def Element(tag, attrib=None, nsmap=None, nsdict=CNSD):
272 if attrib is None:
273 attrib = {}
274 tag, attrib = fix_ns(tag, attrib, nsdict)
275 if WhichElementTree == 'lxml':
276 el = etree.Element(tag, attrib, nsmap=nsmap)
277 else:
278 el = _ElementInterfaceWrapper(tag, attrib)
279 return el
281 def SubElement(parent, tag, attrib=None, nsmap=None, nsdict=CNSD):
282 if attrib is None:
283 attrib = {}
284 tag, attrib = fix_ns(tag, attrib, nsdict)
285 if WhichElementTree == 'lxml':
286 el = etree.SubElement(parent, tag, attrib, nsmap=nsmap)
287 else:
288 el = _ElementInterfaceWrapper(tag, attrib)
289 parent.append(el)
290 el.setparent(parent)
291 return el
293 def fix_ns(tag, attrib, nsdict):
294 nstag = add_ns(tag, nsdict)
295 nsattrib = {}
296 for key, val in attrib.iteritems():
297 nskey = add_ns(key, nsdict)
298 nsattrib[nskey] = val
299 return nstag, nsattrib
301 def add_ns(tag, nsdict=CNSD):
302 if WhichElementTree == 'lxml':
303 nstag, name = tag.split(':')
304 ns = nsdict.get(nstag)
305 if ns is None:
306 raise RuntimeError, 'Invalid namespace prefix: %s' % nstag
307 tag = '{%s}%s' % (ns, name,)
308 return tag
310 def ToString(et):
311 outstream = StringIO.StringIO()
312 if sys.version_info >= (3, 2):
313 et.write(outstream, encoding="unicode")
314 else:
315 et.write(outstream)
316 s1 = outstream.getvalue()
317 outstream.close()
318 return s1
321 def escape_cdata(text):
322 text = text.replace("&", "&amp;")
323 text = text.replace("<", "&lt;")
324 text = text.replace(">", "&gt;")
325 ascii = ''
326 for char in text:
327 if ord(char) >= ord("\x7f"):
328 ascii += "&#x%X;" % ( ord(char), )
329 else:
330 ascii += char
331 return ascii
335 WORD_SPLIT_PAT1 = re.compile(r'\b(\w*)\b\W*')
337 def split_words(line):
338 # We need whitespace at the end of the string for our regexpr.
339 line += ' '
340 words = []
341 pos1 = 0
342 mo = WORD_SPLIT_PAT1.search(line, pos1)
343 while mo is not None:
344 word = mo.groups()[0]
345 words.append(word)
346 pos1 = mo.end()
347 mo = WORD_SPLIT_PAT1.search(line, pos1)
348 return words
352 # Classes
356 class TableStyle(object):
357 def __init__(self, border=None, backgroundcolor=None):
358 self.border = border
359 self.backgroundcolor = backgroundcolor
360 def get_border_(self):
361 return self.border_
362 def set_border_(self, border):
363 self.border_ = border
364 border = property(get_border_, set_border_)
365 def get_backgroundcolor_(self):
366 return self.backgroundcolor_
367 def set_backgroundcolor_(self, backgroundcolor):
368 self.backgroundcolor_ = backgroundcolor
369 backgroundcolor = property(get_backgroundcolor_, set_backgroundcolor_)
371 BUILTIN_DEFAULT_TABLE_STYLE = TableStyle(
372 border = '0.0007in solid #000000')
375 # Information about the indentation level for lists nested inside
376 # other contexts, e.g. dictionary lists.
377 class ListLevel(object):
378 def __init__(self, level, sibling_level=True, nested_level=True):
379 self.level = level
380 self.sibling_level = sibling_level
381 self.nested_level = nested_level
382 def set_sibling(self, sibling_level): self.sibling_level = sibling_level
383 def get_sibling(self): return self.sibling_level
384 def set_nested(self, nested_level): self.nested_level = nested_level
385 def get_nested(self): return self.nested_level
386 def set_level(self, level): self.level = level
387 def get_level(self): return self.level
390 class Writer(writers.Writer):
392 MIME_TYPE = 'application/vnd.oasis.opendocument.text'
393 EXTENSION = '.odt'
395 supported = ('odt', )
396 """Formats this writer supports."""
398 default_stylesheet = 'styles' + EXTENSION
400 default_stylesheet_path = utils.relative_path(
401 os.path.join(os.getcwd(), 'dummy'),
402 os.path.join(os.path.dirname(__file__), default_stylesheet))
404 default_template = 'template.txt'
406 default_template_path = utils.relative_path(
407 os.path.join(os.getcwd(), 'dummy'),
408 os.path.join(os.path.dirname(__file__), default_template))
410 settings_spec = (
411 'ODF-Specific Options',
412 None,
414 ('Specify a stylesheet. '
415 'Default: "%s"' % default_stylesheet_path,
416 ['--stylesheet'],
418 'default': default_stylesheet_path,
419 'dest': 'stylesheet'
421 ('Specify a configuration/mapping file relative to the '
422 'current working '
423 'directory for additional ODF options. '
424 'In particular, this file may contain a section named '
425 '"Formats" that maps default style names to '
426 'names to be used in the resulting output file allowing for '
427 'adhering to external standards. '
428 'For more info and the format of the configuration/mapping file, '
429 'see the odtwriter doc.',
430 ['--odf-config-file'],
431 {'metavar': '<file>'}),
432 ('Obfuscate email addresses to confuse harvesters while still '
433 'keeping email links usable with standards-compliant browsers.',
434 ['--cloak-email-addresses'],
435 {'default': False,
436 'action': 'store_true',
437 'dest': 'cloak_email_addresses',
438 'validator': frontend.validate_boolean}),
439 ('Do not obfuscate email addresses.',
440 ['--no-cloak-email-addresses'],
441 {'default': False,
442 'action': 'store_false',
443 'dest': 'cloak_email_addresses',
444 'validator': frontend.validate_boolean}),
445 ('Specify the thickness of table borders in thousands of a cm. '
446 'Default is 35.',
447 ['--table-border-thickness'],
448 {'default': None,
449 'validator': frontend.validate_nonnegative_int}),
450 ('Add syntax highlighting in literal code blocks.',
451 ['--add-syntax-highlighting'],
452 {'default': False,
453 'action': 'store_true',
454 'dest': 'add_syntax_highlighting',
455 'validator': frontend.validate_boolean}),
456 ('Do not add syntax highlighting in literal code blocks. (default)',
457 ['--no-syntax-highlighting'],
458 {'default': False,
459 'action': 'store_false',
460 'dest': 'add_syntax_highlighting',
461 'validator': frontend.validate_boolean}),
462 ('Create sections for headers. (default)',
463 ['--create-sections'],
464 {'default': True,
465 'action': 'store_true',
466 'dest': 'create_sections',
467 'validator': frontend.validate_boolean}),
468 ('Do not create sections for headers.',
469 ['--no-sections'],
470 {'default': True,
471 'action': 'store_false',
472 'dest': 'create_sections',
473 'validator': frontend.validate_boolean}),
474 ('Create links.',
475 ['--create-links'],
476 {'default': False,
477 'action': 'store_true',
478 'dest': 'create_links',
479 'validator': frontend.validate_boolean}),
480 ('Do not create links. (default)',
481 ['--no-links'],
482 {'default': False,
483 'action': 'store_false',
484 'dest': 'create_links',
485 'validator': frontend.validate_boolean}),
486 ('Generate endnotes at end of document, not footnotes '
487 'at bottom of page.',
488 ['--endnotes-end-doc'],
489 {'default': False,
490 'action': 'store_true',
491 'dest': 'endnotes_end_doc',
492 'validator': frontend.validate_boolean}),
493 ('Generate footnotes at bottom of page, not endnotes '
494 'at end of document. (default)',
495 ['--no-endnotes-end-doc'],
496 {'default': False,
497 'action': 'store_false',
498 'dest': 'endnotes_end_doc',
499 'validator': frontend.validate_boolean}),
500 ('Generate a bullet list table of contents, not '
501 'an ODF/oowriter table of contents.',
502 ['--generate-list-toc'],
503 {'default': True,
504 'action': 'store_false',
505 'dest': 'generate_oowriter_toc',
506 'validator': frontend.validate_boolean}),
507 ('Generate an ODF/oowriter table of contents, not '
508 'a bullet list. (default)',
509 ['--generate-oowriter-toc'],
510 {'default': True,
511 'action': 'store_true',
512 'dest': 'generate_oowriter_toc',
513 'validator': frontend.validate_boolean}),
514 ('Specify the contents of an custom header line. '
515 'See odf_odt writer documentation for details '
516 'about special field character sequences.',
517 ['--custom-odt-header'],
518 { 'default': '',
519 'dest': 'custom_header',
521 ('Specify the contents of an custom footer line. '
522 'See odf_odt writer documentation for details '
523 'about special field character sequences.',
524 ['--custom-odt-footer'],
525 { 'default': '',
526 'dest': 'custom_footer',
531 settings_defaults = {
532 'output_encoding_error_handler': 'xmlcharrefreplace',
535 relative_path_settings = (
536 'stylesheet_path',
539 config_section = 'odf_odt writer'
540 config_section_dependencies = (
541 'writers',
544 def __init__(self):
545 writers.Writer.__init__(self)
546 self.translator_class = ODFTranslator
548 def translate(self):
549 self.settings = self.document.settings
550 self.visitor = self.translator_class(self.document)
551 self.visitor.retrieve_styles(self.EXTENSION)
552 self.document.walkabout(self.visitor)
553 self.visitor.add_doc_title()
554 self.assemble_my_parts()
555 self.output = self.parts['whole']
557 def assemble_my_parts(self):
558 """Assemble the `self.parts` dictionary. Extend in subclasses.
560 writers.Writer.assemble_parts(self)
561 f = tempfile.NamedTemporaryFile()
562 zfile = zipfile.ZipFile(f, 'w', zipfile.ZIP_DEFLATED)
563 self.write_zip_str(zfile, 'mimetype', self.MIME_TYPE,
564 compress_type=zipfile.ZIP_STORED)
565 content = self.visitor.content_astext()
566 self.write_zip_str(zfile, 'content.xml', content)
567 s1 = self.create_manifest()
568 self.write_zip_str(zfile, 'META-INF/manifest.xml', s1)
569 s1 = self.create_meta()
570 self.write_zip_str(zfile, 'meta.xml', s1)
571 s1 = self.get_stylesheet()
572 self.write_zip_str(zfile, 'styles.xml', s1)
573 self.store_embedded_files(zfile)
574 self.copy_from_stylesheet(zfile)
575 zfile.close()
576 f.seek(0)
577 whole = f.read()
578 f.close()
579 self.parts['whole'] = whole
580 self.parts['encoding'] = self.document.settings.output_encoding
581 self.parts['version'] = docutils.__version__
583 def write_zip_str(self, zfile, name, bytes, compress_type=zipfile.ZIP_DEFLATED):
584 localtime = time.localtime(time.time())
585 zinfo = zipfile.ZipInfo(name, localtime)
586 # Add some standard UNIX file access permissions (-rw-r--r--).
587 zinfo.external_attr = (0x81a4 & 0xFFFF) << 16L
588 zinfo.compress_type = compress_type
589 zfile.writestr(zinfo, bytes)
591 def store_embedded_files(self, zfile):
592 embedded_files = self.visitor.get_embedded_file_list()
593 for source, destination in embedded_files:
594 if source is None:
595 continue
596 try:
597 zfile.write(source, destination)
598 except OSError, e:
599 self.document.reporter.warning(
600 "Can't open file %s." % (source, ))
602 def get_settings(self):
604 modeled after get_stylesheet
606 stylespath = self.settings.stylesheet
607 zfile = zipfile.ZipFile(stylespath, 'r')
608 s1 = zfile.read('settings.xml')
609 zfile.close()
610 return s1
612 def get_stylesheet(self):
613 """Get the stylesheet from the visitor.
614 Ask the visitor to setup the page.
616 s1 = self.visitor.setup_page()
617 return s1
619 def copy_from_stylesheet(self, outzipfile):
620 """Copy images, settings, etc from the stylesheet doc into target doc.
622 stylespath = self.settings.stylesheet
623 inzipfile = zipfile.ZipFile(stylespath, 'r')
624 # Copy the styles.
625 s1 = inzipfile.read('settings.xml')
626 self.write_zip_str(outzipfile, 'settings.xml', s1)
627 # Copy the images.
628 namelist = inzipfile.namelist()
629 for name in namelist:
630 if name.startswith('Pictures/'):
631 imageobj = inzipfile.read(name)
632 outzipfile.writestr(name, imageobj)
633 inzipfile.close()
635 def assemble_parts(self):
636 pass
638 def create_manifest(self):
639 if WhichElementTree == 'lxml':
640 root = Element('manifest:manifest',
641 nsmap=MANIFEST_NAMESPACE_DICT,
642 nsdict=MANIFEST_NAMESPACE_DICT,
644 else:
645 root = Element('manifest:manifest',
646 attrib=MANIFEST_NAMESPACE_ATTRIB,
647 nsdict=MANIFEST_NAMESPACE_DICT,
649 doc = etree.ElementTree(root)
650 SubElement(root, 'manifest:file-entry', attrib={
651 'manifest:media-type': self.MIME_TYPE,
652 'manifest:full-path': '/',
653 }, nsdict=MANNSD)
654 SubElement(root, 'manifest:file-entry', attrib={
655 'manifest:media-type': 'text/xml',
656 'manifest:full-path': 'content.xml',
657 }, nsdict=MANNSD)
658 SubElement(root, 'manifest:file-entry', attrib={
659 'manifest:media-type': 'text/xml',
660 'manifest:full-path': 'styles.xml',
661 }, nsdict=MANNSD)
662 SubElement(root, 'manifest:file-entry', attrib={
663 'manifest:media-type': 'text/xml',
664 'manifest:full-path': 'settings.xml',
665 }, nsdict=MANNSD)
666 SubElement(root, 'manifest:file-entry', attrib={
667 'manifest:media-type': 'text/xml',
668 'manifest:full-path': 'meta.xml',
669 }, nsdict=MANNSD)
670 s1 = ToString(doc)
671 doc = minidom.parseString(s1)
672 s1 = doc.toprettyxml(' ')
673 return s1
675 def create_meta(self):
676 if WhichElementTree == 'lxml':
677 root = Element('office:document-meta',
678 nsmap=META_NAMESPACE_DICT,
679 nsdict=META_NAMESPACE_DICT,
681 else:
682 root = Element('office:document-meta',
683 attrib=META_NAMESPACE_ATTRIB,
684 nsdict=META_NAMESPACE_DICT,
686 doc = etree.ElementTree(root)
687 root = SubElement(root, 'office:meta', nsdict=METNSD)
688 el1 = SubElement(root, 'meta:generator', nsdict=METNSD)
689 el1.text = 'Docutils/rst2odf.py/%s' % (VERSION, )
690 s1 = os.environ.get('USER', '')
691 el1 = SubElement(root, 'meta:initial-creator', nsdict=METNSD)
692 el1.text = s1
693 s2 = time.strftime('%Y-%m-%dT%H:%M:%S', time.localtime())
694 el1 = SubElement(root, 'meta:creation-date', nsdict=METNSD)
695 el1.text = s2
696 el1 = SubElement(root, 'dc:creator', nsdict=METNSD)
697 el1.text = s1
698 el1 = SubElement(root, 'dc:date', nsdict=METNSD)
699 el1.text = s2
700 el1 = SubElement(root, 'dc:language', nsdict=METNSD)
701 el1.text = 'en-US'
702 el1 = SubElement(root, 'meta:editing-cycles', nsdict=METNSD)
703 el1.text = '1'
704 el1 = SubElement(root, 'meta:editing-duration', nsdict=METNSD)
705 el1.text = 'PT00M01S'
706 title = self.visitor.get_title()
707 el1 = SubElement(root, 'dc:title', nsdict=METNSD)
708 if title:
709 el1.text = title
710 else:
711 el1.text = '[no title]'
712 meta_dict = self.visitor.get_meta_dict()
713 keywordstr = meta_dict.get('keywords')
714 if keywordstr is not None:
715 keywords = split_words(keywordstr)
716 for keyword in keywords:
717 el1 = SubElement(root, 'meta:keyword', nsdict=METNSD)
718 el1.text = keyword
719 description = meta_dict.get('description')
720 if description is not None:
721 el1 = SubElement(root, 'dc:description', nsdict=METNSD)
722 el1.text = description
723 s1 = ToString(doc)
724 #doc = minidom.parseString(s1)
725 #s1 = doc.toprettyxml(' ')
726 return s1
728 # class ODFTranslator(nodes.SparseNodeVisitor):
730 class ODFTranslator(nodes.GenericNodeVisitor):
732 used_styles = (
733 'attribution', 'blockindent', 'blockquote', 'blockquote-bulletitem',
734 'blockquote-bulletlist', 'blockquote-enumitem', 'blockquote-enumlist',
735 'bulletitem', 'bulletlist',
736 'caption', 'legend',
737 'centeredtextbody', 'codeblock', 'codeblock-indented',
738 'codeblock-classname', 'codeblock-comment', 'codeblock-functionname',
739 'codeblock-keyword', 'codeblock-name', 'codeblock-number',
740 'codeblock-operator', 'codeblock-string', 'emphasis', 'enumitem',
741 'enumlist', 'epigraph', 'epigraph-bulletitem', 'epigraph-bulletlist',
742 'epigraph-enumitem', 'epigraph-enumlist', 'footer',
743 'footnote', 'citation',
744 'header', 'highlights', 'highlights-bulletitem',
745 'highlights-bulletlist', 'highlights-enumitem', 'highlights-enumlist',
746 'horizontalline', 'inlineliteral', 'quotation', 'rubric',
747 'strong', 'table-title', 'textbody', 'tocbulletlist', 'tocenumlist',
748 'title',
749 'subtitle',
750 'heading1',
751 'heading2',
752 'heading3',
753 'heading4',
754 'heading5',
755 'heading6',
756 'heading7',
757 'admon-attention-hdr',
758 'admon-attention-body',
759 'admon-caution-hdr',
760 'admon-caution-body',
761 'admon-danger-hdr',
762 'admon-danger-body',
763 'admon-error-hdr',
764 'admon-error-body',
765 'admon-generic-hdr',
766 'admon-generic-body',
767 'admon-hint-hdr',
768 'admon-hint-body',
769 'admon-important-hdr',
770 'admon-important-body',
771 'admon-note-hdr',
772 'admon-note-body',
773 'admon-tip-hdr',
774 'admon-tip-body',
775 'admon-warning-hdr',
776 'admon-warning-body',
777 'tableoption',
778 'tableoption.%c', 'tableoption.%c%d', 'Table%d', 'Table%d.%c',
779 'Table%d.%c%d',
780 'lineblock1',
781 'lineblock2',
782 'lineblock3',
783 'lineblock4',
784 'lineblock5',
785 'lineblock6',
786 'image', 'figureframe',
789 def __init__(self, document):
790 #nodes.SparseNodeVisitor.__init__(self, document)
791 nodes.GenericNodeVisitor.__init__(self, document)
792 self.settings = document.settings
793 lcode = self.settings.language_code
794 self.language = languages.get_language(lcode, document.reporter)
795 self.format_map = { }
796 if self.settings.odf_config_file:
797 from ConfigParser import ConfigParser
799 parser = ConfigParser()
800 parser.read(self.settings.odf_config_file)
801 for rststyle, format in parser.items("Formats"):
802 if rststyle not in self.used_styles:
803 self.document.reporter.warning(
804 'Style "%s" is not a style used by odtwriter.' % (
805 rststyle, ))
806 self.format_map[rststyle] = format.decode('utf-8')
807 self.section_level = 0
808 self.section_count = 0
809 # Create ElementTree content and styles documents.
810 if WhichElementTree == 'lxml':
811 root = Element(
812 'office:document-content',
813 nsmap=CONTENT_NAMESPACE_DICT,
815 else:
816 root = Element(
817 'office:document-content',
818 attrib=CONTENT_NAMESPACE_ATTRIB,
820 self.content_tree = etree.ElementTree(element=root)
821 self.current_element = root
822 SubElement(root, 'office:scripts')
823 SubElement(root, 'office:font-face-decls')
824 el = SubElement(root, 'office:automatic-styles')
825 self.automatic_styles = el
826 el = SubElement(root, 'office:body')
827 el = self.generate_content_element(el)
828 self.current_element = el
829 self.body_text_element = el
830 self.paragraph_style_stack = [self.rststyle('textbody'), ]
831 self.list_style_stack = []
832 self.table_count = 0
833 self.column_count = ord('A') - 1
834 self.trace_level = -1
835 self.optiontablestyles_generated = False
836 self.field_name = None
837 self.field_element = None
838 self.title = None
839 self.image_count = 0
840 self.image_style_count = 0
841 self.image_dict = {}
842 self.embedded_file_list = []
843 self.syntaxhighlighting = 1
844 self.syntaxhighlight_lexer = 'python'
845 self.header_content = []
846 self.footer_content = []
847 self.in_header = False
848 self.in_footer = False
849 self.blockstyle = ''
850 self.in_table_of_contents = False
851 self.table_of_content_index_body = None
852 self.list_level = 0
853 self.def_list_level = 0
854 self.footnote_ref_dict = {}
855 self.footnote_list = []
856 self.footnote_chars_idx = 0
857 self.footnote_level = 0
858 self.pending_ids = [ ]
859 self.in_paragraph = False
860 self.found_doc_title = False
861 self.bumped_list_level_stack = []
862 self.meta_dict = {}
863 self.line_block_level = 0
864 self.line_indent_level = 0
865 self.citation_id = None
866 self.style_index = 0 # use to form unique style names
867 self.str_stylesheet = ''
868 self.str_stylesheetcontent = ''
869 self.dom_stylesheet = None
870 self.table_styles = None
871 self.in_citation = False
874 def get_str_stylesheet(self):
875 return self.str_stylesheet
877 def retrieve_styles(self, extension):
878 """Retrieve the stylesheet from either a .xml file or from
879 a .odt (zip) file. Return the content as a string.
881 s2 = None
882 stylespath = self.settings.stylesheet
883 ext = os.path.splitext(stylespath)[1]
884 if ext == '.xml':
885 stylesfile = open(stylespath, 'r')
886 s1 = stylesfile.read()
887 stylesfile.close()
888 elif ext == extension:
889 zfile = zipfile.ZipFile(stylespath, 'r')
890 s1 = zfile.read('styles.xml')
891 s2 = zfile.read('content.xml')
892 zfile.close()
893 else:
894 raise RuntimeError, 'stylesheet path (%s) must be %s or .xml file' %(stylespath, extension)
895 self.str_stylesheet = s1
896 self.str_stylesheetcontent = s2
897 self.dom_stylesheet = etree.fromstring(self.str_stylesheet)
898 self.dom_stylesheetcontent = etree.fromstring(self.str_stylesheetcontent)
899 self.table_styles = self.extract_table_styles(s2)
901 def extract_table_styles(self, styles_str):
902 root = etree.fromstring(styles_str)
903 table_styles = {}
904 auto_styles = root.find(
905 '{%s}automatic-styles' % (CNSD['office'], ))
906 for stylenode in auto_styles:
907 name = stylenode.get('{%s}name' % (CNSD['style'], ))
908 tablename = name.split('.')[0]
909 family = stylenode.get('{%s}family' % (CNSD['style'], ))
910 if name.startswith(TABLESTYLEPREFIX):
911 tablestyle = table_styles.get(tablename)
912 if tablestyle is None:
913 tablestyle = TableStyle()
914 table_styles[tablename] = tablestyle
915 if family == 'table':
916 properties = stylenode.find(
917 '{%s}table-properties' % (CNSD['style'], ))
918 property = properties.get('{%s}%s' % (CNSD['fo'],
919 'background-color', ))
920 if property is not None and property != 'none':
921 tablestyle.backgroundcolor = property
922 elif family == 'table-cell':
923 properties = stylenode.find(
924 '{%s}table-cell-properties' % (CNSD['style'], ))
925 if properties is not None:
926 border = self.get_property(properties)
927 if border is not None:
928 tablestyle.border = border
929 return table_styles
931 def get_property(self, stylenode):
932 border = None
933 for propertyname in TABLEPROPERTYNAMES:
934 border = stylenode.get('{%s}%s' % (CNSD['fo'], propertyname, ))
935 if border is not None and border != 'none':
936 return border
937 return border
939 def add_doc_title(self):
940 text = self.settings.title
941 if text:
942 self.title = text
943 if not self.found_doc_title:
944 el = Element('text:p', attrib = {
945 'text:style-name': self.rststyle('title'),
947 el.text = text
948 self.body_text_element.insert(0, el)
949 el = self.find_first_text_p(self.body_text_element)
950 if el is not None:
951 self.attach_page_style(el)
953 def find_first_text_p(self, el):
954 """Search the generated doc and return the first <text:p> element.
956 if (
957 el.tag == 'text:p' or
958 el.tag == 'text:h'
960 return el
961 elif el.getchildren():
962 for child in el.getchildren():
963 el1 = self.find_first_text_p(child)
964 if el1 is not None:
965 return el1
966 return None
967 else:
968 return None
970 def attach_page_style(self, el):
971 """Attach the default page style.
973 Create an automatic-style that refers to the current style
974 of this element and that refers to the default page style.
976 current_style = el.get('text:style-name')
977 style_name = 'P1003'
978 el1 = SubElement(
979 self.automatic_styles, 'style:style', attrib={
980 'style:name': style_name,
981 'style:master-page-name': "rststyle-pagedefault",
982 'style:family': "paragraph",
983 }, nsdict=SNSD)
984 if current_style:
985 el1.set('style:parent-style-name', current_style)
986 el.set('text:style-name', style_name)
989 def rststyle(self, name, parameters=( )):
991 Returns the style name to use for the given style.
993 If `parameters` is given `name` must contain a matching number of ``%`` and
994 is used as a format expression with `parameters` as the value.
996 name1 = name % parameters
997 stylename = self.format_map.get(name1, 'rststyle-%s' % name1)
998 return stylename
1000 def generate_content_element(self, root):
1001 return SubElement(root, 'office:text')
1003 def setup_page(self):
1004 self.setup_paper(self.dom_stylesheet)
1005 if (len(self.header_content) > 0 or len(self.footer_content) > 0 or
1006 self.settings.custom_header or self.settings.custom_footer):
1007 self.add_header_footer(self.dom_stylesheet)
1008 new_content = etree.tostring(self.dom_stylesheet)
1009 return new_content
1011 def setup_paper(self, root_el):
1012 try:
1013 fin = os.popen("paperconf -s 2> /dev/null")
1014 w, h = map(float, fin.read().split())
1015 fin.close()
1016 except:
1017 w, h = 612, 792 # default to Letter
1018 def walk(el):
1019 if el.tag == "{%s}page-layout-properties" % SNSD["style"] and \
1020 not el.attrib.has_key("{%s}page-width" % SNSD["fo"]):
1021 el.attrib["{%s}page-width" % SNSD["fo"]] = "%.3fpt" % w
1022 el.attrib["{%s}page-height" % SNSD["fo"]] = "%.3fpt" % h
1023 el.attrib["{%s}margin-left" % SNSD["fo"]] = \
1024 el.attrib["{%s}margin-right" % SNSD["fo"]] = \
1025 "%.3fpt" % (.1 * w)
1026 el.attrib["{%s}margin-top" % SNSD["fo"]] = \
1027 el.attrib["{%s}margin-bottom" % SNSD["fo"]] = \
1028 "%.3fpt" % (.1 * h)
1029 else:
1030 for subel in el.getchildren(): walk(subel)
1031 walk(root_el)
1033 def add_header_footer(self, root_el):
1034 automatic_styles = root_el.find(
1035 '{%s}automatic-styles' % SNSD['office'])
1036 path = '{%s}master-styles' % (NAME_SPACE_1, )
1037 master_el = root_el.find(path)
1038 if master_el is None:
1039 return
1040 path = '{%s}master-page' % (SNSD['style'], )
1041 master_el_container = master_el.findall(path)
1042 master_el = None
1043 target_attrib = '{%s}name' % (SNSD['style'], )
1044 target_name = self.rststyle('pagedefault')
1045 for el in master_el_container:
1046 if el.get(target_attrib) == target_name:
1047 master_el = el
1048 break
1049 if master_el is None:
1050 return
1051 el1 = master_el
1052 if self.header_content or self.settings.custom_header:
1053 if WhichElementTree == 'lxml':
1054 el2 = SubElement(el1, 'style:header', nsdict=SNSD)
1055 else:
1056 el2 = SubElement(el1, 'style:header',
1057 attrib=STYLES_NAMESPACE_ATTRIB,
1058 nsdict=STYLES_NAMESPACE_DICT,
1060 for el in self.header_content:
1061 attrkey = add_ns('text:style-name', nsdict=SNSD)
1062 el.attrib[attrkey] = self.rststyle('header')
1063 el2.append(el)
1064 if self.settings.custom_header:
1065 elcustom = self.create_custom_headfoot(el2,
1066 self.settings.custom_header, 'header', automatic_styles)
1067 if self.footer_content or self.settings.custom_footer:
1068 if WhichElementTree == 'lxml':
1069 el2 = SubElement(el1, 'style:footer', nsdict=SNSD)
1070 else:
1071 el2 = SubElement(el1, 'style:footer',
1072 attrib=STYLES_NAMESPACE_ATTRIB,
1073 nsdict=STYLES_NAMESPACE_DICT,
1075 for el in self.footer_content:
1076 attrkey = add_ns('text:style-name', nsdict=SNSD)
1077 el.attrib[attrkey] = self.rststyle('footer')
1078 el2.append(el)
1079 if self.settings.custom_footer:
1080 elcustom = self.create_custom_headfoot(el2,
1081 self.settings.custom_footer, 'footer', automatic_styles)
1083 code_none, code_field, code_text = range(3)
1084 field_pat = re.compile(r'%(..?)%')
1086 def create_custom_headfoot(self, parent, text, style_name, automatic_styles):
1087 parent = SubElement(parent, 'text:p', attrib={
1088 'text:style-name': self.rststyle(style_name),
1090 current_element = None
1091 field_iter = self.split_field_specifiers_iter(text)
1092 for item in field_iter:
1093 if item[0] == ODFTranslator.code_field:
1094 if item[1] not in ('p', 'P',
1095 't1', 't2', 't3', 't4',
1096 'd1', 'd2', 'd3', 'd4', 'd5',
1097 's', 't', 'a'):
1098 msg = 'bad field spec: %%%s%%' % (item[1], )
1099 raise RuntimeError, msg
1100 el1 = self.make_field_element(parent,
1101 item[1], style_name, automatic_styles)
1102 if el1 is None:
1103 msg = 'bad field spec: %%%s%%' % (item[1], )
1104 raise RuntimeError, msg
1105 else:
1106 current_element = el1
1107 else:
1108 if current_element is None:
1109 parent.text = item[1]
1110 else:
1111 current_element.tail = item[1]
1113 def make_field_element(self, parent, text, style_name, automatic_styles):
1114 if text == 'p':
1115 el1 = SubElement(parent, 'text:page-number', attrib={
1116 #'text:style-name': self.rststyle(style_name),
1117 'text:select-page': 'current',
1119 elif text == 'P':
1120 el1 = SubElement(parent, 'text:page-count', attrib={
1121 #'text:style-name': self.rststyle(style_name),
1123 elif text == 't1':
1124 self.style_index += 1
1125 el1 = SubElement(parent, 'text:time', attrib={
1126 'text:style-name': self.rststyle(style_name),
1127 'text:fixed': 'true',
1128 'style:data-style-name': 'rst-time-style-%d' % self.style_index,
1130 el2 = SubElement(automatic_styles, 'number:time-style', attrib={
1131 'style:name': 'rst-time-style-%d' % self.style_index,
1132 'xmlns:number': SNSD['number'],
1133 'xmlns:style': SNSD['style'],
1135 el3 = SubElement(el2, 'number:hours', attrib={
1136 'number:style': 'long',
1138 el3 = SubElement(el2, 'number:text')
1139 el3.text = ':'
1140 el3 = SubElement(el2, 'number:minutes', attrib={
1141 'number:style': 'long',
1143 elif text == 't2':
1144 self.style_index += 1
1145 el1 = SubElement(parent, 'text:time', attrib={
1146 'text:style-name': self.rststyle(style_name),
1147 'text:fixed': 'true',
1148 'style:data-style-name': 'rst-time-style-%d' % self.style_index,
1150 el2 = SubElement(automatic_styles, 'number:time-style', attrib={
1151 'style:name': 'rst-time-style-%d' % self.style_index,
1152 'xmlns:number': SNSD['number'],
1153 'xmlns:style': SNSD['style'],
1155 el3 = SubElement(el2, 'number:hours', attrib={
1156 'number:style': 'long',
1158 el3 = SubElement(el2, 'number:text')
1159 el3.text = ':'
1160 el3 = SubElement(el2, 'number:minutes', attrib={
1161 'number:style': 'long',
1163 el3 = SubElement(el2, 'number:text')
1164 el3.text = ':'
1165 el3 = SubElement(el2, 'number:seconds', attrib={
1166 'number:style': 'long',
1168 elif text == 't3':
1169 self.style_index += 1
1170 el1 = SubElement(parent, 'text:time', attrib={
1171 'text:style-name': self.rststyle(style_name),
1172 'text:fixed': 'true',
1173 'style:data-style-name': 'rst-time-style-%d' % self.style_index,
1175 el2 = SubElement(automatic_styles, 'number:time-style', attrib={
1176 'style:name': 'rst-time-style-%d' % self.style_index,
1177 'xmlns:number': SNSD['number'],
1178 'xmlns:style': SNSD['style'],
1180 el3 = SubElement(el2, 'number:hours', attrib={
1181 'number:style': 'long',
1183 el3 = SubElement(el2, 'number:text')
1184 el3.text = ':'
1185 el3 = SubElement(el2, 'number:minutes', attrib={
1186 'number:style': 'long',
1188 el3 = SubElement(el2, 'number:text')
1189 el3.text = ' '
1190 el3 = SubElement(el2, 'number:am-pm')
1191 elif text == 't4':
1192 self.style_index += 1
1193 el1 = SubElement(parent, 'text:time', attrib={
1194 'text:style-name': self.rststyle(style_name),
1195 'text:fixed': 'true',
1196 'style:data-style-name': 'rst-time-style-%d' % self.style_index,
1198 el2 = SubElement(automatic_styles, 'number:time-style', attrib={
1199 'style:name': 'rst-time-style-%d' % self.style_index,
1200 'xmlns:number': SNSD['number'],
1201 'xmlns:style': SNSD['style'],
1203 el3 = SubElement(el2, 'number:hours', attrib={
1204 'number:style': 'long',
1206 el3 = SubElement(el2, 'number:text')
1207 el3.text = ':'
1208 el3 = SubElement(el2, 'number:minutes', attrib={
1209 'number:style': 'long',
1211 el3 = SubElement(el2, 'number:text')
1212 el3.text = ':'
1213 el3 = SubElement(el2, 'number:seconds', attrib={
1214 'number:style': 'long',
1216 el3 = SubElement(el2, 'number:text')
1217 el3.text = ' '
1218 el3 = SubElement(el2, 'number:am-pm')
1219 elif text == 'd1':
1220 self.style_index += 1
1221 el1 = SubElement(parent, 'text:date', attrib={
1222 'text:style-name': self.rststyle(style_name),
1223 'style:data-style-name': 'rst-date-style-%d' % self.style_index,
1225 el2 = SubElement(automatic_styles, 'number:date-style', attrib={
1226 'style:name': 'rst-date-style-%d' % self.style_index,
1227 'number:automatic-order': 'true',
1228 'xmlns:number': SNSD['number'],
1229 'xmlns:style': SNSD['style'],
1231 el3 = SubElement(el2, 'number:month', attrib={
1232 'number:style': 'long',
1234 el3 = SubElement(el2, 'number:text')
1235 el3.text = '/'
1236 el3 = SubElement(el2, 'number:day', attrib={
1237 'number:style': 'long',
1239 el3 = SubElement(el2, 'number:text')
1240 el3.text = '/'
1241 el3 = SubElement(el2, 'number:year')
1242 elif text == 'd2':
1243 self.style_index += 1
1244 el1 = SubElement(parent, 'text:date', attrib={
1245 'text:style-name': self.rststyle(style_name),
1246 'style:data-style-name': 'rst-date-style-%d' % self.style_index,
1248 el2 = SubElement(automatic_styles, 'number:date-style', attrib={
1249 'style:name': 'rst-date-style-%d' % self.style_index,
1250 'number:automatic-order': 'true',
1251 'xmlns:number': SNSD['number'],
1252 'xmlns:style': SNSD['style'],
1254 el3 = SubElement(el2, 'number:month', attrib={
1255 'number:style': 'long',
1257 el3 = SubElement(el2, 'number:text')
1258 el3.text = '/'
1259 el3 = SubElement(el2, 'number:day', attrib={
1260 'number:style': 'long',
1262 el3 = SubElement(el2, 'number:text')
1263 el3.text = '/'
1264 el3 = SubElement(el2, 'number:year', attrib={
1265 'number:style': 'long',
1267 elif text == 'd3':
1268 self.style_index += 1
1269 el1 = SubElement(parent, 'text:date', attrib={
1270 'text:style-name': self.rststyle(style_name),
1271 'style:data-style-name': 'rst-date-style-%d' % self.style_index,
1273 el2 = SubElement(automatic_styles, 'number:date-style', attrib={
1274 'style:name': 'rst-date-style-%d' % self.style_index,
1275 'number:automatic-order': 'true',
1276 'xmlns:number': SNSD['number'],
1277 'xmlns:style': SNSD['style'],
1279 el3 = SubElement(el2, 'number:month', attrib={
1280 'number:textual': 'true',
1282 el3 = SubElement(el2, 'number:text')
1283 el3.text = ' '
1284 el3 = SubElement(el2, 'number:day', attrib={
1286 el3 = SubElement(el2, 'number:text')
1287 el3.text = ', '
1288 el3 = SubElement(el2, 'number:year', attrib={
1289 'number:style': 'long',
1291 elif text == 'd4':
1292 self.style_index += 1
1293 el1 = SubElement(parent, 'text:date', attrib={
1294 'text:style-name': self.rststyle(style_name),
1295 'style:data-style-name': 'rst-date-style-%d' % self.style_index,
1297 el2 = SubElement(automatic_styles, 'number:date-style', attrib={
1298 'style:name': 'rst-date-style-%d' % self.style_index,
1299 'number:automatic-order': 'true',
1300 'xmlns:number': SNSD['number'],
1301 'xmlns:style': SNSD['style'],
1303 el3 = SubElement(el2, 'number:month', attrib={
1304 'number:textual': 'true',
1305 'number:style': 'long',
1307 el3 = SubElement(el2, 'number:text')
1308 el3.text = ' '
1309 el3 = SubElement(el2, 'number:day', attrib={
1311 el3 = SubElement(el2, 'number:text')
1312 el3.text = ', '
1313 el3 = SubElement(el2, 'number:year', attrib={
1314 'number:style': 'long',
1316 elif text == 'd5':
1317 self.style_index += 1
1318 el1 = SubElement(parent, 'text:date', attrib={
1319 'text:style-name': self.rststyle(style_name),
1320 'style:data-style-name': 'rst-date-style-%d' % self.style_index,
1322 el2 = SubElement(automatic_styles, 'number:date-style', attrib={
1323 'style:name': 'rst-date-style-%d' % self.style_index,
1324 'xmlns:number': SNSD['number'],
1325 'xmlns:style': SNSD['style'],
1327 el3 = SubElement(el2, 'number:year', attrib={
1328 'number:style': 'long',
1330 el3 = SubElement(el2, 'number:text')
1331 el3.text = '-'
1332 el3 = SubElement(el2, 'number:month', attrib={
1333 'number:style': 'long',
1335 el3 = SubElement(el2, 'number:text')
1336 el3.text = '-'
1337 el3 = SubElement(el2, 'number:day', attrib={
1338 'number:style': 'long',
1340 elif text == 's':
1341 el1 = SubElement(parent, 'text:subject', attrib={
1342 'text:style-name': self.rststyle(style_name),
1344 elif text == 't':
1345 el1 = SubElement(parent, 'text:title', attrib={
1346 'text:style-name': self.rststyle(style_name),
1348 elif text == 'a':
1349 el1 = SubElement(parent, 'text:author-name', attrib={
1350 'text:fixed': 'false',
1352 else:
1353 el1 = None
1354 return el1
1356 def split_field_specifiers_iter(self, text):
1357 pos1 = 0
1358 pos_end = len(text)
1359 while True:
1360 mo = ODFTranslator.field_pat.search(text, pos1)
1361 if mo:
1362 pos2 = mo.start()
1363 if pos2 > pos1:
1364 yield (ODFTranslator.code_text, text[pos1:pos2])
1365 yield (ODFTranslator.code_field, mo.group(1))
1366 pos1 = mo.end()
1367 else:
1368 break
1369 trailing = text[pos1:]
1370 if trailing:
1371 yield (ODFTranslator.code_text, trailing)
1374 def astext(self):
1375 root = self.content_tree.getroot()
1376 et = etree.ElementTree(root)
1377 s1 = ToString(et)
1378 return s1
1380 def content_astext(self):
1381 return self.astext()
1383 def set_title(self, title): self.title = title
1384 def get_title(self): return self.title
1385 def set_embedded_file_list(self, embedded_file_list):
1386 self.embedded_file_list = embedded_file_list
1387 def get_embedded_file_list(self): return self.embedded_file_list
1388 def get_meta_dict(self): return self.meta_dict
1390 def process_footnotes(self):
1391 for node, el1 in self.footnote_list:
1392 backrefs = node.attributes.get('backrefs', [])
1393 first = True
1394 for ref in backrefs:
1395 el2 = self.footnote_ref_dict.get(ref)
1396 if el2 is not None:
1397 if first:
1398 first = False
1399 el3 = copy.deepcopy(el1)
1400 el2.append(el3)
1401 else:
1402 children = el2.getchildren()
1403 if len(children) > 0: # and 'id' in el2.attrib:
1404 child = children[0]
1405 ref1 = child.text
1406 attribkey = add_ns('text:id', nsdict=SNSD)
1407 id1 = el2.get(attribkey, 'footnote-error')
1408 if id1 is None:
1409 id1 = ''
1410 tag = add_ns('text:note-ref', nsdict=SNSD)
1411 el2.tag = tag
1412 if self.settings.endnotes_end_doc:
1413 note_class = 'endnote'
1414 else:
1415 note_class = 'footnote'
1416 el2.attrib.clear()
1417 attribkey = add_ns('text:note-class', nsdict=SNSD)
1418 el2.attrib[attribkey] = note_class
1419 attribkey = add_ns('text:ref-name', nsdict=SNSD)
1420 el2.attrib[attribkey] = id1
1421 attribkey = add_ns('text:reference-format', nsdict=SNSD)
1422 el2.attrib[attribkey] = 'page'
1423 el2.text = ref1
1426 # Utility methods
1428 def append_child(self, tag, attrib=None, parent=None):
1429 if parent is None:
1430 parent = self.current_element
1431 if attrib is None:
1432 el = SubElement(parent, tag)
1433 else:
1434 el = SubElement(parent, tag, attrib)
1435 return el
1437 def append_p(self, style, text=None):
1438 result = self.append_child('text:p', attrib={
1439 'text:style-name': self.rststyle(style)})
1440 self.append_pending_ids(result)
1441 if text is not None:
1442 result.text = text
1443 return result
1445 def append_pending_ids(self, el):
1446 if self.settings.create_links:
1447 for id in self.pending_ids:
1448 SubElement(el, 'text:reference-mark', attrib={
1449 'text:name': id})
1450 self.pending_ids = [ ]
1452 def set_current_element(self, el):
1453 self.current_element = el
1455 def set_to_parent(self):
1456 self.current_element = self.current_element.getparent()
1458 def generate_labeled_block(self, node, label):
1459 label = '%s:' % (self.language.labels[label], )
1460 el = self.append_p('textbody')
1461 el1 = SubElement(el, 'text:span',
1462 attrib={'text:style-name': self.rststyle('strong')})
1463 el1.text = label
1464 el = self.append_p('blockindent')
1465 return el
1467 def generate_labeled_line(self, node, label):
1468 label = '%s:' % (self.language.labels[label], )
1469 el = self.append_p('textbody')
1470 el1 = SubElement(el, 'text:span',
1471 attrib={'text:style-name': self.rststyle('strong')})
1472 el1.text = label
1473 el1.tail = node.astext()
1474 return el
1476 def encode(self, text):
1477 text = text.replace(u'\u00a0', " ")
1478 return text
1481 # Visitor functions
1483 # In alphabetic order, more or less.
1484 # See docutils.docutils.nodes.node_class_names.
1487 def dispatch_visit(self, node):
1488 """Override to catch basic attributes which many nodes have."""
1489 self.handle_basic_atts(node)
1490 nodes.GenericNodeVisitor.dispatch_visit(self, node)
1492 def handle_basic_atts(self, node):
1493 if isinstance(node, nodes.Element) and node['ids']:
1494 self.pending_ids += node['ids']
1496 def default_visit(self, node):
1497 self.document.reporter.warning('missing visit_%s' % (node.tagname, ))
1499 def default_departure(self, node):
1500 self.document.reporter.warning('missing depart_%s' % (node.tagname, ))
1502 def visit_Text(self, node):
1503 # Skip nodes whose text has been processed in parent nodes.
1504 if isinstance(node.parent, docutils.nodes.literal_block):
1505 return
1506 text = node.astext()
1507 # Are we in mixed content? If so, add the text to the
1508 # etree tail of the previous sibling element.
1509 if len(self.current_element.getchildren()) > 0:
1510 if self.current_element.getchildren()[-1].tail:
1511 self.current_element.getchildren()[-1].tail += text
1512 else:
1513 self.current_element.getchildren()[-1].tail = text
1514 else:
1515 if self.current_element.text:
1516 self.current_element.text += text
1517 else:
1518 self.current_element.text = text
1520 def depart_Text(self, node):
1521 pass
1524 # Pre-defined fields
1527 def visit_address(self, node):
1528 el = self.generate_labeled_block(node, 'address')
1529 self.set_current_element(el)
1531 def depart_address(self, node):
1532 self.set_to_parent()
1534 def visit_author(self, node):
1535 if isinstance(node.parent, nodes.authors):
1536 el = self.append_p('blockindent')
1537 else:
1538 el = self.generate_labeled_block(node, 'author')
1539 self.set_current_element(el)
1541 def depart_author(self, node):
1542 self.set_to_parent()
1544 def visit_authors(self, node):
1545 label = '%s:' % (self.language.labels['authors'], )
1546 el = self.append_p('textbody')
1547 el1 = SubElement(el, 'text:span',
1548 attrib={'text:style-name': self.rststyle('strong')})
1549 el1.text = label
1551 def depart_authors(self, node):
1552 pass
1554 def visit_contact(self, node):
1555 el = self.generate_labeled_block(node, 'contact')
1556 self.set_current_element(el)
1558 def depart_contact(self, node):
1559 self.set_to_parent()
1561 def visit_copyright(self, node):
1562 el = self.generate_labeled_block(node, 'copyright')
1563 self.set_current_element(el)
1565 def depart_copyright(self, node):
1566 self.set_to_parent()
1568 def visit_date(self, node):
1569 self.generate_labeled_line(node, 'date')
1571 def depart_date(self, node):
1572 pass
1574 def visit_organization(self, node):
1575 el = self.generate_labeled_block(node, 'organization')
1576 self.set_current_element(el)
1578 def depart_organization(self, node):
1579 self.set_to_parent()
1581 def visit_status(self, node):
1582 el = self.generate_labeled_block(node, 'status')
1583 self.set_current_element(el)
1585 def depart_status(self, node):
1586 self.set_to_parent()
1588 def visit_revision(self, node):
1589 el = self.generate_labeled_line(node, 'revision')
1591 def depart_revision(self, node):
1592 pass
1594 def visit_version(self, node):
1595 el = self.generate_labeled_line(node, 'version')
1596 #self.set_current_element(el)
1598 def depart_version(self, node):
1599 #self.set_to_parent()
1600 pass
1602 def visit_attribution(self, node):
1603 el = self.append_p('attribution', node.astext())
1605 def depart_attribution(self, node):
1606 pass
1608 def visit_block_quote(self, node):
1609 if 'epigraph' in node.attributes['classes']:
1610 self.paragraph_style_stack.append(self.rststyle('epigraph'))
1611 self.blockstyle = self.rststyle('epigraph')
1612 elif 'highlights' in node.attributes['classes']:
1613 self.paragraph_style_stack.append(self.rststyle('highlights'))
1614 self.blockstyle = self.rststyle('highlights')
1615 else:
1616 self.paragraph_style_stack.append(self.rststyle('blockquote'))
1617 self.blockstyle = self.rststyle('blockquote')
1618 self.line_indent_level += 1
1620 def depart_block_quote(self, node):
1621 self.paragraph_style_stack.pop()
1622 self.blockstyle = ''
1623 self.line_indent_level -= 1
1625 def visit_bullet_list(self, node):
1626 self.list_level +=1
1627 if self.in_table_of_contents:
1628 if self.settings.generate_oowriter_toc:
1629 pass
1630 else:
1631 if node.has_key('classes') and \
1632 'auto-toc' in node.attributes['classes']:
1633 el = SubElement(self.current_element, 'text:list', attrib={
1634 'text:style-name': self.rststyle('tocenumlist'),
1636 self.list_style_stack.append(self.rststyle('enumitem'))
1637 else:
1638 el = SubElement(self.current_element, 'text:list', attrib={
1639 'text:style-name': self.rststyle('tocbulletlist'),
1641 self.list_style_stack.append(self.rststyle('bulletitem'))
1642 self.set_current_element(el)
1643 else:
1644 if self.blockstyle == self.rststyle('blockquote'):
1645 el = SubElement(self.current_element, 'text:list', attrib={
1646 'text:style-name': self.rststyle('blockquote-bulletlist'),
1648 self.list_style_stack.append(
1649 self.rststyle('blockquote-bulletitem'))
1650 elif self.blockstyle == self.rststyle('highlights'):
1651 el = SubElement(self.current_element, 'text:list', attrib={
1652 'text:style-name': self.rststyle('highlights-bulletlist'),
1654 self.list_style_stack.append(
1655 self.rststyle('highlights-bulletitem'))
1656 elif self.blockstyle == self.rststyle('epigraph'):
1657 el = SubElement(self.current_element, 'text:list', attrib={
1658 'text:style-name': self.rststyle('epigraph-bulletlist'),
1660 self.list_style_stack.append(
1661 self.rststyle('epigraph-bulletitem'))
1662 else:
1663 el = SubElement(self.current_element, 'text:list', attrib={
1664 'text:style-name': self.rststyle('bulletlist'),
1666 self.list_style_stack.append(self.rststyle('bulletitem'))
1667 self.set_current_element(el)
1669 def depart_bullet_list(self, node):
1670 if self.in_table_of_contents:
1671 if self.settings.generate_oowriter_toc:
1672 pass
1673 else:
1674 self.set_to_parent()
1675 self.list_style_stack.pop()
1676 else:
1677 self.set_to_parent()
1678 self.list_style_stack.pop()
1679 self.list_level -=1
1681 def visit_caption(self, node):
1682 raise nodes.SkipChildren()
1683 pass
1685 def depart_caption(self, node):
1686 pass
1688 def visit_comment(self, node):
1689 el = self.append_p('textbody')
1690 el1 = SubElement(el, 'office:annotation', attrib={})
1691 el2 = SubElement(el1, 'dc:creator', attrib={})
1692 s1 = os.environ.get('USER', '')
1693 el2.text = s1
1694 el2 = SubElement(el1, 'text:p', attrib={})
1695 el2.text = node.astext()
1697 def depart_comment(self, node):
1698 pass
1700 def visit_compound(self, node):
1701 # The compound directive currently receives no special treatment.
1702 pass
1704 def depart_compound(self, node):
1705 pass
1707 def visit_container(self, node):
1708 styles = node.attributes.get('classes', ())
1709 if len(styles) > 0:
1710 self.paragraph_style_stack.append(self.rststyle(styles[0]))
1712 def depart_container(self, node):
1713 styles = node.attributes.get('classes', ())
1714 if len(styles) > 0:
1715 self.paragraph_style_stack.pop()
1717 def visit_decoration(self, node):
1718 pass
1720 def depart_decoration(self, node):
1721 pass
1723 def visit_definition_list(self, node):
1724 self.def_list_level +=1
1725 if self.list_level > 5:
1726 raise RuntimeError(
1727 'max definition list nesting level exceeded')
1729 def depart_definition_list(self, node):
1730 self.def_list_level -=1
1732 def visit_definition_list_item(self, node):
1733 pass
1735 def depart_definition_list_item(self, node):
1736 pass
1738 def visit_term(self, node):
1739 el = self.append_p('deflist-term-%d' % self.def_list_level)
1740 el.text = node.astext()
1741 self.set_current_element(el)
1742 raise nodes.SkipChildren()
1744 def depart_term(self, node):
1745 self.set_to_parent()
1747 def visit_definition(self, node):
1748 self.paragraph_style_stack.append(
1749 self.rststyle('deflist-def-%d' % self.def_list_level))
1750 self.bumped_list_level_stack.append(ListLevel(1))
1752 def depart_definition(self, node):
1753 self.paragraph_style_stack.pop()
1754 self.bumped_list_level_stack.pop()
1756 def visit_classifier(self, node):
1757 els = self.current_element.getchildren()
1758 if len(els) > 0:
1759 el = els[-1]
1760 el1 = SubElement(el, 'text:span',
1761 attrib={'text:style-name': self.rststyle('emphasis')
1763 el1.text = ' (%s)' % (node.astext(), )
1765 def depart_classifier(self, node):
1766 pass
1768 def visit_document(self, node):
1769 pass
1771 def depart_document(self, node):
1772 self.process_footnotes()
1774 def visit_docinfo(self, node):
1775 self.section_level += 1
1776 self.section_count += 1
1777 if self.settings.create_sections:
1778 el = self.append_child('text:section', attrib={
1779 'text:name': 'Section%d' % self.section_count,
1780 'text:style-name': 'Sect%d' % self.section_level,
1782 self.set_current_element(el)
1784 def depart_docinfo(self, node):
1785 self.section_level -= 1
1786 if self.settings.create_sections:
1787 self.set_to_parent()
1789 def visit_emphasis(self, node):
1790 el = SubElement(self.current_element, 'text:span',
1791 attrib={'text:style-name': self.rststyle('emphasis')})
1792 self.set_current_element(el)
1794 def depart_emphasis(self, node):
1795 self.set_to_parent()
1797 def visit_enumerated_list(self, node):
1798 el1 = self.current_element
1799 if self.blockstyle == self.rststyle('blockquote'):
1800 el2 = SubElement(el1, 'text:list', attrib={
1801 'text:style-name': self.rststyle('blockquote-enumlist'),
1803 self.list_style_stack.append(self.rststyle('blockquote-enumitem'))
1804 elif self.blockstyle == self.rststyle('highlights'):
1805 el2 = SubElement(el1, 'text:list', attrib={
1806 'text:style-name': self.rststyle('highlights-enumlist'),
1808 self.list_style_stack.append(self.rststyle('highlights-enumitem'))
1809 elif self.blockstyle == self.rststyle('epigraph'):
1810 el2 = SubElement(el1, 'text:list', attrib={
1811 'text:style-name': self.rststyle('epigraph-enumlist'),
1813 self.list_style_stack.append(self.rststyle('epigraph-enumitem'))
1814 else:
1815 liststylename = 'enumlist-%s' % (node.get('enumtype', 'arabic'), )
1816 el2 = SubElement(el1, 'text:list', attrib={
1817 'text:style-name': self.rststyle(liststylename),
1819 self.list_style_stack.append(self.rststyle('enumitem'))
1820 self.set_current_element(el2)
1822 def depart_enumerated_list(self, node):
1823 self.set_to_parent()
1824 self.list_style_stack.pop()
1826 def visit_list_item(self, node):
1827 # If we are in a "bumped" list level, then wrap this
1828 # list in an outer lists in order to increase the
1829 # indentation level.
1830 if self.in_table_of_contents:
1831 if self.settings.generate_oowriter_toc:
1832 self.paragraph_style_stack.append(
1833 self.rststyle('contents-%d' % (self.list_level, )))
1834 else:
1835 el1 = self.append_child('text:list-item')
1836 self.set_current_element(el1)
1837 else:
1838 el1 = self.append_child('text:list-item')
1839 el3 = el1
1840 if len(self.bumped_list_level_stack) > 0:
1841 level_obj = self.bumped_list_level_stack[-1]
1842 if level_obj.get_sibling():
1843 level_obj.set_nested(False)
1844 for level_obj1 in self.bumped_list_level_stack:
1845 for idx in range(level_obj1.get_level()):
1846 el2 = self.append_child('text:list', parent=el3)
1847 el3 = self.append_child(
1848 'text:list-item', parent=el2)
1849 self.paragraph_style_stack.append(self.list_style_stack[-1])
1850 self.set_current_element(el3)
1852 def depart_list_item(self, node):
1853 if self.in_table_of_contents:
1854 if self.settings.generate_oowriter_toc:
1855 self.paragraph_style_stack.pop()
1856 else:
1857 self.set_to_parent()
1858 else:
1859 if len(self.bumped_list_level_stack) > 0:
1860 level_obj = self.bumped_list_level_stack[-1]
1861 if level_obj.get_sibling():
1862 level_obj.set_nested(True)
1863 for level_obj1 in self.bumped_list_level_stack:
1864 for idx in range(level_obj1.get_level()):
1865 self.set_to_parent()
1866 self.set_to_parent()
1867 self.paragraph_style_stack.pop()
1868 self.set_to_parent()
1870 def visit_header(self, node):
1871 self.in_header = True
1873 def depart_header(self, node):
1874 self.in_header = False
1876 def visit_footer(self, node):
1877 self.in_footer = True
1879 def depart_footer(self, node):
1880 self.in_footer = False
1882 def visit_field(self, node):
1883 pass
1885 def depart_field(self, node):
1886 pass
1888 def visit_field_list(self, node):
1889 pass
1891 def depart_field_list(self, node):
1892 pass
1894 def visit_field_name(self, node):
1895 el = self.append_p('textbody')
1896 el1 = SubElement(el, 'text:span',
1897 attrib={'text:style-name': self.rststyle('strong')})
1898 el1.text = node.astext()
1900 def depart_field_name(self, node):
1901 pass
1903 def visit_field_body(self, node):
1904 self.paragraph_style_stack.append(self.rststyle('blockindent'))
1906 def depart_field_body(self, node):
1907 self.paragraph_style_stack.pop()
1909 def visit_figure(self, node):
1910 pass
1912 def depart_figure(self, node):
1913 pass
1915 def visit_footnote(self, node):
1916 self.footnote_level += 1
1917 self.save_footnote_current = self.current_element
1918 el1 = Element('text:note-body')
1919 self.current_element = el1
1920 self.footnote_list.append((node, el1))
1921 if isinstance(node, docutils.nodes.citation):
1922 self.paragraph_style_stack.append(self.rststyle('citation'))
1923 else:
1924 self.paragraph_style_stack.append(self.rststyle('footnote'))
1926 def depart_footnote(self, node):
1927 self.paragraph_style_stack.pop()
1928 self.current_element = self.save_footnote_current
1929 self.footnote_level -= 1
1931 footnote_chars = [
1932 '*', '**', '***',
1933 '++', '+++',
1934 '##', '###',
1935 '@@', '@@@',
1938 def visit_footnote_reference(self, node):
1939 if self.footnote_level <= 0:
1940 id = node.attributes['ids'][0]
1941 refid = node.attributes.get('refid')
1942 if refid is None:
1943 refid = ''
1944 if self.settings.endnotes_end_doc:
1945 note_class = 'endnote'
1946 else:
1947 note_class = 'footnote'
1948 el1 = self.append_child('text:note', attrib={
1949 'text:id': '%s' % (refid, ),
1950 'text:note-class': note_class,
1952 note_auto = str(node.attributes.get('auto', 1))
1953 if isinstance(node, docutils.nodes.citation_reference):
1954 citation = '[%s]' % node.astext()
1955 el2 = SubElement(el1, 'text:note-citation', attrib={
1956 'text:label': citation,
1958 el2.text = citation
1959 elif note_auto == '1':
1960 el2 = SubElement(el1, 'text:note-citation', attrib={
1961 'text:label': node.astext(),
1963 el2.text = node.astext()
1964 elif note_auto == '*':
1965 if self.footnote_chars_idx >= len(
1966 ODFTranslator.footnote_chars):
1967 self.footnote_chars_idx = 0
1968 footnote_char = ODFTranslator.footnote_chars[
1969 self.footnote_chars_idx]
1970 self.footnote_chars_idx += 1
1971 el2 = SubElement(el1, 'text:note-citation', attrib={
1972 'text:label': footnote_char,
1974 el2.text = footnote_char
1975 self.footnote_ref_dict[id] = el1
1976 raise nodes.SkipChildren()
1978 def depart_footnote_reference(self, node):
1979 pass
1981 def visit_citation(self, node):
1982 self.in_citation = True
1983 for id in node.attributes['ids']:
1984 self.citation_id = id
1985 break
1986 self.paragraph_style_stack.append(self.rststyle('blockindent'))
1987 self.bumped_list_level_stack.append(ListLevel(1))
1989 def depart_citation(self, node):
1990 self.citation_id = None
1991 self.paragraph_style_stack.pop()
1992 self.bumped_list_level_stack.pop()
1993 self.in_citation = False
1995 def visit_citation_reference(self, node):
1996 if self.settings.create_links:
1997 id = node.attributes['refid']
1998 el = self.append_child('text:reference-ref', attrib={
1999 'text:ref-name': '%s' % (id, ),
2000 'text:reference-format': 'text',
2002 el.text = '['
2003 self.set_current_element(el)
2004 elif self.current_element.text is None:
2005 self.current_element.text = '['
2006 else:
2007 self.current_element.text += '['
2009 def depart_citation_reference(self, node):
2010 self.current_element.text += ']'
2011 if self.settings.create_links:
2012 self.set_to_parent()
2014 def visit_label(self, node):
2015 if isinstance(node.parent, docutils.nodes.footnote):
2016 raise nodes.SkipChildren()
2017 elif self.citation_id is not None:
2018 el = self.append_p('textbody')
2019 self.set_current_element(el)
2020 if self.settings.create_links:
2021 el0 = SubElement(el, 'text:span')
2022 el0.text = '['
2023 el1 = self.append_child('text:reference-mark-start', attrib={
2024 'text:name': '%s' % (self.citation_id, ),
2026 else:
2027 el.text = '['
2029 def depart_label(self, node):
2030 if isinstance(node.parent, docutils.nodes.footnote):
2031 pass
2032 elif self.citation_id is not None:
2033 if self.settings.create_links:
2034 el = self.append_child('text:reference-mark-end', attrib={
2035 'text:name': '%s' % (self.citation_id, ),
2037 el0 = SubElement(self.current_element, 'text:span')
2038 el0.text = ']'
2039 else:
2040 self.current_element.text += ']'
2041 self.set_to_parent()
2043 def visit_generated(self, node):
2044 pass
2046 def depart_generated(self, node):
2047 pass
2049 def check_file_exists(self, path):
2050 if os.path.exists(path):
2051 return 1
2052 else:
2053 return 0
2055 def visit_image(self, node):
2056 # Capture the image file.
2057 if 'uri' in node.attributes:
2058 source = node.attributes['uri']
2059 if not source.startswith('http:'):
2060 if not source.startswith(os.sep):
2061 docsource, line = utils.get_source_line(node)
2062 if docsource:
2063 dirname = os.path.dirname(docsource)
2064 if dirname:
2065 source = '%s%s%s' % (dirname, os.sep, source, )
2066 if not self.check_file_exists(source):
2067 self.document.reporter.warning(
2068 'Cannot find image file %s.' % (source, ))
2069 return
2070 else:
2071 return
2072 if source in self.image_dict:
2073 filename, destination = self.image_dict[source]
2074 else:
2075 self.image_count += 1
2076 filename = os.path.split(source)[1]
2077 destination = 'Pictures/1%08x%s' % (self.image_count, filename, )
2078 if source.startswith('http:'):
2079 try:
2080 imgfile = urllib2.urlopen(source)
2081 content = imgfile.read()
2082 imgfile.close()
2083 imgfile2 = tempfile.NamedTemporaryFile('wb', delete=False)
2084 imgfile2.write(content)
2085 imgfile2.close()
2086 imgfilename = imgfile2.name
2087 source = imgfilename
2088 except urllib2.HTTPError, e:
2089 self.document.reporter.warning(
2090 "Can't open image url %s." % (source, ))
2091 spec = (source, destination,)
2092 else:
2093 spec = (os.path.abspath(source), destination,)
2094 self.embedded_file_list.append(spec)
2095 self.image_dict[source] = (source, destination,)
2096 # Is this a figure (containing an image) or just a plain image?
2097 if self.in_paragraph:
2098 el1 = self.current_element
2099 else:
2100 el1 = SubElement(self.current_element, 'text:p',
2101 attrib={'text:style-name': self.rststyle('textbody')})
2102 el2 = el1
2103 if isinstance(node.parent, docutils.nodes.figure):
2104 el3, el4, el5, caption = self.generate_figure(node, source,
2105 destination, el2)
2106 attrib = {}
2107 el6, width = self.generate_image(node, source, destination,
2108 el5, attrib)
2109 if caption is not None:
2110 el6.tail = caption
2111 else: #if isinstance(node.parent, docutils.nodes.image):
2112 el3 = self.generate_image(node, source, destination, el2)
2114 def depart_image(self, node):
2115 pass
2117 def get_image_width_height(self, node, attr):
2118 size = None
2119 if attr in node.attributes:
2120 size = node.attributes[attr]
2121 unit = size[-2:]
2122 if unit.isalpha():
2123 size = size[:-2]
2124 else:
2125 unit = 'px'
2126 try:
2127 size = float(size)
2128 except ValueError, e:
2129 self.document.reporter.warning(
2130 'Invalid %s for image: "%s"' % (
2131 attr, node.attributes[attr]))
2132 size = [size, unit]
2133 return size
2135 def get_image_scale(self, node):
2136 if 'scale' in node.attributes:
2137 try:
2138 scale = int(node.attributes['scale'])
2139 if scale < 1: # or scale > 100:
2140 self.document.reporter.warning(
2141 'scale out of range (%s), using 1.' % (scale, ))
2142 scale = 1
2143 scale = scale * 0.01
2144 except ValueError, e:
2145 self.document.reporter.warning(
2146 'Invalid scale for image: "%s"' % (
2147 node.attributes['scale'], ))
2148 else:
2149 scale = 1.0
2150 return scale
2152 def get_image_scaled_width_height(self, node, source):
2153 scale = self.get_image_scale(node)
2154 width = self.get_image_width_height(node, 'width')
2155 height = self.get_image_width_height(node, 'height')
2157 dpi = (72, 72)
2158 if PIL is not None and source in self.image_dict:
2159 filename, destination = self.image_dict[source]
2160 imageobj = PIL.Image.open(filename, 'r')
2161 dpi = imageobj.info.get('dpi', dpi)
2162 # dpi information can be (xdpi, ydpi) or xydpi
2163 try: iter(dpi)
2164 except: dpi = (dpi, dpi)
2165 else:
2166 imageobj = None
2168 if width is None or height is None:
2169 if imageobj is None:
2170 raise RuntimeError(
2171 'image size not fully specified and PIL not installed')
2172 if width is None: width = [imageobj.size[0], 'px']
2173 if height is None: height = [imageobj.size[1], 'px']
2175 width[0] *= scale
2176 height[0] *= scale
2177 if width[1] == 'px': width = [width[0] / dpi[0], 'in']
2178 if height[1] == 'px': height = [height[0] / dpi[1], 'in']
2180 width[0] = str(width[0])
2181 height[0] = str(height[0])
2182 return ''.join(width), ''.join(height)
2184 def generate_figure(self, node, source, destination, current_element):
2185 caption = None
2186 width, height = self.get_image_scaled_width_height(node, source)
2187 for node1 in node.parent.children:
2188 if node1.tagname == 'caption':
2189 caption = node1.astext()
2190 self.image_style_count += 1
2192 # Add the style for the caption.
2193 if caption is not None:
2194 attrib = {
2195 'style:class': 'extra',
2196 'style:family': 'paragraph',
2197 'style:name': 'Caption',
2198 'style:parent-style-name': 'Standard',
2200 el1 = SubElement(self.automatic_styles, 'style:style',
2201 attrib=attrib, nsdict=SNSD)
2202 attrib = {
2203 'fo:margin-bottom': '0.0835in',
2204 'fo:margin-top': '0.0835in',
2205 'text:line-number': '0',
2206 'text:number-lines': 'false',
2208 el2 = SubElement(el1, 'style:paragraph-properties',
2209 attrib=attrib, nsdict=SNSD)
2210 attrib = {
2211 'fo:font-size': '12pt',
2212 'fo:font-style': 'italic',
2213 'style:font-name': 'Times',
2214 'style:font-name-complex': 'Lucidasans1',
2215 'style:font-size-asian': '12pt',
2216 'style:font-size-complex': '12pt',
2217 'style:font-style-asian': 'italic',
2218 'style:font-style-complex': 'italic',
2220 el2 = SubElement(el1, 'style:text-properties',
2221 attrib=attrib, nsdict=SNSD)
2222 style_name = 'rstframestyle%d' % self.image_style_count
2223 # Add the styles
2224 attrib = {
2225 'style:name': style_name,
2226 'style:family': 'graphic',
2227 'style:parent-style-name': self.rststyle('figureframe'),
2229 el1 = SubElement(self.automatic_styles,
2230 'style:style', attrib=attrib, nsdict=SNSD)
2231 halign = 'center'
2232 valign = 'top'
2233 if 'align' in node.attributes:
2234 align = node.attributes['align'].split()
2235 for val in align:
2236 if val in ('left', 'center', 'right'):
2237 halign = val
2238 elif val in ('top', 'middle', 'bottom'):
2239 valign = val
2240 attrib = {}
2241 wrap = False
2242 classes = node.parent.attributes.get('classes')
2243 if classes and 'wrap' in classes:
2244 wrap = True
2245 if wrap:
2246 attrib['style:wrap'] = 'dynamic'
2247 else:
2248 attrib['style:wrap'] = 'none'
2249 el2 = SubElement(el1,
2250 'style:graphic-properties', attrib=attrib, nsdict=SNSD)
2251 attrib = {
2252 'draw:style-name': style_name,
2253 'draw:name': 'Frame1',
2254 'text:anchor-type': 'paragraph',
2255 'draw:z-index': '0',
2257 attrib['svg:width'] = width
2258 el3 = SubElement(current_element, 'draw:frame', attrib=attrib)
2259 attrib = {}
2260 el4 = SubElement(el3, 'draw:text-box', attrib=attrib)
2261 attrib = {
2262 'text:style-name': self.rststyle('caption'),
2264 el5 = SubElement(el4, 'text:p', attrib=attrib)
2265 return el3, el4, el5, caption
2267 def generate_image(self, node, source, destination, current_element,
2268 frame_attrs=None):
2269 width, height = self.get_image_scaled_width_height(node, source)
2270 self.image_style_count += 1
2271 style_name = 'rstframestyle%d' % self.image_style_count
2272 # Add the style.
2273 attrib = {
2274 'style:name': style_name,
2275 'style:family': 'graphic',
2276 'style:parent-style-name': self.rststyle('image'),
2278 el1 = SubElement(self.automatic_styles,
2279 'style:style', attrib=attrib, nsdict=SNSD)
2280 halign = None
2281 valign = None
2282 if 'align' in node.attributes:
2283 align = node.attributes['align'].split()
2284 for val in align:
2285 if val in ('left', 'center', 'right'):
2286 halign = val
2287 elif val in ('top', 'middle', 'bottom'):
2288 valign = val
2289 if frame_attrs is None:
2290 attrib = {
2291 'style:vertical-pos': 'top',
2292 'style:vertical-rel': 'paragraph',
2293 'style:horizontal-rel': 'paragraph',
2294 'style:mirror': 'none',
2295 'fo:clip': 'rect(0cm 0cm 0cm 0cm)',
2296 'draw:luminance': '0%',
2297 'draw:contrast': '0%',
2298 'draw:red': '0%',
2299 'draw:green': '0%',
2300 'draw:blue': '0%',
2301 'draw:gamma': '100%',
2302 'draw:color-inversion': 'false',
2303 'draw:image-opacity': '100%',
2304 'draw:color-mode': 'standard',
2306 else:
2307 attrib = frame_attrs
2308 if halign is not None:
2309 attrib['style:horizontal-pos'] = halign
2310 if valign is not None:
2311 attrib['style:vertical-pos'] = valign
2312 # If there is a classes/wrap directive or we are
2313 # inside a table, add a no-wrap style.
2314 wrap = False
2315 classes = node.attributes.get('classes')
2316 if classes and 'wrap' in classes:
2317 wrap = True
2318 if wrap:
2319 attrib['style:wrap'] = 'dynamic'
2320 else:
2321 attrib['style:wrap'] = 'none'
2322 # If we are inside a table, add a no-wrap style.
2323 if self.is_in_table(node):
2324 attrib['style:wrap'] = 'none'
2325 el2 = SubElement(el1,
2326 'style:graphic-properties', attrib=attrib, nsdict=SNSD)
2327 # Add the content.
2328 #el = SubElement(current_element, 'text:p',
2329 # attrib={'text:style-name': self.rststyle('textbody')})
2330 attrib={
2331 'draw:style-name': style_name,
2332 'draw:name': 'graphics2',
2333 'draw:z-index': '1',
2335 if isinstance(node.parent, nodes.TextElement):
2336 attrib['text:anchor-type'] = 'as-char' #vds
2337 else:
2338 attrib['text:anchor-type'] = 'paragraph'
2339 attrib['svg:width'] = width
2340 attrib['svg:height'] = height
2341 el1 = SubElement(current_element, 'draw:frame', attrib=attrib)
2342 el2 = SubElement(el1, 'draw:image', attrib={
2343 'xlink:href': '%s' % (destination, ),
2344 'xlink:type': 'simple',
2345 'xlink:show': 'embed',
2346 'xlink:actuate': 'onLoad',
2348 return el1, width
2350 def is_in_table(self, node):
2351 node1 = node.parent
2352 while node1:
2353 if isinstance(node1, docutils.nodes.entry):
2354 return True
2355 node1 = node1.parent
2356 return False
2358 def visit_legend(self, node):
2359 if isinstance(node.parent, docutils.nodes.figure):
2360 el1 = self.current_element[-1]
2361 el1 = el1[0][0]
2362 self.current_element = el1
2363 self.paragraph_style_stack.append(self.rststyle('legend'))
2365 def depart_legend(self, node):
2366 if isinstance(node.parent, docutils.nodes.figure):
2367 self.paragraph_style_stack.pop()
2368 self.set_to_parent()
2369 self.set_to_parent()
2370 self.set_to_parent()
2372 def visit_line_block(self, node):
2373 self.line_indent_level += 1
2374 self.line_block_level += 1
2376 def depart_line_block(self, node):
2377 self.line_indent_level -= 1
2378 self.line_block_level -= 1
2380 def visit_line(self, node):
2381 style = 'lineblock%d' % self.line_indent_level
2382 el1 = SubElement(self.current_element, 'text:p', attrib={
2383 'text:style-name': self.rststyle(style),
2385 self.current_element = el1
2387 def depart_line(self, node):
2388 self.set_to_parent()
2390 def visit_literal(self, node):
2391 el = SubElement(self.current_element, 'text:span',
2392 attrib={'text:style-name': self.rststyle('inlineliteral')})
2393 self.set_current_element(el)
2395 def depart_literal(self, node):
2396 self.set_to_parent()
2398 def visit_inline(self, node):
2399 styles = node.attributes.get('classes', ())
2400 if len(styles) > 0:
2401 inline_style = styles[0]
2402 el = SubElement(self.current_element, 'text:span',
2403 attrib={'text:style-name': self.rststyle(inline_style)})
2404 self.set_current_element(el)
2406 def depart_inline(self, node):
2407 self.set_to_parent()
2409 def _calculate_code_block_padding(self, line):
2410 count = 0
2411 matchobj = SPACES_PATTERN.match(line)
2412 if matchobj:
2413 pad = matchobj.group()
2414 count = len(pad)
2415 else:
2416 matchobj = TABS_PATTERN.match(line)
2417 if matchobj:
2418 pad = matchobj.group()
2419 count = len(pad) * 8
2420 return count
2422 def _add_syntax_highlighting(self, insource, language):
2423 lexer = pygments.lexers.get_lexer_by_name(language, stripall=True)
2424 if language in ('latex', 'tex'):
2425 fmtr = OdtPygmentsLaTeXFormatter(lambda name, parameters=():
2426 self.rststyle(name, parameters),
2427 escape_function=escape_cdata)
2428 else:
2429 fmtr = OdtPygmentsProgFormatter(lambda name, parameters=():
2430 self.rststyle(name, parameters),
2431 escape_function=escape_cdata)
2432 outsource = pygments.highlight(insource, lexer, fmtr)
2433 return outsource
2435 def fill_line(self, line):
2436 line = FILL_PAT1.sub(self.fill_func1, line)
2437 line = FILL_PAT2.sub(self.fill_func2, line)
2438 return line
2440 def fill_func1(self, matchobj):
2441 spaces = matchobj.group(0)
2442 repl = '<text:s text:c="%d"/>' % (len(spaces), )
2443 return repl
2445 def fill_func2(self, matchobj):
2446 spaces = matchobj.group(0)
2447 repl = ' <text:s text:c="%d"/>' % (len(spaces) - 1, )
2448 return repl
2450 def visit_literal_block(self, node):
2451 if len(self.paragraph_style_stack) > 1:
2452 wrapper1 = '<text:p text:style-name="%s">%%s</text:p>' % (
2453 self.rststyle('codeblock-indented'), )
2454 else:
2455 wrapper1 = '<text:p text:style-name="%s">%%s</text:p>' % (
2456 self.rststyle('codeblock'), )
2457 source = node.astext()
2458 if (pygments and
2459 self.settings.add_syntax_highlighting
2460 #and
2461 #node.get('hilight', False)
2463 language = node.get('language', 'python')
2464 source = self._add_syntax_highlighting(source, language)
2465 else:
2466 source = escape_cdata(source)
2467 lines = source.split('\n')
2468 # If there is an empty last line, remove it.
2469 if lines[-1] == '':
2470 del lines[-1]
2471 lines1 = ['<wrappertag1 xmlns:text="urn:oasis:names:tc:opendocument:xmlns:text:1.0">']
2473 my_lines = []
2474 for my_line in lines:
2475 my_line = self.fill_line(my_line)
2476 my_line = my_line.replace("&#10;", "\n")
2477 my_lines.append(my_line)
2478 my_lines_str = '<text:line-break/>'.join(my_lines)
2479 my_lines_str2 = wrapper1 % (my_lines_str, )
2480 lines1.append(my_lines_str2)
2481 lines1.append('</wrappertag1>')
2482 s1 = ''.join(lines1)
2483 if WhichElementTree != "lxml":
2484 s1 = s1.encode("utf-8")
2485 el1 = etree.fromstring(s1)
2486 children = el1.getchildren()
2487 for child in children:
2488 self.current_element.append(child)
2490 def depart_literal_block(self, node):
2491 pass
2493 visit_doctest_block = visit_literal_block
2494 depart_doctest_block = depart_literal_block
2496 # placeholder for math (see docs/dev/todo.txt)
2497 def visit_math(self, node):
2498 self.document.reporter.warning('"math" role not supported',
2499 base_node=node)
2500 self.visit_literal(node)
2502 def depart_math(self, node):
2503 self.depart_literal(node)
2505 def visit_math_block(self, node):
2506 self.document.reporter.warning('"math" directive not supported',
2507 base_node=node)
2508 self.visit_literal_block(node)
2510 def depart_math_block(self, node):
2511 self.depart_literal_block(node)
2513 def visit_meta(self, node):
2514 name = node.attributes.get('name')
2515 content = node.attributes.get('content')
2516 if name is not None and content is not None:
2517 self.meta_dict[name] = content
2519 def depart_meta(self, node):
2520 pass
2522 def visit_option_list(self, node):
2523 table_name = 'tableoption'
2525 # Generate automatic styles
2526 if not self.optiontablestyles_generated:
2527 self.optiontablestyles_generated = True
2528 el = SubElement(self.automatic_styles, 'style:style', attrib={
2529 'style:name': self.rststyle(table_name),
2530 'style:family': 'table'}, nsdict=SNSD)
2531 el1 = SubElement(el, 'style:table-properties', attrib={
2532 'style:width': '17.59cm',
2533 'table:align': 'left',
2534 'style:shadow': 'none'}, nsdict=SNSD)
2535 el = SubElement(self.automatic_styles, 'style:style', attrib={
2536 'style:name': self.rststyle('%s.%%c' % table_name, ( 'A', )),
2537 'style:family': 'table-column'}, nsdict=SNSD)
2538 el1 = SubElement(el, 'style:table-column-properties', attrib={
2539 'style:column-width': '4.999cm'}, nsdict=SNSD)
2540 el = SubElement(self.automatic_styles, 'style:style', attrib={
2541 'style:name': self.rststyle('%s.%%c' % table_name, ( 'B', )),
2542 'style:family': 'table-column'}, nsdict=SNSD)
2543 el1 = SubElement(el, 'style:table-column-properties', attrib={
2544 'style:column-width': '12.587cm'}, nsdict=SNSD)
2545 el = SubElement(self.automatic_styles, 'style:style', attrib={
2546 'style:name': self.rststyle(
2547 '%s.%%c%%d' % table_name, ( 'A', 1, )),
2548 'style:family': 'table-cell'}, nsdict=SNSD)
2549 el1 = SubElement(el, 'style:table-cell-properties', attrib={
2550 'fo:background-color': 'transparent',
2551 'fo:padding': '0.097cm',
2552 'fo:border-left': '0.035cm solid #000000',
2553 'fo:border-right': 'none',
2554 'fo:border-top': '0.035cm solid #000000',
2555 'fo:border-bottom': '0.035cm solid #000000'}, nsdict=SNSD)
2556 el2 = SubElement(el1, 'style:background-image', nsdict=SNSD)
2557 el = SubElement(self.automatic_styles, 'style:style', attrib={
2558 'style:name': self.rststyle(
2559 '%s.%%c%%d' % table_name, ( 'B', 1, )),
2560 'style:family': 'table-cell'}, nsdict=SNSD)
2561 el1 = SubElement(el, 'style:table-cell-properties', attrib={
2562 'fo:padding': '0.097cm',
2563 'fo:border': '0.035cm solid #000000'}, nsdict=SNSD)
2564 el = SubElement(self.automatic_styles, 'style:style', attrib={
2565 'style:name': self.rststyle(
2566 '%s.%%c%%d' % table_name, ( 'A', 2, )),
2567 'style:family': 'table-cell'}, nsdict=SNSD)
2568 el1 = SubElement(el, 'style:table-cell-properties', attrib={
2569 'fo:padding': '0.097cm',
2570 'fo:border-left': '0.035cm solid #000000',
2571 'fo:border-right': 'none',
2572 'fo:border-top': 'none',
2573 'fo:border-bottom': '0.035cm solid #000000'}, nsdict=SNSD)
2574 el = SubElement(self.automatic_styles, 'style:style', attrib={
2575 'style:name': self.rststyle(
2576 '%s.%%c%%d' % table_name, ( 'B', 2, )),
2577 'style:family': 'table-cell'}, nsdict=SNSD)
2578 el1 = SubElement(el, 'style:table-cell-properties', attrib={
2579 'fo:padding': '0.097cm',
2580 'fo:border-left': '0.035cm solid #000000',
2581 'fo:border-right': '0.035cm solid #000000',
2582 'fo:border-top': 'none',
2583 'fo:border-bottom': '0.035cm solid #000000'}, nsdict=SNSD)
2585 # Generate table data
2586 el = self.append_child('table:table', attrib={
2587 'table:name': self.rststyle(table_name),
2588 'table:style-name': self.rststyle(table_name),
2590 el1 = SubElement(el, 'table:table-column', attrib={
2591 'table:style-name': self.rststyle(
2592 '%s.%%c' % table_name, ( 'A', ))})
2593 el1 = SubElement(el, 'table:table-column', attrib={
2594 'table:style-name': self.rststyle(
2595 '%s.%%c' % table_name, ( 'B', ))})
2596 el1 = SubElement(el, 'table:table-header-rows')
2597 el2 = SubElement(el1, 'table:table-row')
2598 el3 = SubElement(el2, 'table:table-cell', attrib={
2599 'table:style-name': self.rststyle(
2600 '%s.%%c%%d' % table_name, ( 'A', 1, )),
2601 'office:value-type': 'string'})
2602 el4 = SubElement(el3, 'text:p', attrib={
2603 'text:style-name': 'Table_20_Heading'})
2604 el4.text= 'Option'
2605 el3 = SubElement(el2, 'table:table-cell', attrib={
2606 'table:style-name': self.rststyle(
2607 '%s.%%c%%d' % table_name, ( 'B', 1, )),
2608 'office:value-type': 'string'})
2609 el4 = SubElement(el3, 'text:p', attrib={
2610 'text:style-name': 'Table_20_Heading'})
2611 el4.text= 'Description'
2612 self.set_current_element(el)
2614 def depart_option_list(self, node):
2615 self.set_to_parent()
2617 def visit_option_list_item(self, node):
2618 el = self.append_child('table:table-row')
2619 self.set_current_element(el)
2621 def depart_option_list_item(self, node):
2622 self.set_to_parent()
2624 def visit_option_group(self, node):
2625 el = self.append_child('table:table-cell', attrib={
2626 'table:style-name': 'Table%d.A2' % self.table_count,
2627 'office:value-type': 'string',
2629 self.set_current_element(el)
2631 def depart_option_group(self, node):
2632 self.set_to_parent()
2634 def visit_option(self, node):
2635 el = self.append_child('text:p', attrib={
2636 'text:style-name': 'Table_20_Contents'})
2637 el.text = node.astext()
2639 def depart_option(self, node):
2640 pass
2642 def visit_option_string(self, node):
2643 pass
2645 def depart_option_string(self, node):
2646 pass
2648 def visit_option_argument(self, node):
2649 pass
2651 def depart_option_argument(self, node):
2652 pass
2654 def visit_description(self, node):
2655 el = self.append_child('table:table-cell', attrib={
2656 'table:style-name': 'Table%d.B2' % self.table_count,
2657 'office:value-type': 'string',
2659 el1 = SubElement(el, 'text:p', attrib={
2660 'text:style-name': 'Table_20_Contents'})
2661 el1.text = node.astext()
2662 raise nodes.SkipChildren()
2664 def depart_description(self, node):
2665 pass
2667 def visit_paragraph(self, node):
2668 self.in_paragraph = True
2669 if self.in_header:
2670 el = self.append_p('header')
2671 elif self.in_footer:
2672 el = self.append_p('footer')
2673 else:
2674 style_name = self.paragraph_style_stack[-1]
2675 el = self.append_child('text:p',
2676 attrib={'text:style-name': style_name})
2677 self.append_pending_ids(el)
2678 self.set_current_element(el)
2680 def depart_paragraph(self, node):
2681 self.in_paragraph = False
2682 self.set_to_parent()
2683 if self.in_header:
2684 self.header_content.append(
2685 self.current_element.getchildren()[-1])
2686 self.current_element.remove(
2687 self.current_element.getchildren()[-1])
2688 elif self.in_footer:
2689 self.footer_content.append(
2690 self.current_element.getchildren()[-1])
2691 self.current_element.remove(
2692 self.current_element.getchildren()[-1])
2694 def visit_problematic(self, node):
2695 pass
2697 def depart_problematic(self, node):
2698 pass
2700 def visit_raw(self, node):
2701 if 'format' in node.attributes:
2702 formats = node.attributes['format']
2703 formatlist = formats.split()
2704 if 'odt' in formatlist:
2705 rawstr = node.astext()
2706 attrstr = ' '.join(['%s="%s"' % (k, v, )
2707 for k,v in CONTENT_NAMESPACE_ATTRIB.items()])
2708 contentstr = '<stuff %s>%s</stuff>' % (attrstr, rawstr, )
2709 if WhichElementTree != "lxml":
2710 contentstr = contentstr.encode("utf-8")
2711 content = etree.fromstring(contentstr)
2712 elements = content.getchildren()
2713 if len(elements) > 0:
2714 el1 = elements[0]
2715 if self.in_header:
2716 pass
2717 elif self.in_footer:
2718 pass
2719 else:
2720 self.current_element.append(el1)
2721 raise nodes.SkipChildren()
2723 def depart_raw(self, node):
2724 if self.in_header:
2725 pass
2726 elif self.in_footer:
2727 pass
2728 else:
2729 pass
2731 def visit_reference(self, node):
2732 text = node.astext()
2733 if self.settings.create_links:
2734 if node.has_key('refuri'):
2735 href = node['refuri']
2736 if ( self.settings.cloak_email_addresses
2737 and href.startswith('mailto:')):
2738 href = self.cloak_mailto(href)
2739 el = self.append_child('text:a', attrib={
2740 'xlink:href': '%s' % href,
2741 'xlink:type': 'simple',
2743 self.set_current_element(el)
2744 elif node.has_key('refid'):
2745 if self.settings.create_links:
2746 href = node['refid']
2747 el = self.append_child('text:reference-ref', attrib={
2748 'text:ref-name': '%s' % href,
2749 'text:reference-format': 'text',
2751 else:
2752 self.document.reporter.warning(
2753 'References must have "refuri" or "refid" attribute.')
2754 if (self.in_table_of_contents and
2755 len(node.children) >= 1 and
2756 isinstance(node.children[0], docutils.nodes.generated)):
2757 node.remove(node.children[0])
2759 def depart_reference(self, node):
2760 if self.settings.create_links:
2761 if node.has_key('refuri'):
2762 self.set_to_parent()
2764 def visit_rubric(self, node):
2765 style_name = self.rststyle('rubric')
2766 classes = node.get('classes')
2767 if classes:
2768 class1 = classes[0]
2769 if class1:
2770 style_name = class1
2771 el = SubElement(self.current_element, 'text:h', attrib = {
2772 #'text:outline-level': '%d' % section_level,
2773 #'text:style-name': 'Heading_20_%d' % section_level,
2774 'text:style-name': style_name,
2776 text = node.astext()
2777 el.text = self.encode(text)
2779 def depart_rubric(self, node):
2780 pass
2782 def visit_section(self, node, move_ids=1):
2783 self.section_level += 1
2784 self.section_count += 1
2785 if self.settings.create_sections:
2786 el = self.append_child('text:section', attrib={
2787 'text:name': 'Section%d' % self.section_count,
2788 'text:style-name': 'Sect%d' % self.section_level,
2790 self.set_current_element(el)
2792 def depart_section(self, node):
2793 self.section_level -= 1
2794 if self.settings.create_sections:
2795 self.set_to_parent()
2797 def visit_strong(self, node):
2798 el = SubElement(self.current_element, 'text:span',
2799 attrib={'text:style-name': self.rststyle('strong')})
2800 self.set_current_element(el)
2802 def depart_strong(self, node):
2803 self.set_to_parent()
2805 def visit_substitution_definition(self, node):
2806 raise nodes.SkipChildren()
2808 def depart_substitution_definition(self, node):
2809 pass
2811 def visit_system_message(self, node):
2812 pass
2814 def depart_system_message(self, node):
2815 pass
2817 def get_table_style(self, node):
2818 table_style = None
2819 table_name = None
2820 use_predefined_table_style = False
2821 str_classes = node.get('classes')
2822 if str_classes is not None:
2823 for str_class in str_classes:
2824 if str_class.startswith(TABLESTYLEPREFIX):
2825 table_name = str_class
2826 use_predefined_table_style = True
2827 break
2828 if table_name is not None:
2829 table_style = self.table_styles.get(table_name)
2830 if table_style is None:
2831 # If we can't find the table style, issue warning
2832 # and use the default table style.
2833 self.document.reporter.warning(
2834 'Can\'t find table style "%s". Using default.' % (
2835 table_name, ))
2836 table_name = TABLENAMEDEFAULT
2837 table_style = self.table_styles.get(table_name)
2838 if table_style is None:
2839 # If we can't find the default table style, issue a warning
2840 # and use a built-in default style.
2841 self.document.reporter.warning(
2842 'Can\'t find default table style "%s". Using built-in default.' % (
2843 table_name, ))
2844 table_style = BUILTIN_DEFAULT_TABLE_STYLE
2845 else:
2846 table_name = TABLENAMEDEFAULT
2847 table_style = self.table_styles.get(table_name)
2848 if table_style is None:
2849 # If we can't find the default table style, issue a warning
2850 # and use a built-in default style.
2851 self.document.reporter.warning(
2852 'Can\'t find default table style "%s". Using built-in default.' % (
2853 table_name, ))
2854 table_style = BUILTIN_DEFAULT_TABLE_STYLE
2855 return table_style
2857 def visit_table(self, node):
2858 self.table_count += 1
2859 table_style = self.get_table_style(node)
2860 table_name = '%s%%d' % TABLESTYLEPREFIX
2861 el1 = SubElement(self.automatic_styles, 'style:style', attrib={
2862 'style:name': self.rststyle(
2863 '%s' % table_name, ( self.table_count, )),
2864 'style:family': 'table',
2865 }, nsdict=SNSD)
2866 if table_style.backgroundcolor is None:
2867 el1_1 = SubElement(el1, 'style:table-properties', attrib={
2868 #'style:width': '17.59cm',
2869 #'table:align': 'margins',
2870 'table:align': 'left',
2871 'fo:margin-top': '0in',
2872 'fo:margin-bottom': '0.10in',
2873 }, nsdict=SNSD)
2874 else:
2875 el1_1 = SubElement(el1, 'style:table-properties', attrib={
2876 #'style:width': '17.59cm',
2877 'table:align': 'margins',
2878 'fo:margin-top': '0in',
2879 'fo:margin-bottom': '0.10in',
2880 'fo:background-color': table_style.backgroundcolor,
2881 }, nsdict=SNSD)
2882 # We use a single cell style for all cells in this table.
2883 # That's probably not correct, but seems to work.
2884 el2 = SubElement(self.automatic_styles, 'style:style', attrib={
2885 'style:name': self.rststyle(
2886 '%s.%%c%%d' % table_name, ( self.table_count, 'A', 1, )),
2887 'style:family': 'table-cell',
2888 }, nsdict=SNSD)
2889 thickness = self.settings.table_border_thickness
2890 if thickness is None:
2891 line_style1 = table_style.border
2892 else:
2893 line_style1 = '0.%03dcm solid #000000' % (thickness, )
2894 el2_1 = SubElement(el2, 'style:table-cell-properties', attrib={
2895 'fo:padding': '0.049cm',
2896 'fo:border-left': line_style1,
2897 'fo:border-right': line_style1,
2898 'fo:border-top': line_style1,
2899 'fo:border-bottom': line_style1,
2900 }, nsdict=SNSD)
2901 title = None
2902 for child in node.children:
2903 if child.tagname == 'title':
2904 title = child.astext()
2905 break
2906 if title is not None:
2907 el3 = self.append_p('table-title', title)
2908 else:
2909 pass
2910 el4 = SubElement(self.current_element, 'table:table', attrib={
2911 'table:name': self.rststyle(
2912 '%s' % table_name, ( self.table_count, )),
2913 'table:style-name': self.rststyle(
2914 '%s' % table_name, ( self.table_count, )),
2916 self.set_current_element(el4)
2917 self.current_table_style = el1
2918 self.table_width = 0.0
2920 def depart_table(self, node):
2921 attribkey = add_ns('style:width', nsdict=SNSD)
2922 attribval = '%.4fin' % (self.table_width, )
2923 el1 = self.current_table_style
2924 el2 = el1[0]
2925 el2.attrib[attribkey] = attribval
2926 self.set_to_parent()
2928 def visit_tgroup(self, node):
2929 self.column_count = ord('A') - 1
2931 def depart_tgroup(self, node):
2932 pass
2934 def visit_colspec(self, node):
2935 self.column_count += 1
2936 colspec_name = self.rststyle(
2937 '%s%%d.%%s' % TABLESTYLEPREFIX,
2938 (self.table_count, chr(self.column_count), )
2940 colwidth = node['colwidth'] / 12.0
2941 el1 = SubElement(self.automatic_styles, 'style:style', attrib={
2942 'style:name': colspec_name,
2943 'style:family': 'table-column',
2944 }, nsdict=SNSD)
2945 el1_1 = SubElement(el1, 'style:table-column-properties', attrib={
2946 'style:column-width': '%.4fin' % colwidth
2948 nsdict=SNSD)
2949 el2 = self.append_child('table:table-column', attrib={
2950 'table:style-name': colspec_name,
2952 self.table_width += colwidth
2954 def depart_colspec(self, node):
2955 pass
2957 def visit_thead(self, node):
2958 el = self.append_child('table:table-header-rows')
2959 self.set_current_element(el)
2960 self.in_thead = True
2961 self.paragraph_style_stack.append('Table_20_Heading')
2963 def depart_thead(self, node):
2964 self.set_to_parent()
2965 self.in_thead = False
2966 self.paragraph_style_stack.pop()
2968 def visit_row(self, node):
2969 self.column_count = ord('A') - 1
2970 el = self.append_child('table:table-row')
2971 self.set_current_element(el)
2973 def depart_row(self, node):
2974 self.set_to_parent()
2976 def visit_entry(self, node):
2977 self.column_count += 1
2978 cellspec_name = self.rststyle(
2979 '%s%%d.%%c%%d' % TABLESTYLEPREFIX,
2980 (self.table_count, 'A', 1, )
2982 attrib={
2983 'table:style-name': cellspec_name,
2984 'office:value-type': 'string',
2986 morecols = node.get('morecols', 0)
2987 if morecols > 0:
2988 attrib['table:number-columns-spanned'] = '%d' % (morecols + 1,)
2989 self.column_count += morecols
2990 morerows = node.get('morerows', 0)
2991 if morerows > 0:
2992 attrib['table:number-rows-spanned'] = '%d' % (morerows + 1,)
2993 el1 = self.append_child('table:table-cell', attrib=attrib)
2994 self.set_current_element(el1)
2996 def depart_entry(self, node):
2997 self.set_to_parent()
2999 def visit_tbody(self, node):
3000 pass
3002 def depart_tbody(self, node):
3003 pass
3005 def visit_target(self, node):
3007 # I don't know how to implement targets in ODF.
3008 # How do we create a target in oowriter? A cross-reference?
3009 if not (node.has_key('refuri') or node.has_key('refid')
3010 or node.has_key('refname')):
3011 pass
3012 else:
3013 pass
3015 def depart_target(self, node):
3016 pass
3018 def visit_title(self, node, move_ids=1, title_type='title'):
3019 if isinstance(node.parent, docutils.nodes.section):
3020 section_level = self.section_level
3021 if section_level > 7:
3022 self.document.reporter.warning(
3023 'Heading/section levels greater than 7 not supported.')
3024 self.document.reporter.warning(
3025 ' Reducing to heading level 7 for heading: "%s"' % (
3026 node.astext(), ))
3027 section_level = 7
3028 el1 = self.append_child('text:h', attrib = {
3029 'text:outline-level': '%d' % section_level,
3030 #'text:style-name': 'Heading_20_%d' % section_level,
3031 'text:style-name': self.rststyle(
3032 'heading%d', (section_level, )),
3034 self.append_pending_ids(el1)
3035 self.set_current_element(el1)
3036 elif isinstance(node.parent, docutils.nodes.document):
3037 # text = self.settings.title
3038 #else:
3039 # text = node.astext()
3040 el1 = SubElement(self.current_element, 'text:p', attrib = {
3041 'text:style-name': self.rststyle(title_type),
3043 self.append_pending_ids(el1)
3044 text = node.astext()
3045 self.title = text
3046 self.found_doc_title = True
3047 self.set_current_element(el1)
3049 def depart_title(self, node):
3050 if (isinstance(node.parent, docutils.nodes.section) or
3051 isinstance(node.parent, docutils.nodes.document)):
3052 self.set_to_parent()
3054 def visit_subtitle(self, node, move_ids=1):
3055 self.visit_title(node, move_ids, title_type='subtitle')
3057 def depart_subtitle(self, node):
3058 self.depart_title(node)
3060 def visit_title_reference(self, node):
3061 el = self.append_child('text:span', attrib={
3062 'text:style-name': self.rststyle('quotation')})
3063 el.text = self.encode(node.astext())
3064 raise nodes.SkipChildren()
3066 def depart_title_reference(self, node):
3067 pass
3069 def generate_table_of_content_entry_template(self, el1):
3070 for idx in range(1, 11):
3071 el2 = SubElement(el1,
3072 'text:table-of-content-entry-template',
3073 attrib={
3074 'text:outline-level': "%d" % (idx, ),
3075 'text:style-name': self.rststyle('contents-%d' % (idx, )),
3077 el3 = SubElement(el2, 'text:index-entry-chapter')
3078 el3 = SubElement(el2, 'text:index-entry-text')
3079 el3 = SubElement(el2, 'text:index-entry-tab-stop', attrib={
3080 'style:leader-char': ".",
3081 'style:type': "right",
3083 el3 = SubElement(el2, 'text:index-entry-page-number')
3085 def find_title_label(self, node, class_type, label_key):
3086 label = ''
3087 title_node = None
3088 for child in node.children:
3089 if isinstance(child, class_type):
3090 title_node = child
3091 break
3092 if title_node is not None:
3093 label = title_node.astext()
3094 else:
3095 label = self.language.labels[label_key]
3096 return label
3098 def visit_topic(self, node):
3099 if 'classes' in node.attributes:
3100 if 'contents' in node.attributes['classes']:
3101 label = self.find_title_label(node, docutils.nodes.title,
3102 'contents')
3103 if self.settings.generate_oowriter_toc:
3104 el1 = self.append_child('text:table-of-content', attrib={
3105 'text:name': 'Table of Contents1',
3106 'text:protected': 'true',
3107 'text:style-name': 'Sect1',
3109 el2 = SubElement(el1,
3110 'text:table-of-content-source',
3111 attrib={
3112 'text:outline-level': '10',
3114 el3 =SubElement(el2, 'text:index-title-template', attrib={
3115 'text:style-name': 'Contents_20_Heading',
3117 el3.text = label
3118 self.generate_table_of_content_entry_template(el2)
3119 el4 = SubElement(el1, 'text:index-body')
3120 el5 = SubElement(el4, 'text:index-title')
3121 el6 = SubElement(el5, 'text:p', attrib={
3122 'text:style-name': self.rststyle('contents-heading'),
3124 el6.text = label
3125 self.save_current_element = self.current_element
3126 self.table_of_content_index_body = el4
3127 self.set_current_element(el4)
3128 else:
3129 el = self.append_p('horizontalline')
3130 el = self.append_p('centeredtextbody')
3131 el1 = SubElement(el, 'text:span',
3132 attrib={'text:style-name': self.rststyle('strong')})
3133 el1.text = label
3134 self.in_table_of_contents = True
3135 elif 'abstract' in node.attributes['classes']:
3136 el = self.append_p('horizontalline')
3137 el = self.append_p('centeredtextbody')
3138 el1 = SubElement(el, 'text:span',
3139 attrib={'text:style-name': self.rststyle('strong')})
3140 label = self.find_title_label(node, docutils.nodes.title,
3141 'abstract')
3142 el1.text = label
3143 elif 'dedication' in node.attributes['classes']:
3144 el = self.append_p('horizontalline')
3145 el = self.append_p('centeredtextbody')
3146 el1 = SubElement(el, 'text:span',
3147 attrib={'text:style-name': self.rststyle('strong')})
3148 label = self.find_title_label(node, docutils.nodes.title,
3149 'dedication')
3150 el1.text = label
3152 def depart_topic(self, node):
3153 if 'classes' in node.attributes:
3154 if 'contents' in node.attributes['classes']:
3155 if self.settings.generate_oowriter_toc:
3156 self.update_toc_page_numbers(
3157 self.table_of_content_index_body)
3158 self.set_current_element(self.save_current_element)
3159 else:
3160 el = self.append_p('horizontalline')
3161 self.in_table_of_contents = False
3163 def update_toc_page_numbers(self, el):
3164 collection = []
3165 self.update_toc_collect(el, 0, collection)
3166 self.update_toc_add_numbers(collection)
3168 def update_toc_collect(self, el, level, collection):
3169 collection.append((level, el))
3170 level += 1
3171 for child_el in el.getchildren():
3172 if child_el.tag != 'text:index-body':
3173 self.update_toc_collect(child_el, level, collection)
3175 def update_toc_add_numbers(self, collection):
3176 for level, el1 in collection:
3177 if (el1.tag == 'text:p' and
3178 el1.text != 'Table of Contents'):
3179 el2 = SubElement(el1, 'text:tab')
3180 el2.tail = '9999'
3183 def visit_transition(self, node):
3184 el = self.append_p('horizontalline')
3186 def depart_transition(self, node):
3187 pass
3190 # Admonitions
3192 def visit_warning(self, node):
3193 self.generate_admonition(node, 'warning')
3195 def depart_warning(self, node):
3196 self.paragraph_style_stack.pop()
3198 def visit_attention(self, node):
3199 self.generate_admonition(node, 'attention')
3201 depart_attention = depart_warning
3203 def visit_caution(self, node):
3204 self.generate_admonition(node, 'caution')
3206 depart_caution = depart_warning
3208 def visit_danger(self, node):
3209 self.generate_admonition(node, 'danger')
3211 depart_danger = depart_warning
3213 def visit_error(self, node):
3214 self.generate_admonition(node, 'error')
3216 depart_error = depart_warning
3218 def visit_hint(self, node):
3219 self.generate_admonition(node, 'hint')
3221 depart_hint = depart_warning
3223 def visit_important(self, node):
3224 self.generate_admonition(node, 'important')
3226 depart_important = depart_warning
3228 def visit_note(self, node):
3229 self.generate_admonition(node, 'note')
3231 depart_note = depart_warning
3233 def visit_tip(self, node):
3234 self.generate_admonition(node, 'tip')
3236 depart_tip = depart_warning
3238 def visit_admonition(self, node):
3239 title = None
3240 for child in node.children:
3241 if child.tagname == 'title':
3242 title = child.astext()
3243 if title is None:
3244 classes1 = node.get('classes')
3245 if classes1:
3246 title = classes1[0]
3247 self.generate_admonition(node, 'generic', title)
3249 depart_admonition = depart_warning
3251 def generate_admonition(self, node, label, title=None):
3252 el1 = SubElement(self.current_element, 'text:p', attrib = {
3253 'text:style-name': self.rststyle('admon-%s-hdr', ( label, )),
3255 if title:
3256 el1.text = title
3257 else:
3258 el1.text = '%s!' % (label.capitalize(), )
3259 s1 = self.rststyle('admon-%s-body', ( label, ))
3260 self.paragraph_style_stack.append(s1)
3263 # Roles (e.g. subscript, superscript, strong, ...
3265 def visit_subscript(self, node):
3266 el = self.append_child('text:span', attrib={
3267 'text:style-name': 'rststyle-subscript',
3269 self.set_current_element(el)
3271 def depart_subscript(self, node):
3272 self.set_to_parent()
3274 def visit_superscript(self, node):
3275 el = self.append_child('text:span', attrib={
3276 'text:style-name': 'rststyle-superscript',
3278 self.set_current_element(el)
3280 def depart_superscript(self, node):
3281 self.set_to_parent()
3284 # Use an own reader to modify transformations done.
3285 class Reader(standalone.Reader):
3287 def get_transforms(self):
3288 default = standalone.Reader.get_transforms(self)
3289 if self.settings.create_links:
3290 return default
3291 return [ i
3292 for i in default
3293 if i is not references.DanglingReferences ]