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