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