You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
3501 lines
132 KiB
3501 lines
132 KiB
7 years ago
|
# $Id: __init__.py 8131 2017-07-03 22:06:53Z dkuhlman $
|
||
|
# Author: Dave Kuhlman <dkuhlman@rexx.com>
|
||
|
# Copyright: This module has been placed in the public domain.
|
||
|
|
||
|
"""
|
||
|
Open Document Format (ODF) Writer.
|
||
|
|
||
|
"""
|
||
|
|
||
|
VERSION = '1.0a'
|
||
|
|
||
|
__docformat__ = 'reStructuredText'
|
||
|
|
||
|
|
||
|
import sys
|
||
|
import os
|
||
|
import os.path
|
||
|
import tempfile
|
||
|
import zipfile
|
||
|
from xml.dom import minidom
|
||
|
import time
|
||
|
import re
|
||
|
import StringIO
|
||
|
import copy
|
||
|
import urllib2
|
||
|
import itertools
|
||
|
import docutils
|
||
|
try:
|
||
|
import locale # module missing in Jython
|
||
|
except ImportError:
|
||
|
pass
|
||
|
from docutils import frontend, nodes, utils, writers, languages
|
||
|
from docutils.readers import standalone
|
||
|
from docutils.transforms import references
|
||
|
|
||
|
|
||
|
IMAGE_NAME_COUNTER = itertools.count()
|
||
|
WhichElementTree = ''
|
||
|
try:
|
||
|
# 1. Try to use lxml.
|
||
|
#from lxml import etree
|
||
|
#WhichElementTree = 'lxml'
|
||
|
raise ImportError('Ignoring lxml')
|
||
|
except ImportError, e:
|
||
|
try:
|
||
|
# 2. Try to use ElementTree from the Python standard library.
|
||
|
from xml.etree import ElementTree as etree
|
||
|
WhichElementTree = 'elementtree'
|
||
|
except ImportError, e:
|
||
|
try:
|
||
|
# 3. Try to use a version of ElementTree installed as a separate
|
||
|
# product.
|
||
|
from elementtree import ElementTree as etree
|
||
|
WhichElementTree = 'elementtree'
|
||
|
except ImportError, e:
|
||
|
s1 = 'Must install either a version of Python containing ' \
|
||
|
'ElementTree (Python version >=2.5) or install ElementTree.'
|
||
|
raise ImportError(s1)
|
||
|
|
||
|
#
|
||
|
# Import pygments and odtwriter pygments formatters if possible.
|
||
|
try:
|
||
|
import pygments
|
||
|
import pygments.lexers
|
||
|
from pygmentsformatter import OdtPygmentsProgFormatter, \
|
||
|
OdtPygmentsLaTeXFormatter
|
||
|
except (ImportError, SyntaxError), exp:
|
||
|
pygments = None
|
||
|
|
||
|
# check for the Python Imaging Library
|
||
|
try:
|
||
|
import PIL.Image
|
||
|
except ImportError:
|
||
|
try: # sometimes PIL modules are put in PYTHONPATH's root
|
||
|
import Image
|
||
|
class PIL(object): pass # dummy wrapper
|
||
|
PIL.Image = Image
|
||
|
except ImportError:
|
||
|
PIL = None
|
||
|
|
||
|
## import warnings
|
||
|
## warnings.warn('importing IPShellEmbed', UserWarning)
|
||
|
## from IPython.Shell import IPShellEmbed
|
||
|
## args = ['-pdb', '-pi1', 'In <\\#>: ', '-pi2', ' .\\D.: ',
|
||
|
## '-po', 'Out<\\#>: ', '-nosep']
|
||
|
## ipshell = IPShellEmbed(args,
|
||
|
## banner = 'Entering IPython. Press Ctrl-D to exit.',
|
||
|
## exit_msg = 'Leaving Interpreter, back to program.')
|
||
|
|
||
|
|
||
|
#
|
||
|
# ElementTree does not support getparent method (lxml does).
|
||
|
# This wrapper class and the following support functions provide
|
||
|
# that support for the ability to get the parent of an element.
|
||
|
#
|
||
|
if WhichElementTree == 'elementtree':
|
||
|
import weakref
|
||
|
_parents = weakref.WeakKeyDictionary()
|
||
|
if isinstance(etree.Element, type):
|
||
|
_ElementInterface = etree.Element
|
||
|
else:
|
||
|
_ElementInterface = etree._ElementInterface
|
||
|
class _ElementInterfaceWrapper(_ElementInterface):
|
||
|
def __init__(self, tag, attrib=None):
|
||
|
_ElementInterface.__init__(self, tag, attrib)
|
||
|
_parents[self] = None
|
||
|
def setparent(self, parent):
|
||
|
_parents[self] = parent
|
||
|
def getparent(self):
|
||
|
return _parents[self]
|
||
|
|
||
|
|
||
|
#
|
||
|
# Constants and globals
|
||
|
|
||
|
SPACES_PATTERN = re.compile(r'( +)')
|
||
|
TABS_PATTERN = re.compile(r'(\t+)')
|
||
|
FILL_PAT1 = re.compile(r'^ +')
|
||
|
FILL_PAT2 = re.compile(r' {2,}')
|
||
|
|
||
|
TABLESTYLEPREFIX = 'rststyle-table-'
|
||
|
TABLENAMEDEFAULT = '%s0' % TABLESTYLEPREFIX
|
||
|
TABLEPROPERTYNAMES = ('border', 'border-top', 'border-left',
|
||
|
'border-right', 'border-bottom', )
|
||
|
|
||
|
GENERATOR_DESC = 'Docutils.org/odf_odt'
|
||
|
|
||
|
NAME_SPACE_1 = 'urn:oasis:names:tc:opendocument:xmlns:office:1.0'
|
||
|
|
||
|
CONTENT_NAMESPACE_DICT = CNSD = {
|
||
|
# 'office:version': '1.0',
|
||
|
'chart': 'urn:oasis:names:tc:opendocument:xmlns:chart:1.0',
|
||
|
'dc': 'http://purl.org/dc/elements/1.1/',
|
||
|
'dom': 'http://www.w3.org/2001/xml-events',
|
||
|
'dr3d': 'urn:oasis:names:tc:opendocument:xmlns:dr3d:1.0',
|
||
|
'draw': 'urn:oasis:names:tc:opendocument:xmlns:drawing:1.0',
|
||
|
'fo': 'urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0',
|
||
|
'form': 'urn:oasis:names:tc:opendocument:xmlns:form:1.0',
|
||
|
'math': 'http://www.w3.org/1998/Math/MathML',
|
||
|
'meta': 'urn:oasis:names:tc:opendocument:xmlns:meta:1.0',
|
||
|
'number': 'urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0',
|
||
|
'office': NAME_SPACE_1,
|
||
|
'ooo': 'http://openoffice.org/2004/office',
|
||
|
'oooc': 'http://openoffice.org/2004/calc',
|
||
|
'ooow': 'http://openoffice.org/2004/writer',
|
||
|
'presentation': 'urn:oasis:names:tc:opendocument:xmlns:presentation:1.0',
|
||
|
|
||
|
'script': 'urn:oasis:names:tc:opendocument:xmlns:script:1.0',
|
||
|
'style': 'urn:oasis:names:tc:opendocument:xmlns:style:1.0',
|
||
|
'svg': 'urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0',
|
||
|
'table': 'urn:oasis:names:tc:opendocument:xmlns:table:1.0',
|
||
|
'text': 'urn:oasis:names:tc:opendocument:xmlns:text:1.0',
|
||
|
'xforms': 'http://www.w3.org/2002/xforms',
|
||
|
'xlink': 'http://www.w3.org/1999/xlink',
|
||
|
'xsd': 'http://www.w3.org/2001/XMLSchema',
|
||
|
'xsi': 'http://www.w3.org/2001/XMLSchema-instance',
|
||
|
}
|
||
|
|
||
|
STYLES_NAMESPACE_DICT = SNSD = {
|
||
|
# 'office:version': '1.0',
|
||
|
'chart': 'urn:oasis:names:tc:opendocument:xmlns:chart:1.0',
|
||
|
'dc': 'http://purl.org/dc/elements/1.1/',
|
||
|
'dom': 'http://www.w3.org/2001/xml-events',
|
||
|
'dr3d': 'urn:oasis:names:tc:opendocument:xmlns:dr3d:1.0',
|
||
|
'draw': 'urn:oasis:names:tc:opendocument:xmlns:drawing:1.0',
|
||
|
'fo': 'urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0',
|
||
|
'form': 'urn:oasis:names:tc:opendocument:xmlns:form:1.0',
|
||
|
'math': 'http://www.w3.org/1998/Math/MathML',
|
||
|
'meta': 'urn:oasis:names:tc:opendocument:xmlns:meta:1.0',
|
||
|
'number': 'urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0',
|
||
|
'office': NAME_SPACE_1,
|
||
|
'presentation': 'urn:oasis:names:tc:opendocument:xmlns:presentation:1.0',
|
||
|
'ooo': 'http://openoffice.org/2004/office',
|
||
|
'oooc': 'http://openoffice.org/2004/calc',
|
||
|
'ooow': 'http://openoffice.org/2004/writer',
|
||
|
'script': 'urn:oasis:names:tc:opendocument:xmlns:script:1.0',
|
||
|
'style': 'urn:oasis:names:tc:opendocument:xmlns:style:1.0',
|
||
|
'svg': 'urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0',
|
||
|
'table': 'urn:oasis:names:tc:opendocument:xmlns:table:1.0',
|
||
|
'text': 'urn:oasis:names:tc:opendocument:xmlns:text:1.0',
|
||
|
'xlink': 'http://www.w3.org/1999/xlink',
|
||
|
}
|
||
|
|
||
|
MANIFEST_NAMESPACE_DICT = MANNSD = {
|
||
|
'manifest': 'urn:oasis:names:tc:opendocument:xmlns:manifest:1.0',
|
||
|
}
|
||
|
|
||
|
META_NAMESPACE_DICT = METNSD = {
|
||
|
# 'office:version': '1.0',
|
||
|
'dc': 'http://purl.org/dc/elements/1.1/',
|
||
|
'meta': 'urn:oasis:names:tc:opendocument:xmlns:meta:1.0',
|
||
|
'office': NAME_SPACE_1,
|
||
|
'ooo': 'http://openoffice.org/2004/office',
|
||
|
'xlink': 'http://www.w3.org/1999/xlink',
|
||
|
}
|
||
|
|
||
|
#
|
||
|
# Attribute dictionaries for use with ElementTree (not lxml), which
|
||
|
# does not support use of nsmap parameter on Element() and SubElement().
|
||
|
|
||
|
CONTENT_NAMESPACE_ATTRIB = {
|
||
|
#'office:version': '1.0',
|
||
|
'xmlns:chart': 'urn:oasis:names:tc:opendocument:xmlns:chart:1.0',
|
||
|
'xmlns:dc': 'http://purl.org/dc/elements/1.1/',
|
||
|
'xmlns:dom': 'http://www.w3.org/2001/xml-events',
|
||
|
'xmlns:dr3d': 'urn:oasis:names:tc:opendocument:xmlns:dr3d:1.0',
|
||
|
'xmlns:draw': 'urn:oasis:names:tc:opendocument:xmlns:drawing:1.0',
|
||
|
'xmlns:fo': 'urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0',
|
||
|
'xmlns:form': 'urn:oasis:names:tc:opendocument:xmlns:form:1.0',
|
||
|
'xmlns:math': 'http://www.w3.org/1998/Math/MathML',
|
||
|
'xmlns:meta': 'urn:oasis:names:tc:opendocument:xmlns:meta:1.0',
|
||
|
'xmlns:number': 'urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0',
|
||
|
'xmlns:office': NAME_SPACE_1,
|
||
|
'xmlns:presentation': 'urn:oasis:names:tc:opendocument:xmlns:presentation:1.0',
|
||
|
'xmlns:ooo': 'http://openoffice.org/2004/office',
|
||
|
'xmlns:oooc': 'http://openoffice.org/2004/calc',
|
||
|
'xmlns:ooow': 'http://openoffice.org/2004/writer',
|
||
|
'xmlns:script': 'urn:oasis:names:tc:opendocument:xmlns:script:1.0',
|
||
|
'xmlns:style': 'urn:oasis:names:tc:opendocument:xmlns:style:1.0',
|
||
|
'xmlns:svg': 'urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0',
|
||
|
'xmlns:table': 'urn:oasis:names:tc:opendocument:xmlns:table:1.0',
|
||
|
'xmlns:text': 'urn:oasis:names:tc:opendocument:xmlns:text:1.0',
|
||
|
'xmlns:xforms': 'http://www.w3.org/2002/xforms',
|
||
|
'xmlns:xlink': 'http://www.w3.org/1999/xlink',
|
||
|
'xmlns:xsd': 'http://www.w3.org/2001/XMLSchema',
|
||
|
'xmlns:xsi': 'http://www.w3.org/2001/XMLSchema-instance',
|
||
|
}
|
||
|
|
||
|
STYLES_NAMESPACE_ATTRIB = {
|
||
|
#'office:version': '1.0',
|
||
|
'xmlns:chart': 'urn:oasis:names:tc:opendocument:xmlns:chart:1.0',
|
||
|
'xmlns:dc': 'http://purl.org/dc/elements/1.1/',
|
||
|
'xmlns:dom': 'http://www.w3.org/2001/xml-events',
|
||
|
'xmlns:dr3d': 'urn:oasis:names:tc:opendocument:xmlns:dr3d:1.0',
|
||
|
'xmlns:draw': 'urn:oasis:names:tc:opendocument:xmlns:drawing:1.0',
|
||
|
'xmlns:fo': 'urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0',
|
||
|
'xmlns:form': 'urn:oasis:names:tc:opendocument:xmlns:form:1.0',
|
||
|
'xmlns:math': 'http://www.w3.org/1998/Math/MathML',
|
||
|
'xmlns:meta': 'urn:oasis:names:tc:opendocument:xmlns:meta:1.0',
|
||
|
'xmlns:number': 'urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0',
|
||
|
'xmlns:office': NAME_SPACE_1,
|
||
|
'xmlns:presentation': 'urn:oasis:names:tc:opendocument:xmlns:presentation:1.0',
|
||
|
'xmlns:ooo': 'http://openoffice.org/2004/office',
|
||
|
'xmlns:oooc': 'http://openoffice.org/2004/calc',
|
||
|
'xmlns:ooow': 'http://openoffice.org/2004/writer',
|
||
|
'xmlns:script': 'urn:oasis:names:tc:opendocument:xmlns:script:1.0',
|
||
|
'xmlns:style': 'urn:oasis:names:tc:opendocument:xmlns:style:1.0',
|
||
|
'xmlns:svg': 'urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0',
|
||
|
'xmlns:table': 'urn:oasis:names:tc:opendocument:xmlns:table:1.0',
|
||
|
'xmlns:text': 'urn:oasis:names:tc:opendocument:xmlns:text:1.0',
|
||
|
'xmlns:xlink': 'http://www.w3.org/1999/xlink',
|
||
|
}
|
||
|
|
||
|
MANIFEST_NAMESPACE_ATTRIB = {
|
||
|
'xmlns:manifest': 'urn:oasis:names:tc:opendocument:xmlns:manifest:1.0',
|
||
|
}
|
||
|
|
||
|
META_NAMESPACE_ATTRIB = {
|
||
|
#'office:version': '1.0',
|
||
|
'xmlns:dc': 'http://purl.org/dc/elements/1.1/',
|
||
|
'xmlns:meta': 'urn:oasis:names:tc:opendocument:xmlns:meta:1.0',
|
||
|
'xmlns:office': NAME_SPACE_1,
|
||
|
'xmlns:ooo': 'http://openoffice.org/2004/office',
|
||
|
'xmlns:xlink': 'http://www.w3.org/1999/xlink',
|
||
|
}
|
||
|
|
||
|
|
||
|
#
|
||
|
# Functions
|
||
|
#
|
||
|
|
||
|
#
|
||
|
# ElementTree support functions.
|
||
|
# In order to be able to get the parent of elements, must use these
|
||
|
# instead of the functions with same name provided by ElementTree.
|
||
|
#
|
||
|
def Element(tag, attrib=None, nsmap=None, nsdict=CNSD):
|
||
|
if attrib is None:
|
||
|
attrib = {}
|
||
|
tag, attrib = fix_ns(tag, attrib, nsdict)
|
||
|
if WhichElementTree == 'lxml':
|
||
|
el = etree.Element(tag, attrib, nsmap=nsmap)
|
||
|
else:
|
||
|
el = _ElementInterfaceWrapper(tag, attrib)
|
||
|
return el
|
||
|
|
||
|
def SubElement(parent, tag, attrib=None, nsmap=None, nsdict=CNSD):
|
||
|
if attrib is None:
|
||
|
attrib = {}
|
||
|
tag, attrib = fix_ns(tag, attrib, nsdict)
|
||
|
if WhichElementTree == 'lxml':
|
||
|
el = etree.SubElement(parent, tag, attrib, nsmap=nsmap)
|
||
|
else:
|
||
|
el = _ElementInterfaceWrapper(tag, attrib)
|
||
|
parent.append(el)
|
||
|
el.setparent(parent)
|
||
|
return el
|
||
|
|
||
|
def fix_ns(tag, attrib, nsdict):
|
||
|
nstag = add_ns(tag, nsdict)
|
||
|
nsattrib = {}
|
||
|
for key, val in attrib.iteritems():
|
||
|
nskey = add_ns(key, nsdict)
|
||
|
nsattrib[nskey] = val
|
||
|
return nstag, nsattrib
|
||
|
|
||
|
def add_ns(tag, nsdict=CNSD):
|
||
|
if WhichElementTree == 'lxml':
|
||
|
nstag, name = tag.split(':')
|
||
|
ns = nsdict.get(nstag)
|
||
|
if ns is None:
|
||
|
raise RuntimeError, 'Invalid namespace prefix: %s' % nstag
|
||
|
tag = '{%s}%s' % (ns, name,)
|
||
|
return tag
|
||
|
|
||
|
def ToString(et):
|
||
|
outstream = StringIO.StringIO()
|
||
|
if sys.version_info >= (3, 2):
|
||
|
et.write(outstream, encoding="unicode")
|
||
|
else:
|
||
|
et.write(outstream)
|
||
|
s1 = outstream.getvalue()
|
||
|
outstream.close()
|
||
|
return s1
|
||
|
|
||
|
|
||
|
def escape_cdata(text):
|
||
|
text = text.replace("&", "&")
|
||
|
text = text.replace("<", "<")
|
||
|
text = text.replace(">", ">")
|
||
|
ascii = ''
|
||
|
for char in text:
|
||
|
if ord(char) >= ord("\x7f"):
|
||
|
ascii += "&#x%X;" % ( ord(char), )
|
||
|
else:
|
||
|
ascii += char
|
||
|
return ascii
|
||
|
|
||
|
|
||
|
|
||
|
WORD_SPLIT_PAT1 = re.compile(r'\b(\w*)\b\W*')
|
||
|
|
||
|
def split_words(line):
|
||
|
# We need whitespace at the end of the string for our regexpr.
|
||
|
line += ' '
|
||
|
words = []
|
||
|
pos1 = 0
|
||
|
mo = WORD_SPLIT_PAT1.search(line, pos1)
|
||
|
while mo is not None:
|
||
|
word = mo.groups()[0]
|
||
|
words.append(word)
|
||
|
pos1 = mo.end()
|
||
|
mo = WORD_SPLIT_PAT1.search(line, pos1)
|
||
|
return words
|
||
|
|
||
|
|
||
|
#
|
||
|
# Classes
|
||
|
#
|
||
|
|
||
|
|
||
|
class TableStyle(object):
|
||
|
def __init__(self, border=None, backgroundcolor=None):
|
||
|
self.border = border
|
||
|
self.backgroundcolor = backgroundcolor
|
||
|
def get_border_(self):
|
||
|
return self.border_
|
||
|
def set_border_(self, border):
|
||
|
self.border_ = border
|
||
|
border = property(get_border_, set_border_)
|
||
|
def get_backgroundcolor_(self):
|
||
|
return self.backgroundcolor_
|
||
|
def set_backgroundcolor_(self, backgroundcolor):
|
||
|
self.backgroundcolor_ = backgroundcolor
|
||
|
backgroundcolor = property(get_backgroundcolor_, set_backgroundcolor_)
|
||
|
|
||
|
BUILTIN_DEFAULT_TABLE_STYLE = TableStyle(
|
||
|
border = '0.0007in solid #000000')
|
||
|
|
||
|
#
|
||
|
# Information about the indentation level for lists nested inside
|
||
|
# other contexts, e.g. dictionary lists.
|
||
|
class ListLevel(object):
|
||
|
def __init__(self, level, sibling_level=True, nested_level=True):
|
||
|
self.level = level
|
||
|
self.sibling_level = sibling_level
|
||
|
self.nested_level = nested_level
|
||
|
def set_sibling(self, sibling_level): self.sibling_level = sibling_level
|
||
|
def get_sibling(self): return self.sibling_level
|
||
|
def set_nested(self, nested_level): self.nested_level = nested_level
|
||
|
def get_nested(self): return self.nested_level
|
||
|
def set_level(self, level): self.level = level
|
||
|
def get_level(self): return self.level
|
||
|
|
||
|
|
||
|
class Writer(writers.Writer):
|
||
|
|
||
|
MIME_TYPE = 'application/vnd.oasis.opendocument.text'
|
||
|
EXTENSION = '.odt'
|
||
|
|
||
|
supported = ('odt', )
|
||
|
"""Formats this writer supports."""
|
||
|
|
||
|
default_stylesheet = 'styles' + EXTENSION
|
||
|
|
||
|
default_stylesheet_path = utils.relative_path(
|
||
|
os.path.join(os.getcwd(), 'dummy'),
|
||
|
os.path.join(os.path.dirname(__file__), default_stylesheet))
|
||
|
|
||
|
default_template = 'template.txt'
|
||
|
|
||
|
default_template_path = utils.relative_path(
|
||
|
os.path.join(os.getcwd(), 'dummy'),
|
||
|
os.path.join(os.path.dirname(__file__), default_template))
|
||
|
|
||
|
settings_spec = (
|
||
|
'ODF-Specific Options',
|
||
|
None,
|
||
|
(
|
||
|
('Specify a stylesheet. '
|
||
|
'Default: "%s"' % default_stylesheet_path,
|
||
|
['--stylesheet'],
|
||
|
{
|
||
|
'default': default_stylesheet_path,
|
||
|
'dest': 'stylesheet'
|
||
|
}),
|
||
|
('Specify a configuration/mapping file relative to the '
|
||
|
'current working '
|
||
|
'directory for additional ODF options. '
|
||
|
'In particular, this file may contain a section named '
|
||
|
'"Formats" that maps default style names to '
|
||
|
'names to be used in the resulting output file allowing for '
|
||
|
'adhering to external standards. '
|
||
|
'For more info and the format of the configuration/mapping file, '
|
||
|
'see the odtwriter doc.',
|
||
|
['--odf-config-file'],
|
||
|
{'metavar': '<file>'}),
|
||
|
('Obfuscate email addresses to confuse harvesters while still '
|
||
|
'keeping email links usable with standards-compliant browsers.',
|
||
|
['--cloak-email-addresses'],
|
||
|
{'default': False,
|
||
|
'action': 'store_true',
|
||
|
'dest': 'cloak_email_addresses',
|
||
|
'validator': frontend.validate_boolean}),
|
||
|
('Do not obfuscate email addresses.',
|
||
|
['--no-cloak-email-addresses'],
|
||
|
{'default': False,
|
||
|
'action': 'store_false',
|
||
|
'dest': 'cloak_email_addresses',
|
||
|
'validator': frontend.validate_boolean}),
|
||
|
('Specify the thickness of table borders in thousands of a cm. '
|
||
|
'Default is 35.',
|
||
|
['--table-border-thickness'],
|
||
|
{'default': None,
|
||
|
'validator': frontend.validate_nonnegative_int}),
|
||
|
('Add syntax highlighting in literal code blocks.',
|
||
|
['--add-syntax-highlighting'],
|
||
|
{'default': False,
|
||
|
'action': 'store_true',
|
||
|
'dest': 'add_syntax_highlighting',
|
||
|
'validator': frontend.validate_boolean}),
|
||
|
('Do not add syntax highlighting in literal code blocks. (default)',
|
||
|
['--no-syntax-highlighting'],
|
||
|
{'default': False,
|
||
|
'action': 'store_false',
|
||
|
'dest': 'add_syntax_highlighting',
|
||
|
'validator': frontend.validate_boolean}),
|
||
|
('Create sections for headers. (default)',
|
||
|
['--create-sections'],
|
||
|
{'default': True,
|
||
|
'action': 'store_true',
|
||
|
'dest': 'create_sections',
|
||
|
'validator': frontend.validate_boolean}),
|
||
|
('Do not create sections for headers.',
|
||
|
['--no-sections'],
|
||
|
{'default': True,
|
||
|
'action': 'store_false',
|
||
|
'dest': 'create_sections',
|
||
|
'validator': frontend.validate_boolean}),
|
||
|
('Create links.',
|
||
|
['--create-links'],
|
||
|
{'default': False,
|
||
|
'action': 'store_true',
|
||
|
'dest': 'create_links',
|
||
|
'validator': frontend.validate_boolean}),
|
||
|
('Do not create links. (default)',
|
||
|
['--no-links'],
|
||
|
{'default': False,
|
||
|
'action': 'store_false',
|
||
|
'dest': 'create_links',
|
||
|
'validator': frontend.validate_boolean}),
|
||
|
('Generate endnotes at end of document, not footnotes '
|
||
|
'at bottom of page.',
|
||
|
['--endnotes-end-doc'],
|
||
|
{'default': False,
|
||
|
'action': 'store_true',
|
||
|
'dest': 'endnotes_end_doc',
|
||
|
'validator': frontend.validate_boolean}),
|
||
|
('Generate footnotes at bottom of page, not endnotes '
|
||
|
'at end of document. (default)',
|
||
|
['--no-endnotes-end-doc'],
|
||
|
{'default': False,
|
||
|
'action': 'store_false',
|
||
|
'dest': 'endnotes_end_doc',
|
||
|
'validator': frontend.validate_boolean}),
|
||
|
('Generate a bullet list table of contents, not '
|
||
|
'an ODF/oowriter table of contents.',
|
||
|
['--generate-list-toc'],
|
||
|
{'default': True,
|
||
|
'action': 'store_false',
|
||
|
'dest': 'generate_oowriter_toc',
|
||
|
'validator': frontend.validate_boolean}),
|
||
|
('Generate an ODF/oowriter table of contents, not '
|
||
|
'a bullet list. (default)',
|
||
|
['--generate-oowriter-toc'],
|
||
|
{'default': True,
|
||
|
'action': 'store_true',
|
||
|
'dest': 'generate_oowriter_toc',
|
||
|
'validator': frontend.validate_boolean}),
|
||
|
('Specify the contents of an custom header line. '
|
||
|
'See odf_odt writer documentation for details '
|
||
|
'about special field character sequences.',
|
||
|
['--custom-odt-header'],
|
||
|
{ 'default': '',
|
||
|
'dest': 'custom_header',
|
||
|
}),
|
||
|
('Specify the contents of an custom footer line. '
|
||
|
'See odf_odt writer documentation for details '
|
||
|
'about special field character sequences.',
|
||
|
['--custom-odt-footer'],
|
||
|
{ 'default': '',
|
||
|
'dest': 'custom_footer',
|
||
|
}),
|
||
|
)
|
||
|
)
|
||
|
|
||
|
settings_defaults = {
|
||
|
'output_encoding_error_handler': 'xmlcharrefreplace',
|
||
|
}
|
||
|
|
||
|
relative_path_settings = (
|
||
|
'stylesheet_path',
|
||
|
)
|
||
|
|
||
|
config_section = 'odf_odt writer'
|
||
|
config_section_dependencies = (
|
||
|
'writers',
|
||
|
)
|
||
|
|
||
|
def __init__(self):
|
||
|
writers.Writer.__init__(self)
|
||
|
self.translator_class = ODFTranslator
|
||
|
|
||
|
def translate(self):
|
||
|
self.settings = self.document.settings
|
||
|
self.visitor = self.translator_class(self.document)
|
||
|
self.visitor.retrieve_styles(self.EXTENSION)
|
||
|
self.document.walkabout(self.visitor)
|
||
|
self.visitor.add_doc_title()
|
||
|
self.assemble_my_parts()
|
||
|
self.output = self.parts['whole']
|
||
|
|
||
|
def assemble_my_parts(self):
|
||
|
"""Assemble the `self.parts` dictionary. Extend in subclasses.
|
||
|
"""
|
||
|
writers.Writer.assemble_parts(self)
|
||
|
f = tempfile.NamedTemporaryFile()
|
||
|
zfile = zipfile.ZipFile(f, 'w', zipfile.ZIP_DEFLATED)
|
||
|
self.write_zip_str(zfile, 'mimetype', self.MIME_TYPE,
|
||
|
compress_type=zipfile.ZIP_STORED)
|
||
|
content = self.visitor.content_astext()
|
||
|
self.write_zip_str(zfile, 'content.xml', content)
|
||
|
s1 = self.create_manifest()
|
||
|
self.write_zip_str(zfile, 'META-INF/manifest.xml', s1)
|
||
|
s1 = self.create_meta()
|
||
|
self.write_zip_str(zfile, 'meta.xml', s1)
|
||
|
s1 = self.get_stylesheet()
|
||
|
# Set default language in document to be generated.
|
||
|
# Language is specified by the -l/--language command line option.
|
||
|
# The format is described in BCP 47. If region is omitted, we use
|
||
|
# local.normalize(ll) to obtain a region.
|
||
|
language_code = None
|
||
|
region_code = None
|
||
|
if self.visitor.language_code:
|
||
|
language_ids = self.visitor.language_code.replace('_', '-')
|
||
|
language_ids = language_ids.split('-')
|
||
|
# first tag is primary language tag
|
||
|
language_code = language_ids[0].lower()
|
||
|
# 2-letter region subtag may follow in 2nd or 3rd position
|
||
|
for subtag in language_ids[1:]:
|
||
|
if len(subtag) == 2 and subtag.isalpha():
|
||
|
region_code = subtag.upper()
|
||
|
break
|
||
|
elif len(subtag) == 1:
|
||
|
break # 1-letter tag is never before valid region tag
|
||
|
if region_code is None:
|
||
|
try:
|
||
|
rcode = locale.normalize(language_code)
|
||
|
except NameError:
|
||
|
rcode = language_code
|
||
|
rcode = rcode.split('_')
|
||
|
if len(rcode) > 1:
|
||
|
rcode = rcode[1].split('.')
|
||
|
region_code = rcode[0]
|
||
|
if region_code is None:
|
||
|
self.document.reporter.warning(
|
||
|
'invalid language-region.\n'
|
||
|
' Could not find region with locale.normalize().\n'
|
||
|
' Please specify both language and region (ll-RR).\n'
|
||
|
' Examples: es-MX (Spanish, Mexico),\n'
|
||
|
' en-AU (English, Australia).')
|
||
|
# Update the style ElementTree with the language and region.
|
||
|
# Note that we keep a reference to the modified node because
|
||
|
# it is possible that ElementTree will throw away the Python
|
||
|
# representation of the updated node if we do not.
|
||
|
updated, new_dom_styles, updated_node = self.update_stylesheet(
|
||
|
self.visitor.get_dom_stylesheet(), language_code, region_code)
|
||
|
if updated:
|
||
|
s1 = etree.tostring(new_dom_styles)
|
||
|
self.write_zip_str(zfile, 'styles.xml', s1)
|
||
|
self.store_embedded_files(zfile)
|
||
|
self.copy_from_stylesheet(zfile)
|
||
|
zfile.close()
|
||
|
f.seek(0)
|
||
|
whole = f.read()
|
||
|
f.close()
|
||
|
self.parts['whole'] = whole
|
||
|
self.parts['encoding'] = self.document.settings.output_encoding
|
||
|
self.parts['version'] = docutils.__version__
|
||
|
|
||
|
def update_stylesheet(self, stylesheet_root, language_code, region_code):
|
||
|
"""Update xml style sheet element with language and region/country."""
|
||
|
updated = False
|
||
|
modified_nodes = set()
|
||
|
if language_code is not None or region_code is not None:
|
||
|
n1 = stylesheet_root.find(
|
||
|
'{urn:oasis:names:tc:opendocument:xmlns:office:1.0}'
|
||
|
'styles')
|
||
|
if n1 is None:
|
||
|
raise RuntimeError(
|
||
|
"Cannot find 'styles' element in styles.odt/styles.xml")
|
||
|
n2_nodes = n1.findall(
|
||
|
'{urn:oasis:names:tc:opendocument:xmlns:style:1.0}'
|
||
|
'default-style')
|
||
|
if not n2_nodes:
|
||
|
raise RuntimeError(
|
||
|
"Cannot find 'default-style' "
|
||
|
"element in styles.xml")
|
||
|
for node in n2_nodes:
|
||
|
family = node.attrib.get(
|
||
|
'{urn:oasis:names:tc:opendocument:xmlns:style:1.0}'
|
||
|
'family')
|
||
|
if family == 'paragraph' or family == 'graphic':
|
||
|
n3 = node.find(
|
||
|
'{urn:oasis:names:tc:opendocument:xmlns:style:1.0}'
|
||
|
'text-properties')
|
||
|
if n3 is None:
|
||
|
raise RuntimeError(
|
||
|
"Cannot find 'text-properties' "
|
||
|
"element in styles.xml")
|
||
|
if language_code is not None:
|
||
|
n3.attrib[
|
||
|
'{urn:oasis:names:tc:opendocument:xmlns:'
|
||
|
'xsl-fo-compatible:1.0}language'] = language_code
|
||
|
n3.attrib[
|
||
|
'{urn:oasis:names:tc:opendocument:xmlns:'
|
||
|
'style:1.0}language-complex'] = language_code
|
||
|
updated = True
|
||
|
modified_nodes.add(n3)
|
||
|
if region_code is not None:
|
||
|
n3.attrib[
|
||
|
'{urn:oasis:names:tc:opendocument:xmlns:'
|
||
|
'xsl-fo-compatible:1.0}country'] = region_code
|
||
|
n3.attrib[
|
||
|
'{urn:oasis:names:tc:opendocument:xmlns:'
|
||
|
'style:1.0}country-complex'] = region_code
|
||
|
updated = True
|
||
|
modified_nodes.add(n3)
|
||
|
return updated, stylesheet_root, modified_nodes
|
||
|
|
||
|
def write_zip_str(
|
||
|
self, zfile, name, bytes, compress_type=zipfile.ZIP_DEFLATED):
|
||
|
localtime = time.localtime(time.time())
|
||
|
zinfo = zipfile.ZipInfo(name, localtime)
|
||
|
# Add some standard UNIX file access permissions (-rw-r--r--).
|
||
|
zinfo.external_attr = (0x81a4 & 0xFFFF) << 16L
|
||
|
zinfo.compress_type = compress_type
|
||
|
zfile.writestr(zinfo, bytes)
|
||
|
|
||
|
def store_embedded_files(self, zfile):
|
||
|
embedded_files = self.visitor.get_embedded_file_list()
|
||
|
for source, destination in embedded_files:
|
||
|
if source is None:
|
||
|
continue
|
||
|
try:
|
||
|
zfile.write(source, destination)
|
||
|
except OSError, e:
|
||
|
self.document.reporter.warning(
|
||
|
"Can't open file %s." % (source, ))
|
||
|
|
||
|
def get_settings(self):
|
||
|
"""
|
||
|
modeled after get_stylesheet
|
||
|
"""
|
||
|
stylespath = self.settings.stylesheet
|
||
|
zfile = zipfile.ZipFile(stylespath, 'r')
|
||
|
s1 = zfile.read('settings.xml')
|
||
|
zfile.close()
|
||
|
return s1
|
||
|
|
||
|
def get_stylesheet(self):
|
||
|
"""Get the stylesheet from the visitor.
|
||
|
Ask the visitor to setup the page.
|
||
|
"""
|
||
|
s1 = self.visitor.setup_page()
|
||
|
return s1
|
||
|
|
||
|
def copy_from_stylesheet(self, outzipfile):
|
||
|
"""Copy images, settings, etc from the stylesheet doc into target doc.
|
||
|
"""
|
||
|
stylespath = self.settings.stylesheet
|
||
|
inzipfile = zipfile.ZipFile(stylespath, 'r')
|
||
|
# Copy the styles.
|
||
|
s1 = inzipfile.read('settings.xml')
|
||
|
self.write_zip_str(outzipfile, 'settings.xml', s1)
|
||
|
# Copy the images.
|
||
|
namelist = inzipfile.namelist()
|
||
|
for name in namelist:
|
||
|
if name.startswith('Pictures/'):
|
||
|
imageobj = inzipfile.read(name)
|
||
|
outzipfile.writestr(name, imageobj)
|
||
|
inzipfile.close()
|
||
|
|
||
|
def assemble_parts(self):
|
||
|
pass
|
||
|
|
||
|
def create_manifest(self):
|
||
|
if WhichElementTree == 'lxml':
|
||
|
root = Element('manifest:manifest',
|
||
|
nsmap=MANIFEST_NAMESPACE_DICT,
|
||
|
nsdict=MANIFEST_NAMESPACE_DICT,
|
||
|
)
|
||
|
else:
|
||
|
root = Element('manifest:manifest',
|
||
|
attrib=MANIFEST_NAMESPACE_ATTRIB,
|
||
|
nsdict=MANIFEST_NAMESPACE_DICT,
|
||
|
)
|
||
|
doc = etree.ElementTree(root)
|
||
|
SubElement(root, 'manifest:file-entry', attrib={
|
||
|
'manifest:media-type': self.MIME_TYPE,
|
||
|
'manifest:full-path': '/',
|
||
|
}, nsdict=MANNSD)
|
||
|
SubElement(root, 'manifest:file-entry', attrib={
|
||
|
'manifest:media-type': 'text/xml',
|
||
|
'manifest:full-path': 'content.xml',
|
||
|
}, nsdict=MANNSD)
|
||
|
SubElement(root, 'manifest:file-entry', attrib={
|
||
|
'manifest:media-type': 'text/xml',
|
||
|
'manifest:full-path': 'styles.xml',
|
||
|
}, nsdict=MANNSD)
|
||
|
SubElement(root, 'manifest:file-entry', attrib={
|
||
|
'manifest:media-type': 'text/xml',
|
||
|
'manifest:full-path': 'settings.xml',
|
||
|
}, nsdict=MANNSD)
|
||
|
SubElement(root, 'manifest:file-entry', attrib={
|
||
|
'manifest:media-type': 'text/xml',
|
||
|
'manifest:full-path': 'meta.xml',
|
||
|
}, nsdict=MANNSD)
|
||
|
s1 = ToString(doc)
|
||
|
doc = minidom.parseString(s1)
|
||
|
s1 = doc.toprettyxml(' ')
|
||
|
return s1
|
||
|
|
||
|
def create_meta(self):
|
||
|
if WhichElementTree == 'lxml':
|
||
|
root = Element('office:document-meta',
|
||
|
nsmap=META_NAMESPACE_DICT,
|
||
|
nsdict=META_NAMESPACE_DICT,
|
||
|
)
|
||
|
else:
|
||
|
root = Element('office:document-meta',
|
||
|
attrib=META_NAMESPACE_ATTRIB,
|
||
|
nsdict=META_NAMESPACE_DICT,
|
||
|
)
|
||
|
doc = etree.ElementTree(root)
|
||
|
root = SubElement(root, 'office:meta', nsdict=METNSD)
|
||
|
el1 = SubElement(root, 'meta:generator', nsdict=METNSD)
|
||
|
el1.text = 'Docutils/rst2odf.py/%s' % (VERSION, )
|
||
|
s1 = os.environ.get('USER', '')
|
||
|
el1 = SubElement(root, 'meta:initial-creator', nsdict=METNSD)
|
||
|
el1.text = s1
|
||
|
s2 = time.strftime('%Y-%m-%dT%H:%M:%S', time.localtime())
|
||
|
el1 = SubElement(root, 'meta:creation-date', nsdict=METNSD)
|
||
|
el1.text = s2
|
||
|
el1 = SubElement(root, 'dc:creator', nsdict=METNSD)
|
||
|
el1.text = s1
|
||
|
el1 = SubElement(root, 'dc:date', nsdict=METNSD)
|
||
|
el1.text = s2
|
||
|
el1 = SubElement(root, 'dc:language', nsdict=METNSD)
|
||
|
el1.text = 'en-US'
|
||
|
el1 = SubElement(root, 'meta:editing-cycles', nsdict=METNSD)
|
||
|
el1.text = '1'
|
||
|
el1 = SubElement(root, 'meta:editing-duration', nsdict=METNSD)
|
||
|
el1.text = 'PT00M01S'
|
||
|
title = self.visitor.get_title()
|
||
|
el1 = SubElement(root, 'dc:title', nsdict=METNSD)
|
||
|
if title:
|
||
|
el1.text = title
|
||
|
else:
|
||
|
el1.text = '[no title]'
|
||
|
meta_dict = self.visitor.get_meta_dict()
|
||
|
keywordstr = meta_dict.get('keywords')
|
||
|
if keywordstr is not None:
|
||
|
keywords = split_words(keywordstr)
|
||
|
for keyword in keywords:
|
||
|
el1 = SubElement(root, 'meta:keyword', nsdict=METNSD)
|
||
|
el1.text = keyword
|
||
|
description = meta_dict.get('description')
|
||
|
if description is not None:
|
||
|
el1 = SubElement(root, 'dc:description', nsdict=METNSD)
|
||
|
el1.text = description
|
||
|
s1 = ToString(doc)
|
||
|
#doc = minidom.parseString(s1)
|
||
|
#s1 = doc.toprettyxml(' ')
|
||
|
return s1
|
||
|
|
||
|
|
||
|
# class ODFTranslator(nodes.SparseNodeVisitor):
|
||
|
class ODFTranslator(nodes.GenericNodeVisitor):
|
||
|
|
||
|
used_styles = (
|
||
|
'attribution', 'blockindent', 'blockquote', 'blockquote-bulletitem',
|
||
|
'blockquote-bulletlist', 'blockquote-enumitem', 'blockquote-enumlist',
|
||
|
'bulletitem', 'bulletlist',
|
||
|
'caption', 'legend',
|
||
|
'centeredtextbody', 'codeblock', 'codeblock-indented',
|
||
|
'codeblock-classname', 'codeblock-comment', 'codeblock-functionname',
|
||
|
'codeblock-keyword', 'codeblock-name', 'codeblock-number',
|
||
|
'codeblock-operator', 'codeblock-string', 'emphasis', 'enumitem',
|
||
|
'enumlist', 'epigraph', 'epigraph-bulletitem', 'epigraph-bulletlist',
|
||
|
'epigraph-enumitem', 'epigraph-enumlist', 'footer',
|
||
|
'footnote', 'citation',
|
||
|
'header', 'highlights', 'highlights-bulletitem',
|
||
|
'highlights-bulletlist', 'highlights-enumitem', 'highlights-enumlist',
|
||
|
'horizontalline', 'inlineliteral', 'quotation', 'rubric',
|
||
|
'strong', 'table-title', 'textbody', 'tocbulletlist', 'tocenumlist',
|
||
|
'title',
|
||
|
'subtitle',
|
||
|
'heading1',
|
||
|
'heading2',
|
||
|
'heading3',
|
||
|
'heading4',
|
||
|
'heading5',
|
||
|
'heading6',
|
||
|
'heading7',
|
||
|
'admon-attention-hdr',
|
||
|
'admon-attention-body',
|
||
|
'admon-caution-hdr',
|
||
|
'admon-caution-body',
|
||
|
'admon-danger-hdr',
|
||
|
'admon-danger-body',
|
||
|
'admon-error-hdr',
|
||
|
'admon-error-body',
|
||
|
'admon-generic-hdr',
|
||
|
'admon-generic-body',
|
||
|
'admon-hint-hdr',
|
||
|
'admon-hint-body',
|
||
|
'admon-important-hdr',
|
||
|
'admon-important-body',
|
||
|
'admon-note-hdr',
|
||
|
'admon-note-body',
|
||
|
'admon-tip-hdr',
|
||
|
'admon-tip-body',
|
||
|
'admon-warning-hdr',
|
||
|
'admon-warning-body',
|
||
|
'tableoption',
|
||
|
'tableoption.%c', 'tableoption.%c%d', 'Table%d', 'Table%d.%c',
|
||
|
'Table%d.%c%d',
|
||
|
'lineblock1',
|
||
|
'lineblock2',
|
||
|
'lineblock3',
|
||
|
'lineblock4',
|
||
|
'lineblock5',
|
||
|
'lineblock6',
|
||
|
'image', 'figureframe',
|
||
|
)
|
||
|
|
||
|
def __init__(self, document):
|
||
|
#nodes.SparseNodeVisitor.__init__(self, document)
|
||
|
nodes.GenericNodeVisitor.__init__(self, document)
|
||
|
self.settings = document.settings
|
||
|
self.language_code = self.settings.language_code
|
||
|
self.language = languages.get_language(
|
||
|
self.language_code,
|
||
|
document.reporter)
|
||
|
self.format_map = {}
|
||
|
if self.settings.odf_config_file:
|
||
|
from ConfigParser import ConfigParser
|
||
|
|
||
|
parser = ConfigParser()
|
||
|
parser.read(self.settings.odf_config_file)
|
||
|
for rststyle, format in parser.items("Formats"):
|
||
|
if rststyle not in self.used_styles:
|
||
|
self.document.reporter.warning(
|
||
|
'Style "%s" is not a style used by odtwriter.' % (
|
||
|
rststyle, ))
|
||
|
if sys.version_info.major == 2:
|
||
|
self.format_map[rststyle] = format.decode('utf-8')
|
||
|
self.section_level = 0
|
||
|
self.section_count = 0
|
||
|
# Create ElementTree content and styles documents.
|
||
|
if WhichElementTree == 'lxml':
|
||
|
root = Element(
|
||
|
'office:document-content',
|
||
|
nsmap=CONTENT_NAMESPACE_DICT,
|
||
|
)
|
||
|
else:
|
||
|
root = Element(
|
||
|
'office:document-content',
|
||
|
attrib=CONTENT_NAMESPACE_ATTRIB,
|
||
|
)
|
||
|
self.content_tree = etree.ElementTree(element=root)
|
||
|
self.current_element = root
|
||
|
SubElement(root, 'office:scripts')
|
||
|
SubElement(root, 'office:font-face-decls')
|
||
|
el = SubElement(root, 'office:automatic-styles')
|
||
|
self.automatic_styles = el
|
||
|
el = SubElement(root, 'office:body')
|
||
|
el = self.generate_content_element(el)
|
||
|
self.current_element = el
|
||
|
self.body_text_element = el
|
||
|
self.paragraph_style_stack = [self.rststyle('textbody'), ]
|
||
|
self.list_style_stack = []
|
||
|
self.table_count = 0
|
||
|
self.column_count = ord('A') - 1
|
||
|
self.trace_level = -1
|
||
|
self.optiontablestyles_generated = False
|
||
|
self.field_name = None
|
||
|
self.field_element = None
|
||
|
self.title = None
|
||
|
self.image_count = 0
|
||
|
self.image_style_count = 0
|
||
|
self.image_dict = {}
|
||
|
self.embedded_file_list = []
|
||
|
self.syntaxhighlighting = 1
|
||
|
self.syntaxhighlight_lexer = 'python'
|
||
|
self.header_content = []
|
||
|
self.footer_content = []
|
||
|
self.in_header = False
|
||
|
self.in_footer = False
|
||
|
self.blockstyle = ''
|
||
|
self.in_table_of_contents = False
|
||
|
self.table_of_content_index_body = None
|
||
|
self.list_level = 0
|
||
|
self.def_list_level = 0
|
||
|
self.footnote_ref_dict = {}
|
||
|
self.footnote_list = []
|
||
|
self.footnote_chars_idx = 0
|
||
|
self.footnote_level = 0
|
||
|
self.pending_ids = [ ]
|
||
|
self.in_paragraph = False
|
||
|
self.found_doc_title = False
|
||
|
self.bumped_list_level_stack = []
|
||
|
self.meta_dict = {}
|
||
|
self.line_block_level = 0
|
||
|
self.line_indent_level = 0
|
||
|
self.citation_id = None
|
||
|
self.style_index = 0 # use to form unique style names
|
||
|
self.str_stylesheet = ''
|
||
|
self.str_stylesheetcontent = ''
|
||
|
self.dom_stylesheet = None
|
||
|
self.table_styles = None
|
||
|
self.in_citation = False
|
||
|
|
||
|
# Keep track of nested styling classes
|
||
|
self.inline_style_count_stack = []
|
||
|
|
||
|
def get_str_stylesheet(self):
|
||
|
return self.str_stylesheet
|
||
|
|
||
|
def retrieve_styles(self, extension):
|
||
|
"""Retrieve the stylesheet from either a .xml file or from
|
||
|
a .odt (zip) file. Return the content as a string.
|
||
|
"""
|
||
|
s2 = None
|
||
|
stylespath = self.settings.stylesheet
|
||
|
ext = os.path.splitext(stylespath)[1]
|
||
|
if ext == '.xml':
|
||
|
stylesfile = open(stylespath, 'r')
|
||
|
s1 = stylesfile.read()
|
||
|
stylesfile.close()
|
||
|
elif ext == extension:
|
||
|
zfile = zipfile.ZipFile(stylespath, 'r')
|
||
|
s1 = zfile.read('styles.xml')
|
||
|
s2 = zfile.read('content.xml')
|
||
|
zfile.close()
|
||
|
else:
|
||
|
raise RuntimeError, 'stylesheet path (%s) must be %s or .xml file' %(stylespath, extension)
|
||
|
self.str_stylesheet = s1
|
||
|
self.str_stylesheetcontent = s2
|
||
|
self.dom_stylesheet = etree.fromstring(self.str_stylesheet)
|
||
|
self.dom_stylesheetcontent = etree.fromstring(self.str_stylesheetcontent)
|
||
|
self.table_styles = self.extract_table_styles(s2)
|
||
|
|
||
|
def extract_table_styles(self, styles_str):
|
||
|
root = etree.fromstring(styles_str)
|
||
|
table_styles = {}
|
||
|
auto_styles = root.find(
|
||
|
'{%s}automatic-styles' % (CNSD['office'], ))
|
||
|
for stylenode in auto_styles:
|
||
|
name = stylenode.get('{%s}name' % (CNSD['style'], ))
|
||
|
tablename = name.split('.')[0]
|
||
|
family = stylenode.get('{%s}family' % (CNSD['style'], ))
|
||
|
if name.startswith(TABLESTYLEPREFIX):
|
||
|
tablestyle = table_styles.get(tablename)
|
||
|
if tablestyle is None:
|
||
|
tablestyle = TableStyle()
|
||
|
table_styles[tablename] = tablestyle
|
||
|
if family == 'table':
|
||
|
properties = stylenode.find(
|
||
|
'{%s}table-properties' % (CNSD['style'], ))
|
||
|
property = properties.get('{%s}%s' % (CNSD['fo'],
|
||
|
'background-color', ))
|
||
|
if property is not None and property != 'none':
|
||
|
tablestyle.backgroundcolor = property
|
||
|
elif family == 'table-cell':
|
||
|
properties = stylenode.find(
|
||
|
'{%s}table-cell-properties' % (CNSD['style'], ))
|
||
|
if properties is not None:
|
||
|
border = self.get_property(properties)
|
||
|
if border is not None:
|
||
|
tablestyle.border = border
|
||
|
return table_styles
|
||
|
|
||
|
def get_property(self, stylenode):
|
||
|
border = None
|
||
|
for propertyname in TABLEPROPERTYNAMES:
|
||
|
border = stylenode.get('{%s}%s' % (CNSD['fo'], propertyname, ))
|
||
|
if border is not None and border != 'none':
|
||
|
return border
|
||
|
return border
|
||
|
|
||
|
def add_doc_title(self):
|
||
|
text = self.settings.title
|
||
|
if text:
|
||
|
self.title = text
|
||
|
if not self.found_doc_title:
|
||
|
el = Element('text:p', attrib = {
|
||
|
'text:style-name': self.rststyle('title'),
|
||
|
})
|
||
|
el.text = text
|
||
|
self.body_text_element.insert(0, el)
|
||
|
el = self.find_first_text_p(self.body_text_element)
|
||
|
if el is not None:
|
||
|
self.attach_page_style(el)
|
||
|
|
||
|
def find_first_text_p(self, el):
|
||
|
"""Search the generated doc and return the first <text:p> element.
|
||
|
"""
|
||
|
if (
|
||
|
el.tag == 'text:p' or
|
||
|
el.tag == 'text:h'
|
||
|
):
|
||
|
return el
|
||
|
elif el.getchildren():
|
||
|
for child in el.getchildren():
|
||
|
el1 = self.find_first_text_p(child)
|
||
|
if el1 is not None:
|
||
|
return el1
|
||
|
return None
|
||
|
else:
|
||
|
return None
|
||
|
|
||
|
def attach_page_style(self, el):
|
||
|
"""Attach the default page style.
|
||
|
|
||
|
Create an automatic-style that refers to the current style
|
||
|
of this element and that refers to the default page style.
|
||
|
"""
|
||
|
current_style = el.get('text:style-name')
|
||
|
style_name = 'P1003'
|
||
|
el1 = SubElement(
|
||
|
self.automatic_styles, 'style:style', attrib={
|
||
|
'style:name': style_name,
|
||
|
'style:master-page-name': "rststyle-pagedefault",
|
||
|
'style:family': "paragraph",
|
||
|
}, nsdict=SNSD)
|
||
|
if current_style:
|
||
|
el1.set('style:parent-style-name', current_style)
|
||
|
el.set('text:style-name', style_name)
|
||
|
|
||
|
def rststyle(self, name, parameters=()):
|
||
|
"""
|
||
|
Returns the style name to use for the given style.
|
||
|
|
||
|
If `parameters` is given `name` must contain a matching number of
|
||
|
``%`` and is used as a format expression with `parameters` as
|
||
|
the value.
|
||
|
"""
|
||
|
name1 = name % parameters
|
||
|
stylename = self.format_map.get(name1, 'rststyle-%s' % name1)
|
||
|
return stylename
|
||
|
|
||
|
def generate_content_element(self, root):
|
||
|
return SubElement(root, 'office:text')
|
||
|
|
||
|
def setup_page(self):
|
||
|
self.setup_paper(self.dom_stylesheet)
|
||
|
if (len(self.header_content) > 0 or len(self.footer_content) > 0 or
|
||
|
self.settings.custom_header or self.settings.custom_footer):
|
||
|
self.add_header_footer(self.dom_stylesheet)
|
||
|
new_content = etree.tostring(self.dom_stylesheet)
|
||
|
return new_content
|
||
|
|
||
|
def get_dom_stylesheet(self):
|
||
|
return self.dom_stylesheet
|
||
|
|
||
|
def setup_paper(self, root_el):
|
||
|
try:
|
||
|
fin = os.popen("paperconf -s 2> /dev/null")
|
||
|
w, h = map(float, fin.read().split())
|
||
|
fin.close()
|
||
|
except:
|
||
|
w, h = 612, 792 # default to Letter
|
||
|
def walk(el):
|
||
|
if el.tag == "{%s}page-layout-properties" % SNSD["style"] and \
|
||
|
not el.attrib.has_key("{%s}page-width" % SNSD["fo"]):
|
||
|
el.attrib["{%s}page-width" % SNSD["fo"]] = "%.3fpt" % w
|
||
|
el.attrib["{%s}page-height" % SNSD["fo"]] = "%.3fpt" % h
|
||
|
el.attrib["{%s}margin-left" % SNSD["fo"]] = \
|
||
|
el.attrib["{%s}margin-right" % SNSD["fo"]] = \
|
||
|
"%.3fpt" % (.1 * w)
|
||
|
el.attrib["{%s}margin-top" % SNSD["fo"]] = \
|
||
|
el.attrib["{%s}margin-bottom" % SNSD["fo"]] = \
|
||
|
"%.3fpt" % (.1 * h)
|
||
|
else:
|
||
|
for subel in el.getchildren(): walk(subel)
|
||
|
walk(root_el)
|
||
|
|
||
|
def add_header_footer(self, root_el):
|
||
|
automatic_styles = root_el.find(
|
||
|
'{%s}automatic-styles' % SNSD['office'])
|
||
|
path = '{%s}master-styles' % (NAME_SPACE_1, )
|
||
|
master_el = root_el.find(path)
|
||
|
if master_el is None:
|
||
|
return
|
||
|
path = '{%s}master-page' % (SNSD['style'], )
|
||
|
master_el_container = master_el.findall(path)
|
||
|
master_el = None
|
||
|
target_attrib = '{%s}name' % (SNSD['style'], )
|
||
|
target_name = self.rststyle('pagedefault')
|
||
|
for el in master_el_container:
|
||
|
if el.get(target_attrib) == target_name:
|
||
|
master_el = el
|
||
|
break
|
||
|
if master_el is None:
|
||
|
return
|
||
|
el1 = master_el
|
||
|
if self.header_content or self.settings.custom_header:
|
||
|
if WhichElementTree == 'lxml':
|
||
|
el2 = SubElement(el1, 'style:header', nsdict=SNSD)
|
||
|
else:
|
||
|
el2 = SubElement(el1, 'style:header',
|
||
|
attrib=STYLES_NAMESPACE_ATTRIB,
|
||
|
nsdict=STYLES_NAMESPACE_DICT,
|
||
|
)
|
||
|
for el in self.header_content:
|
||
|
attrkey = add_ns('text:style-name', nsdict=SNSD)
|
||
|
el.attrib[attrkey] = self.rststyle('header')
|
||
|
el2.append(el)
|
||
|
if self.settings.custom_header:
|
||
|
elcustom = self.create_custom_headfoot(el2,
|
||
|
self.settings.custom_header, 'header', automatic_styles)
|
||
|
if self.footer_content or self.settings.custom_footer:
|
||
|
if WhichElementTree == 'lxml':
|
||
|
el2 = SubElement(el1, 'style:footer', nsdict=SNSD)
|
||
|
else:
|
||
|
el2 = SubElement(el1, 'style:footer',
|
||
|
attrib=STYLES_NAMESPACE_ATTRIB,
|
||
|
nsdict=STYLES_NAMESPACE_DICT,
|
||
|
)
|
||
|
for el in self.footer_content:
|
||
|
attrkey = add_ns('text:style-name', nsdict=SNSD)
|
||
|
el.attrib[attrkey] = self.rststyle('footer')
|
||
|
el2.append(el)
|
||
|
if self.settings.custom_footer:
|
||
|
elcustom = self.create_custom_headfoot(el2,
|
||
|
self.settings.custom_footer, 'footer', automatic_styles)
|
||
|
|
||
|
code_none, code_field, code_text = range(3)
|
||
|
field_pat = re.compile(r'%(..?)%')
|
||
|
|
||
|
def create_custom_headfoot(self, parent, text, style_name, automatic_styles):
|
||
|
parent = SubElement(parent, 'text:p', attrib={
|
||
|
'text:style-name': self.rststyle(style_name),
|
||
|
})
|
||
|
current_element = None
|
||
|
field_iter = self.split_field_specifiers_iter(text)
|
||
|
for item in field_iter:
|
||
|
if item[0] == ODFTranslator.code_field:
|
||
|
if item[1] not in ('p', 'P',
|
||
|
't1', 't2', 't3', 't4',
|
||
|
'd1', 'd2', 'd3', 'd4', 'd5',
|
||
|
's', 't', 'a'):
|
||
|
msg = 'bad field spec: %%%s%%' % (item[1], )
|
||
|
raise RuntimeError, msg
|
||
|
el1 = self.make_field_element(parent,
|
||
|
item[1], style_name, automatic_styles)
|
||
|
if el1 is None:
|
||
|
msg = 'bad field spec: %%%s%%' % (item[1], )
|
||
|
raise RuntimeError, msg
|
||
|
else:
|
||
|
current_element = el1
|
||
|
else:
|
||
|
if current_element is None:
|
||
|
parent.text = item[1]
|
||
|
else:
|
||
|
current_element.tail = item[1]
|
||
|
|
||
|
def make_field_element(self, parent, text, style_name, automatic_styles):
|
||
|
if text == 'p':
|
||
|
el1 = SubElement(parent, 'text:page-number', attrib={
|
||
|
#'text:style-name': self.rststyle(style_name),
|
||
|
'text:select-page': 'current',
|
||
|
})
|
||
|
elif text == 'P':
|
||
|
el1 = SubElement(parent, 'text:page-count', attrib={
|
||
|
#'text:style-name': self.rststyle(style_name),
|
||
|
})
|
||
|
elif text == 't1':
|
||
|
self.style_index += 1
|
||
|
el1 = SubElement(parent, 'text:time', attrib={
|
||
|
'text:style-name': self.rststyle(style_name),
|
||
|
'text:fixed': 'true',
|
||
|
'style:data-style-name': 'rst-time-style-%d' % self.style_index,
|
||
|
})
|
||
|
el2 = SubElement(automatic_styles, 'number:time-style', attrib={
|
||
|
'style:name': 'rst-time-style-%d' % self.style_index,
|
||
|
'xmlns:number': SNSD['number'],
|
||
|
'xmlns:style': SNSD['style'],
|
||
|
})
|
||
|
el3 = SubElement(el2, 'number:hours', attrib={
|
||
|
'number:style': 'long',
|
||
|
})
|
||
|
el3 = SubElement(el2, 'number:text')
|
||
|
el3.text = ':'
|
||
|
el3 = SubElement(el2, 'number:minutes', attrib={
|
||
|
'number:style': 'long',
|
||
|
})
|
||
|
elif text == 't2':
|
||
|
self.style_index += 1
|
||
|
el1 = SubElement(parent, 'text:time', attrib={
|
||
|
'text:style-name': self.rststyle(style_name),
|
||
|
'text:fixed': 'true',
|
||
|
'style:data-style-name': 'rst-time-style-%d' % self.style_index,
|
||
|
})
|
||
|
el2 = SubElement(automatic_styles, 'number:time-style', attrib={
|
||
|
'style:name': 'rst-time-style-%d' % self.style_index,
|
||
|
'xmlns:number': SNSD['number'],
|
||
|
'xmlns:style': SNSD['style'],
|
||
|
})
|
||
|
el3 = SubElement(el2, 'number:hours', attrib={
|
||
|
'number:style': 'long',
|
||
|
})
|
||
|
el3 = SubElement(el2, 'number:text')
|
||
|
el3.text = ':'
|
||
|
el3 = SubElement(el2, 'number:minutes', attrib={
|
||
|
'number:style': 'long',
|
||
|
})
|
||
|
el3 = SubElement(el2, 'number:text')
|
||
|
el3.text = ':'
|
||
|
el3 = SubElement(el2, 'number:seconds', attrib={
|
||
|
'number:style': 'long',
|
||
|
})
|
||
|
elif text == 't3':
|
||
|
self.style_index += 1
|
||
|
el1 = SubElement(parent, 'text:time', attrib={
|
||
|
'text:style-name': self.rststyle(style_name),
|
||
|
'text:fixed': 'true',
|
||
|
'style:data-style-name': 'rst-time-style-%d' % self.style_index,
|
||
|
})
|
||
|
el2 = SubElement(automatic_styles, 'number:time-style', attrib={
|
||
|
'style:name': 'rst-time-style-%d' % self.style_index,
|
||
|
'xmlns:number': SNSD['number'],
|
||
|
'xmlns:style': SNSD['style'],
|
||
|
})
|
||
|
el3 = SubElement(el2, 'number:hours', attrib={
|
||
|
'number:style': 'long',
|
||
|
})
|
||
|
el3 = SubElement(el2, 'number:text')
|
||
|
el3.text = ':'
|
||
|
el3 = SubElement(el2, 'number:minutes', attrib={
|
||
|
'number:style': 'long',
|
||
|
})
|
||
|
el3 = SubElement(el2, 'number:text')
|
||
|
el3.text = ' '
|
||
|
el3 = SubElement(el2, 'number:am-pm')
|
||
|
elif text == 't4':
|
||
|
self.style_index += 1
|
||
|
el1 = SubElement(parent, 'text:time', attrib={
|
||
|
'text:style-name': self.rststyle(style_name),
|
||
|
'text:fixed': 'true',
|
||
|
'style:data-style-name': 'rst-time-style-%d' % self.style_index,
|
||
|
})
|
||
|
el2 = SubElement(automatic_styles, 'number:time-style', attrib={
|
||
|
'style:name': 'rst-time-style-%d' % self.style_index,
|
||
|
'xmlns:number': SNSD['number'],
|
||
|
'xmlns:style': SNSD['style'],
|
||
|
})
|
||
|
el3 = SubElement(el2, 'number:hours', attrib={
|
||
|
'number:style': 'long',
|
||
|
})
|
||
|
el3 = SubElement(el2, 'number:text')
|
||
|
el3.text = ':'
|
||
|
el3 = SubElement(el2, 'number:minutes', attrib={
|
||
|
'number:style': 'long',
|
||
|
})
|
||
|
el3 = SubElement(el2, 'number:text')
|
||
|
el3.text = ':'
|
||
|
el3 = SubElement(el2, 'number:seconds', attrib={
|
||
|
'number:style': 'long',
|
||
|
})
|
||
|
el3 = SubElement(el2, 'number:text')
|
||
|
el3.text = ' '
|
||
|
el3 = SubElement(el2, 'number:am-pm')
|
||
|
elif text == 'd1':
|
||
|
self.style_index += 1
|
||
|
el1 = SubElement(parent, 'text:date', attrib={
|
||
|
'text:style-name': self.rststyle(style_name),
|
||
|
'style:data-style-name': 'rst-date-style-%d' % self.style_index,
|
||
|
})
|
||
|
el2 = SubElement(automatic_styles, 'number:date-style', attrib={
|
||
|
'style:name': 'rst-date-style-%d' % self.style_index,
|
||
|
'number:automatic-order': 'true',
|
||
|
'xmlns:number': SNSD['number'],
|
||
|
'xmlns:style': SNSD['style'],
|
||
|
})
|
||
|
el3 = SubElement(el2, 'number:month', attrib={
|
||
|
'number:style': 'long',
|
||
|
})
|
||
|
el3 = SubElement(el2, 'number:text')
|
||
|
el3.text = '/'
|
||
|
el3 = SubElement(el2, 'number:day', attrib={
|
||
|
'number:style': 'long',
|
||
|
})
|
||
|
el3 = SubElement(el2, 'number:text')
|
||
|
el3.text = '/'
|
||
|
el3 = SubElement(el2, 'number:year')
|
||
|
elif text == 'd2':
|
||
|
self.style_index += 1
|
||
|
el1 = SubElement(parent, 'text:date', attrib={
|
||
|
'text:style-name': self.rststyle(style_name),
|
||
|
'style:data-style-name': 'rst-date-style-%d' % self.style_index,
|
||
|
})
|
||
|
el2 = SubElement(automatic_styles, 'number:date-style', attrib={
|
||
|
'style:name': 'rst-date-style-%d' % self.style_index,
|
||
|
'number:automatic-order': 'true',
|
||
|
'xmlns:number': SNSD['number'],
|
||
|
'xmlns:style': SNSD['style'],
|
||
|
})
|
||
|
el3 = SubElement(el2, 'number:month', attrib={
|
||
|
'number:style': 'long',
|
||
|
})
|
||
|
el3 = SubElement(el2, 'number:text')
|
||
|
el3.text = '/'
|
||
|
el3 = SubElement(el2, 'number:day', attrib={
|
||
|
'number:style': 'long',
|
||
|
})
|
||
|
el3 = SubElement(el2, 'number:text')
|
||
|
el3.text = '/'
|
||
|
el3 = SubElement(el2, 'number:year', attrib={
|
||
|
'number:style': 'long',
|
||
|
})
|
||
|
elif text == 'd3':
|
||
|
self.style_index += 1
|
||
|
el1 = SubElement(parent, 'text:date', attrib={
|
||
|
'text:style-name': self.rststyle(style_name),
|
||
|
'style:data-style-name': 'rst-date-style-%d' % self.style_index,
|
||
|
})
|
||
|
el2 = SubElement(automatic_styles, 'number:date-style', attrib={
|
||
|
'style:name': 'rst-date-style-%d' % self.style_index,
|
||
|
'number:automatic-order': 'true',
|
||
|
'xmlns:number': SNSD['number'],
|
||
|
'xmlns:style': SNSD['style'],
|
||
|
})
|
||
|
el3 = SubElement(el2, 'number:month', attrib={
|
||
|
'number:textual': 'true',
|
||
|
})
|
||
|
el3 = SubElement(el2, 'number:text')
|
||
|
el3.text = ' '
|
||
|
el3 = SubElement(el2, 'number:day', attrib={
|
||
|
})
|
||
|
el3 = SubElement(el2, 'number:text')
|
||
|
el3.text = ', '
|
||
|
el3 = SubElement(el2, 'number:year', attrib={
|
||
|
'number:style': 'long',
|
||
|
})
|
||
|
elif text == 'd4':
|
||
|
self.style_index += 1
|
||
|
el1 = SubElement(parent, 'text:date', attrib={
|
||
|
'text:style-name': self.rststyle(style_name),
|
||
|
'style:data-style-name': 'rst-date-style-%d' % self.style_index,
|
||
|
})
|
||
|
el2 = SubElement(automatic_styles, 'number:date-style', attrib={
|
||
|
'style:name': 'rst-date-style-%d' % self.style_index,
|
||
|
'number:automatic-order': 'true',
|
||
|
'xmlns:number': SNSD['number'],
|
||
|
'xmlns:style': SNSD['style'],
|
||
|
})
|
||
|
el3 = SubElement(el2, 'number:month', attrib={
|
||
|
'number:textual': 'true',
|
||
|
'number:style': 'long',
|
||
|
})
|
||
|
el3 = SubElement(el2, 'number:text')
|
||
|
el3.text = ' '
|
||
|
el3 = SubElement(el2, 'number:day', attrib={
|
||
|
})
|
||
|
el3 = SubElement(el2, 'number:text')
|
||
|
el3.text = ', '
|
||
|
el3 = SubElement(el2, 'number:year', attrib={
|
||
|
'number:style': 'long',
|
||
|
})
|
||
|
elif text == 'd5':
|
||
|
self.style_index += 1
|
||
|
el1 = SubElement(parent, 'text:date', attrib={
|
||
|
'text:style-name': self.rststyle(style_name),
|
||
|
'style:data-style-name': 'rst-date-style-%d' % self.style_index,
|
||
|
})
|
||
|
el2 = SubElement(automatic_styles, 'number:date-style', attrib={
|
||
|
'style:name': 'rst-date-style-%d' % self.style_index,
|
||
|
'xmlns:number': SNSD['number'],
|
||
|
'xmlns:style': SNSD['style'],
|
||
|
})
|
||
|
el3 = SubElement(el2, 'number:year', attrib={
|
||
|
'number:style': 'long',
|
||
|
})
|
||
|
el3 = SubElement(el2, 'number:text')
|
||
|
el3.text = '-'
|
||
|
el3 = SubElement(el2, 'number:month', attrib={
|
||
|
'number:style': 'long',
|
||
|
})
|
||
|
el3 = SubElement(el2, 'number:text')
|
||
|
el3.text = '-'
|
||
|
el3 = SubElement(el2, 'number:day', attrib={
|
||
|
'number:style': 'long',
|
||
|
})
|
||
|
elif text == 's':
|
||
|
el1 = SubElement(parent, 'text:subject', attrib={
|
||
|
'text:style-name': self.rststyle(style_name),
|
||
|
})
|
||
|
elif text == 't':
|
||
|
el1 = SubElement(parent, 'text:title', attrib={
|
||
|
'text:style-name': self.rststyle(style_name),
|
||
|
})
|
||
|
elif text == 'a':
|
||
|
el1 = SubElement(parent, 'text:author-name', attrib={
|
||
|
'text:fixed': 'false',
|
||
|
})
|
||
|
else:
|
||
|
el1 = None
|
||
|
return el1
|
||
|
|
||
|
def split_field_specifiers_iter(self, text):
|
||
|
pos1 = 0
|
||
|
pos_end = len(text)
|
||
|
while True:
|
||
|
mo = ODFTranslator.field_pat.search(text, pos1)
|
||
|
if mo:
|
||
|
pos2 = mo.start()
|
||
|
if pos2 > pos1:
|
||
|
yield (ODFTranslator.code_text, text[pos1:pos2])
|
||
|
yield (ODFTranslator.code_field, mo.group(1))
|
||
|
pos1 = mo.end()
|
||
|
else:
|
||
|
break
|
||
|
trailing = text[pos1:]
|
||
|
if trailing:
|
||
|
yield (ODFTranslator.code_text, trailing)
|
||
|
|
||
|
|
||
|
def astext(self):
|
||
|
root = self.content_tree.getroot()
|
||
|
et = etree.ElementTree(root)
|
||
|
s1 = ToString(et)
|
||
|
return s1
|
||
|
|
||
|
def content_astext(self):
|
||
|
return self.astext()
|
||
|
|
||
|
def set_title(self, title): self.title = title
|
||
|
def get_title(self): return self.title
|
||
|
def set_embedded_file_list(self, embedded_file_list):
|
||
|
self.embedded_file_list = embedded_file_list
|
||
|
def get_embedded_file_list(self): return self.embedded_file_list
|
||
|
def get_meta_dict(self): return self.meta_dict
|
||
|
|
||
|
def process_footnotes(self):
|
||
|
for node, el1 in self.footnote_list:
|
||
|
backrefs = node.attributes.get('backrefs', [])
|
||
|
first = True
|
||
|
for ref in backrefs:
|
||
|
el2 = self.footnote_ref_dict.get(ref)
|
||
|
if el2 is not None:
|
||
|
if first:
|
||
|
first = False
|
||
|
el3 = copy.deepcopy(el1)
|
||
|
el2.append(el3)
|
||
|
else:
|
||
|
children = el2.getchildren()
|
||
|
if len(children) > 0: # and 'id' in el2.attrib:
|
||
|
child = children[0]
|
||
|
ref1 = child.text
|
||
|
attribkey = add_ns('text:id', nsdict=SNSD)
|
||
|
id1 = el2.get(attribkey, 'footnote-error')
|
||
|
if id1 is None:
|
||
|
id1 = ''
|
||
|
tag = add_ns('text:note-ref', nsdict=SNSD)
|
||
|
el2.tag = tag
|
||
|
if self.settings.endnotes_end_doc:
|
||
|
note_class = 'endnote'
|
||
|
else:
|
||
|
note_class = 'footnote'
|
||
|
el2.attrib.clear()
|
||
|
attribkey = add_ns('text:note-class', nsdict=SNSD)
|
||
|
el2.attrib[attribkey] = note_class
|
||
|
attribkey = add_ns('text:ref-name', nsdict=SNSD)
|
||
|
el2.attrib[attribkey] = id1
|
||
|
attribkey = add_ns('text:reference-format', nsdict=SNSD)
|
||
|
el2.attrib[attribkey] = 'page'
|
||
|
el2.text = ref1
|
||
|
|
||
|
#
|
||
|
# Utility methods
|
||
|
|
||
|
def append_child(self, tag, attrib=None, parent=None):
|
||
|
if parent is None:
|
||
|
parent = self.current_element
|
||
|
if attrib is None:
|
||
|
el = SubElement(parent, tag)
|
||
|
else:
|
||
|
el = SubElement(parent, tag, attrib)
|
||
|
return el
|
||
|
|
||
|
def append_p(self, style, text=None):
|
||
|
result = self.append_child('text:p', attrib={
|
||
|
'text:style-name': self.rststyle(style)})
|
||
|
self.append_pending_ids(result)
|
||
|
if text is not None:
|
||
|
result.text = text
|
||
|
return result
|
||
|
|
||
|
def append_pending_ids(self, el):
|
||
|
if self.settings.create_links:
|
||
|
for id in self.pending_ids:
|
||
|
SubElement(el, 'text:reference-mark', attrib={
|
||
|
'text:name': id})
|
||
|
self.pending_ids = [ ]
|
||
|
|
||
|
def set_current_element(self, el):
|
||
|
self.current_element = el
|
||
|
|
||
|
def set_to_parent(self):
|
||
|
self.current_element = self.current_element.getparent()
|
||
|
|
||
|
def generate_labeled_block(self, node, label):
|
||
|
label = '%s:' % (self.language.labels[label], )
|
||
|
el = self.append_p('textbody')
|
||
|
el1 = SubElement(el, 'text:span',
|
||
|
attrib={'text:style-name': self.rststyle('strong')})
|
||
|
el1.text = label
|
||
|
el = self.append_p('blockindent')
|
||
|
return el
|
||
|
|
||
|
def generate_labeled_line(self, node, label):
|
||
|
label = '%s:' % (self.language.labels[label], )
|
||
|
el = self.append_p('textbody')
|
||
|
el1 = SubElement(el, 'text:span',
|
||
|
attrib={'text:style-name': self.rststyle('strong')})
|
||
|
el1.text = label
|
||
|
el1.tail = node.astext()
|
||
|
return el
|
||
|
|
||
|
def encode(self, text):
|
||
|
text = text.replace(u'\u00a0', " ")
|
||
|
return text
|
||
|
|
||
|
#
|
||
|
# Visitor functions
|
||
|
#
|
||
|
# In alphabetic order, more or less.
|
||
|
# See docutils.docutils.nodes.node_class_names.
|
||
|
#
|
||
|
|
||
|
def dispatch_visit(self, node):
|
||
|
"""Override to catch basic attributes which many nodes have."""
|
||
|
self.handle_basic_atts(node)
|
||
|
nodes.GenericNodeVisitor.dispatch_visit(self, node)
|
||
|
|
||
|
def handle_basic_atts(self, node):
|
||
|
if isinstance(node, nodes.Element) and node['ids']:
|
||
|
self.pending_ids += node['ids']
|
||
|
|
||
|
def default_visit(self, node):
|
||
|
self.document.reporter.warning('missing visit_%s' % (node.tagname, ))
|
||
|
|
||
|
def default_departure(self, node):
|
||
|
self.document.reporter.warning('missing depart_%s' % (node.tagname, ))
|
||
|
|
||
|
def visit_Text(self, node):
|
||
|
# Skip nodes whose text has been processed in parent nodes.
|
||
|
if isinstance(node.parent, docutils.nodes.literal_block):
|
||
|
return
|
||
|
text = node.astext()
|
||
|
# Are we in mixed content? If so, add the text to the
|
||
|
# etree tail of the previous sibling element.
|
||
|
if len(self.current_element.getchildren()) > 0:
|
||
|
if self.current_element.getchildren()[-1].tail:
|
||
|
self.current_element.getchildren()[-1].tail += text
|
||
|
else:
|
||
|
self.current_element.getchildren()[-1].tail = text
|
||
|
else:
|
||
|
if self.current_element.text:
|
||
|
self.current_element.text += text
|
||
|
else:
|
||
|
self.current_element.text = text
|
||
|
|
||
|
def depart_Text(self, node):
|
||
|
pass
|
||
|
|
||
|
#
|
||
|
# Pre-defined fields
|
||
|
#
|
||
|
|
||
|
def visit_address(self, node):
|
||
|
el = self.generate_labeled_block(node, 'address')
|
||
|
self.set_current_element(el)
|
||
|
|
||
|
def depart_address(self, node):
|
||
|
self.set_to_parent()
|
||
|
|
||
|
def visit_author(self, node):
|
||
|
if isinstance(node.parent, nodes.authors):
|
||
|
el = self.append_p('blockindent')
|
||
|
else:
|
||
|
el = self.generate_labeled_block(node, 'author')
|
||
|
self.set_current_element(el)
|
||
|
|
||
|
def depart_author(self, node):
|
||
|
self.set_to_parent()
|
||
|
|
||
|
def visit_authors(self, node):
|
||
|
label = '%s:' % (self.language.labels['authors'], )
|
||
|
el = self.append_p('textbody')
|
||
|
el1 = SubElement(el, 'text:span',
|
||
|
attrib={'text:style-name': self.rststyle('strong')})
|
||
|
el1.text = label
|
||
|
|
||
|
def depart_authors(self, node):
|
||
|
pass
|
||
|
|
||
|
def visit_contact(self, node):
|
||
|
el = self.generate_labeled_block(node, 'contact')
|
||
|
self.set_current_element(el)
|
||
|
|
||
|
def depart_contact(self, node):
|
||
|
self.set_to_parent()
|
||
|
|
||
|
def visit_copyright(self, node):
|
||
|
el = self.generate_labeled_block(node, 'copyright')
|
||
|
self.set_current_element(el)
|
||
|
|
||
|
def depart_copyright(self, node):
|
||
|
self.set_to_parent()
|
||
|
|
||
|
def visit_date(self, node):
|
||
|
self.generate_labeled_line(node, 'date')
|
||
|
|
||
|
def depart_date(self, node):
|
||
|
pass
|
||
|
|
||
|
def visit_organization(self, node):
|
||
|
el = self.generate_labeled_block(node, 'organization')
|
||
|
self.set_current_element(el)
|
||
|
|
||
|
def depart_organization(self, node):
|
||
|
self.set_to_parent()
|
||
|
|
||
|
def visit_status(self, node):
|
||
|
el = self.generate_labeled_block(node, 'status')
|
||
|
self.set_current_element(el)
|
||
|
|
||
|
def depart_status(self, node):
|
||
|
self.set_to_parent()
|
||
|
|
||
|
def visit_revision(self, node):
|
||
|
el = self.generate_labeled_line(node, 'revision')
|
||
|
|
||
|
def depart_revision(self, node):
|
||
|
pass
|
||
|
|
||
|
def visit_version(self, node):
|
||
|
el = self.generate_labeled_line(node, 'version')
|
||
|
#self.set_current_element(el)
|
||
|
|
||
|
def depart_version(self, node):
|
||
|
#self.set_to_parent()
|
||
|
pass
|
||
|
|
||
|
def visit_attribution(self, node):
|
||
|
el = self.append_p('attribution', node.astext())
|
||
|
|
||
|
def depart_attribution(self, node):
|
||
|
pass
|
||
|
|
||
|
def visit_block_quote(self, node):
|
||
|
if 'epigraph' in node.attributes['classes']:
|
||
|
self.paragraph_style_stack.append(self.rststyle('epigraph'))
|
||
|
self.blockstyle = self.rststyle('epigraph')
|
||
|
elif 'highlights' in node.attributes['classes']:
|
||
|
self.paragraph_style_stack.append(self.rststyle('highlights'))
|
||
|
self.blockstyle = self.rststyle('highlights')
|
||
|
else:
|
||
|
self.paragraph_style_stack.append(self.rststyle('blockquote'))
|
||
|
self.blockstyle = self.rststyle('blockquote')
|
||
|
self.line_indent_level += 1
|
||
|
|
||
|
def depart_block_quote(self, node):
|
||
|
self.paragraph_style_stack.pop()
|
||
|
self.blockstyle = ''
|
||
|
self.line_indent_level -= 1
|
||
|
|
||
|
def visit_bullet_list(self, node):
|
||
|
self.list_level +=1
|
||
|
if self.in_table_of_contents:
|
||
|
if self.settings.generate_oowriter_toc:
|
||
|
pass
|
||
|
else:
|
||
|
if node.has_key('classes') and \
|
||
|
'auto-toc' in node.attributes['classes']:
|
||
|
el = SubElement(self.current_element, 'text:list', attrib={
|
||
|
'text:style-name': self.rststyle('tocenumlist'),
|
||
|
})
|
||
|
self.list_style_stack.append(self.rststyle('enumitem'))
|
||
|
else:
|
||
|
el = SubElement(self.current_element, 'text:list', attrib={
|
||
|
'text:style-name': self.rststyle('tocbulletlist'),
|
||
|
})
|
||
|
self.list_style_stack.append(self.rststyle('bulletitem'))
|
||
|
self.set_current_element(el)
|
||
|
else:
|
||
|
if self.blockstyle == self.rststyle('blockquote'):
|
||
|
el = SubElement(self.current_element, 'text:list', attrib={
|
||
|
'text:style-name': self.rststyle('blockquote-bulletlist'),
|
||
|
})
|
||
|
self.list_style_stack.append(
|
||
|
self.rststyle('blockquote-bulletitem'))
|
||
|
elif self.blockstyle == self.rststyle('highlights'):
|
||
|
el = SubElement(self.current_element, 'text:list', attrib={
|
||
|
'text:style-name': self.rststyle('highlights-bulletlist'),
|
||
|
})
|
||
|
self.list_style_stack.append(
|
||
|
self.rststyle('highlights-bulletitem'))
|
||
|
elif self.blockstyle == self.rststyle('epigraph'):
|
||
|
el = SubElement(self.current_element, 'text:list', attrib={
|
||
|
'text:style-name': self.rststyle('epigraph-bulletlist'),
|
||
|
})
|
||
|
self.list_style_stack.append(
|
||
|
self.rststyle('epigraph-bulletitem'))
|
||
|
else:
|
||
|
el = SubElement(self.current_element, 'text:list', attrib={
|
||
|
'text:style-name': self.rststyle('bulletlist'),
|
||
|
})
|
||
|
self.list_style_stack.append(self.rststyle('bulletitem'))
|
||
|
self.set_current_element(el)
|
||
|
|
||
|
def depart_bullet_list(self, node):
|
||
|
if self.in_table_of_contents:
|
||
|
if self.settings.generate_oowriter_toc:
|
||
|
pass
|
||
|
else:
|
||
|
self.set_to_parent()
|
||
|
self.list_style_stack.pop()
|
||
|
else:
|
||
|
self.set_to_parent()
|
||
|
self.list_style_stack.pop()
|
||
|
self.list_level -=1
|
||
|
|
||
|
def visit_caption(self, node):
|
||
|
raise nodes.SkipChildren()
|
||
|
pass
|
||
|
|
||
|
def depart_caption(self, node):
|
||
|
pass
|
||
|
|
||
|
def visit_comment(self, node):
|
||
|
el = self.append_p('textbody')
|
||
|
el1 = SubElement(el, 'office:annotation', attrib={})
|
||
|
el2 = SubElement(el1, 'dc:creator', attrib={})
|
||
|
s1 = os.environ.get('USER', '')
|
||
|
el2.text = s1
|
||
|
el2 = SubElement(el1, 'text:p', attrib={})
|
||
|
el2.text = node.astext()
|
||
|
|
||
|
def depart_comment(self, node):
|
||
|
pass
|
||
|
|
||
|
def visit_compound(self, node):
|
||
|
# The compound directive currently receives no special treatment.
|
||
|
pass
|
||
|
|
||
|
def depart_compound(self, node):
|
||
|
pass
|
||
|
|
||
|
def visit_container(self, node):
|
||
|
styles = node.attributes.get('classes', ())
|
||
|
if len(styles) > 0:
|
||
|
self.paragraph_style_stack.append(self.rststyle(styles[0]))
|
||
|
|
||
|
def depart_container(self, node):
|
||
|
styles = node.attributes.get('classes', ())
|
||
|
if len(styles) > 0:
|
||
|
self.paragraph_style_stack.pop()
|
||
|
|
||
|
def visit_decoration(self, node):
|
||
|
pass
|
||
|
|
||
|
def depart_decoration(self, node):
|
||
|
pass
|
||
|
|
||
|
def visit_definition_list(self, node):
|
||
|
self.def_list_level +=1
|
||
|
if self.list_level > 5:
|
||
|
raise RuntimeError(
|
||
|
'max definition list nesting level exceeded')
|
||
|
|
||
|
def depart_definition_list(self, node):
|
||
|
self.def_list_level -=1
|
||
|
|
||
|
def visit_definition_list_item(self, node):
|
||
|
pass
|
||
|
|
||
|
def depart_definition_list_item(self, node):
|
||
|
pass
|
||
|
|
||
|
def visit_term(self, node):
|
||
|
el = self.append_p('deflist-term-%d' % self.def_list_level)
|
||
|
el.text = node.astext()
|
||
|
self.set_current_element(el)
|
||
|
raise nodes.SkipChildren()
|
||
|
|
||
|
def depart_term(self, node):
|
||
|
self.set_to_parent()
|
||
|
|
||
|
def visit_definition(self, node):
|
||
|
self.paragraph_style_stack.append(
|
||
|
self.rststyle('deflist-def-%d' % self.def_list_level))
|
||
|
self.bumped_list_level_stack.append(ListLevel(1))
|
||
|
|
||
|
def depart_definition(self, node):
|
||
|
self.paragraph_style_stack.pop()
|
||
|
self.bumped_list_level_stack.pop()
|
||
|
|
||
|
def visit_classifier(self, node):
|
||
|
els = self.current_element.getchildren()
|
||
|
if len(els) > 0:
|
||
|
el = els[-1]
|
||
|
el1 = SubElement(el, 'text:span',
|
||
|
attrib={'text:style-name': self.rststyle('emphasis')
|
||
|
})
|
||
|
el1.text = ' (%s)' % (node.astext(), )
|
||
|
|
||
|
def depart_classifier(self, node):
|
||
|
pass
|
||
|
|
||
|
def visit_document(self, node):
|
||
|
pass
|
||
|
|
||
|
def depart_document(self, node):
|
||
|
self.process_footnotes()
|
||
|
|
||
|
def visit_docinfo(self, node):
|
||
|
self.section_level += 1
|
||
|
self.section_count += 1
|
||
|
if self.settings.create_sections:
|
||
|
el = self.append_child('text:section', attrib={
|
||
|
'text:name': 'Section%d' % self.section_count,
|
||
|
'text:style-name': 'Sect%d' % self.section_level,
|
||
|
})
|
||
|
self.set_current_element(el)
|
||
|
|
||
|
def depart_docinfo(self, node):
|
||
|
self.section_level -= 1
|
||
|
if self.settings.create_sections:
|
||
|
self.set_to_parent()
|
||
|
|
||
|
def visit_emphasis(self, node):
|
||
|
el = SubElement(self.current_element, 'text:span',
|
||
|
attrib={'text:style-name': self.rststyle('emphasis')})
|
||
|
self.set_current_element(el)
|
||
|
|
||
|
def depart_emphasis(self, node):
|
||
|
self.set_to_parent()
|
||
|
|
||
|
def visit_enumerated_list(self, node):
|
||
|
el1 = self.current_element
|
||
|
if self.blockstyle == self.rststyle('blockquote'):
|
||
|
el2 = SubElement(el1, 'text:list', attrib={
|
||
|
'text:style-name': self.rststyle('blockquote-enumlist'),
|
||
|
})
|
||
|
self.list_style_stack.append(self.rststyle('blockquote-enumitem'))
|
||
|
elif self.blockstyle == self.rststyle('highlights'):
|
||
|
el2 = SubElement(el1, 'text:list', attrib={
|
||
|
'text:style-name': self.rststyle('highlights-enumlist'),
|
||
|
})
|
||
|
self.list_style_stack.append(self.rststyle('highlights-enumitem'))
|
||
|
elif self.blockstyle == self.rststyle('epigraph'):
|
||
|
el2 = SubElement(el1, 'text:list', attrib={
|
||
|
'text:style-name': self.rststyle('epigraph-enumlist'),
|
||
|
})
|
||
|
self.list_style_stack.append(self.rststyle('epigraph-enumitem'))
|
||
|
else:
|
||
|
liststylename = 'enumlist-%s' % (node.get('enumtype', 'arabic'), )
|
||
|
el2 = SubElement(el1, 'text:list', attrib={
|
||
|
'text:style-name': self.rststyle(liststylename),
|
||
|
})
|
||
|
self.list_style_stack.append(self.rststyle('enumitem'))
|
||
|
self.set_current_element(el2)
|
||
|
|
||
|
def depart_enumerated_list(self, node):
|
||
|
self.set_to_parent()
|
||
|
self.list_style_stack.pop()
|
||
|
|
||
|
def visit_list_item(self, node):
|
||
|
# If we are in a "bumped" list level, then wrap this
|
||
|
# list in an outer lists in order to increase the
|
||
|
# indentation level.
|
||
|
if self.in_table_of_contents:
|
||
|
if self.settings.generate_oowriter_toc:
|
||
|
self.paragraph_style_stack.append(
|
||
|
self.rststyle('contents-%d' % (self.list_level, )))
|
||
|
else:
|
||
|
el1 = self.append_child('text:list-item')
|
||
|
self.set_current_element(el1)
|
||
|
else:
|
||
|
el1 = self.append_child('text:list-item')
|
||
|
el3 = el1
|
||
|
if len(self.bumped_list_level_stack) > 0:
|
||
|
level_obj = self.bumped_list_level_stack[-1]
|
||
|
if level_obj.get_sibling():
|
||
|
level_obj.set_nested(False)
|
||
|
for level_obj1 in self.bumped_list_level_stack:
|
||
|
for idx in range(level_obj1.get_level()):
|
||
|
el2 = self.append_child('text:list', parent=el3)
|
||
|
el3 = self.append_child(
|
||
|
'text:list-item', parent=el2)
|
||
|
self.paragraph_style_stack.append(self.list_style_stack[-1])
|
||
|
self.set_current_element(el3)
|
||
|
|
||
|
def depart_list_item(self, node):
|
||
|
if self.in_table_of_contents:
|
||
|
if self.settings.generate_oowriter_toc:
|
||
|
self.paragraph_style_stack.pop()
|
||
|
else:
|
||
|
self.set_to_parent()
|
||
|
else:
|
||
|
if len(self.bumped_list_level_stack) > 0:
|
||
|
level_obj = self.bumped_list_level_stack[-1]
|
||
|
if level_obj.get_sibling():
|
||
|
level_obj.set_nested(True)
|
||
|
for level_obj1 in self.bumped_list_level_stack:
|
||
|
for idx in range(level_obj1.get_level()):
|
||
|
self.set_to_parent()
|
||
|
self.set_to_parent()
|
||
|
self.paragraph_style_stack.pop()
|
||
|
self.set_to_parent()
|
||
|
|
||
|
def visit_header(self, node):
|
||
|
self.in_header = True
|
||
|
|
||
|
def depart_header(self, node):
|
||
|
self.in_header = False
|
||
|
|
||
|
def visit_footer(self, node):
|
||
|
self.in_footer = True
|
||
|
|
||
|
def depart_footer(self, node):
|
||
|
self.in_footer = False
|
||
|
|
||
|
def visit_field(self, node):
|
||
|
pass
|
||
|
|
||
|
def depart_field(self, node):
|
||
|
pass
|
||
|
|
||
|
def visit_field_list(self, node):
|
||
|
pass
|
||
|
|
||
|
def depart_field_list(self, node):
|
||
|
pass
|
||
|
|
||
|
def visit_field_name(self, node):
|
||
|
el = self.append_p('textbody')
|
||
|
el1 = SubElement(el, 'text:span',
|
||
|
attrib={'text:style-name': self.rststyle('strong')})
|
||
|
el1.text = node.astext()
|
||
|
|
||
|
def depart_field_name(self, node):
|
||
|
pass
|
||
|
|
||
|
def visit_field_body(self, node):
|
||
|
self.paragraph_style_stack.append(self.rststyle('blockindent'))
|
||
|
|
||
|
def depart_field_body(self, node):
|
||
|
self.paragraph_style_stack.pop()
|
||
|
|
||
|
def visit_figure(self, node):
|
||
|
pass
|
||
|
|
||
|
def depart_figure(self, node):
|
||
|
pass
|
||
|
|
||
|
def visit_footnote(self, node):
|
||
|
self.footnote_level += 1
|
||
|
self.save_footnote_current = self.current_element
|
||
|
el1 = Element('text:note-body')
|
||
|
self.current_element = el1
|
||
|
self.footnote_list.append((node, el1))
|
||
|
if isinstance(node, docutils.nodes.citation):
|
||
|
self.paragraph_style_stack.append(self.rststyle('citation'))
|
||
|
else:
|
||
|
self.paragraph_style_stack.append(self.rststyle('footnote'))
|
||
|
|
||
|
def depart_footnote(self, node):
|
||
|
self.paragraph_style_stack.pop()
|
||
|
self.current_element = self.save_footnote_current
|
||
|
self.footnote_level -= 1
|
||
|
|
||
|
footnote_chars = [
|
||
|
'*', '**', '***',
|
||
|
'++', '+++',
|
||
|
'##', '###',
|
||
|
'@@', '@@@',
|
||
|
]
|
||
|
|
||
|
def visit_footnote_reference(self, node):
|
||
|
if self.footnote_level <= 0:
|
||
|
id = node.attributes['ids'][0]
|
||
|
refid = node.attributes.get('refid')
|
||
|
if refid is None:
|
||
|
refid = ''
|
||
|
if self.settings.endnotes_end_doc:
|
||
|
note_class = 'endnote'
|
||
|
else:
|
||
|
note_class = 'footnote'
|
||
|
el1 = self.append_child('text:note', attrib={
|
||
|
'text:id': '%s' % (refid, ),
|
||
|
'text:note-class': note_class,
|
||
|
})
|
||
|
note_auto = str(node.attributes.get('auto', 1))
|
||
|
if isinstance(node, docutils.nodes.citation_reference):
|
||
|
citation = '[%s]' % node.astext()
|
||
|
el2 = SubElement(el1, 'text:note-citation', attrib={
|
||
|
'text:label': citation,
|
||
|
})
|
||
|
el2.text = citation
|
||
|
elif note_auto == '1':
|
||
|
el2 = SubElement(el1, 'text:note-citation', attrib={
|
||
|
'text:label': node.astext(),
|
||
|
})
|
||
|
el2.text = node.astext()
|
||
|
elif note_auto == '*':
|
||
|
if self.footnote_chars_idx >= len(
|
||
|
ODFTranslator.footnote_chars):
|
||
|
self.footnote_chars_idx = 0
|
||
|
footnote_char = ODFTranslator.footnote_chars[
|
||
|
self.footnote_chars_idx]
|
||
|
self.footnote_chars_idx += 1
|
||
|
el2 = SubElement(el1, 'text:note-citation', attrib={
|
||
|
'text:label': footnote_char,
|
||
|
})
|
||
|
el2.text = footnote_char
|
||
|
self.footnote_ref_dict[id] = el1
|
||
|
raise nodes.SkipChildren()
|
||
|
|
||
|
def depart_footnote_reference(self, node):
|
||
|
pass
|
||
|
|
||
|
def visit_citation(self, node):
|
||
|
self.in_citation = True
|
||
|
for id in node.attributes['ids']:
|
||
|
self.citation_id = id
|
||
|
break
|
||
|
self.paragraph_style_stack.append(self.rststyle('blockindent'))
|
||
|
self.bumped_list_level_stack.append(ListLevel(1))
|
||
|
|
||
|
def depart_citation(self, node):
|
||
|
self.citation_id = None
|
||
|
self.paragraph_style_stack.pop()
|
||
|
self.bumped_list_level_stack.pop()
|
||
|
self.in_citation = False
|
||
|
|
||
|
def visit_citation_reference(self, node):
|
||
|
if self.settings.create_links:
|
||
|
id = node.attributes['refid']
|
||
|
el = self.append_child('text:reference-ref', attrib={
|
||
|
'text:ref-name': '%s' % (id, ),
|
||
|
'text:reference-format': 'text',
|
||
|
})
|
||
|
el.text = '['
|
||
|
self.set_current_element(el)
|
||
|
elif self.current_element.text is None:
|
||
|
self.current_element.text = '['
|
||
|
else:
|
||
|
self.current_element.text += '['
|
||
|
|
||
|
def depart_citation_reference(self, node):
|
||
|
self.current_element.text += ']'
|
||
|
if self.settings.create_links:
|
||
|
self.set_to_parent()
|
||
|
|
||
|
def visit_label(self, node):
|
||
|
if isinstance(node.parent, docutils.nodes.footnote):
|
||
|
raise nodes.SkipChildren()
|
||
|
elif self.citation_id is not None:
|
||
|
el = self.append_p('textbody')
|
||
|
self.set_current_element(el)
|
||
|
if self.settings.create_links:
|
||
|
el0 = SubElement(el, 'text:span')
|
||
|
el0.text = '['
|
||
|
el1 = self.append_child('text:reference-mark-start', attrib={
|
||
|
'text:name': '%s' % (self.citation_id, ),
|
||
|
})
|
||
|
else:
|
||
|
el.text = '['
|
||
|
|
||
|
def depart_label(self, node):
|
||
|
if isinstance(node.parent, docutils.nodes.footnote):
|
||
|
pass
|
||
|
elif self.citation_id is not None:
|
||
|
if self.settings.create_links:
|
||
|
el = self.append_child('text:reference-mark-end', attrib={
|
||
|
'text:name': '%s' % (self.citation_id, ),
|
||
|
})
|
||
|
el0 = SubElement(self.current_element, 'text:span')
|
||
|
el0.text = ']'
|
||
|
else:
|
||
|
self.current_element.text += ']'
|
||
|
self.set_to_parent()
|
||
|
|
||
|
def visit_generated(self, node):
|
||
|
pass
|
||
|
|
||
|
def depart_generated(self, node):
|
||
|
pass
|
||
|
|
||
|
def check_file_exists(self, path):
|
||
|
if os.path.exists(path):
|
||
|
return 1
|
||
|
else:
|
||
|
return 0
|
||
|
|
||
|
def visit_image(self, node):
|
||
|
# Capture the image file.
|
||
|
if 'uri' in node.attributes:
|
||
|
source = node.attributes['uri']
|
||
|
if not (source.startswith('http:') or source.startswith('https:')):
|
||
|
if not source.startswith(os.sep):
|
||
|
docsource, line = utils.get_source_line(node)
|
||
|
if docsource:
|
||
|
dirname = os.path.dirname(docsource)
|
||
|
if dirname:
|
||
|
source = '%s%s%s' % (dirname, os.sep, source, )
|
||
|
if not self.check_file_exists(source):
|
||
|
self.document.reporter.warning(
|
||
|
'Cannot find image file %s.' % (source, ))
|
||
|
return
|
||
|
else:
|
||
|
return
|
||
|
if source in self.image_dict:
|
||
|
filename, destination = self.image_dict[source]
|
||
|
else:
|
||
|
self.image_count += 1
|
||
|
filename = os.path.split(source)[1]
|
||
|
destination = 'Pictures/1%08x%s' % (self.image_count, filename, )
|
||
|
if source.startswith('http:') or source.startswith('https:'):
|
||
|
try:
|
||
|
imgfile = urllib2.urlopen(source)
|
||
|
content = imgfile.read()
|
||
|
imgfile.close()
|
||
|
imgfile2 = tempfile.NamedTemporaryFile('wb', delete=False)
|
||
|
imgfile2.write(content)
|
||
|
imgfile2.close()
|
||
|
imgfilename = imgfile2.name
|
||
|
source = imgfilename
|
||
|
except urllib2.HTTPError, e:
|
||
|
self.document.reporter.warning(
|
||
|
"Can't open image url %s." % (source, ))
|
||
|
spec = (source, destination,)
|
||
|
else:
|
||
|
spec = (os.path.abspath(source), destination,)
|
||
|
self.embedded_file_list.append(spec)
|
||
|
self.image_dict[source] = (source, destination,)
|
||
|
# Is this a figure (containing an image) or just a plain image?
|
||
|
if self.in_paragraph:
|
||
|
el1 = self.current_element
|
||
|
else:
|
||
|
el1 = SubElement(self.current_element, 'text:p',
|
||
|
attrib={'text:style-name': self.rststyle('textbody')})
|
||
|
el2 = el1
|
||
|
if isinstance(node.parent, docutils.nodes.figure):
|
||
|
el3, el4, el5, caption = self.generate_figure(node, source,
|
||
|
destination, el2)
|
||
|
attrib = {}
|
||
|
el6, width = self.generate_image(node, source, destination,
|
||
|
el5, attrib)
|
||
|
if caption is not None:
|
||
|
el6.tail = caption
|
||
|
else: #if isinstance(node.parent, docutils.nodes.image):
|
||
|
el3 = self.generate_image(node, source, destination, el2)
|
||
|
|
||
|
def depart_image(self, node):
|
||
|
pass
|
||
|
|
||
|
def get_image_width_height(self, node, attr):
|
||
|
size = None
|
||
|
unit = None
|
||
|
if attr in node.attributes:
|
||
|
size = node.attributes[attr]
|
||
|
size = size.strip()
|
||
|
# For conversion factors, see:
|
||
|
# http://www.unitconversion.org/unit_converter/typography-ex.html
|
||
|
try:
|
||
|
if size.endswith('%'):
|
||
|
if attr == 'height':
|
||
|
# Percentage allowed for width but not height.
|
||
|
raise ValueError('percentage not allowed for height')
|
||
|
size = size.rstrip(' %')
|
||
|
size = float(size) / 100.0
|
||
|
unit = '%'
|
||
|
else:
|
||
|
size, unit = convert_to_cm(size)
|
||
|
except ValueError, exp:
|
||
|
self.document.reporter.warning(
|
||
|
'Invalid %s for image: "%s". '
|
||
|
'Error: "%s".' % (
|
||
|
attr, node.attributes[attr], exp))
|
||
|
return size, unit
|
||
|
|
||
|
def convert_to_cm(self, size):
|
||
|
"""Convert various units to centimeters.
|
||
|
|
||
|
Note that a call to this method should be wrapped in:
|
||
|
try: except ValueError:
|
||
|
"""
|
||
|
size = size.strip()
|
||
|
if size.endswith('px'):
|
||
|
size = float(size[:-2]) * 0.026 # convert px to cm
|
||
|
elif size.endswith('in'):
|
||
|
size = float(size[:-2]) * 2.54 # convert in to cm
|
||
|
elif size.endswith('pt'):
|
||
|
size = float(size[:-2]) * 0.035 # convert pt to cm
|
||
|
elif size.endswith('pc'):
|
||
|
size = float(size[:-2]) * 2.371 # convert pc to cm
|
||
|
elif size.endswith('mm'):
|
||
|
size = float(size[:-2]) * 10.0 # convert mm to cm
|
||
|
elif size.endswith('cm'):
|
||
|
size = float(size[:-2])
|
||
|
else:
|
||
|
raise ValueError('unknown unit type')
|
||
|
unit = 'cm'
|
||
|
return size, unit
|
||
|
|
||
|
def get_image_scale(self, node):
|
||
|
if 'scale' in node.attributes:
|
||
|
scale = node.attributes['scale']
|
||
|
try:
|
||
|
scale = int(scale)
|
||
|
except ValueError:
|
||
|
self.document.reporter.warning(
|
||
|
'Invalid scale for image: "%s"' % (
|
||
|
node.attributes['scale'], ))
|
||
|
if scale < 1: # or scale > 100:
|
||
|
self.document.reporter.warning(
|
||
|
'scale out of range (%s), using 1.' % (scale, ))
|
||
|
scale = 1
|
||
|
scale = scale * 0.01
|
||
|
else:
|
||
|
scale = 1.0
|
||
|
return scale
|
||
|
|
||
|
def get_image_scaled_width_height(self, node, source):
|
||
|
"""Return the image size in centimeters adjusted by image attrs."""
|
||
|
scale = self.get_image_scale(node)
|
||
|
width, width_unit = self.get_image_width_height(node, 'width')
|
||
|
height, _ = self.get_image_width_height(node, 'height')
|
||
|
dpi = (72, 72)
|
||
|
if PIL is not None and source in self.image_dict:
|
||
|
filename, destination = self.image_dict[source]
|
||
|
imageobj = PIL.Image.open(filename, 'r')
|
||
|
dpi = imageobj.info.get('dpi', dpi)
|
||
|
# dpi information can be (xdpi, ydpi) or xydpi
|
||
|
try:
|
||
|
iter(dpi)
|
||
|
except:
|
||
|
dpi = (dpi, dpi)
|
||
|
else:
|
||
|
imageobj = None
|
||
|
if width is None or height is None:
|
||
|
if imageobj is None:
|
||
|
raise RuntimeError(
|
||
|
'image size not fully specified and PIL not installed')
|
||
|
if width is None:
|
||
|
width = imageobj.size[0]
|
||
|
width = float(width) * 0.026 # convert px to cm
|
||
|
if height is None:
|
||
|
height = imageobj.size[1]
|
||
|
height = float(height) * 0.026 # convert px to cm
|
||
|
if width_unit == '%':
|
||
|
factor = width
|
||
|
image_width = imageobj.size[0]
|
||
|
image_width = float(image_width) * 0.026 # convert px to cm
|
||
|
image_height = imageobj.size[1]
|
||
|
image_height = float(image_height) * 0.026 # convert px to cm
|
||
|
line_width = self.get_page_width()
|
||
|
width = factor * line_width
|
||
|
factor = (factor * line_width) / image_width
|
||
|
height = factor * image_height
|
||
|
width *= scale
|
||
|
height *= scale
|
||
|
width = '%.2fcm' % width
|
||
|
height = '%.2fcm' % height
|
||
|
return width, height
|
||
|
|
||
|
def get_page_width(self):
|
||
|
"""Return the document's page width in centimeters."""
|
||
|
root = self.get_dom_stylesheet()
|
||
|
nodes = root.iterfind(
|
||
|
'.//{urn:oasis:names:tc:opendocument:xmlns:style:1.0}'
|
||
|
'page-layout/'
|
||
|
'{urn:oasis:names:tc:opendocument:xmlns:style:1.0}'
|
||
|
'page-layout-properties')
|
||
|
width = None
|
||
|
for node in nodes:
|
||
|
page_width = node.get(
|
||
|
'{urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0}'
|
||
|
'page-width')
|
||
|
margin_left = node.get(
|
||
|
'{urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0}'
|
||
|
'margin-left')
|
||
|
margin_right = node.get(
|
||
|
'{urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0}'
|
||
|
'margin-right')
|
||
|
if (page_width is None or
|
||
|
margin_left is None or
|
||
|
margin_right is None):
|
||
|
continue
|
||
|
try:
|
||
|
page_width, _ = self.convert_to_cm(page_width)
|
||
|
margin_left, _ = self.convert_to_cm(margin_left)
|
||
|
margin_right, _ = self.convert_to_cm(margin_right)
|
||
|
except ValueError, exp:
|
||
|
self.document.reporter.warning(
|
||
|
'Stylesheet file contains invalid page width '
|
||
|
'or margin size.')
|
||
|
width = page_width - margin_left - margin_right
|
||
|
if width is None:
|
||
|
# We can't find the width in styles, so we make a guess.
|
||
|
# Use a width of 6 in = 15.24 cm.
|
||
|
width = 15.24
|
||
|
return width
|
||
|
|
||
|
def generate_figure(self, node, source, destination, current_element):
|
||
|
caption = None
|
||
|
width, height = self.get_image_scaled_width_height(node, source)
|
||
|
for node1 in node.parent.children:
|
||
|
if node1.tagname == 'caption':
|
||
|
caption = node1.astext()
|
||
|
self.image_style_count += 1
|
||
|
#
|
||
|
# Add the style for the caption.
|
||
|
if caption is not None:
|
||
|
attrib = {
|
||
|
'style:class': 'extra',
|
||
|
'style:family': 'paragraph',
|
||
|
'style:name': 'Caption',
|
||
|
'style:parent-style-name': 'Standard',
|
||
|
}
|
||
|
el1 = SubElement(self.automatic_styles, 'style:style',
|
||
|
attrib=attrib, nsdict=SNSD)
|
||
|
attrib = {
|
||
|
'fo:margin-bottom': '0.0835in',
|
||
|
'fo:margin-top': '0.0835in',
|
||
|
'text:line-number': '0',
|
||
|
'text:number-lines': 'false',
|
||
|
}
|
||
|
el2 = SubElement(el1, 'style:paragraph-properties',
|
||
|
attrib=attrib, nsdict=SNSD)
|
||
|
attrib = {
|
||
|
'fo:font-size': '12pt',
|
||
|
'fo:font-style': 'italic',
|
||
|
'style:font-name': 'Times',
|
||
|
'style:font-name-complex': 'Lucidasans1',
|
||
|
'style:font-size-asian': '12pt',
|
||
|
'style:font-size-complex': '12pt',
|
||
|
'style:font-style-asian': 'italic',
|
||
|
'style:font-style-complex': 'italic',
|
||
|
}
|
||
|
el2 = SubElement(el1, 'style:text-properties',
|
||
|
attrib=attrib, nsdict=SNSD)
|
||
|
style_name = 'rstframestyle%d' % self.image_style_count
|
||
|
draw_name = 'graphics%d' % IMAGE_NAME_COUNTER.next()
|
||
|
# Add the styles
|
||
|
attrib = {
|
||
|
'style:name': style_name,
|
||
|
'style:family': 'graphic',
|
||
|
'style:parent-style-name': self.rststyle('figureframe'),
|
||
|
}
|
||
|
el1 = SubElement(self.automatic_styles,
|
||
|
'style:style', attrib=attrib, nsdict=SNSD)
|
||
|
halign = 'center'
|
||
|
valign = 'top'
|
||
|
if 'align' in node.attributes:
|
||
|
align = node.attributes['align'].split()
|
||
|
for val in align:
|
||
|
if val in ('left', 'center', 'right'):
|
||
|
halign = val
|
||
|
elif val in ('top', 'middle', 'bottom'):
|
||
|
valign = val
|
||
|
attrib = {}
|
||
|
wrap = False
|
||
|
classes = node.parent.attributes.get('classes')
|
||
|
if classes and 'wrap' in classes:
|
||
|
wrap = True
|
||
|
if wrap:
|
||
|
attrib['style:wrap'] = 'dynamic'
|
||
|
else:
|
||
|
attrib['style:wrap'] = 'none'
|
||
|
el2 = SubElement(el1,
|
||
|
'style:graphic-properties', attrib=attrib, nsdict=SNSD)
|
||
|
attrib = {
|
||
|
'draw:style-name': style_name,
|
||
|
'draw:name': draw_name,
|
||
|
'text:anchor-type': 'paragraph',
|
||
|
'draw:z-index': '0',
|
||
|
}
|
||
|
attrib['svg:width'] = width
|
||
|
el3 = SubElement(current_element, 'draw:frame', attrib=attrib)
|
||
|
attrib = {}
|
||
|
el4 = SubElement(el3, 'draw:text-box', attrib=attrib)
|
||
|
attrib = {
|
||
|
'text:style-name': self.rststyle('caption'),
|
||
|
}
|
||
|
el5 = SubElement(el4, 'text:p', attrib=attrib)
|
||
|
return el3, el4, el5, caption
|
||
|
|
||
|
def generate_image(self, node, source, destination, current_element,
|
||
|
frame_attrs=None):
|
||
|
width, height = self.get_image_scaled_width_height(node, source)
|
||
|
self.image_style_count += 1
|
||
|
style_name = 'rstframestyle%d' % self.image_style_count
|
||
|
# Add the style.
|
||
|
attrib = {
|
||
|
'style:name': style_name,
|
||
|
'style:family': 'graphic',
|
||
|
'style:parent-style-name': self.rststyle('image'),
|
||
|
}
|
||
|
el1 = SubElement(self.automatic_styles,
|
||
|
'style:style', attrib=attrib, nsdict=SNSD)
|
||
|
halign = None
|
||
|
valign = None
|
||
|
if 'align' in node.attributes:
|
||
|
align = node.attributes['align'].split()
|
||
|
for val in align:
|
||
|
if val in ('left', 'center', 'right'):
|
||
|
halign = val
|
||
|
elif val in ('top', 'middle', 'bottom'):
|
||
|
valign = val
|
||
|
if frame_attrs is None:
|
||
|
attrib = {
|
||
|
'style:vertical-pos': 'top',
|
||
|
'style:vertical-rel': 'paragraph',
|
||
|
'style:horizontal-rel': 'paragraph',
|
||
|
'style:mirror': 'none',
|
||
|
'fo:clip': 'rect(0cm 0cm 0cm 0cm)',
|
||
|
'draw:luminance': '0%',
|
||
|
'draw:contrast': '0%',
|
||
|
'draw:red': '0%',
|
||
|
'draw:green': '0%',
|
||
|
'draw:blue': '0%',
|
||
|
'draw:gamma': '100%',
|
||
|
'draw:color-inversion': 'false',
|
||
|
'draw:image-opacity': '100%',
|
||
|
'draw:color-mode': 'standard',
|
||
|
}
|
||
|
else:
|
||
|
attrib = frame_attrs
|
||
|
if halign is not None:
|
||
|
attrib['style:horizontal-pos'] = halign
|
||
|
if valign is not None:
|
||
|
attrib['style:vertical-pos'] = valign
|
||
|
# If there is a classes/wrap directive or we are
|
||
|
# inside a table, add a no-wrap style.
|
||
|
wrap = False
|
||
|
classes = node.attributes.get('classes')
|
||
|
if classes and 'wrap' in classes:
|
||
|
wrap = True
|
||
|
if wrap:
|
||
|
attrib['style:wrap'] = 'dynamic'
|
||
|
else:
|
||
|
attrib['style:wrap'] = 'none'
|
||
|
# If we are inside a table, add a no-wrap style.
|
||
|
if self.is_in_table(node):
|
||
|
attrib['style:wrap'] = 'none'
|
||
|
el2 = SubElement(el1,
|
||
|
'style:graphic-properties', attrib=attrib, nsdict=SNSD)
|
||
|
draw_name = 'graphics%d' % IMAGE_NAME_COUNTER.next()
|
||
|
# Add the content.
|
||
|
#el = SubElement(current_element, 'text:p',
|
||
|
# attrib={'text:style-name': self.rststyle('textbody')})
|
||
|
attrib={
|
||
|
'draw:style-name': style_name,
|
||
|
'draw:name': draw_name,
|
||
|
'draw:z-index': '1',
|
||
|
}
|
||
|
if isinstance(node.parent, nodes.TextElement):
|
||
|
attrib['text:anchor-type'] = 'as-char' #vds
|
||
|
else:
|
||
|
attrib['text:anchor-type'] = 'paragraph'
|
||
|
attrib['svg:width'] = width
|
||
|
attrib['svg:height'] = height
|
||
|
el1 = SubElement(current_element, 'draw:frame', attrib=attrib)
|
||
|
el2 = SubElement(el1, 'draw:image', attrib={
|
||
|
'xlink:href': '%s' % (destination, ),
|
||
|
'xlink:type': 'simple',
|
||
|
'xlink:show': 'embed',
|
||
|
'xlink:actuate': 'onLoad',
|
||
|
})
|
||
|
return el1, width
|
||
|
|
||
|
def is_in_table(self, node):
|
||
|
node1 = node.parent
|
||
|
while node1:
|
||
|
if isinstance(node1, docutils.nodes.entry):
|
||
|
return True
|
||
|
node1 = node1.parent
|
||
|
return False
|
||
|
|
||
|
def visit_legend(self, node):
|
||
|
if isinstance(node.parent, docutils.nodes.figure):
|
||
|
el1 = self.current_element[-1]
|
||
|
el1 = el1[0][0]
|
||
|
self.current_element = el1
|
||
|
self.paragraph_style_stack.append(self.rststyle('legend'))
|
||
|
|
||
|
def depart_legend(self, node):
|
||
|
if isinstance(node.parent, docutils.nodes.figure):
|
||
|
self.paragraph_style_stack.pop()
|
||
|
self.set_to_parent()
|
||
|
self.set_to_parent()
|
||
|
self.set_to_parent()
|
||
|
|
||
|
def visit_line_block(self, node):
|
||
|
self.line_indent_level += 1
|
||
|
self.line_block_level += 1
|
||
|
|
||
|
def depart_line_block(self, node):
|
||
|
self.line_indent_level -= 1
|
||
|
self.line_block_level -= 1
|
||
|
|
||
|
def visit_line(self, node):
|
||
|
style = 'lineblock%d' % self.line_indent_level
|
||
|
el1 = SubElement(self.current_element, 'text:p', attrib={
|
||
|
'text:style-name': self.rststyle(style),
|
||
|
})
|
||
|
self.current_element = el1
|
||
|
|
||
|
def depart_line(self, node):
|
||
|
self.set_to_parent()
|
||
|
|
||
|
def visit_literal(self, node):
|
||
|
el = SubElement(self.current_element, 'text:span',
|
||
|
attrib={'text:style-name': self.rststyle('inlineliteral')})
|
||
|
self.set_current_element(el)
|
||
|
|
||
|
def depart_literal(self, node):
|
||
|
self.set_to_parent()
|
||
|
|
||
|
def visit_inline(self, node):
|
||
|
styles = node.attributes.get('classes', ())
|
||
|
if styles:
|
||
|
el = self.current_element
|
||
|
for inline_style in styles:
|
||
|
el = SubElement(el, 'text:span',
|
||
|
attrib={'text:style-name':
|
||
|
self.rststyle(inline_style)})
|
||
|
count = len(styles)
|
||
|
else:
|
||
|
# No style was specified so use a default style (old code
|
||
|
# crashed if no style was given)
|
||
|
el = SubElement(self.current_element, 'text:span')
|
||
|
count = 1
|
||
|
|
||
|
self.set_current_element(el)
|
||
|
self.inline_style_count_stack.append(count)
|
||
|
|
||
|
def depart_inline(self, node):
|
||
|
count = self.inline_style_count_stack.pop()
|
||
|
for x in range(count):
|
||
|
self.set_to_parent()
|
||
|
|
||
|
def _calculate_code_block_padding(self, line):
|
||
|
count = 0
|
||
|
matchobj = SPACES_PATTERN.match(line)
|
||
|
if matchobj:
|
||
|
pad = matchobj.group()
|
||
|
count = len(pad)
|
||
|
else:
|
||
|
matchobj = TABS_PATTERN.match(line)
|
||
|
if matchobj:
|
||
|
pad = matchobj.group()
|
||
|
count = len(pad) * 8
|
||
|
return count
|
||
|
|
||
|
def _add_syntax_highlighting(self, insource, language):
|
||
|
lexer = pygments.lexers.get_lexer_by_name(language, stripall=True)
|
||
|
if language in ('latex', 'tex'):
|
||
|
fmtr = OdtPygmentsLaTeXFormatter(lambda name, parameters=():
|
||
|
self.rststyle(name, parameters),
|
||
|
escape_function=escape_cdata)
|
||
|
else:
|
||
|
fmtr = OdtPygmentsProgFormatter(lambda name, parameters=():
|
||
|
self.rststyle(name, parameters),
|
||
|
escape_function=escape_cdata)
|
||
|
outsource = pygments.highlight(insource, lexer, fmtr)
|
||
|
return outsource
|
||
|
|
||
|
def fill_line(self, line):
|
||
|
line = FILL_PAT1.sub(self.fill_func1, line)
|
||
|
line = FILL_PAT2.sub(self.fill_func2, line)
|
||
|
return line
|
||
|
|
||
|
def fill_func1(self, matchobj):
|
||
|
spaces = matchobj.group(0)
|
||
|
repl = '<text:s text:c="%d"/>' % (len(spaces), )
|
||
|
return repl
|
||
|
|
||
|
def fill_func2(self, matchobj):
|
||
|
spaces = matchobj.group(0)
|
||
|
repl = ' <text:s text:c="%d"/>' % (len(spaces) - 1, )
|
||
|
return repl
|
||
|
|
||
|
def visit_literal_block(self, node):
|
||
|
if len(self.paragraph_style_stack) > 1:
|
||
|
wrapper1 = '<text:p text:style-name="%s">%%s</text:p>' % (
|
||
|
self.rststyle('codeblock-indented'), )
|
||
|
else:
|
||
|
wrapper1 = '<text:p text:style-name="%s">%%s</text:p>' % (
|
||
|
self.rststyle('codeblock'), )
|
||
|
source = node.astext()
|
||
|
if (pygments and
|
||
|
self.settings.add_syntax_highlighting
|
||
|
#and
|
||
|
#node.get('hilight', False)
|
||
|
):
|
||
|
language = node.get('language', 'python')
|
||
|
source = self._add_syntax_highlighting(source, language)
|
||
|
else:
|
||
|
source = escape_cdata(source)
|
||
|
lines = source.split('\n')
|
||
|
# If there is an empty last line, remove it.
|
||
|
if lines[-1] == '':
|
||
|
del lines[-1]
|
||
|
lines1 = ['<wrappertag1 xmlns:text="urn:oasis:names:tc:opendocument:xmlns:text:1.0">']
|
||
|
|
||
|
my_lines = []
|
||
|
for my_line in lines:
|
||
|
my_line = self.fill_line(my_line)
|
||
|
my_line = my_line.replace(" ", "\n")
|
||
|
my_lines.append(my_line)
|
||
|
my_lines_str = '<text:line-break/>'.join(my_lines)
|
||
|
my_lines_str2 = wrapper1 % (my_lines_str, )
|
||
|
lines1.append(my_lines_str2)
|
||
|
lines1.append('</wrappertag1>')
|
||
|
s1 = ''.join(lines1)
|
||
|
if WhichElementTree != "lxml":
|
||
|
s1 = s1.encode("utf-8")
|
||
|
el1 = etree.fromstring(s1)
|
||
|
children = el1.getchildren()
|
||
|
for child in children:
|
||
|
self.current_element.append(child)
|
||
|
|
||
|
def depart_literal_block(self, node):
|
||
|
pass
|
||
|
|
||
|
visit_doctest_block = visit_literal_block
|
||
|
depart_doctest_block = depart_literal_block
|
||
|
|
||
|
# placeholder for math (see docs/dev/todo.txt)
|
||
|
def visit_math(self, node):
|
||
|
self.document.reporter.warning('"math" role not supported',
|
||
|
base_node=node)
|
||
|
self.visit_literal(node)
|
||
|
|
||
|
def depart_math(self, node):
|
||
|
self.depart_literal(node)
|
||
|
|
||
|
def visit_math_block(self, node):
|
||
|
self.document.reporter.warning('"math" directive not supported',
|
||
|
base_node=node)
|
||
|
self.visit_literal_block(node)
|
||
|
|
||
|
def depart_math_block(self, node):
|
||
|
self.depart_literal_block(node)
|
||
|
|
||
|
def visit_meta(self, node):
|
||
|
name = node.attributes.get('name')
|
||
|
content = node.attributes.get('content')
|
||
|
if name is not None and content is not None:
|
||
|
self.meta_dict[name] = content
|
||
|
|
||
|
def depart_meta(self, node):
|
||
|
pass
|
||
|
|
||
|
def visit_option_list(self, node):
|
||
|
table_name = 'tableoption'
|
||
|
#
|
||
|
# Generate automatic styles
|
||
|
if not self.optiontablestyles_generated:
|
||
|
self.optiontablestyles_generated = True
|
||
|
el = SubElement(self.automatic_styles, 'style:style', attrib={
|
||
|
'style:name': self.rststyle(table_name),
|
||
|
'style:family': 'table'}, nsdict=SNSD)
|
||
|
el1 = SubElement(el, 'style:table-properties', attrib={
|
||
|
'style:width': '17.59cm',
|
||
|
'table:align': 'left',
|
||
|
'style:shadow': 'none'}, nsdict=SNSD)
|
||
|
el = SubElement(self.automatic_styles, 'style:style', attrib={
|
||
|
'style:name': self.rststyle('%s.%%c' % table_name, ( 'A', )),
|
||
|
'style:family': 'table-column'}, nsdict=SNSD)
|
||
|
el1 = SubElement(el, 'style:table-column-properties', attrib={
|
||
|
'style:column-width': '4.999cm'}, nsdict=SNSD)
|
||
|
el = SubElement(self.automatic_styles, 'style:style', attrib={
|
||
|
'style:name': self.rststyle('%s.%%c' % table_name, ( 'B', )),
|
||
|
'style:family': 'table-column'}, nsdict=SNSD)
|
||
|
el1 = SubElement(el, 'style:table-column-properties', attrib={
|
||
|
'style:column-width': '12.587cm'}, nsdict=SNSD)
|
||
|
el = SubElement(self.automatic_styles, 'style:style', attrib={
|
||
|
'style:name': self.rststyle(
|
||
|
'%s.%%c%%d' % table_name, ( 'A', 1, )),
|
||
|
'style:family': 'table-cell'}, nsdict=SNSD)
|
||
|
el1 = SubElement(el, 'style:table-cell-properties', attrib={
|
||
|
'fo:background-color': 'transparent',
|
||
|
'fo:padding': '0.097cm',
|
||
|
'fo:border-left': '0.035cm solid #000000',
|
||
|
'fo:border-right': 'none',
|
||
|
'fo:border-top': '0.035cm solid #000000',
|
||
|
'fo:border-bottom': '0.035cm solid #000000'}, nsdict=SNSD)
|
||
|
el2 = SubElement(el1, 'style:background-image', nsdict=SNSD)
|
||
|
el = SubElement(self.automatic_styles, 'style:style', attrib={
|
||
|
'style:name': self.rststyle(
|
||
|
'%s.%%c%%d' % table_name, ( 'B', 1, )),
|
||
|
'style:family': 'table-cell'}, nsdict=SNSD)
|
||
|
el1 = SubElement(el, 'style:table-cell-properties', attrib={
|
||
|
'fo:padding': '0.097cm',
|
||
|
'fo:border': '0.035cm solid #000000'}, nsdict=SNSD)
|
||
|
el = SubElement(self.automatic_styles, 'style:style', attrib={
|
||
|
'style:name': self.rststyle(
|
||
|
'%s.%%c%%d' % table_name, ( 'A', 2, )),
|
||
|
'style:family': 'table-cell'}, nsdict=SNSD)
|
||
|
el1 = SubElement(el, 'style:table-cell-properties', attrib={
|
||
|
'fo:padding': '0.097cm',
|
||
|
'fo:border-left': '0.035cm solid #000000',
|
||
|
'fo:border-right': 'none',
|
||
|
'fo:border-top': 'none',
|
||
|
'fo:border-bottom': '0.035cm solid #000000'}, nsdict=SNSD)
|
||
|
el = SubElement(self.automatic_styles, 'style:style', attrib={
|
||
|
'style:name': self.rststyle(
|
||
|
'%s.%%c%%d' % table_name, ( 'B', 2, )),
|
||
|
'style:family': 'table-cell'}, nsdict=SNSD)
|
||
|
el1 = SubElement(el, 'style:table-cell-properties', attrib={
|
||
|
'fo:padding': '0.097cm',
|
||
|
'fo:border-left': '0.035cm solid #000000',
|
||
|
'fo:border-right': '0.035cm solid #000000',
|
||
|
'fo:border-top': 'none',
|
||
|
'fo:border-bottom': '0.035cm solid #000000'}, nsdict=SNSD)
|
||
|
#
|
||
|
# Generate table data
|
||
|
el = self.append_child('table:table', attrib={
|
||
|
'table:name': self.rststyle(table_name),
|
||
|
'table:style-name': self.rststyle(table_name),
|
||
|
})
|
||
|
el1 = SubElement(el, 'table:table-column', attrib={
|
||
|
'table:style-name': self.rststyle(
|
||
|
'%s.%%c' % table_name, ( 'A', ))})
|
||
|
el1 = SubElement(el, 'table:table-column', attrib={
|
||
|
'table:style-name': self.rststyle(
|
||
|
'%s.%%c' % table_name, ( 'B', ))})
|
||
|
el1 = SubElement(el, 'table:table-header-rows')
|
||
|
el2 = SubElement(el1, 'table:table-row')
|
||
|
el3 = SubElement(el2, 'table:table-cell', attrib={
|
||
|
'table:style-name': self.rststyle(
|
||
|
'%s.%%c%%d' % table_name, ( 'A', 1, )),
|
||
|
'office:value-type': 'string'})
|
||
|
el4 = SubElement(el3, 'text:p', attrib={
|
||
|
'text:style-name': 'Table_20_Heading'})
|
||
|
el4.text= 'Option'
|
||
|
el3 = SubElement(el2, 'table:table-cell', attrib={
|
||
|
'table:style-name': self.rststyle(
|
||
|
'%s.%%c%%d' % table_name, ( 'B', 1, )),
|
||
|
'office:value-type': 'string'})
|
||
|
el4 = SubElement(el3, 'text:p', attrib={
|
||
|
'text:style-name': 'Table_20_Heading'})
|
||
|
el4.text= 'Description'
|
||
|
self.set_current_element(el)
|
||
|
|
||
|
def depart_option_list(self, node):
|
||
|
self.set_to_parent()
|
||
|
|
||
|
def visit_option_list_item(self, node):
|
||
|
el = self.append_child('table:table-row')
|
||
|
self.set_current_element(el)
|
||
|
|
||
|
def depart_option_list_item(self, node):
|
||
|
self.set_to_parent()
|
||
|
|
||
|
def visit_option_group(self, node):
|
||
|
el = self.append_child('table:table-cell', attrib={
|
||
|
'table:style-name': 'Table%d.A2' % self.table_count,
|
||
|
'office:value-type': 'string',
|
||
|
})
|
||
|
self.set_current_element(el)
|
||
|
|
||
|
def depart_option_group(self, node):
|
||
|
self.set_to_parent()
|
||
|
|
||
|
def visit_option(self, node):
|
||
|
el = self.append_child('text:p', attrib={
|
||
|
'text:style-name': 'Table_20_Contents'})
|
||
|
el.text = node.astext()
|
||
|
|
||
|
def depart_option(self, node):
|
||
|
pass
|
||
|
|
||
|
def visit_option_string(self, node):
|
||
|
pass
|
||
|
|
||
|
def depart_option_string(self, node):
|
||
|
pass
|
||
|
|
||
|
def visit_option_argument(self, node):
|
||
|
pass
|
||
|
|
||
|
def depart_option_argument(self, node):
|
||
|
pass
|
||
|
|
||
|
def visit_description(self, node):
|
||
|
el = self.append_child('table:table-cell', attrib={
|
||
|
'table:style-name': 'Table%d.B2' % self.table_count,
|
||
|
'office:value-type': 'string',
|
||
|
})
|
||
|
el1 = SubElement(el, 'text:p', attrib={
|
||
|
'text:style-name': 'Table_20_Contents'})
|
||
|
el1.text = node.astext()
|
||
|
raise nodes.SkipChildren()
|
||
|
|
||
|
def depart_description(self, node):
|
||
|
pass
|
||
|
|
||
|
def visit_paragraph(self, node):
|
||
|
self.in_paragraph = True
|
||
|
if self.in_header:
|
||
|
el = self.append_p('header')
|
||
|
elif self.in_footer:
|
||
|
el = self.append_p('footer')
|
||
|
else:
|
||
|
style_name = self.paragraph_style_stack[-1]
|
||
|
el = self.append_child('text:p',
|
||
|
attrib={'text:style-name': style_name})
|
||
|
self.append_pending_ids(el)
|
||
|
self.set_current_element(el)
|
||
|
|
||
|
def depart_paragraph(self, node):
|
||
|
self.in_paragraph = False
|
||
|
self.set_to_parent()
|
||
|
if self.in_header:
|
||
|
self.header_content.append(
|
||
|
self.current_element.getchildren()[-1])
|
||
|
self.current_element.remove(
|
||
|
self.current_element.getchildren()[-1])
|
||
|
elif self.in_footer:
|
||
|
self.footer_content.append(
|
||
|
self.current_element.getchildren()[-1])
|
||
|
self.current_element.remove(
|
||
|
self.current_element.getchildren()[-1])
|
||
|
|
||
|
def visit_problematic(self, node):
|
||
|
pass
|
||
|
|
||
|
def depart_problematic(self, node):
|
||
|
pass
|
||
|
|
||
|
def visit_raw(self, node):
|
||
|
if 'format' in node.attributes:
|
||
|
formats = node.attributes['format']
|
||
|
formatlist = formats.split()
|
||
|
if 'odt' in formatlist:
|
||
|
rawstr = node.astext()
|
||
|
attrstr = ' '.join(['%s="%s"' % (k, v, )
|
||
|
for k,v in CONTENT_NAMESPACE_ATTRIB.items()])
|
||
|
contentstr = '<stuff %s>%s</stuff>' % (attrstr, rawstr, )
|
||
|
if WhichElementTree != "lxml":
|
||
|
contentstr = contentstr.encode("utf-8")
|
||
|
content = etree.fromstring(contentstr)
|
||
|
elements = content.getchildren()
|
||
|
if len(elements) > 0:
|
||
|
el1 = elements[0]
|
||
|
if self.in_header:
|
||
|
pass
|
||
|
elif self.in_footer:
|
||
|
pass
|
||
|
else:
|
||
|
self.current_element.append(el1)
|
||
|
raise nodes.SkipChildren()
|
||
|
|
||
|
def depart_raw(self, node):
|
||
|
if self.in_header:
|
||
|
pass
|
||
|
elif self.in_footer:
|
||
|
pass
|
||
|
else:
|
||
|
pass
|
||
|
|
||
|
def visit_reference(self, node):
|
||
|
text = node.astext()
|
||
|
if self.settings.create_links:
|
||
|
if node.has_key('refuri'):
|
||
|
href = node['refuri']
|
||
|
if ( self.settings.cloak_email_addresses
|
||
|
and href.startswith('mailto:')):
|
||
|
href = self.cloak_mailto(href)
|
||
|
el = self.append_child('text:a', attrib={
|
||
|
'xlink:href': '%s' % href,
|
||
|
'xlink:type': 'simple',
|
||
|
})
|
||
|
self.set_current_element(el)
|
||
|
elif node.has_key('refid'):
|
||
|
if self.settings.create_links:
|
||
|
href = node['refid']
|
||
|
el = self.append_child('text:reference-ref', attrib={
|
||
|
'text:ref-name': '%s' % href,
|
||
|
'text:reference-format': 'text',
|
||
|
})
|
||
|
else:
|
||
|
self.document.reporter.warning(
|
||
|
'References must have "refuri" or "refid" attribute.')
|
||
|
if (self.in_table_of_contents and
|
||
|
len(node.children) >= 1 and
|
||
|
isinstance(node.children[0], docutils.nodes.generated)):
|
||
|
node.remove(node.children[0])
|
||
|
|
||
|
def depart_reference(self, node):
|
||
|
if self.settings.create_links:
|
||
|
if node.has_key('refuri'):
|
||
|
self.set_to_parent()
|
||
|
|
||
|
def visit_rubric(self, node):
|
||
|
style_name = self.rststyle('rubric')
|
||
|
classes = node.get('classes')
|
||
|
if classes:
|
||
|
class1 = classes[0]
|
||
|
if class1:
|
||
|
style_name = class1
|
||
|
el = SubElement(self.current_element, 'text:h', attrib = {
|
||
|
#'text:outline-level': '%d' % section_level,
|
||
|
#'text:style-name': 'Heading_20_%d' % section_level,
|
||
|
'text:style-name': style_name,
|
||
|
})
|
||
|
text = node.astext()
|
||
|
el.text = self.encode(text)
|
||
|
|
||
|
def depart_rubric(self, node):
|
||
|
pass
|
||
|
|
||
|
def visit_section(self, node, move_ids=1):
|
||
|
self.section_level += 1
|
||
|
self.section_count += 1
|
||
|
if self.settings.create_sections:
|
||
|
el = self.append_child('text:section', attrib={
|
||
|
'text:name': 'Section%d' % self.section_count,
|
||
|
'text:style-name': 'Sect%d' % self.section_level,
|
||
|
})
|
||
|
self.set_current_element(el)
|
||
|
|
||
|
def depart_section(self, node):
|
||
|
self.section_level -= 1
|
||
|
if self.settings.create_sections:
|
||
|
self.set_to_parent()
|
||
|
|
||
|
def visit_strong(self, node):
|
||
|
el = SubElement(self.current_element, 'text:span',
|
||
|
attrib={'text:style-name': self.rststyle('strong')})
|
||
|
self.set_current_element(el)
|
||
|
|
||
|
def depart_strong(self, node):
|
||
|
self.set_to_parent()
|
||
|
|
||
|
def visit_substitution_definition(self, node):
|
||
|
raise nodes.SkipChildren()
|
||
|
|
||
|
def depart_substitution_definition(self, node):
|
||
|
pass
|
||
|
|
||
|
def visit_system_message(self, node):
|
||
|
pass
|
||
|
|
||
|
def depart_system_message(self, node):
|
||
|
pass
|
||
|
|
||
|
def get_table_style(self, node):
|
||
|
table_style = None
|
||
|
table_name = None
|
||
|
use_predefined_table_style = False
|
||
|
str_classes = node.get('classes')
|
||
|
if str_classes is not None:
|
||
|
for str_class in str_classes:
|
||
|
if str_class.startswith(TABLESTYLEPREFIX):
|
||
|
table_name = str_class
|
||
|
use_predefined_table_style = True
|
||
|
break
|
||
|
if table_name is not None:
|
||
|
table_style = self.table_styles.get(table_name)
|
||
|
if table_style is None:
|
||
|
# If we can't find the table style, issue warning
|
||
|
# and use the default table style.
|
||
|
self.document.reporter.warning(
|
||
|
'Can\'t find table style "%s". Using default.' % (
|
||
|
table_name, ))
|
||
|
table_name = TABLENAMEDEFAULT
|
||
|
table_style = self.table_styles.get(table_name)
|
||
|
if table_style is None:
|
||
|
# If we can't find the default table style, issue a warning
|
||
|
# and use a built-in default style.
|
||
|
self.document.reporter.warning(
|
||
|
'Can\'t find default table style "%s". Using built-in default.' % (
|
||
|
table_name, ))
|
||
|
table_style = BUILTIN_DEFAULT_TABLE_STYLE
|
||
|
else:
|
||
|
table_name = TABLENAMEDEFAULT
|
||
|
table_style = self.table_styles.get(table_name)
|
||
|
if table_style is None:
|
||
|
# If we can't find the default table style, issue a warning
|
||
|
# and use a built-in default style.
|
||
|
self.document.reporter.warning(
|
||
|
'Can\'t find default table style "%s". Using built-in default.' % (
|
||
|
table_name, ))
|
||
|
table_style = BUILTIN_DEFAULT_TABLE_STYLE
|
||
|
return table_style
|
||
|
|
||
|
def visit_table(self, node):
|
||
|
self.table_count += 1
|
||
|
table_style = self.get_table_style(node)
|
||
|
table_name = '%s%%d' % TABLESTYLEPREFIX
|
||
|
el1 = SubElement(self.automatic_styles, 'style:style', attrib={
|
||
|
'style:name': self.rststyle(
|
||
|
'%s' % table_name, ( self.table_count, )),
|
||
|
'style:family': 'table',
|
||
|
}, nsdict=SNSD)
|
||
|
if table_style.backgroundcolor is None:
|
||
|
el1_1 = SubElement(el1, 'style:table-properties', attrib={
|
||
|
#'style:width': '17.59cm',
|
||
|
#'table:align': 'margins',
|
||
|
'table:align': 'left',
|
||
|
'fo:margin-top': '0in',
|
||
|
'fo:margin-bottom': '0.10in',
|
||
|
}, nsdict=SNSD)
|
||
|
else:
|
||
|
el1_1 = SubElement(el1, 'style:table-properties', attrib={
|
||
|
#'style:width': '17.59cm',
|
||
|
'table:align': 'margins',
|
||
|
'fo:margin-top': '0in',
|
||
|
'fo:margin-bottom': '0.10in',
|
||
|
'fo:background-color': table_style.backgroundcolor,
|
||
|
}, nsdict=SNSD)
|
||
|
# We use a single cell style for all cells in this table.
|
||
|
# That's probably not correct, but seems to work.
|
||
|
el2 = SubElement(self.automatic_styles, 'style:style', attrib={
|
||
|
'style:name': self.rststyle(
|
||
|
'%s.%%c%%d' % table_name, ( self.table_count, 'A', 1, )),
|
||
|
'style:family': 'table-cell',
|
||
|
}, nsdict=SNSD)
|
||
|
thickness = self.settings.table_border_thickness
|
||
|
if thickness is None:
|
||
|
line_style1 = table_style.border
|
||
|
else:
|
||
|
line_style1 = '0.%03dcm solid #000000' % (thickness, )
|
||
|
el2_1 = SubElement(el2, 'style:table-cell-properties', attrib={
|
||
|
'fo:padding': '0.049cm',
|
||
|
'fo:border-left': line_style1,
|
||
|
'fo:border-right': line_style1,
|
||
|
'fo:border-top': line_style1,
|
||
|
'fo:border-bottom': line_style1,
|
||
|
}, nsdict=SNSD)
|
||
|
title = None
|
||
|
for child in node.children:
|
||
|
if child.tagname == 'title':
|
||
|
title = child.astext()
|
||
|
break
|
||
|
if title is not None:
|
||
|
el3 = self.append_p('table-title', title)
|
||
|
else:
|
||
|
pass
|
||
|
el4 = SubElement(self.current_element, 'table:table', attrib={
|
||
|
'table:name': self.rststyle(
|
||
|
'%s' % table_name, ( self.table_count, )),
|
||
|
'table:style-name': self.rststyle(
|
||
|
'%s' % table_name, ( self.table_count, )),
|
||
|
})
|
||
|
self.set_current_element(el4)
|
||
|
self.current_table_style = el1
|
||
|
self.table_width = 0.0
|
||
|
|
||
|
def depart_table(self, node):
|
||
|
attribkey = add_ns('style:width', nsdict=SNSD)
|
||
|
attribval = '%.4fin' % (self.table_width, )
|
||
|
el1 = self.current_table_style
|
||
|
el2 = el1[0]
|
||
|
el2.attrib[attribkey] = attribval
|
||
|
self.set_to_parent()
|
||
|
|
||
|
def visit_tgroup(self, node):
|
||
|
self.column_count = ord('A') - 1
|
||
|
|
||
|
def depart_tgroup(self, node):
|
||
|
pass
|
||
|
|
||
|
def visit_colspec(self, node):
|
||
|
self.column_count += 1
|
||
|
colspec_name = self.rststyle(
|
||
|
'%s%%d.%%s' % TABLESTYLEPREFIX,
|
||
|
(self.table_count, chr(self.column_count), )
|
||
|
)
|
||
|
colwidth = node['colwidth'] / 12.0
|
||
|
el1 = SubElement(self.automatic_styles, 'style:style', attrib={
|
||
|
'style:name': colspec_name,
|
||
|
'style:family': 'table-column',
|
||
|
}, nsdict=SNSD)
|
||
|
el1_1 = SubElement(el1, 'style:table-column-properties', attrib={
|
||
|
'style:column-width': '%.4fin' % colwidth
|
||
|
},
|
||
|
nsdict=SNSD)
|
||
|
el2 = self.append_child('table:table-column', attrib={
|
||
|
'table:style-name': colspec_name,
|
||
|
})
|
||
|
self.table_width += colwidth
|
||
|
|
||
|
def depart_colspec(self, node):
|
||
|
pass
|
||
|
|
||
|
def visit_thead(self, node):
|
||
|
el = self.append_child('table:table-header-rows')
|
||
|
self.set_current_element(el)
|
||
|
self.in_thead = True
|
||
|
self.paragraph_style_stack.append('Table_20_Heading')
|
||
|
|
||
|
def depart_thead(self, node):
|
||
|
self.set_to_parent()
|
||
|
self.in_thead = False
|
||
|
self.paragraph_style_stack.pop()
|
||
|
|
||
|
def visit_row(self, node):
|
||
|
self.column_count = ord('A') - 1
|
||
|
el = self.append_child('table:table-row')
|
||
|
self.set_current_element(el)
|
||
|
|
||
|
def depart_row(self, node):
|
||
|
self.set_to_parent()
|
||
|
|
||
|
def visit_entry(self, node):
|
||
|
self.column_count += 1
|
||
|
cellspec_name = self.rststyle(
|
||
|
'%s%%d.%%c%%d' % TABLESTYLEPREFIX,
|
||
|
(self.table_count, 'A', 1, )
|
||
|
)
|
||
|
attrib={
|
||
|
'table:style-name': cellspec_name,
|
||
|
'office:value-type': 'string',
|
||
|
}
|
||
|
morecols = node.get('morecols', 0)
|
||
|
if morecols > 0:
|
||
|
attrib['table:number-columns-spanned'] = '%d' % (morecols + 1,)
|
||
|
self.column_count += morecols
|
||
|
morerows = node.get('morerows', 0)
|
||
|
if morerows > 0:
|
||
|
attrib['table:number-rows-spanned'] = '%d' % (morerows + 1,)
|
||
|
el1 = self.append_child('table:table-cell', attrib=attrib)
|
||
|
self.set_current_element(el1)
|
||
|
|
||
|
def depart_entry(self, node):
|
||
|
self.set_to_parent()
|
||
|
|
||
|
def visit_tbody(self, node):
|
||
|
pass
|
||
|
|
||
|
def depart_tbody(self, node):
|
||
|
pass
|
||
|
|
||
|
def visit_target(self, node):
|
||
|
#
|
||
|
# I don't know how to implement targets in ODF.
|
||
|
# How do we create a target in oowriter? A cross-reference?
|
||
|
if not (node.has_key('refuri') or node.has_key('refid')
|
||
|
or node.has_key('refname')):
|
||
|
pass
|
||
|
else:
|
||
|
pass
|
||
|
|
||
|
def depart_target(self, node):
|
||
|
pass
|
||
|
|
||
|
def visit_title(self, node, move_ids=1, title_type='title'):
|
||
|
if isinstance(node.parent, docutils.nodes.section):
|
||
|
section_level = self.section_level
|
||
|
if section_level > 7:
|
||
|
self.document.reporter.warning(
|
||
|
'Heading/section levels greater than 7 not supported.')
|
||
|
self.document.reporter.warning(
|
||
|
' Reducing to heading level 7 for heading: "%s"' % (
|
||
|
node.astext(), ))
|
||
|
section_level = 7
|
||
|
el1 = self.append_child('text:h', attrib = {
|
||
|
'text:outline-level': '%d' % section_level,
|
||
|
#'text:style-name': 'Heading_20_%d' % section_level,
|
||
|
'text:style-name': self.rststyle(
|
||
|
'heading%d', (section_level, )),
|
||
|
})
|
||
|
self.append_pending_ids(el1)
|
||
|
self.set_current_element(el1)
|
||
|
elif isinstance(node.parent, docutils.nodes.document):
|
||
|
# text = self.settings.title
|
||
|
#else:
|
||
|
# text = node.astext()
|
||
|
el1 = SubElement(self.current_element, 'text:p', attrib = {
|
||
|
'text:style-name': self.rststyle(title_type),
|
||
|
})
|
||
|
self.append_pending_ids(el1)
|
||
|
text = node.astext()
|
||
|
self.title = text
|
||
|
self.found_doc_title = True
|
||
|
self.set_current_element(el1)
|
||
|
|
||
|
def depart_title(self, node):
|
||
|
if (isinstance(node.parent, docutils.nodes.section) or
|
||
|
isinstance(node.parent, docutils.nodes.document)):
|
||
|
self.set_to_parent()
|
||
|
|
||
|
def visit_subtitle(self, node, move_ids=1):
|
||
|
self.visit_title(node, move_ids, title_type='subtitle')
|
||
|
|
||
|
def depart_subtitle(self, node):
|
||
|
self.depart_title(node)
|
||
|
|
||
|
def visit_title_reference(self, node):
|
||
|
el = self.append_child('text:span', attrib={
|
||
|
'text:style-name': self.rststyle('quotation')})
|
||
|
el.text = self.encode(node.astext())
|
||
|
raise nodes.SkipChildren()
|
||
|
|
||
|
def depart_title_reference(self, node):
|
||
|
pass
|
||
|
|
||
|
def generate_table_of_content_entry_template(self, el1):
|
||
|
for idx in range(1, 11):
|
||
|
el2 = SubElement(el1,
|
||
|
'text:table-of-content-entry-template',
|
||
|
attrib={
|
||
|
'text:outline-level': "%d" % (idx, ),
|
||
|
'text:style-name': self.rststyle('contents-%d' % (idx, )),
|
||
|
})
|
||
|
el3 = SubElement(el2, 'text:index-entry-chapter')
|
||
|
el3 = SubElement(el2, 'text:index-entry-text')
|
||
|
el3 = SubElement(el2, 'text:index-entry-tab-stop', attrib={
|
||
|
'style:leader-char': ".",
|
||
|
'style:type': "right",
|
||
|
})
|
||
|
el3 = SubElement(el2, 'text:index-entry-page-number')
|
||
|
|
||
|
def find_title_label(self, node, class_type, label_key):
|
||
|
label = ''
|
||
|
title_node = None
|
||
|
for child in node.children:
|
||
|
if isinstance(child, class_type):
|
||
|
title_node = child
|
||
|
break
|
||
|
if title_node is not None:
|
||
|
label = title_node.astext()
|
||
|
else:
|
||
|
label = self.language.labels[label_key]
|
||
|
return label
|
||
|
|
||
|
def visit_topic(self, node):
|
||
|
if 'classes' in node.attributes:
|
||
|
if 'contents' in node.attributes['classes']:
|
||
|
label = self.find_title_label(node, docutils.nodes.title,
|
||
|
'contents')
|
||
|
if self.settings.generate_oowriter_toc:
|
||
|
el1 = self.append_child('text:table-of-content', attrib={
|
||
|
'text:name': 'Table of Contents1',
|
||
|
'text:protected': 'true',
|
||
|
'text:style-name': 'Sect1',
|
||
|
})
|
||
|
el2 = SubElement(el1,
|
||
|
'text:table-of-content-source',
|
||
|
attrib={
|
||
|
'text:outline-level': '10',
|
||
|
})
|
||
|
el3 =SubElement(el2, 'text:index-title-template', attrib={
|
||
|
'text:style-name': 'Contents_20_Heading',
|
||
|
})
|
||
|
el3.text = label
|
||
|
self.generate_table_of_content_entry_template(el2)
|
||
|
el4 = SubElement(el1, 'text:index-body')
|
||
|
el5 = SubElement(el4, 'text:index-title')
|
||
|
el6 = SubElement(el5, 'text:p', attrib={
|
||
|
'text:style-name': self.rststyle('contents-heading'),
|
||
|
})
|
||
|
el6.text = label
|
||
|
self.save_current_element = self.current_element
|
||
|
self.table_of_content_index_body = el4
|
||
|
self.set_current_element(el4)
|
||
|
else:
|
||
|
el = self.append_p('horizontalline')
|
||
|
el = self.append_p('centeredtextbody')
|
||
|
el1 = SubElement(el, 'text:span',
|
||
|
attrib={'text:style-name': self.rststyle('strong')})
|
||
|
el1.text = label
|
||
|
self.in_table_of_contents = True
|
||
|
elif 'abstract' in node.attributes['classes']:
|
||
|
el = self.append_p('horizontalline')
|
||
|
el = self.append_p('centeredtextbody')
|
||
|
el1 = SubElement(el, 'text:span',
|
||
|
attrib={'text:style-name': self.rststyle('strong')})
|
||
|
label = self.find_title_label(node, docutils.nodes.title,
|
||
|
'abstract')
|
||
|
el1.text = label
|
||
|
elif 'dedication' in node.attributes['classes']:
|
||
|
el = self.append_p('horizontalline')
|
||
|
el = self.append_p('centeredtextbody')
|
||
|
el1 = SubElement(el, 'text:span',
|
||
|
attrib={'text:style-name': self.rststyle('strong')})
|
||
|
label = self.find_title_label(node, docutils.nodes.title,
|
||
|
'dedication')
|
||
|
el1.text = label
|
||
|
|
||
|
def depart_topic(self, node):
|
||
|
if 'classes' in node.attributes:
|
||
|
if 'contents' in node.attributes['classes']:
|
||
|
if self.settings.generate_oowriter_toc:
|
||
|
self.update_toc_page_numbers(
|
||
|
self.table_of_content_index_body)
|
||
|
self.set_current_element(self.save_current_element)
|
||
|
else:
|
||
|
el = self.append_p('horizontalline')
|
||
|
self.in_table_of_contents = False
|
||
|
|
||
|
def update_toc_page_numbers(self, el):
|
||
|
collection = []
|
||
|
self.update_toc_collect(el, 0, collection)
|
||
|
self.update_toc_add_numbers(collection)
|
||
|
|
||
|
def update_toc_collect(self, el, level, collection):
|
||
|
collection.append((level, el))
|
||
|
level += 1
|
||
|
for child_el in el.getchildren():
|
||
|
if child_el.tag != 'text:index-body':
|
||
|
self.update_toc_collect(child_el, level, collection)
|
||
|
|
||
|
def update_toc_add_numbers(self, collection):
|
||
|
for level, el1 in collection:
|
||
|
if (el1.tag == 'text:p' and
|
||
|
el1.text != 'Table of Contents'):
|
||
|
el2 = SubElement(el1, 'text:tab')
|
||
|
el2.tail = '9999'
|
||
|
|
||
|
|
||
|
def visit_transition(self, node):
|
||
|
el = self.append_p('horizontalline')
|
||
|
|
||
|
def depart_transition(self, node):
|
||
|
pass
|
||
|
|
||
|
#
|
||
|
# Admonitions
|
||
|
#
|
||
|
def visit_warning(self, node):
|
||
|
self.generate_admonition(node, 'warning')
|
||
|
|
||
|
def depart_warning(self, node):
|
||
|
self.paragraph_style_stack.pop()
|
||
|
|
||
|
def visit_attention(self, node):
|
||
|
self.generate_admonition(node, 'attention')
|
||
|
|
||
|
depart_attention = depart_warning
|
||
|
|
||
|
def visit_caution(self, node):
|
||
|
self.generate_admonition(node, 'caution')
|
||
|
|
||
|
depart_caution = depart_warning
|
||
|
|
||
|
def visit_danger(self, node):
|
||
|
self.generate_admonition(node, 'danger')
|
||
|
|
||
|
depart_danger = depart_warning
|
||
|
|
||
|
def visit_error(self, node):
|
||
|
self.generate_admonition(node, 'error')
|
||
|
|
||
|
depart_error = depart_warning
|
||
|
|
||
|
def visit_hint(self, node):
|
||
|
self.generate_admonition(node, 'hint')
|
||
|
|
||
|
depart_hint = depart_warning
|
||
|
|
||
|
def visit_important(self, node):
|
||
|
self.generate_admonition(node, 'important')
|
||
|
|
||
|
depart_important = depart_warning
|
||
|
|
||
|
def visit_note(self, node):
|
||
|
self.generate_admonition(node, 'note')
|
||
|
|
||
|
depart_note = depart_warning
|
||
|
|
||
|
def visit_tip(self, node):
|
||
|
self.generate_admonition(node, 'tip')
|
||
|
|
||
|
depart_tip = depart_warning
|
||
|
|
||
|
def visit_admonition(self, node):
|
||
|
title = None
|
||
|
for child in node.children:
|
||
|
if child.tagname == 'title':
|
||
|
title = child.astext()
|
||
|
if title is None:
|
||
|
classes1 = node.get('classes')
|
||
|
if classes1:
|
||
|
title = classes1[0]
|
||
|
self.generate_admonition(node, 'generic', title)
|
||
|
|
||
|
depart_admonition = depart_warning
|
||
|
|
||
|
def generate_admonition(self, node, label, title=None):
|
||
|
if hasattr(self.language, 'labels'):
|
||
|
translated_label = self.language.labels[label]
|
||
|
else:
|
||
|
translated_label = label
|
||
|
el1 = SubElement(self.current_element, 'text:p', attrib={
|
||
|
'text:style-name': self.rststyle(
|
||
|
'admon-%s-hdr', (label, )),
|
||
|
})
|
||
|
if title:
|
||
|
el1.text = title
|
||
|
else:
|
||
|
el1.text = '%s!' % (translated_label.capitalize(), )
|
||
|
s1 = self.rststyle('admon-%s-body', (label, ))
|
||
|
self.paragraph_style_stack.append(s1)
|
||
|
|
||
|
#
|
||
|
# Roles (e.g. subscript, superscript, strong, ...
|
||
|
#
|
||
|
def visit_subscript(self, node):
|
||
|
el = self.append_child('text:span', attrib={
|
||
|
'text:style-name': 'rststyle-subscript',
|
||
|
})
|
||
|
self.set_current_element(el)
|
||
|
|
||
|
def depart_subscript(self, node):
|
||
|
self.set_to_parent()
|
||
|
|
||
|
def visit_superscript(self, node):
|
||
|
el = self.append_child('text:span', attrib={
|
||
|
'text:style-name': 'rststyle-superscript',
|
||
|
})
|
||
|
self.set_current_element(el)
|
||
|
|
||
|
def depart_superscript(self, node):
|
||
|
self.set_to_parent()
|
||
|
|
||
|
|
||
|
# Use an own reader to modify transformations done.
|
||
|
class Reader(standalone.Reader):
|
||
|
|
||
|
def get_transforms(self):
|
||
|
default = standalone.Reader.get_transforms(self)
|
||
|
if self.settings.create_links:
|
||
|
return default
|
||
|
return [ i
|
||
|
for i in default
|
||
|
if i is not references.DanglingReferences ]
|