Added support for ``title`` and ``subtitle``.
[docutils/kirr.git] / sandbox / OpenDocument / odtwriter / __init__.py
blob475d2007266611e0a84606f9be02199f489d24bb
2 """
3 Open Document Format (ODF) Writer.
5 """
7 VERSION = '1.0a'
9 __docformat__ = 'reStructuredText'
12 import sys
13 import os
14 import os.path
15 import tempfile
16 import zipfile
17 from xml.dom import minidom
18 import time
19 import re
20 import StringIO
21 import inspect
22 import imp
23 import docutils
24 from docutils import frontend, nodes, utils, writers, languages
25 from docutils.parsers import rst
26 from docutils.readers import standalone
27 from docutils.transforms import references
30 WhichElementTree = ''
31 try:
32 # 1. Try to use lxml.
33 from lxml import etree
34 #print '*** using lxml'
35 WhichElementTree = 'lxml'
36 except ImportError, e:
37 try:
38 # 2. Try to use ElementTree from the Python standard library.
39 from xml.etree import ElementTree as etree
40 #print '*** using ElementTree'
41 WhichElementTree = 'elementtree'
42 except ImportError, e:
43 try:
44 # 3. Try to use a version of ElementTree installed as a separate
45 # product.
46 from elementtree import ElementTree as etree
47 WhichElementTree = 'elementtree'
48 except ImportError, e:
49 print '***'
50 print '*** Error: Must install either ElementTree or lxml or'
51 print '*** a version of Python containing ElementTree.'
52 print '***'
53 raise
55 try:
56 import pygments
57 import pygments.formatter
58 import pygments.lexers
59 class OdtPygmentsFormatter(pygments.formatter.Formatter):
60 def __init__(self, rststyle_function):
61 pygments.formatter.Formatter.__init__(self)
62 self.rststyle_function = rststyle_function
64 def rststyle(self, name, parameters=( )):
65 return self.rststyle_function(name, parameters)
67 class OdtPygmentsProgFormatter(OdtPygmentsFormatter):
68 def format(self, tokensource, outfile):
69 tokenclass = pygments.token.Token
70 for ttype, value in tokensource:
71 value = escape_cdata(value)
72 if ttype == tokenclass.Keyword:
73 s2 = self.rststyle('codeblock-keyword')
74 s1 = '<text:span text:style-name="%s">%s</text:span>' % \
75 (s2, value, )
76 elif ttype == tokenclass.Literal.String:
77 s2 = self.rststyle('codeblock-string')
78 s1 = '<text:span text:style-name="%s">%s</text:span>' % \
79 (s2, value, )
80 elif ttype in (
81 tokenclass.Literal.Number.Integer,
82 tokenclass.Literal.Number.Integer.Long,
83 tokenclass.Literal.Number.Float,
84 tokenclass.Literal.Number.Hex,
85 tokenclass.Literal.Number.Oct,
86 tokenclass.Literal.Number,
88 s2 = self.rststyle('codeblock-number')
89 s1 = '<text:span text:style-name="%s">%s</text:span>' % \
90 (s2, value, )
91 elif ttype == tokenclass.Operator:
92 s2 = self.rststyle('codeblock-operator')
93 s1 = '<text:span text:style-name="%s">%s</text:span>' % \
94 (s2, value, )
95 elif ttype == tokenclass.Comment:
96 s2 = self.rststyle('codeblock-comment')
97 s1 = '<text:span text:style-name="%s">%s</text:span>' % \
98 (s2, value, )
99 elif ttype == tokenclass.Name.Class:
100 s2 = self.rststyle('codeblock-classname')
101 s1 = '<text:span text:style-name="%s">%s</text:span>' % \
102 (s2, value, )
103 elif ttype == tokenclass.Name.Function:
104 s2 = self.rststyle('codeblock-functionname')
105 s1 = '<text:span text:style-name="%s">%s</text:span>' % \
106 (s2, value, )
107 elif ttype == tokenclass.Name:
108 s2 = self.rststyle('codeblock-name')
109 s1 = '<text:span text:style-name="%s">%s</text:span>' % \
110 (s2, value, )
111 else:
112 s1 = value
113 outfile.write(s1)
114 class OdtPygmentsLaTeXFormatter(OdtPygmentsFormatter):
115 def format(self, tokensource, outfile):
116 tokenclass = pygments.token.Token
117 for ttype, value in tokensource:
118 value = escape_cdata(value)
119 if ttype == tokenclass.Keyword:
120 s2 = self.rststyle('codeblock-keyword')
121 s1 = '<text:span text:style-name="%s">%s</text:span>' % \
122 (s2, value, )
123 elif ttype in (tokenclass.Literal.String,
124 tokenclass.Literal.String.Backtick,
126 s2 = self.rststyle('codeblock-string')
127 s1 = '<text:span text:style-name="%s">%s</text:span>' % \
128 (s2, value, )
129 elif ttype == tokenclass.Name.Attribute:
130 s2 = self.rststyle('codeblock-operator')
131 s1 = '<text:span text:style-name="%s">%s</text:span>' % \
132 (s2, value, )
133 elif ttype == tokenclass.Comment:
134 if value[-1] == '\n':
135 s2 = self.rststyle('codeblock-comment')
136 s1 = '<text:span text:style-name="%s">%s</text:span>\n' % \
137 (s2, value[:-1], )
138 else:
139 s2 = self.rststyle('codeblock-comment')
140 s1 = '<text:span text:style-name="%s">%s</text:span>' % \
141 (s2, value, )
142 elif ttype == tokenclass.Name.Builtin:
143 s2 = self.rststyle('codeblock-name')
144 s1 = '<text:span text:style-name="%s">%s</text:span>' % \
145 (s2, value, )
146 else:
147 s1 = value
148 outfile.write(s1)
150 except ImportError, e:
151 pygments = None
154 # Is the PIL imaging library installed?
155 try:
156 import Image
157 except ImportError, exp:
158 Image = None
160 ## from IPython.Shell import IPShellEmbed
161 ## args = ['-pdb', '-pi1', 'In <\\#>: ', '-pi2', ' .\\D.: ',
162 ## '-po', 'Out<\\#>: ', '-nosep']
163 ## ipshell = IPShellEmbed(args,
164 ## banner = 'Entering IPython. Press Ctrl-D to exit.',
165 ## exit_msg = 'Leaving Interpreter, back to program.')
169 # ElementTree does not support getparent method (lxml does).
170 # This wrapper class and the following support functions provide
171 # that support for the ability to get the parent of an element.
173 if WhichElementTree == 'elementtree':
174 class _ElementInterfaceWrapper(etree._ElementInterface):
175 def __init__(self, tag, attrib=None):
176 etree._ElementInterface.__init__(self, tag, attrib)
177 if attrib is None:
178 attrib = {}
179 self.parent = None
180 def setparent(self, parent):
181 self.parent = parent
182 def getparent(self):
183 return self.parent
187 # Constants and globals
189 # Turn tracing on/off. See methods trace_visit_node/trace_depart_node.
190 DEBUG = 0
191 SPACES_PATTERN = re.compile(r'( +)')
192 TABS_PATTERN = re.compile(r'(\t+)')
193 FILL_PAT1 = re.compile(r'^ +')
194 FILL_PAT2 = re.compile(r' {2,}')
195 # Match a section number followed by 3 \xa0 bytes.
196 # Note that we actually check for the class "auto-toc" instead.
197 #SECTNUM_PAT = re.compile(r'^\d*(\.\d*)*\240\240\240')
199 TableStylePrefix = 'Table'
201 GENERATOR_DESC = 'Docutils.org/odtwriter'
203 NAME_SPACE_1 = 'urn:oasis:names:tc:opendocument:xmlns:office:1.0'
205 CONTENT_NAMESPACE_DICT = CNSD = {
206 # 'office:version': '1.0',
207 'chart': 'urn:oasis:names:tc:opendocument:xmlns:chart:1.0',
208 'dc': 'http://purl.org/dc/elements/1.1/',
209 'dom': 'http://www.w3.org/2001/xml-events',
210 'dr3d': 'urn:oasis:names:tc:opendocument:xmlns:dr3d:1.0',
211 'draw': 'urn:oasis:names:tc:opendocument:xmlns:drawing:1.0',
212 'fo': 'urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0',
213 'form': 'urn:oasis:names:tc:opendocument:xmlns:form:1.0',
214 'math': 'http://www.w3.org/1998/Math/MathML',
215 'meta': 'urn:oasis:names:tc:opendocument:xmlns:meta:1.0',
216 'number': 'urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0',
217 'office': NAME_SPACE_1,
218 'ooo': 'http://openoffice.org/2004/office',
219 'oooc': 'http://openoffice.org/2004/calc',
220 'ooow': 'http://openoffice.org/2004/writer',
221 'script': 'urn:oasis:names:tc:opendocument:xmlns:script:1.0',
222 'style': 'urn:oasis:names:tc:opendocument:xmlns:style:1.0',
223 'svg': 'urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0',
224 'table': 'urn:oasis:names:tc:opendocument:xmlns:table:1.0',
225 'text': 'urn:oasis:names:tc:opendocument:xmlns:text:1.0',
226 'xforms': 'http://www.w3.org/2002/xforms',
227 'xlink': 'http://www.w3.org/1999/xlink',
228 'xsd': 'http://www.w3.org/2001/XMLSchema',
229 'xsi': 'http://www.w3.org/2001/XMLSchema-instance',
232 STYLES_NAMESPACE_DICT = SNSD = {
233 # 'office:version': '1.0',
234 'chart': 'urn:oasis:names:tc:opendocument:xmlns:chart:1.0',
235 'dc': 'http://purl.org/dc/elements/1.1/',
236 'dom': 'http://www.w3.org/2001/xml-events',
237 'dr3d': 'urn:oasis:names:tc:opendocument:xmlns:dr3d:1.0',
238 'draw': 'urn:oasis:names:tc:opendocument:xmlns:drawing:1.0',
239 'fo': 'urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0',
240 'form': 'urn:oasis:names:tc:opendocument:xmlns:form:1.0',
241 'math': 'http://www.w3.org/1998/Math/MathML',
242 'meta': 'urn:oasis:names:tc:opendocument:xmlns:meta:1.0',
243 'number': 'urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0',
244 'office': NAME_SPACE_1,
245 'ooo': 'http://openoffice.org/2004/office',
246 'oooc': 'http://openoffice.org/2004/calc',
247 'ooow': 'http://openoffice.org/2004/writer',
248 'script': 'urn:oasis:names:tc:opendocument:xmlns:script:1.0',
249 'style': 'urn:oasis:names:tc:opendocument:xmlns:style:1.0',
250 'svg': 'urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0',
251 'table': 'urn:oasis:names:tc:opendocument:xmlns:table:1.0',
252 'text': 'urn:oasis:names:tc:opendocument:xmlns:text:1.0',
253 'xlink': 'http://www.w3.org/1999/xlink',
256 MANIFEST_NAMESPACE_DICT = MANNSD = {
257 'manifest': 'urn:oasis:names:tc:opendocument:xmlns:manifest:1.0',
260 META_NAMESPACE_DICT = METNSD = {
261 # 'office:version': '1.0',
262 'dc': 'http://purl.org/dc/elements/1.1/',
263 'meta': 'urn:oasis:names:tc:opendocument:xmlns:meta:1.0',
264 'office': NAME_SPACE_1,
265 'ooo': 'http://openoffice.org/2004/office',
266 'xlink': 'http://www.w3.org/1999/xlink',
269 MIME_TYPE = 'application/vnd.oasis.opendocument.text'
273 # Attribute dictionaries for use with ElementTree (not lxml), which
274 # does not support use of nsmap parameter on Element() and SubElement().
276 CONTENT_NAMESPACE_ATTRIB = {
277 'office:version': '1.0',
278 'xmlns:chart': 'urn:oasis:names:tc:opendocument:xmlns:chart:1.0',
279 'xmlns:dc': 'http://purl.org/dc/elements/1.1/',
280 'xmlns:dom': 'http://www.w3.org/2001/xml-events',
281 'xmlns:dr3d': 'urn:oasis:names:tc:opendocument:xmlns:dr3d:1.0',
282 'xmlns:draw': 'urn:oasis:names:tc:opendocument:xmlns:drawing:1.0',
283 'xmlns:fo': 'urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0',
284 'xmlns:form': 'urn:oasis:names:tc:opendocument:xmlns:form:1.0',
285 'xmlns:math': 'http://www.w3.org/1998/Math/MathML',
286 'xmlns:meta': 'urn:oasis:names:tc:opendocument:xmlns:meta:1.0',
287 'xmlns:number': 'urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0',
288 'xmlns:office': NAME_SPACE_1,
289 'xmlns:ooo': 'http://openoffice.org/2004/office',
290 'xmlns:oooc': 'http://openoffice.org/2004/calc',
291 'xmlns:ooow': 'http://openoffice.org/2004/writer',
292 'xmlns:script': 'urn:oasis:names:tc:opendocument:xmlns:script:1.0',
293 'xmlns:style': 'urn:oasis:names:tc:opendocument:xmlns:style:1.0',
294 'xmlns:svg': 'urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0',
295 'xmlns:table': 'urn:oasis:names:tc:opendocument:xmlns:table:1.0',
296 'xmlns:text': 'urn:oasis:names:tc:opendocument:xmlns:text:1.0',
297 'xmlns:xforms': 'http://www.w3.org/2002/xforms',
298 'xmlns:xlink': 'http://www.w3.org/1999/xlink',
299 'xmlns:xsd': 'http://www.w3.org/2001/XMLSchema',
300 'xmlns:xsi': 'http://www.w3.org/2001/XMLSchema-instance',
303 STYLES_NAMESPACE_ATTRIB = {
304 'office:version': '1.0',
305 'xmlns:chart': 'urn:oasis:names:tc:opendocument:xmlns:chart:1.0',
306 'xmlns:dc': 'http://purl.org/dc/elements/1.1/',
307 'xmlns:dom': 'http://www.w3.org/2001/xml-events',
308 'xmlns:dr3d': 'urn:oasis:names:tc:opendocument:xmlns:dr3d:1.0',
309 'xmlns:draw': 'urn:oasis:names:tc:opendocument:xmlns:drawing:1.0',
310 'xmlns:fo': 'urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0',
311 'xmlns:form': 'urn:oasis:names:tc:opendocument:xmlns:form:1.0',
312 'xmlns:math': 'http://www.w3.org/1998/Math/MathML',
313 'xmlns:meta': 'urn:oasis:names:tc:opendocument:xmlns:meta:1.0',
314 'xmlns:number': 'urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0',
315 'xmlns:office': NAME_SPACE_1,
316 'xmlns:ooo': 'http://openoffice.org/2004/office',
317 'xmlns:oooc': 'http://openoffice.org/2004/calc',
318 'xmlns:ooow': 'http://openoffice.org/2004/writer',
319 'xmlns:script': 'urn:oasis:names:tc:opendocument:xmlns:script:1.0',
320 'xmlns:style': 'urn:oasis:names:tc:opendocument:xmlns:style:1.0',
321 'xmlns:svg': 'urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0',
322 'xmlns:table': 'urn:oasis:names:tc:opendocument:xmlns:table:1.0',
323 'xmlns:text': 'urn:oasis:names:tc:opendocument:xmlns:text:1.0',
324 'xmlns:xlink': 'http://www.w3.org/1999/xlink',
327 MANIFEST_NAMESPACE_ATTRIB = {
328 'xmlns:manifest': 'urn:oasis:names:tc:opendocument:xmlns:manifest:1.0',
331 META_NAMESPACE_ATTRIB = {
332 'office:version': '1.0',
333 'xmlns:dc': 'http://purl.org/dc/elements/1.1/',
334 'xmlns:meta': 'urn:oasis:names:tc:opendocument:xmlns:meta:1.0',
335 'xmlns:office': NAME_SPACE_1,
336 'xmlns:ooo': 'http://openoffice.org/2004/office',
337 'xmlns:xlink': 'http://www.w3.org/1999/xlink',
342 # Functions
346 # ElementTree support functions.
347 # In order to be able to get the parent of elements, must use these
348 # instead of the functions with same name provided by ElementTree.
350 def Element(tag, attrib=None, nsmap=None, nsdict=CNSD):
351 if attrib is None:
352 attrib = {}
353 tag, attrib = fix_ns(tag, attrib, nsdict)
354 if WhichElementTree == 'lxml':
355 el = etree.Element(tag, attrib, nsmap=nsmap)
356 else:
357 el = _ElementInterfaceWrapper(tag, attrib)
358 return el
360 def SubElement(parent, tag, attrib=None, nsmap=None, nsdict=CNSD):
361 if attrib is None:
362 attrib = {}
363 tag, attrib = fix_ns(tag, attrib, nsdict)
364 if WhichElementTree == 'lxml':
365 el = etree.SubElement(parent, tag, attrib, nsmap=nsmap)
366 else:
367 el = _ElementInterfaceWrapper(tag, attrib)
368 parent.append(el)
369 el.setparent(parent)
370 return el
372 def fix_ns(tag, attrib, nsdict):
373 nstag = add_ns(tag, nsdict)
374 nsattrib = {}
375 for key, val in attrib.iteritems():
376 nskey = add_ns(key, nsdict)
377 nsattrib[nskey] = val
378 return nstag, nsattrib
380 def add_ns(tag, nsdict=CNSD):
381 if WhichElementTree == 'lxml':
382 nstag, name = tag.split(':')
383 ns = nsdict.get(nstag)
384 if ns is None:
385 raise RuntimeError, 'Invalid namespace prefix: %s' % nstag
386 tag = '{%s}%s' % (ns, name,)
387 #print '*** tag: "%s"' % tag
388 return tag
390 def ToString(et):
391 outstream = StringIO.StringIO()
392 et.write(outstream)
393 s1 = outstream.getvalue()
394 outstream.close()
395 return s1
397 def escape_cdata(text):
398 text = text.replace("&", "&amp;")
399 text = text.replace("<", "&lt;")
400 text = text.replace(">", "&gt;")
401 ascii = ''
402 for char in text:
403 if ord(char) >= ord("\x7f"):
404 ascii += "&#x%X;" % ( ord(char), )
405 else:
406 ascii += char
407 return ascii
411 # Classes
414 # Does this version of Docutils has Directive support?
415 if hasattr(rst, 'Directive'):
417 # Class to control syntax highlighting.
418 ## class SyntaxHighlight(rst.Directive):
419 ## required_arguments = 1
420 ## optional_arguments = 0
421 ## #
422 ## # See visit_field for code that processes the node created here.
423 ## def run(self):
424 ## arguments = ' '.join(self.arguments)
425 ## paragraph = nodes.paragraph(arguments, arguments)
426 ## field_body = nodes.field_body()
427 ## field_body += paragraph
428 ## paragraph = nodes.paragraph('syntaxhighlight', 'syntaxhighlight')
429 ## field_name = nodes.field_name()
430 ## field_name += paragraph
431 ## field = nodes.field()
432 ## field += field_name
433 ## field += field_body
434 ## return [field]
436 ## rst.directives.register_directive('sourcecode', SyntaxHighlight)
438 class SyntaxHighlightCodeBlock(rst.Directive):
439 required_arguments = 1
440 optional_arguments = 0
441 has_content = True
443 # See visit_literal_block for code that processes the node
444 # created here.
445 def run(self):
446 language = self.arguments[0]
447 code_block = nodes.literal_block(classes=["code-block", language],
448 language=language)
449 lines = self.content
450 content = '\n'.join(lines)
451 text_node = nodes.Text(content)
452 code_block.append(text_node)
453 # Mark this node for high-lighting so that visit_literal_block
454 # will be able to hight-light those produced here and
455 # *not* high-light regular literal blocks (:: in reST).
456 code_block['hilight'] = True
457 #import pdb; pdb.set_trace()
458 return [code_block]
460 rst.directives.register_directive('sourcecode', SyntaxHighlightCodeBlock)
461 rst.directives.register_directive('code', SyntaxHighlightCodeBlock)
465 # Register directives defined in a module named "odtwriter_plugins".
467 def load_plugins():
468 plugin_mod = None
469 count = 0
470 try:
471 name = 'odtwriter_plugins'
472 fp, pathname, description = imp.find_module(name)
473 plugin_mod = imp.load_module(name, fp, pathname, description)
474 #import odtwriter_plugins
475 #plugin_mod = odtwriter_plugins
476 except ImportError, e:
477 pass
478 if plugin_mod is None:
479 return count
480 klasses = inspect.getmembers(plugin_mod, inspect.isclass)
481 for klass in klasses:
482 if register_plugin(*klass):
483 count += 1
484 return count
486 def register_plugin(name, klass):
487 plugin_name = getattr(klass, 'plugin_name', None)
488 if plugin_name is not None:
489 rst.directives.register_directive(plugin_name, klass)
491 load_plugins()
494 WORD_SPLIT_PAT1 = re.compile(r'\b(\w*)\b\W*')
496 def split_words(line):
497 # We need whitespace at the end of the string for our regexpr.
498 line += ' '
499 words = []
500 pos1 = 0
501 mo = WORD_SPLIT_PAT1.search(line, pos1)
502 while mo is not None:
503 word = mo.groups()[0]
504 words.append(word)
505 pos1 = mo.end()
506 mo = WORD_SPLIT_PAT1.search(line, pos1)
507 return words
511 # Information about the indentation level for lists nested inside
512 # other contexts, e.g. dictionary lists.
513 class ListLevel(object):
514 def __init__(self, level, sibling_level=True, nested_level=True):
515 self.level = level
516 self.sibling_level = sibling_level
517 self.nested_level = nested_level
518 def set_sibling(self, sibling_level): self.sibling_level = sibling_level
519 def get_sibling(self): return self.sibling_level
520 def set_nested(self, nested_level): self.nested_level = nested_level
521 def get_nested(self): return self.nested_level
522 def set_level(self, level): self.level = level
523 def get_level(self): return self.level
526 class Writer(writers.Writer):
528 EXTENSION = '.odt'
530 supported = ('html', 'html4css1', 'xhtml')
531 """Formats this writer supports."""
533 default_stylesheet = 'styles' + EXTENSION
534 ## default_plugins_name = 'docutils_plugins'
536 default_stylesheet_path = utils.relative_path(
537 os.path.join(os.getcwd(), 'dummy'),
538 os.path.join(os.path.dirname(__file__), default_stylesheet))
540 default_template = 'template.txt'
542 default_template_path = utils.relative_path(
543 os.path.join(os.getcwd(), 'dummy'),
544 os.path.join(os.path.dirname(__file__), default_template))
546 ## settings_spec = (
547 ## 'ODF-Specific Options',
548 ## None,
549 ## (('Specify the template file (UTF-8 encoded). Default is "%s".'
550 ## % default_template_path,
551 ## ['--template'],
552 ## {'default': default_template_path, 'metavar': '<file>'}),
553 ## ('Specify a stylesheet URL, used verbatim. Overrides '
554 ## '--stylesheet-path.',
555 ## ['--stylesheet'],
556 ## {'metavar': '<URL>', 'overrides': 'stylesheet_path'}),
557 ## ('Specify a stylesheet file, relative to the current working '
558 ## 'directory. The path is adjusted relative to the output ODF '
559 ## 'file. Overrides --stylesheet. Default: "%s"'
560 ## % default_stylesheet_path,
561 ## ['--stylesheet-path'],
562 ## {'metavar': '<file>', 'overrides': 'stylesheet',
563 ## 'default': default_stylesheet_path}),
564 ## ('Specify the initial header level. Default is 1 for "<h1>". '
565 ## 'Does not affect document title & subtitle (see --no-doc-title).',
566 ## ['--initial-header-level'],
567 ## {'choices': '1 2 3 4 5 6'.split(), 'default': '1',
568 ## 'metavar': '<level>'}),
569 ## ('Specify the maximum width (in characters) for one-column field '
570 ## 'names. Longer field names will span an entire row of the table '
571 ## 'used to render the field list. Default is 14 characters. '
572 ## 'Use 0 for "no limit".',
573 ## ['--field-name-limit'],
574 ## {'default': 14, 'metavar': '<level>',
575 ## 'validator': frontend.validate_nonnegative_int}),
576 ## ('Specify the maximum width (in characters) for options in option '
577 ## 'lists. Longer options will span an entire row of the table used '
578 ## 'to render the option list. Default is 14 characters. '
579 ## 'Use 0 for "no limit".',
580 ## ['--option-limit'],
581 ## {'default': 14, 'metavar': '<level>',
582 ## 'validator': frontend.validate_nonnegative_int}),
583 ## ('Format for footnote references: one of "superscript" or '
584 ## '"brackets". Default is "brackets".',
585 ## ['--footnote-references'],
586 ## {'choices': ['superscript', 'brackets'], 'default': 'brackets',
587 ## 'metavar': '<format>',
588 ## 'overrides': 'trim_footnote_reference_space'}),
589 ## ('Format for block quote attributions: one of "dash" (em-dash '
590 ## 'prefix), "parentheses"/"parens", or "none". Default is "dash".',
591 ## ['--attribution'],
592 ## {'choices': ['dash', 'parentheses', 'parens', 'none'],
593 ## 'default': 'dash', 'metavar': '<format>'}),
594 ## ('Remove extra vertical whitespace between items of "simple" bullet '
595 ## 'lists and enumerated lists. Default: enabled.',
596 ## ['--compact-lists'],
597 ## {'default': 1, 'action': 'store_true',
598 ## 'validator': frontend.validate_boolean}),
599 ## ('Disable compact simple bullet and enumerated lists.',
600 ## ['--no-compact-lists'],
601 ## {'dest': 'compact_lists', 'action': 'store_false'}),
602 ## ('Remove extra vertical whitespace between items of simple field '
603 ## 'lists. Default: enabled.',
604 ## ['--compact-field-lists'],
605 ## {'default': 1, 'action': 'store_true',
606 ## 'validator': frontend.validate_boolean}),
607 ## ('Disable compact simple field lists.',
608 ## ['--no-compact-field-lists'],
609 ## {'dest': 'compact_field_lists', 'action': 'store_false'}),
610 ## ('Omit the XML declaration. Use with caution.',
611 ## ['--no-xml-declaration'],
612 ## {'dest': 'xml_declaration', 'default': 1, 'action': 'store_false',
613 ## 'validator': frontend.validate_boolean}),
614 ## ('Obfuscate email addresses to confuse harvesters while still '
615 ## 'keeping email links usable with standards-compliant browsers.',
616 ## ['--cloak-email-addresses'],
617 ## {'action': 'store_true', 'validator': frontend.validate_boolean}),
618 ## ))
620 settings_spec = (
621 'ODF-Specific Options',
622 None,
624 ('Specify a stylesheet URL, used verbatim. Overrides '
625 '--stylesheet-path.',
626 ['--stylesheet'],
627 {'metavar': '<URL>', 'overrides': 'stylesheet_path'}),
628 ('Specify a stylesheet file, relative to the current working '
629 'directory. The path is adjusted relative to the output ODF '
630 'file. Overrides --stylesheet. Default: "%s"'
631 % default_stylesheet_path,
632 ['--stylesheet-path'],
633 {'metavar': '<file>', 'overrides': 'stylesheet',
634 'default': default_stylesheet_path}),
635 ('Specify a configuration/mapping file relative to the '
636 'current working '
637 'directory for additional ODF options. '
638 'In particular, this file may contain a section named '
639 '"Formats" that maps default style names to '
640 'names to be used in the resulting output file allowing for '
641 'adhering to external standards. '
642 'For more info and the format of the configuration/mapping file, '
643 'see the odtwriter doc.',
644 ['--odf-config-file'],
645 {'metavar': '<file>'}),
646 ('Obfuscate email addresses to confuse harvesters while still '
647 'keeping email links usable with standards-compliant browsers.',
648 ['--cloak-email-addresses'],
649 {'default': False, 'action': 'store_true',
650 'validator': frontend.validate_boolean}),
651 ('Specify the thickness of table borders in thousands of a cm. '
652 'Default is 35.',
653 ['--table-border-thickness'],
654 {'default': 35,
655 'validator': frontend.validate_nonnegative_int}),
656 ('Add syntax highlighting in literal code blocks.'
657 'Default is No. Requires installation of Pygments.',
658 ['--add-syntax-highlighting'],
659 {'default': False, 'action': 'store_true',
660 'validator': frontend.validate_boolean}),
661 ('Create sections for headers. '
662 'Default is Yes.',
663 ['--create-sections'],
664 {'default': True, 'action': 'store_true',
665 'validator': frontend.validate_boolean}),
666 ('Create no sections for headers.',
667 ['--no-create-sections'],
668 {'action': 'store_false',
669 'dest': 'create_sections',
670 'validator': frontend.validate_boolean}),
671 ('Create links. '
672 'Default is No.',
673 ['--create-links'],
674 {'default': False, 'action': 'store_true',
675 'validator': frontend.validate_boolean}),
676 ('Create no links.',
677 ['--no-create-links'],
678 {'action': 'store_false',
679 'dest': 'create_links',
680 'validator': frontend.validate_boolean}),
681 ## ('Specify a plugins/directives module (without .py). '
682 ## 'Default: "%s"' % default_plugins_name,
683 ## ['--plugins-module-name'],
684 ## {'default': default_plugins_name}),
687 settings_defaults = {
688 'output_encoding_error_handler': 'xmlcharrefreplace',
691 relative_path_settings = (
692 'stylesheet_path',
695 config_section = 'opendocument odf writer'
696 config_section_dependencies = (
697 'writers',
700 def __init__(self):
701 writers.Writer.__init__(self)
702 self.translator_class = ODFTranslator
704 def translate(self):
705 #import pdb; pdb.set_trace()
706 self.settings = self.document.settings
707 self.visitor = self.translator_class(self.document)
708 self.document.walkabout(self.visitor)
709 self.visitor.add_doc_title()
710 self.assemble_my_parts()
711 self.output = self.parts['whole']
713 def assemble_my_parts(self):
714 """Assemble the `self.parts` dictionary. Extend in subclasses.
716 #ipshell('At assemble_parts')
717 writers.Writer.assemble_parts(self)
718 f = tempfile.NamedTemporaryFile()
719 zfile = zipfile.ZipFile(f, 'w', zipfile.ZIP_DEFLATED)
720 self.write_zip_str(zfile, 'content.xml', self.visitor.content_astext())
721 self.write_zip_str(zfile, 'mimetype', MIME_TYPE)
722 s1 = self.create_manifest()
723 self.write_zip_str(zfile, 'META-INF/manifest.xml', s1)
724 s1 = self.create_meta()
725 self.write_zip_str(zfile, 'meta.xml', s1)
726 s1 = self.get_stylesheet()
727 self.write_zip_str(zfile, 'styles.xml', s1)
728 self.store_embedded_files(zfile)
729 zfile.close()
730 f.seek(0)
731 whole = f.read()
732 f.close()
733 self.parts['whole'] = whole
734 self.parts['encoding'] = self.document.settings.output_encoding
735 self.parts['version'] = docutils.__version__
737 def write_zip_str(self, zfile, name, bytes):
738 localtime = time.localtime(time.time())
739 zinfo = zipfile.ZipInfo(name, localtime)
740 # Add some standard UNIX file access permissions (-rw-r--r--).
741 zinfo.external_attr = (0x81a4 & 0xFFFF) << 16L
742 zinfo.compress_type = zipfile.ZIP_DEFLATED
743 zfile.writestr(zinfo, bytes)
745 def store_embedded_files(self, zfile):
746 embedded_files = self.visitor.get_embedded_file_list()
747 for source, destination in embedded_files:
748 if source is None:
749 continue
750 try:
751 # encode/decode
752 destination1 = destination.decode('latin-1').encode('utf-8')
753 zfile.write(source, destination1, zipfile.ZIP_STORED)
754 except OSError, e:
755 print "Error: Can't open file %s." % (source, )
757 def get_stylesheet(self):
758 """Retrieve the stylesheet from either a .xml file or from
759 a .odt (zip) file. Return the content as a string.
761 stylespath = utils.get_stylesheet_reference(self.settings,
762 os.path.join(os.getcwd(), 'dummy'))
763 ext = os.path.splitext(stylespath)[1]
764 if ext == '.xml':
765 stylesfile = open(stylespath, 'r')
766 s1 = stylesfile.read()
767 stylesfile.close()
768 elif ext == self.EXTENSION:
769 zfile = zipfile.ZipFile(stylespath, 'r')
770 s1 = zfile.read('styles.xml')
771 zfile.close()
772 else:
773 raise RuntimeError, 'stylesheet path must be ' + self.EXTENSION + ' or .xml file.'
774 s1 = self.visitor.setup_page(s1)
775 return s1
777 def assemble_parts(self):
778 pass
780 def create_manifest(self):
781 if WhichElementTree == 'lxml':
782 root = Element('manifest:manifest',
783 nsmap=MANIFEST_NAMESPACE_DICT,
784 nsdict=MANIFEST_NAMESPACE_DICT,
786 else:
787 root = Element('manifest:manifest',
788 attrib=MANIFEST_NAMESPACE_ATTRIB,
789 nsdict=MANIFEST_NAMESPACE_DICT,
791 doc = etree.ElementTree(root)
792 SubElement(root, 'manifest:file-entry', attrib={
793 'manifest:media-type': MIME_TYPE,
794 'manifest:full-path': '/',
795 }, nsdict=MANNSD)
796 SubElement(root, 'manifest:file-entry', attrib={
797 'manifest:media-type': 'text/xml',
798 'manifest:full-path': 'content.xml',
799 }, nsdict=MANNSD)
800 SubElement(root, 'manifest:file-entry', attrib={
801 'manifest:media-type': 'text/xml',
802 'manifest:full-path': 'styles.xml',
803 }, nsdict=MANNSD)
804 SubElement(root, 'manifest:file-entry', attrib={
805 'manifest:media-type': 'text/xml',
806 'manifest:full-path': 'meta.xml',
807 }, nsdict=MANNSD)
808 s1 = ToString(doc)
809 doc = minidom.parseString(s1)
810 s1 = doc.toprettyxml(' ')
811 return s1
813 def create_meta(self):
814 if WhichElementTree == 'lxml':
815 root = Element('office:document-meta',
816 nsmap=META_NAMESPACE_DICT,
817 nsdict=META_NAMESPACE_DICT,
819 else:
820 root = Element('office:document-meta',
821 attrib=META_NAMESPACE_ATTRIB,
822 nsdict=META_NAMESPACE_DICT,
824 doc = etree.ElementTree(root)
825 root = SubElement(root, 'office:meta', nsdict=METNSD)
826 el1 = SubElement(root, 'meta:generator', nsdict=METNSD)
827 el1.text = 'Docutils/rst2odf.py/%s' % (VERSION, )
828 s1 = os.environ.get('USER', '')
829 el1 = SubElement(root, 'meta:initial-creator', nsdict=METNSD)
830 el1.text = s1
831 s2 = time.strftime('%Y-%m-%dT%H:%M:%S', time.localtime())
832 el1 = SubElement(root, 'meta:creation-date', nsdict=METNSD)
833 el1.text = s2
834 el1 = SubElement(root, 'dc:creator', nsdict=METNSD)
835 el1.text = s1
836 el1 = SubElement(root, 'dc:date', nsdict=METNSD)
837 el1.text = s2
838 el1 = SubElement(root, 'dc:language', nsdict=METNSD)
839 el1.text = 'en-US'
840 el1 = SubElement(root, 'meta:editing-cycles', nsdict=METNSD)
841 el1.text = '1'
842 el1 = SubElement(root, 'meta:editing-duration', nsdict=METNSD)
843 el1.text = 'PT00M01S'
844 title = self.visitor.get_title()
845 el1 = SubElement(root, 'dc:title', nsdict=METNSD)
846 if title:
847 el1.text = title
848 else:
849 el1.text = '[no title]'
850 meta_dict = self.visitor.get_meta_dict()
851 keywordstr = meta_dict.get('keywords')
852 if keywordstr is not None:
853 keywords = split_words(keywordstr)
854 for keyword in keywords:
855 el1 = SubElement(root, 'meta:keyword', nsdict=METNSD)
856 el1.text = keyword
857 description = meta_dict.get('description')
858 if description is not None:
859 el1 = SubElement(root, 'dc:description', nsdict=METNSD)
860 el1.text = description
861 s1 = ToString(doc)
862 #doc = minidom.parseString(s1)
863 #s1 = doc.toprettyxml(' ')
864 return s1
866 # class ODFTranslator(nodes.SparseNodeVisitor):
868 class ODFTranslator(nodes.GenericNodeVisitor):
870 ## used_styles = (
871 ## 'attribution', 'blockindent', 'blockquote', 'blockquote-bulletitem',
872 ## 'blockquote-bulletlist', 'blockquote-enumitem', 'blockquote-enumlist',
873 ## 'bulletitem', 'bulletlist', 'caption', 'centeredtextbody', 'codeblock',
874 ## 'codeblock-classname', 'codeblock-comment', 'codeblock-functionname',
875 ## 'codeblock-keyword', 'codeblock-name', 'codeblock-number',
876 ## 'codeblock-operator', 'codeblock-string', 'emphasis', 'enumitem',
877 ## 'enumlist', 'epigraph', 'epigraph-bulletitem', 'epigraph-bulletlist',
878 ## 'epigraph-enumitem', 'epigraph-enumlist', 'footer', 'footnote',
879 ## 'header', 'highlights', 'highlights-bulletitem',
880 ## 'highlights-bulletlist', 'highlights-enumitem', 'highlights-enumlist',
881 ## 'horizontalline', 'inlineliteral', 'lineblock', 'quotation', 'rubric',
882 ## 'strong', 'table-title', 'textbody', 'tocbulletlist', 'tocenumlist',
883 ## 'heading%d', 'admon-%s-hdr', 'admon-%s-body', 'tableoption',
884 ## 'tableoption.%c', 'tableoption.%c%d', 'Table%d', 'Table%d.%c',
885 ## 'Table%d.%c%d',
886 ## )
888 used_styles = (
889 'attribution', 'blockindent', 'blockquote', 'blockquote-bulletitem',
890 'blockquote-bulletlist', 'blockquote-enumitem', 'blockquote-enumlist',
891 'bulletitem', 'bulletlist', 'caption', 'centeredtextbody', 'codeblock',
892 'codeblock-classname', 'codeblock-comment', 'codeblock-functionname',
893 'codeblock-keyword', 'codeblock-name', 'codeblock-number',
894 'codeblock-operator', 'codeblock-string', 'emphasis', 'enumitem',
895 'enumlist', 'epigraph', 'epigraph-bulletitem', 'epigraph-bulletlist',
896 'epigraph-enumitem', 'epigraph-enumlist', 'footer',
897 'footnote', 'citation',
898 'header', 'highlights', 'highlights-bulletitem',
899 'highlights-bulletlist', 'highlights-enumitem', 'highlights-enumlist',
900 'horizontalline', 'inlineliteral', 'lineblock', 'quotation', 'rubric',
901 'strong', 'table-title', 'textbody', 'tocbulletlist', 'tocenumlist',
902 'title',
903 'subtitle',
904 'heading1',
905 'heading2',
906 'heading3',
907 'heading4',
908 'heading5',
909 'heading6',
910 'heading7',
911 'admon-attention-hdr',
912 'admon-attention-body',
913 'admon-caution-hdr',
914 'admon-caution-body',
915 'admon-danger-hdr',
916 'admon-danger-body',
917 'admon-error-hdr',
918 'admon-error-body',
919 'admon-generic-hdr',
920 'admon-generic-body',
921 'admon-hint-hdr',
922 'admon-hint-body',
923 'admon-important-hdr',
924 'admon-important-body',
925 'admon-note-hdr',
926 'admon-note-body',
927 'admon-tip-hdr',
928 'admon-tip-body',
929 'admon-warning-hdr',
930 'admon-warning-body',
931 'tableoption',
932 'tableoption.%c', 'tableoption.%c%d', 'Table%d', 'Table%d.%c',
933 'Table%d.%c%d',
936 def __init__(self, document):
937 #nodes.SparseNodeVisitor.__init__(self, document)
938 nodes.GenericNodeVisitor.__init__(self, document)
939 self.settings = document.settings
940 self.format_map = { }
941 if self.settings.odf_config_file:
942 from ConfigParser import ConfigParser
944 parser = ConfigParser()
945 parser.read(self.settings.odf_config_file)
946 for ( rststyle, format, ) in parser.items("Formats"):
947 if rststyle not in self.used_styles:
948 print '***'
949 print ('*** Warning: Style "%s" '
950 'is not a style used by odtwriter.' % (
951 rststyle, ))
952 print '***'
953 #raise RuntimeError, 'Unused style "%s"' % ( rststyle, )
954 self.format_map[rststyle] = format
955 self.section_level = 0
956 self.section_count = 0
957 # Create ElementTree content and styles documents.
958 if WhichElementTree == 'lxml':
959 root = Element(
960 'office:document-content',
961 nsmap=CONTENT_NAMESPACE_DICT,
963 else:
964 root = Element(
965 'office:document-content',
966 attrib=CONTENT_NAMESPACE_ATTRIB,
968 self.content_tree = etree.ElementTree(element=root)
969 self.current_element = root
970 SubElement(root, 'office:scripts')
971 SubElement(root, 'office:font-face-decls')
972 el = SubElement(root, 'office:automatic-styles')
973 self.automatic_styles = el
974 el = SubElement(root, 'office:body')
975 el = self.generate_content_element(el)
976 self.current_element = el
977 self.body_text_element = el
978 # test styles
979 ## if WhichElementTree == 'lxml':
980 ## root = Element(
981 ## 'office:document-styles',
982 ## nsmap=STYLES_NAMESPACE_DICT,
983 ## nsdict=STYLES_NAMESPACE_DICT,
984 ## )
985 ## else:
986 ## root = Element('office:document-styles',
987 ## attrib=STYLES_NAMESPACE_ATTRIB,
988 ## nsdict=STYLES_NAMESPACE_DICT,
989 ## )
990 ## self.styles_tree = etree.ElementTree(element=root)
991 self.paragraph_style_stack = [self.rststyle('textbody'), ]
992 self.list_style_stack = []
993 self.table_count = 0
994 self.column_count = ord('A') - 1
995 self.trace_level = -1
996 self.optiontablestyles_generated = False
997 self.field_name = None
998 self.field_element = None
999 self.title = None
1000 self.image_count = 0
1001 self.image_style_count = 0
1002 self.image_dict = {}
1003 self.embedded_file_list = []
1004 self.syntaxhighlighting = 1
1005 self.syntaxhighlight_lexer = 'python'
1006 self.header_content = []
1007 self.footer_content = []
1008 self.in_header = False
1009 self.in_footer = False
1010 self.blockstyle = ''
1011 self.in_table_of_contents = False
1012 self.footnote_dict = {}
1013 self.footnote_found = False
1014 self.pending_ids = [ ]
1015 self.in_paragraph = False
1016 self.found_doc_title = False
1017 self.bumped_list_level_stack = []
1018 self.meta_dict = {}
1019 self.in_footnote = False
1020 self.in_citation = None
1022 def add_doc_title(self):
1023 text = self.settings.title
1024 if text:
1025 self.title = text
1026 if not self.found_doc_title:
1027 el = Element('text:p', attrib = {
1028 'text:style-name': self.rststyle('title'),
1030 el.text = text
1031 self.body_text_element.insert(0, el)
1033 def rststyle(self, name, parameters=( )):
1035 Returns the style name to use for the given style.
1037 If `parameters` is given `name` must contain a matching number of ``%`` and
1038 is used as a format expression with `parameters` as the value.
1040 ## template = self.format_map.get(name, 'rststyle-%s' % name)
1041 ## return template % parameters
1042 name1 = name % parameters
1043 stylename = self.format_map.get(name1, 'rststyle-%s' % name1)
1044 return stylename
1046 def generate_content_element(self, root):
1047 return SubElement(root, 'office:text')
1049 def setup_page(self, content):
1050 root_el = etree.fromstring(content)
1051 self.setup_paper(root_el)
1052 if len(self.header_content) > 0 or len(self.footer_content) > 0:
1053 self.add_header_footer(root_el)
1054 new_content = etree.tostring(root_el)
1055 return new_content
1057 def setup_paper(self, root_el):
1058 try:
1059 fin = os.popen("paperconf -s")
1060 w, h = map(float, fin.read().split())
1061 fin.close()
1062 except:
1063 w, h = 612, 792 # default to Letter
1064 def walk(el):
1065 if el.tag == "{%s}page-layout-properties" % SNSD["style"] and \
1066 not el.attrib.has_key("{%s}page-width" % SNSD["fo"]):
1067 el.attrib["{%s}page-width" % SNSD["fo"]] = "%.3fpt" % w
1068 el.attrib["{%s}page-height" % SNSD["fo"]] = "%.3fpt" % h
1069 el.attrib["{%s}margin-left" % SNSD["fo"]] = \
1070 el.attrib["{%s}margin-right" % SNSD["fo"]] = \
1071 "%.3fpt" % (.1 * w)
1072 el.attrib["{%s}margin-top" % SNSD["fo"]] = \
1073 el.attrib["{%s}margin-bottom" % SNSD["fo"]] = \
1074 "%.3fpt" % (.1 * h)
1075 else:
1076 for subel in el.getchildren(): walk(subel)
1077 walk(root_el)
1079 def add_header_footer(self, root_el):
1080 path = '{%s}master-styles' % (NAME_SPACE_1, )
1081 master_el = root_el.find(path)
1082 if master_el is None:
1083 return
1084 path = '{%s}master-page' % (SNSD['style'], )
1085 master_el = master_el.find(path)
1086 if master_el is None:
1087 return
1088 el1 = master_el
1089 if len(self.header_content) > 0:
1090 if WhichElementTree == 'lxml':
1091 el2 = SubElement(el1, 'style:header', nsdict=SNSD)
1092 else:
1093 el2 = SubElement(el1, 'style:header',
1094 attrib=STYLES_NAMESPACE_ATTRIB,
1095 nsdict=STYLES_NAMESPACE_DICT,
1097 for el in self.header_content:
1098 attrkey = add_ns('text:style-name', nsdict=SNSD)
1099 el.attrib[attrkey] = self.rststyle('header')
1100 el2.append(el)
1101 if len(self.footer_content) > 0:
1102 if WhichElementTree == 'lxml':
1103 el2 = SubElement(el1, 'style:footer', nsdict=SNSD)
1104 else:
1105 el2 = SubElement(el1, 'style:footer',
1106 attrib=STYLES_NAMESPACE_ATTRIB,
1107 nsdict=STYLES_NAMESPACE_DICT,
1109 for el in self.footer_content:
1110 attrkey = add_ns('text:style-name', nsdict=SNSD)
1111 el.attrib[attrkey] = self.rststyle('footer')
1112 el2.append(el)
1113 #new_tree = etree.ElementTree(root_el)
1114 #new_content = ToString(new_tree)
1116 def astext(self):
1117 root = self.content_tree.getroot()
1118 et = etree.ElementTree(root)
1119 s1 = ToString(et)
1120 return s1
1122 def content_astext(self):
1123 return self.astext()
1125 # test styles
1126 ## def styles_astext(self):
1127 ## root = self.styles_tree.getroot()
1128 ## et = etree.ElementTree(root)
1129 ## s1 = ToString(et)
1130 ## return s1
1132 def set_title(self, title): self.title = title
1133 def get_title(self): return self.title
1134 def set_embedded_file_list(self, embedded_file_list):
1135 self.embedded_file_list = embedded_file_list
1136 def get_embedded_file_list(self): return self.embedded_file_list
1137 def get_meta_dict(self): return self.meta_dict
1140 # Utility methods
1142 def append_child(self, tag, attrib=None, parent=None):
1143 if parent is None:
1144 parent = self.current_element
1145 if attrib is None:
1146 el = SubElement(parent, tag)
1147 else:
1148 el = SubElement(parent, tag, attrib)
1149 return el
1151 def append_p(self, style, text=None):
1152 result = self.append_child('text:p', attrib={
1153 'text:style-name': self.rststyle(style)})
1154 self.append_pending_ids(result)
1155 if text is not None:
1156 result.text = text
1157 return result
1159 def append_pending_ids(self, el):
1160 if self.settings.create_links:
1161 for id in self.pending_ids:
1162 SubElement(el, 'text:reference-mark', attrib={
1163 'text:name': id})
1164 self.pending_ids = [ ]
1166 def set_current_element(self, el):
1167 self.current_element = el
1169 def set_to_parent(self):
1170 self.current_element = self.current_element.getparent()
1172 def generate_labeled_block(self, node, label):
1173 el = self.append_p('textbody')
1174 el1 = SubElement(el, 'text:span',
1175 attrib={'text:style-name': self.rststyle('strong')})
1176 el1.text = label
1177 el = self.append_p('blockindent')
1178 return el
1180 def generate_labeled_line(self, node, label):
1181 el = self.append_p('textbody')
1182 el1 = SubElement(el, 'text:span',
1183 attrib={'text:style-name': self.rststyle('strong')})
1184 el1.text = label
1185 el1.tail = node.astext()
1186 return el
1188 def encode(self, text):
1189 text = text.replace(u'\u00a0', " ")
1190 return text
1192 def trace_visit_node(self, node):
1193 if DEBUG >= 1:
1194 self.trace_level += 1
1195 self._trace_show_level(self.trace_level)
1196 if DEBUG >= 2:
1197 print '(visit_%s) node: %s' % (node.tagname, node.astext(), )
1198 else:
1199 print '(visit_%s)' % node.tagname
1201 def trace_depart_node(self, node):
1202 if not DEBUG:
1203 return
1204 self._trace_show_level(self.trace_level)
1205 print '(depart_%s)' % node.tagname
1206 self.trace_level -= 1
1208 def _trace_show_level(self, level):
1209 for idx in range(level):
1210 print ' ',
1213 # Visitor functions
1215 # In alphabetic order, more or less.
1216 # See docutils.docutils.nodes.node_class_names.
1219 def dispatch_visit(self, node):
1220 """Override to catch basic attributes which many nodes have."""
1221 self.handle_basic_atts(node)
1222 nodes.GenericNodeVisitor.dispatch_visit(self, node)
1224 def handle_basic_atts(self, node):
1225 if isinstance(node, nodes.Element) and node['ids']:
1226 self.pending_ids += node['ids']
1228 def default_visit(self, node):
1229 #ipshell('At default_visit')
1230 print 'missing visit_%s' % (node.tagname, )
1232 def default_departure(self, node):
1233 print 'missing depart_%s' % (node.tagname, )
1235 def visit_Text(self, node):
1236 #ipshell('At visit_Text')
1237 # Skip nodes whose text has been processed in parent nodes.
1238 if isinstance(node.parent, docutils.nodes.literal_block):
1239 #isinstance(node.parent, docutils.nodes.term) or \
1240 #isinstance(node.parent, docutils.nodes.definition):
1241 return
1242 text = node.astext()
1243 # Are we in mixed content? If so, add the text to the
1244 # etree tail of the previous sibling element.
1245 if len(self.current_element.getchildren()) > 0:
1246 #print '*** (visit_Text) 1. text: %s' % text
1247 if self.current_element.getchildren()[-1].tail:
1248 self.current_element.getchildren()[-1].tail += text
1249 else:
1250 self.current_element.getchildren()[-1].tail = text
1251 else:
1252 if self.current_element.text:
1253 self.current_element.text += text
1254 else:
1255 self.current_element.text = text
1257 def depart_Text(self, node):
1258 pass
1261 # Pre-defined fields
1264 def visit_address(self, node):
1265 #ipshell('At visit_address')
1266 el = self.generate_labeled_block(node, 'Address: ')
1267 self.set_current_element(el)
1269 def depart_address(self, node):
1270 self.set_to_parent()
1272 def visit_author(self, node):
1273 if isinstance(node.parent, nodes.authors):
1274 el = self.append_p('blockindent')
1275 else:
1276 el = self.generate_labeled_block(node, 'Author: ')
1277 self.set_current_element(el)
1279 def depart_author(self, node):
1280 self.set_to_parent()
1282 def visit_authors(self, node):
1283 #ipshell('At visit_authors')
1284 #self.trace_visit_node(node)
1285 label = 'Authors:'
1286 el = self.append_p('textbody')
1287 el1 = SubElement(el, 'text:span',
1288 attrib={'text:style-name': self.rststyle('strong')})
1289 el1.text = label
1291 def depart_authors(self, node):
1292 #self.trace_depart_node(node)
1293 pass
1295 def visit_contact(self, node):
1296 el = self.generate_labeled_block(node, 'Contact: ')
1297 self.set_current_element(el)
1299 def depart_contact(self, node):
1300 self.set_to_parent()
1302 def visit_copyright(self, node):
1303 el = self.generate_labeled_block(node, 'Copyright: ')
1304 self.set_current_element(el)
1306 def depart_copyright(self, node):
1307 self.set_to_parent()
1309 def visit_date(self, node):
1310 self.generate_labeled_line(node, 'Date: ')
1312 def depart_date(self, node):
1313 pass
1315 def visit_organization(self, node):
1316 el = self.generate_labeled_block(node, 'Organization: ')
1317 self.set_current_element(el)
1319 def depart_organization(self, node):
1320 self.set_to_parent()
1322 def visit_status(self, node):
1323 el = self.generate_labeled_block(node, 'Status: ')
1324 self.set_current_element(el)
1326 def depart_status(self, node):
1327 self.set_to_parent()
1329 def visit_revision(self, node):
1330 self.generate_labeled_line(node, 'Revision: ')
1332 def depart_revision(self, node):
1333 pass
1335 def visit_version(self, node):
1336 el = self.generate_labeled_line(node, 'Version: ')
1337 #self.set_current_element(el)
1339 def depart_version(self, node):
1340 #self.set_to_parent()
1341 pass
1343 def visit_attribution(self, node):
1344 #ipshell('At visit_attribution')
1345 el = self.append_p('attribution', node.astext())
1347 def depart_attribution(self, node):
1348 #ipshell('At depart_attribution')
1349 pass
1351 def visit_block_quote(self, node):
1352 #ipshell('At visit_block_quote')
1353 if 'epigraph' in node.attributes['classes']:
1354 self.paragraph_style_stack.append(self.rststyle('epigraph'))
1355 self.blockstyle = self.rststyle('epigraph')
1356 elif 'highlights' in node.attributes['classes']:
1357 self.paragraph_style_stack.append(self.rststyle('highlights'))
1358 self.blockstyle = self.rststyle('highlights')
1359 else:
1360 self.paragraph_style_stack.append(self.rststyle('blockquote'))
1361 self.blockstyle = self.rststyle('blockquote')
1363 def depart_block_quote(self, node):
1364 self.paragraph_style_stack.pop()
1365 self.blockstyle = ''
1367 def visit_bullet_list(self, node):
1368 #ipshell('At visit_bullet_list')
1369 if self.in_table_of_contents:
1370 if node.has_key('classes') and \
1371 'auto-toc' in node.attributes['classes']:
1372 el = SubElement(self.current_element, 'text:list', attrib={
1373 'text:style-name': self.rststyle('tocenumlist'),
1375 self.list_style_stack.append(self.rststyle('enumitem'))
1376 else:
1377 el = SubElement(self.current_element, 'text:list', attrib={
1378 'text:style-name': self.rststyle('tocbulletlist'),
1380 self.list_style_stack.append(self.rststyle('bulletitem'))
1381 else:
1382 if self.blockstyle == self.rststyle('blockquote'):
1383 el = SubElement(self.current_element, 'text:list', attrib={
1384 'text:style-name': self.rststyle('blockquote-bulletlist'),
1386 self.list_style_stack.append(self.rststyle('blockquote-bulletitem'))
1387 elif self.blockstyle == self.rststyle('highlights'):
1388 el = SubElement(self.current_element, 'text:list', attrib={
1389 'text:style-name': self.rststyle('highlights-bulletlist'),
1391 self.list_style_stack.append(self.rststyle('highlights-bulletitem'))
1392 elif self.blockstyle == self.rststyle('epigraph'):
1393 el = SubElement(self.current_element, 'text:list', attrib={
1394 'text:style-name': self.rststyle('epigraph-bulletlist'),
1396 self.list_style_stack.append(self.rststyle('epigraph-bulletitem'))
1397 else:
1398 el = SubElement(self.current_element, 'text:list', attrib={
1399 'text:style-name': self.rststyle('bulletlist'),
1401 self.list_style_stack.append(self.rststyle('bulletitem'))
1402 self.set_current_element(el)
1404 def depart_bullet_list(self, node):
1405 self.set_to_parent()
1406 self.list_style_stack.pop()
1408 def visit_caption(self, node):
1409 raise nodes.SkipChildren()
1410 pass
1412 def depart_caption(self, node):
1413 pass
1415 def visit_comment(self, node):
1416 #ipshell('At visit_comment')
1417 el = self.append_p('textbody')
1418 el1 = SubElement(el, 'office:annotation', attrib={})
1419 el2 = SubElement(el1, 'text:p', attrib={})
1420 el2.text = node.astext()
1422 def depart_comment(self, node):
1423 pass
1425 def visit_compound(self, node):
1426 # The compound directive currently receives no special treatment.
1427 pass
1429 def depart_compound(self, node):
1430 pass
1432 def visit_container(self, node):
1433 styles = node.attributes.get('classes', ())
1434 if len(styles) > 0:
1435 self.paragraph_style_stack.append(styles[0])
1437 def depart_container(self, node):
1438 #ipshell('At depart_container')
1439 styles = node.attributes.get('classes', ())
1440 if len(styles) > 0:
1441 self.paragraph_style_stack.pop()
1443 def visit_decoration(self, node):
1444 #global DEBUG
1445 #ipshell('At visit_decoration')
1446 #DEBUG = 1
1447 #self.trace_visit_node(node)
1448 pass
1450 def depart_decoration(self, node):
1451 #ipshell('At depart_decoration')
1452 pass
1454 def visit_definition(self, node):
1455 self.paragraph_style_stack.append(self.rststyle('blockindent'))
1456 self.bumped_list_level_stack.append(ListLevel(1))
1458 def depart_definition(self, node):
1459 self.paragraph_style_stack.pop()
1460 self.bumped_list_level_stack.pop()
1462 def visit_definition_list(self, node):
1463 pass
1465 def depart_definition_list(self, node):
1466 pass
1468 def visit_definition_list_item(self, node):
1469 pass
1471 def depart_definition_list_item(self, node):
1472 pass
1474 def visit_term(self, node):
1475 #ipshell('At visit_term')
1476 el = self.append_p('textbody')
1477 el1 = SubElement(el, 'text:span',
1478 attrib={'text:style-name': self.rststyle('strong')})
1479 #el1.text = node.astext()
1480 self.set_current_element(el1)
1482 def depart_term(self, node):
1483 #ipshell('At depart_term')
1484 self.set_to_parent()
1485 self.set_to_parent()
1487 def visit_classifier(self, node):
1488 #ipshell('At visit_classifier')
1489 els = self.current_element.getchildren()
1490 if len(els) > 0:
1491 el = els[-1]
1492 el1 = SubElement(el, 'text:span',
1493 attrib={'text:style-name': self.rststyle('emphasis')
1495 el1.text = ' (%s)' % (node.astext(), )
1497 def depart_classifier(self, node):
1498 pass
1500 def visit_document(self, node):
1501 #ipshell('At visit_document')
1502 #self.set_current_element(self.content_tree.getroot())
1503 pass
1505 def depart_document(self, node):
1506 pass
1508 def visit_docinfo(self, node):
1509 #self.document.reporter.debug_flag = 1
1510 self.trace_visit_node(node)
1511 self.section_level += 1
1512 self.section_count += 1
1513 if self.settings.create_sections:
1514 el = self.append_child('text:section', attrib={
1515 'text:name': 'Section%d' % self.section_count,
1516 'text:style-name': 'Sect%d' % self.section_level,
1518 self.set_current_element(el)
1520 def depart_docinfo(self, node):
1521 #self.document.reporter.debug_flag = 0
1522 self.trace_depart_node(node)
1523 self.section_level -= 1
1524 if self.settings.create_sections:
1525 self.set_to_parent()
1527 def visit_emphasis(self, node):
1528 el = SubElement(self.current_element, 'text:span',
1529 attrib={'text:style-name': self.rststyle('emphasis')})
1530 self.set_current_element(el)
1532 def depart_emphasis(self, node):
1533 self.set_to_parent()
1535 def visit_enumerated_list(self, node):
1536 el1 = self.current_element
1537 if self.blockstyle == self.rststyle('blockquote'):
1538 el2 = SubElement(el1, 'text:list', attrib={
1539 'text:style-name': self.rststyle('blockquote-enumlist'),
1541 self.list_style_stack.append(self.rststyle('blockquote-enumitem'))
1542 elif self.blockstyle == self.rststyle('highlights'):
1543 el2 = SubElement(el1, 'text:list', attrib={
1544 'text:style-name': self.rststyle('highlights-enumlist'),
1546 self.list_style_stack.append(self.rststyle('highlights-enumitem'))
1547 elif self.blockstyle == self.rststyle('epigraph'):
1548 el2 = SubElement(el1, 'text:list', attrib={
1549 'text:style-name': self.rststyle('epigraph-enumlist'),
1551 self.list_style_stack.append(self.rststyle('epigraph-enumitem'))
1552 else:
1553 el2 = SubElement(el1, 'text:list', attrib={
1554 'text:style-name': self.rststyle('enumlist'),
1556 self.list_style_stack.append(self.rststyle('enumitem'))
1557 self.set_current_element(el2)
1559 def depart_enumerated_list(self, node):
1560 self.set_to_parent()
1561 self.list_style_stack.pop()
1563 def visit_list_item(self, node):
1564 #ipshell('At visit_list_item')
1565 el1 = self.append_child('text:list-item')
1566 # If we are in a "bumped" list level, then wrap this
1567 # list in an outer lists in order to increase the
1568 # indentation level.
1569 el3 = el1
1570 if len(self.bumped_list_level_stack) > 0:
1571 level_obj = self.bumped_list_level_stack[-1]
1572 if level_obj.get_sibling():
1573 level_obj.set_nested(False)
1574 for level_obj1 in self.bumped_list_level_stack:
1575 for idx in range(level_obj1.get_level()):
1576 el2 = self.append_child('text:list', parent=el3)
1577 el3 = self.append_child('text:list-item', parent=el2)
1578 self.paragraph_style_stack.append(self.list_style_stack[-1])
1579 self.set_current_element(el3)
1581 def depart_list_item(self, node):
1582 if len(self.bumped_list_level_stack) > 0:
1583 level_obj = self.bumped_list_level_stack[-1]
1584 if level_obj.get_sibling():
1585 level_obj.set_nested(True)
1586 for level_obj1 in self.bumped_list_level_stack:
1587 for idx in range(level_obj1.get_level()):
1588 self.set_to_parent()
1589 self.set_to_parent()
1590 self.paragraph_style_stack.pop()
1591 self.set_to_parent()
1593 def visit_header(self, node):
1594 #ipshell('At visit_header')
1595 self.in_header = True
1597 def depart_header(self, node):
1598 #ipshell('At depart_header')
1599 self.in_header = False
1601 def visit_footer(self, node):
1602 #ipshell('At visit_footer')
1603 self.in_footer = True
1605 def depart_footer(self, node):
1606 #ipshell('At depart_footer')
1607 self.in_footer = False
1609 def visit_field(self, node):
1610 pass
1611 ## # Note that the syntaxhighlight directive produces this field node.
1612 ## # See class SyntaxHighlight, above.
1613 ## #ipshell('At visit_field')
1614 ## children = node.children
1615 ## if len(children) == 2:
1616 ## name = children[0].astext()
1617 ## if name == 'syntaxhighlight':
1618 ## body = children[1].astext()
1619 ## args = body.split()
1620 ## if args[0] == 'on':
1621 ## self.syntaxhighlighting = 1
1622 ## elif args[0] == 'off':
1623 ## self.syntaxhighlighting = 0
1624 ## else:
1625 ## self.syntaxhighlight_lexer = args[0]
1626 ## raise nodes.SkipChildren()
1628 def depart_field(self, node):
1629 pass
1631 def visit_field_list(self, node):
1632 #ipshell('At visit_field_list')
1633 pass
1635 def depart_field_list(self, node):
1636 #ipshell('At depart_field_list')
1637 pass
1639 def visit_field_name(self, node):
1640 #ipshell('At visit_field_name')
1641 #self.trace_visit_node(node)
1642 el = self.append_p('textbody')
1643 el1 = SubElement(el, 'text:span',
1644 attrib={'text:style-name': self.rststyle('strong')})
1645 el1.text = node.astext()
1647 def depart_field_name(self, node):
1648 #self.trace_depart_node(node)
1649 pass
1651 def visit_field_body(self, node):
1652 #ipshell('At visit_field_body')
1653 #self.trace_visit_node(node)
1654 self.paragraph_style_stack.append(self.rststyle('blockindent'))
1656 def depart_field_body(self, node):
1657 #self.trace_depart_node(node)
1658 self.paragraph_style_stack.pop()
1660 def visit_figure(self, node):
1661 #ipshell('At visit_figure')
1662 #self.trace_visit_node(node)
1663 pass
1665 def depart_figure(self, node):
1666 #self.trace_depart_node(node)
1667 pass
1669 def visit_footnote(self, node):
1670 #ipshell('At visit_footnote')
1671 self.in_footnote = True
1672 ids = node.attributes['ids']
1673 el1 = None
1674 for id in ids:
1675 if id in self.footnote_dict:
1676 el1 = self.footnote_dict[id]
1677 break
1678 if el1 is not None:
1679 el2 = SubElement(el1, 'text:note-body')
1680 self.paragraph_style_stack.append(self.rststyle('footnote'))
1681 self.set_current_element(el2)
1682 self.footnote_found = True
1683 else:
1684 self.footnote_found = False
1686 def depart_footnote(self, node):
1687 #ipshell('At depart_footnote')
1688 self.in_footnote = False
1689 if self.footnote_found:
1690 self.current_element.text = ''
1691 self.set_to_parent()
1692 self.set_to_parent()
1693 self.set_to_parent()
1694 self.paragraph_style_stack.pop()
1696 def visit_footnote_reference(self, node):
1697 #ipshell('At visit_footnote_reference')
1698 if not self.in_footnote:
1699 id = node.attributes['refid']
1700 children = self.current_element.getchildren()
1701 if len(children) > 0:
1702 if children[-1].tail and children[-1].tail[-1] == ' ':
1703 children[-1].tail = children[-1].tail[:-1]
1704 elif (self.current_element.text and
1705 self.current_element.text[-1] == ' '):
1706 self.current_element.text = self.current_element.text[:-1]
1707 el1 = self.append_child('text:note', attrib={
1708 'text:id': '%s' % (id, ),
1709 'text:note-class': 'footnote',
1711 self.footnote_dict[id] = el1
1712 raise nodes.SkipChildren()
1714 def depart_footnote_reference(self, node):
1715 #ipshell('At depart_footnote_reference')
1716 pass
1718 def visit_citation(self, node):
1719 #ipshell('At visit_citation')
1720 for id in node.attributes['ids']:
1721 self.in_citation = id
1722 break
1723 self.paragraph_style_stack.append(self.rststyle('blockindent'))
1724 self.bumped_list_level_stack.append(ListLevel(1))
1726 def depart_citation(self, node):
1727 #ipshell('At depart_citation')
1728 self.in_citation = None
1729 self.paragraph_style_stack.pop()
1730 self.bumped_list_level_stack.pop()
1732 def visit_citation_reference(self, node):
1733 #ipshell('At visit_citation_reference')
1734 if self.settings.create_links:
1735 id = node.attributes['refid']
1736 el = self.append_child('text:reference-ref', attrib={
1737 'text:ref-name': '%s' % (id, ),
1738 'text:reference-format': 'text',
1740 el.text = '['
1741 self.set_current_element(el)
1742 else:
1743 self.current_element.text += '['
1745 def depart_citation_reference(self, node):
1746 #ipshell('At depart_citation_reference')
1747 self.current_element.text += ']'
1748 if self.settings.create_links:
1749 self.set_to_parent()
1751 def visit_label(self, node):
1752 #ipshell('At visit_label')
1753 if self.in_citation is not None:
1754 el = self.append_p('textbody')
1755 self.set_current_element(el)
1756 if self.settings.create_links:
1757 el1 = self.append_child('text:reference-mark-start', attrib={
1758 'text:name': '%s' % (self.in_citation, ),
1761 def depart_label(self, node):
1762 #ipshell('At depart_label')
1763 if self.in_citation is not None:
1764 if self.settings.create_links:
1765 el = self.append_child('text:reference-mark-end', attrib={
1766 'text:name': '%s' % (self.in_citation, ),
1768 self.set_to_parent()
1770 def visit_generated(self, node):
1771 pass
1773 def depart_generated(self, node):
1774 pass
1776 def check_file_exists(self, path):
1777 if os.path.exists(path):
1778 return 1
1779 else:
1780 return 0
1782 def visit_image(self, node):
1783 #ipshell('At visit_image')
1784 #self.trace_visit_node(node)
1785 # Capture the image file.
1786 if 'uri' in node.attributes:
1787 source = node.attributes['uri']
1788 if not self.check_file_exists(source):
1789 print 'Error: Cannot find image file %s.' % (source, )
1790 return
1791 else:
1792 return
1793 if source in self.image_dict:
1794 filename, destination = self.image_dict[source]
1795 else:
1796 self.image_count += 1
1797 filename = os.path.split(source)[1]
1798 destination = 'Pictures/1%08x%s' % (self.image_count, filename, )
1799 spec = (source, destination,)
1800 self.embedded_file_list.append(spec)
1801 self.image_dict[source] = (source, destination,)
1802 # Is this a figure (containing an image) or just a plain image?
1803 if self.in_paragraph:
1804 el1 = self.current_element
1805 else:
1806 el1 = SubElement(self.current_element, 'text:p',
1807 attrib={'text:style-name': self.rststyle('textbody')})
1808 el2 = el1
1809 if isinstance(node.parent, docutils.nodes.figure):
1810 el3, el4, caption = self.generate_figure(node, source,
1811 destination, el2)
1812 attrib = {
1813 'draw:blue': '0%',
1814 'draw:color-inversion': 'false',
1815 'draw:color-mode': 'standard',
1816 'draw:contrast': '0%',
1817 'draw:gamma': '100%',
1818 'draw:green': '0%',
1819 'draw:image-opacity': '100%',
1820 'draw:luminance': '0%',
1821 'draw:red': '0%',
1822 'fo:border': 'none',
1823 'fo:clip': 'rect(0in 0in 0in 0in)',
1824 'fo:margin-bottom': '0in',
1825 'fo:margin-left': '0in',
1826 'fo:margin-right': '0in',
1827 'fo:margin-top': '0in',
1828 'fo:padding': '0in',
1829 'style:horizontal-pos': 'from-left',
1830 'style:horizontal-rel': 'paragraph-content',
1831 'style:mirror': 'none',
1832 'style:run-through': 'foreground',
1833 'style:shadow': 'none',
1834 'style:vertical-pos': 'from-top',
1835 'style:vertical-rel': 'paragraph-content',
1836 'style:wrap': 'none',
1838 el5, width = self.generate_image(node, source, destination,
1839 el4, attrib)
1840 if caption is not None:
1841 el5.tail = caption
1842 else: #if isinstance(node.parent, docutils.nodes.image):
1843 el3 = self.generate_image(node, source, destination, el2)
1845 def depart_image(self, node):
1846 pass
1848 def get_image_width_height(self, node, attr, scale):
1849 size = None
1850 if attr in node.attributes:
1851 try:
1852 size = int(node.attributes[attr])
1853 size *= 35.278 / 1000.0
1854 size *= scale
1855 except ValueError, e:
1856 print 'Error: Invalid %s for image: "%s"' % (
1857 attr, node.attributes[attr], )
1858 return size
1860 def get_image_scale(self, node):
1861 if 'scale' in node.attributes:
1862 try:
1863 scale = int(node.attributes['scale'])
1864 if scale < 1: # or scale > 100:
1865 raise ValueError
1866 scale = scale * 0.01
1867 except ValueError, e:
1868 print 'Error: Invalid scale for image: "%s"' % (
1869 node.attributes['scale'], )
1870 else:
1871 scale = 1.0
1872 return scale
1874 def get_image_scale_width_height(self, node, source):
1875 scale = self.get_image_scale(node)
1876 width = self.get_image_width_height(node, 'width', scale)
1877 height = self.get_image_width_height(node, 'height', scale)
1878 if ('scale' in node.attributes and
1879 ('width' not in node.attributes or
1880 'height' not in node.attributes)):
1881 if Image is not None:
1882 if source in self.image_dict:
1883 filename, destination = self.image_dict[source]
1884 imageobj = Image.open(filename, 'r')
1885 width, height = imageobj.size
1886 width = width * (35.278 / 1000.0)
1887 width *= scale
1888 height = height * (35.278 / 1000.0)
1889 height *= scale
1890 else:
1891 raise RuntimeError, 'image has scale and no height/width and PIL not installed'
1892 return scale, width, height
1894 def generate_figure(self, node, source, destination, current_element):
1895 #ipshell('At generate_figure')
1896 caption = None
1897 scale, width, height = self.get_image_scale_width_height(node, source)
1898 for node1 in node.parent.children:
1899 if node1.tagname == 'caption':
1900 caption = node1.astext()
1901 self.image_style_count += 1
1903 # Add the style for the caption.
1904 if caption is not None:
1905 attrib = {
1906 'style:class': 'extra',
1907 'style:family': 'paragraph',
1908 'style:name': 'Caption',
1909 'style:parent-style-name': 'Standard',
1911 el1 = SubElement(self.automatic_styles, 'style:style',
1912 attrib=attrib, nsdict=SNSD)
1913 attrib = {
1914 'fo:margin-bottom': '0.0835in',
1915 'fo:margin-top': '0.0835in',
1916 'text:line-number': '0',
1917 'text:number-lines': 'false',
1919 el2 = SubElement(el1, 'style:paragraph-properties',
1920 attrib=attrib, nsdict=SNSD)
1921 attrib = {
1922 'fo:font-size': '12pt',
1923 'fo:font-style': 'italic',
1924 'style:font-name': 'Times',
1925 'style:font-name-complex': 'Lucidasans1',
1926 'style:font-size-asian': '12pt',
1927 'style:font-size-complex': '12pt',
1928 'style:font-style-asian': 'italic',
1929 'style:font-style-complex': 'italic',
1931 el2 = SubElement(el1, 'style:text-properties',
1932 attrib=attrib, nsdict=SNSD)
1933 style_name = 'rstframestyle%d' % self.image_style_count
1934 # Add the styles
1935 attrib = {
1936 'style:name': style_name,
1937 'style:family': 'graphic',
1938 'style:parent-style-name': 'Frame',
1940 el1 = SubElement(self.automatic_styles,
1941 'style:style', attrib=attrib, nsdict=SNSD)
1942 halign = 'center'
1943 valign = 'top'
1944 if 'align' in node.attributes:
1945 align = node.attributes['align'].split()
1946 for val in align:
1947 if val in ('left', 'center', 'right'):
1948 halign = val
1949 elif val in ('top', 'middle', 'bottom'):
1950 valign = val
1951 attrib = {
1952 'fo:margin-left': '0cm',
1953 'fo:margin-right': '0cm',
1954 'fo:margin-top': '0cm',
1955 'fo:margin-bottom': '0cm',
1956 'style:wrap': 'dynamic',
1957 'style:number-wrapped-paragraphs': 'no-limit',
1958 'style:vertical-pos': valign,
1959 'style:vertical-rel': 'paragraph',
1960 'style:horizontal-pos': halign,
1961 'style:horizontal-rel': 'paragraph',
1962 'fo:padding': '0cm',
1963 'fo:border': 'none',
1965 el2 = SubElement(el1,
1966 'style:graphic-properties', attrib=attrib, nsdict=SNSD)
1967 # Add the content wrapper.
1968 ## attrib = {'text:style-name': self.rststyle('textbody')}
1969 ## el1 = SubElement(self.current_element, 'text:p', attrib=attrib)
1970 attrib = {
1971 'draw:style-name': style_name,
1972 'draw:name': 'Frame1',
1973 'text:anchor-type': 'paragraph',
1974 'draw:z-index': '1',
1976 if width is not None:
1977 attrib['svg:width'] = '%.2fcm' % (width, )
1978 el3 = SubElement(current_element, 'draw:frame', attrib=attrib)
1979 attrib = {}
1980 el4 = SubElement(el3, 'draw:text-box', attrib=attrib)
1981 attrib = {
1982 'text:style-name': self.rststyle('caption'),
1984 el5 = SubElement(el4, 'text:p', attrib=attrib)
1985 ## if caption is not None:
1986 ## el3.tail = caption
1987 return el3, el5, caption
1989 def generate_image(self, node, source, destination, current_element,
1990 #ipshell('At generate_image')
1991 frame_attrs=None):
1992 scale, width, height = self.get_image_scale_width_height(node, source)
1993 self.image_style_count += 1
1994 style_name = 'rstframestyle%d' % self.image_style_count
1995 # Add the style.
1996 attrib = {
1997 'style:name': style_name,
1998 'style:family': 'graphic',
1999 'style:parent-style-name': 'Graphics',
2001 el1 = SubElement(self.automatic_styles,
2002 'style:style', attrib=attrib, nsdict=SNSD)
2003 halign = None
2004 valign = None
2005 if 'align' in node.attributes:
2006 align = node.attributes['align'].split()
2007 for val in align:
2008 if val in ('left', 'center', 'right'):
2009 halign = val
2010 elif val in ('top', 'middle', 'bottom'):
2011 valign = val
2012 if frame_attrs is None:
2013 attrib = {
2014 'style:vertical-pos': 'top',
2015 'style:vertical-rel': 'paragraph',
2016 #'style:horizontal-pos': halign,
2017 #'style:vertical-pos': valign,
2018 'style:horizontal-rel': 'paragraph',
2019 'style:mirror': 'none',
2020 'fo:clip': 'rect(0cm 0cm 0cm 0cm)',
2021 'draw:luminance': '0%',
2022 'draw:contrast': '0%',
2023 'draw:red': '0%',
2024 'draw:green': '0%',
2025 'draw:blue': '0%',
2026 'draw:gamma': '100%',
2027 'draw:color-inversion': 'false',
2028 'draw:image-opacity': '100%',
2029 'draw:color-mode': 'standard',
2031 else:
2032 attrib = frame_attrs
2033 if halign is not None:
2034 attrib['style:horizontal-pos'] = halign
2035 if valign is not None:
2036 attrib['style:vertical-pos'] = valign
2037 #ipshell('At generate_image')
2038 # If we are inside a table, add a no-wrap style.
2039 if self.is_in_table(node):
2040 attrib['style:wrap'] = 'none'
2041 el2 = SubElement(el1,
2042 'style:graphic-properties', attrib=attrib, nsdict=SNSD)
2043 # Add the content.
2044 #el = SubElement(current_element, 'text:p',
2045 # attrib={'text:style-name': self.rststyle('textbody')})
2046 attrib={
2047 'draw:style-name': style_name,
2048 'draw:name': 'graphics2',
2049 #'text:anchor-type': 'paragraph',
2050 #'svg:width': '%fcm' % (width, ),
2051 #'svg:height': '%fcm' % (height, ),
2052 'draw:z-index': '1',
2054 if isinstance(node.parent, nodes.TextElement):
2055 attrib['text:anchor-type'] = 'char'
2056 else:
2057 attrib['text:anchor-type'] = 'paragraph'
2058 if width is not None:
2059 attrib['svg:width'] = '%.2fcm' % (width, )
2060 if height is not None:
2061 attrib['svg:height'] = '%.2fcm' % (height, )
2062 el1 = SubElement(current_element, 'draw:frame', attrib=attrib)
2063 el2 = SubElement(el1, 'draw:image', attrib={
2064 'xlink:href': '%s' % (destination, ),
2065 'xlink:type': 'simple',
2066 'xlink:show': 'embed',
2067 'xlink:actuate': 'onLoad',
2069 return el1, width
2071 def is_in_table(self, node):
2072 node1 = node.parent
2073 while node1:
2074 if isinstance(node1, docutils.nodes.entry):
2075 return True
2076 node1 = node1.parent
2077 return False
2079 def visit_legend(self, node):
2080 # Currently, the legend receives *no* special treatment.
2081 #ipshell('At visit_legend')
2082 pass
2084 def depart_legend(self, node):
2085 pass
2087 def visit_line(self, node):
2088 #ipshell('At visit_line')
2089 pass
2091 def depart_line(self, node):
2092 #ipshell('At depart_line')
2093 pass
2095 def visit_line_block(self, node):
2096 #ipshell('At visit_line_block')
2097 s1 = node.astext()
2098 lines = s1.split('\n')
2099 el = self.append_p('lineblock', lines[0])
2100 first = True
2101 if len(lines) > 1:
2102 for line in lines[1:]:
2103 if line == '':
2104 if first:
2105 first = False
2106 continue
2107 first = True
2108 el1 = SubElement(el, 'text:line-break')
2109 el1.tail = line
2111 def depart_line_block(self, node):
2112 #ipshell('At depart_line_block')
2113 pass
2115 def visit_literal(self, node):
2116 #ipshell('At visit_literal')
2117 el = SubElement(self.current_element, 'text:span',
2118 attrib={'text:style-name': self.rststyle('inlineliteral')})
2119 self.set_current_element(el)
2121 def depart_literal(self, node):
2122 self.set_to_parent()
2124 def _calculate_code_block_padding(self, line):
2125 count = 0
2126 matchobj = SPACES_PATTERN.match(line)
2127 if matchobj:
2128 pad = matchobj.group()
2129 count = len(pad)
2130 else:
2131 matchobj = TABS_PATTERN.match(line)
2132 if matchobj:
2133 pad = matchobj.group()
2134 count = len(pad) * 8
2135 return count
2137 def _add_syntax_highlighting(self, insource, language):
2138 #print '(_add_syntax_highlighting) using lexer: %s' % (language, )
2139 lexer = pygments.lexers.get_lexer_by_name(language, stripall=True)
2140 if language in ('latex', 'tex'):
2141 fmtr = OdtPygmentsLaTeXFormatter(lambda name, parameters=():
2142 self.rststyle(name, parameters))
2143 else:
2144 fmtr = OdtPygmentsProgFormatter(lambda name, parameters=():
2145 self.rststyle(name, parameters))
2146 outsource = pygments.highlight(insource, lexer, fmtr)
2147 return outsource
2149 def fill_line(self, line):
2150 line = FILL_PAT1.sub(self.fill_func1, line)
2151 line = FILL_PAT2.sub(self.fill_func2, line)
2152 return line
2154 def fill_func1(self, matchobj):
2155 spaces = matchobj.group(0)
2156 repl = '<text:s text:c="%d"/>' % (len(spaces), )
2157 return repl
2159 def fill_func2(self, matchobj):
2160 spaces = matchobj.group(0)
2161 repl = ' <text:s text:c="%d"/>' % (len(spaces) - 1, )
2162 return repl
2164 def visit_literal_block(self, node):
2165 #ipshell('At visit_literal_block')
2166 wrapper1 = '<text:p text:style-name="%s">%%s</text:p>' % (
2167 self.rststyle('codeblock'), )
2168 source = node.astext()
2169 if (pygments and
2170 self.settings.add_syntax_highlighting and
2171 node.get('hilight', False)):
2172 language = node.get('language', 'python')
2173 source = self._add_syntax_highlighting(source, language)
2174 else:
2175 source = escape_cdata(source)
2176 lines = source.split('\n')
2177 lines1 = ['<wrappertag1 xmlns:text="urn:oasis:names:tc:opendocument:xmlns:text:1.0">']
2179 my_lines = []
2180 for my_line in lines:
2181 my_line = self.fill_line(my_line)
2182 my_line = my_line.replace("&#10;", "\n")
2183 my_lines.append(my_line)
2184 my_lines_str = '<text:line-break/>'.join(my_lines)
2185 my_lines_str2 = wrapper1 % (my_lines_str, )
2186 lines1.append(my_lines_str2)
2187 lines1.append('</wrappertag1>')
2188 s1 = ''.join(lines1)
2189 if WhichElementTree != "lxml":
2190 s1 = s1.encode("utf-8")
2191 el1 = etree.fromstring(s1)
2192 children = el1.getchildren()
2193 for child in children:
2194 self.current_element.append(child)
2196 def depart_literal_block(self, node):
2197 pass
2199 visit_doctest_block = visit_literal_block
2200 depart_doctest_block = depart_literal_block
2202 def visit_meta(self, node):
2203 #ipshell('At visit_meta')
2204 name = node.attributes.get('name')
2205 content = node.attributes.get('content')
2206 if name is not None and content is not None:
2207 self.meta_dict[name] = content
2209 def depart_meta(self, node):
2210 pass
2212 def visit_option_list(self, node):
2213 table_name = 'tableoption'
2215 # Generate automatic styles
2216 if not self.optiontablestyles_generated:
2217 self.optiontablestyles_generated = True
2218 el = SubElement(self.automatic_styles, 'style:style', attrib={
2219 'style:name': self.rststyle(table_name),
2220 'style:family': 'table'}, nsdict=SNSD)
2221 el1 = SubElement(el, 'style:table-properties', attrib={
2222 'style:width': '17.59cm',
2223 'table:align': 'left',
2224 'style:shadow': 'none'}, nsdict=SNSD)
2225 el = SubElement(self.automatic_styles, 'style:style', attrib={
2226 'style:name': self.rststyle('%s.%%c' % table_name, ( 'A', )),
2227 'style:family': 'table-column'}, nsdict=SNSD)
2228 el1 = SubElement(el, 'style:table-column-properties', attrib={
2229 'style:column-width': '4.999cm'}, nsdict=SNSD)
2230 el = SubElement(self.automatic_styles, 'style:style', attrib={
2231 'style:name': self.rststyle('%s.%%c' % table_name, ( 'B', )),
2232 'style:family': 'table-column'}, nsdict=SNSD)
2233 el1 = SubElement(el, 'style:table-column-properties', attrib={
2234 'style:column-width': '12.587cm'}, nsdict=SNSD)
2235 el = SubElement(self.automatic_styles, 'style:style', attrib={
2236 'style:name': self.rststyle('%s.%%c%%d' % table_name, ( 'A', 1, )),
2237 'style:family': 'table-cell'}, nsdict=SNSD)
2238 el1 = SubElement(el, 'style:table-cell-properties', attrib={
2239 'fo:background-color': 'transparent',
2240 'fo:padding': '0.097cm',
2241 'fo:border-left': '0.035cm solid #000000',
2242 'fo:border-right': 'none',
2243 'fo:border-top': '0.035cm solid #000000',
2244 'fo:border-bottom': '0.035cm solid #000000'}, nsdict=SNSD)
2245 el2 = SubElement(el1, 'style:background-image', nsdict=SNSD)
2246 el = SubElement(self.automatic_styles, 'style:style', attrib={
2247 'style:name': self.rststyle('%s.%%c%%d' % table_name, ( 'B', 1, )),
2248 'style:family': 'table-cell'}, nsdict=SNSD)
2249 el1 = SubElement(el, 'style:table-cell-properties', attrib={
2250 'fo:padding': '0.097cm',
2251 'fo:border': '0.035cm solid #000000'}, nsdict=SNSD)
2252 el = SubElement(self.automatic_styles, 'style:style', attrib={
2253 'style:name': self.rststyle('%s.%%c%%d' % table_name, ( 'A', 2, )),
2254 'style:family': 'table-cell'}, nsdict=SNSD)
2255 el1 = SubElement(el, 'style:table-cell-properties', attrib={
2256 'fo:padding': '0.097cm',
2257 'fo:border-left': '0.035cm solid #000000',
2258 'fo:border-right': 'none',
2259 'fo:border-top': 'none',
2260 'fo:border-bottom': '0.035cm solid #000000'}, nsdict=SNSD)
2261 el = SubElement(self.automatic_styles, 'style:style', attrib={
2262 'style:name': self.rststyle('%s.%%c%%d' % table_name, ( 'B', 2, )),
2263 'style:family': 'table-cell'}, nsdict=SNSD)
2264 el1 = SubElement(el, 'style:table-cell-properties', attrib={
2265 'fo:padding': '0.097cm',
2266 'fo:border-left': '0.035cm solid #000000',
2267 'fo:border-right': '0.035cm solid #000000',
2268 'fo:border-top': 'none',
2269 'fo:border-bottom': '0.035cm solid #000000'}, nsdict=SNSD)
2271 # Generate table data
2272 el = self.append_child('table:table', attrib={
2273 'table:name': self.rststyle(table_name),
2274 'table:style-name': self.rststyle(table_name),
2276 el1 = SubElement(el, 'table:table-column', attrib={
2277 'table:style-name': self.rststyle('%s.%%c' % table_name, ( 'A', ))})
2278 el1 = SubElement(el, 'table:table-column', attrib={
2279 'table:style-name': self.rststyle('%s.%%c' % table_name, ( 'B', ))})
2280 el1 = SubElement(el, 'table:table-header-rows')
2281 el2 = SubElement(el1, 'table:table-row')
2282 el3 = SubElement(el2, 'table:table-cell', attrib={
2283 'table:style-name': self.rststyle('%s.%%c%%d' % table_name, ( 'A', 1, )),
2284 'office:value-type': 'string'})
2285 el4 = SubElement(el3, 'text:p', attrib={
2286 'text:style-name': 'Table_20_Heading'})
2287 el4.text= 'Option'
2288 el3 = SubElement(el2, 'table:table-cell', attrib={
2289 'table:style-name': self.rststyle('%s.%%c%%d' % table_name, ( 'B', 1, )),
2290 'office:value-type': 'string'})
2291 el4 = SubElement(el3, 'text:p', attrib={
2292 'text:style-name': 'Table_20_Heading'})
2293 el4.text= 'Description'
2294 self.set_current_element(el)
2296 def depart_option_list(self, node):
2297 #self.document.reporter.debug_flag = 0
2298 self.set_to_parent()
2300 def visit_option_list_item(self, node):
2301 el = self.append_child('table:table-row')
2302 self.set_current_element(el)
2304 def depart_option_list_item(self, node):
2305 self.set_to_parent()
2307 def visit_option_group(self, node):
2308 el = self.append_child('table:table-cell', attrib={
2309 'table:style-name': 'Table%d.A2' % self.table_count,
2310 'office:value-type': 'string',
2312 self.set_current_element(el)
2314 def depart_option_group(self, node):
2315 self.set_to_parent()
2317 def visit_option(self, node):
2318 el = self.append_child('text:p', attrib={
2319 'text:style-name': 'Table_20_Contents'})
2320 el.text = node.astext()
2322 def depart_option(self, node):
2323 pass
2325 def visit_option_string(self, node):
2326 pass
2328 def depart_option_string(self, node):
2329 pass
2331 def visit_option_argument(self, node):
2332 #ipshell('At visit_option_argument')
2333 pass
2335 def depart_option_argument(self, node):
2336 pass
2338 def visit_description(self, node):
2339 el = self.append_child('table:table-cell', attrib={
2340 'table:style-name': 'Table%d.B2' % self.table_count,
2341 'office:value-type': 'string',
2343 el1 = SubElement(el, 'text:p', attrib={
2344 'text:style-name': 'Table_20_Contents'})
2345 el1.text = node.astext()
2346 raise nodes.SkipChildren()
2348 def depart_description(self, node):
2349 pass
2351 def visit_paragraph(self, node):
2352 #ipshell('At visit_paragraph')
2353 #self.trace_visit_node(node)
2354 self.in_paragraph = True
2355 if self.in_header:
2356 el = self.append_p('header')
2357 elif self.in_footer:
2358 el = self.append_p('footer')
2359 else:
2360 style_name = self.paragraph_style_stack[-1]
2361 el = self.append_child('text:p',
2362 attrib={'text:style-name': style_name})
2363 self.append_pending_ids(el)
2364 self.set_current_element(el)
2366 def depart_paragraph(self, node):
2367 #ipshell('At depart_paragraph')
2368 #self.trace_depart_node(node)
2369 self.in_paragraph = False
2370 self.set_to_parent()
2371 if self.in_header:
2372 self.header_content.append(self.current_element.getchildren()[-1])
2373 self.current_element.remove(self.current_element.getchildren()[-1])
2374 elif self.in_footer:
2375 self.footer_content.append(self.current_element.getchildren()[-1])
2376 self.current_element.remove(self.current_element.getchildren()[-1])
2378 def visit_problematic(self, node):
2379 #print '(visit_problematic) node: %s' % (node.astext(), )
2380 pass
2382 def depart_problematic(self, node):
2383 pass
2385 def visit_raw(self, node):
2386 #ipshell('At visit_raw')
2387 if 'format' in node.attributes:
2388 formats = node.attributes['format']
2389 formatlist = formats.split()
2390 if 'odt' in formatlist:
2391 rawstr = node.astext()
2392 attrstr = ' '.join(['%s="%s"' % (k, v, )
2393 for k,v in CONTENT_NAMESPACE_ATTRIB.items()])
2394 contentstr = '<stuff %s>%s</stuff>' % (attrstr, rawstr, )
2395 if WhichElementTree != "lxml":
2396 content = content.encode("utf-8")
2397 content = etree.fromstring(contentstr)
2398 elements = content.getchildren()
2399 if len(elements) > 0:
2400 el1 = elements[0]
2401 if self.in_header:
2402 pass
2403 elif self.in_footer:
2404 pass
2405 else:
2406 self.current_element.append(el1)
2407 raise nodes.SkipChildren()
2409 def depart_raw(self, node):
2410 if self.in_header:
2411 pass
2412 elif self.in_footer:
2413 pass
2414 else:
2415 pass
2417 def visit_reference(self, node):
2418 #self.trace_visit_node(node)
2419 text = node.astext()
2420 if self.settings.create_links:
2421 if node.has_key('refuri'):
2422 href = node['refuri']
2423 if ( self.settings.cloak_email_addresses
2424 and href.startswith('mailto:')):
2425 href = self.cloak_mailto(href)
2426 el = self.append_child('text:a', attrib={
2427 'xlink:href': '%s' % href,
2428 'xlink:type': 'simple',
2430 self.set_current_element(el)
2431 elif node.has_key('refid'):
2432 if self.settings.create_links:
2433 href = node['refid']
2434 el = self.append_child('text:reference-ref', attrib={
2435 'text:ref-name': '%s' % href,
2436 'text:reference-format': 'text',
2438 else:
2439 raise RuntimeError, 'References must have "refuri" or "refid" attribute.'
2440 #print '(visit_reference) href: "%s" text: "%s"' % (href, text, )
2441 if (self.in_table_of_contents and
2442 len(node.children) >= 1 and
2443 isinstance(node.children[0], docutils.nodes.generated)):
2444 node.remove(node.children[0])
2446 def depart_reference(self, node):
2447 #self.trace_depart_node(node)
2448 if self.settings.create_links:
2449 if node.has_key('refuri'):
2450 self.set_to_parent()
2452 def visit_rubric(self, node):
2453 style_name = self.rststyle('rubric')
2454 classes = node.get('classes')
2455 if classes:
2456 class1 = classes[0]
2457 if class1:
2458 style_name = class1
2459 el = SubElement(self.current_element, 'text:h', attrib = {
2460 #'text:outline-level': '%d' % section_level,
2461 #'text:style-name': 'Heading_20_%d' % section_level,
2462 'text:style-name': style_name,
2464 text = node.astext()
2465 el.text = self.encode(text)
2467 def depart_rubric(self, node):
2468 pass
2470 def visit_section(self, node, move_ids=1):
2471 #ipshell('At visit_section')
2472 self.section_level += 1
2473 self.section_count += 1
2474 if self.settings.create_sections:
2475 el = self.append_child('text:section', attrib={
2476 'text:name': 'Section%d' % self.section_count,
2477 'text:style-name': 'Sect%d' % self.section_level,
2479 self.set_current_element(el)
2481 def depart_section(self, node):
2482 self.section_level -= 1
2483 if self.settings.create_sections:
2484 self.set_to_parent()
2486 def visit_strong(self, node):
2487 #ipshell('At visit_strong')
2488 el = SubElement(self.current_element, 'text:span',
2489 attrib={'text:style-name': self.rststyle('strong')})
2490 self.set_current_element(el)
2492 def depart_strong(self, node):
2493 self.set_to_parent()
2495 def visit_substitution_definition(self, node):
2496 #ipshell('At visit_substitution_definition')
2497 raise nodes.SkipChildren()
2499 def depart_substitution_definition(self, node):
2500 #ipshell('At depart_substitution_definition')
2501 pass
2503 def visit_system_message(self, node):
2504 #print '(visit_system_message) node: %s' % (node.astext(), )
2505 pass
2507 def depart_system_message(self, node):
2508 pass
2510 def visit_table(self, node):
2511 #self.trace_visit_node(node)
2512 #ipshell('At visit_table')
2513 self.table_count += 1
2514 table_name = '%s%%d' % TableStylePrefix
2515 el1 = SubElement(self.automatic_styles, 'style:style', attrib={
2516 'style:name': self.rststyle('%s' % table_name, ( self.table_count, )),
2517 'style:family': 'table',
2518 }, nsdict=SNSD)
2519 el1_1 = SubElement(el1, 'style:table-properties', attrib={
2520 #'style:width': '17.59cm',
2521 'table:align': 'margins',
2522 }, nsdict=SNSD)
2523 # We use a single cell style for all cells in this table.
2524 # That's probably not correct, but seems to work.
2525 el2 = SubElement(self.automatic_styles, 'style:style', attrib={
2526 'style:name': self.rststyle('%s.%%c%%d' % table_name, ( self.table_count, 'A', 1, )),
2527 'style:family': 'table-cell',
2528 }, nsdict=SNSD)
2529 line_style1 = '0.%03dcm solid #000000' % self.settings.table_border_thickness
2530 el2_1 = SubElement(el2, 'style:table-cell-properties', attrib={
2531 'fo:padding': '0.049cm',
2532 'fo:border-left': line_style1,
2533 'fo:border-right': line_style1,
2534 'fo:border-top': line_style1,
2535 'fo:border-bottom': line_style1,
2536 }, nsdict=SNSD)
2537 title = None
2538 for child in node.children:
2539 if child.tagname == 'title':
2540 title = child.astext()
2541 break
2542 if title is not None:
2543 el3 = self.append_p('table-title', title)
2544 else:
2545 #print 'no table title'
2546 pass
2547 el4 = SubElement(self.current_element, 'table:table', attrib={
2548 'table:name': self.rststyle('%s' % table_name, ( self.table_count, )),
2549 'table:style-name': self.rststyle('%s' % table_name, ( self.table_count, )),
2551 self.set_current_element(el4)
2552 self.current_table_style = el1
2553 self.table_width = 0
2555 def depart_table(self, node):
2556 #self.trace_depart_node(node)
2557 #ipshell('At depart_table')
2558 attribkey = add_ns('style:width', nsdict=SNSD)
2559 attribval = '%dcm' % self.table_width
2560 self.current_table_style.attrib[attribkey] = attribval
2561 self.set_to_parent()
2563 def visit_tgroup(self, node):
2564 #self.trace_visit_node(node)
2565 #ipshell('At visit_tgroup')
2566 self.column_count = ord('A') - 1
2568 def depart_tgroup(self, node):
2569 #self.trace_depart_node(node)
2570 pass
2572 def visit_colspec(self, node):
2573 #self.trace_visit_node(node)
2574 #ipshell('At visit_colspec')
2575 self.column_count += 1
2576 colspec_name = self.rststyle('%s%%d.%%s' % TableStylePrefix, ( self.table_count, chr(self.column_count), ))
2577 colwidth = node['colwidth']
2578 el1 = SubElement(self.automatic_styles, 'style:style', attrib={
2579 'style:name': colspec_name,
2580 'style:family': 'table-column',
2581 }, nsdict=SNSD)
2582 el1_1 = SubElement(el1, 'style:table-column-properties', attrib={
2583 'style:column-width': '%dcm' % colwidth }, nsdict=SNSD)
2584 el2 = self.append_child('table:table-column', attrib={
2585 'table:style-name': colspec_name,
2587 self.table_width += colwidth
2589 def depart_colspec(self, node):
2590 #self.trace_depart_node(node)
2591 pass
2593 def visit_thead(self, node):
2594 #self.trace_visit_node(node)
2595 #ipshell('At visit_thead')
2596 el = self.append_child('table:table-header-rows')
2597 self.set_current_element(el)
2598 self.in_thead = True
2599 self.paragraph_style_stack.append('Table_20_Heading')
2601 def depart_thead(self, node):
2602 #self.trace_depart_node(node)
2603 self.set_to_parent()
2604 self.in_thead = False
2605 self.paragraph_style_stack.pop()
2607 def visit_row(self, node):
2608 #self.trace_visit_node(node)
2609 #ipshell('At visit_row')
2610 self.column_count = ord('A') - 1
2611 el = self.append_child('table:table-row')
2612 self.set_current_element(el)
2614 def depart_row(self, node):
2615 #self.trace_depart_node(node)
2616 self.set_to_parent()
2618 def visit_entry(self, node):
2619 #self.trace_visit_node(node)
2620 #ipshell('At visit_entry')
2621 self.column_count += 1
2622 cellspec_name = self.rststyle('%s%%d.%%c%%d' % TableStylePrefix, ( self.table_count, 'A', 1, ))
2623 attrib={
2624 'table:style-name': cellspec_name,
2625 'office:value-type': 'string',
2627 morecols = node.get('morecols', 0)
2628 if morecols > 0:
2629 attrib['table:number-columns-spanned'] = '%d' % (morecols + 1,)
2630 self.column_count += morecols
2631 el1 = self.append_child('table:table-cell', attrib=attrib)
2632 self.set_current_element(el1)
2634 def depart_entry(self, node):
2635 #self.trace_depart_node(node)
2636 self.set_to_parent()
2638 def visit_tbody(self, node):
2639 #self.trace_visit_node(node)
2640 #ipshell('At visit_')
2641 pass
2643 def depart_tbody(self, node):
2644 #self.trace_depart_node(node)
2645 pass
2647 def visit_target(self, node):
2649 # I don't know how to implement targets in ODF.
2650 # How do we create a target in oowriter? A cross-reference?
2651 if not (node.has_key('refuri') or node.has_key('refid')
2652 or node.has_key('refname')):
2653 pass
2654 else:
2655 pass
2657 def depart_target(self, node):
2658 pass
2660 def visit_title(self, node, move_ids=1, title_type='title'):
2661 #ipshell('At visit_title')
2662 if isinstance(node.parent, docutils.nodes.section):
2663 section_level = self.section_level
2664 if section_level > 7:
2665 print 'Warning: Heading/section levels greater than 7 not supported.'
2666 print ' Reducing to heading level 7 for heading:'
2667 print ' "%s"' % node.astext()
2668 section_level = 7
2669 el1 = self.append_child('text:h', attrib = {
2670 'text:outline-level': '%d' % section_level,
2671 #'text:style-name': 'Heading_20_%d' % section_level,
2672 'text:style-name': self.rststyle('heading%d', (section_level, )),
2674 self.append_pending_ids(el1)
2675 self.set_current_element(el1)
2676 elif isinstance(node.parent, docutils.nodes.document):
2677 # text = self.settings.title
2678 #else:
2679 # text = node.astext()
2680 el1 = SubElement(self.current_element, 'text:p', attrib = {
2681 'text:style-name': self.rststyle(title_type),
2683 self.append_pending_ids(el1)
2684 text = node.astext()
2685 self.title = text
2686 self.found_doc_title = True
2687 self.set_current_element(el1)
2689 def depart_title(self, node):
2690 if (isinstance(node.parent, docutils.nodes.section) or
2691 isinstance(node.parent, docutils.nodes.document)):
2692 self.set_to_parent()
2694 def visit_subtitle(self, node, move_ids=1):
2695 self.visit_title(node, move_ids, title_type='subtitle')
2697 def depart_subtitle(self, node):
2698 self.depart_title(node)
2700 def visit_title_reference(self, node):
2701 #ipshell('At visit_title_reference')
2702 el = self.append_child('text:span', attrib={
2703 'text:style-name': self.rststyle('quotation')})
2704 el.text = self.encode(node.astext())
2706 def depart_title_reference(self, node):
2707 pass
2709 def visit_topic(self, node):
2710 #ipshell('At visit_topic')
2711 if 'classes' in node.attributes:
2712 if 'contents' in node.attributes['classes']:
2713 el = self.append_p('horizontalline')
2714 el = self.append_p('centeredtextbody')
2715 el1 = SubElement(el, 'text:span',
2716 attrib={'text:style-name': self.rststyle('strong')})
2717 el1.text = 'Contents'
2718 self.in_table_of_contents = True
2719 elif 'abstract' in node.attributes['classes']:
2720 el = self.append_p('horizontalline')
2721 el = self.append_p('centeredtextbody')
2722 el1 = SubElement(el, 'text:span',
2723 attrib={'text:style-name': self.rststyle('strong')})
2724 el1.text = 'Abstract'
2726 def depart_topic(self, node):
2727 #ipshell('At depart_topic')
2728 if 'classes' in node.attributes:
2729 if 'contents' in node.attributes['classes']:
2730 el = self.append_p('horizontalline')
2731 self.in_table_of_contents = False
2733 def visit_transition(self, node):
2734 el = self.append_p('horizontalline')
2736 def depart_transition(self, node):
2737 pass
2740 # Admonitions
2742 def visit_warning(self, node):
2743 self.generate_admonition(node, 'warning')
2745 def depart_warning(self, node):
2746 self.paragraph_style_stack.pop()
2748 def visit_attention(self, node):
2749 self.generate_admonition(node, 'attention')
2751 depart_attention = depart_warning
2753 def visit_caution(self, node):
2754 self.generate_admonition(node, 'caution')
2756 depart_caution = depart_warning
2758 def visit_danger(self, node):
2759 self.generate_admonition(node, 'danger')
2761 depart_danger = depart_warning
2763 def visit_error(self, node):
2764 self.generate_admonition(node, 'error')
2766 depart_error = depart_warning
2768 def visit_hint(self, node):
2769 self.generate_admonition(node, 'hint')
2771 depart_hint = depart_warning
2773 def visit_important(self, node):
2774 self.generate_admonition(node, 'important')
2776 depart_important = depart_warning
2778 def visit_note(self, node):
2779 self.generate_admonition(node, 'note')
2781 depart_note = depart_warning
2783 def visit_tip(self, node):
2784 self.generate_admonition(node, 'tip')
2786 depart_tip = depart_warning
2788 def visit_admonition(self, node):
2789 #import pdb; pdb.set_trace()
2790 title = None
2791 for child in node.children:
2792 if child.tagname == 'title':
2793 title = child.astext()
2794 if title is None:
2795 classes1 = node.get('classes')
2796 if classes1:
2797 title = classes1[0]
2798 self.generate_admonition(node, 'generic', title)
2800 depart_admonition = depart_warning
2802 def generate_admonition(self, node, label, title=None):
2803 el1 = SubElement(self.current_element, 'text:p', attrib = {
2804 'text:style-name': self.rststyle('admon-%s-hdr', ( label, )),
2806 if title:
2807 el1.text = title
2808 else:
2809 el1.text = '%s!' % (label.capitalize(), )
2810 s1 = self.rststyle('admon-%s-body', ( label, ))
2811 self.paragraph_style_stack.append(s1)
2814 # Roles (e.g. subscript, superscript, strong, ...
2816 def visit_subscript(self, node):
2817 el = self.append_child('text:span', attrib={
2818 'text:style-name': 'rststyle-subscript',
2820 self.set_current_element(el)
2822 def depart_subscript(self, node):
2823 self.set_to_parent()
2825 def visit_superscript(self, node):
2826 el = self.append_child('text:span', attrib={
2827 'text:style-name': 'rststyle-superscript',
2829 self.set_current_element(el)
2831 def depart_superscript(self, node):
2832 self.set_to_parent()
2835 # Use an own reader to modify transformations done.
2836 class Reader(standalone.Reader):
2838 def get_transforms(self):
2839 default = standalone.Reader.get_transforms(self)
2840 if self.settings.create_links:
2841 return default
2842 return [ i
2843 for i in default
2844 if i is not references.DanglingReferences ]