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.
3500 lines
132 KiB
3500 lines
132 KiB
# $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 ]
|
|
|