2 # Author: Dave Kuhlman <dkuhlman@rexx.com>
3 # Copyright: This module has been placed in the public domain.
6 Open Document Format (ODF) Writer.
12 __docformat__
= 'reStructuredText'
20 from xml
.dom
import minidom
27 from docutils
import frontend
, nodes
, utils
, writers
, languages
28 from docutils
.readers
import standalone
29 from docutils
.transforms
import references
35 #from lxml import etree
36 #WhichElementTree = 'lxml'
37 raise ImportError('Ignoring lxml')
38 except ImportError, e
:
40 # 2. Try to use ElementTree from the Python standard library.
41 from xml
.etree
import ElementTree
as etree
42 WhichElementTree
= 'elementtree'
43 except ImportError, e
:
45 # 3. Try to use a version of ElementTree installed as a separate
47 from elementtree
import ElementTree
as etree
48 WhichElementTree
= 'elementtree'
49 except ImportError, e
:
50 s1
= 'Must install either a version of Python containing ' \
51 'ElementTree (Python version >=2.5) or install ElementTree.'
55 # Import pygments and odtwriter pygments formatters if possible.
58 import pygments
.lexers
59 from pygmentsformatter
import OdtPygmentsProgFormatter
, \
60 OdtPygmentsLaTeXFormatter
61 except ImportError, exp
:
64 # check for the Python Imaging Library
68 try: # sometimes PIL modules are put in PYTHONPATH's root
70 class PIL(object): pass # dummy wrapper
76 ## warnings.warn('importing IPShellEmbed', UserWarning)
77 ## from IPython.Shell import IPShellEmbed
78 ## args = ['-pdb', '-pi1', 'In <\\#>: ', '-pi2', ' .\\D.: ',
79 ## '-po', 'Out<\\#>: ', '-nosep']
80 ## ipshell = IPShellEmbed(args,
81 ## banner = 'Entering IPython. Press Ctrl-D to exit.',
82 ## exit_msg = 'Leaving Interpreter, back to program.')
86 # ElementTree does not support getparent method (lxml does).
87 # This wrapper class and the following support functions provide
88 # that support for the ability to get the parent of an element.
90 if WhichElementTree
== 'elementtree':
91 class _ElementInterfaceWrapper(etree
._ElementInterface
):
92 def __init__(self
, tag
, attrib
=None):
93 etree
._ElementInterface
.__init
__(self
, tag
, attrib
)
97 def setparent(self
, parent
):
104 # Constants and globals
106 SPACES_PATTERN
= re
.compile(r
'( +)')
107 TABS_PATTERN
= re
.compile(r
'(\t+)')
108 FILL_PAT1
= re
.compile(r
'^ +')
109 FILL_PAT2
= re
.compile(r
' {2,}')
111 TABLESTYLEPREFIX
= 'rststyle-table-'
112 TABLENAMEDEFAULT
= '%s0' % TABLESTYLEPREFIX
113 TABLEPROPERTYNAMES
= ('border', 'border-top', 'border-left',
114 'border-right', 'border-bottom', )
116 GENERATOR_DESC
= 'Docutils.org/odf_odt'
118 NAME_SPACE_1
= 'urn:oasis:names:tc:opendocument:xmlns:office:1.0'
120 CONTENT_NAMESPACE_DICT
= CNSD
= {
121 # 'office:version': '1.0',
122 'chart': 'urn:oasis:names:tc:opendocument:xmlns:chart:1.0',
123 'dc': 'http://purl.org/dc/elements/1.1/',
124 'dom': 'http://www.w3.org/2001/xml-events',
125 'dr3d': 'urn:oasis:names:tc:opendocument:xmlns:dr3d:1.0',
126 'draw': 'urn:oasis:names:tc:opendocument:xmlns:drawing:1.0',
127 'fo': 'urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0',
128 'form': 'urn:oasis:names:tc:opendocument:xmlns:form:1.0',
129 'math': 'http://www.w3.org/1998/Math/MathML',
130 'meta': 'urn:oasis:names:tc:opendocument:xmlns:meta:1.0',
131 'number': 'urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0',
132 'office': NAME_SPACE_1
,
133 'ooo': 'http://openoffice.org/2004/office',
134 'oooc': 'http://openoffice.org/2004/calc',
135 'ooow': 'http://openoffice.org/2004/writer',
136 'presentation': 'urn:oasis:names:tc:opendocument:xmlns:presentation:1.0',
138 'script': 'urn:oasis:names:tc:opendocument:xmlns:script:1.0',
139 'style': 'urn:oasis:names:tc:opendocument:xmlns:style:1.0',
140 'svg': 'urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0',
141 'table': 'urn:oasis:names:tc:opendocument:xmlns:table:1.0',
142 'text': 'urn:oasis:names:tc:opendocument:xmlns:text:1.0',
143 'xforms': 'http://www.w3.org/2002/xforms',
144 'xlink': 'http://www.w3.org/1999/xlink',
145 'xsd': 'http://www.w3.org/2001/XMLSchema',
146 'xsi': 'http://www.w3.org/2001/XMLSchema-instance',
149 STYLES_NAMESPACE_DICT
= SNSD
= {
150 # 'office:version': '1.0',
151 'chart': 'urn:oasis:names:tc:opendocument:xmlns:chart:1.0',
152 'dc': 'http://purl.org/dc/elements/1.1/',
153 'dom': 'http://www.w3.org/2001/xml-events',
154 'dr3d': 'urn:oasis:names:tc:opendocument:xmlns:dr3d:1.0',
155 'draw': 'urn:oasis:names:tc:opendocument:xmlns:drawing:1.0',
156 'fo': 'urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0',
157 'form': 'urn:oasis:names:tc:opendocument:xmlns:form:1.0',
158 'math': 'http://www.w3.org/1998/Math/MathML',
159 'meta': 'urn:oasis:names:tc:opendocument:xmlns:meta:1.0',
160 'number': 'urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0',
161 'office': NAME_SPACE_1
,
162 'presentation': 'urn:oasis:names:tc:opendocument:xmlns:presentation:1.0',
163 'ooo': 'http://openoffice.org/2004/office',
164 'oooc': 'http://openoffice.org/2004/calc',
165 'ooow': 'http://openoffice.org/2004/writer',
166 'script': 'urn:oasis:names:tc:opendocument:xmlns:script:1.0',
167 'style': 'urn:oasis:names:tc:opendocument:xmlns:style:1.0',
168 'svg': 'urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0',
169 'table': 'urn:oasis:names:tc:opendocument:xmlns:table:1.0',
170 'text': 'urn:oasis:names:tc:opendocument:xmlns:text:1.0',
171 'xlink': 'http://www.w3.org/1999/xlink',
174 MANIFEST_NAMESPACE_DICT
= MANNSD
= {
175 'manifest': 'urn:oasis:names:tc:opendocument:xmlns:manifest:1.0',
178 META_NAMESPACE_DICT
= METNSD
= {
179 # 'office:version': '1.0',
180 'dc': 'http://purl.org/dc/elements/1.1/',
181 'meta': 'urn:oasis:names:tc:opendocument:xmlns:meta:1.0',
182 'office': NAME_SPACE_1
,
183 'ooo': 'http://openoffice.org/2004/office',
184 'xlink': 'http://www.w3.org/1999/xlink',
188 # Attribute dictionaries for use with ElementTree (not lxml), which
189 # does not support use of nsmap parameter on Element() and SubElement().
191 CONTENT_NAMESPACE_ATTRIB
= {
192 #'office:version': '1.0',
193 'xmlns:chart': 'urn:oasis:names:tc:opendocument:xmlns:chart:1.0',
194 'xmlns:dc': 'http://purl.org/dc/elements/1.1/',
195 'xmlns:dom': 'http://www.w3.org/2001/xml-events',
196 'xmlns:dr3d': 'urn:oasis:names:tc:opendocument:xmlns:dr3d:1.0',
197 'xmlns:draw': 'urn:oasis:names:tc:opendocument:xmlns:drawing:1.0',
198 'xmlns:fo': 'urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0',
199 'xmlns:form': 'urn:oasis:names:tc:opendocument:xmlns:form:1.0',
200 'xmlns:math': 'http://www.w3.org/1998/Math/MathML',
201 'xmlns:meta': 'urn:oasis:names:tc:opendocument:xmlns:meta:1.0',
202 'xmlns:number': 'urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0',
203 'xmlns:office': NAME_SPACE_1
,
204 'xmlns:presentation': 'urn:oasis:names:tc:opendocument:xmlns:presentation:1.0',
205 'xmlns:ooo': 'http://openoffice.org/2004/office',
206 'xmlns:oooc': 'http://openoffice.org/2004/calc',
207 'xmlns:ooow': 'http://openoffice.org/2004/writer',
208 'xmlns:script': 'urn:oasis:names:tc:opendocument:xmlns:script:1.0',
209 'xmlns:style': 'urn:oasis:names:tc:opendocument:xmlns:style:1.0',
210 'xmlns:svg': 'urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0',
211 'xmlns:table': 'urn:oasis:names:tc:opendocument:xmlns:table:1.0',
212 'xmlns:text': 'urn:oasis:names:tc:opendocument:xmlns:text:1.0',
213 'xmlns:xforms': 'http://www.w3.org/2002/xforms',
214 'xmlns:xlink': 'http://www.w3.org/1999/xlink',
215 'xmlns:xsd': 'http://www.w3.org/2001/XMLSchema',
216 'xmlns:xsi': 'http://www.w3.org/2001/XMLSchema-instance',
219 STYLES_NAMESPACE_ATTRIB
= {
220 #'office:version': '1.0',
221 'xmlns:chart': 'urn:oasis:names:tc:opendocument:xmlns:chart:1.0',
222 'xmlns:dc': 'http://purl.org/dc/elements/1.1/',
223 'xmlns:dom': 'http://www.w3.org/2001/xml-events',
224 'xmlns:dr3d': 'urn:oasis:names:tc:opendocument:xmlns:dr3d:1.0',
225 'xmlns:draw': 'urn:oasis:names:tc:opendocument:xmlns:drawing:1.0',
226 'xmlns:fo': 'urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0',
227 'xmlns:form': 'urn:oasis:names:tc:opendocument:xmlns:form:1.0',
228 'xmlns:math': 'http://www.w3.org/1998/Math/MathML',
229 'xmlns:meta': 'urn:oasis:names:tc:opendocument:xmlns:meta:1.0',
230 'xmlns:number': 'urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0',
231 'xmlns:office': NAME_SPACE_1
,
232 'xmlns:presentation': 'urn:oasis:names:tc:opendocument:xmlns:presentation:1.0',
233 'xmlns:ooo': 'http://openoffice.org/2004/office',
234 'xmlns:oooc': 'http://openoffice.org/2004/calc',
235 'xmlns:ooow': 'http://openoffice.org/2004/writer',
236 'xmlns:script': 'urn:oasis:names:tc:opendocument:xmlns:script:1.0',
237 'xmlns:style': 'urn:oasis:names:tc:opendocument:xmlns:style:1.0',
238 'xmlns:svg': 'urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0',
239 'xmlns:table': 'urn:oasis:names:tc:opendocument:xmlns:table:1.0',
240 'xmlns:text': 'urn:oasis:names:tc:opendocument:xmlns:text:1.0',
241 'xmlns:xlink': 'http://www.w3.org/1999/xlink',
244 MANIFEST_NAMESPACE_ATTRIB
= {
245 'xmlns:manifest': 'urn:oasis:names:tc:opendocument:xmlns:manifest:1.0',
248 META_NAMESPACE_ATTRIB
= {
249 #'office:version': '1.0',
250 'xmlns:dc': 'http://purl.org/dc/elements/1.1/',
251 'xmlns:meta': 'urn:oasis:names:tc:opendocument:xmlns:meta:1.0',
252 'xmlns:office': NAME_SPACE_1
,
253 'xmlns:ooo': 'http://openoffice.org/2004/office',
254 'xmlns:xlink': 'http://www.w3.org/1999/xlink',
263 # ElementTree support functions.
264 # In order to be able to get the parent of elements, must use these
265 # instead of the functions with same name provided by ElementTree.
267 def Element(tag
, attrib
=None, nsmap
=None, nsdict
=CNSD
):
270 tag
, attrib
= fix_ns(tag
, attrib
, nsdict
)
271 if WhichElementTree
== 'lxml':
272 el
= etree
.Element(tag
, attrib
, nsmap
=nsmap
)
274 el
= _ElementInterfaceWrapper(tag
, attrib
)
277 def SubElement(parent
, tag
, attrib
=None, nsmap
=None, nsdict
=CNSD
):
280 tag
, attrib
= fix_ns(tag
, attrib
, nsdict
)
281 if WhichElementTree
== 'lxml':
282 el
= etree
.SubElement(parent
, tag
, attrib
, nsmap
=nsmap
)
284 el
= _ElementInterfaceWrapper(tag
, attrib
)
289 def fix_ns(tag
, attrib
, nsdict
):
290 nstag
= add_ns(tag
, nsdict
)
292 for key
, val
in attrib
.iteritems():
293 nskey
= add_ns(key
, nsdict
)
294 nsattrib
[nskey
] = val
295 return nstag
, nsattrib
297 def add_ns(tag
, nsdict
=CNSD
):
298 if WhichElementTree
== 'lxml':
299 nstag
, name
= tag
.split(':')
300 ns
= nsdict
.get(nstag
)
302 raise RuntimeError, 'Invalid namespace prefix: %s' % nstag
303 tag
= '{%s}%s' % (ns
, name
,)
307 outstream
= StringIO
.StringIO()
308 if sys
.version_info
>= (3, 2):
309 et
.write(outstream
, encoding
="unicode")
312 s1
= outstream
.getvalue()
317 def escape_cdata(text
):
318 text
= text
.replace("&", "&")
319 text
= text
.replace("<", "<")
320 text
= text
.replace(">", ">")
323 if ord(char
) >= ord("\x7f"):
324 ascii
+= "&#x%X;" % ( ord(char
), )
331 WORD_SPLIT_PAT1
= re
.compile(r
'\b(\w*)\b\W*')
333 def split_words(line
):
334 # We need whitespace at the end of the string for our regexpr.
338 mo
= WORD_SPLIT_PAT1
.search(line
, pos1
)
339 while mo
is not None:
340 word
= mo
.groups()[0]
343 mo
= WORD_SPLIT_PAT1
.search(line
, pos1
)
352 class TableStyle(object):
353 def __init__(self
, border
=None, backgroundcolor
=None):
355 self
.backgroundcolor
= backgroundcolor
356 def get_border_(self
):
358 def set_border_(self
, border
):
359 self
.border_
= border
360 border
= property(get_border_
, set_border_
)
361 def get_backgroundcolor_(self
):
362 return self
.backgroundcolor_
363 def set_backgroundcolor_(self
, backgroundcolor
):
364 self
.backgroundcolor_
= backgroundcolor
365 backgroundcolor
= property(get_backgroundcolor_
, set_backgroundcolor_
)
367 BUILTIN_DEFAULT_TABLE_STYLE
= TableStyle(
368 border
= '0.0007in solid #000000')
371 # Information about the indentation level for lists nested inside
372 # other contexts, e.g. dictionary lists.
373 class ListLevel(object):
374 def __init__(self
, level
, sibling_level
=True, nested_level
=True):
376 self
.sibling_level
= sibling_level
377 self
.nested_level
= nested_level
378 def set_sibling(self
, sibling_level
): self
.sibling_level
= sibling_level
379 def get_sibling(self
): return self
.sibling_level
380 def set_nested(self
, nested_level
): self
.nested_level
= nested_level
381 def get_nested(self
): return self
.nested_level
382 def set_level(self
, level
): self
.level
= level
383 def get_level(self
): return self
.level
386 class Writer(writers
.Writer
):
388 MIME_TYPE
= 'application/vnd.oasis.opendocument.text'
391 supported
= ('odt', )
392 """Formats this writer supports."""
394 default_stylesheet
= 'styles' + EXTENSION
396 default_stylesheet_path
= utils
.relative_path(
397 os
.path
.join(os
.getcwd(), 'dummy'),
398 os
.path
.join(os
.path
.dirname(__file__
), default_stylesheet
))
400 default_template
= 'template.txt'
402 default_template_path
= utils
.relative_path(
403 os
.path
.join(os
.getcwd(), 'dummy'),
404 os
.path
.join(os
.path
.dirname(__file__
), default_template
))
407 'ODF-Specific Options',
410 ('Specify a stylesheet. '
411 'Default: "%s"' % default_stylesheet_path
,
414 'default': default_stylesheet_path
,
417 ('Specify a configuration/mapping file relative to the '
419 'directory for additional ODF options. '
420 'In particular, this file may contain a section named '
421 '"Formats" that maps default style names to '
422 'names to be used in the resulting output file allowing for '
423 'adhering to external standards. '
424 'For more info and the format of the configuration/mapping file, '
425 'see the odtwriter doc.',
426 ['--odf-config-file'],
427 {'metavar': '<file>'}),
428 ('Obfuscate email addresses to confuse harvesters while still '
429 'keeping email links usable with standards-compliant browsers.',
430 ['--cloak-email-addresses'],
432 'action': 'store_true',
433 'dest': 'cloak_email_addresses',
434 'validator': frontend
.validate_boolean
}),
435 ('Do not obfuscate email addresses.',
436 ['--no-cloak-email-addresses'],
438 'action': 'store_false',
439 'dest': 'cloak_email_addresses',
440 'validator': frontend
.validate_boolean
}),
441 ('Specify the thickness of table borders in thousands of a cm. '
443 ['--table-border-thickness'],
445 'validator': frontend
.validate_nonnegative_int
}),
446 ('Add syntax highlighting in literal code blocks.',
447 ['--add-syntax-highlighting'],
449 'action': 'store_true',
450 'dest': 'add_syntax_highlighting',
451 'validator': frontend
.validate_boolean
}),
452 ('Do not add syntax highlighting in literal code blocks. (default)',
453 ['--no-syntax-highlighting'],
455 'action': 'store_false',
456 'dest': 'add_syntax_highlighting',
457 'validator': frontend
.validate_boolean
}),
458 ('Create sections for headers. (default)',
459 ['--create-sections'],
461 'action': 'store_true',
462 'dest': 'create_sections',
463 'validator': frontend
.validate_boolean
}),
464 ('Do not create sections for headers.',
467 'action': 'store_false',
468 'dest': 'create_sections',
469 'validator': frontend
.validate_boolean
}),
473 'action': 'store_true',
474 'dest': 'create_links',
475 'validator': frontend
.validate_boolean
}),
476 ('Do not create links. (default)',
479 'action': 'store_false',
480 'dest': 'create_links',
481 'validator': frontend
.validate_boolean
}),
482 ('Generate endnotes at end of document, not footnotes '
483 'at bottom of page.',
484 ['--endnotes-end-doc'],
486 'action': 'store_true',
487 'dest': 'endnotes_end_doc',
488 'validator': frontend
.validate_boolean
}),
489 ('Generate footnotes at bottom of page, not endnotes '
490 'at end of document. (default)',
491 ['--no-endnotes-end-doc'],
493 'action': 'store_false',
494 'dest': 'endnotes_end_doc',
495 'validator': frontend
.validate_boolean
}),
496 ('Generate a bullet list table of contents, not '
497 'an ODF/oowriter table of contents.',
498 ['--generate-list-toc'],
500 'action': 'store_false',
501 'dest': 'generate_oowriter_toc',
502 'validator': frontend
.validate_boolean
}),
503 ('Generate an ODF/oowriter table of contents, not '
504 'a bullet list. (default)',
505 ['--generate-oowriter-toc'],
507 'action': 'store_true',
508 'dest': 'generate_oowriter_toc',
509 'validator': frontend
.validate_boolean
}),
510 ('Specify the contents of an custom header line. '
511 'See odf_odt writer documentation for details '
512 'about special field character sequences.',
513 ['--custom-odt-header'],
515 'dest': 'custom_header',
517 ('Specify the contents of an custom footer line. '
518 'See odf_odt writer documentation for details '
519 'about special field character sequences.',
520 ['--custom-odt-footer'],
522 'dest': 'custom_footer',
527 settings_defaults
= {
528 'output_encoding_error_handler': 'xmlcharrefreplace',
531 relative_path_settings
= (
535 config_section
= 'odf_odt writer'
536 config_section_dependencies
= (
541 writers
.Writer
.__init
__(self
)
542 self
.translator_class
= ODFTranslator
545 self
.settings
= self
.document
.settings
546 self
.visitor
= self
.translator_class(self
.document
)
547 self
.visitor
.retrieve_styles(self
.EXTENSION
)
548 self
.document
.walkabout(self
.visitor
)
549 self
.visitor
.add_doc_title()
550 self
.assemble_my_parts()
551 self
.output
= self
.parts
['whole']
553 def assemble_my_parts(self
):
554 """Assemble the `self.parts` dictionary. Extend in subclasses.
556 writers
.Writer
.assemble_parts(self
)
557 f
= tempfile
.NamedTemporaryFile()
558 zfile
= zipfile
.ZipFile(f
, 'w', zipfile
.ZIP_DEFLATED
)
559 self
.write_zip_str(zfile
, 'mimetype', self
.MIME_TYPE
,
560 compress_type
=zipfile
.ZIP_STORED
)
561 content
= self
.visitor
.content_astext()
562 self
.write_zip_str(zfile
, 'content.xml', content
)
563 s1
= self
.create_manifest()
564 self
.write_zip_str(zfile
, 'META-INF/manifest.xml', s1
)
565 s1
= self
.create_meta()
566 self
.write_zip_str(zfile
, 'meta.xml', s1
)
567 s1
= self
.get_stylesheet()
568 self
.write_zip_str(zfile
, 'styles.xml', s1
)
569 self
.store_embedded_files(zfile
)
570 self
.copy_from_stylesheet(zfile
)
575 self
.parts
['whole'] = whole
576 self
.parts
['encoding'] = self
.document
.settings
.output_encoding
577 self
.parts
['version'] = docutils
.__version
__
579 def write_zip_str(self
, zfile
, name
, bytes
, compress_type
=zipfile
.ZIP_DEFLATED
):
580 localtime
= time
.localtime(time
.time())
581 zinfo
= zipfile
.ZipInfo(name
, localtime
)
582 # Add some standard UNIX file access permissions (-rw-r--r--).
583 zinfo
.external_attr
= (0x81a4 & 0xFFFF) << 16L
584 zinfo
.compress_type
= compress_type
585 zfile
.writestr(zinfo
, bytes
)
587 def store_embedded_files(self
, zfile
):
588 embedded_files
= self
.visitor
.get_embedded_file_list()
589 for source
, destination
in embedded_files
:
594 destination1
= destination
.decode('latin-1').encode('utf-8')
595 zfile
.write(source
, destination1
)
597 self
.document
.reporter
.warning(
598 "Can't open file %s." % (source
, ))
600 def get_settings(self
):
602 modeled after get_stylesheet
604 stylespath
= self
.settings
.stylesheet
605 zfile
= zipfile
.ZipFile(stylespath
, 'r')
606 s1
= zfile
.read('settings.xml')
610 def get_stylesheet(self
):
611 """Get the stylesheet from the visitor.
612 Ask the visitor to setup the page.
614 s1
= self
.visitor
.setup_page()
617 def copy_from_stylesheet(self
, outzipfile
):
618 """Copy images, settings, etc from the stylesheet doc into target doc.
620 stylespath
= self
.settings
.stylesheet
621 inzipfile
= zipfile
.ZipFile(stylespath
, 'r')
623 s1
= inzipfile
.read('settings.xml')
624 self
.write_zip_str(outzipfile
, 'settings.xml', s1
)
626 namelist
= inzipfile
.namelist()
627 for name
in namelist
:
628 if name
.startswith('Pictures/'):
629 imageobj
= inzipfile
.read(name
)
630 outzipfile
.writestr(name
, imageobj
)
633 def assemble_parts(self
):
636 def create_manifest(self
):
637 if WhichElementTree
== 'lxml':
638 root
= Element('manifest:manifest',
639 nsmap
=MANIFEST_NAMESPACE_DICT
,
640 nsdict
=MANIFEST_NAMESPACE_DICT
,
643 root
= Element('manifest:manifest',
644 attrib
=MANIFEST_NAMESPACE_ATTRIB
,
645 nsdict
=MANIFEST_NAMESPACE_DICT
,
647 doc
= etree
.ElementTree(root
)
648 SubElement(root
, 'manifest:file-entry', attrib
={
649 'manifest:media-type': self
.MIME_TYPE
,
650 'manifest:full-path': '/',
652 SubElement(root
, 'manifest:file-entry', attrib
={
653 'manifest:media-type': 'text/xml',
654 'manifest:full-path': 'content.xml',
656 SubElement(root
, 'manifest:file-entry', attrib
={
657 'manifest:media-type': 'text/xml',
658 'manifest:full-path': 'styles.xml',
660 SubElement(root
, 'manifest:file-entry', attrib
={
661 'manifest:media-type': 'text/xml',
662 'manifest:full-path': 'settings.xml',
664 SubElement(root
, 'manifest:file-entry', attrib
={
665 'manifest:media-type': 'text/xml',
666 'manifest:full-path': 'meta.xml',
669 doc
= minidom
.parseString(s1
)
670 s1
= doc
.toprettyxml(' ')
673 def create_meta(self
):
674 if WhichElementTree
== 'lxml':
675 root
= Element('office:document-meta',
676 nsmap
=META_NAMESPACE_DICT
,
677 nsdict
=META_NAMESPACE_DICT
,
680 root
= Element('office:document-meta',
681 attrib
=META_NAMESPACE_ATTRIB
,
682 nsdict
=META_NAMESPACE_DICT
,
684 doc
= etree
.ElementTree(root
)
685 root
= SubElement(root
, 'office:meta', nsdict
=METNSD
)
686 el1
= SubElement(root
, 'meta:generator', nsdict
=METNSD
)
687 el1
.text
= 'Docutils/rst2odf.py/%s' % (VERSION
, )
688 s1
= os
.environ
.get('USER', '')
689 el1
= SubElement(root
, 'meta:initial-creator', nsdict
=METNSD
)
691 s2
= time
.strftime('%Y-%m-%dT%H:%M:%S', time
.localtime())
692 el1
= SubElement(root
, 'meta:creation-date', nsdict
=METNSD
)
694 el1
= SubElement(root
, 'dc:creator', nsdict
=METNSD
)
696 el1
= SubElement(root
, 'dc:date', nsdict
=METNSD
)
698 el1
= SubElement(root
, 'dc:language', nsdict
=METNSD
)
700 el1
= SubElement(root
, 'meta:editing-cycles', nsdict
=METNSD
)
702 el1
= SubElement(root
, 'meta:editing-duration', nsdict
=METNSD
)
703 el1
.text
= 'PT00M01S'
704 title
= self
.visitor
.get_title()
705 el1
= SubElement(root
, 'dc:title', nsdict
=METNSD
)
709 el1
.text
= '[no title]'
710 meta_dict
= self
.visitor
.get_meta_dict()
711 keywordstr
= meta_dict
.get('keywords')
712 if keywordstr
is not None:
713 keywords
= split_words(keywordstr
)
714 for keyword
in keywords
:
715 el1
= SubElement(root
, 'meta:keyword', nsdict
=METNSD
)
717 description
= meta_dict
.get('description')
718 if description
is not None:
719 el1
= SubElement(root
, 'dc:description', nsdict
=METNSD
)
720 el1
.text
= description
722 #doc = minidom.parseString(s1)
723 #s1 = doc.toprettyxml(' ')
726 # class ODFTranslator(nodes.SparseNodeVisitor):
728 class ODFTranslator(nodes
.GenericNodeVisitor
):
731 'attribution', 'blockindent', 'blockquote', 'blockquote-bulletitem',
732 'blockquote-bulletlist', 'blockquote-enumitem', 'blockquote-enumlist',
733 'bulletitem', 'bulletlist',
735 'centeredtextbody', 'codeblock', 'codeblock-indented',
736 'codeblock-classname', 'codeblock-comment', 'codeblock-functionname',
737 'codeblock-keyword', 'codeblock-name', 'codeblock-number',
738 'codeblock-operator', 'codeblock-string', 'emphasis', 'enumitem',
739 'enumlist', 'epigraph', 'epigraph-bulletitem', 'epigraph-bulletlist',
740 'epigraph-enumitem', 'epigraph-enumlist', 'footer',
741 'footnote', 'citation',
742 'header', 'highlights', 'highlights-bulletitem',
743 'highlights-bulletlist', 'highlights-enumitem', 'highlights-enumlist',
744 'horizontalline', 'inlineliteral', 'quotation', 'rubric',
745 'strong', 'table-title', 'textbody', 'tocbulletlist', 'tocenumlist',
755 'admon-attention-hdr',
756 'admon-attention-body',
758 'admon-caution-body',
764 'admon-generic-body',
767 'admon-important-hdr',
768 'admon-important-body',
774 'admon-warning-body',
776 'tableoption.%c', 'tableoption.%c%d', 'Table%d', 'Table%d.%c',
784 'image', 'figureframe',
787 def __init__(self
, document
):
788 #nodes.SparseNodeVisitor.__init__(self, document)
789 nodes
.GenericNodeVisitor
.__init
__(self
, document
)
790 self
.settings
= document
.settings
791 lcode
= self
.settings
.language_code
792 self
.language
= languages
.get_language(lcode
, document
.reporter
)
793 self
.format_map
= { }
794 if self
.settings
.odf_config_file
:
795 from ConfigParser
import ConfigParser
797 parser
= ConfigParser()
798 parser
.read(self
.settings
.odf_config_file
)
799 for rststyle
, format
in parser
.items("Formats"):
800 if rststyle
not in self
.used_styles
:
801 self
.document
.reporter
.warning(
802 'Style "%s" is not a style used by odtwriter.' % (
804 self
.format_map
[rststyle
] = format
.decode('utf-8')
805 self
.section_level
= 0
806 self
.section_count
= 0
807 # Create ElementTree content and styles documents.
808 if WhichElementTree
== 'lxml':
810 'office:document-content',
811 nsmap
=CONTENT_NAMESPACE_DICT
,
815 'office:document-content',
816 attrib
=CONTENT_NAMESPACE_ATTRIB
,
818 self
.content_tree
= etree
.ElementTree(element
=root
)
819 self
.current_element
= root
820 SubElement(root
, 'office:scripts')
821 SubElement(root
, 'office:font-face-decls')
822 el
= SubElement(root
, 'office:automatic-styles')
823 self
.automatic_styles
= el
824 el
= SubElement(root
, 'office:body')
825 el
= self
.generate_content_element(el
)
826 self
.current_element
= el
827 self
.body_text_element
= el
828 self
.paragraph_style_stack
= [self
.rststyle('textbody'), ]
829 self
.list_style_stack
= []
831 self
.column_count
= ord('A') - 1
832 self
.trace_level
= -1
833 self
.optiontablestyles_generated
= False
834 self
.field_name
= None
835 self
.field_element
= None
838 self
.image_style_count
= 0
840 self
.embedded_file_list
= []
841 self
.syntaxhighlighting
= 1
842 self
.syntaxhighlight_lexer
= 'python'
843 self
.header_content
= []
844 self
.footer_content
= []
845 self
.in_header
= False
846 self
.in_footer
= False
848 self
.in_table_of_contents
= False
849 self
.table_of_content_index_body
= None
851 self
.def_list_level
= 0
852 self
.footnote_ref_dict
= {}
853 self
.footnote_list
= []
854 self
.footnote_chars_idx
= 0
855 self
.footnote_level
= 0
856 self
.pending_ids
= [ ]
857 self
.in_paragraph
= False
858 self
.found_doc_title
= False
859 self
.bumped_list_level_stack
= []
861 self
.line_block_level
= 0
862 self
.line_indent_level
= 0
863 self
.citation_id
= None
864 self
.style_index
= 0 # use to form unique style names
865 self
.str_stylesheet
= ''
866 self
.str_stylesheetcontent
= ''
867 self
.dom_stylesheet
= None
868 self
.table_styles
= None
869 self
.in_citation
= False
872 def get_str_stylesheet(self
):
873 return self
.str_stylesheet
875 def retrieve_styles(self
, extension
):
876 """Retrieve the stylesheet from either a .xml file or from
877 a .odt (zip) file. Return the content as a string.
880 stylespath
= self
.settings
.stylesheet
881 ext
= os
.path
.splitext(stylespath
)[1]
883 stylesfile
= open(stylespath
, 'r')
884 s1
= stylesfile
.read()
886 elif ext
== extension
:
887 zfile
= zipfile
.ZipFile(stylespath
, 'r')
888 s1
= zfile
.read('styles.xml')
889 s2
= zfile
.read('content.xml')
892 raise RuntimeError, 'stylesheet path (%s) must be %s or .xml file' %(stylespath
, extension
)
893 self
.str_stylesheet
= s1
894 self
.str_stylesheetcontent
= s2
895 self
.dom_stylesheet
= etree
.fromstring(self
.str_stylesheet
)
896 self
.dom_stylesheetcontent
= etree
.fromstring(self
.str_stylesheetcontent
)
897 self
.table_styles
= self
.extract_table_styles(s2
)
899 def extract_table_styles(self
, styles_str
):
900 root
= etree
.fromstring(styles_str
)
902 auto_styles
= root
.find(
903 '{%s}automatic-styles' % (CNSD
['office'], ))
904 for stylenode
in auto_styles
:
905 name
= stylenode
.get('{%s}name' % (CNSD
['style'], ))
906 tablename
= name
.split('.')[0]
907 family
= stylenode
.get('{%s}family' % (CNSD
['style'], ))
908 if name
.startswith(TABLESTYLEPREFIX
):
909 tablestyle
= table_styles
.get(tablename
)
910 if tablestyle
is None:
911 tablestyle
= TableStyle()
912 table_styles
[tablename
] = tablestyle
913 if family
== 'table':
914 properties
= stylenode
.find(
915 '{%s}table-properties' % (CNSD
['style'], ))
916 property = properties
.get('{%s}%s' % (CNSD
['fo'],
917 'background-color', ))
918 if property is not None and property != 'none':
919 tablestyle
.backgroundcolor
= property
920 elif family
== 'table-cell':
921 properties
= stylenode
.find(
922 '{%s}table-cell-properties' % (CNSD
['style'], ))
923 if properties
is not None:
924 border
= self
.get_property(properties
)
925 if border
is not None:
926 tablestyle
.border
= border
929 def get_property(self
, stylenode
):
931 for propertyname
in TABLEPROPERTYNAMES
:
932 border
= stylenode
.get('{%s}%s' % (CNSD
['fo'], propertyname
, ))
933 if border
is not None and border
!= 'none':
937 def add_doc_title(self
):
938 text
= self
.settings
.title
941 if not self
.found_doc_title
:
942 el
= Element('text:p', attrib
= {
943 'text:style-name': self
.rststyle('title'),
946 self
.body_text_element
.insert(0, el
)
947 el
= self
.find_first_text_p(self
.body_text_element
)
949 self
.attach_page_style(el
)
951 def find_first_text_p(self
, el
):
952 """Search the generated doc and return the first <text:p> element.
955 el
.tag
== 'text:p' or
959 elif el
.getchildren():
960 for child
in el
.getchildren():
961 el1
= self
.find_first_text_p(child
)
968 def attach_page_style(self
, el
):
969 """Attach the default page style.
971 Create an automatic-style that refers to the current style
972 of this element and that refers to the default page style.
974 current_style
= el
.get('text:style-name')
977 self
.automatic_styles
, 'style:style', attrib
={
978 'style:name': style_name
,
979 'style:master-page-name': "rststyle-pagedefault",
980 'style:family': "paragraph",
983 el1
.set('style:parent-style-name', current_style
)
984 el
.set('text:style-name', style_name
)
987 def rststyle(self
, name
, parameters
=( )):
989 Returns the style name to use for the given style.
991 If `parameters` is given `name` must contain a matching number of ``%`` and
992 is used as a format expression with `parameters` as the value.
994 name1
= name
% parameters
995 stylename
= self
.format_map
.get(name1
, 'rststyle-%s' % name1
)
998 def generate_content_element(self
, root
):
999 return SubElement(root
, 'office:text')
1001 def setup_page(self
):
1002 self
.setup_paper(self
.dom_stylesheet
)
1003 if (len(self
.header_content
) > 0 or len(self
.footer_content
) > 0 or
1004 self
.settings
.custom_header
or self
.settings
.custom_footer
):
1005 self
.add_header_footer(self
.dom_stylesheet
)
1006 new_content
= etree
.tostring(self
.dom_stylesheet
)
1009 def setup_paper(self
, root_el
):
1011 fin
= os
.popen("paperconf -s 2> /dev/null")
1012 w
, h
= map(float, fin
.read().split())
1015 w
, h
= 612, 792 # default to Letter
1017 if el
.tag
== "{%s}page-layout-properties" % SNSD
["style"] and \
1018 not el
.attrib
.has_key("{%s}page-width" % SNSD
["fo"]):
1019 el
.attrib
["{%s}page-width" % SNSD
["fo"]] = "%.3fpt" % w
1020 el
.attrib
["{%s}page-height" % SNSD
["fo"]] = "%.3fpt" % h
1021 el
.attrib
["{%s}margin-left" % SNSD
["fo"]] = \
1022 el
.attrib
["{%s}margin-right" % SNSD
["fo"]] = \
1024 el
.attrib
["{%s}margin-top" % SNSD
["fo"]] = \
1025 el
.attrib
["{%s}margin-bottom" % SNSD
["fo"]] = \
1028 for subel
in el
.getchildren(): walk(subel
)
1031 def add_header_footer(self
, root_el
):
1032 automatic_styles
= root_el
.find(
1033 '{%s}automatic-styles' % SNSD
['office'])
1034 path
= '{%s}master-styles' % (NAME_SPACE_1
, )
1035 master_el
= root_el
.find(path
)
1036 if master_el
is None:
1038 path
= '{%s}master-page' % (SNSD
['style'], )
1039 master_el_container
= master_el
.findall(path
)
1041 target_attrib
= '{%s}name' % (SNSD
['style'], )
1042 target_name
= self
.rststyle('pagedefault')
1043 for el
in master_el_container
:
1044 if el
.get(target_attrib
) == target_name
:
1047 if master_el
is None:
1050 if self
.header_content
or self
.settings
.custom_header
:
1051 if WhichElementTree
== 'lxml':
1052 el2
= SubElement(el1
, 'style:header', nsdict
=SNSD
)
1054 el2
= SubElement(el1
, 'style:header',
1055 attrib
=STYLES_NAMESPACE_ATTRIB
,
1056 nsdict
=STYLES_NAMESPACE_DICT
,
1058 for el
in self
.header_content
:
1059 attrkey
= add_ns('text:style-name', nsdict
=SNSD
)
1060 el
.attrib
[attrkey
] = self
.rststyle('header')
1062 if self
.settings
.custom_header
:
1063 elcustom
= self
.create_custom_headfoot(el2
,
1064 self
.settings
.custom_header
, 'header', automatic_styles
)
1065 if self
.footer_content
or self
.settings
.custom_footer
:
1066 if WhichElementTree
== 'lxml':
1067 el2
= SubElement(el1
, 'style:footer', nsdict
=SNSD
)
1069 el2
= SubElement(el1
, 'style:footer',
1070 attrib
=STYLES_NAMESPACE_ATTRIB
,
1071 nsdict
=STYLES_NAMESPACE_DICT
,
1073 for el
in self
.footer_content
:
1074 attrkey
= add_ns('text:style-name', nsdict
=SNSD
)
1075 el
.attrib
[attrkey
] = self
.rststyle('footer')
1077 if self
.settings
.custom_footer
:
1078 elcustom
= self
.create_custom_headfoot(el2
,
1079 self
.settings
.custom_footer
, 'footer', automatic_styles
)
1081 code_none
, code_field
, code_text
= range(3)
1082 field_pat
= re
.compile(r
'%(..?)%')
1084 def create_custom_headfoot(self
, parent
, text
, style_name
, automatic_styles
):
1085 parent
= SubElement(parent
, 'text:p', attrib
={
1086 'text:style-name': self
.rststyle(style_name
),
1088 current_element
= None
1089 field_iter
= self
.split_field_specifiers_iter(text
)
1090 for item
in field_iter
:
1091 if item
[0] == ODFTranslator
.code_field
:
1092 if item
[1] not in ('p', 'P',
1093 't1', 't2', 't3', 't4',
1094 'd1', 'd2', 'd3', 'd4', 'd5',
1096 msg
= 'bad field spec: %%%s%%' % (item
[1], )
1097 raise RuntimeError, msg
1098 el1
= self
.make_field_element(parent
,
1099 item
[1], style_name
, automatic_styles
)
1101 msg
= 'bad field spec: %%%s%%' % (item
[1], )
1102 raise RuntimeError, msg
1104 current_element
= el1
1106 if current_element
is None:
1107 parent
.text
= item
[1]
1109 current_element
.tail
= item
[1]
1111 def make_field_element(self
, parent
, text
, style_name
, automatic_styles
):
1113 el1
= SubElement(parent
, 'text:page-number', attrib
={
1114 #'text:style-name': self.rststyle(style_name),
1115 'text:select-page': 'current',
1118 el1
= SubElement(parent
, 'text:page-count', attrib
={
1119 #'text:style-name': self.rststyle(style_name),
1122 self
.style_index
+= 1
1123 el1
= SubElement(parent
, 'text:time', attrib
={
1124 'text:style-name': self
.rststyle(style_name
),
1125 'text:fixed': 'true',
1126 'style:data-style-name': 'rst-time-style-%d' % self
.style_index
,
1128 el2
= SubElement(automatic_styles
, 'number:time-style', attrib
={
1129 'style:name': 'rst-time-style-%d' % self
.style_index
,
1130 'xmlns:number': SNSD
['number'],
1131 'xmlns:style': SNSD
['style'],
1133 el3
= SubElement(el2
, 'number:hours', attrib
={
1134 'number:style': 'long',
1136 el3
= SubElement(el2
, 'number:text')
1138 el3
= SubElement(el2
, 'number:minutes', attrib
={
1139 'number:style': 'long',
1142 self
.style_index
+= 1
1143 el1
= SubElement(parent
, 'text:time', attrib
={
1144 'text:style-name': self
.rststyle(style_name
),
1145 'text:fixed': 'true',
1146 'style:data-style-name': 'rst-time-style-%d' % self
.style_index
,
1148 el2
= SubElement(automatic_styles
, 'number:time-style', attrib
={
1149 'style:name': 'rst-time-style-%d' % self
.style_index
,
1150 'xmlns:number': SNSD
['number'],
1151 'xmlns:style': SNSD
['style'],
1153 el3
= SubElement(el2
, 'number:hours', attrib
={
1154 'number:style': 'long',
1156 el3
= SubElement(el2
, 'number:text')
1158 el3
= SubElement(el2
, 'number:minutes', attrib
={
1159 'number:style': 'long',
1161 el3
= SubElement(el2
, 'number:text')
1163 el3
= SubElement(el2
, 'number:seconds', attrib
={
1164 'number:style': 'long',
1167 self
.style_index
+= 1
1168 el1
= SubElement(parent
, 'text:time', attrib
={
1169 'text:style-name': self
.rststyle(style_name
),
1170 'text:fixed': 'true',
1171 'style:data-style-name': 'rst-time-style-%d' % self
.style_index
,
1173 el2
= SubElement(automatic_styles
, 'number:time-style', attrib
={
1174 'style:name': 'rst-time-style-%d' % self
.style_index
,
1175 'xmlns:number': SNSD
['number'],
1176 'xmlns:style': SNSD
['style'],
1178 el3
= SubElement(el2
, 'number:hours', attrib
={
1179 'number:style': 'long',
1181 el3
= SubElement(el2
, 'number:text')
1183 el3
= SubElement(el2
, 'number:minutes', attrib
={
1184 'number:style': 'long',
1186 el3
= SubElement(el2
, 'number:text')
1188 el3
= SubElement(el2
, 'number:am-pm')
1190 self
.style_index
+= 1
1191 el1
= SubElement(parent
, 'text:time', attrib
={
1192 'text:style-name': self
.rststyle(style_name
),
1193 'text:fixed': 'true',
1194 'style:data-style-name': 'rst-time-style-%d' % self
.style_index
,
1196 el2
= SubElement(automatic_styles
, 'number:time-style', attrib
={
1197 'style:name': 'rst-time-style-%d' % self
.style_index
,
1198 'xmlns:number': SNSD
['number'],
1199 'xmlns:style': SNSD
['style'],
1201 el3
= SubElement(el2
, 'number:hours', attrib
={
1202 'number:style': 'long',
1204 el3
= SubElement(el2
, 'number:text')
1206 el3
= SubElement(el2
, 'number:minutes', attrib
={
1207 'number:style': 'long',
1209 el3
= SubElement(el2
, 'number:text')
1211 el3
= SubElement(el2
, 'number:seconds', attrib
={
1212 'number:style': 'long',
1214 el3
= SubElement(el2
, 'number:text')
1216 el3
= SubElement(el2
, 'number:am-pm')
1218 self
.style_index
+= 1
1219 el1
= SubElement(parent
, 'text:date', attrib
={
1220 'text:style-name': self
.rststyle(style_name
),
1221 'style:data-style-name': 'rst-date-style-%d' % self
.style_index
,
1223 el2
= SubElement(automatic_styles
, 'number:date-style', attrib
={
1224 'style:name': 'rst-date-style-%d' % self
.style_index
,
1225 'number:automatic-order': 'true',
1226 'xmlns:number': SNSD
['number'],
1227 'xmlns:style': SNSD
['style'],
1229 el3
= SubElement(el2
, 'number:month', attrib
={
1230 'number:style': 'long',
1232 el3
= SubElement(el2
, 'number:text')
1234 el3
= SubElement(el2
, 'number:day', attrib
={
1235 'number:style': 'long',
1237 el3
= SubElement(el2
, 'number:text')
1239 el3
= SubElement(el2
, 'number:year')
1241 self
.style_index
+= 1
1242 el1
= SubElement(parent
, 'text:date', attrib
={
1243 'text:style-name': self
.rststyle(style_name
),
1244 'style:data-style-name': 'rst-date-style-%d' % self
.style_index
,
1246 el2
= SubElement(automatic_styles
, 'number:date-style', attrib
={
1247 'style:name': 'rst-date-style-%d' % self
.style_index
,
1248 'number:automatic-order': 'true',
1249 'xmlns:number': SNSD
['number'],
1250 'xmlns:style': SNSD
['style'],
1252 el3
= SubElement(el2
, 'number:month', attrib
={
1253 'number:style': 'long',
1255 el3
= SubElement(el2
, 'number:text')
1257 el3
= SubElement(el2
, 'number:day', attrib
={
1258 'number:style': 'long',
1260 el3
= SubElement(el2
, 'number:text')
1262 el3
= SubElement(el2
, 'number:year', attrib
={
1263 'number:style': 'long',
1266 self
.style_index
+= 1
1267 el1
= SubElement(parent
, 'text:date', attrib
={
1268 'text:style-name': self
.rststyle(style_name
),
1269 'style:data-style-name': 'rst-date-style-%d' % self
.style_index
,
1271 el2
= SubElement(automatic_styles
, 'number:date-style', attrib
={
1272 'style:name': 'rst-date-style-%d' % self
.style_index
,
1273 'number:automatic-order': 'true',
1274 'xmlns:number': SNSD
['number'],
1275 'xmlns:style': SNSD
['style'],
1277 el3
= SubElement(el2
, 'number:month', attrib
={
1278 'number:textual': 'true',
1280 el3
= SubElement(el2
, 'number:text')
1282 el3
= SubElement(el2
, 'number:day', attrib
={
1284 el3
= SubElement(el2
, 'number:text')
1286 el3
= SubElement(el2
, 'number:year', attrib
={
1287 'number:style': 'long',
1290 self
.style_index
+= 1
1291 el1
= SubElement(parent
, 'text:date', attrib
={
1292 'text:style-name': self
.rststyle(style_name
),
1293 'style:data-style-name': 'rst-date-style-%d' % self
.style_index
,
1295 el2
= SubElement(automatic_styles
, 'number:date-style', attrib
={
1296 'style:name': 'rst-date-style-%d' % self
.style_index
,
1297 'number:automatic-order': 'true',
1298 'xmlns:number': SNSD
['number'],
1299 'xmlns:style': SNSD
['style'],
1301 el3
= SubElement(el2
, 'number:month', attrib
={
1302 'number:textual': 'true',
1303 'number:style': 'long',
1305 el3
= SubElement(el2
, 'number:text')
1307 el3
= SubElement(el2
, 'number:day', attrib
={
1309 el3
= SubElement(el2
, 'number:text')
1311 el3
= SubElement(el2
, 'number:year', attrib
={
1312 'number:style': 'long',
1315 self
.style_index
+= 1
1316 el1
= SubElement(parent
, 'text:date', attrib
={
1317 'text:style-name': self
.rststyle(style_name
),
1318 'style:data-style-name': 'rst-date-style-%d' % self
.style_index
,
1320 el2
= SubElement(automatic_styles
, 'number:date-style', attrib
={
1321 'style:name': 'rst-date-style-%d' % self
.style_index
,
1322 'xmlns:number': SNSD
['number'],
1323 'xmlns:style': SNSD
['style'],
1325 el3
= SubElement(el2
, 'number:year', attrib
={
1326 'number:style': 'long',
1328 el3
= SubElement(el2
, 'number:text')
1330 el3
= SubElement(el2
, 'number:month', attrib
={
1331 'number:style': 'long',
1333 el3
= SubElement(el2
, 'number:text')
1335 el3
= SubElement(el2
, 'number:day', attrib
={
1336 'number:style': 'long',
1339 el1
= SubElement(parent
, 'text:subject', attrib
={
1340 'text:style-name': self
.rststyle(style_name
),
1343 el1
= SubElement(parent
, 'text:title', attrib
={
1344 'text:style-name': self
.rststyle(style_name
),
1347 el1
= SubElement(parent
, 'text:author-name', attrib
={
1348 'text:fixed': 'false',
1354 def split_field_specifiers_iter(self
, text
):
1358 mo
= ODFTranslator
.field_pat
.search(text
, pos1
)
1362 yield (ODFTranslator
.code_text
, text
[pos1
:pos2
])
1363 yield (ODFTranslator
.code_field
, mo
.group(1))
1367 trailing
= text
[pos1
:]
1369 yield (ODFTranslator
.code_text
, trailing
)
1373 root
= self
.content_tree
.getroot()
1374 et
= etree
.ElementTree(root
)
1378 def content_astext(self
):
1379 return self
.astext()
1381 def set_title(self
, title
): self
.title
= title
1382 def get_title(self
): return self
.title
1383 def set_embedded_file_list(self
, embedded_file_list
):
1384 self
.embedded_file_list
= embedded_file_list
1385 def get_embedded_file_list(self
): return self
.embedded_file_list
1386 def get_meta_dict(self
): return self
.meta_dict
1388 def process_footnotes(self
):
1389 for node
, el1
in self
.footnote_list
:
1390 backrefs
= node
.attributes
.get('backrefs', [])
1392 for ref
in backrefs
:
1393 el2
= self
.footnote_ref_dict
.get(ref
)
1397 el3
= copy
.deepcopy(el1
)
1400 children
= el2
.getchildren()
1401 if len(children
) > 0: # and 'id' in el2.attrib:
1404 attribkey
= add_ns('text:id', nsdict
=SNSD
)
1405 id1
= el2
.get(attribkey
, 'footnote-error')
1408 tag
= add_ns('text:note-ref', nsdict
=SNSD
)
1410 if self
.settings
.endnotes_end_doc
:
1411 note_class
= 'endnote'
1413 note_class
= 'footnote'
1415 attribkey
= add_ns('text:note-class', nsdict
=SNSD
)
1416 el2
.attrib
[attribkey
] = note_class
1417 attribkey
= add_ns('text:ref-name', nsdict
=SNSD
)
1418 el2
.attrib
[attribkey
] = id1
1419 attribkey
= add_ns('text:reference-format', nsdict
=SNSD
)
1420 el2
.attrib
[attribkey
] = 'page'
1426 def append_child(self
, tag
, attrib
=None, parent
=None):
1428 parent
= self
.current_element
1430 el
= SubElement(parent
, tag
)
1432 el
= SubElement(parent
, tag
, attrib
)
1435 def append_p(self
, style
, text
=None):
1436 result
= self
.append_child('text:p', attrib
={
1437 'text:style-name': self
.rststyle(style
)})
1438 self
.append_pending_ids(result
)
1439 if text
is not None:
1443 def append_pending_ids(self
, el
):
1444 if self
.settings
.create_links
:
1445 for id in self
.pending_ids
:
1446 SubElement(el
, 'text:reference-mark', attrib
={
1448 self
.pending_ids
= [ ]
1450 def set_current_element(self
, el
):
1451 self
.current_element
= el
1453 def set_to_parent(self
):
1454 self
.current_element
= self
.current_element
.getparent()
1456 def generate_labeled_block(self
, node
, label
):
1457 label
= '%s:' % (self
.language
.labels
[label
], )
1458 el
= self
.append_p('textbody')
1459 el1
= SubElement(el
, 'text:span',
1460 attrib
={'text:style-name': self
.rststyle('strong')})
1462 el
= self
.append_p('blockindent')
1465 def generate_labeled_line(self
, node
, label
):
1466 label
= '%s:' % (self
.language
.labels
[label
], )
1467 el
= self
.append_p('textbody')
1468 el1
= SubElement(el
, 'text:span',
1469 attrib
={'text:style-name': self
.rststyle('strong')})
1471 el1
.tail
= node
.astext()
1474 def encode(self
, text
):
1475 text
= text
.replace(u
'\u00a0', " ")
1481 # In alphabetic order, more or less.
1482 # See docutils.docutils.nodes.node_class_names.
1485 def dispatch_visit(self
, node
):
1486 """Override to catch basic attributes which many nodes have."""
1487 self
.handle_basic_atts(node
)
1488 nodes
.GenericNodeVisitor
.dispatch_visit(self
, node
)
1490 def handle_basic_atts(self
, node
):
1491 if isinstance(node
, nodes
.Element
) and node
['ids']:
1492 self
.pending_ids
+= node
['ids']
1494 def default_visit(self
, node
):
1495 self
.document
.reporter
.warning('missing visit_%s' % (node
.tagname
, ))
1497 def default_departure(self
, node
):
1498 self
.document
.reporter
.warning('missing depart_%s' % (node
.tagname
, ))
1500 def visit_Text(self
, node
):
1501 # Skip nodes whose text has been processed in parent nodes.
1502 if isinstance(node
.parent
, docutils
.nodes
.literal_block
):
1504 text
= node
.astext()
1505 # Are we in mixed content? If so, add the text to the
1506 # etree tail of the previous sibling element.
1507 if len(self
.current_element
.getchildren()) > 0:
1508 if self
.current_element
.getchildren()[-1].tail
:
1509 self
.current_element
.getchildren()[-1].tail
+= text
1511 self
.current_element
.getchildren()[-1].tail
= text
1513 if self
.current_element
.text
:
1514 self
.current_element
.text
+= text
1516 self
.current_element
.text
= text
1518 def depart_Text(self
, node
):
1522 # Pre-defined fields
1525 def visit_address(self
, node
):
1526 el
= self
.generate_labeled_block(node
, 'address')
1527 self
.set_current_element(el
)
1529 def depart_address(self
, node
):
1530 self
.set_to_parent()
1532 def visit_author(self
, node
):
1533 if isinstance(node
.parent
, nodes
.authors
):
1534 el
= self
.append_p('blockindent')
1536 el
= self
.generate_labeled_block(node
, 'author')
1537 self
.set_current_element(el
)
1539 def depart_author(self
, node
):
1540 self
.set_to_parent()
1542 def visit_authors(self
, node
):
1543 label
= '%s:' % (self
.language
.labels
['authors'], )
1544 el
= self
.append_p('textbody')
1545 el1
= SubElement(el
, 'text:span',
1546 attrib
={'text:style-name': self
.rststyle('strong')})
1549 def depart_authors(self
, node
):
1552 def visit_contact(self
, node
):
1553 el
= self
.generate_labeled_block(node
, 'contact')
1554 self
.set_current_element(el
)
1556 def depart_contact(self
, node
):
1557 self
.set_to_parent()
1559 def visit_copyright(self
, node
):
1560 el
= self
.generate_labeled_block(node
, 'copyright')
1561 self
.set_current_element(el
)
1563 def depart_copyright(self
, node
):
1564 self
.set_to_parent()
1566 def visit_date(self
, node
):
1567 self
.generate_labeled_line(node
, 'date')
1569 def depart_date(self
, node
):
1572 def visit_organization(self
, node
):
1573 el
= self
.generate_labeled_block(node
, 'organization')
1574 self
.set_current_element(el
)
1576 def depart_organization(self
, node
):
1577 self
.set_to_parent()
1579 def visit_status(self
, node
):
1580 el
= self
.generate_labeled_block(node
, 'status')
1581 self
.set_current_element(el
)
1583 def depart_status(self
, node
):
1584 self
.set_to_parent()
1586 def visit_revision(self
, node
):
1587 el
= self
.generate_labeled_line(node
, 'revision')
1589 def depart_revision(self
, node
):
1592 def visit_version(self
, node
):
1593 el
= self
.generate_labeled_line(node
, 'version')
1594 #self.set_current_element(el)
1596 def depart_version(self
, node
):
1597 #self.set_to_parent()
1600 def visit_attribution(self
, node
):
1601 el
= self
.append_p('attribution', node
.astext())
1603 def depart_attribution(self
, node
):
1606 def visit_block_quote(self
, node
):
1607 if 'epigraph' in node
.attributes
['classes']:
1608 self
.paragraph_style_stack
.append(self
.rststyle('epigraph'))
1609 self
.blockstyle
= self
.rststyle('epigraph')
1610 elif 'highlights' in node
.attributes
['classes']:
1611 self
.paragraph_style_stack
.append(self
.rststyle('highlights'))
1612 self
.blockstyle
= self
.rststyle('highlights')
1614 self
.paragraph_style_stack
.append(self
.rststyle('blockquote'))
1615 self
.blockstyle
= self
.rststyle('blockquote')
1616 self
.line_indent_level
+= 1
1618 def depart_block_quote(self
, node
):
1619 self
.paragraph_style_stack
.pop()
1620 self
.blockstyle
= ''
1621 self
.line_indent_level
-= 1
1623 def visit_bullet_list(self
, node
):
1625 if self
.in_table_of_contents
:
1626 if self
.settings
.generate_oowriter_toc
:
1629 if node
.has_key('classes') and \
1630 'auto-toc' in node
.attributes
['classes']:
1631 el
= SubElement(self
.current_element
, 'text:list', attrib
={
1632 'text:style-name': self
.rststyle('tocenumlist'),
1634 self
.list_style_stack
.append(self
.rststyle('enumitem'))
1636 el
= SubElement(self
.current_element
, 'text:list', attrib
={
1637 'text:style-name': self
.rststyle('tocbulletlist'),
1639 self
.list_style_stack
.append(self
.rststyle('bulletitem'))
1640 self
.set_current_element(el
)
1642 if self
.blockstyle
== self
.rststyle('blockquote'):
1643 el
= SubElement(self
.current_element
, 'text:list', attrib
={
1644 'text:style-name': self
.rststyle('blockquote-bulletlist'),
1646 self
.list_style_stack
.append(
1647 self
.rststyle('blockquote-bulletitem'))
1648 elif self
.blockstyle
== self
.rststyle('highlights'):
1649 el
= SubElement(self
.current_element
, 'text:list', attrib
={
1650 'text:style-name': self
.rststyle('highlights-bulletlist'),
1652 self
.list_style_stack
.append(
1653 self
.rststyle('highlights-bulletitem'))
1654 elif self
.blockstyle
== self
.rststyle('epigraph'):
1655 el
= SubElement(self
.current_element
, 'text:list', attrib
={
1656 'text:style-name': self
.rststyle('epigraph-bulletlist'),
1658 self
.list_style_stack
.append(
1659 self
.rststyle('epigraph-bulletitem'))
1661 el
= SubElement(self
.current_element
, 'text:list', attrib
={
1662 'text:style-name': self
.rststyle('bulletlist'),
1664 self
.list_style_stack
.append(self
.rststyle('bulletitem'))
1665 self
.set_current_element(el
)
1667 def depart_bullet_list(self
, node
):
1668 if self
.in_table_of_contents
:
1669 if self
.settings
.generate_oowriter_toc
:
1672 self
.set_to_parent()
1673 self
.list_style_stack
.pop()
1675 self
.set_to_parent()
1676 self
.list_style_stack
.pop()
1679 def visit_caption(self
, node
):
1680 raise nodes
.SkipChildren()
1683 def depart_caption(self
, node
):
1686 def visit_comment(self
, node
):
1687 el
= self
.append_p('textbody')
1688 el1
= SubElement(el
, 'office:annotation', attrib
={})
1689 el2
= SubElement(el1
, 'dc:creator', attrib
={})
1690 s1
= os
.environ
.get('USER', '')
1692 el2
= SubElement(el1
, 'text:p', attrib
={})
1693 el2
.text
= node
.astext()
1695 def depart_comment(self
, node
):
1698 def visit_compound(self
, node
):
1699 # The compound directive currently receives no special treatment.
1702 def depart_compound(self
, node
):
1705 def visit_container(self
, node
):
1706 styles
= node
.attributes
.get('classes', ())
1708 self
.paragraph_style_stack
.append(self
.rststyle(styles
[0]))
1710 def depart_container(self
, node
):
1711 styles
= node
.attributes
.get('classes', ())
1713 self
.paragraph_style_stack
.pop()
1715 def visit_decoration(self
, node
):
1718 def depart_decoration(self
, node
):
1721 def visit_definition_list(self
, node
):
1722 self
.def_list_level
+=1
1723 if self
.list_level
> 5:
1725 'max definition list nesting level exceeded')
1727 def depart_definition_list(self
, node
):
1728 self
.def_list_level
-=1
1730 def visit_definition_list_item(self
, node
):
1733 def depart_definition_list_item(self
, node
):
1736 def visit_term(self
, node
):
1737 el
= self
.append_p('deflist-term-%d' % self
.def_list_level
)
1738 el
.text
= node
.astext()
1739 self
.set_current_element(el
)
1740 raise nodes
.SkipChildren()
1742 def depart_term(self
, node
):
1743 self
.set_to_parent()
1745 def visit_definition(self
, node
):
1746 self
.paragraph_style_stack
.append(
1747 self
.rststyle('deflist-def-%d' % self
.def_list_level
))
1748 self
.bumped_list_level_stack
.append(ListLevel(1))
1750 def depart_definition(self
, node
):
1751 self
.paragraph_style_stack
.pop()
1752 self
.bumped_list_level_stack
.pop()
1754 def visit_classifier(self
, node
):
1755 els
= self
.current_element
.getchildren()
1758 el1
= SubElement(el
, 'text:span',
1759 attrib
={'text:style-name': self
.rststyle('emphasis')
1761 el1
.text
= ' (%s)' % (node
.astext(), )
1763 def depart_classifier(self
, node
):
1766 def visit_document(self
, node
):
1769 def depart_document(self
, node
):
1770 self
.process_footnotes()
1772 def visit_docinfo(self
, node
):
1773 self
.section_level
+= 1
1774 self
.section_count
+= 1
1775 if self
.settings
.create_sections
:
1776 el
= self
.append_child('text:section', attrib
={
1777 'text:name': 'Section%d' % self
.section_count
,
1778 'text:style-name': 'Sect%d' % self
.section_level
,
1780 self
.set_current_element(el
)
1782 def depart_docinfo(self
, node
):
1783 self
.section_level
-= 1
1784 if self
.settings
.create_sections
:
1785 self
.set_to_parent()
1787 def visit_emphasis(self
, node
):
1788 el
= SubElement(self
.current_element
, 'text:span',
1789 attrib
={'text:style-name': self
.rststyle('emphasis')})
1790 self
.set_current_element(el
)
1792 def depart_emphasis(self
, node
):
1793 self
.set_to_parent()
1795 def visit_enumerated_list(self
, node
):
1796 el1
= self
.current_element
1797 if self
.blockstyle
== self
.rststyle('blockquote'):
1798 el2
= SubElement(el1
, 'text:list', attrib
={
1799 'text:style-name': self
.rststyle('blockquote-enumlist'),
1801 self
.list_style_stack
.append(self
.rststyle('blockquote-enumitem'))
1802 elif self
.blockstyle
== self
.rststyle('highlights'):
1803 el2
= SubElement(el1
, 'text:list', attrib
={
1804 'text:style-name': self
.rststyle('highlights-enumlist'),
1806 self
.list_style_stack
.append(self
.rststyle('highlights-enumitem'))
1807 elif self
.blockstyle
== self
.rststyle('epigraph'):
1808 el2
= SubElement(el1
, 'text:list', attrib
={
1809 'text:style-name': self
.rststyle('epigraph-enumlist'),
1811 self
.list_style_stack
.append(self
.rststyle('epigraph-enumitem'))
1813 liststylename
= 'enumlist-%s' % (node
.get('enumtype', 'arabic'), )
1814 el2
= SubElement(el1
, 'text:list', attrib
={
1815 'text:style-name': self
.rststyle(liststylename
),
1817 self
.list_style_stack
.append(self
.rststyle('enumitem'))
1818 self
.set_current_element(el2
)
1820 def depart_enumerated_list(self
, node
):
1821 self
.set_to_parent()
1822 self
.list_style_stack
.pop()
1824 def visit_list_item(self
, node
):
1825 # If we are in a "bumped" list level, then wrap this
1826 # list in an outer lists in order to increase the
1827 # indentation level.
1828 if self
.in_table_of_contents
:
1829 if self
.settings
.generate_oowriter_toc
:
1830 self
.paragraph_style_stack
.append(
1831 self
.rststyle('contents-%d' % (self
.list_level
, )))
1833 el1
= self
.append_child('text:list-item')
1834 self
.set_current_element(el1
)
1836 el1
= self
.append_child('text:list-item')
1838 if len(self
.bumped_list_level_stack
) > 0:
1839 level_obj
= self
.bumped_list_level_stack
[-1]
1840 if level_obj
.get_sibling():
1841 level_obj
.set_nested(False)
1842 for level_obj1
in self
.bumped_list_level_stack
:
1843 for idx
in range(level_obj1
.get_level()):
1844 el2
= self
.append_child('text:list', parent
=el3
)
1845 el3
= self
.append_child(
1846 'text:list-item', parent
=el2
)
1847 self
.paragraph_style_stack
.append(self
.list_style_stack
[-1])
1848 self
.set_current_element(el3
)
1850 def depart_list_item(self
, node
):
1851 if self
.in_table_of_contents
:
1852 if self
.settings
.generate_oowriter_toc
:
1853 self
.paragraph_style_stack
.pop()
1855 self
.set_to_parent()
1857 if len(self
.bumped_list_level_stack
) > 0:
1858 level_obj
= self
.bumped_list_level_stack
[-1]
1859 if level_obj
.get_sibling():
1860 level_obj
.set_nested(True)
1861 for level_obj1
in self
.bumped_list_level_stack
:
1862 for idx
in range(level_obj1
.get_level()):
1863 self
.set_to_parent()
1864 self
.set_to_parent()
1865 self
.paragraph_style_stack
.pop()
1866 self
.set_to_parent()
1868 def visit_header(self
, node
):
1869 self
.in_header
= True
1871 def depart_header(self
, node
):
1872 self
.in_header
= False
1874 def visit_footer(self
, node
):
1875 self
.in_footer
= True
1877 def depart_footer(self
, node
):
1878 self
.in_footer
= False
1880 def visit_field(self
, node
):
1883 def depart_field(self
, node
):
1886 def visit_field_list(self
, node
):
1889 def depart_field_list(self
, node
):
1892 def visit_field_name(self
, node
):
1893 el
= self
.append_p('textbody')
1894 el1
= SubElement(el
, 'text:span',
1895 attrib
={'text:style-name': self
.rststyle('strong')})
1896 el1
.text
= node
.astext()
1898 def depart_field_name(self
, node
):
1901 def visit_field_body(self
, node
):
1902 self
.paragraph_style_stack
.append(self
.rststyle('blockindent'))
1904 def depart_field_body(self
, node
):
1905 self
.paragraph_style_stack
.pop()
1907 def visit_figure(self
, node
):
1910 def depart_figure(self
, node
):
1913 def visit_footnote(self
, node
):
1914 self
.footnote_level
+= 1
1915 self
.save_footnote_current
= self
.current_element
1916 el1
= Element('text:note-body')
1917 self
.current_element
= el1
1918 self
.footnote_list
.append((node
, el1
))
1919 if isinstance(node
, docutils
.nodes
.citation
):
1920 self
.paragraph_style_stack
.append(self
.rststyle('citation'))
1922 self
.paragraph_style_stack
.append(self
.rststyle('footnote'))
1924 def depart_footnote(self
, node
):
1925 self
.paragraph_style_stack
.pop()
1926 self
.current_element
= self
.save_footnote_current
1927 self
.footnote_level
-= 1
1936 def visit_footnote_reference(self
, node
):
1937 if self
.footnote_level
<= 0:
1938 id = node
.attributes
['ids'][0]
1939 refid
= node
.attributes
.get('refid')
1942 if self
.settings
.endnotes_end_doc
:
1943 note_class
= 'endnote'
1945 note_class
= 'footnote'
1946 el1
= self
.append_child('text:note', attrib
={
1947 'text:id': '%s' % (refid
, ),
1948 'text:note-class': note_class
,
1950 note_auto
= str(node
.attributes
.get('auto', 1))
1951 if isinstance(node
, docutils
.nodes
.citation_reference
):
1952 citation
= '[%s]' % node
.astext()
1953 el2
= SubElement(el1
, 'text:note-citation', attrib
={
1954 'text:label': citation
,
1957 elif note_auto
== '1':
1958 el2
= SubElement(el1
, 'text:note-citation', attrib
={
1959 'text:label': node
.astext(),
1961 el2
.text
= node
.astext()
1962 elif note_auto
== '*':
1963 if self
.footnote_chars_idx
>= len(
1964 ODFTranslator
.footnote_chars
):
1965 self
.footnote_chars_idx
= 0
1966 footnote_char
= ODFTranslator
.footnote_chars
[
1967 self
.footnote_chars_idx
]
1968 self
.footnote_chars_idx
+= 1
1969 el2
= SubElement(el1
, 'text:note-citation', attrib
={
1970 'text:label': footnote_char
,
1972 el2
.text
= footnote_char
1973 self
.footnote_ref_dict
[id] = el1
1974 raise nodes
.SkipChildren()
1976 def depart_footnote_reference(self
, node
):
1979 def visit_citation(self
, node
):
1980 self
.in_citation
= True
1981 for id in node
.attributes
['ids']:
1982 self
.citation_id
= id
1984 self
.paragraph_style_stack
.append(self
.rststyle('blockindent'))
1985 self
.bumped_list_level_stack
.append(ListLevel(1))
1987 def depart_citation(self
, node
):
1988 self
.citation_id
= None
1989 self
.paragraph_style_stack
.pop()
1990 self
.bumped_list_level_stack
.pop()
1991 self
.in_citation
= False
1993 def visit_citation_reference(self
, node
):
1994 if self
.settings
.create_links
:
1995 id = node
.attributes
['refid']
1996 el
= self
.append_child('text:reference-ref', attrib
={
1997 'text:ref-name': '%s' % (id, ),
1998 'text:reference-format': 'text',
2001 self
.set_current_element(el
)
2002 elif self
.current_element
.text
is None:
2003 self
.current_element
.text
= '['
2005 self
.current_element
.text
+= '['
2007 def depart_citation_reference(self
, node
):
2008 self
.current_element
.text
+= ']'
2009 if self
.settings
.create_links
:
2010 self
.set_to_parent()
2012 def visit_label(self
, node
):
2013 if isinstance(node
.parent
, docutils
.nodes
.footnote
):
2014 raise nodes
.SkipChildren()
2015 elif self
.citation_id
is not None:
2016 el
= self
.append_p('textbody')
2017 self
.set_current_element(el
)
2019 if self
.settings
.create_links
:
2020 el1
= self
.append_child('text:reference-mark-start', attrib
={
2021 'text:name': '%s' % (self
.citation_id
, ),
2024 def depart_label(self
, node
):
2025 if isinstance(node
.parent
, docutils
.nodes
.footnote
):
2027 elif self
.citation_id
is not None:
2028 self
.current_element
.text
+= ']'
2029 if self
.settings
.create_links
:
2030 el
= self
.append_child('text:reference-mark-end', attrib
={
2031 'text:name': '%s' % (self
.citation_id
, ),
2033 self
.set_to_parent()
2035 def visit_generated(self
, node
):
2038 def depart_generated(self
, node
):
2041 def check_file_exists(self
, path
):
2042 if os
.path
.exists(path
):
2047 def visit_image(self
, node
):
2048 # Capture the image file.
2049 if 'uri' in node
.attributes
:
2050 source
= node
.attributes
['uri']
2051 if not source
.startswith('http:'):
2052 if not source
.startswith(os
.sep
):
2053 docsource
, line
= utils
.get_source_line(node
)
2055 dirname
= os
.path
.dirname(docsource
)
2057 source
= '%s%s%s' % (dirname
, os
.sep
, source
, )
2058 if not self
.check_file_exists(source
):
2059 self
.document
.reporter
.warning(
2060 'Cannot find image file %s.' % (source
, ))
2064 if source
in self
.image_dict
:
2065 filename
, destination
= self
.image_dict
[source
]
2067 self
.image_count
+= 1
2068 filename
= os
.path
.split(source
)[1]
2069 destination
= 'Pictures/1%08x%s' % (self
.image_count
, filename
, )
2070 if source
.startswith('http:'):
2072 imgfile
= urllib2
.urlopen(source
)
2073 content
= imgfile
.read()
2075 imgfile2
= tempfile
.NamedTemporaryFile('wb', delete
=False)
2076 imgfile2
.write(content
)
2078 imgfilename
= imgfile2
.name
2079 source
= imgfilename
2080 except urllib2
.HTTPError
, e
:
2081 self
.document
.reporter
.warning(
2082 "Can't open image url %s." % (source
, ))
2083 spec
= (source
, destination
,)
2085 spec
= (os
.path
.abspath(source
), destination
,)
2086 self
.embedded_file_list
.append(spec
)
2087 self
.image_dict
[source
] = (source
, destination
,)
2088 # Is this a figure (containing an image) or just a plain image?
2089 if self
.in_paragraph
:
2090 el1
= self
.current_element
2092 el1
= SubElement(self
.current_element
, 'text:p',
2093 attrib
={'text:style-name': self
.rststyle('textbody')})
2095 if isinstance(node
.parent
, docutils
.nodes
.figure
):
2096 el3
, el4
, el5
, caption
= self
.generate_figure(node
, source
,
2099 el6
, width
= self
.generate_image(node
, source
, destination
,
2101 if caption
is not None:
2103 else: #if isinstance(node.parent, docutils.nodes.image):
2104 el3
= self
.generate_image(node
, source
, destination
, el2
)
2106 def depart_image(self
, node
):
2109 def get_image_width_height(self
, node
, attr
):
2111 if attr
in node
.attributes
:
2112 size
= node
.attributes
[attr
]
2120 except ValueError, e
:
2121 self
.document
.reporter
.warning(
2122 'Invalid %s for image: "%s"' % (
2123 attr
, node
.attributes
[attr
]))
2127 def get_image_scale(self
, node
):
2128 if 'scale' in node
.attributes
:
2130 scale
= int(node
.attributes
['scale'])
2131 if scale
< 1: # or scale > 100:
2132 self
.document
.reporter
.warning(
2133 'scale out of range (%s), using 1.' % (scale
, ))
2135 scale
= scale
* 0.01
2136 except ValueError, e
:
2137 self
.document
.reporter
.warning(
2138 'Invalid scale for image: "%s"' % (
2139 node
.attributes
['scale'], ))
2144 def get_image_scaled_width_height(self
, node
, source
):
2145 scale
= self
.get_image_scale(node
)
2146 width
= self
.get_image_width_height(node
, 'width')
2147 height
= self
.get_image_width_height(node
, 'height')
2150 if PIL
is not None and source
in self
.image_dict
:
2151 filename
, destination
= self
.image_dict
[source
]
2152 imageobj
= PIL
.Image
.open(filename
, 'r')
2153 dpi
= imageobj
.info
.get('dpi', dpi
)
2154 # dpi information can be (xdpi, ydpi) or xydpi
2156 except: dpi
= (dpi
, dpi
)
2160 if width
is None or height
is None:
2161 if imageobj
is None:
2163 'image size not fully specified and PIL not installed')
2164 if width
is None: width
= [imageobj
.size
[0], 'px']
2165 if height
is None: height
= [imageobj
.size
[1], 'px']
2169 if width
[1] == 'px': width
= [width
[0] / dpi
[0], 'in']
2170 if height
[1] == 'px': height
= [height
[0] / dpi
[1], 'in']
2172 width
[0] = str(width
[0])
2173 height
[0] = str(height
[0])
2174 return ''.join(width
), ''.join(height
)
2176 def generate_figure(self
, node
, source
, destination
, current_element
):
2178 width
, height
= self
.get_image_scaled_width_height(node
, source
)
2179 for node1
in node
.parent
.children
:
2180 if node1
.tagname
== 'caption':
2181 caption
= node1
.astext()
2182 self
.image_style_count
+= 1
2184 # Add the style for the caption.
2185 if caption
is not None:
2187 'style:class': 'extra',
2188 'style:family': 'paragraph',
2189 'style:name': 'Caption',
2190 'style:parent-style-name': 'Standard',
2192 el1
= SubElement(self
.automatic_styles
, 'style:style',
2193 attrib
=attrib
, nsdict
=SNSD
)
2195 'fo:margin-bottom': '0.0835in',
2196 'fo:margin-top': '0.0835in',
2197 'text:line-number': '0',
2198 'text:number-lines': 'false',
2200 el2
= SubElement(el1
, 'style:paragraph-properties',
2201 attrib
=attrib
, nsdict
=SNSD
)
2203 'fo:font-size': '12pt',
2204 'fo:font-style': 'italic',
2205 'style:font-name': 'Times',
2206 'style:font-name-complex': 'Lucidasans1',
2207 'style:font-size-asian': '12pt',
2208 'style:font-size-complex': '12pt',
2209 'style:font-style-asian': 'italic',
2210 'style:font-style-complex': 'italic',
2212 el2
= SubElement(el1
, 'style:text-properties',
2213 attrib
=attrib
, nsdict
=SNSD
)
2214 style_name
= 'rstframestyle%d' % self
.image_style_count
2217 'style:name': style_name
,
2218 'style:family': 'graphic',
2219 'style:parent-style-name': self
.rststyle('figureframe'),
2221 el1
= SubElement(self
.automatic_styles
,
2222 'style:style', attrib
=attrib
, nsdict
=SNSD
)
2225 if 'align' in node
.attributes
:
2226 align
= node
.attributes
['align'].split()
2228 if val
in ('left', 'center', 'right'):
2230 elif val
in ('top', 'middle', 'bottom'):
2234 classes
= node
.parent
.attributes
.get('classes')
2235 if classes
and 'wrap' in classes
:
2238 attrib
['style:wrap'] = 'dynamic'
2240 attrib
['style:wrap'] = 'none'
2241 el2
= SubElement(el1
,
2242 'style:graphic-properties', attrib
=attrib
, nsdict
=SNSD
)
2244 'draw:style-name': style_name
,
2245 'draw:name': 'Frame1',
2246 'text:anchor-type': 'paragraph',
2247 'draw:z-index': '0',
2249 attrib
['svg:width'] = width
2250 el3
= SubElement(current_element
, 'draw:frame', attrib
=attrib
)
2252 el4
= SubElement(el3
, 'draw:text-box', attrib
=attrib
)
2254 'text:style-name': self
.rststyle('caption'),
2256 el5
= SubElement(el4
, 'text:p', attrib
=attrib
)
2257 return el3
, el4
, el5
, caption
2259 def generate_image(self
, node
, source
, destination
, current_element
,
2261 width
, height
= self
.get_image_scaled_width_height(node
, source
)
2262 self
.image_style_count
+= 1
2263 style_name
= 'rstframestyle%d' % self
.image_style_count
2266 'style:name': style_name
,
2267 'style:family': 'graphic',
2268 'style:parent-style-name': self
.rststyle('image'),
2270 el1
= SubElement(self
.automatic_styles
,
2271 'style:style', attrib
=attrib
, nsdict
=SNSD
)
2274 if 'align' in node
.attributes
:
2275 align
= node
.attributes
['align'].split()
2277 if val
in ('left', 'center', 'right'):
2279 elif val
in ('top', 'middle', 'bottom'):
2281 if frame_attrs
is None:
2283 'style:vertical-pos': 'top',
2284 'style:vertical-rel': 'paragraph',
2285 'style:horizontal-rel': 'paragraph',
2286 'style:mirror': 'none',
2287 'fo:clip': 'rect(0cm 0cm 0cm 0cm)',
2288 'draw:luminance': '0%',
2289 'draw:contrast': '0%',
2293 'draw:gamma': '100%',
2294 'draw:color-inversion': 'false',
2295 'draw:image-opacity': '100%',
2296 'draw:color-mode': 'standard',
2299 attrib
= frame_attrs
2300 if halign
is not None:
2301 attrib
['style:horizontal-pos'] = halign
2302 if valign
is not None:
2303 attrib
['style:vertical-pos'] = valign
2304 # If there is a classes/wrap directive or we are
2305 # inside a table, add a no-wrap style.
2307 classes
= node
.attributes
.get('classes')
2308 if classes
and 'wrap' in classes
:
2311 attrib
['style:wrap'] = 'dynamic'
2313 attrib
['style:wrap'] = 'none'
2314 # If we are inside a table, add a no-wrap style.
2315 if self
.is_in_table(node
):
2316 attrib
['style:wrap'] = 'none'
2317 el2
= SubElement(el1
,
2318 'style:graphic-properties', attrib
=attrib
, nsdict
=SNSD
)
2320 #el = SubElement(current_element, 'text:p',
2321 # attrib={'text:style-name': self.rststyle('textbody')})
2323 'draw:style-name': style_name
,
2324 'draw:name': 'graphics2',
2325 'draw:z-index': '1',
2327 if isinstance(node
.parent
, nodes
.TextElement
):
2328 attrib
['text:anchor-type'] = 'as-char' #vds
2330 attrib
['text:anchor-type'] = 'paragraph'
2331 attrib
['svg:width'] = width
2332 attrib
['svg:height'] = height
2333 el1
= SubElement(current_element
, 'draw:frame', attrib
=attrib
)
2334 el2
= SubElement(el1
, 'draw:image', attrib
={
2335 'xlink:href': '%s' % (destination
, ),
2336 'xlink:type': 'simple',
2337 'xlink:show': 'embed',
2338 'xlink:actuate': 'onLoad',
2342 def is_in_table(self
, node
):
2345 if isinstance(node1
, docutils
.nodes
.entry
):
2347 node1
= node1
.parent
2350 def visit_legend(self
, node
):
2351 if isinstance(node
.parent
, docutils
.nodes
.figure
):
2352 el1
= self
.current_element
[-1]
2354 self
.current_element
= el1
2355 self
.paragraph_style_stack
.append(self
.rststyle('legend'))
2357 def depart_legend(self
, node
):
2358 if isinstance(node
.parent
, docutils
.nodes
.figure
):
2359 self
.paragraph_style_stack
.pop()
2360 self
.set_to_parent()
2361 self
.set_to_parent()
2362 self
.set_to_parent()
2364 def visit_line_block(self
, node
):
2365 self
.line_indent_level
+= 1
2366 self
.line_block_level
+= 1
2368 def depart_line_block(self
, node
):
2369 self
.line_indent_level
-= 1
2370 self
.line_block_level
-= 1
2372 def visit_line(self
, node
):
2373 style
= 'lineblock%d' % self
.line_indent_level
2374 el1
= SubElement(self
.current_element
, 'text:p', attrib
={
2375 'text:style-name': self
.rststyle(style
),
2377 self
.current_element
= el1
2379 def depart_line(self
, node
):
2380 self
.set_to_parent()
2382 def visit_literal(self
, node
):
2383 el
= SubElement(self
.current_element
, 'text:span',
2384 attrib
={'text:style-name': self
.rststyle('inlineliteral')})
2385 self
.set_current_element(el
)
2387 def depart_literal(self
, node
):
2388 self
.set_to_parent()
2390 def visit_inline(self
, node
):
2391 styles
= node
.attributes
.get('classes', ())
2393 inline_style
= styles
[0]
2394 el
= SubElement(self
.current_element
, 'text:span',
2395 attrib
={'text:style-name': self
.rststyle(inline_style
)})
2396 self
.set_current_element(el
)
2398 def depart_inline(self
, node
):
2399 self
.set_to_parent()
2401 def _calculate_code_block_padding(self
, line
):
2403 matchobj
= SPACES_PATTERN
.match(line
)
2405 pad
= matchobj
.group()
2408 matchobj
= TABS_PATTERN
.match(line
)
2410 pad
= matchobj
.group()
2411 count
= len(pad
) * 8
2414 def _add_syntax_highlighting(self
, insource
, language
):
2415 lexer
= pygments
.lexers
.get_lexer_by_name(language
, stripall
=True)
2416 if language
in ('latex', 'tex'):
2417 fmtr
= OdtPygmentsLaTeXFormatter(lambda name
, parameters
=():
2418 self
.rststyle(name
, parameters
),
2419 escape_function
=escape_cdata
)
2421 fmtr
= OdtPygmentsProgFormatter(lambda name
, parameters
=():
2422 self
.rststyle(name
, parameters
),
2423 escape_function
=escape_cdata
)
2424 outsource
= pygments
.highlight(insource
, lexer
, fmtr
)
2427 def fill_line(self
, line
):
2428 line
= FILL_PAT1
.sub(self
.fill_func1
, line
)
2429 line
= FILL_PAT2
.sub(self
.fill_func2
, line
)
2432 def fill_func1(self
, matchobj
):
2433 spaces
= matchobj
.group(0)
2434 repl
= '<text:s text:c="%d"/>' % (len(spaces
), )
2437 def fill_func2(self
, matchobj
):
2438 spaces
= matchobj
.group(0)
2439 repl
= ' <text:s text:c="%d"/>' % (len(spaces
) - 1, )
2442 def visit_literal_block(self
, node
):
2443 if len(self
.paragraph_style_stack
) > 1:
2444 wrapper1
= '<text:p text:style-name="%s">%%s</text:p>' % (
2445 self
.rststyle('codeblock-indented'), )
2447 wrapper1
= '<text:p text:style-name="%s">%%s</text:p>' % (
2448 self
.rststyle('codeblock'), )
2449 source
= node
.astext()
2451 self
.settings
.add_syntax_highlighting
2453 #node.get('hilight', False)
2455 language
= node
.get('language', 'python')
2456 source
= self
._add
_syntax
_highlighting
(source
, language
)
2458 source
= escape_cdata(source
)
2459 lines
= source
.split('\n')
2460 # If there is an empty last line, remove it.
2463 lines1
= ['<wrappertag1 xmlns:text="urn:oasis:names:tc:opendocument:xmlns:text:1.0">']
2466 for my_line
in lines
:
2467 my_line
= self
.fill_line(my_line
)
2468 my_line
= my_line
.replace(" ", "\n")
2469 my_lines
.append(my_line
)
2470 my_lines_str
= '<text:line-break/>'.join(my_lines
)
2471 my_lines_str2
= wrapper1
% (my_lines_str
, )
2472 lines1
.append(my_lines_str2
)
2473 lines1
.append('</wrappertag1>')
2474 s1
= ''.join(lines1
)
2475 if WhichElementTree
!= "lxml":
2476 s1
= s1
.encode("utf-8")
2477 el1
= etree
.fromstring(s1
)
2478 children
= el1
.getchildren()
2479 for child
in children
:
2480 self
.current_element
.append(child
)
2482 def depart_literal_block(self
, node
):
2485 visit_doctest_block
= visit_literal_block
2486 depart_doctest_block
= depart_literal_block
2488 # placeholder for math (see docs/dev/todo.txt)
2489 def visit_math(self
, node
):
2490 self
.document
.reporter
.warning('"math" role not supported',
2492 self
.visit_literal(node
)
2494 def depart_math(self
, node
):
2495 self
.depart_literal(node
)
2497 def visit_math_block(self
, node
):
2498 self
.document
.reporter
.warning('"math" directive not supported',
2500 self
.visit_literal_block(node
)
2502 def depart_math_block(self
, node
):
2503 self
.depart_literal_block(node
)
2505 def visit_meta(self
, node
):
2506 name
= node
.attributes
.get('name')
2507 content
= node
.attributes
.get('content')
2508 if name
is not None and content
is not None:
2509 self
.meta_dict
[name
] = content
2511 def depart_meta(self
, node
):
2514 def visit_option_list(self
, node
):
2515 table_name
= 'tableoption'
2517 # Generate automatic styles
2518 if not self
.optiontablestyles_generated
:
2519 self
.optiontablestyles_generated
= True
2520 el
= SubElement(self
.automatic_styles
, 'style:style', attrib
={
2521 'style:name': self
.rststyle(table_name
),
2522 'style:family': 'table'}, nsdict
=SNSD
)
2523 el1
= SubElement(el
, 'style:table-properties', attrib
={
2524 'style:width': '17.59cm',
2525 'table:align': 'left',
2526 'style:shadow': 'none'}, nsdict
=SNSD
)
2527 el
= SubElement(self
.automatic_styles
, 'style:style', attrib
={
2528 'style:name': self
.rststyle('%s.%%c' % table_name
, ( 'A', )),
2529 'style:family': 'table-column'}, nsdict
=SNSD
)
2530 el1
= SubElement(el
, 'style:table-column-properties', attrib
={
2531 'style:column-width': '4.999cm'}, nsdict
=SNSD
)
2532 el
= SubElement(self
.automatic_styles
, 'style:style', attrib
={
2533 'style:name': self
.rststyle('%s.%%c' % table_name
, ( 'B', )),
2534 'style:family': 'table-column'}, nsdict
=SNSD
)
2535 el1
= SubElement(el
, 'style:table-column-properties', attrib
={
2536 'style:column-width': '12.587cm'}, nsdict
=SNSD
)
2537 el
= SubElement(self
.automatic_styles
, 'style:style', attrib
={
2538 'style:name': self
.rststyle(
2539 '%s.%%c%%d' % table_name
, ( 'A', 1, )),
2540 'style:family': 'table-cell'}, nsdict
=SNSD
)
2541 el1
= SubElement(el
, 'style:table-cell-properties', attrib
={
2542 'fo:background-color': 'transparent',
2543 'fo:padding': '0.097cm',
2544 'fo:border-left': '0.035cm solid #000000',
2545 'fo:border-right': 'none',
2546 'fo:border-top': '0.035cm solid #000000',
2547 'fo:border-bottom': '0.035cm solid #000000'}, nsdict
=SNSD
)
2548 el2
= SubElement(el1
, 'style:background-image', nsdict
=SNSD
)
2549 el
= SubElement(self
.automatic_styles
, 'style:style', attrib
={
2550 'style:name': self
.rststyle(
2551 '%s.%%c%%d' % table_name
, ( 'B', 1, )),
2552 'style:family': 'table-cell'}, nsdict
=SNSD
)
2553 el1
= SubElement(el
, 'style:table-cell-properties', attrib
={
2554 'fo:padding': '0.097cm',
2555 'fo:border': '0.035cm solid #000000'}, nsdict
=SNSD
)
2556 el
= SubElement(self
.automatic_styles
, 'style:style', attrib
={
2557 'style:name': self
.rststyle(
2558 '%s.%%c%%d' % table_name
, ( 'A', 2, )),
2559 'style:family': 'table-cell'}, nsdict
=SNSD
)
2560 el1
= SubElement(el
, 'style:table-cell-properties', attrib
={
2561 'fo:padding': '0.097cm',
2562 'fo:border-left': '0.035cm solid #000000',
2563 'fo:border-right': 'none',
2564 'fo:border-top': 'none',
2565 'fo:border-bottom': '0.035cm solid #000000'}, nsdict
=SNSD
)
2566 el
= SubElement(self
.automatic_styles
, 'style:style', attrib
={
2567 'style:name': self
.rststyle(
2568 '%s.%%c%%d' % table_name
, ( 'B', 2, )),
2569 'style:family': 'table-cell'}, nsdict
=SNSD
)
2570 el1
= SubElement(el
, 'style:table-cell-properties', attrib
={
2571 'fo:padding': '0.097cm',
2572 'fo:border-left': '0.035cm solid #000000',
2573 'fo:border-right': '0.035cm solid #000000',
2574 'fo:border-top': 'none',
2575 'fo:border-bottom': '0.035cm solid #000000'}, nsdict
=SNSD
)
2577 # Generate table data
2578 el
= self
.append_child('table:table', attrib
={
2579 'table:name': self
.rststyle(table_name
),
2580 'table:style-name': self
.rststyle(table_name
),
2582 el1
= SubElement(el
, 'table:table-column', attrib
={
2583 'table:style-name': self
.rststyle(
2584 '%s.%%c' % table_name
, ( 'A', ))})
2585 el1
= SubElement(el
, 'table:table-column', attrib
={
2586 'table:style-name': self
.rststyle(
2587 '%s.%%c' % table_name
, ( 'B', ))})
2588 el1
= SubElement(el
, 'table:table-header-rows')
2589 el2
= SubElement(el1
, 'table:table-row')
2590 el3
= SubElement(el2
, 'table:table-cell', attrib
={
2591 'table:style-name': self
.rststyle(
2592 '%s.%%c%%d' % table_name
, ( 'A', 1, )),
2593 'office:value-type': 'string'})
2594 el4
= SubElement(el3
, 'text:p', attrib
={
2595 'text:style-name': 'Table_20_Heading'})
2597 el3
= SubElement(el2
, 'table:table-cell', attrib
={
2598 'table:style-name': self
.rststyle(
2599 '%s.%%c%%d' % table_name
, ( 'B', 1, )),
2600 'office:value-type': 'string'})
2601 el4
= SubElement(el3
, 'text:p', attrib
={
2602 'text:style-name': 'Table_20_Heading'})
2603 el4
.text
= 'Description'
2604 self
.set_current_element(el
)
2606 def depart_option_list(self
, node
):
2607 self
.set_to_parent()
2609 def visit_option_list_item(self
, node
):
2610 el
= self
.append_child('table:table-row')
2611 self
.set_current_element(el
)
2613 def depart_option_list_item(self
, node
):
2614 self
.set_to_parent()
2616 def visit_option_group(self
, node
):
2617 el
= self
.append_child('table:table-cell', attrib
={
2618 'table:style-name': 'Table%d.A2' % self
.table_count
,
2619 'office:value-type': 'string',
2621 self
.set_current_element(el
)
2623 def depart_option_group(self
, node
):
2624 self
.set_to_parent()
2626 def visit_option(self
, node
):
2627 el
= self
.append_child('text:p', attrib
={
2628 'text:style-name': 'Table_20_Contents'})
2629 el
.text
= node
.astext()
2631 def depart_option(self
, node
):
2634 def visit_option_string(self
, node
):
2637 def depart_option_string(self
, node
):
2640 def visit_option_argument(self
, node
):
2643 def depart_option_argument(self
, node
):
2646 def visit_description(self
, node
):
2647 el
= self
.append_child('table:table-cell', attrib
={
2648 'table:style-name': 'Table%d.B2' % self
.table_count
,
2649 'office:value-type': 'string',
2651 el1
= SubElement(el
, 'text:p', attrib
={
2652 'text:style-name': 'Table_20_Contents'})
2653 el1
.text
= node
.astext()
2654 raise nodes
.SkipChildren()
2656 def depart_description(self
, node
):
2659 def visit_paragraph(self
, node
):
2660 self
.in_paragraph
= True
2662 el
= self
.append_p('header')
2663 elif self
.in_footer
:
2664 el
= self
.append_p('footer')
2666 style_name
= self
.paragraph_style_stack
[-1]
2667 el
= self
.append_child('text:p',
2668 attrib
={'text:style-name': style_name
})
2669 self
.append_pending_ids(el
)
2670 self
.set_current_element(el
)
2672 def depart_paragraph(self
, node
):
2673 self
.in_paragraph
= False
2674 self
.set_to_parent()
2676 self
.header_content
.append(
2677 self
.current_element
.getchildren()[-1])
2678 self
.current_element
.remove(
2679 self
.current_element
.getchildren()[-1])
2680 elif self
.in_footer
:
2681 self
.footer_content
.append(
2682 self
.current_element
.getchildren()[-1])
2683 self
.current_element
.remove(
2684 self
.current_element
.getchildren()[-1])
2686 def visit_problematic(self
, node
):
2689 def depart_problematic(self
, node
):
2692 def visit_raw(self
, node
):
2693 if 'format' in node
.attributes
:
2694 formats
= node
.attributes
['format']
2695 formatlist
= formats
.split()
2696 if 'odt' in formatlist
:
2697 rawstr
= node
.astext()
2698 attrstr
= ' '.join(['%s="%s"' % (k
, v
, )
2699 for k
,v
in CONTENT_NAMESPACE_ATTRIB
.items()])
2700 contentstr
= '<stuff %s>%s</stuff>' % (attrstr
, rawstr
, )
2701 if WhichElementTree
!= "lxml":
2702 contentstr
= contentstr
.encode("utf-8")
2703 content
= etree
.fromstring(contentstr
)
2704 elements
= content
.getchildren()
2705 if len(elements
) > 0:
2709 elif self
.in_footer
:
2712 self
.current_element
.append(el1
)
2713 raise nodes
.SkipChildren()
2715 def depart_raw(self
, node
):
2718 elif self
.in_footer
:
2723 def visit_reference(self
, node
):
2724 text
= node
.astext()
2725 if self
.settings
.create_links
:
2726 if node
.has_key('refuri'):
2727 href
= node
['refuri']
2728 if ( self
.settings
.cloak_email_addresses
2729 and href
.startswith('mailto:')):
2730 href
= self
.cloak_mailto(href
)
2731 el
= self
.append_child('text:a', attrib
={
2732 'xlink:href': '%s' % href
,
2733 'xlink:type': 'simple',
2735 self
.set_current_element(el
)
2736 elif node
.has_key('refid'):
2737 if self
.settings
.create_links
:
2738 href
= node
['refid']
2739 el
= self
.append_child('text:reference-ref', attrib
={
2740 'text:ref-name': '%s' % href
,
2741 'text:reference-format': 'text',
2744 self
.document
.reporter
.warning(
2745 'References must have "refuri" or "refid" attribute.')
2746 if (self
.in_table_of_contents
and
2747 len(node
.children
) >= 1 and
2748 isinstance(node
.children
[0], docutils
.nodes
.generated
)):
2749 node
.remove(node
.children
[0])
2751 def depart_reference(self
, node
):
2752 if self
.settings
.create_links
:
2753 if node
.has_key('refuri'):
2754 self
.set_to_parent()
2756 def visit_rubric(self
, node
):
2757 style_name
= self
.rststyle('rubric')
2758 classes
= node
.get('classes')
2763 el
= SubElement(self
.current_element
, 'text:h', attrib
= {
2764 #'text:outline-level': '%d' % section_level,
2765 #'text:style-name': 'Heading_20_%d' % section_level,
2766 'text:style-name': style_name
,
2768 text
= node
.astext()
2769 el
.text
= self
.encode(text
)
2771 def depart_rubric(self
, node
):
2774 def visit_section(self
, node
, move_ids
=1):
2775 self
.section_level
+= 1
2776 self
.section_count
+= 1
2777 if self
.settings
.create_sections
:
2778 el
= self
.append_child('text:section', attrib
={
2779 'text:name': 'Section%d' % self
.section_count
,
2780 'text:style-name': 'Sect%d' % self
.section_level
,
2782 self
.set_current_element(el
)
2784 def depart_section(self
, node
):
2785 self
.section_level
-= 1
2786 if self
.settings
.create_sections
:
2787 self
.set_to_parent()
2789 def visit_strong(self
, node
):
2790 el
= SubElement(self
.current_element
, 'text:span',
2791 attrib
={'text:style-name': self
.rststyle('strong')})
2792 self
.set_current_element(el
)
2794 def depart_strong(self
, node
):
2795 self
.set_to_parent()
2797 def visit_substitution_definition(self
, node
):
2798 raise nodes
.SkipChildren()
2800 def depart_substitution_definition(self
, node
):
2803 def visit_system_message(self
, node
):
2806 def depart_system_message(self
, node
):
2809 def get_table_style(self
, node
):
2812 use_predefined_table_style
= False
2813 str_classes
= node
.get('classes')
2814 if str_classes
is not None:
2815 for str_class
in str_classes
:
2816 if str_class
.startswith(TABLESTYLEPREFIX
):
2817 table_name
= str_class
2818 use_predefined_table_style
= True
2820 if table_name
is not None:
2821 table_style
= self
.table_styles
.get(table_name
)
2822 if table_style
is None:
2823 # If we can't find the table style, issue warning
2824 # and use the default table style.
2825 self
.document
.reporter
.warning(
2826 'Can\'t find table style "%s". Using default.' % (
2828 table_name
= TABLENAMEDEFAULT
2829 table_style
= self
.table_styles
.get(table_name
)
2830 if table_style
is None:
2831 # If we can't find the default table style, issue a warning
2832 # and use a built-in default style.
2833 self
.document
.reporter
.warning(
2834 'Can\'t find default table style "%s". Using built-in default.' % (
2836 table_style
= BUILTIN_DEFAULT_TABLE_STYLE
2838 table_name
= TABLENAMEDEFAULT
2839 table_style
= self
.table_styles
.get(table_name
)
2840 if table_style
is None:
2841 # If we can't find the default table style, issue a warning
2842 # and use a built-in default style.
2843 self
.document
.reporter
.warning(
2844 'Can\'t find default table style "%s". Using built-in default.' % (
2846 table_style
= BUILTIN_DEFAULT_TABLE_STYLE
2849 def visit_table(self
, node
):
2850 self
.table_count
+= 1
2851 table_style
= self
.get_table_style(node
)
2852 table_name
= '%s%%d' % TABLESTYLEPREFIX
2853 el1
= SubElement(self
.automatic_styles
, 'style:style', attrib
={
2854 'style:name': self
.rststyle(
2855 '%s' % table_name
, ( self
.table_count
, )),
2856 'style:family': 'table',
2858 if table_style
.backgroundcolor
is None:
2859 el1_1
= SubElement(el1
, 'style:table-properties', attrib
={
2860 #'style:width': '17.59cm',
2861 #'table:align': 'margins',
2862 'table:align': 'left',
2863 'fo:margin-top': '0in',
2864 'fo:margin-bottom': '0.10in',
2867 el1_1
= SubElement(el1
, 'style:table-properties', attrib
={
2868 #'style:width': '17.59cm',
2869 'table:align': 'margins',
2870 'fo:margin-top': '0in',
2871 'fo:margin-bottom': '0.10in',
2872 'fo:background-color': table_style
.backgroundcolor
,
2874 # We use a single cell style for all cells in this table.
2875 # That's probably not correct, but seems to work.
2876 el2
= SubElement(self
.automatic_styles
, 'style:style', attrib
={
2877 'style:name': self
.rststyle(
2878 '%s.%%c%%d' % table_name
, ( self
.table_count
, 'A', 1, )),
2879 'style:family': 'table-cell',
2881 thickness
= self
.settings
.table_border_thickness
2882 if thickness
is None:
2883 line_style1
= table_style
.border
2885 line_style1
= '0.%03dcm solid #000000' % (thickness
, )
2886 el2_1
= SubElement(el2
, 'style:table-cell-properties', attrib
={
2887 'fo:padding': '0.049cm',
2888 'fo:border-left': line_style1
,
2889 'fo:border-right': line_style1
,
2890 'fo:border-top': line_style1
,
2891 'fo:border-bottom': line_style1
,
2894 for child
in node
.children
:
2895 if child
.tagname
== 'title':
2896 title
= child
.astext()
2898 if title
is not None:
2899 el3
= self
.append_p('table-title', title
)
2902 el4
= SubElement(self
.current_element
, 'table:table', attrib
={
2903 'table:name': self
.rststyle(
2904 '%s' % table_name
, ( self
.table_count
, )),
2905 'table:style-name': self
.rststyle(
2906 '%s' % table_name
, ( self
.table_count
, )),
2908 self
.set_current_element(el4
)
2909 self
.current_table_style
= el1
2910 self
.table_width
= 0.0
2912 def depart_table(self
, node
):
2913 attribkey
= add_ns('style:width', nsdict
=SNSD
)
2914 attribval
= '%.4fin' % (self
.table_width
, )
2915 el1
= self
.current_table_style
2917 el2
.attrib
[attribkey
] = attribval
2918 self
.set_to_parent()
2920 def visit_tgroup(self
, node
):
2921 self
.column_count
= ord('A') - 1
2923 def depart_tgroup(self
, node
):
2926 def visit_colspec(self
, node
):
2927 self
.column_count
+= 1
2928 colspec_name
= self
.rststyle(
2929 '%s%%d.%%s' % TABLESTYLEPREFIX
,
2930 (self
.table_count
, chr(self
.column_count
), )
2932 colwidth
= node
['colwidth'] / 12.0
2933 el1
= SubElement(self
.automatic_styles
, 'style:style', attrib
={
2934 'style:name': colspec_name
,
2935 'style:family': 'table-column',
2937 el1_1
= SubElement(el1
, 'style:table-column-properties', attrib
={
2938 'style:column-width': '%.4fin' % colwidth
2941 el2
= self
.append_child('table:table-column', attrib
={
2942 'table:style-name': colspec_name
,
2944 self
.table_width
+= colwidth
2946 def depart_colspec(self
, node
):
2949 def visit_thead(self
, node
):
2950 el
= self
.append_child('table:table-header-rows')
2951 self
.set_current_element(el
)
2952 self
.in_thead
= True
2953 self
.paragraph_style_stack
.append('Table_20_Heading')
2955 def depart_thead(self
, node
):
2956 self
.set_to_parent()
2957 self
.in_thead
= False
2958 self
.paragraph_style_stack
.pop()
2960 def visit_row(self
, node
):
2961 self
.column_count
= ord('A') - 1
2962 el
= self
.append_child('table:table-row')
2963 self
.set_current_element(el
)
2965 def depart_row(self
, node
):
2966 self
.set_to_parent()
2968 def visit_entry(self
, node
):
2969 self
.column_count
+= 1
2970 cellspec_name
= self
.rststyle(
2971 '%s%%d.%%c%%d' % TABLESTYLEPREFIX
,
2972 (self
.table_count
, 'A', 1, )
2975 'table:style-name': cellspec_name
,
2976 'office:value-type': 'string',
2978 morecols
= node
.get('morecols', 0)
2980 attrib
['table:number-columns-spanned'] = '%d' % (morecols
+ 1,)
2981 self
.column_count
+= morecols
2982 morerows
= node
.get('morerows', 0)
2984 attrib
['table:number-rows-spanned'] = '%d' % (morerows
+ 1,)
2985 el1
= self
.append_child('table:table-cell', attrib
=attrib
)
2986 self
.set_current_element(el1
)
2988 def depart_entry(self
, node
):
2989 self
.set_to_parent()
2991 def visit_tbody(self
, node
):
2994 def depart_tbody(self
, node
):
2997 def visit_target(self
, node
):
2999 # I don't know how to implement targets in ODF.
3000 # How do we create a target in oowriter? A cross-reference?
3001 if not (node
.has_key('refuri') or node
.has_key('refid')
3002 or node
.has_key('refname')):
3007 def depart_target(self
, node
):
3010 def visit_title(self
, node
, move_ids
=1, title_type
='title'):
3011 if isinstance(node
.parent
, docutils
.nodes
.section
):
3012 section_level
= self
.section_level
3013 if section_level
> 7:
3014 self
.document
.reporter
.warning(
3015 'Heading/section levels greater than 7 not supported.')
3016 self
.document
.reporter
.warning(
3017 ' Reducing to heading level 7 for heading: "%s"' % (
3020 el1
= self
.append_child('text:h', attrib
= {
3021 'text:outline-level': '%d' % section_level
,
3022 #'text:style-name': 'Heading_20_%d' % section_level,
3023 'text:style-name': self
.rststyle(
3024 'heading%d', (section_level
, )),
3026 self
.append_pending_ids(el1
)
3027 self
.set_current_element(el1
)
3028 elif isinstance(node
.parent
, docutils
.nodes
.document
):
3029 # text = self.settings.title
3031 # text = node.astext()
3032 el1
= SubElement(self
.current_element
, 'text:p', attrib
= {
3033 'text:style-name': self
.rststyle(title_type
),
3035 self
.append_pending_ids(el1
)
3036 text
= node
.astext()
3038 self
.found_doc_title
= True
3039 self
.set_current_element(el1
)
3041 def depart_title(self
, node
):
3042 if (isinstance(node
.parent
, docutils
.nodes
.section
) or
3043 isinstance(node
.parent
, docutils
.nodes
.document
)):
3044 self
.set_to_parent()
3046 def visit_subtitle(self
, node
, move_ids
=1):
3047 self
.visit_title(node
, move_ids
, title_type
='subtitle')
3049 def depart_subtitle(self
, node
):
3050 self
.depart_title(node
)
3052 def visit_title_reference(self
, node
):
3053 el
= self
.append_child('text:span', attrib
={
3054 'text:style-name': self
.rststyle('quotation')})
3055 el
.text
= self
.encode(node
.astext())
3056 raise nodes
.SkipChildren()
3058 def depart_title_reference(self
, node
):
3061 def generate_table_of_content_entry_template(self
, el1
):
3062 for idx
in range(1, 11):
3063 el2
= SubElement(el1
,
3064 'text:table-of-content-entry-template',
3066 'text:outline-level': "%d" % (idx
, ),
3067 'text:style-name': self
.rststyle('contents-%d' % (idx
, )),
3069 el3
= SubElement(el2
, 'text:index-entry-chapter')
3070 el3
= SubElement(el2
, 'text:index-entry-text')
3071 el3
= SubElement(el2
, 'text:index-entry-tab-stop', attrib
={
3072 'style:leader-char': ".",
3073 'style:type': "right",
3075 el3
= SubElement(el2
, 'text:index-entry-page-number')
3077 def find_title_label(self
, node
, class_type
, label_key
):
3080 for child
in node
.children
:
3081 if isinstance(child
, class_type
):
3084 if title_node
is not None:
3085 label
= title_node
.astext()
3087 label
= self
.language
.labels
[label_key
]
3090 def visit_topic(self
, node
):
3091 if 'classes' in node
.attributes
:
3092 if 'contents' in node
.attributes
['classes']:
3093 label
= self
.find_title_label(node
, docutils
.nodes
.title
,
3095 if self
.settings
.generate_oowriter_toc
:
3096 el1
= self
.append_child('text:table-of-content', attrib
={
3097 'text:name': 'Table of Contents1',
3098 'text:protected': 'true',
3099 'text:style-name': 'Sect1',
3101 el2
= SubElement(el1
,
3102 'text:table-of-content-source',
3104 'text:outline-level': '10',
3106 el3
=SubElement(el2
, 'text:index-title-template', attrib
={
3107 'text:style-name': 'Contents_20_Heading',
3110 self
.generate_table_of_content_entry_template(el2
)
3111 el4
= SubElement(el1
, 'text:index-body')
3112 el5
= SubElement(el4
, 'text:index-title')
3113 el6
= SubElement(el5
, 'text:p', attrib
={
3114 'text:style-name': self
.rststyle('contents-heading'),
3117 self
.save_current_element
= self
.current_element
3118 self
.table_of_content_index_body
= el4
3119 self
.set_current_element(el4
)
3121 el
= self
.append_p('horizontalline')
3122 el
= self
.append_p('centeredtextbody')
3123 el1
= SubElement(el
, 'text:span',
3124 attrib
={'text:style-name': self
.rststyle('strong')})
3126 self
.in_table_of_contents
= True
3127 elif 'abstract' in node
.attributes
['classes']:
3128 el
= self
.append_p('horizontalline')
3129 el
= self
.append_p('centeredtextbody')
3130 el1
= SubElement(el
, 'text:span',
3131 attrib
={'text:style-name': self
.rststyle('strong')})
3132 label
= self
.find_title_label(node
, docutils
.nodes
.title
,
3135 elif 'dedication' in node
.attributes
['classes']:
3136 el
= self
.append_p('horizontalline')
3137 el
= self
.append_p('centeredtextbody')
3138 el1
= SubElement(el
, 'text:span',
3139 attrib
={'text:style-name': self
.rststyle('strong')})
3140 label
= self
.find_title_label(node
, docutils
.nodes
.title
,
3144 def depart_topic(self
, node
):
3145 if 'classes' in node
.attributes
:
3146 if 'contents' in node
.attributes
['classes']:
3147 if self
.settings
.generate_oowriter_toc
:
3148 self
.update_toc_page_numbers(
3149 self
.table_of_content_index_body
)
3150 self
.set_current_element(self
.save_current_element
)
3152 el
= self
.append_p('horizontalline')
3153 self
.in_table_of_contents
= False
3155 def update_toc_page_numbers(self
, el
):
3157 self
.update_toc_collect(el
, 0, collection
)
3158 self
.update_toc_add_numbers(collection
)
3160 def update_toc_collect(self
, el
, level
, collection
):
3161 collection
.append((level
, el
))
3163 for child_el
in el
.getchildren():
3164 if child_el
.tag
!= 'text:index-body':
3165 self
.update_toc_collect(child_el
, level
, collection
)
3167 def update_toc_add_numbers(self
, collection
):
3168 for level
, el1
in collection
:
3169 if (el1
.tag
== 'text:p' and
3170 el1
.text
!= 'Table of Contents'):
3171 el2
= SubElement(el1
, 'text:tab')
3175 def visit_transition(self
, node
):
3176 el
= self
.append_p('horizontalline')
3178 def depart_transition(self
, node
):
3184 def visit_warning(self
, node
):
3185 self
.generate_admonition(node
, 'warning')
3187 def depart_warning(self
, node
):
3188 self
.paragraph_style_stack
.pop()
3190 def visit_attention(self
, node
):
3191 self
.generate_admonition(node
, 'attention')
3193 depart_attention
= depart_warning
3195 def visit_caution(self
, node
):
3196 self
.generate_admonition(node
, 'caution')
3198 depart_caution
= depart_warning
3200 def visit_danger(self
, node
):
3201 self
.generate_admonition(node
, 'danger')
3203 depart_danger
= depart_warning
3205 def visit_error(self
, node
):
3206 self
.generate_admonition(node
, 'error')
3208 depart_error
= depart_warning
3210 def visit_hint(self
, node
):
3211 self
.generate_admonition(node
, 'hint')
3213 depart_hint
= depart_warning
3215 def visit_important(self
, node
):
3216 self
.generate_admonition(node
, 'important')
3218 depart_important
= depart_warning
3220 def visit_note(self
, node
):
3221 self
.generate_admonition(node
, 'note')
3223 depart_note
= depart_warning
3225 def visit_tip(self
, node
):
3226 self
.generate_admonition(node
, 'tip')
3228 depart_tip
= depart_warning
3230 def visit_admonition(self
, node
):
3232 for child
in node
.children
:
3233 if child
.tagname
== 'title':
3234 title
= child
.astext()
3236 classes1
= node
.get('classes')
3239 self
.generate_admonition(node
, 'generic', title
)
3241 depart_admonition
= depart_warning
3243 def generate_admonition(self
, node
, label
, title
=None):
3244 el1
= SubElement(self
.current_element
, 'text:p', attrib
= {
3245 'text:style-name': self
.rststyle('admon-%s-hdr', ( label
, )),
3250 el1
.text
= '%s!' % (label
.capitalize(), )
3251 s1
= self
.rststyle('admon-%s-body', ( label
, ))
3252 self
.paragraph_style_stack
.append(s1
)
3255 # Roles (e.g. subscript, superscript, strong, ...
3257 def visit_subscript(self
, node
):
3258 el
= self
.append_child('text:span', attrib
={
3259 'text:style-name': 'rststyle-subscript',
3261 self
.set_current_element(el
)
3263 def depart_subscript(self
, node
):
3264 self
.set_to_parent()
3266 def visit_superscript(self
, node
):
3267 el
= self
.append_child('text:span', attrib
={
3268 'text:style-name': 'rststyle-superscript',
3270 self
.set_current_element(el
)
3272 def depart_superscript(self
, node
):
3273 self
.set_to_parent()
3276 # Use an own reader to modify transformations done.
3277 class Reader(standalone
.Reader
):
3279 def get_transforms(self
):
3280 default
= standalone
.Reader
.get_transforms(self
)
3281 if self
.settings
.create_links
:
3285 if i
is not references
.DanglingReferences
]