| # $Id: __init__.py 6381 2010-07-26 19:26:14Z 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 inspect |
| import imp |
| import copy |
| import docutils |
| from docutils import frontend, nodes, utils, writers, languages |
| from docutils.parsers import rst |
| from docutils.readers import standalone |
| from docutils.transforms import references |
| |
| |
| 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, exp: |
| pygments = None |
| |
| # |
| # Is the PIL imaging library installed? |
| try: |
| import Image |
| except ImportError, exp: |
| Image = 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': |
| class _ElementInterfaceWrapper(etree._ElementInterface): |
| def __init__(self, tag, attrib=None): |
| etree._ElementInterface.__init__(self, tag, attrib) |
| if attrib is None: |
| attrib = {} |
| self.parent = None |
| def setparent(self, parent): |
| self.parent = parent |
| def getparent(self): |
| return self.parent |
| |
| |
| # |
| # 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() |
| 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 = 'opendocument odf 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) |
| content = self.visitor.content_astext() |
| self.write_zip_str(zfile, 'content.xml', content) |
| self.write_zip_str(zfile, 'mimetype', self.MIME_TYPE) |
| 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() |
| self.write_zip_str(zfile, 'styles.xml', s1) |
| s1 = self.get_settings() |
| self.write_zip_str(zfile, 'settings.xml', s1) |
| self.store_embedded_files(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 write_zip_str(self, zfile, name, bytes): |
| 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 = zipfile.ZIP_DEFLATED |
| 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: |
| # encode/decode |
| destination1 = destination.decode('latin-1').encode('utf-8') |
| zfile.write(source, destination1, zipfile.ZIP_STORED) |
| 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 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': '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-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.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, )) |
| self.format_map[rststyle] = format |
| 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.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 |
| |
| 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) |
| |
| 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 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 = master_el.find(path) |
| 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): |
| 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 |
| if current_element is None: |
| parent = SubElement(parent, 'text:p', attrib={ |
| 'text:style-name': self.rststyle(style_name), |
| }) |
| 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 = SubElement(parent, 'text:p', attrib={ |
| 'text:style-name': self.rststyle(style_name), |
| }) |
| 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): |
| 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): |
| 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 = '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): |
| 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, '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(self, node): |
| self.paragraph_style_stack.append(self.rststyle('blockindent')) |
| 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_definition_list(self, node): |
| pass |
| |
| def depart_definition_list(self, node): |
| pass |
| |
| 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('textbody') |
| el1 = SubElement(el, 'text:span', |
| attrib={'text:style-name': self.rststyle('strong')}) |
| #el1.text = node.astext() |
| self.set_current_element(el1) |
| |
| def depart_term(self, node): |
| self.set_to_parent() |
| self.set_to_parent() |
| |
| 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): |
| 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() |
| |
| 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) |
| el.text = '[' |
| if self.settings.create_links: |
| el1 = self.append_child('text:reference-mark-start', attrib={ |
| 'text:name': '%s' % (self.citation_id, ), |
| }) |
| |
| def depart_label(self, node): |
| if isinstance(node.parent, docutils.nodes.footnote): |
| pass |
| elif self.citation_id is not None: |
| self.current_element.text += ']' |
| if self.settings.create_links: |
| el = self.append_child('text:reference-mark-end', attrib={ |
| 'text:name': '%s' % (self.citation_id, ), |
| }) |
| 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 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, ) |
| 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 |
| if attr in node.attributes: |
| size = node.attributes[attr] |
| unit = size[-2:] |
| if unit.isalpha(): |
| size = size[:-2] |
| else: |
| unit = 'px' |
| try: |
| size = float(size) |
| except ValueError, e: |
| self.document.reporter.warning( |
| 'Invalid %s for image: "%s"' % ( |
| attr, node.attributes[attr])) |
| size = [size, unit] |
| return size |
| |
| def get_image_scale(self, node): |
| if 'scale' in node.attributes: |
| try: |
| scale = int(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 |
| except ValueError, e: |
| self.document.reporter.warning( |
| 'Invalid scale for image: "%s"' % ( |
| node.attributes['scale'], )) |
| else: |
| scale = 1.0 |
| return scale |
| |
| def get_image_scaled_width_height(self, node, source): |
| scale = self.get_image_scale(node) |
| width = self.get_image_width_height(node, 'width') |
| height = self.get_image_width_height(node, 'height') |
| |
| dpi = (72, 72) |
| if Image is not None and source in self.image_dict: |
| filename, destination = self.image_dict[source] |
| imageobj = 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], 'px'] |
| if height is None: height = [imageobj.size[1], 'px'] |
| |
| width[0] *= scale |
| height[0] *= scale |
| if width[1] == 'px': width = [width[0] / dpi[0], 'in'] |
| if height[1] == 'px': height = [height[0] / dpi[1], 'in'] |
| |
| width[0] = str(width[0]) |
| height[0] = str(height[0]) |
| return ''.join(width), ''.join(height) |
| |
| 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 |
| # 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': 'Frame1', |
| 'text:anchor-type': 'paragraph', |
| 'draw:z-index': '0', |
| } |
| attrib['svg:width'] = width |
| # dbg |
| #attrib['svg:height'] = height |
| 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) |
| # Add the content. |
| #el = SubElement(current_element, 'text:p', |
| # attrib={'text:style-name': self.rststyle('textbody')}) |
| attrib={ |
| 'draw:style-name': style_name, |
| 'draw:name': 'graphics2', |
| '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 len(styles) > 0: |
| inline_style = styles[0] |
| el = SubElement(self.current_element, 'text:span', |
| attrib={'text:style-name': self.rststyle(inline_style)}) |
| self.set_current_element(el) |
| |
| def depart_inline(self, node): |
| 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): |
| 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') |
| 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 |
| |
| 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', |
| '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 |
| |
| def depart_table(self, node): |
| attribkey = add_ns('style:width', nsdict=SNSD) |
| attribval = '%dcm' % self.table_width |
| self.current_table_style.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'] |
| 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': '%dcm' % 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 visit_topic(self, node): |
| if 'classes' in node.attributes: |
| if 'contents' in node.attributes['classes']: |
| 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 = 'Table of Contents' |
| 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 = 'Table of Contents' |
| 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 = 'Contents' |
| 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')}) |
| el1.text = 'Abstract' |
| |
| 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): |
| 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!' % (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 ] |