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
29 from docutils
import frontend
, nodes
, utils
, writers
, languages
30 from docutils
.parsers
import rst
31 from docutils
.readers
import standalone
32 from docutils
.transforms
import references
38 #from lxml import etree
39 #WhichElementTree = 'lxml'
40 raise ImportError('Ignoring lxml')
41 except ImportError, e
:
43 # 2. Try to use ElementTree from the Python standard library.
44 from xml
.etree
import ElementTree
as etree
45 WhichElementTree
= 'elementtree'
46 except ImportError, e
:
48 # 3. Try to use a version of ElementTree installed as a separate
50 from elementtree
import ElementTree
as etree
51 WhichElementTree
= 'elementtree'
52 except ImportError, e
:
53 s1
= 'Must install either a version of Python containing ' \
54 'ElementTree (Python version >=2.5) or install ElementTree.'
58 # Import pygments and odtwriter pygments formatters if possible.
61 import pygments
.lexers
62 from pygmentsformatter
import OdtPygmentsProgFormatter
, \
63 OdtPygmentsLaTeXFormatter
64 except ImportError, exp
:
67 try: # check for the Python Imaging Library
70 try: # sometimes PIL modules are put in PYTHONPATH's root
72 class PIL(object): pass # dummy wrapper
78 ## warnings.warn('importing IPShellEmbed', UserWarning)
79 ## from IPython.Shell import IPShellEmbed
80 ## args = ['-pdb', '-pi1', 'In <\\#>: ', '-pi2', ' .\\D.: ',
81 ## '-po', 'Out<\\#>: ', '-nosep']
82 ## ipshell = IPShellEmbed(args,
83 ## banner = 'Entering IPython. Press Ctrl-D to exit.',
84 ## exit_msg = 'Leaving Interpreter, back to program.')
88 # ElementTree does not support getparent method (lxml does).
89 # This wrapper class and the following support functions provide
90 # that support for the ability to get the parent of an element.
92 if WhichElementTree
== 'elementtree':
93 class _ElementInterfaceWrapper(etree
._ElementInterface
):
94 def __init__(self
, tag
, attrib
=None):
95 etree
._ElementInterface
.__init
__(self
, tag
, attrib
)
99 def setparent(self
, parent
):
106 # Constants and globals
108 SPACES_PATTERN
= re
.compile(r
'( +)')
109 TABS_PATTERN
= re
.compile(r
'(\t+)')
110 FILL_PAT1
= re
.compile(r
'^ +')
111 FILL_PAT2
= re
.compile(r
' {2,}')
113 TABLESTYLEPREFIX
= 'rststyle-table-'
114 TABLENAMEDEFAULT
= '%s0' % TABLESTYLEPREFIX
115 TABLEPROPERTYNAMES
= ('border', 'border-top', 'border-left',
116 'border-right', 'border-bottom', )
118 GENERATOR_DESC
= 'Docutils.org/odf_odt'
120 NAME_SPACE_1
= 'urn:oasis:names:tc:opendocument:xmlns:office:1.0'
122 CONTENT_NAMESPACE_DICT
= CNSD
= {
123 # 'office:version': '1.0',
124 'chart': 'urn:oasis:names:tc:opendocument:xmlns:chart:1.0',
125 'dc': 'http://purl.org/dc/elements/1.1/',
126 'dom': 'http://www.w3.org/2001/xml-events',
127 'dr3d': 'urn:oasis:names:tc:opendocument:xmlns:dr3d:1.0',
128 'draw': 'urn:oasis:names:tc:opendocument:xmlns:drawing:1.0',
129 'fo': 'urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0',
130 'form': 'urn:oasis:names:tc:opendocument:xmlns:form:1.0',
131 'math': 'http://www.w3.org/1998/Math/MathML',
132 'meta': 'urn:oasis:names:tc:opendocument:xmlns:meta:1.0',
133 'number': 'urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0',
134 'office': NAME_SPACE_1
,
135 'ooo': 'http://openoffice.org/2004/office',
136 'oooc': 'http://openoffice.org/2004/calc',
137 'ooow': 'http://openoffice.org/2004/writer',
138 'presentation': 'urn:oasis:names:tc:opendocument:xmlns:presentation:1.0',
140 'script': 'urn:oasis:names:tc:opendocument:xmlns:script:1.0',
141 'style': 'urn:oasis:names:tc:opendocument:xmlns:style:1.0',
142 'svg': 'urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0',
143 'table': 'urn:oasis:names:tc:opendocument:xmlns:table:1.0',
144 'text': 'urn:oasis:names:tc:opendocument:xmlns:text:1.0',
145 'xforms': 'http://www.w3.org/2002/xforms',
146 'xlink': 'http://www.w3.org/1999/xlink',
147 'xsd': 'http://www.w3.org/2001/XMLSchema',
148 'xsi': 'http://www.w3.org/2001/XMLSchema-instance',
151 STYLES_NAMESPACE_DICT
= SNSD
= {
152 # 'office:version': '1.0',
153 'chart': 'urn:oasis:names:tc:opendocument:xmlns:chart:1.0',
154 'dc': 'http://purl.org/dc/elements/1.1/',
155 'dom': 'http://www.w3.org/2001/xml-events',
156 'dr3d': 'urn:oasis:names:tc:opendocument:xmlns:dr3d:1.0',
157 'draw': 'urn:oasis:names:tc:opendocument:xmlns:drawing:1.0',
158 'fo': 'urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0',
159 'form': 'urn:oasis:names:tc:opendocument:xmlns:form:1.0',
160 'math': 'http://www.w3.org/1998/Math/MathML',
161 'meta': 'urn:oasis:names:tc:opendocument:xmlns:meta:1.0',
162 'number': 'urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0',
163 'office': NAME_SPACE_1
,
164 'presentation': 'urn:oasis:names:tc:opendocument:xmlns:presentation:1.0',
165 'ooo': 'http://openoffice.org/2004/office',
166 'oooc': 'http://openoffice.org/2004/calc',
167 'ooow': 'http://openoffice.org/2004/writer',
168 'script': 'urn:oasis:names:tc:opendocument:xmlns:script:1.0',
169 'style': 'urn:oasis:names:tc:opendocument:xmlns:style:1.0',
170 'svg': 'urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0',
171 'table': 'urn:oasis:names:tc:opendocument:xmlns:table:1.0',
172 'text': 'urn:oasis:names:tc:opendocument:xmlns:text:1.0',
173 'xlink': 'http://www.w3.org/1999/xlink',
176 MANIFEST_NAMESPACE_DICT
= MANNSD
= {
177 'manifest': 'urn:oasis:names:tc:opendocument:xmlns:manifest:1.0',
180 META_NAMESPACE_DICT
= METNSD
= {
181 # 'office:version': '1.0',
182 'dc': 'http://purl.org/dc/elements/1.1/',
183 'meta': 'urn:oasis:names:tc:opendocument:xmlns:meta:1.0',
184 'office': NAME_SPACE_1
,
185 'ooo': 'http://openoffice.org/2004/office',
186 'xlink': 'http://www.w3.org/1999/xlink',
190 # Attribute dictionaries for use with ElementTree (not lxml), which
191 # does not support use of nsmap parameter on Element() and SubElement().
193 CONTENT_NAMESPACE_ATTRIB
= {
194 #'office:version': '1.0',
195 'xmlns:chart': 'urn:oasis:names:tc:opendocument:xmlns:chart:1.0',
196 'xmlns:dc': 'http://purl.org/dc/elements/1.1/',
197 'xmlns:dom': 'http://www.w3.org/2001/xml-events',
198 'xmlns:dr3d': 'urn:oasis:names:tc:opendocument:xmlns:dr3d:1.0',
199 'xmlns:draw': 'urn:oasis:names:tc:opendocument:xmlns:drawing:1.0',
200 'xmlns:fo': 'urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0',
201 'xmlns:form': 'urn:oasis:names:tc:opendocument:xmlns:form:1.0',
202 'xmlns:math': 'http://www.w3.org/1998/Math/MathML',
203 'xmlns:meta': 'urn:oasis:names:tc:opendocument:xmlns:meta:1.0',
204 'xmlns:number': 'urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0',
205 'xmlns:office': NAME_SPACE_1
,
206 'xmlns:presentation': 'urn:oasis:names:tc:opendocument:xmlns:presentation:1.0',
207 'xmlns:ooo': 'http://openoffice.org/2004/office',
208 'xmlns:oooc': 'http://openoffice.org/2004/calc',
209 'xmlns:ooow': 'http://openoffice.org/2004/writer',
210 'xmlns:script': 'urn:oasis:names:tc:opendocument:xmlns:script:1.0',
211 'xmlns:style': 'urn:oasis:names:tc:opendocument:xmlns:style:1.0',
212 'xmlns:svg': 'urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0',
213 'xmlns:table': 'urn:oasis:names:tc:opendocument:xmlns:table:1.0',
214 'xmlns:text': 'urn:oasis:names:tc:opendocument:xmlns:text:1.0',
215 'xmlns:xforms': 'http://www.w3.org/2002/xforms',
216 'xmlns:xlink': 'http://www.w3.org/1999/xlink',
217 'xmlns:xsd': 'http://www.w3.org/2001/XMLSchema',
218 'xmlns:xsi': 'http://www.w3.org/2001/XMLSchema-instance',
221 STYLES_NAMESPACE_ATTRIB
= {
222 #'office:version': '1.0',
223 'xmlns:chart': 'urn:oasis:names:tc:opendocument:xmlns:chart:1.0',
224 'xmlns:dc': 'http://purl.org/dc/elements/1.1/',
225 'xmlns:dom': 'http://www.w3.org/2001/xml-events',
226 'xmlns:dr3d': 'urn:oasis:names:tc:opendocument:xmlns:dr3d:1.0',
227 'xmlns:draw': 'urn:oasis:names:tc:opendocument:xmlns:drawing:1.0',
228 'xmlns:fo': 'urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0',
229 'xmlns:form': 'urn:oasis:names:tc:opendocument:xmlns:form:1.0',
230 'xmlns:math': 'http://www.w3.org/1998/Math/MathML',
231 'xmlns:meta': 'urn:oasis:names:tc:opendocument:xmlns:meta:1.0',
232 'xmlns:number': 'urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0',
233 'xmlns:office': NAME_SPACE_1
,
234 'xmlns:presentation': 'urn:oasis:names:tc:opendocument:xmlns:presentation:1.0',
235 'xmlns:ooo': 'http://openoffice.org/2004/office',
236 'xmlns:oooc': 'http://openoffice.org/2004/calc',
237 'xmlns:ooow': 'http://openoffice.org/2004/writer',
238 'xmlns:script': 'urn:oasis:names:tc:opendocument:xmlns:script:1.0',
239 'xmlns:style': 'urn:oasis:names:tc:opendocument:xmlns:style:1.0',
240 'xmlns:svg': 'urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0',
241 'xmlns:table': 'urn:oasis:names:tc:opendocument:xmlns:table:1.0',
242 'xmlns:text': 'urn:oasis:names:tc:opendocument:xmlns:text:1.0',
243 'xmlns:xlink': 'http://www.w3.org/1999/xlink',
246 MANIFEST_NAMESPACE_ATTRIB
= {
247 'xmlns:manifest': 'urn:oasis:names:tc:opendocument:xmlns:manifest:1.0',
250 META_NAMESPACE_ATTRIB
= {
251 #'office:version': '1.0',
252 'xmlns:dc': 'http://purl.org/dc/elements/1.1/',
253 'xmlns:meta': 'urn:oasis:names:tc:opendocument:xmlns:meta:1.0',
254 'xmlns:office': NAME_SPACE_1
,
255 'xmlns:ooo': 'http://openoffice.org/2004/office',
256 'xmlns:xlink': 'http://www.w3.org/1999/xlink',
265 # ElementTree support functions.
266 # In order to be able to get the parent of elements, must use these
267 # instead of the functions with same name provided by ElementTree.
269 def Element(tag
, attrib
=None, nsmap
=None, nsdict
=CNSD
):
272 tag
, attrib
= fix_ns(tag
, attrib
, nsdict
)
273 if WhichElementTree
== 'lxml':
274 el
= etree
.Element(tag
, attrib
, nsmap
=nsmap
)
276 el
= _ElementInterfaceWrapper(tag
, attrib
)
279 def SubElement(parent
, tag
, attrib
=None, nsmap
=None, nsdict
=CNSD
):
282 tag
, attrib
= fix_ns(tag
, attrib
, nsdict
)
283 if WhichElementTree
== 'lxml':
284 el
= etree
.SubElement(parent
, tag
, attrib
, nsmap
=nsmap
)
286 el
= _ElementInterfaceWrapper(tag
, attrib
)
291 def fix_ns(tag
, attrib
, nsdict
):
292 nstag
= add_ns(tag
, nsdict
)
294 for key
, val
in attrib
.iteritems():
295 nskey
= add_ns(key
, nsdict
)
296 nsattrib
[nskey
] = val
297 return nstag
, nsattrib
299 def add_ns(tag
, nsdict
=CNSD
):
300 if WhichElementTree
== 'lxml':
301 nstag
, name
= tag
.split(':')
302 ns
= nsdict
.get(nstag
)
304 raise RuntimeError, 'Invalid namespace prefix: %s' % nstag
305 tag
= '{%s}%s' % (ns
, name
,)
309 outstream
= StringIO
.StringIO()
310 if sys
.version_info
>= (3, 2):
311 et
.write(outstream
, encoding
="unicode")
314 s1
= outstream
.getvalue()
319 def escape_cdata(text
):
320 text
= text
.replace("&", "&")
321 text
= text
.replace("<", "<")
322 text
= text
.replace(">", ">")
325 if ord(char
) >= ord("\x7f"):
326 ascii
+= "&#x%X;" % ( ord(char
), )
333 WORD_SPLIT_PAT1
= re
.compile(r
'\b(\w*)\b\W*')
335 def split_words(line
):
336 # We need whitespace at the end of the string for our regexpr.
340 mo
= WORD_SPLIT_PAT1
.search(line
, pos1
)
341 while mo
is not None:
342 word
= mo
.groups()[0]
345 mo
= WORD_SPLIT_PAT1
.search(line
, pos1
)
354 class TableStyle(object):
355 def __init__(self
, border
=None, backgroundcolor
=None):
357 self
.backgroundcolor
= backgroundcolor
358 def get_border_(self
):
360 def set_border_(self
, border
):
361 self
.border_
= border
362 border
= property(get_border_
, set_border_
)
363 def get_backgroundcolor_(self
):
364 return self
.backgroundcolor_
365 def set_backgroundcolor_(self
, backgroundcolor
):
366 self
.backgroundcolor_
= backgroundcolor
367 backgroundcolor
= property(get_backgroundcolor_
, set_backgroundcolor_
)
369 BUILTIN_DEFAULT_TABLE_STYLE
= TableStyle(
370 border
= '0.0007in solid #000000')
373 # Information about the indentation level for lists nested inside
374 # other contexts, e.g. dictionary lists.
375 class ListLevel(object):
376 def __init__(self
, level
, sibling_level
=True, nested_level
=True):
378 self
.sibling_level
= sibling_level
379 self
.nested_level
= nested_level
380 def set_sibling(self
, sibling_level
): self
.sibling_level
= sibling_level
381 def get_sibling(self
): return self
.sibling_level
382 def set_nested(self
, nested_level
): self
.nested_level
= nested_level
383 def get_nested(self
): return self
.nested_level
384 def set_level(self
, level
): self
.level
= level
385 def get_level(self
): return self
.level
388 class Writer(writers
.Writer
):
390 MIME_TYPE
= 'application/vnd.oasis.opendocument.text'
393 supported
= ('odt', )
394 """Formats this writer supports."""
396 default_stylesheet
= 'styles' + EXTENSION
398 default_stylesheet_path
= utils
.relative_path(
399 os
.path
.join(os
.getcwd(), 'dummy'),
400 os
.path
.join(os
.path
.dirname(__file__
), default_stylesheet
))
402 default_template
= 'template.txt'
404 default_template_path
= utils
.relative_path(
405 os
.path
.join(os
.getcwd(), 'dummy'),
406 os
.path
.join(os
.path
.dirname(__file__
), default_template
))
409 'ODF-Specific Options',
412 ('Specify a stylesheet. '
413 'Default: "%s"' % default_stylesheet_path
,
416 'default': default_stylesheet_path
,
419 ('Specify a configuration/mapping file relative to the '
421 'directory for additional ODF options. '
422 'In particular, this file may contain a section named '
423 '"Formats" that maps default style names to '
424 'names to be used in the resulting output file allowing for '
425 'adhering to external standards. '
426 'For more info and the format of the configuration/mapping file, '
427 'see the odtwriter doc.',
428 ['--odf-config-file'],
429 {'metavar': '<file>'}),
430 ('Obfuscate email addresses to confuse harvesters while still '
431 'keeping email links usable with standards-compliant browsers.',
432 ['--cloak-email-addresses'],
434 'action': 'store_true',
435 'dest': 'cloak_email_addresses',
436 'validator': frontend
.validate_boolean
}),
437 ('Do not obfuscate email addresses.',
438 ['--no-cloak-email-addresses'],
440 'action': 'store_false',
441 'dest': 'cloak_email_addresses',
442 'validator': frontend
.validate_boolean
}),
443 ('Specify the thickness of table borders in thousands of a cm. '
445 ['--table-border-thickness'],
447 'validator': frontend
.validate_nonnegative_int
}),
448 ('Add syntax highlighting in literal code blocks.',
449 ['--add-syntax-highlighting'],
451 'action': 'store_true',
452 'dest': 'add_syntax_highlighting',
453 'validator': frontend
.validate_boolean
}),
454 ('Do not add syntax highlighting in literal code blocks. (default)',
455 ['--no-syntax-highlighting'],
457 'action': 'store_false',
458 'dest': 'add_syntax_highlighting',
459 'validator': frontend
.validate_boolean
}),
460 ('Create sections for headers. (default)',
461 ['--create-sections'],
463 'action': 'store_true',
464 'dest': 'create_sections',
465 'validator': frontend
.validate_boolean
}),
466 ('Do not create sections for headers.',
469 'action': 'store_false',
470 'dest': 'create_sections',
471 'validator': frontend
.validate_boolean
}),
475 'action': 'store_true',
476 'dest': 'create_links',
477 'validator': frontend
.validate_boolean
}),
478 ('Do not create links. (default)',
481 'action': 'store_false',
482 'dest': 'create_links',
483 'validator': frontend
.validate_boolean
}),
484 ('Generate endnotes at end of document, not footnotes '
485 'at bottom of page.',
486 ['--endnotes-end-doc'],
488 'action': 'store_true',
489 'dest': 'endnotes_end_doc',
490 'validator': frontend
.validate_boolean
}),
491 ('Generate footnotes at bottom of page, not endnotes '
492 'at end of document. (default)',
493 ['--no-endnotes-end-doc'],
495 'action': 'store_false',
496 'dest': 'endnotes_end_doc',
497 'validator': frontend
.validate_boolean
}),
498 ('Generate a bullet list table of contents, not '
499 'an ODF/oowriter table of contents.',
500 ['--generate-list-toc'],
502 'action': 'store_false',
503 'dest': 'generate_oowriter_toc',
504 'validator': frontend
.validate_boolean
}),
505 ('Generate an ODF/oowriter table of contents, not '
506 'a bullet list. (default)',
507 ['--generate-oowriter-toc'],
509 'action': 'store_true',
510 'dest': 'generate_oowriter_toc',
511 'validator': frontend
.validate_boolean
}),
512 ('Specify the contents of an custom header line. '
513 'See odf_odt writer documentation for details '
514 'about special field character sequences.',
515 ['--custom-odt-header'],
517 'dest': 'custom_header',
519 ('Specify the contents of an custom footer line. '
520 'See odf_odt writer documentation for details '
521 'about special field character sequences.',
522 ['--custom-odt-footer'],
524 'dest': 'custom_footer',
529 settings_defaults
= {
530 'output_encoding_error_handler': 'xmlcharrefreplace',
533 relative_path_settings
= (
537 config_section
= 'opendocument odf writer'
538 config_section_dependencies
= (
543 writers
.Writer
.__init
__(self
)
544 self
.translator_class
= ODFTranslator
547 self
.settings
= self
.document
.settings
548 self
.visitor
= self
.translator_class(self
.document
)
549 self
.visitor
.retrieve_styles(self
.EXTENSION
)
550 self
.document
.walkabout(self
.visitor
)
551 self
.visitor
.add_doc_title()
552 self
.assemble_my_parts()
553 self
.output
= self
.parts
['whole']
555 def assemble_my_parts(self
):
556 """Assemble the `self.parts` dictionary. Extend in subclasses.
558 writers
.Writer
.assemble_parts(self
)
559 f
= tempfile
.NamedTemporaryFile()
560 zfile
= zipfile
.ZipFile(f
, 'w', zipfile
.ZIP_DEFLATED
)
561 self
.write_zip_str(zfile
, 'mimetype', self
.MIME_TYPE
,
562 compress_type
=zipfile
.ZIP_STORED
)
563 content
= self
.visitor
.content_astext()
564 self
.write_zip_str(zfile
, 'content.xml', content
)
565 s1
= self
.create_manifest()
566 self
.write_zip_str(zfile
, 'META-INF/manifest.xml', s1
)
567 s1
= self
.create_meta()
568 self
.write_zip_str(zfile
, 'meta.xml', s1
)
569 s1
= self
.get_stylesheet()
570 self
.write_zip_str(zfile
, 'styles.xml', s1
)
571 self
.store_embedded_files(zfile
)
572 self
.copy_from_stylesheet(zfile
)
577 self
.parts
['whole'] = whole
578 self
.parts
['encoding'] = self
.document
.settings
.output_encoding
579 self
.parts
['version'] = docutils
.__version
__
581 def write_zip_str(self
, zfile
, name
, bytes
, compress_type
=zipfile
.ZIP_DEFLATED
):
582 localtime
= time
.localtime(time
.time())
583 zinfo
= zipfile
.ZipInfo(name
, localtime
)
584 # Add some standard UNIX file access permissions (-rw-r--r--).
585 zinfo
.external_attr
= (0x81a4 & 0xFFFF) << 16L
586 zinfo
.compress_type
= compress_type
587 zfile
.writestr(zinfo
, bytes
)
589 def store_embedded_files(self
, zfile
):
590 embedded_files
= self
.visitor
.get_embedded_file_list()
591 for source
, destination
in embedded_files
:
596 destination1
= destination
.decode('latin-1').encode('utf-8')
597 zfile
.write(source
, destination1
)
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
)
950 def rststyle(self
, name
, parameters
=( )):
952 Returns the style name to use for the given style.
954 If `parameters` is given `name` must contain a matching number of ``%`` and
955 is used as a format expression with `parameters` as the value.
957 name1
= name
% parameters
958 stylename
= self
.format_map
.get(name1
, 'rststyle-%s' % name1
)
961 def generate_content_element(self
, root
):
962 return SubElement(root
, 'office:text')
964 def setup_page(self
):
965 self
.setup_paper(self
.dom_stylesheet
)
966 if (len(self
.header_content
) > 0 or len(self
.footer_content
) > 0 or
967 self
.settings
.custom_header
or self
.settings
.custom_footer
):
968 self
.add_header_footer(self
.dom_stylesheet
)
969 new_content
= etree
.tostring(self
.dom_stylesheet
)
972 def setup_paper(self
, root_el
):
974 fin
= os
.popen("paperconf -s 2> /dev/null")
975 w
, h
= map(float, fin
.read().split())
978 w
, h
= 612, 792 # default to Letter
980 if el
.tag
== "{%s}page-layout-properties" % SNSD
["style"] and \
981 not el
.attrib
.has_key("{%s}page-width" % SNSD
["fo"]):
982 el
.attrib
["{%s}page-width" % SNSD
["fo"]] = "%.3fpt" % w
983 el
.attrib
["{%s}page-height" % SNSD
["fo"]] = "%.3fpt" % h
984 el
.attrib
["{%s}margin-left" % SNSD
["fo"]] = \
985 el
.attrib
["{%s}margin-right" % SNSD
["fo"]] = \
987 el
.attrib
["{%s}margin-top" % SNSD
["fo"]] = \
988 el
.attrib
["{%s}margin-bottom" % SNSD
["fo"]] = \
991 for subel
in el
.getchildren(): walk(subel
)
994 def add_header_footer(self
, root_el
):
995 automatic_styles
= root_el
.find(
996 '{%s}automatic-styles' % SNSD
['office'])
997 path
= '{%s}master-styles' % (NAME_SPACE_1
, )
998 master_el
= root_el
.find(path
)
999 if master_el
is None:
1001 path
= '{%s}master-page' % (SNSD
['style'], )
1002 master_el
= master_el
.find(path
)
1003 if master_el
is None:
1006 if self
.header_content
or self
.settings
.custom_header
:
1007 if WhichElementTree
== 'lxml':
1008 el2
= SubElement(el1
, 'style:header', nsdict
=SNSD
)
1010 el2
= SubElement(el1
, 'style:header',
1011 attrib
=STYLES_NAMESPACE_ATTRIB
,
1012 nsdict
=STYLES_NAMESPACE_DICT
,
1014 for el
in self
.header_content
:
1015 attrkey
= add_ns('text:style-name', nsdict
=SNSD
)
1016 el
.attrib
[attrkey
] = self
.rststyle('header')
1018 if self
.settings
.custom_header
:
1019 elcustom
= self
.create_custom_headfoot(el2
,
1020 self
.settings
.custom_header
, 'header', automatic_styles
)
1021 if self
.footer_content
or self
.settings
.custom_footer
:
1022 if WhichElementTree
== 'lxml':
1023 el2
= SubElement(el1
, 'style:footer', nsdict
=SNSD
)
1025 el2
= SubElement(el1
, 'style:footer',
1026 attrib
=STYLES_NAMESPACE_ATTRIB
,
1027 nsdict
=STYLES_NAMESPACE_DICT
,
1029 for el
in self
.footer_content
:
1030 attrkey
= add_ns('text:style-name', nsdict
=SNSD
)
1031 el
.attrib
[attrkey
] = self
.rststyle('footer')
1033 if self
.settings
.custom_footer
:
1034 elcustom
= self
.create_custom_headfoot(el2
,
1035 self
.settings
.custom_footer
, 'footer', automatic_styles
)
1037 code_none
, code_field
, code_text
= range(3)
1038 field_pat
= re
.compile(r
'%(..?)%')
1040 def create_custom_headfoot(self
, parent
, text
, style_name
, automatic_styles
):
1041 parent
= SubElement(parent
, 'text:p', attrib
={
1042 'text:style-name': self
.rststyle(style_name
),
1044 current_element
= None
1045 field_iter
= self
.split_field_specifiers_iter(text
)
1046 for item
in field_iter
:
1047 if item
[0] == ODFTranslator
.code_field
:
1048 if item
[1] not in ('p', 'P',
1049 't1', 't2', 't3', 't4',
1050 'd1', 'd2', 'd3', 'd4', 'd5',
1052 msg
= 'bad field spec: %%%s%%' % (item
[1], )
1053 raise RuntimeError, msg
1054 el1
= self
.make_field_element(parent
,
1055 item
[1], style_name
, automatic_styles
)
1057 msg
= 'bad field spec: %%%s%%' % (item
[1], )
1058 raise RuntimeError, msg
1060 current_element
= el1
1062 if current_element
is None:
1063 parent
.text
= item
[1]
1065 current_element
.tail
= item
[1]
1067 def make_field_element(self
, parent
, text
, style_name
, automatic_styles
):
1069 el1
= SubElement(parent
, 'text:page-number', attrib
={
1070 #'text:style-name': self.rststyle(style_name),
1071 'text:select-page': 'current',
1074 el1
= SubElement(parent
, 'text:page-count', attrib
={
1075 #'text:style-name': self.rststyle(style_name),
1078 self
.style_index
+= 1
1079 el1
= SubElement(parent
, 'text:time', attrib
={
1080 'text:style-name': self
.rststyle(style_name
),
1081 'text:fixed': 'true',
1082 'style:data-style-name': 'rst-time-style-%d' % self
.style_index
,
1084 el2
= SubElement(automatic_styles
, 'number:time-style', attrib
={
1085 'style:name': 'rst-time-style-%d' % self
.style_index
,
1086 'xmlns:number': SNSD
['number'],
1087 'xmlns:style': SNSD
['style'],
1089 el3
= SubElement(el2
, 'number:hours', attrib
={
1090 'number:style': 'long',
1092 el3
= SubElement(el2
, 'number:text')
1094 el3
= SubElement(el2
, 'number:minutes', attrib
={
1095 'number:style': 'long',
1098 self
.style_index
+= 1
1099 el1
= SubElement(parent
, 'text:time', attrib
={
1100 'text:style-name': self
.rststyle(style_name
),
1101 'text:fixed': 'true',
1102 'style:data-style-name': 'rst-time-style-%d' % self
.style_index
,
1104 el2
= SubElement(automatic_styles
, 'number:time-style', attrib
={
1105 'style:name': 'rst-time-style-%d' % self
.style_index
,
1106 'xmlns:number': SNSD
['number'],
1107 'xmlns:style': SNSD
['style'],
1109 el3
= SubElement(el2
, 'number:hours', attrib
={
1110 'number:style': 'long',
1112 el3
= SubElement(el2
, 'number:text')
1114 el3
= SubElement(el2
, 'number:minutes', attrib
={
1115 'number:style': 'long',
1117 el3
= SubElement(el2
, 'number:text')
1119 el3
= SubElement(el2
, 'number:seconds', attrib
={
1120 'number:style': 'long',
1123 self
.style_index
+= 1
1124 el1
= SubElement(parent
, 'text:time', attrib
={
1125 'text:style-name': self
.rststyle(style_name
),
1126 'text:fixed': 'true',
1127 'style:data-style-name': 'rst-time-style-%d' % self
.style_index
,
1129 el2
= SubElement(automatic_styles
, 'number:time-style', attrib
={
1130 'style:name': 'rst-time-style-%d' % self
.style_index
,
1131 'xmlns:number': SNSD
['number'],
1132 'xmlns:style': SNSD
['style'],
1134 el3
= SubElement(el2
, 'number:hours', attrib
={
1135 'number:style': 'long',
1137 el3
= SubElement(el2
, 'number:text')
1139 el3
= SubElement(el2
, 'number:minutes', attrib
={
1140 'number:style': 'long',
1142 el3
= SubElement(el2
, 'number:text')
1144 el3
= SubElement(el2
, 'number:am-pm')
1146 self
.style_index
+= 1
1147 el1
= SubElement(parent
, 'text:time', attrib
={
1148 'text:style-name': self
.rststyle(style_name
),
1149 'text:fixed': 'true',
1150 'style:data-style-name': 'rst-time-style-%d' % self
.style_index
,
1152 el2
= SubElement(automatic_styles
, 'number:time-style', attrib
={
1153 'style:name': 'rst-time-style-%d' % self
.style_index
,
1154 'xmlns:number': SNSD
['number'],
1155 'xmlns:style': SNSD
['style'],
1157 el3
= SubElement(el2
, 'number:hours', attrib
={
1158 'number:style': 'long',
1160 el3
= SubElement(el2
, 'number:text')
1162 el3
= SubElement(el2
, 'number:minutes', attrib
={
1163 'number:style': 'long',
1165 el3
= SubElement(el2
, 'number:text')
1167 el3
= SubElement(el2
, 'number:seconds', attrib
={
1168 'number:style': 'long',
1170 el3
= SubElement(el2
, 'number:text')
1172 el3
= SubElement(el2
, 'number:am-pm')
1174 self
.style_index
+= 1
1175 el1
= SubElement(parent
, 'text:date', attrib
={
1176 'text:style-name': self
.rststyle(style_name
),
1177 'style:data-style-name': 'rst-date-style-%d' % self
.style_index
,
1179 el2
= SubElement(automatic_styles
, 'number:date-style', attrib
={
1180 'style:name': 'rst-date-style-%d' % self
.style_index
,
1181 'number:automatic-order': 'true',
1182 'xmlns:number': SNSD
['number'],
1183 'xmlns:style': SNSD
['style'],
1185 el3
= SubElement(el2
, 'number:month', attrib
={
1186 'number:style': 'long',
1188 el3
= SubElement(el2
, 'number:text')
1190 el3
= SubElement(el2
, 'number:day', attrib
={
1191 'number:style': 'long',
1193 el3
= SubElement(el2
, 'number:text')
1195 el3
= SubElement(el2
, 'number:year')
1197 self
.style_index
+= 1
1198 el1
= SubElement(parent
, 'text:date', attrib
={
1199 'text:style-name': self
.rststyle(style_name
),
1200 'style:data-style-name': 'rst-date-style-%d' % self
.style_index
,
1202 el2
= SubElement(automatic_styles
, 'number:date-style', attrib
={
1203 'style:name': 'rst-date-style-%d' % self
.style_index
,
1204 'number:automatic-order': 'true',
1205 'xmlns:number': SNSD
['number'],
1206 'xmlns:style': SNSD
['style'],
1208 el3
= SubElement(el2
, 'number:month', attrib
={
1209 'number:style': 'long',
1211 el3
= SubElement(el2
, 'number:text')
1213 el3
= SubElement(el2
, 'number:day', attrib
={
1214 'number:style': 'long',
1216 el3
= SubElement(el2
, 'number:text')
1218 el3
= SubElement(el2
, 'number:year', attrib
={
1219 'number:style': 'long',
1222 self
.style_index
+= 1
1223 el1
= SubElement(parent
, 'text:date', attrib
={
1224 'text:style-name': self
.rststyle(style_name
),
1225 'style:data-style-name': 'rst-date-style-%d' % self
.style_index
,
1227 el2
= SubElement(automatic_styles
, 'number:date-style', attrib
={
1228 'style:name': 'rst-date-style-%d' % self
.style_index
,
1229 'number:automatic-order': 'true',
1230 'xmlns:number': SNSD
['number'],
1231 'xmlns:style': SNSD
['style'],
1233 el3
= SubElement(el2
, 'number:month', attrib
={
1234 'number:textual': 'true',
1236 el3
= SubElement(el2
, 'number:text')
1238 el3
= SubElement(el2
, 'number:day', attrib
={
1240 el3
= SubElement(el2
, 'number:text')
1242 el3
= SubElement(el2
, 'number:year', attrib
={
1243 'number:style': 'long',
1246 self
.style_index
+= 1
1247 el1
= SubElement(parent
, 'text:date', attrib
={
1248 'text:style-name': self
.rststyle(style_name
),
1249 'style:data-style-name': 'rst-date-style-%d' % self
.style_index
,
1251 el2
= SubElement(automatic_styles
, 'number:date-style', attrib
={
1252 'style:name': 'rst-date-style-%d' % self
.style_index
,
1253 'number:automatic-order': 'true',
1254 'xmlns:number': SNSD
['number'],
1255 'xmlns:style': SNSD
['style'],
1257 el3
= SubElement(el2
, 'number:month', attrib
={
1258 'number:textual': 'true',
1259 'number:style': 'long',
1261 el3
= SubElement(el2
, 'number:text')
1263 el3
= SubElement(el2
, 'number:day', attrib
={
1265 el3
= SubElement(el2
, 'number:text')
1267 el3
= SubElement(el2
, 'number:year', attrib
={
1268 'number:style': 'long',
1271 self
.style_index
+= 1
1272 el1
= SubElement(parent
, 'text:date', attrib
={
1273 'text:style-name': self
.rststyle(style_name
),
1274 'style:data-style-name': 'rst-date-style-%d' % self
.style_index
,
1276 el2
= SubElement(automatic_styles
, 'number:date-style', attrib
={
1277 'style:name': 'rst-date-style-%d' % self
.style_index
,
1278 'xmlns:number': SNSD
['number'],
1279 'xmlns:style': SNSD
['style'],
1281 el3
= SubElement(el2
, 'number:year', attrib
={
1282 'number:style': 'long',
1284 el3
= SubElement(el2
, 'number:text')
1286 el3
= SubElement(el2
, 'number:month', attrib
={
1287 'number:style': 'long',
1289 el3
= SubElement(el2
, 'number:text')
1291 el3
= SubElement(el2
, 'number:day', attrib
={
1292 'number:style': 'long',
1295 el1
= SubElement(parent
, 'text:subject', attrib
={
1296 'text:style-name': self
.rststyle(style_name
),
1299 el1
= SubElement(parent
, 'text:title', attrib
={
1300 'text:style-name': self
.rststyle(style_name
),
1303 el1
= SubElement(parent
, 'text:author-name', attrib
={
1304 'text:fixed': 'false',
1310 def split_field_specifiers_iter(self
, text
):
1314 mo
= ODFTranslator
.field_pat
.search(text
, pos1
)
1318 yield (ODFTranslator
.code_text
, text
[pos1
:pos2
])
1319 yield (ODFTranslator
.code_field
, mo
.group(1))
1323 trailing
= text
[pos1
:]
1325 yield (ODFTranslator
.code_text
, trailing
)
1329 root
= self
.content_tree
.getroot()
1330 et
= etree
.ElementTree(root
)
1334 def content_astext(self
):
1335 return self
.astext()
1337 def set_title(self
, title
): self
.title
= title
1338 def get_title(self
): return self
.title
1339 def set_embedded_file_list(self
, embedded_file_list
):
1340 self
.embedded_file_list
= embedded_file_list
1341 def get_embedded_file_list(self
): return self
.embedded_file_list
1342 def get_meta_dict(self
): return self
.meta_dict
1344 def process_footnotes(self
):
1345 for node
, el1
in self
.footnote_list
:
1346 backrefs
= node
.attributes
.get('backrefs', [])
1348 for ref
in backrefs
:
1349 el2
= self
.footnote_ref_dict
.get(ref
)
1353 el3
= copy
.deepcopy(el1
)
1356 children
= el2
.getchildren()
1357 if len(children
) > 0: # and 'id' in el2.attrib:
1360 attribkey
= add_ns('text:id', nsdict
=SNSD
)
1361 id1
= el2
.get(attribkey
, 'footnote-error')
1364 tag
= add_ns('text:note-ref', nsdict
=SNSD
)
1366 if self
.settings
.endnotes_end_doc
:
1367 note_class
= 'endnote'
1369 note_class
= 'footnote'
1371 attribkey
= add_ns('text:note-class', nsdict
=SNSD
)
1372 el2
.attrib
[attribkey
] = note_class
1373 attribkey
= add_ns('text:ref-name', nsdict
=SNSD
)
1374 el2
.attrib
[attribkey
] = id1
1375 attribkey
= add_ns('text:reference-format', nsdict
=SNSD
)
1376 el2
.attrib
[attribkey
] = 'page'
1382 def append_child(self
, tag
, attrib
=None, parent
=None):
1384 parent
= self
.current_element
1386 el
= SubElement(parent
, tag
)
1388 el
= SubElement(parent
, tag
, attrib
)
1391 def append_p(self
, style
, text
=None):
1392 result
= self
.append_child('text:p', attrib
={
1393 'text:style-name': self
.rststyle(style
)})
1394 self
.append_pending_ids(result
)
1395 if text
is not None:
1399 def append_pending_ids(self
, el
):
1400 if self
.settings
.create_links
:
1401 for id in self
.pending_ids
:
1402 SubElement(el
, 'text:reference-mark', attrib
={
1404 self
.pending_ids
= [ ]
1406 def set_current_element(self
, el
):
1407 self
.current_element
= el
1409 def set_to_parent(self
):
1410 self
.current_element
= self
.current_element
.getparent()
1412 def generate_labeled_block(self
, node
, label
):
1413 label
= '%s:' % (self
.language
.labels
[label
], )
1414 el
= self
.append_p('textbody')
1415 el1
= SubElement(el
, 'text:span',
1416 attrib
={'text:style-name': self
.rststyle('strong')})
1418 el
= self
.append_p('blockindent')
1421 def generate_labeled_line(self
, node
, label
):
1422 label
= '%s:' % (self
.language
.labels
[label
], )
1423 el
= self
.append_p('textbody')
1424 el1
= SubElement(el
, 'text:span',
1425 attrib
={'text:style-name': self
.rststyle('strong')})
1427 el1
.tail
= node
.astext()
1430 def encode(self
, text
):
1431 text
= text
.replace(u
'\u00a0', " ")
1437 # In alphabetic order, more or less.
1438 # See docutils.docutils.nodes.node_class_names.
1441 def dispatch_visit(self
, node
):
1442 """Override to catch basic attributes which many nodes have."""
1443 self
.handle_basic_atts(node
)
1444 nodes
.GenericNodeVisitor
.dispatch_visit(self
, node
)
1446 def handle_basic_atts(self
, node
):
1447 if isinstance(node
, nodes
.Element
) and node
['ids']:
1448 self
.pending_ids
+= node
['ids']
1450 def default_visit(self
, node
):
1451 self
.document
.reporter
.warning('missing visit_%s' % (node
.tagname
, ))
1453 def default_departure(self
, node
):
1454 self
.document
.reporter
.warning('missing depart_%s' % (node
.tagname
, ))
1456 ## def add_text_to_element(self, text):
1457 ## # Are we in a citation. If so, add text to current element, not
1459 ## # Are we in mixed content? If so, add the text to the
1460 ## # etree tail of the previous sibling element.
1461 ## if not self.in_citation and len(self.current_element.getchildren()) > 0:
1462 ## if self.current_element.getchildren()[-1].tail:
1463 ## self.current_element.getchildren()[-1].tail += text
1465 ## self.current_element.getchildren()[-1].tail = text
1467 ## if self.current_element.text:
1468 ## self.current_element.text += text
1470 ## self.current_element.text = text
1472 ## def visit_Text(self, node):
1473 ## # Skip nodes whose text has been processed in parent nodes.
1474 ## if isinstance(node.parent, docutils.nodes.literal_block):
1476 ## text = node.astext()
1477 ## self.add_text_to_element(text)
1479 def visit_Text(self
, node
):
1480 # Skip nodes whose text has been processed in parent nodes.
1481 if isinstance(node
.parent
, docutils
.nodes
.literal_block
):
1483 text
= node
.astext()
1484 # Are we in mixed content? If so, add the text to the
1485 # etree tail of the previous sibling element.
1486 if len(self
.current_element
.getchildren()) > 0:
1487 if self
.current_element
.getchildren()[-1].tail
:
1488 self
.current_element
.getchildren()[-1].tail
+= text
1490 self
.current_element
.getchildren()[-1].tail
= text
1492 if self
.current_element
.text
:
1493 self
.current_element
.text
+= text
1495 self
.current_element
.text
= text
1497 def depart_Text(self
, node
):
1501 # Pre-defined fields
1504 def visit_address(self
, node
):
1505 el
= self
.generate_labeled_block(node
, 'address')
1506 self
.set_current_element(el
)
1508 def depart_address(self
, node
):
1509 self
.set_to_parent()
1511 def visit_author(self
, node
):
1512 if isinstance(node
.parent
, nodes
.authors
):
1513 el
= self
.append_p('blockindent')
1515 el
= self
.generate_labeled_block(node
, 'author')
1516 self
.set_current_element(el
)
1518 def depart_author(self
, node
):
1519 self
.set_to_parent()
1521 def visit_authors(self
, node
):
1522 label
= '%s:' % (self
.language
.labels
['authors'], )
1523 el
= self
.append_p('textbody')
1524 el1
= SubElement(el
, 'text:span',
1525 attrib
={'text:style-name': self
.rststyle('strong')})
1528 def depart_authors(self
, node
):
1531 def visit_contact(self
, node
):
1532 el
= self
.generate_labeled_block(node
, 'contact')
1533 self
.set_current_element(el
)
1535 def depart_contact(self
, node
):
1536 self
.set_to_parent()
1538 def visit_copyright(self
, node
):
1539 el
= self
.generate_labeled_block(node
, 'copyright')
1540 self
.set_current_element(el
)
1542 def depart_copyright(self
, node
):
1543 self
.set_to_parent()
1545 def visit_date(self
, node
):
1546 self
.generate_labeled_line(node
, 'date')
1548 def depart_date(self
, node
):
1551 def visit_organization(self
, node
):
1552 el
= self
.generate_labeled_block(node
, 'organization')
1553 self
.set_current_element(el
)
1555 def depart_organization(self
, node
):
1556 self
.set_to_parent()
1558 def visit_status(self
, node
):
1559 el
= self
.generate_labeled_block(node
, 'status')
1560 self
.set_current_element(el
)
1562 def depart_status(self
, node
):
1563 self
.set_to_parent()
1565 def visit_revision(self
, node
):
1566 el
= self
.generate_labeled_line(node
, 'revision')
1568 def depart_revision(self
, node
):
1571 def visit_version(self
, node
):
1572 el
= self
.generate_labeled_line(node
, 'version')
1573 #self.set_current_element(el)
1575 def depart_version(self
, node
):
1576 #self.set_to_parent()
1579 def visit_attribution(self
, node
):
1580 el
= self
.append_p('attribution', node
.astext())
1582 def depart_attribution(self
, node
):
1585 def visit_block_quote(self
, node
):
1586 if 'epigraph' in node
.attributes
['classes']:
1587 self
.paragraph_style_stack
.append(self
.rststyle('epigraph'))
1588 self
.blockstyle
= self
.rststyle('epigraph')
1589 elif 'highlights' in node
.attributes
['classes']:
1590 self
.paragraph_style_stack
.append(self
.rststyle('highlights'))
1591 self
.blockstyle
= self
.rststyle('highlights')
1593 self
.paragraph_style_stack
.append(self
.rststyle('blockquote'))
1594 self
.blockstyle
= self
.rststyle('blockquote')
1595 self
.line_indent_level
+= 1
1597 def depart_block_quote(self
, node
):
1598 self
.paragraph_style_stack
.pop()
1599 self
.blockstyle
= ''
1600 self
.line_indent_level
-= 1
1602 def visit_bullet_list(self
, node
):
1604 if self
.in_table_of_contents
:
1605 if self
.settings
.generate_oowriter_toc
:
1608 if node
.has_key('classes') and \
1609 'auto-toc' in node
.attributes
['classes']:
1610 el
= SubElement(self
.current_element
, 'text:list', attrib
={
1611 'text:style-name': self
.rststyle('tocenumlist'),
1613 self
.list_style_stack
.append(self
.rststyle('enumitem'))
1615 el
= SubElement(self
.current_element
, 'text:list', attrib
={
1616 'text:style-name': self
.rststyle('tocbulletlist'),
1618 self
.list_style_stack
.append(self
.rststyle('bulletitem'))
1619 self
.set_current_element(el
)
1621 if self
.blockstyle
== self
.rststyle('blockquote'):
1622 el
= SubElement(self
.current_element
, 'text:list', attrib
={
1623 'text:style-name': self
.rststyle('blockquote-bulletlist'),
1625 self
.list_style_stack
.append(
1626 self
.rststyle('blockquote-bulletitem'))
1627 elif self
.blockstyle
== self
.rststyle('highlights'):
1628 el
= SubElement(self
.current_element
, 'text:list', attrib
={
1629 'text:style-name': self
.rststyle('highlights-bulletlist'),
1631 self
.list_style_stack
.append(
1632 self
.rststyle('highlights-bulletitem'))
1633 elif self
.blockstyle
== self
.rststyle('epigraph'):
1634 el
= SubElement(self
.current_element
, 'text:list', attrib
={
1635 'text:style-name': self
.rststyle('epigraph-bulletlist'),
1637 self
.list_style_stack
.append(
1638 self
.rststyle('epigraph-bulletitem'))
1640 el
= SubElement(self
.current_element
, 'text:list', attrib
={
1641 'text:style-name': self
.rststyle('bulletlist'),
1643 self
.list_style_stack
.append(self
.rststyle('bulletitem'))
1644 self
.set_current_element(el
)
1646 def depart_bullet_list(self
, node
):
1647 if self
.in_table_of_contents
:
1648 if self
.settings
.generate_oowriter_toc
:
1651 self
.set_to_parent()
1652 self
.list_style_stack
.pop()
1654 self
.set_to_parent()
1655 self
.list_style_stack
.pop()
1658 def visit_caption(self
, node
):
1659 raise nodes
.SkipChildren()
1662 def depart_caption(self
, node
):
1665 def visit_comment(self
, node
):
1666 el
= self
.append_p('textbody')
1667 el1
= SubElement(el
, 'office:annotation', attrib
={})
1668 el2
= SubElement(el1
, 'dc:creator', attrib
={})
1669 s1
= os
.environ
.get('USER', '')
1671 el2
= SubElement(el1
, 'text:p', attrib
={})
1672 el2
.text
= node
.astext()
1674 def depart_comment(self
, node
):
1677 def visit_compound(self
, node
):
1678 # The compound directive currently receives no special treatment.
1681 def depart_compound(self
, node
):
1684 def visit_container(self
, node
):
1685 styles
= node
.attributes
.get('classes', ())
1687 self
.paragraph_style_stack
.append(self
.rststyle(styles
[0]))
1689 def depart_container(self
, node
):
1690 styles
= node
.attributes
.get('classes', ())
1692 self
.paragraph_style_stack
.pop()
1694 def visit_decoration(self
, node
):
1697 def depart_decoration(self
, node
):
1700 def visit_definition_list(self
, node
):
1701 self
.def_list_level
+=1
1702 if self
.list_level
> 5:
1704 'max definition list nesting level exceeded')
1706 def depart_definition_list(self
, node
):
1707 self
.def_list_level
-=1
1709 def visit_definition_list_item(self
, node
):
1712 def depart_definition_list_item(self
, node
):
1715 def visit_term(self
, node
):
1716 el
= self
.append_p('deflist-term-%d' % self
.def_list_level
)
1717 el
.text
= node
.astext()
1718 self
.set_current_element(el
)
1719 raise nodes
.SkipChildren()
1721 def depart_term(self
, node
):
1722 self
.set_to_parent()
1724 def visit_definition(self
, node
):
1725 self
.paragraph_style_stack
.append(
1726 self
.rststyle('deflist-def-%d' % self
.def_list_level
))
1727 self
.bumped_list_level_stack
.append(ListLevel(1))
1729 def depart_definition(self
, node
):
1730 self
.paragraph_style_stack
.pop()
1731 self
.bumped_list_level_stack
.pop()
1733 def visit_classifier(self
, node
):
1734 els
= self
.current_element
.getchildren()
1737 el1
= SubElement(el
, 'text:span',
1738 attrib
={'text:style-name': self
.rststyle('emphasis')
1740 el1
.text
= ' (%s)' % (node
.astext(), )
1742 def depart_classifier(self
, node
):
1745 def visit_document(self
, node
):
1748 def depart_document(self
, node
):
1749 self
.process_footnotes()
1751 def visit_docinfo(self
, node
):
1752 self
.section_level
+= 1
1753 self
.section_count
+= 1
1754 if self
.settings
.create_sections
:
1755 el
= self
.append_child('text:section', attrib
={
1756 'text:name': 'Section%d' % self
.section_count
,
1757 'text:style-name': 'Sect%d' % self
.section_level
,
1759 self
.set_current_element(el
)
1761 def depart_docinfo(self
, node
):
1762 self
.section_level
-= 1
1763 if self
.settings
.create_sections
:
1764 self
.set_to_parent()
1766 def visit_emphasis(self
, node
):
1767 el
= SubElement(self
.current_element
, 'text:span',
1768 attrib
={'text:style-name': self
.rststyle('emphasis')})
1769 self
.set_current_element(el
)
1771 def depart_emphasis(self
, node
):
1772 self
.set_to_parent()
1774 def visit_enumerated_list(self
, node
):
1775 el1
= self
.current_element
1776 if self
.blockstyle
== self
.rststyle('blockquote'):
1777 el2
= SubElement(el1
, 'text:list', attrib
={
1778 'text:style-name': self
.rststyle('blockquote-enumlist'),
1780 self
.list_style_stack
.append(self
.rststyle('blockquote-enumitem'))
1781 elif self
.blockstyle
== self
.rststyle('highlights'):
1782 el2
= SubElement(el1
, 'text:list', attrib
={
1783 'text:style-name': self
.rststyle('highlights-enumlist'),
1785 self
.list_style_stack
.append(self
.rststyle('highlights-enumitem'))
1786 elif self
.blockstyle
== self
.rststyle('epigraph'):
1787 el2
= SubElement(el1
, 'text:list', attrib
={
1788 'text:style-name': self
.rststyle('epigraph-enumlist'),
1790 self
.list_style_stack
.append(self
.rststyle('epigraph-enumitem'))
1792 liststylename
= 'enumlist-%s' % (node
.get('enumtype', 'arabic'), )
1793 el2
= SubElement(el1
, 'text:list', attrib
={
1794 'text:style-name': self
.rststyle(liststylename
),
1796 self
.list_style_stack
.append(self
.rststyle('enumitem'))
1797 self
.set_current_element(el2
)
1799 def depart_enumerated_list(self
, node
):
1800 self
.set_to_parent()
1801 self
.list_style_stack
.pop()
1803 def visit_list_item(self
, node
):
1804 # If we are in a "bumped" list level, then wrap this
1805 # list in an outer lists in order to increase the
1806 # indentation level.
1807 if self
.in_table_of_contents
:
1808 if self
.settings
.generate_oowriter_toc
:
1809 self
.paragraph_style_stack
.append(
1810 self
.rststyle('contents-%d' % (self
.list_level
, )))
1812 el1
= self
.append_child('text:list-item')
1813 self
.set_current_element(el1
)
1815 el1
= self
.append_child('text:list-item')
1817 if len(self
.bumped_list_level_stack
) > 0:
1818 level_obj
= self
.bumped_list_level_stack
[-1]
1819 if level_obj
.get_sibling():
1820 level_obj
.set_nested(False)
1821 for level_obj1
in self
.bumped_list_level_stack
:
1822 for idx
in range(level_obj1
.get_level()):
1823 el2
= self
.append_child('text:list', parent
=el3
)
1824 el3
= self
.append_child(
1825 'text:list-item', parent
=el2
)
1826 self
.paragraph_style_stack
.append(self
.list_style_stack
[-1])
1827 self
.set_current_element(el3
)
1829 def depart_list_item(self
, node
):
1830 if self
.in_table_of_contents
:
1831 if self
.settings
.generate_oowriter_toc
:
1832 self
.paragraph_style_stack
.pop()
1834 self
.set_to_parent()
1836 if len(self
.bumped_list_level_stack
) > 0:
1837 level_obj
= self
.bumped_list_level_stack
[-1]
1838 if level_obj
.get_sibling():
1839 level_obj
.set_nested(True)
1840 for level_obj1
in self
.bumped_list_level_stack
:
1841 for idx
in range(level_obj1
.get_level()):
1842 self
.set_to_parent()
1843 self
.set_to_parent()
1844 self
.paragraph_style_stack
.pop()
1845 self
.set_to_parent()
1847 def visit_header(self
, node
):
1848 self
.in_header
= True
1850 def depart_header(self
, node
):
1851 self
.in_header
= False
1853 def visit_footer(self
, node
):
1854 self
.in_footer
= True
1856 def depart_footer(self
, node
):
1857 self
.in_footer
= False
1859 def visit_field(self
, node
):
1862 def depart_field(self
, node
):
1865 def visit_field_list(self
, node
):
1868 def depart_field_list(self
, node
):
1871 def visit_field_name(self
, node
):
1872 el
= self
.append_p('textbody')
1873 el1
= SubElement(el
, 'text:span',
1874 attrib
={'text:style-name': self
.rststyle('strong')})
1875 el1
.text
= node
.astext()
1877 def depart_field_name(self
, node
):
1880 def visit_field_body(self
, node
):
1881 self
.paragraph_style_stack
.append(self
.rststyle('blockindent'))
1883 def depart_field_body(self
, node
):
1884 self
.paragraph_style_stack
.pop()
1886 def visit_figure(self
, node
):
1889 def depart_figure(self
, node
):
1892 def visit_footnote(self
, node
):
1893 self
.footnote_level
+= 1
1894 self
.save_footnote_current
= self
.current_element
1895 el1
= Element('text:note-body')
1896 self
.current_element
= el1
1897 self
.footnote_list
.append((node
, el1
))
1898 if isinstance(node
, docutils
.nodes
.citation
):
1899 self
.paragraph_style_stack
.append(self
.rststyle('citation'))
1901 self
.paragraph_style_stack
.append(self
.rststyle('footnote'))
1903 def depart_footnote(self
, node
):
1904 self
.paragraph_style_stack
.pop()
1905 self
.current_element
= self
.save_footnote_current
1906 self
.footnote_level
-= 1
1915 def visit_footnote_reference(self
, node
):
1916 if self
.footnote_level
<= 0:
1917 id = node
.attributes
['ids'][0]
1918 refid
= node
.attributes
.get('refid')
1921 if self
.settings
.endnotes_end_doc
:
1922 note_class
= 'endnote'
1924 note_class
= 'footnote'
1925 el1
= self
.append_child('text:note', attrib
={
1926 'text:id': '%s' % (refid
, ),
1927 'text:note-class': note_class
,
1929 note_auto
= str(node
.attributes
.get('auto', 1))
1930 if isinstance(node
, docutils
.nodes
.citation_reference
):
1931 citation
= '[%s]' % node
.astext()
1932 el2
= SubElement(el1
, 'text:note-citation', attrib
={
1933 'text:label': citation
,
1936 elif note_auto
== '1':
1937 el2
= SubElement(el1
, 'text:note-citation', attrib
={
1938 'text:label': node
.astext(),
1940 el2
.text
= node
.astext()
1941 elif note_auto
== '*':
1942 if self
.footnote_chars_idx
>= len(
1943 ODFTranslator
.footnote_chars
):
1944 self
.footnote_chars_idx
= 0
1945 footnote_char
= ODFTranslator
.footnote_chars
[
1946 self
.footnote_chars_idx
]
1947 self
.footnote_chars_idx
+= 1
1948 el2
= SubElement(el1
, 'text:note-citation', attrib
={
1949 'text:label': footnote_char
,
1951 el2
.text
= footnote_char
1952 self
.footnote_ref_dict
[id] = el1
1953 raise nodes
.SkipChildren()
1955 def depart_footnote_reference(self
, node
):
1958 def visit_citation(self
, node
):
1959 self
.in_citation
= True
1960 for id in node
.attributes
['ids']:
1961 self
.citation_id
= id
1963 self
.paragraph_style_stack
.append(self
.rststyle('blockindent'))
1964 self
.bumped_list_level_stack
.append(ListLevel(1))
1966 def depart_citation(self
, node
):
1967 self
.citation_id
= None
1968 self
.paragraph_style_stack
.pop()
1969 self
.bumped_list_level_stack
.pop()
1970 self
.in_citation
= False
1972 def visit_citation_reference(self
, node
):
1973 if self
.settings
.create_links
:
1974 id = node
.attributes
['refid']
1975 el
= self
.append_child('text:reference-ref', attrib
={
1976 'text:ref-name': '%s' % (id, ),
1977 'text:reference-format': 'text',
1980 self
.set_current_element(el
)
1981 elif self
.current_element
.text
is None:
1982 self
.current_element
.text
= '['
1984 self
.current_element
.text
+= '['
1986 def depart_citation_reference(self
, node
):
1987 self
.current_element
.text
+= ']'
1988 if self
.settings
.create_links
:
1989 self
.set_to_parent()
1991 def visit_label(self
, node
):
1992 if isinstance(node
.parent
, docutils
.nodes
.footnote
):
1993 raise nodes
.SkipChildren()
1994 elif self
.citation_id
is not None:
1995 el
= self
.append_p('textbody')
1996 self
.set_current_element(el
)
1998 if self
.settings
.create_links
:
1999 el1
= self
.append_child('text:reference-mark-start', attrib
={
2000 'text:name': '%s' % (self
.citation_id
, ),
2003 def depart_label(self
, node
):
2004 if isinstance(node
.parent
, docutils
.nodes
.footnote
):
2006 elif self
.citation_id
is not None:
2007 self
.current_element
.text
+= ']'
2008 if self
.settings
.create_links
:
2009 el
= self
.append_child('text:reference-mark-end', attrib
={
2010 'text:name': '%s' % (self
.citation_id
, ),
2012 self
.set_to_parent()
2014 def visit_generated(self
, node
):
2017 def depart_generated(self
, node
):
2020 def check_file_exists(self
, path
):
2021 if os
.path
.exists(path
):
2026 def visit_image(self
, node
):
2027 # Capture the image file.
2028 if 'uri' in node
.attributes
:
2029 source
= node
.attributes
['uri']
2030 if not source
.startswith('http:'):
2031 if not source
.startswith(os
.sep
):
2032 docsource
, line
= utils
.get_source_line(node
)
2034 dirname
= os
.path
.dirname(docsource
)
2036 source
= '%s%s%s' % (dirname
, os
.sep
, source
, )
2037 if not self
.check_file_exists(source
):
2038 self
.document
.reporter
.warning(
2039 'Cannot find image file %s.' % (source
, ))
2043 if source
in self
.image_dict
:
2044 filename
, destination
= self
.image_dict
[source
]
2046 self
.image_count
+= 1
2047 filename
= os
.path
.split(source
)[1]
2048 destination
= 'Pictures/1%08x%s' % (self
.image_count
, filename
, )
2049 if source
.startswith('http:'):
2051 imgfile
= urllib2
.urlopen(source
)
2052 content
= imgfile
.read()
2054 imgfile2
= tempfile
.NamedTemporaryFile('wb', delete
=False)
2055 imgfile2
.write(content
)
2057 imgfilename
= imgfile2
.name
2058 source
= imgfilename
2059 except urllib2
.HTTPError
, e
:
2060 self
.document
.reporter
.warning(
2061 "Can't open image url %s." % (source
, ))
2062 spec
= (source
, destination
,)
2064 spec
= (os
.path
.abspath(source
), destination
,)
2065 self
.embedded_file_list
.append(spec
)
2066 self
.image_dict
[source
] = (source
, destination
,)
2067 # Is this a figure (containing an image) or just a plain image?
2068 if self
.in_paragraph
:
2069 el1
= self
.current_element
2071 el1
= SubElement(self
.current_element
, 'text:p',
2072 attrib
={'text:style-name': self
.rststyle('textbody')})
2074 if isinstance(node
.parent
, docutils
.nodes
.figure
):
2075 el3
, el4
, el5
, caption
= self
.generate_figure(node
, source
,
2078 el6
, width
= self
.generate_image(node
, source
, destination
,
2080 if caption
is not None:
2082 else: #if isinstance(node.parent, docutils.nodes.image):
2083 el3
= self
.generate_image(node
, source
, destination
, el2
)
2085 def depart_image(self
, node
):
2088 def get_image_width_height(self
, node
, attr
):
2090 if attr
in node
.attributes
:
2091 size
= node
.attributes
[attr
]
2099 except ValueError, e
:
2100 self
.document
.reporter
.warning(
2101 'Invalid %s for image: "%s"' % (
2102 attr
, node
.attributes
[attr
]))
2106 def get_image_scale(self
, node
):
2107 if 'scale' in node
.attributes
:
2109 scale
= int(node
.attributes
['scale'])
2110 if scale
< 1: # or scale > 100:
2111 self
.document
.reporter
.warning(
2112 'scale out of range (%s), using 1.' % (scale
, ))
2114 scale
= scale
* 0.01
2115 except ValueError, e
:
2116 self
.document
.reporter
.warning(
2117 'Invalid scale for image: "%s"' % (
2118 node
.attributes
['scale'], ))
2123 def get_image_scaled_width_height(self
, node
, source
):
2124 scale
= self
.get_image_scale(node
)
2125 width
= self
.get_image_width_height(node
, 'width')
2126 height
= self
.get_image_width_height(node
, 'height')
2129 if PIL
is not None and source
in self
.image_dict
:
2130 filename
, destination
= self
.image_dict
[source
]
2131 imageobj
= PIL
.Image
.open(filename
, 'r')
2132 dpi
= imageobj
.info
.get('dpi', dpi
)
2133 # dpi information can be (xdpi, ydpi) or xydpi
2135 except: dpi
= (dpi
, dpi
)
2139 if width
is None or height
is None:
2140 if imageobj
is None:
2142 'image size not fully specified and PIL not installed')
2143 if width
is None: width
= [imageobj
.size
[0], 'px']
2144 if height
is None: height
= [imageobj
.size
[1], 'px']
2148 if width
[1] == 'px': width
= [width
[0] / dpi
[0], 'in']
2149 if height
[1] == 'px': height
= [height
[0] / dpi
[1], 'in']
2151 width
[0] = str(width
[0])
2152 height
[0] = str(height
[0])
2153 return ''.join(width
), ''.join(height
)
2155 def generate_figure(self
, node
, source
, destination
, current_element
):
2157 width
, height
= self
.get_image_scaled_width_height(node
, source
)
2158 for node1
in node
.parent
.children
:
2159 if node1
.tagname
== 'caption':
2160 caption
= node1
.astext()
2161 self
.image_style_count
+= 1
2163 # Add the style for the caption.
2164 if caption
is not None:
2166 'style:class': 'extra',
2167 'style:family': 'paragraph',
2168 'style:name': 'Caption',
2169 'style:parent-style-name': 'Standard',
2171 el1
= SubElement(self
.automatic_styles
, 'style:style',
2172 attrib
=attrib
, nsdict
=SNSD
)
2174 'fo:margin-bottom': '0.0835in',
2175 'fo:margin-top': '0.0835in',
2176 'text:line-number': '0',
2177 'text:number-lines': 'false',
2179 el2
= SubElement(el1
, 'style:paragraph-properties',
2180 attrib
=attrib
, nsdict
=SNSD
)
2182 'fo:font-size': '12pt',
2183 'fo:font-style': 'italic',
2184 'style:font-name': 'Times',
2185 'style:font-name-complex': 'Lucidasans1',
2186 'style:font-size-asian': '12pt',
2187 'style:font-size-complex': '12pt',
2188 'style:font-style-asian': 'italic',
2189 'style:font-style-complex': 'italic',
2191 el2
= SubElement(el1
, 'style:text-properties',
2192 attrib
=attrib
, nsdict
=SNSD
)
2193 style_name
= 'rstframestyle%d' % self
.image_style_count
2196 'style:name': style_name
,
2197 'style:family': 'graphic',
2198 'style:parent-style-name': self
.rststyle('figureframe'),
2200 el1
= SubElement(self
.automatic_styles
,
2201 'style:style', attrib
=attrib
, nsdict
=SNSD
)
2204 if 'align' in node
.attributes
:
2205 align
= node
.attributes
['align'].split()
2207 if val
in ('left', 'center', 'right'):
2209 elif val
in ('top', 'middle', 'bottom'):
2213 classes
= node
.parent
.attributes
.get('classes')
2214 if classes
and 'wrap' in classes
:
2217 attrib
['style:wrap'] = 'dynamic'
2219 attrib
['style:wrap'] = 'none'
2220 el2
= SubElement(el1
,
2221 'style:graphic-properties', attrib
=attrib
, nsdict
=SNSD
)
2223 'draw:style-name': style_name
,
2224 'draw:name': 'Frame1',
2225 'text:anchor-type': 'paragraph',
2226 'draw:z-index': '0',
2228 attrib
['svg:width'] = width
2230 #attrib['svg:height'] = height
2231 el3
= SubElement(current_element
, 'draw:frame', attrib
=attrib
)
2233 el4
= SubElement(el3
, 'draw:text-box', attrib
=attrib
)
2235 'text:style-name': self
.rststyle('caption'),
2237 el5
= SubElement(el4
, 'text:p', attrib
=attrib
)
2238 return el3
, el4
, el5
, caption
2240 def generate_image(self
, node
, source
, destination
, current_element
,
2242 width
, height
= self
.get_image_scaled_width_height(node
, source
)
2243 self
.image_style_count
+= 1
2244 style_name
= 'rstframestyle%d' % self
.image_style_count
2247 'style:name': style_name
,
2248 'style:family': 'graphic',
2249 'style:parent-style-name': self
.rststyle('image'),
2251 el1
= SubElement(self
.automatic_styles
,
2252 'style:style', attrib
=attrib
, nsdict
=SNSD
)
2255 if 'align' in node
.attributes
:
2256 align
= node
.attributes
['align'].split()
2258 if val
in ('left', 'center', 'right'):
2260 elif val
in ('top', 'middle', 'bottom'):
2262 if frame_attrs
is None:
2264 'style:vertical-pos': 'top',
2265 'style:vertical-rel': 'paragraph',
2266 'style:horizontal-rel': 'paragraph',
2267 'style:mirror': 'none',
2268 'fo:clip': 'rect(0cm 0cm 0cm 0cm)',
2269 'draw:luminance': '0%',
2270 'draw:contrast': '0%',
2274 'draw:gamma': '100%',
2275 'draw:color-inversion': 'false',
2276 'draw:image-opacity': '100%',
2277 'draw:color-mode': 'standard',
2280 attrib
= frame_attrs
2281 if halign
is not None:
2282 attrib
['style:horizontal-pos'] = halign
2283 if valign
is not None:
2284 attrib
['style:vertical-pos'] = valign
2285 # If there is a classes/wrap directive or we are
2286 # inside a table, add a no-wrap style.
2288 classes
= node
.attributes
.get('classes')
2289 if classes
and 'wrap' in classes
:
2292 attrib
['style:wrap'] = 'dynamic'
2294 attrib
['style:wrap'] = 'none'
2295 # If we are inside a table, add a no-wrap style.
2296 if self
.is_in_table(node
):
2297 attrib
['style:wrap'] = 'none'
2298 el2
= SubElement(el1
,
2299 'style:graphic-properties', attrib
=attrib
, nsdict
=SNSD
)
2301 #el = SubElement(current_element, 'text:p',
2302 # attrib={'text:style-name': self.rststyle('textbody')})
2304 'draw:style-name': style_name
,
2305 'draw:name': 'graphics2',
2306 'draw:z-index': '1',
2308 if isinstance(node
.parent
, nodes
.TextElement
):
2309 attrib
['text:anchor-type'] = 'as-char' #vds
2311 attrib
['text:anchor-type'] = 'paragraph'
2312 attrib
['svg:width'] = width
2313 attrib
['svg:height'] = height
2314 el1
= SubElement(current_element
, 'draw:frame', attrib
=attrib
)
2315 el2
= SubElement(el1
, 'draw:image', attrib
={
2316 'xlink:href': '%s' % (destination
, ),
2317 'xlink:type': 'simple',
2318 'xlink:show': 'embed',
2319 'xlink:actuate': 'onLoad',
2323 def is_in_table(self
, node
):
2326 if isinstance(node1
, docutils
.nodes
.entry
):
2328 node1
= node1
.parent
2331 def visit_legend(self
, node
):
2332 if isinstance(node
.parent
, docutils
.nodes
.figure
):
2333 el1
= self
.current_element
[-1]
2335 self
.current_element
= el1
2336 self
.paragraph_style_stack
.append(self
.rststyle('legend'))
2338 def depart_legend(self
, node
):
2339 if isinstance(node
.parent
, docutils
.nodes
.figure
):
2340 self
.paragraph_style_stack
.pop()
2341 self
.set_to_parent()
2342 self
.set_to_parent()
2343 self
.set_to_parent()
2345 def visit_line_block(self
, node
):
2346 self
.line_indent_level
+= 1
2347 self
.line_block_level
+= 1
2349 def depart_line_block(self
, node
):
2350 self
.line_indent_level
-= 1
2351 self
.line_block_level
-= 1
2353 def visit_line(self
, node
):
2354 style
= 'lineblock%d' % self
.line_indent_level
2355 el1
= SubElement(self
.current_element
, 'text:p', attrib
={
2356 'text:style-name': self
.rststyle(style
),
2358 self
.current_element
= el1
2360 def depart_line(self
, node
):
2361 self
.set_to_parent()
2363 def visit_literal(self
, node
):
2364 el
= SubElement(self
.current_element
, 'text:span',
2365 attrib
={'text:style-name': self
.rststyle('inlineliteral')})
2366 self
.set_current_element(el
)
2368 def depart_literal(self
, node
):
2369 self
.set_to_parent()
2371 def visit_inline(self
, node
):
2372 styles
= node
.attributes
.get('classes', ())
2374 inline_style
= styles
[0]
2375 el
= SubElement(self
.current_element
, 'text:span',
2376 attrib
={'text:style-name': self
.rststyle(inline_style
)})
2377 self
.set_current_element(el
)
2379 def depart_inline(self
, node
):
2380 self
.set_to_parent()
2382 def _calculate_code_block_padding(self
, line
):
2384 matchobj
= SPACES_PATTERN
.match(line
)
2386 pad
= matchobj
.group()
2389 matchobj
= TABS_PATTERN
.match(line
)
2391 pad
= matchobj
.group()
2392 count
= len(pad
) * 8
2395 def _add_syntax_highlighting(self
, insource
, language
):
2396 lexer
= pygments
.lexers
.get_lexer_by_name(language
, stripall
=True)
2397 if language
in ('latex', 'tex'):
2398 fmtr
= OdtPygmentsLaTeXFormatter(lambda name
, parameters
=():
2399 self
.rststyle(name
, parameters
),
2400 escape_function
=escape_cdata
)
2402 fmtr
= OdtPygmentsProgFormatter(lambda name
, parameters
=():
2403 self
.rststyle(name
, parameters
),
2404 escape_function
=escape_cdata
)
2405 outsource
= pygments
.highlight(insource
, lexer
, fmtr
)
2408 def fill_line(self
, line
):
2409 line
= FILL_PAT1
.sub(self
.fill_func1
, line
)
2410 line
= FILL_PAT2
.sub(self
.fill_func2
, line
)
2413 def fill_func1(self
, matchobj
):
2414 spaces
= matchobj
.group(0)
2415 repl
= '<text:s text:c="%d"/>' % (len(spaces
), )
2418 def fill_func2(self
, matchobj
):
2419 spaces
= matchobj
.group(0)
2420 repl
= ' <text:s text:c="%d"/>' % (len(spaces
) - 1, )
2423 def visit_literal_block(self
, node
):
2424 if len(self
.paragraph_style_stack
) > 1:
2425 wrapper1
= '<text:p text:style-name="%s">%%s</text:p>' % (
2426 self
.rststyle('codeblock-indented'), )
2428 wrapper1
= '<text:p text:style-name="%s">%%s</text:p>' % (
2429 self
.rststyle('codeblock'), )
2430 source
= node
.astext()
2432 self
.settings
.add_syntax_highlighting
2434 #node.get('hilight', False)
2436 language
= node
.get('language', 'python')
2437 source
= self
._add
_syntax
_highlighting
(source
, language
)
2439 source
= escape_cdata(source
)
2440 lines
= source
.split('\n')
2441 # If there is an empty last line, remove it.
2444 lines1
= ['<wrappertag1 xmlns:text="urn:oasis:names:tc:opendocument:xmlns:text:1.0">']
2447 for my_line
in lines
:
2448 my_line
= self
.fill_line(my_line
)
2449 my_line
= my_line
.replace(" ", "\n")
2450 my_lines
.append(my_line
)
2451 my_lines_str
= '<text:line-break/>'.join(my_lines
)
2452 my_lines_str2
= wrapper1
% (my_lines_str
, )
2453 lines1
.append(my_lines_str2
)
2454 lines1
.append('</wrappertag1>')
2455 s1
= ''.join(lines1
)
2456 if WhichElementTree
!= "lxml":
2457 s1
= s1
.encode("utf-8")
2458 el1
= etree
.fromstring(s1
)
2459 children
= el1
.getchildren()
2460 for child
in children
:
2461 self
.current_element
.append(child
)
2463 def depart_literal_block(self
, node
):
2466 visit_doctest_block
= visit_literal_block
2467 depart_doctest_block
= depart_literal_block
2469 # placeholder for math (see docs/dev/todo.txt)
2470 def visit_math(self
, node
):
2471 self
.document
.reporter
.warning('"math" role not supported',
2473 self
.visit_literal(node
)
2475 def depart_math(self
, node
):
2476 self
.depart_literal(node
)
2478 def visit_math_block(self
, node
):
2479 self
.document
.reporter
.warning('"math" directive not supported',
2481 self
.visit_literal_block(node
)
2483 def depart_math_block(self
, node
):
2484 self
.depart_literal_block(node
)
2486 def visit_meta(self
, node
):
2487 name
= node
.attributes
.get('name')
2488 content
= node
.attributes
.get('content')
2489 if name
is not None and content
is not None:
2490 self
.meta_dict
[name
] = content
2492 def depart_meta(self
, node
):
2495 def visit_option_list(self
, node
):
2496 table_name
= 'tableoption'
2498 # Generate automatic styles
2499 if not self
.optiontablestyles_generated
:
2500 self
.optiontablestyles_generated
= True
2501 el
= SubElement(self
.automatic_styles
, 'style:style', attrib
={
2502 'style:name': self
.rststyle(table_name
),
2503 'style:family': 'table'}, nsdict
=SNSD
)
2504 el1
= SubElement(el
, 'style:table-properties', attrib
={
2505 'style:width': '17.59cm',
2506 'table:align': 'left',
2507 'style:shadow': 'none'}, nsdict
=SNSD
)
2508 el
= SubElement(self
.automatic_styles
, 'style:style', attrib
={
2509 'style:name': self
.rststyle('%s.%%c' % table_name
, ( 'A', )),
2510 'style:family': 'table-column'}, nsdict
=SNSD
)
2511 el1
= SubElement(el
, 'style:table-column-properties', attrib
={
2512 'style:column-width': '4.999cm'}, nsdict
=SNSD
)
2513 el
= SubElement(self
.automatic_styles
, 'style:style', attrib
={
2514 'style:name': self
.rststyle('%s.%%c' % table_name
, ( 'B', )),
2515 'style:family': 'table-column'}, nsdict
=SNSD
)
2516 el1
= SubElement(el
, 'style:table-column-properties', attrib
={
2517 'style:column-width': '12.587cm'}, nsdict
=SNSD
)
2518 el
= SubElement(self
.automatic_styles
, 'style:style', attrib
={
2519 'style:name': self
.rststyle(
2520 '%s.%%c%%d' % table_name
, ( 'A', 1, )),
2521 'style:family': 'table-cell'}, nsdict
=SNSD
)
2522 el1
= SubElement(el
, 'style:table-cell-properties', attrib
={
2523 'fo:background-color': 'transparent',
2524 'fo:padding': '0.097cm',
2525 'fo:border-left': '0.035cm solid #000000',
2526 'fo:border-right': 'none',
2527 'fo:border-top': '0.035cm solid #000000',
2528 'fo:border-bottom': '0.035cm solid #000000'}, nsdict
=SNSD
)
2529 el2
= SubElement(el1
, 'style:background-image', nsdict
=SNSD
)
2530 el
= SubElement(self
.automatic_styles
, 'style:style', attrib
={
2531 'style:name': self
.rststyle(
2532 '%s.%%c%%d' % table_name
, ( 'B', 1, )),
2533 'style:family': 'table-cell'}, nsdict
=SNSD
)
2534 el1
= SubElement(el
, 'style:table-cell-properties', attrib
={
2535 'fo:padding': '0.097cm',
2536 'fo:border': '0.035cm solid #000000'}, nsdict
=SNSD
)
2537 el
= SubElement(self
.automatic_styles
, 'style:style', attrib
={
2538 'style:name': self
.rststyle(
2539 '%s.%%c%%d' % table_name
, ( 'A', 2, )),
2540 'style:family': 'table-cell'}, nsdict
=SNSD
)
2541 el1
= SubElement(el
, 'style:table-cell-properties', attrib
={
2542 'fo:padding': '0.097cm',
2543 'fo:border-left': '0.035cm solid #000000',
2544 'fo:border-right': 'none',
2545 'fo:border-top': 'none',
2546 'fo:border-bottom': '0.035cm solid #000000'}, nsdict
=SNSD
)
2547 el
= SubElement(self
.automatic_styles
, 'style:style', attrib
={
2548 'style:name': self
.rststyle(
2549 '%s.%%c%%d' % table_name
, ( 'B', 2, )),
2550 'style:family': 'table-cell'}, nsdict
=SNSD
)
2551 el1
= SubElement(el
, 'style:table-cell-properties', attrib
={
2552 'fo:padding': '0.097cm',
2553 'fo:border-left': '0.035cm solid #000000',
2554 'fo:border-right': '0.035cm solid #000000',
2555 'fo:border-top': 'none',
2556 'fo:border-bottom': '0.035cm solid #000000'}, nsdict
=SNSD
)
2558 # Generate table data
2559 el
= self
.append_child('table:table', attrib
={
2560 'table:name': self
.rststyle(table_name
),
2561 'table:style-name': self
.rststyle(table_name
),
2563 el1
= SubElement(el
, 'table:table-column', attrib
={
2564 'table:style-name': self
.rststyle(
2565 '%s.%%c' % table_name
, ( 'A', ))})
2566 el1
= SubElement(el
, 'table:table-column', attrib
={
2567 'table:style-name': self
.rststyle(
2568 '%s.%%c' % table_name
, ( 'B', ))})
2569 el1
= SubElement(el
, 'table:table-header-rows')
2570 el2
= SubElement(el1
, 'table:table-row')
2571 el3
= SubElement(el2
, 'table:table-cell', attrib
={
2572 'table:style-name': self
.rststyle(
2573 '%s.%%c%%d' % table_name
, ( 'A', 1, )),
2574 'office:value-type': 'string'})
2575 el4
= SubElement(el3
, 'text:p', attrib
={
2576 'text:style-name': 'Table_20_Heading'})
2578 el3
= SubElement(el2
, 'table:table-cell', attrib
={
2579 'table:style-name': self
.rststyle(
2580 '%s.%%c%%d' % table_name
, ( 'B', 1, )),
2581 'office:value-type': 'string'})
2582 el4
= SubElement(el3
, 'text:p', attrib
={
2583 'text:style-name': 'Table_20_Heading'})
2584 el4
.text
= 'Description'
2585 self
.set_current_element(el
)
2587 def depart_option_list(self
, node
):
2588 self
.set_to_parent()
2590 def visit_option_list_item(self
, node
):
2591 el
= self
.append_child('table:table-row')
2592 self
.set_current_element(el
)
2594 def depart_option_list_item(self
, node
):
2595 self
.set_to_parent()
2597 def visit_option_group(self
, node
):
2598 el
= self
.append_child('table:table-cell', attrib
={
2599 'table:style-name': 'Table%d.A2' % self
.table_count
,
2600 'office:value-type': 'string',
2602 self
.set_current_element(el
)
2604 def depart_option_group(self
, node
):
2605 self
.set_to_parent()
2607 def visit_option(self
, node
):
2608 el
= self
.append_child('text:p', attrib
={
2609 'text:style-name': 'Table_20_Contents'})
2610 el
.text
= node
.astext()
2612 def depart_option(self
, node
):
2615 def visit_option_string(self
, node
):
2618 def depart_option_string(self
, node
):
2621 def visit_option_argument(self
, node
):
2624 def depart_option_argument(self
, node
):
2627 def visit_description(self
, node
):
2628 el
= self
.append_child('table:table-cell', attrib
={
2629 'table:style-name': 'Table%d.B2' % self
.table_count
,
2630 'office:value-type': 'string',
2632 el1
= SubElement(el
, 'text:p', attrib
={
2633 'text:style-name': 'Table_20_Contents'})
2634 el1
.text
= node
.astext()
2635 raise nodes
.SkipChildren()
2637 def depart_description(self
, node
):
2640 def visit_paragraph(self
, node
):
2641 self
.in_paragraph
= True
2643 el
= self
.append_p('header')
2644 elif self
.in_footer
:
2645 el
= self
.append_p('footer')
2647 style_name
= self
.paragraph_style_stack
[-1]
2648 el
= self
.append_child('text:p',
2649 attrib
={'text:style-name': style_name
})
2650 self
.append_pending_ids(el
)
2651 self
.set_current_element(el
)
2653 def depart_paragraph(self
, node
):
2654 self
.in_paragraph
= False
2655 self
.set_to_parent()
2657 self
.header_content
.append(
2658 self
.current_element
.getchildren()[-1])
2659 self
.current_element
.remove(
2660 self
.current_element
.getchildren()[-1])
2661 elif self
.in_footer
:
2662 self
.footer_content
.append(
2663 self
.current_element
.getchildren()[-1])
2664 self
.current_element
.remove(
2665 self
.current_element
.getchildren()[-1])
2667 def visit_problematic(self
, node
):
2670 def depart_problematic(self
, node
):
2673 def visit_raw(self
, node
):
2674 if 'format' in node
.attributes
:
2675 formats
= node
.attributes
['format']
2676 formatlist
= formats
.split()
2677 if 'odt' in formatlist
:
2678 rawstr
= node
.astext()
2679 attrstr
= ' '.join(['%s="%s"' % (k
, v
, )
2680 for k
,v
in CONTENT_NAMESPACE_ATTRIB
.items()])
2681 contentstr
= '<stuff %s>%s</stuff>' % (attrstr
, rawstr
, )
2682 if WhichElementTree
!= "lxml":
2683 contentstr
= contentstr
.encode("utf-8")
2684 content
= etree
.fromstring(contentstr
)
2685 elements
= content
.getchildren()
2686 if len(elements
) > 0:
2690 elif self
.in_footer
:
2693 self
.current_element
.append(el1
)
2694 raise nodes
.SkipChildren()
2696 def depart_raw(self
, node
):
2699 elif self
.in_footer
:
2704 def visit_reference(self
, node
):
2705 text
= node
.astext()
2706 if self
.settings
.create_links
:
2707 if node
.has_key('refuri'):
2708 href
= node
['refuri']
2709 if ( self
.settings
.cloak_email_addresses
2710 and href
.startswith('mailto:')):
2711 href
= self
.cloak_mailto(href
)
2712 el
= self
.append_child('text:a', attrib
={
2713 'xlink:href': '%s' % href
,
2714 'xlink:type': 'simple',
2716 self
.set_current_element(el
)
2717 elif node
.has_key('refid'):
2718 if self
.settings
.create_links
:
2719 href
= node
['refid']
2720 el
= self
.append_child('text:reference-ref', attrib
={
2721 'text:ref-name': '%s' % href
,
2722 'text:reference-format': 'text',
2725 self
.document
.reporter
.warning(
2726 'References must have "refuri" or "refid" attribute.')
2727 if (self
.in_table_of_contents
and
2728 len(node
.children
) >= 1 and
2729 isinstance(node
.children
[0], docutils
.nodes
.generated
)):
2730 node
.remove(node
.children
[0])
2732 def depart_reference(self
, node
):
2733 if self
.settings
.create_links
:
2734 if node
.has_key('refuri'):
2735 self
.set_to_parent()
2737 def visit_rubric(self
, node
):
2738 style_name
= self
.rststyle('rubric')
2739 classes
= node
.get('classes')
2744 el
= SubElement(self
.current_element
, 'text:h', attrib
= {
2745 #'text:outline-level': '%d' % section_level,
2746 #'text:style-name': 'Heading_20_%d' % section_level,
2747 'text:style-name': style_name
,
2749 text
= node
.astext()
2750 el
.text
= self
.encode(text
)
2752 def depart_rubric(self
, node
):
2755 def visit_section(self
, node
, move_ids
=1):
2756 self
.section_level
+= 1
2757 self
.section_count
+= 1
2758 if self
.settings
.create_sections
:
2759 el
= self
.append_child('text:section', attrib
={
2760 'text:name': 'Section%d' % self
.section_count
,
2761 'text:style-name': 'Sect%d' % self
.section_level
,
2763 self
.set_current_element(el
)
2765 def depart_section(self
, node
):
2766 self
.section_level
-= 1
2767 if self
.settings
.create_sections
:
2768 self
.set_to_parent()
2770 def visit_strong(self
, node
):
2771 el
= SubElement(self
.current_element
, 'text:span',
2772 attrib
={'text:style-name': self
.rststyle('strong')})
2773 self
.set_current_element(el
)
2775 def depart_strong(self
, node
):
2776 self
.set_to_parent()
2778 def visit_substitution_definition(self
, node
):
2779 raise nodes
.SkipChildren()
2781 def depart_substitution_definition(self
, node
):
2784 def visit_system_message(self
, node
):
2787 def depart_system_message(self
, node
):
2790 def get_table_style(self
, node
):
2793 use_predefined_table_style
= False
2794 str_classes
= node
.get('classes')
2795 if str_classes
is not None:
2796 for str_class
in str_classes
:
2797 if str_class
.startswith(TABLESTYLEPREFIX
):
2798 table_name
= str_class
2799 use_predefined_table_style
= True
2801 if table_name
is not None:
2802 table_style
= self
.table_styles
.get(table_name
)
2803 if table_style
is None:
2804 # If we can't find the table style, issue warning
2805 # and use the default table style.
2806 self
.document
.reporter
.warning(
2807 'Can\'t find table style "%s". Using default.' % (
2809 table_name
= TABLENAMEDEFAULT
2810 table_style
= self
.table_styles
.get(table_name
)
2811 if table_style
is None:
2812 # If we can't find the default table style, issue a warning
2813 # and use a built-in default style.
2814 self
.document
.reporter
.warning(
2815 'Can\'t find default table style "%s". Using built-in default.' % (
2817 table_style
= BUILTIN_DEFAULT_TABLE_STYLE
2819 table_name
= TABLENAMEDEFAULT
2820 table_style
= self
.table_styles
.get(table_name
)
2821 if table_style
is None:
2822 # If we can't find the default table style, issue a warning
2823 # and use a built-in default style.
2824 self
.document
.reporter
.warning(
2825 'Can\'t find default table style "%s". Using built-in default.' % (
2827 table_style
= BUILTIN_DEFAULT_TABLE_STYLE
2830 def visit_table(self
, node
):
2831 self
.table_count
+= 1
2832 table_style
= self
.get_table_style(node
)
2833 table_name
= '%s%%d' % TABLESTYLEPREFIX
2834 el1
= SubElement(self
.automatic_styles
, 'style:style', attrib
={
2835 'style:name': self
.rststyle(
2836 '%s' % table_name
, ( self
.table_count
, )),
2837 'style:family': 'table',
2839 if table_style
.backgroundcolor
is None:
2840 el1_1
= SubElement(el1
, 'style:table-properties', attrib
={
2841 #'style:width': '17.59cm',
2842 #'table:align': 'margins',
2843 'table:align': 'left',
2844 'fo:margin-top': '0in',
2845 'fo:margin-bottom': '0.10in',
2848 el1_1
= SubElement(el1
, 'style:table-properties', attrib
={
2849 #'style:width': '17.59cm',
2850 'table:align': 'margins',
2851 'fo:margin-top': '0in',
2852 'fo:margin-bottom': '0.10in',
2853 'fo:background-color': table_style
.backgroundcolor
,
2855 # We use a single cell style for all cells in this table.
2856 # That's probably not correct, but seems to work.
2857 el2
= SubElement(self
.automatic_styles
, 'style:style', attrib
={
2858 'style:name': self
.rststyle(
2859 '%s.%%c%%d' % table_name
, ( self
.table_count
, 'A', 1, )),
2860 'style:family': 'table-cell',
2862 thickness
= self
.settings
.table_border_thickness
2863 if thickness
is None:
2864 line_style1
= table_style
.border
2866 line_style1
= '0.%03dcm solid #000000' % (thickness
, )
2867 el2_1
= SubElement(el2
, 'style:table-cell-properties', attrib
={
2868 'fo:padding': '0.049cm',
2869 'fo:border-left': line_style1
,
2870 'fo:border-right': line_style1
,
2871 'fo:border-top': line_style1
,
2872 'fo:border-bottom': line_style1
,
2875 for child
in node
.children
:
2876 if child
.tagname
== 'title':
2877 title
= child
.astext()
2879 if title
is not None:
2880 el3
= self
.append_p('table-title', title
)
2883 el4
= SubElement(self
.current_element
, 'table:table', attrib
={
2884 'table:name': self
.rststyle(
2885 '%s' % table_name
, ( self
.table_count
, )),
2886 'table:style-name': self
.rststyle(
2887 '%s' % table_name
, ( self
.table_count
, )),
2889 self
.set_current_element(el4
)
2890 self
.current_table_style
= el1
2891 self
.table_width
= 0.0
2893 def depart_table(self
, node
):
2894 attribkey
= add_ns('style:width', nsdict
=SNSD
)
2895 attribval
= '%.4fin' % (self
.table_width
, )
2896 el1
= self
.current_table_style
2898 el2
.attrib
[attribkey
] = attribval
2899 self
.set_to_parent()
2901 def visit_tgroup(self
, node
):
2902 self
.column_count
= ord('A') - 1
2904 def depart_tgroup(self
, node
):
2907 def visit_colspec(self
, node
):
2908 self
.column_count
+= 1
2909 colspec_name
= self
.rststyle(
2910 '%s%%d.%%s' % TABLESTYLEPREFIX
,
2911 (self
.table_count
, chr(self
.column_count
), )
2913 colwidth
= node
['colwidth'] / 12.0
2914 el1
= SubElement(self
.automatic_styles
, 'style:style', attrib
={
2915 'style:name': colspec_name
,
2916 'style:family': 'table-column',
2918 el1_1
= SubElement(el1
, 'style:table-column-properties', attrib
={
2919 'style:column-width': '%.4fin' % colwidth
2922 el2
= self
.append_child('table:table-column', attrib
={
2923 'table:style-name': colspec_name
,
2925 self
.table_width
+= colwidth
2927 def depart_colspec(self
, node
):
2930 def visit_thead(self
, node
):
2931 el
= self
.append_child('table:table-header-rows')
2932 self
.set_current_element(el
)
2933 self
.in_thead
= True
2934 self
.paragraph_style_stack
.append('Table_20_Heading')
2936 def depart_thead(self
, node
):
2937 self
.set_to_parent()
2938 self
.in_thead
= False
2939 self
.paragraph_style_stack
.pop()
2941 def visit_row(self
, node
):
2942 self
.column_count
= ord('A') - 1
2943 el
= self
.append_child('table:table-row')
2944 self
.set_current_element(el
)
2946 def depart_row(self
, node
):
2947 self
.set_to_parent()
2949 def visit_entry(self
, node
):
2950 self
.column_count
+= 1
2951 cellspec_name
= self
.rststyle(
2952 '%s%%d.%%c%%d' % TABLESTYLEPREFIX
,
2953 (self
.table_count
, 'A', 1, )
2956 'table:style-name': cellspec_name
,
2957 'office:value-type': 'string',
2959 morecols
= node
.get('morecols', 0)
2961 attrib
['table:number-columns-spanned'] = '%d' % (morecols
+ 1,)
2962 self
.column_count
+= morecols
2963 morerows
= node
.get('morerows', 0)
2965 attrib
['table:number-rows-spanned'] = '%d' % (morerows
+ 1,)
2966 el1
= self
.append_child('table:table-cell', attrib
=attrib
)
2967 self
.set_current_element(el1
)
2969 def depart_entry(self
, node
):
2970 self
.set_to_parent()
2972 def visit_tbody(self
, node
):
2975 def depart_tbody(self
, node
):
2978 def visit_target(self
, node
):
2980 # I don't know how to implement targets in ODF.
2981 # How do we create a target in oowriter? A cross-reference?
2982 if not (node
.has_key('refuri') or node
.has_key('refid')
2983 or node
.has_key('refname')):
2988 def depart_target(self
, node
):
2991 def visit_title(self
, node
, move_ids
=1, title_type
='title'):
2992 if isinstance(node
.parent
, docutils
.nodes
.section
):
2993 section_level
= self
.section_level
2994 if section_level
> 7:
2995 self
.document
.reporter
.warning(
2996 'Heading/section levels greater than 7 not supported.')
2997 self
.document
.reporter
.warning(
2998 ' Reducing to heading level 7 for heading: "%s"' % (
3001 el1
= self
.append_child('text:h', attrib
= {
3002 'text:outline-level': '%d' % section_level
,
3003 #'text:style-name': 'Heading_20_%d' % section_level,
3004 'text:style-name': self
.rststyle(
3005 'heading%d', (section_level
, )),
3007 self
.append_pending_ids(el1
)
3008 self
.set_current_element(el1
)
3009 elif isinstance(node
.parent
, docutils
.nodes
.document
):
3010 # text = self.settings.title
3012 # text = node.astext()
3013 el1
= SubElement(self
.current_element
, 'text:p', attrib
= {
3014 'text:style-name': self
.rststyle(title_type
),
3016 self
.append_pending_ids(el1
)
3017 text
= node
.astext()
3019 self
.found_doc_title
= True
3020 self
.set_current_element(el1
)
3022 def depart_title(self
, node
):
3023 if (isinstance(node
.parent
, docutils
.nodes
.section
) or
3024 isinstance(node
.parent
, docutils
.nodes
.document
)):
3025 self
.set_to_parent()
3027 def visit_subtitle(self
, node
, move_ids
=1):
3028 self
.visit_title(node
, move_ids
, title_type
='subtitle')
3030 def depart_subtitle(self
, node
):
3031 self
.depart_title(node
)
3033 def visit_title_reference(self
, node
):
3034 el
= self
.append_child('text:span', attrib
={
3035 'text:style-name': self
.rststyle('quotation')})
3036 el
.text
= self
.encode(node
.astext())
3037 raise nodes
.SkipChildren()
3039 def depart_title_reference(self
, node
):
3042 def generate_table_of_content_entry_template(self
, el1
):
3043 for idx
in range(1, 11):
3044 el2
= SubElement(el1
,
3045 'text:table-of-content-entry-template',
3047 'text:outline-level': "%d" % (idx
, ),
3048 'text:style-name': self
.rststyle('contents-%d' % (idx
, )),
3050 el3
= SubElement(el2
, 'text:index-entry-chapter')
3051 el3
= SubElement(el2
, 'text:index-entry-text')
3052 el3
= SubElement(el2
, 'text:index-entry-tab-stop', attrib
={
3053 'style:leader-char': ".",
3054 'style:type': "right",
3056 el3
= SubElement(el2
, 'text:index-entry-page-number')
3058 def find_title_label(self
, node
, class_type
, label_key
):
3061 for child
in node
.children
:
3062 if isinstance(child
, class_type
):
3065 if title_node
is not None:
3066 label
= title_node
.astext()
3068 label
= self
.language
.labels
[label_key
]
3071 def visit_topic(self
, node
):
3072 if 'classes' in node
.attributes
:
3073 if 'contents' in node
.attributes
['classes']:
3074 label
= self
.find_title_label(node
, docutils
.nodes
.title
,
3076 if self
.settings
.generate_oowriter_toc
:
3077 el1
= self
.append_child('text:table-of-content', attrib
={
3078 'text:name': 'Table of Contents1',
3079 'text:protected': 'true',
3080 'text:style-name': 'Sect1',
3082 el2
= SubElement(el1
,
3083 'text:table-of-content-source',
3085 'text:outline-level': '10',
3087 el3
=SubElement(el2
, 'text:index-title-template', attrib
={
3088 'text:style-name': 'Contents_20_Heading',
3091 self
.generate_table_of_content_entry_template(el2
)
3092 el4
= SubElement(el1
, 'text:index-body')
3093 el5
= SubElement(el4
, 'text:index-title')
3094 el6
= SubElement(el5
, 'text:p', attrib
={
3095 'text:style-name': self
.rststyle('contents-heading'),
3098 self
.save_current_element
= self
.current_element
3099 self
.table_of_content_index_body
= el4
3100 self
.set_current_element(el4
)
3102 el
= self
.append_p('horizontalline')
3103 el
= self
.append_p('centeredtextbody')
3104 el1
= SubElement(el
, 'text:span',
3105 attrib
={'text:style-name': self
.rststyle('strong')})
3107 self
.in_table_of_contents
= True
3108 elif 'abstract' in node
.attributes
['classes']:
3109 el
= self
.append_p('horizontalline')
3110 el
= self
.append_p('centeredtextbody')
3111 el1
= SubElement(el
, 'text:span',
3112 attrib
={'text:style-name': self
.rststyle('strong')})
3113 label
= self
.find_title_label(node
, docutils
.nodes
.title
,
3116 elif 'dedication' in node
.attributes
['classes']:
3117 el
= self
.append_p('horizontalline')
3118 el
= self
.append_p('centeredtextbody')
3119 el1
= SubElement(el
, 'text:span',
3120 attrib
={'text:style-name': self
.rststyle('strong')})
3121 label
= self
.find_title_label(node
, docutils
.nodes
.title
,
3125 def depart_topic(self
, node
):
3126 if 'classes' in node
.attributes
:
3127 if 'contents' in node
.attributes
['classes']:
3128 if self
.settings
.generate_oowriter_toc
:
3129 self
.update_toc_page_numbers(
3130 self
.table_of_content_index_body
)
3131 self
.set_current_element(self
.save_current_element
)
3133 el
= self
.append_p('horizontalline')
3134 self
.in_table_of_contents
= False
3136 def update_toc_page_numbers(self
, el
):
3138 self
.update_toc_collect(el
, 0, collection
)
3139 self
.update_toc_add_numbers(collection
)
3141 def update_toc_collect(self
, el
, level
, collection
):
3142 collection
.append((level
, el
))
3144 for child_el
in el
.getchildren():
3145 if child_el
.tag
!= 'text:index-body':
3146 self
.update_toc_collect(child_el
, level
, collection
)
3148 def update_toc_add_numbers(self
, collection
):
3149 for level
, el1
in collection
:
3150 if (el1
.tag
== 'text:p' and
3151 el1
.text
!= 'Table of Contents'):
3152 el2
= SubElement(el1
, 'text:tab')
3156 def visit_transition(self
, node
):
3157 el
= self
.append_p('horizontalline')
3159 def depart_transition(self
, node
):
3165 def visit_warning(self
, node
):
3166 self
.generate_admonition(node
, 'warning')
3168 def depart_warning(self
, node
):
3169 self
.paragraph_style_stack
.pop()
3171 def visit_attention(self
, node
):
3172 self
.generate_admonition(node
, 'attention')
3174 depart_attention
= depart_warning
3176 def visit_caution(self
, node
):
3177 self
.generate_admonition(node
, 'caution')
3179 depart_caution
= depart_warning
3181 def visit_danger(self
, node
):
3182 self
.generate_admonition(node
, 'danger')
3184 depart_danger
= depart_warning
3186 def visit_error(self
, node
):
3187 self
.generate_admonition(node
, 'error')
3189 depart_error
= depart_warning
3191 def visit_hint(self
, node
):
3192 self
.generate_admonition(node
, 'hint')
3194 depart_hint
= depart_warning
3196 def visit_important(self
, node
):
3197 self
.generate_admonition(node
, 'important')
3199 depart_important
= depart_warning
3201 def visit_note(self
, node
):
3202 self
.generate_admonition(node
, 'note')
3204 depart_note
= depart_warning
3206 def visit_tip(self
, node
):
3207 self
.generate_admonition(node
, 'tip')
3209 depart_tip
= depart_warning
3211 def visit_admonition(self
, node
):
3213 for child
in node
.children
:
3214 if child
.tagname
== 'title':
3215 title
= child
.astext()
3217 classes1
= node
.get('classes')
3220 self
.generate_admonition(node
, 'generic', title
)
3222 depart_admonition
= depart_warning
3224 def generate_admonition(self
, node
, label
, title
=None):
3225 el1
= SubElement(self
.current_element
, 'text:p', attrib
= {
3226 'text:style-name': self
.rststyle('admon-%s-hdr', ( label
, )),
3231 el1
.text
= '%s!' % (label
.capitalize(), )
3232 s1
= self
.rststyle('admon-%s-body', ( label
, ))
3233 self
.paragraph_style_stack
.append(s1
)
3236 # Roles (e.g. subscript, superscript, strong, ...
3238 def visit_subscript(self
, node
):
3239 el
= self
.append_child('text:span', attrib
={
3240 'text:style-name': 'rststyle-subscript',
3242 self
.set_current_element(el
)
3244 def depart_subscript(self
, node
):
3245 self
.set_to_parent()
3247 def visit_superscript(self
, node
):
3248 el
= self
.append_child('text:span', attrib
={
3249 'text:style-name': 'rststyle-superscript',
3251 self
.set_current_element(el
)
3253 def depart_superscript(self
, node
):
3254 self
.set_to_parent()
3257 # Use an own reader to modify transformations done.
3258 class Reader(standalone
.Reader
):
3260 def get_transforms(self
):
3261 default
= standalone
.Reader
.get_transforms(self
)
3262 if self
.settings
.create_links
:
3266 if i
is not references
.DanglingReferences
]