2 # Author: Dave Kuhlman <dkuhlman@rexx.com>
3 # Copyright: This module has been placed in the public domain.
6 Open Document Format (ODF) Writer.
12 __docformat__
= 'reStructuredText'
20 from xml
.dom
import minidom
27 from docutils
import frontend
, nodes
, utils
, writers
, languages
28 from docutils
.readers
import standalone
29 from docutils
.transforms
import references
35 #from lxml import etree
36 #WhichElementTree = 'lxml'
37 raise ImportError('Ignoring lxml')
38 except ImportError, e
:
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
:
45 # 3. Try to use a version of ElementTree installed as a separate
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.'
55 # Import pygments and odtwriter pygments formatters if possible.
58 import pygments
.lexers
59 from pygmentsformatter
import OdtPygmentsProgFormatter
, \
60 OdtPygmentsLaTeXFormatter
61 except ImportError, exp
:
64 # check for the Python Imaging Library
68 try: # sometimes PIL modules are put in PYTHONPATH's root
70 class PIL(object): pass # dummy wrapper
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':
92 _parents
= weakref
.WeakKeyDictionary()
93 if isinstance(etree
.Element
, type):
94 _ElementInterface
= etree
.Element
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
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',
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
):
274 tag
, attrib
= fix_ns(tag
, attrib
, nsdict
)
275 if WhichElementTree
== 'lxml':
276 el
= etree
.Element(tag
, attrib
, nsmap
=nsmap
)
278 el
= _ElementInterfaceWrapper(tag
, attrib
)
281 def SubElement(parent
, tag
, attrib
=None, nsmap
=None, nsdict
=CNSD
):
284 tag
, attrib
= fix_ns(tag
, attrib
, nsdict
)
285 if WhichElementTree
== 'lxml':
286 el
= etree
.SubElement(parent
, tag
, attrib
, nsmap
=nsmap
)
288 el
= _ElementInterfaceWrapper(tag
, attrib
)
293 def fix_ns(tag
, attrib
, nsdict
):
294 nstag
= add_ns(tag
, nsdict
)
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
)
306 raise RuntimeError, 'Invalid namespace prefix: %s' % nstag
307 tag
= '{%s}%s' % (ns
, name
,)
311 outstream
= StringIO
.StringIO()
312 if sys
.version_info
>= (3, 2):
313 et
.write(outstream
, encoding
="unicode")
316 s1
= outstream
.getvalue()
321 def escape_cdata(text
):
322 text
= text
.replace("&", "&")
323 text
= text
.replace("<", "<")
324 text
= text
.replace(">", ">")
327 if ord(char
) >= ord("\x7f"):
328 ascii
+= "&#x%X;" % ( ord(char
), )
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.
342 mo
= WORD_SPLIT_PAT1
.search(line
, pos1
)
343 while mo
is not None:
344 word
= mo
.groups()[0]
347 mo
= WORD_SPLIT_PAT1
.search(line
, pos1
)
356 class TableStyle(object):
357 def __init__(self
, border
=None, backgroundcolor
=None):
359 self
.backgroundcolor
= backgroundcolor
360 def get_border_(self
):
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):
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'
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
))
411 'ODF-Specific Options',
414 ('Specify a stylesheet. '
415 'Default: "%s"' % default_stylesheet_path
,
418 'default': default_stylesheet_path
,
421 ('Specify a configuration/mapping file relative to the '
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'],
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'],
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. '
447 ['--table-border-thickness'],
449 'validator': frontend
.validate_nonnegative_int
}),
450 ('Add syntax highlighting in literal code blocks.',
451 ['--add-syntax-highlighting'],
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'],
459 'action': 'store_false',
460 'dest': 'add_syntax_highlighting',
461 'validator': frontend
.validate_boolean
}),
462 ('Create sections for headers. (default)',
463 ['--create-sections'],
465 'action': 'store_true',
466 'dest': 'create_sections',
467 'validator': frontend
.validate_boolean
}),
468 ('Do not create sections for headers.',
471 'action': 'store_false',
472 'dest': 'create_sections',
473 'validator': frontend
.validate_boolean
}),
477 'action': 'store_true',
478 'dest': 'create_links',
479 'validator': frontend
.validate_boolean
}),
480 ('Do not create links. (default)',
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'],
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'],
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'],
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'],
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'],
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'],
526 'dest': 'custom_footer',
531 settings_defaults
= {
532 'output_encoding_error_handler': 'xmlcharrefreplace',
535 relative_path_settings
= (
539 config_section
= 'odf_odt writer'
540 config_section_dependencies
= (
545 writers
.Writer
.__init
__(self
)
546 self
.translator_class
= ODFTranslator
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
)
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
:
597 zfile
.write(source
, destination
)
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')
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()
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')
625 s1
= inzipfile
.read('settings.xml')
626 self
.write_zip_str(outzipfile
, 'settings.xml', s1
)
628 namelist
= inzipfile
.namelist()
629 for name
in namelist
:
630 if name
.startswith('Pictures/'):
631 imageobj
= inzipfile
.read(name
)
632 outzipfile
.writestr(name
, imageobj
)
635 def assemble_parts(self
):
638 def create_manifest(self
):
639 if WhichElementTree
== 'lxml':
640 root
= Element('manifest:manifest',
641 nsmap
=MANIFEST_NAMESPACE_DICT
,
642 nsdict
=MANIFEST_NAMESPACE_DICT
,
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': '/',
654 SubElement(root
, 'manifest:file-entry', attrib
={
655 'manifest:media-type': 'text/xml',
656 'manifest:full-path': 'content.xml',
658 SubElement(root
, 'manifest:file-entry', attrib
={
659 'manifest:media-type': 'text/xml',
660 'manifest:full-path': 'styles.xml',
662 SubElement(root
, 'manifest:file-entry', attrib
={
663 'manifest:media-type': 'text/xml',
664 'manifest:full-path': 'settings.xml',
666 SubElement(root
, 'manifest:file-entry', attrib
={
667 'manifest:media-type': 'text/xml',
668 'manifest:full-path': 'meta.xml',
671 doc
= minidom
.parseString(s1
)
672 s1
= doc
.toprettyxml(' ')
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
,
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
)
693 s2
= time
.strftime('%Y-%m-%dT%H:%M:%S', time
.localtime())
694 el1
= SubElement(root
, 'meta:creation-date', nsdict
=METNSD
)
696 el1
= SubElement(root
, 'dc:creator', nsdict
=METNSD
)
698 el1
= SubElement(root
, 'dc:date', nsdict
=METNSD
)
700 el1
= SubElement(root
, 'dc:language', nsdict
=METNSD
)
702 el1
= SubElement(root
, 'meta:editing-cycles', nsdict
=METNSD
)
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
)
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
)
719 description
= meta_dict
.get('description')
720 if description
is not None:
721 el1
= SubElement(root
, 'dc:description', nsdict
=METNSD
)
722 el1
.text
= description
724 #doc = minidom.parseString(s1)
725 #s1 = doc.toprettyxml(' ')
728 # class ODFTranslator(nodes.SparseNodeVisitor):
730 class ODFTranslator(nodes
.GenericNodeVisitor
):
733 'attribution', 'blockindent', 'blockquote', 'blockquote-bulletitem',
734 'blockquote-bulletlist', 'blockquote-enumitem', 'blockquote-enumlist',
735 'bulletitem', 'bulletlist',
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',
757 'admon-attention-hdr',
758 'admon-attention-body',
760 'admon-caution-body',
766 'admon-generic-body',
769 'admon-important-hdr',
770 'admon-important-body',
776 'admon-warning-body',
778 'tableoption.%c', 'tableoption.%c%d', 'Table%d', 'Table%d.%c',
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.' % (
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':
812 'office:document-content',
813 nsmap
=CONTENT_NAMESPACE_DICT
,
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
= []
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
840 self
.image_style_count
= 0
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
850 self
.in_table_of_contents
= False
851 self
.table_of_content_index_body
= None
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
= []
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.
882 stylespath
= self
.settings
.stylesheet
883 ext
= os
.path
.splitext(stylespath
)[1]
885 stylesfile
= open(stylespath
, 'r')
886 s1
= stylesfile
.read()
888 elif ext
== extension
:
889 zfile
= zipfile
.ZipFile(stylespath
, 'r')
890 s1
= zfile
.read('styles.xml')
891 s2
= zfile
.read('content.xml')
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
)
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
931 def get_property(self
, stylenode
):
933 for propertyname
in TABLEPROPERTYNAMES
:
934 border
= stylenode
.get('{%s}%s' % (CNSD
['fo'], propertyname
, ))
935 if border
is not None and border
!= 'none':
939 def add_doc_title(self
):
940 text
= self
.settings
.title
943 if not self
.found_doc_title
:
944 el
= Element('text:p', attrib
= {
945 'text:style-name': self
.rststyle('title'),
948 self
.body_text_element
.insert(0, el
)
949 el
= self
.find_first_text_p(self
.body_text_element
)
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.
957 el
.tag
== 'text:p' or
961 elif el
.getchildren():
962 for child
in el
.getchildren():
963 el1
= self
.find_first_text_p(child
)
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')
979 self
.automatic_styles
, 'style:style', attrib
={
980 'style:name': style_name
,
981 'style:master-page-name': "rststyle-pagedefault",
982 'style:family': "paragraph",
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
)
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
)
1011 def setup_paper(self
, root_el
):
1013 fin
= os
.popen("paperconf -s 2> /dev/null")
1014 w
, h
= map(float, fin
.read().split())
1017 w
, h
= 612, 792 # default to Letter
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"]] = \
1026 el
.attrib
["{%s}margin-top" % SNSD
["fo"]] = \
1027 el
.attrib
["{%s}margin-bottom" % SNSD
["fo"]] = \
1030 for subel
in el
.getchildren(): walk(subel
)
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:
1040 path
= '{%s}master-page' % (SNSD
['style'], )
1041 master_el_container
= master_el
.findall(path
)
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
:
1049 if master_el
is None:
1052 if self
.header_content
or self
.settings
.custom_header
:
1053 if WhichElementTree
== 'lxml':
1054 el2
= SubElement(el1
, 'style:header', nsdict
=SNSD
)
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')
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
)
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')
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',
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
)
1103 msg
= 'bad field spec: %%%s%%' % (item
[1], )
1104 raise RuntimeError, msg
1106 current_element
= el1
1108 if current_element
is None:
1109 parent
.text
= item
[1]
1111 current_element
.tail
= item
[1]
1113 def make_field_element(self
, parent
, text
, style_name
, automatic_styles
):
1115 el1
= SubElement(parent
, 'text:page-number', attrib
={
1116 #'text:style-name': self.rststyle(style_name),
1117 'text:select-page': 'current',
1120 el1
= SubElement(parent
, 'text:page-count', attrib
={
1121 #'text:style-name': self.rststyle(style_name),
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')
1140 el3
= SubElement(el2
, 'number:minutes', attrib
={
1141 'number:style': 'long',
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')
1160 el3
= SubElement(el2
, 'number:minutes', attrib
={
1161 'number:style': 'long',
1163 el3
= SubElement(el2
, 'number:text')
1165 el3
= SubElement(el2
, 'number:seconds', attrib
={
1166 'number:style': 'long',
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')
1185 el3
= SubElement(el2
, 'number:minutes', attrib
={
1186 'number:style': 'long',
1188 el3
= SubElement(el2
, 'number:text')
1190 el3
= SubElement(el2
, 'number:am-pm')
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')
1208 el3
= SubElement(el2
, 'number:minutes', attrib
={
1209 'number:style': 'long',
1211 el3
= SubElement(el2
, 'number:text')
1213 el3
= SubElement(el2
, 'number:seconds', attrib
={
1214 'number:style': 'long',
1216 el3
= SubElement(el2
, 'number:text')
1218 el3
= SubElement(el2
, 'number:am-pm')
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')
1236 el3
= SubElement(el2
, 'number:day', attrib
={
1237 'number:style': 'long',
1239 el3
= SubElement(el2
, 'number:text')
1241 el3
= SubElement(el2
, 'number:year')
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')
1259 el3
= SubElement(el2
, 'number:day', attrib
={
1260 'number:style': 'long',
1262 el3
= SubElement(el2
, 'number:text')
1264 el3
= SubElement(el2
, 'number:year', attrib
={
1265 'number:style': 'long',
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')
1284 el3
= SubElement(el2
, 'number:day', attrib
={
1286 el3
= SubElement(el2
, 'number:text')
1288 el3
= SubElement(el2
, 'number:year', attrib
={
1289 'number:style': 'long',
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')
1309 el3
= SubElement(el2
, 'number:day', attrib
={
1311 el3
= SubElement(el2
, 'number:text')
1313 el3
= SubElement(el2
, 'number:year', attrib
={
1314 'number:style': 'long',
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')
1332 el3
= SubElement(el2
, 'number:month', attrib
={
1333 'number:style': 'long',
1335 el3
= SubElement(el2
, 'number:text')
1337 el3
= SubElement(el2
, 'number:day', attrib
={
1338 'number:style': 'long',
1341 el1
= SubElement(parent
, 'text:subject', attrib
={
1342 'text:style-name': self
.rststyle(style_name
),
1345 el1
= SubElement(parent
, 'text:title', attrib
={
1346 'text:style-name': self
.rststyle(style_name
),
1349 el1
= SubElement(parent
, 'text:author-name', attrib
={
1350 'text:fixed': 'false',
1356 def split_field_specifiers_iter(self
, text
):
1360 mo
= ODFTranslator
.field_pat
.search(text
, pos1
)
1364 yield (ODFTranslator
.code_text
, text
[pos1
:pos2
])
1365 yield (ODFTranslator
.code_field
, mo
.group(1))
1369 trailing
= text
[pos1
:]
1371 yield (ODFTranslator
.code_text
, trailing
)
1375 root
= self
.content_tree
.getroot()
1376 et
= etree
.ElementTree(root
)
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', [])
1394 for ref
in backrefs
:
1395 el2
= self
.footnote_ref_dict
.get(ref
)
1399 el3
= copy
.deepcopy(el1
)
1402 children
= el2
.getchildren()
1403 if len(children
) > 0: # and 'id' in el2.attrib:
1406 attribkey
= add_ns('text:id', nsdict
=SNSD
)
1407 id1
= el2
.get(attribkey
, 'footnote-error')
1410 tag
= add_ns('text:note-ref', nsdict
=SNSD
)
1412 if self
.settings
.endnotes_end_doc
:
1413 note_class
= 'endnote'
1415 note_class
= 'footnote'
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'
1428 def append_child(self
, tag
, attrib
=None, parent
=None):
1430 parent
= self
.current_element
1432 el
= SubElement(parent
, tag
)
1434 el
= SubElement(parent
, tag
, attrib
)
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:
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
={
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')})
1464 el
= self
.append_p('blockindent')
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')})
1473 el1
.tail
= node
.astext()
1476 def encode(self
, text
):
1477 text
= text
.replace(u
'\u00a0', " ")
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
):
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
1513 self
.current_element
.getchildren()[-1].tail
= text
1515 if self
.current_element
.text
:
1516 self
.current_element
.text
+= text
1518 self
.current_element
.text
= text
1520 def depart_Text(self
, node
):
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')
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')})
1551 def depart_authors(self
, node
):
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
):
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
):
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()
1602 def visit_attribution(self
, node
):
1603 el
= self
.append_p('attribution', node
.astext())
1605 def depart_attribution(self
, node
):
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')
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
):
1627 if self
.in_table_of_contents
:
1628 if self
.settings
.generate_oowriter_toc
:
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'))
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
)
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'))
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
:
1674 self
.set_to_parent()
1675 self
.list_style_stack
.pop()
1677 self
.set_to_parent()
1678 self
.list_style_stack
.pop()
1681 def visit_caption(self
, node
):
1682 raise nodes
.SkipChildren()
1685 def depart_caption(self
, node
):
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', '')
1694 el2
= SubElement(el1
, 'text:p', attrib
={})
1695 el2
.text
= node
.astext()
1697 def depart_comment(self
, node
):
1700 def visit_compound(self
, node
):
1701 # The compound directive currently receives no special treatment.
1704 def depart_compound(self
, node
):
1707 def visit_container(self
, node
):
1708 styles
= node
.attributes
.get('classes', ())
1710 self
.paragraph_style_stack
.append(self
.rststyle(styles
[0]))
1712 def depart_container(self
, node
):
1713 styles
= node
.attributes
.get('classes', ())
1715 self
.paragraph_style_stack
.pop()
1717 def visit_decoration(self
, node
):
1720 def depart_decoration(self
, node
):
1723 def visit_definition_list(self
, node
):
1724 self
.def_list_level
+=1
1725 if self
.list_level
> 5:
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
):
1735 def depart_definition_list_item(self
, node
):
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()
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
):
1768 def visit_document(self
, node
):
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'))
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
, )))
1835 el1
= self
.append_child('text:list-item')
1836 self
.set_current_element(el1
)
1838 el1
= self
.append_child('text:list-item')
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()
1857 self
.set_to_parent()
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
):
1885 def depart_field(self
, node
):
1888 def visit_field_list(self
, node
):
1891 def depart_field_list(self
, node
):
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
):
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
):
1912 def depart_figure(self
, node
):
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'))
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
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')
1944 if self
.settings
.endnotes_end_doc
:
1945 note_class
= 'endnote'
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
,
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
):
1981 def visit_citation(self
, node
):
1982 self
.in_citation
= True
1983 for id in node
.attributes
['ids']:
1984 self
.citation_id
= id
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',
2003 self
.set_current_element(el
)
2004 elif self
.current_element
.text
is None:
2005 self
.current_element
.text
= '['
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')
2023 el1
= self
.append_child('text:reference-mark-start', attrib
={
2024 'text:name': '%s' % (self
.citation_id
, ),
2029 def depart_label(self
, node
):
2030 if isinstance(node
.parent
, docutils
.nodes
.footnote
):
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')
2040 self
.current_element
.text
+= ']'
2041 self
.set_to_parent()
2043 def visit_generated(self
, node
):
2046 def depart_generated(self
, node
):
2049 def check_file_exists(self
, path
):
2050 if os
.path
.exists(path
):
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
)
2063 dirname
= os
.path
.dirname(docsource
)
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
, ))
2072 if source
in self
.image_dict
:
2073 filename
, destination
= self
.image_dict
[source
]
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:'):
2080 imgfile
= urllib2
.urlopen(source
)
2081 content
= imgfile
.read()
2083 imgfile2
= tempfile
.NamedTemporaryFile('wb', delete
=False)
2084 imgfile2
.write(content
)
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
,)
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
2100 el1
= SubElement(self
.current_element
, 'text:p',
2101 attrib
={'text:style-name': self
.rststyle('textbody')})
2103 if isinstance(node
.parent
, docutils
.nodes
.figure
):
2104 el3
, el4
, el5
, caption
= self
.generate_figure(node
, source
,
2107 el6
, width
= self
.generate_image(node
, source
, destination
,
2109 if caption
is not None:
2111 else: #if isinstance(node.parent, docutils.nodes.image):
2112 el3
= self
.generate_image(node
, source
, destination
, el2
)
2114 def depart_image(self
, node
):
2117 def get_image_width_height(self
, node
, attr
):
2119 if attr
in node
.attributes
:
2120 size
= node
.attributes
[attr
]
2128 except ValueError, e
:
2129 self
.document
.reporter
.warning(
2130 'Invalid %s for image: "%s"' % (
2131 attr
, node
.attributes
[attr
]))
2135 def get_image_scale(self
, node
):
2136 if 'scale' in node
.attributes
:
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
, ))
2143 scale
= scale
* 0.01
2144 except ValueError, e
:
2145 self
.document
.reporter
.warning(
2146 'Invalid scale for image: "%s"' % (
2147 node
.attributes
['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')
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
2164 except: dpi
= (dpi
, dpi
)
2168 if width
is None or height
is None:
2169 if imageobj
is None:
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']
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
):
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:
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
)
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
)
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
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
)
2233 if 'align' in node
.attributes
:
2234 align
= node
.attributes
['align'].split()
2236 if val
in ('left', 'center', 'right'):
2238 elif val
in ('top', 'middle', 'bottom'):
2242 classes
= node
.parent
.attributes
.get('classes')
2243 if classes
and 'wrap' in classes
:
2246 attrib
['style:wrap'] = 'dynamic'
2248 attrib
['style:wrap'] = 'none'
2249 el2
= SubElement(el1
,
2250 'style:graphic-properties', attrib
=attrib
, nsdict
=SNSD
)
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
)
2260 el4
= SubElement(el3
, 'draw:text-box', attrib
=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
,
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
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
)
2282 if 'align' in node
.attributes
:
2283 align
= node
.attributes
['align'].split()
2285 if val
in ('left', 'center', 'right'):
2287 elif val
in ('top', 'middle', 'bottom'):
2289 if frame_attrs
is None:
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%',
2301 'draw:gamma': '100%',
2302 'draw:color-inversion': 'false',
2303 'draw:image-opacity': '100%',
2304 'draw:color-mode': 'standard',
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.
2315 classes
= node
.attributes
.get('classes')
2316 if classes
and 'wrap' in classes
:
2319 attrib
['style:wrap'] = 'dynamic'
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
)
2328 #el = SubElement(current_element, 'text:p',
2329 # attrib={'text:style-name': self.rststyle('textbody')})
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
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',
2350 def is_in_table(self
, node
):
2353 if isinstance(node1
, docutils
.nodes
.entry
):
2355 node1
= node1
.parent
2358 def visit_legend(self
, node
):
2359 if isinstance(node
.parent
, docutils
.nodes
.figure
):
2360 el1
= self
.current_element
[-1]
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', ())
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
):
2411 matchobj
= SPACES_PATTERN
.match(line
)
2413 pad
= matchobj
.group()
2416 matchobj
= TABS_PATTERN
.match(line
)
2418 pad
= matchobj
.group()
2419 count
= len(pad
) * 8
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
)
2429 fmtr
= OdtPygmentsProgFormatter(lambda name
, parameters
=():
2430 self
.rststyle(name
, parameters
),
2431 escape_function
=escape_cdata
)
2432 outsource
= pygments
.highlight(insource
, lexer
, fmtr
)
2435 def fill_line(self
, line
):
2436 line
= FILL_PAT1
.sub(self
.fill_func1
, line
)
2437 line
= FILL_PAT2
.sub(self
.fill_func2
, line
)
2440 def fill_func1(self
, matchobj
):
2441 spaces
= matchobj
.group(0)
2442 repl
= '<text:s text:c="%d"/>' % (len(spaces
), )
2445 def fill_func2(self
, matchobj
):
2446 spaces
= matchobj
.group(0)
2447 repl
= ' <text:s text:c="%d"/>' % (len(spaces
) - 1, )
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'), )
2455 wrapper1
= '<text:p text:style-name="%s">%%s</text:p>' % (
2456 self
.rststyle('codeblock'), )
2457 source
= node
.astext()
2459 self
.settings
.add_syntax_highlighting
2461 #node.get('hilight', False)
2463 language
= node
.get('language', 'python')
2464 source
= self
._add
_syntax
_highlighting
(source
, language
)
2466 source
= escape_cdata(source
)
2467 lines
= source
.split('\n')
2468 # If there is an empty last line, remove it.
2471 lines1
= ['<wrappertag1 xmlns:text="urn:oasis:names:tc:opendocument:xmlns:text:1.0">']
2474 for my_line
in lines
:
2475 my_line
= self
.fill_line(my_line
)
2476 my_line
= my_line
.replace(" ", "\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
):
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',
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',
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
):
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'})
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
):
2642 def visit_option_string(self
, node
):
2645 def depart_option_string(self
, node
):
2648 def visit_option_argument(self
, node
):
2651 def depart_option_argument(self
, node
):
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
):
2667 def visit_paragraph(self
, node
):
2668 self
.in_paragraph
= True
2670 el
= self
.append_p('header')
2671 elif self
.in_footer
:
2672 el
= self
.append_p('footer')
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()
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
):
2697 def depart_problematic(self
, node
):
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:
2717 elif self
.in_footer
:
2720 self
.current_element
.append(el1
)
2721 raise nodes
.SkipChildren()
2723 def depart_raw(self
, node
):
2726 elif self
.in_footer
:
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',
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')
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
):
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
):
2811 def visit_system_message(self
, node
):
2814 def depart_system_message(self
, node
):
2817 def get_table_style(self
, node
):
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
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.' % (
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.' % (
2844 table_style
= BUILTIN_DEFAULT_TABLE_STYLE
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.' % (
2854 table_style
= BUILTIN_DEFAULT_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',
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',
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
,
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',
2889 thickness
= self
.settings
.table_border_thickness
2890 if thickness
is None:
2891 line_style1
= table_style
.border
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
,
2902 for child
in node
.children
:
2903 if child
.tagname
== 'title':
2904 title
= child
.astext()
2906 if title
is not None:
2907 el3
= self
.append_p('table-title', title
)
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
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
):
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',
2945 el1_1
= SubElement(el1
, 'style:table-column-properties', attrib
={
2946 'style:column-width': '%.4fin' % colwidth
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
):
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, )
2983 'table:style-name': cellspec_name
,
2984 'office:value-type': 'string',
2986 morecols
= node
.get('morecols', 0)
2988 attrib
['table:number-columns-spanned'] = '%d' % (morecols
+ 1,)
2989 self
.column_count
+= morecols
2990 morerows
= node
.get('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
):
3002 def depart_tbody(self
, node
):
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')):
3015 def depart_target(self
, node
):
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"' % (
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
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()
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
):
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',
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
):
3088 for child
in node
.children
:
3089 if isinstance(child
, class_type
):
3092 if title_node
is not None:
3093 label
= title_node
.astext()
3095 label
= self
.language
.labels
[label_key
]
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
,
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',
3112 'text:outline-level': '10',
3114 el3
=SubElement(el2
, 'text:index-title-template', attrib
={
3115 'text:style-name': 'Contents_20_Heading',
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'),
3125 self
.save_current_element
= self
.current_element
3126 self
.table_of_content_index_body
= el4
3127 self
.set_current_element(el4
)
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')})
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
,
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
,
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
)
3160 el
= self
.append_p('horizontalline')
3161 self
.in_table_of_contents
= False
3163 def update_toc_page_numbers(self
, el
):
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
))
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')
3183 def visit_transition(self
, node
):
3184 el
= self
.append_p('horizontalline')
3186 def depart_transition(self
, node
):
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
):
3240 for child
in node
.children
:
3241 if child
.tagname
== 'title':
3242 title
= child
.astext()
3244 classes1
= node
.get('classes')
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
, )),
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
:
3293 if i
is not references
.DanglingReferences
]