summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rwxr-xr-xacprep2
-rwxr-xr-xcontrib/getquote-uk.py13
-rwxr-xr-xcontrib/ledger-du32
-rwxr-xr-xcontrib/non-profit-audit-reports/csv2ods.py38
-rw-r--r--contrib/non-profit-audit-reports/ooolib2/__init__.py3930
-rwxr-xr-xcontrib/non-profit-audit-reports/readcsv.py8
-rwxr-xr-xpython/demo.py2
-rwxr-xr-xtest/CheckBaselineTests.py3
-rw-r--r--test/CheckComments.py46
-rwxr-xr-xtest/CheckManpage.py3
-rwxr-xr-xtest/CheckOptions.py19
-rwxr-xr-xtest/CheckTexinfo.py3
-rwxr-xr-xtest/ConfirmTests.py10
-rwxr-xr-xtest/DocTests.py3
-rwxr-xr-xtest/GenerateTests.py2
-rwxr-xr-xtest/LedgerHarness.py3
-rwxr-xr-xtest/RegressTests.py3
-rwxr-xr-xtest/convert.py2
-rw-r--r--test/python/JournalTest.py2
-rw-r--r--test/python/PostingTest.py2
-rw-r--r--test/python/TransactionTest.py2
-rw-r--r--test/regress/B21BF389.py3
-rwxr-xr-xtools/average6
-rwxr-xr-xtools/genuuid29
24 files changed, 2101 insertions, 2065 deletions
diff --git a/acprep b/acprep
index c6665e24..ec1576f0 100755
--- a/acprep
+++ b/acprep
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
# acprep, version 3.1
#
diff --git a/contrib/getquote-uk.py b/contrib/getquote-uk.py
index a69d4e7d..0c6c052a 100755
--- a/contrib/getquote-uk.py
+++ b/contrib/getquote-uk.py
@@ -1,7 +1,6 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
+#!/usr/bin/env python3
-import urllib, string, sys
+import urllib, string, sys, os
def download(sym):
url = "http://uk.old.finance.yahoo.com/d/quotes.csv?s="
@@ -13,11 +12,13 @@ def download(sym):
result = float(fields[1])/100
return result
-
+if len(sys.argv) == 1:
+ print(f'USAGE: {os.path.basename(__file__)} SYMBOL', file=sys.stderr)
+ sys.exit(-1)
sym = sys.argv[1]
sym = sym.replace('_', '.')
if sym == '£':
- print '£1.00'
+ print('£1.00')
else:
- try: print "£" +str(download(sym))
+ try: print(f'£ {str(download(sym))}')
except: pass
diff --git a/contrib/ledger-du b/contrib/ledger-du
index 580e916e..fe5a0706 100755
--- a/contrib/ledger-du
+++ b/contrib/ledger-du
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
import string
import sys
@@ -9,28 +9,28 @@ from stat import *
from os.path import *
def report_file(path):
- dir_elems = string.split(dirname(path), os.sep)
+ dir_elems = dirname(path).split(os.sep)
if dir_elems[0] == "." or dir_elems[0] == "":
- dir_elems = dir_elems[1 :]
- account = string.join(dir_elems, ":")
+ dir_elems = dir_elems[1 :]
+ account = ":".join(dir_elems)
info = os.stat(path)
- print time.strftime("%Y/%m/%d", time.localtime(info[ST_MTIME])),
+ print(time.strftime("%Y/%m/%d", time.localtime(info[ST_MTIME])))
- print basename(path)
- print " ", account, " ", info[ST_SIZE], "b"
- print " Equity:Files"
- print
+ print(f'''{basename(path)}
+ \t{account} {info[ST_SIZE]}b
+ \tEquity:Files
+ ''')
def find_files(path):
xacts = os.listdir(path)
for xact in xacts:
xact = join(path, xact)
- if not islink(xact):
- if isdir(xact) and xact != "/proc":
- find_files(xact)
- else:
- report_file(xact)
+ if not islink(xact):
+ if isdir(xact) and xact != "/proc":
+ find_files(xact)
+ else:
+ report_file(xact)
args = sys.argv[1:]
if len(args):
@@ -38,12 +38,12 @@ if len(args):
else:
paths = ["."]
-print """
+print("""
C 1.00 Kb = 1024 b
C 1.00 Mb = 1024 Kb
C 1.00 Gb = 1024 Mb
C 1.00 Tb = 1024 Gb
-"""
+""")
for path in paths:
find_files(path)
diff --git a/contrib/non-profit-audit-reports/csv2ods.py b/contrib/non-profit-audit-reports/csv2ods.py
index 6aabcb59..d2eda740 100755
--- a/contrib/non-profit-audit-reports/csv2ods.py
+++ b/contrib/non-profit-audit-reports/csv2ods.py
@@ -1,4 +1,4 @@
-#!/usr/bin/python
+#!/usr/bin/env python3
# csv2ods.py
# Convert example csv file to ods
#
@@ -25,10 +25,15 @@ import csv
import ooolib2
import shutil
import string
-from Crypto.Hash import SHA256
+try:
+ from Crypto.Hash import SHA256
+except ModuleNotFoundError:
+ print("Missing pycrypto")
+ #sys.exit(-1)
+
def err(msg):
- print 'error: %s' % msg
+ print(f'error: {msg}')
sys.exit(1)
def ReadChecksums(inputFile):
@@ -57,7 +62,7 @@ def ChecksumFile(filename):
def main():
program = os.path.basename(sys.argv[0])
- print get_file_checksum(sys.argv[1])
+ print(get_file_checksum(sys.argv[1]))
def csv2ods(csvname, odsname, encoding='', singleFileDirectory=None, knownChecksums={}, verbose = False):
filesSavedinManifest = {}
@@ -66,7 +71,7 @@ def csv2ods(csvname, odsname, encoding='', singleFileDirectory=None, knownChecks
checksumCache = {}
if verbose:
- print 'converting from %s to %s' % (csvname, odsname)
+ print(f'converting from {csvname} to {odsname}')
if singleFileDirectory:
if not os.path.isdir(os.path.join(os.getcwd(),singleFileDirectory)):
@@ -88,14 +93,14 @@ def csv2ods(csvname, odsname, encoding='', singleFileDirectory=None, knownChecks
csvdir = os.path.dirname(csvname)
if len(csvdir) == 0:
csvdir = '.'
- csvfile = open(csvname, 'rb')
+ csvfile = open(csvname, 'r')
reader = csv.reader(csvfile, delimiter=',', quotechar='"')
for fields in reader:
if len(fields) > 0:
for col in range(len(fields)):
val = fields[col]
if encoding != '' and val[0:5] != "link:": # Only utf8 encode if it's not a filename
- val = unicode(val, 'utf8')
+ val = val.encode('utf-8')
if len(val) > 0 and val[0] == '$':
doc.set_cell_value(col + 1, row, 'currency', val[1:])
else:
@@ -116,7 +121,7 @@ def csv2ods(csvname, odsname, encoding='', singleFileDirectory=None, knownChecks
if knownChecksums.has_key(checksum):
newFile = knownChecksums[checksum]
- print "FOUND new file in known: " + newFile
+ print(f'FOUND new file in known: {newFile}')
if not newFile:
relativeFileWithPath = os.path.basename(val)
@@ -161,10 +166,10 @@ def csv2ods(csvname, odsname, encoding='', singleFileDirectory=None, knownChecks
filesSavedinManifest[newFile] = val
if not os.path.exists(linkpath):
- print "WARNING: link %s DOES NOT EXIST at %s" % (val, linkpath)
+ print(f'WARNING: link {val} DOES NOT EXIST at {linkpath}')
if verbose:
if os.path.exists(linkpath):
- print 'relative link %s EXISTS at %s' % (val, linkpath)
+ print('relative link {val} EXISTS at {linkpath}')
else:
if val == "pagebreak":
doc.sheets[doc.sheet_index].set_sheet_config(('row', row), style_pagebreak)
@@ -210,16 +215,19 @@ def main():
if len(args) != 0:
parser.error("not expecting extra args")
+ if not options.csv:
+ parser.error('Missing required --csv option')
if not os.path.exists(options.csv):
- err('csv does not exist: %s' % options.csv)
+ err(f'csv does not exist: {options.csv}')
if not options.ods:
(root, ext) = os.path.splitext(options.csv)
options.ods = root + '.ods'
if options.verbose:
- print '%s: verbose mode on' % program
- print 'csv:', options.csv
- print 'ods:', options.ods
- print 'ods:', options.encoding
+ print(f'''{program}: verbose mode on
+ csv: {options.csv}
+ ods: {options.ods}
+ ods: {options.encoding}
+ ''')
if options.known_checksum_list and not options.single_file_directory:
err(program + ": --known-checksum-list option is completely useless without --single-file-directory")
knownChecksums = {}
diff --git a/contrib/non-profit-audit-reports/ooolib2/__init__.py b/contrib/non-profit-audit-reports/ooolib2/__init__.py
index 3bc940bd..0b149e54 100644
--- a/contrib/non-profit-audit-reports/ooolib2/__init__.py
+++ b/contrib/non-profit-audit-reports/ooolib2/__init__.py
@@ -22,1966 +22,1992 @@
# Import Standard Modules
import zipfile # Needed for reading/writing documents
import time
-import sys
-import glob
-import os
import re
import xml.parsers.expat # Needed for parsing documents
+
def version_number():
- "Get the ooolib-python version number"
- return "0.0.17"
+ "Get the ooolib-python version number"
+ return "0.1.0"
+
def version():
- "Get the ooolib-python version"
- return "ooolib-python-%s" % version_number()
+ "Get the ooolib-python version"
+ return "ooolib-python-%s" % version_number()
+
def clean_string(data):
- "Returns an XML friendly copy of the data string"
-
- data = unicode(data) # This line thanks to Chris Ender
-
- data = data.replace('&', '&')
- data = data.replace("'", ''')
- data = data.replace('"', '"')
- data = data.replace('<', '&lt;')
- data = data.replace('>', '&gt;')
- data = data.replace('\t', '<text:tab-stop/>')
- data = data.replace('\n', '<text:line-break/>')
- return data
-
-class XML:
- "XML Class - Used to convert nested lists into XML"
- def __init__(self):
- "Initialize ooolib XML instance"
- pass
-
- def _xmldata(self, data):
- datatype = data.pop(0)
- datavalue = data.pop(0)
- outstring = '%s' % datavalue
- return outstring
-
- def _xmltag(self, data):
- outstring = ''
- # First two
- datatype = data.pop(0)
- dataname = data.pop(0)
- outstring = '<%s' % dataname
- # Element Section
- element = 1
- while(data):
- # elements
- newdata = data.pop(0)
- if (newdata[0] == 'element' and element):
- newstring = self._xmlelement(newdata)
- outstring = '%s %s' % (outstring, newstring)
- continue
- if (newdata[0] != 'element' and element):
- element = 0
- outstring = '%s>' % outstring
- if (newdata[0] == 'tag' or newdata[0] == 'tagline'):
- outstring = '%s\n' % outstring
- if (newdata[0] == 'tag'):
- newstring = self._xmltag(newdata)
- outstring = '%s%s' % (outstring, newstring)
- continue
- if (newdata[0] == 'tagline'):
- newstring = self._xmltagline(newdata)
- outstring = '%s%s' % (outstring, newstring)
- continue
- if (newdata[0] == 'data'):
- newstring = self._xmldata(newdata)
- outstring = '%s%s' % (outstring, newstring)
- continue
- if (element):
- element = 0
- outstring = '%s>\n' % outstring
- outstring = '%s</%s>\n' % (outstring, dataname)
- return outstring
-
- def _xmltagline(self, data):
- outstring = ''
- # First two
- datatype = data.pop(0)
- dataname = data.pop(0)
- outstring = '<%s' % dataname
- # Element Section
- while(data):
- # elements
- newdata = data.pop(0)
- if (newdata[0] != 'element'): break
- newstring = self._xmlelement(newdata)
- outstring = '%s %s' % (outstring, newstring)
- outstring = '%s/>\n' % outstring
- # Non-Element Section should not exist
- return outstring
-
- def _xmlelement(self, data):
- datatype = data.pop(0)
- dataname = data.pop(0)
- datavalue = data.pop(0)
- outstring = '%s="%s"' % (dataname, datavalue)
- return outstring
-
- def convert(self, data):
- """Convert nested lists into XML
-
- The convert method takes a nested lists and converts them
- into XML to be used in Open Document Format documents.
- There are three types of lists that are recognized at this
- time. They are as follows:
-
- 'tag' - Tag opens a set of data that is eventually closed
- with a similar tag.
- List: ['tag', 'xml']
- XML: <xml></xml>
-
- 'tagline' - Taglines are similar to tags, except they open
- and close themselves.
- List: ['tagline', 'xml']
- XML: <xml/>
-
- 'element' - Elements are pieces of information stored in an
- opening tag or tagline.
- List: ['element', 'color', 'blue']
- XML: color="blue"
-
- 'data' - Data is plain text directly inserted into the XML
- document.
- List: ['data', 'hello']
- XML: hello
-
- Bring them all together for something like this.
-
- Lists:
- ['tag', 'xml', ['element', 'a', 'b'], ['tagline', 'xml2'],
- ['data', 'asdf']]
-
- XML:
- <xml a="b"><xml2/>asdf</xml>
- """
- outlines = []
- outlines.append('<?xml version="1.0" encoding="UTF-8"?>')
- if (type(data) == type([]) and len(data) > 0):
- if data[0] == 'tag': outlines.append(self._xmltag(data))
- return outlines
-
-class Meta:
- "Meta Data Class"
-
- def __init__(self, doctype, debug=False):
- self.doctype = doctype
-
- # Set the debug mode
- self.debug = debug
-
- # The generator should always default to the version number
- self.meta_generator = version()
- self.meta_title = ''
- self.meta_subject = ''
- self.meta_description = ''
- self.meta_keywords = []
- self.meta_creator = 'ooolib-python'
- self.meta_editor = ''
- self.meta_user1_name = 'Info 1'
- self.meta_user2_name = 'Info 2'
- self.meta_user3_name = 'Info 3'
- self.meta_user4_name = 'Info 4'
- self.meta_user1_value = ''
- self.meta_user2_value = ''
- self.meta_user3_value = ''
- self.meta_user4_value = ''
- self.meta_creation_date = self.meta_time()
-
- # Parser data
- self.parser_element_list = []
- self.parser_element = ""
- self.parser_count = 0
-
- def set_meta(self, metaname, value):
- """Set meta data in your document.
-
- Currently implemented metaname options are as follows:
- 'creator' - The document author
- """
- if metaname == 'creator': self.meta_creator = value
- if metaname == 'editor': self.meta_editor = value
- if metaname == 'title': self.meta_title = value
- if metaname == 'subject': self.meta_subject = value
- if metaname == 'description': self.meta_description = value
- if metaname == 'user1name': self.meta_user1_name = value
- if metaname == 'user2name': self.meta_user2_name = value
- if metaname == 'user3name': self.meta_user3_name = value
- if metaname == 'user4name': self.meta_user4_name = value
- if metaname == 'user1value': self.meta_user1_value = value
- if metaname == 'user2value': self.meta_user2_value = value
- if metaname == 'user3value': self.meta_user3_value = value
- if metaname == 'user4value': self.meta_user4_value = value
- if metaname == 'keyword':
- if value not in self.meta_keywords:
- self.meta_keywords.append(value)
-
- def get_meta_value(self, metaname):
- "Get meta data value for a given metaname."
-
- if metaname == 'creator': return self.meta_creator
- if metaname == 'editor': return self.meta_editor
- if metaname == 'title': return self.meta_title
- if metaname == 'subject': return self.meta_subject
- if metaname == 'description': return self.meta_description
- if metaname == 'user1name': return self.meta_user1_name
- if metaname == 'user2name': return self.meta_user2_name
- if metaname == 'user3name': return self.meta_user3_name
- if metaname == 'user4name': return self.meta_user4_name
- if metaname == 'user1value': return self.meta_user1_value
- if metaname == 'user2value': return self.meta_user2_value
- if metaname == 'user3value': return self.meta_user3_value
- if metaname == 'user4value': return self.meta_user4_value
- if metaname == 'keyword': return self.meta_keywords
-
- def meta_time(self):
- "Return time string in meta data format"
- t = time.localtime()
- stamp = "%04d-%02d-%02dT%02d:%02d:%02d" % (t[0], t[1], t[2], t[3], t[4], t[5])
- return stamp
-
- def parse_start_element(self, name, attrs):
- if self.debug: print '* Start element:', name
- self.parser_element_list.append(name)
- self.parser_element = self.parser_element_list[-1]
-
- # Need the meta name from the user-defined tags
- if (self.parser_element == "meta:user-defined"):
- self.parser_count += 1
- # Set user-defined name
- self.set_meta("user%dname" % self.parser_count, attrs['meta:name'])
-
- # Debugging statements
- if self.debug: print " List: ", self.parser_element_list
- if self.debug: print " Attributes: ", attrs
-
-
- def parse_end_element(self, name):
- if self.debug: print '* End element:', name
- if name != self.parser_element:
- print "Tag Mismatch: '%s' != '%s'" % (name, self.parser_element)
- self.parser_element_list.pop()
-
- # Readjust parser_element_list and parser_element
- if (self.parser_element_list):
- self.parser_element = self.parser_element_list[-1]
- else:
- self.parser_element = ""
-
- def parse_char_data(self, data):
- if self.debug: print " Character data: ", repr(data)
-
- # Collect Meta data fields
- if (self.parser_element == "dc:title"):
- self.set_meta("title", data)
- if (self.parser_element == "dc:description"):
- self.set_meta("description", data)
- if (self.parser_element == "dc:subject"):
- self.set_meta("subject", data)
- if (self.parser_element == "meta:initial-creator"):
- self.set_meta("creator", data)
-
- # Try to maintain the same creation date
- if (self.parser_element == "meta:creation-date"):
- self.meta_creation_date = data
-
- # The user defined fields need to be kept track of, parser_count does that
- if (self.parser_element == "meta:user-defined"):
- self.set_meta("user%dvalue" % self.parser_count, data)
-
- def meta_parse(self, data):
- "Parse Meta Data from a meta.xml file"
-
- # Debugging statements
- if self.debug:
- # Sometimes it helps to see the document that was read from
- print data
- print "\n\n\n"
-
- # Create parser
- parser = xml.parsers.expat.ParserCreate()
- # Set up parser callback functions
- parser.StartElementHandler = self.parse_start_element
- parser.EndElementHandler = self.parse_end_element
- parser.CharacterDataHandler = self.parse_char_data
-
- # Actually parse the data
- parser.Parse(data, 1)
-
- def get_meta(self):
- "Generate meta.xml file data"
- self.meta_date = self.meta_time()
- self.data = ['tag', 'office:document-meta',
- ['element', 'xmlns:office', 'urn:oasis:names:tc:opendocument:xmlns:office:1.0'],
- ['element', 'xmlns:xlink', 'http://www.w3.org/1999/xlink'],
- ['element', 'xmlns:dc', 'http://purl.org/dc/elements/1.1/'],
- ['element', 'xmlns:meta', 'urn:oasis:names:tc:opendocument:xmlns:meta:1.0'],
- ['element', 'xmlns:ooo', 'http://openoffice.org/2004/office'],
- ['element', 'office:version', '1.0'],
- ['tag', 'office:meta',
- ['tag', 'meta:generator', # Was: 'OpenOffice.org/2.0$Linux OpenOffice.org_project/680m5$Build-9011'
- ['data', self.meta_generator]], # Generator is set the ooolib-python version.
- ['tag', 'dc:title',
- ['data', self.meta_title]], # This data is the document title
- ['tag', 'dc:description',
- ['data', self.meta_description]], # This data is the document description
- ['tag', 'dc:subject',
- ['data', self.meta_subject]], # This data is the document subject
- ['tag', 'meta:initial-creator',
- ['data', self.meta_creator]], # This data is the document creator
- ['tag', 'meta:creation-date',
- ['data', self.meta_creation_date]], # This is the original creation date of the document
- ['tag', 'dc:creator',
- ['data', self.meta_editor]], # This data is the document editor
- ['tag', 'dc:date',
- ['data', self.meta_date]], # This is the last modified date of the document
- ['tag', 'dc:language',
- ['data', 'en-US']], # We will probably always use en-US for language
- ['tag', 'meta:editing-cycles',
- ['data', '1']], # Edit cycles will probably always be 1 for generated documents
- ['tag', 'meta:editing-duration',
- ['data', 'PT0S']], # Editing duration is modified - creation date
- ['tag', 'meta:user-defined',
- ['element', 'meta:name', self.meta_user1_name],
- ['data', self.meta_user1_value]],
- ['tag', 'meta:user-defined',
- ['element', 'meta:name', self.meta_user2_name],
- ['data', self.meta_user2_value]],
- ['tag', 'meta:user-defined',
- ['element', 'meta:name', self.meta_user3_name],
- ['data', self.meta_user3_value]],
- ['tag', 'meta:user-defined',
- ['element', 'meta:name', self.meta_user4_name],
- ['data', self.meta_user4_value]]]]
-# ['tagline', 'meta:document-statistic',
-# ['element', 'meta:table-count', len(self.sheets)], # len(self.sheets) ?
-# ['element', 'meta:cell-count', '15']]]] # Not sure how to keep track
-
- # Generate content.xml XML data
- xml = XML()
- self.lines = xml.convert(self.data)
- self.filedata = '\n'.join(self.lines)
- # Return generated data
- return self.filedata
-
-
-
-class CalcStyles:
- "Calc Style Management - Used to keep track of created styles."
-
- def __init__(self):
- self.style_config = {}
- # Style Counters
- self.style_table = 1
- self.style_column = 1
- self.style_row = 1
- self.style_cell = 1
- # Style Properties (Defaults) - To be used later
- self.property_column_width_default = '0.8925in' # Default Column Width
- self.property_row_height_default = '0.189in' # Default Row Height
- # Set Defaults
- self.property_column_width = '0.8925in' # Default Column Width
- self.property_row_height = '0.189in' # Default Row Height
- self.property_cell_bold = False # Bold off be default
- self.property_cell_italic = False # Italic off be default
- self.property_cell_underline = False # Underline off be default
- self.property_cell_fg_color = 'default' # Text Color Default
- self.property_cell_bg_color = 'default' # Cell Background Default
- self.property_cell_bg_image = 'none' # Cell Background Default
- self.property_cell_fontsize = '10' # Cell Font Size Default
- self.property_cell_valign = 'default' # Vertical Alignment Default
- self.property_cell_halign = 'default' # Horizantal Alignment Default
-
- def get_next_style(self, style):
- "Returns the next style code for the given style"
- style_code = ""
- if style == 'table':
- style_code = 'ta%d' % self.style_table
- self.style_table+=1
- if style == 'column':
- style_code = 'co%d' % self.style_column
- self.style_column+=1
- if style == 'row':
- style_code = 'ro%d' % self.style_row
- self.style_row+=1
- if style == 'cell':
- style_code = 'ce%d' % self.style_cell
- self.style_cell+=1
- return style_code
-
- def set_property(self, style, name, value):
- "Sets a property which will later be turned into a code"
- if style == 'table':
- pass
- if style == 'column':
- if name == 'style:column-width': self.property_column_width = value
- if style == 'row':
- if name == 'style:row-height': self.property_row_height = value
- if style == 'cell':
- if name == 'bold' and type(value) == type(True): self.property_cell_bold = value
- if name == 'italic' and type(value) == type(True): self.property_cell_italic = value
- if name == 'underline' and type(value) == type(True): self.property_cell_underline = value
- if name == 'fontsize': self.property_cell_fontsize = value
- if name == 'color':
- self.property_cell_fg_color = 'default'
- redata = re.search("^(#[\da-fA-F]{6})$", value)
- if redata: self.property_cell_fg_color = value.lower()
- if name == 'background':
- self.property_cell_bg_color = 'default'
- redata = re.search("^(#[\da-fA-F]{6})$", value)
- if redata: self.property_cell_bg_color = value.lower()
- if name == 'backgroundimage':
- self.property_cell_bg_image = value
- if name == 'valign':
- self.property_cell_valign = value
- if name == 'halign':
- self.property_cell_halign = value
-
- def get_style_code(self, style):
- style_code = ""
- if style == 'table':
- style_code = "ta1"
- if style == 'column':
- style_data = tuple([style,
- ('style:column-width', self.property_column_width)])
- if style_data in self.style_config:
- # Style Exists, return code
- style_code = self.style_config[style_data]
- else:
- # Style does not exist, create code and return it
- style_code = self.get_next_style(style)
- self.style_config[style_data] = style_code
- if style == 'row':
- style_data = tuple([style,
- ('style:row-height', self.property_row_height)])
- if style_data in self.style_config:
- # Style Exists, return code
- style_code = self.style_config[style_data]
- else:
- # Style does not exist, create code and return it
- style_code = self.get_next_style(style)
- self.style_config[style_data] = style_code
- if style == 'cell':
- style_data = [style]
- # Add additional styles
- if self.property_cell_bold: style_data.append(('bold', True))
- if self.property_cell_italic: style_data.append(('italic', True))
- if self.property_cell_underline: style_data.append(('underline', True))
- if self.property_cell_fontsize != '10':
- style_data.append(('fontsize', self.property_cell_fontsize))
- if self.property_cell_fg_color != 'default':
- style_data.append(('color', self.property_cell_fg_color))
- if self.property_cell_bg_color != 'default':
- style_data.append(('background', self.property_cell_bg_color))
- if self.property_cell_bg_image != 'none':
- style_data.append(('backgroundimage', self.property_cell_bg_image))
- if self.property_cell_valign != 'default':
- style_data.append(('valign', self.property_cell_valign))
- if self.property_cell_halign != 'default':
- style_data.append(('halign', self.property_cell_halign))
-
- style_data = tuple(style_data)
- if style_data in self.style_config:
- # Style Exists, return code
- style_code = self.style_config[style_data]
- else:
- # Style does not exist, create code and return it
- style_code = self.get_next_style(style)
- self.style_config[style_data] = style_code
- return style_code
-
- def get_automatic_styles(self):
- "Return 'office:automatic-styles' lists"
- automatic_styles = ['tag', 'office:automatic-styles']
-
- for style_data in self.style_config:
- style_code = self.style_config[style_data]
- style_data = list(style_data)
- style = style_data.pop(0)
-
- if style == 'column':
- style_list = ['tag', 'style:style',
- ['element', 'style:name', style_code], # Column 'co1' properties
- ['element', 'style:family', 'table-column']]
- tagline = ['tagline', 'style:table-column-properties',
- ['element', 'fo:break-before', 'auto']] # unsure what break before means
-
- for set in style_data:
- name, value = set
- if name == 'style:column-width':
- tagline.append(['element', 'style:column-width', value])
- style_list.append(tagline)
- automatic_styles.append(style_list)
-
- if style == 'row':
- style_list = ['tag', 'style:style',
- ['element', 'style:name', style_code], # Column 'ro1' properties
- ['element', 'style:family', 'table-row']]
- tagline = ['tagline', 'style:table-row-properties']
-
- for set in style_data:
- name, value = set
- if name == 'style:row-height':
- tagline.append(['element', 'style:row-height', value])
- tagline.append(['element', 'fo:break-before', 'auto'])
-# tagline.append(['element', 'style:use-optimal-row-height', 'true']) # Overrides settings
- style_list.append(tagline)
- automatic_styles.append(style_list)
-
- if style == 'pagebreak':
- style_list = ['tag', 'style:style',
- ['element', 'style:name', style_code], # Column 'ro1' properties
- ['element', 'style:family', 'table-row']]
- tagline = ['tagline', 'style:table-row-properties']
-
- for set in style_data:
- name, value = set
- if name == 'style:row-height':
- tagline.append(['element', 'style:row-height', value])
- tagline.append(['element', 'fo:break-before', 'page'])
-# tagline.append(['element', 'style:use-optimal-row-height', 'true']) # Overrides settings
- style_list.append(tagline)
- automatic_styles.append(style_list)
-
- if style == 'cell':
- style_list = ['tag', 'style:style',
- ['element', 'style:name', style_code], # ce1 style
- ['element', 'style:family', 'table-cell'], # cell
- ['element', 'style:parent-style-name', 'Default']] # parent is Default
- # hack for currency
- if style_code == 'ce1':
- style_list.append(['element',
- 'style:data-style-name',
- 'N104'])
-
- # Cell Properties
- tagline = ['tag', 'style:table-cell-properties']
- tagline_additional = []
- for set in style_data:
- name, value = set
- if name == 'background':
- tagline.append(['element', 'fo:background-color', value])
- if name == 'backgroundimage':
- tagline.append(['element', 'fo:background-color', 'transparent'])
- # Additional tags added later
- bgimagetag = ['tagline', 'style:background-image']
- bgimagetag.append(['element', 'xlink:href', value])
- bgimagetag.append(['element', 'xlink:type', 'simple'])
- bgimagetag.append(['element', 'xlink:actuate', 'onLoad'])
- tagline_additional.append(bgimagetag)
- if name == 'valign':
- if value in ['top', 'bottom', 'middle']:
- tagline.append(['element', 'style:vertical-align', value])
- if name == 'halign':
- tagline.append(['element', 'style:text-align-source', 'fix'])
- if value in ['filled']:
- tagline.append(['element', 'style:repeat-content', 'true'])
- else:
- tagline.append(['element', 'style:repeat-content', 'false'])
-
- # Add any additional internal tags
- while tagline_additional:
- tagadd = tagline_additional.pop(0)
- tagline.append(tagadd)
-
- style_list.append(tagline)
-
- # Paragraph Properties
- tagline = ['tagline', 'style:paragraph-properties']
- tagline_valid = False
- for set in style_data:
- name, value = set
- if name == 'halign':
- tagline_valid = True
- if value in ['center']:
- tagline.append(['element', 'fo:text-align', 'center'])
- if value in ['end', 'right']:
- tagline.append(['element', 'fo:text-align', 'end'])
- if value in ['start', 'filled', 'left']:
- tagline.append(['element', 'fo:text-align', 'start'])
- if value in ['justify']:
- tagline.append(['element', 'fo:text-align', 'justify'])
- # Conditionally add the tagline
- if tagline_valid: style_list.append(tagline)
-
-
- # Text Properties
- tagline = ['tagline', 'style:text-properties']
- for set in style_data:
- name, value = set
- if name == 'bold':
- tagline.append(['element', 'fo:font-weight', 'bold'])
- if name == 'italic':
- tagline.append(['element', 'fo:font-style', 'italic'])
- if name == 'underline':
- tagline.append(['element', 'style:text-underline-style', 'solid'])
- tagline.append(['element', 'style:text-underline-width', 'auto'])
- tagline.append(['element', 'style:text-underline-color', 'font-color'])
- if name == 'color':
- tagline.append(['element', 'fo:color', value])
- if name == 'fontsize':
- tagline.append(['element', 'fo:font-size', '%spt' % value])
- style_list.append(tagline)
-
- automatic_styles.append(style_list)
-
-
- # Attach ta1 style
- automatic_styles.append(['tag', 'style:style',
- ['element', 'style:name', 'ta1'],
- ['element', 'style:family', 'table'],
- ['element', 'style:master-page-name', 'Default'],
- ['tagline', 'style:table-properties',
- ['element', 'table:display', 'true'],
- ['element', 'style:writing-mode', 'lr-tb']]])
-
-
- return automatic_styles
-
-
-
-class CalcSheet:
- "Calc Sheet Class - Used to keep track of the data for an individual sheet."
-
- def __init__(self, sheetname):
- "Initialize a sheet"
- self.sheet_name = sheetname
- self.sheet_values = {}
- self.sheet_config = {}
- self.max_col = 0
- self.max_row = 0
-
- def get_sheet_dimensions(self):
- "Returns the max column and row"
- return (self.max_col, self.max_row)
-
- def clean_formula(self, data):
- "Returns a formula for use in ODF"
- # Example Translations
- # '=SUM(A1:A2)'
- # datavalue = 'oooc:=SUM([.A1:.A2])'
- # '=IF((A5>A4);A4;"")'
- # datavalue = 'oooc:=IF(([.A5]&gt;[.A4]);[.A4];&quot;&quot;)'
- data = str(data)
- data = clean_string(data)
- redata = re.search('^=([A-Z]+)(\(.*)$', data)
- if redata:
- # funct is the function name. The rest if the string will be the functArgs
- funct = redata.group(1)
- functArgs = redata.group(2)
- # Search for cell lebels and replace them
- reList = re.findall('([A-Z]+\d+)', functArgs)
- # sort and keep track so we do not do a cell more than once
- reList.sort()
- lastVar = ''
- while reList:
- # Replace each cell label
- curVar = reList.pop()
- if curVar == lastVar: continue
- lastVar = curVar
- functArgs = functArgs.replace(curVar, '[.%s]' % curVar)
- data = 'oooc:=%s%s' % (funct, functArgs)
- return data
-
- def get_name(self):
- "Returns the sheet name"
- return self.sheet_name
-
- def set_name(self, sheetname):
- "Resets the sheet name"
- self.sheet_name = sheetname
-
- def get_sheet_values(self):
- "Returns the sheet cell values"
- return self.sheet_values
-
- def get_sheet_value(self, col, row):
- "Get the value contents of a cell"
- cell = (col, row)
- if cell in self.sheet_values:
- return self.sheet_values[cell]
- else:
- return None
-
- def get_sheet_config(self):
- "Returns the sheet cell properties"
- return self.sheet_config
-
- def set_sheet_config(self, location, style_code):
- "Sets Style Code for a given location"
- self.sheet_config[location] = style_code
-
- def set_sheet_value(self, cell, datatype, datavalue):
- """Sets the value for a specific cell
-
- cell must be in the format (col, row) where row and col are int.
- Example: B5 would be written as (2, 5)
- datatype must be one of 'string', 'float', 'formula', 'currency'
- datavalue should be a string
- """
- # Catch invalid data
- if type(cell) != type(()) or len(cell) != 2:
- print "Invalid Cell"
- return
- (col, row) = cell
- if type(col) != type(1):
- print "Invalid Cell"
- return
- if type(row) != type(1):
- print "Invalid Cell"
- return
- # Fix String Data
- if datatype in ['string', 'annotation']:
- datavalue = clean_string(datavalue)
- # Fix Link Data. Link's value is a tuple containing (url, description)
- if (datatype == 'link'):
- url = clean_string(datavalue[0])
- desc = clean_string(datavalue[1])
- datavalue = (url, desc)
- # Fix Formula Data
- if datatype == 'formula':
- datavalue = self.clean_formula(datavalue)
- # Adjust maximum sizes
- if col > self.max_col: self.max_col = col
- if row > self.max_row: self.max_row = row
- datatype = str(datatype)
- if (datatype not in ['string', 'float', 'currency', 'formula', 'annotation', 'link']):
- # Set all unknown cell types to string
- datatype = 'string'
- datavalue = str(datavalue)
-
- # The following lines are taken directly from HPS
- # self.sheet_values[cell] = (datatype, datavalue)
- # HPS: Cell content is now a list of tuples instead of a tuple
- # While storing here, store the cell contents first and the annotation next. While generating the XML reverse this
- contents = self.sheet_values.get(cell, {'annotation':None,'link':None, 'value':None})
- if datatype == 'annotation':
- contents['annotation'] = (datatype, datavalue)
- elif datatype == 'link':
- contents['link'] = (datatype, datavalue)
- else:
- contents['value'] = (datatype, datavalue)
-
- self.sheet_values[cell] = contents
-
-
- def get_lists(self):
- "Returns nested lists for XML processing"
- if (self.max_col == 0 and self.max_row == 0):
- sheet_lists = ['tag', 'table:table',
- ['element', 'table:name', self.sheet_name], # Set the Sheet Name
- ['element', 'table:style-name', 'ta1'],
- ['element', 'table:print', 'false'],
- ['tagline', 'table:table-column',
- ['element', 'table:style-name', 'co1'],
- ['element', 'table:default-cell-style-name', 'Default']],
- ['tag', 'table:table-row',
- ['element', 'table:style-name', 'ro1'],
- ['tagline', 'table:table-cell']]]
- else:
- # Base Information
- sheet_lists = ['tag', 'table:table',
- ['element', 'table:name', self.sheet_name], # Set the sheet name
- ['element', 'table:style-name', 'ta1'],
- ['element', 'table:print', 'false']]
-
-# ['tagline', 'table:table-column',
-# ['element', 'table:style-name', 'co1'],
-# ['element', 'table:number-columns-repeated', self.max_col], # max_col? '2'
-# ['element', 'table:default-cell-style-name', 'Default']],
-
- # Need to add column information
- for col in range(1, self.max_col+1):
- location = ('col', col)
- style_code = 'co1'
- if location in self.sheet_config:
- style_code = self.sheet_config[location]
- sheet_lists.append(['tagline', 'table:table-column',
- ['element', 'table:style-name', style_code],
- ['element', 'table:default-cell-style-name', 'Default']])
-
-
- # Need to create each row
- for row in range(1, self.max_row + 1):
- location = ('row', row)
- style_code = 'ro1'
- if location in self.sheet_config:
- style_code = self.sheet_config[location]
- rowlist = ['tag', 'table:table-row',
- ['element', 'table:style-name', style_code]]
- for col in range(1, self.max_col + 1):
- cell = (col, row)
- style_code = 'ce1' # Default all cells to ce1
- if cell in self.sheet_config:
- style_code = self.sheet_config[cell] # Lookup cell if available
- if cell in self.sheet_values:
- # (datatype, datavalue) = self.sheet_values[cell] # Marked for removal
- collist = ['tag', 'table:table-cell']
- if style_code != 'ce1':
- collist.append(['element', 'table:style-name', style_code])
-
- # Contents, annotations, and links added by HPS
- contents = self.sheet_values[cell] # cell contents is a dictionary
- if contents['value']:
- (datatype, datavalue) = contents['value']
- if datatype == 'float':
- collist.append(['element', 'office:value-type', datatype])
- collist.append(['element', 'office:value', datavalue])
- if datatype == 'currency':
- collist.append(['element', 'table:style-name', "ce1"])
- collist.append(['element', 'office:value-type', datatype])
- collist.append(['element', 'office:currency', 'USD'])
- collist.append(['element', 'office:value', datavalue])
-
- if datatype == 'string':
- collist.append(['element', 'office:value-type', datatype])
- if datatype == 'formula':
- collist.append(['element', 'table:formula', datavalue])
- collist.append(['element', 'office:value-type', 'float'])
- collist.append(['element', 'office:value', '0'])
- datavalue = '0'
- else:
- datavalue = None
-
- if contents['annotation']:
- (annotype, annoval) = contents['annotation']
- collist.append(['tag', 'office:annotation',
- ['tag', 'text:p', ['data', annoval]]])
-
- if contents['link']:
- (linktype, linkval) = contents['link']
- if datavalue:
- collist.append(['tag', 'text:p', ['data', datavalue],
- ['tag', 'text:a', ['element', 'xlink:href', linkval[0]],
- ['data', linkval[1]]]])
- else: # no value; just fill the link
- collist.append(['tag', 'text:p',
- ['tag', 'text:a', ['element', 'xlink:href', linkval[0]],
- ['data', linkval[1]]]])
- else:
- if datavalue:
- collist.append(['tag', 'text:p', ['data', datavalue]])
-
-
-
- else:
- collist = ['tagline', 'table:table-cell']
- rowlist.append(collist)
- sheet_lists.append(rowlist)
- return sheet_lists
-
-class Calc:
- "Calc Class - Used to create OpenDocument Format Calc Spreadsheets."
- def __init__(self, sheetname=None, opendoc=None, debug=False):
- "Initialize ooolib Calc instance"
- # Default to no debugging
- self.debug = debug
- if not sheetname: sheetname = "Sheet1"
- self.sheets = [CalcSheet(sheetname)] # The main sheet will be initially called 'Sheet1'
- self.sheet_index = 0 # We initially start on the first sheet
- self.styles = CalcStyles()
- self.meta = Meta('ods')
- self.styles.get_style_code('column') # Force generation of default column
- self.styles.get_style_code('row') # Force generation of default row
- self.styles.get_style_code('table') # Force generation of default table
- self.styles.get_style_code('cell') # Force generation of default cell
- self.manifest_files = [] # List of extra files included
- self.manifest_index = 1 # Index of added manifest files
-
- # Data Parsing
- self.parser_element_list = []
- self.parser_element = ""
- self.parser_sheet_num = 0
- self.parser_sheet_row = 0
- self.parser_sheet_column = 0
- self.parser_cell_repeats = 0
- self.parser_cell_string_pending = False
- self.parser_cell_string_line = ""
-
- # See if we need to read a document
- if opendoc:
- # Verify that the document exists
- if self.debug: print "Opening Document: %s" % opendoc
-
- # Okay, now we load the file
- self.load(opendoc)
-
- def debug_level(self, level):
- """Set debug level:
- True if you want debugging messages
- False if you do not.
- """
- self.debug = level
-
- def file_mimetype(self, filename):
- "Determine the filetype from the filename"
- parts = filename.lower().split('.')
- ext = parts[-1]
- if (ext == 'png'): return (ext, "image/png")
- if (ext == 'gif'): return (ext, "image/gif")
- return (ext, "image/unknown")
-
- def add_file(self, filename):
- """Prepare a file for loading into ooolib
-
- The filename should be the local filesystem name for
- the file. The file is then prepared to be included in
- the creation of the final document. The file needs to
- remain in place so that it is available when the actual
- document creation happens.
- """
- # mimetype set to (ext, filetype)
- mimetype = self.file_mimetype(filename)
- newname = "Pictures/%08d.%s" % (self.manifest_index, mimetype[0])
- self.manifest_index += 1
- filetype = mimetype[1]
- self.manifest_files.append((filename, filetype, newname))
- return newname
-
- def set_meta(self, metaname, value):
- "Set meta data in your document."
- self.meta.set_meta(metaname, value)
-
- def get_meta_value(self, metaname):
- "Get meta data value for a given metaname"
- return self.meta.get_meta_value(metaname)
-
- def get_sheet_name(self):
- "Returns the sheet name"
- return self.sheets[self.sheet_index].get_name()
-
- def get_sheet_dimensions(self):
- "Returns the sheet dimensions in (cols, rows)"
- return self.sheets[self.sheet_index].get_sheet_dimensions()
-
- def set_column_property(self, column, name, value):
- "Set Column Properties"
- if name == 'width':
- # column number column needs column-width set to value
- self.styles.set_property('column', 'style:column-width', value)
- style_code = self.styles.get_style_code('column')
- self.sheets[self.sheet_index].set_sheet_config(('col', column), style_code)
-
- def set_row_property(self, row, name, value):
- "Set row Properties"
- if name == 'height':
- # row number row needs row-height set to value
- self.styles.set_property('row', 'style:row-height', value)
- style_code = self.styles.get_style_code('row')
- self.sheets[self.sheet_index].set_sheet_config(('row', row), style_code)
-
- def set_cell_property(self, name, value):
- """Turn and off cell properties
-
- Actual application of properties is handled by setting a value."""
- # background images need to be handled a little differently
- # because they need to also be inserted into the final document
- if (name == 'backgroundimage'):
- # Add file and modify value
- value = self.add_file(value)
- self.styles.set_property('cell', name, value)
-
- def get_sheet_index(self):
- "Return the current sheet index number"
- return self.sheet_index
-
- def set_sheet_index(self, index):
- "Set the sheet index"
- if type(index) == type(1):
- if index >= 0 and index < len(self.sheets):
- self.sheet_index = index
- return self.sheet_index
-
- def get_sheet_count(self):
- "Returns the number of existing sheets"
- return len(self.sheets)
-
- def new_sheet(self, sheetname):
- "Create a new sheet"
- self.sheet_index = len(self.sheets)
- self.sheets.append(CalcSheet(sheetname))
- return self.sheet_index
-
- def set_cell_value(self, col, row, datatype, value):
- "Set the value for a given cell"
- self.sheets[self.sheet_index].set_sheet_value((col, row), datatype, value)
- style_code = self.styles.get_style_code('cell')
- self.sheets[self.sheet_index].set_sheet_config((col, row), style_code)
-
- def get_cell_value(self, col, row):
- "Get a cell value tuple (type, value) for a given cell"
- sheetvalue = self.sheets[self.sheet_index].get_sheet_value(col, row)
- # We stop here if there is no value for sheetvalue
- if sheetvalue == None: return sheetvalue
- # Now check to see if we have a value tuple
- if 'value' in sheetvalue:
- return sheetvalue['value']
- else:
- return None
-
- def load(self, filename):
- """Load .ods spreadsheet.
-
- The load function loads data from a document into the current cells.
- """
- # Read in the important files
-
- # meta.xml
- data = self._zip_read(filename, "meta.xml")
- self.meta.meta_parse(data)
-
- # content.xml
- data = self._zip_read(filename, "content.xml")
- self.content_parse(data)
-
- # settings.xml - I do not remember putting anything here
- # styles.xml - I do not remember putting anything here
-
- def parse_content_start_element(self, name, attrs):
- if self.debug: print '* Start element:', name
- self.parser_element_list.append(name)
- self.parser_element = self.parser_element_list[-1]
-
- # Keep track of the current sheet number
- if (self.parser_element == 'table:table'):
- # Move to starting cell
- self.parser_sheet_row = 0
- self.parser_sheet_column = 0
- # Increment the sheet number count
- self.parser_sheet_num += 1
- if (self.parser_sheet_num - 1 != self.sheet_index):
- # We are not on the first sheet and need to create a new sheet.
- # We will automatically move to the new sheet
- sheetname = "Sheet%d" % self.parser_sheet_num
- if 'table:name' in attrs: sheetname = attrs['table:name']
- self.new_sheet(sheetname)
- else:
- # We are on the first sheet and will need to overwrite the default name
- sheetname = "Sheet%d" % self.parser_sheet_num
- if 'table:name' in attrs: sheetname = attrs['table:name']
- self.sheets[self.sheet_index].set_name(sheetname)
-
- # Update the row numbers
- if (self.parser_element == 'table:table-row'):
- self.parser_sheet_row += 1
- self.parser_sheet_column = 0
-
- # Okay, now keep track of the sheet cell data
- if (self.parser_element == 'table:table-cell'):
- # By default it will repeat zero times
- self.parser_cell_repeats = 0
- # We must be in a new column
- self.parser_sheet_column += 1
- # Set some default values
- datatype = ""
- value = ""
- # Get values from attrs hash
- if 'office:value-type' in attrs: datatype = attrs['office:value-type']
- if 'office:value' in attrs: value = attrs['office:value']
- if 'table:formula' in attrs:
- datatype = 'formula'
- value = attrs['table:formula']
- if datatype == 'string':
- datatype = ""
- self.parser_cell_string_pending = True
- self.parser_cell_string_line = ""
- if 'table:number-columns-repeated' in attrs:
- self.parser_cell_repeats = int(attrs['table:number-columns-repeated']) - 1
- # Set the cell value
- if datatype:
- # I should do this once per cell repeat above 0
- for i in range(0, self.parser_cell_repeats+1):
- self.set_cell_value(self.parser_sheet_column+i, self.parser_sheet_row, datatype, value)
-
- # There are lots of interesting cases with table:table-cell data. One problem is
- # reading the number of embedded spaces correctly. This code should help us get
- # the number of spaces out.
-
- if (self.parser_element == 'text:s'):
- # This means we have a number of spaces
- count_num = 0
- if 'text:c' in attrs:
- count_alpha = attrs['text:c']
- if (count_alpha.isdigit()):
- count_num = int(count_alpha)
- # I am not sure what to do if we do not have a string pending
- if (self.parser_cell_string_pending == True):
- # Append the currect number of spaces to the end
- self.parser_cell_string_line = "%s%s" % (self.parser_cell_string_line, ' '*count_num)
-
- if (self.parser_element == 'text:tab-stop'):
- if (self.parser_cell_string_pending == True):
- self.parser_cell_string_line = "%s\t" % (self.parser_cell_string_line)
-
- if (self.parser_element == 'text:line-break'):
- if (self.parser_cell_string_pending == True):
- self.parser_cell_string_line = "%s\n" % (self.parser_cell_string_line)
-
- # Debugging statements
- if self.debug: print " List: ", self.parser_element_list
- if self.debug: print " Attributes: ", attrs
-
-
- def parse_content_end_element(self, name):
- if self.debug: print '* End element:', name
- if name != self.parser_element:
- print "Tag Mismatch: '%s' != '%s'" % (name, self.parser_element)
- self.parser_element_list.pop()
-
- # If the element was text:p and we are in string mode
- if (self.parser_element == 'text:p'):
- if (self.parser_cell_string_pending):
- self.parser_cell_string_pending = False
-
- # Take care of repeated cells
- if (self.parser_element == 'table:table-cell'):
- self.parser_sheet_column += self.parser_cell_repeats
-
- # Readjust parser_element_list and parser_element
- if (self.parser_element_list):
- self.parser_element = self.parser_element_list[-1]
- else:
- self.parser_element = ""
-
- def parse_content_char_data(self, data):
- if self.debug: print " Character data: ", repr(data)
-
- if (self.parser_element == 'text:p' or self.parser_element == 'text:span'):
- if (self.parser_cell_string_pending):
- # Set the string and leave string pending mode
- # This does feel a little kludgy, but it does the job
- self.parser_cell_string_line = "%s%s" % (self.parser_cell_string_line, data)
-
- # I should do this once per cell repeat above 0
- for i in range(0, self.parser_cell_repeats+1):
- self.set_cell_value(self.parser_sheet_column+i, self.parser_sheet_row,
- 'string', self.parser_cell_string_line)
-
-
- def content_parse(self, data):
- "Parse Content Data from a content.xml file"
-
- # Debugging statements
- if self.debug:
- # Sometimes it helps to see the document that was read from
- print data
- print "\n\n\n"
-
- # Create parser
- parser = xml.parsers.expat.ParserCreate()
- # Set up parser callback functions
- parser.StartElementHandler = self.parse_content_start_element
- parser.EndElementHandler = self.parse_content_end_element
- parser.CharacterDataHandler = self.parse_content_char_data
-
- # Actually parse the data
- parser.Parse(data, 1)
-
- def save(self, filename):
- """Save .ods spreadsheet.
-
- The save function saves the current cells and settings into a document.
- """
- if self.debug: print "Writing %s" % filename
- self.savefile = zipfile.ZipFile(filename, "w")
- if self.debug: print " meta.xml"
- self._zip_insert(self.savefile, "meta.xml", self.meta.get_meta())
- if self.debug: print " mimetype"
- self._zip_insert(self.savefile, "mimetype", "application/vnd.oasis.opendocument.spreadsheet")
- if self.debug: print " Configurations2/accelerator/current.xml"
- self._zip_insert(self.savefile, "Configurations2/accelerator/current.xml", "")
- if self.debug: print " META-INF/manifest.xml"
- self._zip_insert(self.savefile, "META-INF/manifest.xml", self._ods_manifest())
- if self.debug: print " content.xml"
- self._zip_insert(self.savefile, "content.xml", self._ods_content())
- if self.debug: print " settings.xml"
- self._zip_insert(self.savefile, "settings.xml", self._ods_settings())
- if self.debug: print " styles.xml"
- self._zip_insert(self.savefile, "styles.xml", self._ods_styles())
-
- # Add additional files if needed
- for fileset in self.manifest_files:
- (filename, filetype, newname) = fileset
- # Read in the file
- data = self._file_load(filename)
- if self.debug: print " Inserting '%s' as '%s'" % (filename, newname)
- self._zip_insert_binary(self.savefile, newname, data)
-
- def _file_load(self, filename):
- "Load a file"
- file = open(filename, "rb")
- data = file.read()
- file.close()
- return data
-
- def _zip_insert_binary(self, file, filename, data):
- "Insert a binary file into the zip archive"
- now = time.localtime(time.time())[:6]
- info = zipfile.ZipInfo(filename)
- info.date_time = now
- info.compress_type = zipfile.ZIP_DEFLATED
- file.writestr(info, data)
-
-
- def _zip_insert(self, file, filename, data):
- "Insert a file into the zip archive"
-
- # zip seems to struggle with non-ascii characters
- data = data.encode('utf-8')
-
- now = time.localtime(time.time())[:6]
- info = zipfile.ZipInfo(filename)
- info.date_time = now
- info.compress_type = zipfile.ZIP_DEFLATED
- file.writestr(info, data)
-
- def _zip_read(self, file, filename):
- "Get the data from a file in the zip archive by filename"
- file = zipfile.ZipFile(file, "r")
- data = file.read(filename)
- # Need to close the file
- file.close()
- return data
-
- def _ods_content(self):
- "Generate ods content.xml data"
-
- # This will list all of the sheets in the document
- self.sheetdata = ['tag', 'office:spreadsheet']
- for sheet in self.sheets:
- if self.debug:
- sheet_name = sheet.get_name()
- print " Creating Sheet '%s'" % sheet_name
- sheet_list = sheet.get_lists()
- self.sheetdata.append(sheet_list)
- # Automatic Styles
- self.automatic_styles = self.styles.get_automatic_styles()
-
- self.data = ['tag', 'office:document-content',
- ['element', 'xmlns:office', 'urn:oasis:names:tc:opendocument:xmlns:office:1.0'],
- ['element', 'xmlns:style', 'urn:oasis:names:tc:opendocument:xmlns:style:1.0'],
- ['element', 'xmlns:text', 'urn:oasis:names:tc:opendocument:xmlns:text:1.0'],
- ['element', 'xmlns:table', 'urn:oasis:names:tc:opendocument:xmlns:table:1.0'],
- ['element', 'xmlns:draw', 'urn:oasis:names:tc:opendocument:xmlns:drawing:1.0'],
- ['element', 'xmlns:fo', 'urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0'],
- ['element', 'xmlns:xlink', 'http://www.w3.org/1999/xlink'],
- ['element', 'xmlns:dc', 'http://purl.org/dc/elements/1.1/'],
- ['element', 'xmlns:meta', 'urn:oasis:names:tc:opendocument:xmlns:meta:1.0'],
- ['element', 'xmlns:number', 'urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0'],
- ['element', 'xmlns:svg', 'urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0'],
- ['element', 'xmlns:chart', 'urn:oasis:names:tc:opendocument:xmlns:chart:1.0'],
- ['element', 'xmlns:dr3d', 'urn:oasis:names:tc:opendocument:xmlns:dr3d:1.0'],
- ['element', 'xmlns:math', 'http://www.w3.org/1998/Math/MathML'],
- ['element', 'xmlns:form', 'urn:oasis:names:tc:opendocument:xmlns:form:1.0'],
- ['element', 'xmlns:script', 'urn:oasis:names:tc:opendocument:xmlns:script:1.0'],
- ['element', 'xmlns:ooo', 'http://openoffice.org/2004/office'],
- ['element', 'xmlns:ooow', 'http://openoffice.org/2004/writer'],
- ['element', 'xmlns:oooc', 'http://openoffice.org/2004/calc'],
- ['element', 'xmlns:dom', 'http://www.w3.org/2001/xml-events'],
- ['element', 'xmlns:xforms', 'http://www.w3.org/2002/xforms'],
- ['element', 'xmlns:xsd', 'http://www.w3.org/2001/XMLSchema'],
- ['element', 'xmlns:xsi', 'http://www.w3.org/2001/XMLSchema-instance'],
- ['element', 'office:version', '1.0'],
- ['tagline', 'office:scripts'],
- ['tag', 'office:font-face-decls',
- ['tagline', 'style:font-face',
- ['element', 'style:name', 'DejaVu Sans'],
- ['element', 'svg:font-family', '&apos;DejaVu Sans&apos;'],
- ['element', 'style:font-pitch', 'variable']],
- ['tagline', 'style:font-face',
- ['element', 'style:name', 'Nimbus Sans L'],
- ['element', 'svg:font-family', '&apos;Nimbus Sans L&apos;'],
- ['element', 'style:font-family-generic', 'swiss'],
- ['element', 'style:font-pitch', 'variable']]],
-
- # Automatic Styles
- self.automatic_styles,
-
- ['tag', 'office:body',
- self.sheetdata]] # Sheets are generated from the CalcSheet class
-
- # Generate content.xml XML data
- xml = XML()
- self.lines = xml.convert(self.data)
- self.filedata = '\n'.join(self.lines)
- # Return generated data
- return self.filedata
-
- def _ods_manifest(self):
- "Generate ods manifest.xml data"
- self.data = ['tag', 'manifest:manifest',
- ['element', 'xmlns:manifest', 'urn:oasis:names:tc:opendocument:xmlns:manifest:1.0'],
- ['tagline', 'manifest:file-entry',
- ['element', 'manifest:media-type', 'application/vnd.oasis.opendocument.spreadsheet'],
- ['element', 'manifest:full-path', '/']],
- ['tagline', 'manifest:file-entry',
- ['element', 'manifest:media-type', 'application/vnd.sun.xml.ui.configuration'],
- ['element', 'manifest:full-path', 'Configurations2/']],
- ['tagline', 'manifest:file-entry',
- ['element', 'manifest:media-type', ''],
- ['element', 'manifest:full-path', 'Configurations2/accelerator/']],
- ['tagline', 'manifest:file-entry',
- ['element', 'manifest:media-type', ''],
- ['element', 'manifest:full-path', 'Configurations2/accelerator/current.xml']],
- ['tagline', 'manifest:file-entry',
- ['element', 'manifest:media-type', 'text/xml'],
- ['element', 'manifest:full-path', 'content.xml']],
- ['tagline', 'manifest:file-entry',
- ['element', 'manifest:media-type', 'text/xml'],
- ['element', 'manifest:full-path', 'styles.xml']],
- ['tagline', 'manifest:file-entry',
- ['element', 'manifest:media-type', 'text/xml'],
- ['element', 'manifest:full-path', 'meta.xml']],
- ['tagline', 'manifest:file-entry',
- ['element', 'manifest:media-type', 'text/xml'],
- ['element', 'manifest:full-path', 'settings.xml']]]
-
- # Add additional files to manifest list
- for fileset in self.manifest_files:
- (filename, filetype, newname) = fileset
- addfile = ['tagline', 'manifest:file-entry',
- ['element', 'manifest:media-type', filetype],
- ['element', 'manifest:full-path', newname]]
- self.data.append(addfile)
-
- # Generate content.xml XML data
- xml = XML()
- self.lines = xml.convert(self.data)
- self.filedata = '\n'.join(self.lines)
- # Return generated data
- return self.filedata
-
-
- def _ods_settings(self):
- "Generate ods settings.xml data"
- self.data = ['tag', 'office:document-settings',
- ['element', 'xmlns:office', 'urn:oasis:names:tc:opendocument:xmlns:office:1.0'],
- ['element', 'xmlns:xlink', 'http://www.w3.org/1999/xlink'],
- ['element', 'xmlns:config', 'urn:oasis:names:tc:opendocument:xmlns:config:1.0'],
- ['element', 'xmlns:ooo', 'http://openoffice.org/2004/office'],
- ['element', 'office:version', '1.0'],
- ['tag', 'office:settings',
- ['tag', 'config:config-item-set',
- ['element', 'config:name', 'ooo:view-settings'],
- ['tag', 'config:config-item',
- ['element', 'config:name', 'VisibleAreaTop'],
- ['element', 'config:type', 'int'],
- ['data', '0']],
- ['tag', 'config:config-item',
- ['element', 'config:name', 'VisibleAreaLeft'],
- ['element', 'config:type', 'int'],
- ['data', '0']],
- ['tag', 'config:config-item',
- ['element', 'config:name', 'VisibleAreaWidth'],
- ['element', 'config:type', 'int'],
- ['data', '6774']],
- ['tag', 'config:config-item',
- ['element', 'config:name', 'VisibleAreaHeight'],
- ['element', 'config:type', 'int'],
- ['data', '2389']],
- ['tag', 'config:config-item-map-indexed',
- ['element', 'config:name', 'Views'],
- ['tag', 'config:config-item-map-entry',
- ['tag', 'config:config-item',
- ['element', 'config:name', 'ViewId'],
- ['element', 'config:type', 'string'],
- ['data', 'View1']],
- ['tag', 'config:config-item-map-named',
- ['element', 'config:name', 'Tables'],
- ['tag', 'config:config-item-map-entry',
- ['element', 'config:name', 'Sheet1'],
- ['tag', 'config:config-item',
- ['element', 'config:name', 'CursorPositionX'], # Cursor Position A
- ['element', 'config:type', 'int'],
- ['data', '0']],
- ['tag', 'config:config-item',
- ['element', 'config:name', 'CursorPositionY'], # Cursor Position 1
- ['element', 'config:type', 'int'],
- ['data', '0']],
- ['tag', 'config:config-item',
- ['element', 'config:name', 'HorizontalSplitMode'],
- ['element', 'config:type', 'short'],
- ['data', '0']],
- ['tag', 'config:config-item',
- ['element', 'config:name', 'VerticalSplitMode'],
- ['element', 'config:type', 'short'],
- ['data', '0']],
- ['tag', 'config:config-item',
- ['element', 'config:name', 'HorizontalSplitPosition'],
- ['element', 'config:type', 'int'],
- ['data', '0']],
- ['tag', 'config:config-item',
- ['element', 'config:name', 'VerticalSplitPosition'],
- ['element', 'config:type', 'int'],
- ['data', '0']],
- ['tag', 'config:config-item',
- ['element', 'config:name', 'ActiveSplitRange'],
- ['element', 'config:type', 'short'],
- ['data', '2']],
- ['tag', 'config:config-item',
- ['element', 'config:name', 'PositionLeft'],
- ['element', 'config:type', 'int'],
- ['data', '0']],
- ['tag', 'config:config-item',
- ['element', 'config:name', 'PositionRight'],
- ['element', 'config:type', 'int'],
- ['data', '0']],
- ['tag', 'config:config-item',
- ['element', 'config:name', 'PositionTop'],
- ['element', 'config:type', 'int'],
- ['data', '0']],
- ['tag', 'config:config-item',
- ['element', 'config:name', 'PositionBottom'],
- ['element', 'config:type', 'int'],
- ['data', '0']]]],
- ['tag', 'config:config-item',
- ['element', 'config:name', 'ActiveTable'],
- ['element', 'config:type', 'string'],
- ['data', 'Sheet1']],
- ['tag', 'config:config-item',
- ['element', 'config:name', 'HorizontalScrollbarWidth'],
- ['element', 'config:type', 'int'],
- ['data', '270']],
- ['tag', 'config:config-item',
- ['element', 'config:name', 'ZoomType'],
- ['element', 'config:type', 'short'],
- ['data', '0']],
- ['tag', 'config:config-item',
- ['element', 'config:name', 'ZoomValue'],
- ['element', 'config:type', 'int'],
- ['data', '100']],
- ['tag', 'config:config-item',
- ['element', 'config:name', 'PageViewZoomValue'],
- ['element', 'config:type', 'int'],
- ['data', '60']],
- ['tag', 'config:config-item',
- ['element', 'config:name', 'ShowPageBreakPreview'],
- ['element', 'config:type', 'boolean'],
- ['data', 'false']],
- ['tag', 'config:config-item',
- ['element', 'config:name', 'ShowZeroValues'],
- ['element', 'config:type', 'boolean'],
- ['data', 'true']],
- ['tag', 'config:config-item',
- ['element', 'config:name', 'ShowNotes'],
- ['element', 'config:type', 'boolean'],
- ['data', 'true']],
- ['tag', 'config:config-item',
- ['element', 'config:name', 'ShowGrid'],
- ['element', 'config:type', 'boolean'],
- ['data', 'true']],
- ['tag', 'config:config-item',
- ['element', 'config:name', 'GridColor'],
- ['element', 'config:type', 'long'],
- ['data', '12632256']],
- ['tag', 'config:config-item',
- ['element', 'config:name', 'ShowPageBreaks'],
- ['element', 'config:type', 'boolean'],
- ['data', 'true']],
- ['tag', 'config:config-item',
- ['element', 'config:name', 'HasColumnRowHeaders'],
- ['element', 'config:type', 'boolean'],
- ['data', 'true']],
- ['tag', 'config:config-item',
- ['element', 'config:name', 'HasSheetTabs'],
- ['element', 'config:type', 'boolean'],
- ['data', 'true']],
- ['tag', 'config:config-item',
- ['element', 'config:name', 'IsOutlineSymbolsSet'],
- ['element', 'config:type', 'boolean'],
- ['data', 'true']],
- ['tag', 'config:config-item',
- ['element', 'config:name', 'IsSnapToRaster'],
- ['element', 'config:type', 'boolean'],
- ['data', 'false']],
- ['tag', 'config:config-item',
- ['element', 'config:name', 'RasterIsVisible'],
- ['element', 'config:type', 'boolean'],
- ['data', 'false']],
- ['tag', 'config:config-item',
- ['element', 'config:name', 'RasterResolutionX'],
- ['element', 'config:type', 'int'],
- ['data', '1270']],
- ['tag', 'config:config-item',
- ['element', 'config:name', 'RasterResolutionY'],
- ['element', 'config:type', 'int'],
- ['data', '1270']],
- ['tag', 'config:config-item',
- ['element', 'config:name', 'RasterSubdivisionX'],
- ['element', 'config:type', 'int'],
- ['data', '1']],
- ['tag', 'config:config-item',
- ['element', 'config:name', 'RasterSubdivisionY'],
- ['element', 'config:type', 'int'],
- ['data', '1']],
- ['tag', 'config:config-item',
- ['element', 'config:name', 'IsRasterAxisSynchronized'],
- ['element', 'config:type', 'boolean'],
- ['data', 'true']]]]],
- ['tag', 'config:config-item-set',
- ['element', 'config:name', 'ooo:configuration-settings'],
- ['tag', 'config:config-item',
- ['element', 'config:name', 'ShowZeroValues'],
- ['element', 'config:type', 'boolean'],
- ['data', 'true']],
- ['tag', 'config:config-item',
- ['element', 'config:name', 'ShowNotes'],
- ['element', 'config:type', 'boolean'],
- ['data', 'true']],
- ['tag', 'config:config-item',
- ['element', 'config:name', 'ShowGrid'],
- ['element', 'config:type', 'boolean'],
- ['data', 'true']],
- ['tag', 'config:config-item',
- ['element', 'config:name', 'GridColor'],
- ['element', 'config:type', 'long'],
- ['data', '12632256']],
- ['tag', 'config:config-item',
- ['element', 'config:name', 'ShowPageBreaks'],
- ['element', 'config:type', 'boolean'],
- ['data', 'true']],
- ['tag', 'config:config-item',
- ['element', 'config:name', 'LinkUpdateMode'],
- ['element', 'config:type', 'short'],
- ['data', '3']],
- ['tag', 'config:config-item',
- ['element', 'config:name', 'HasColumnRowHeaders'],
- ['element', 'config:type', 'boolean'],
- ['data', 'true']],
- ['tag', 'config:config-item',
- ['element', 'config:name', 'HasSheetTabs'],
- ['element', 'config:type', 'boolean'],
- ['data', 'true']],
- ['tag', 'config:config-item',
- ['element', 'config:name', 'IsOutlineSymbolsSet'],
- ['element', 'config:type', 'boolean'],
- ['data', 'true']],
- ['tag', 'config:config-item',
- ['element', 'config:name', 'IsSnapToRaster'],
- ['element', 'config:type', 'boolean'],
- ['data', 'false']],
- ['tag', 'config:config-item',
- ['element', 'config:name', 'RasterIsVisible'],
- ['element', 'config:type', 'boolean'],
- ['data', 'false']],
- ['tag', 'config:config-item',
- ['element', 'config:name', 'RasterResolutionX'],
- ['element', 'config:type', 'int'],
- ['data', '1270']],
- ['tag', 'config:config-item',
- ['element', 'config:name', 'RasterResolutionY'],
- ['element', 'config:type', 'int'],
- ['data', '1270']],
- ['tag', 'config:config-item',
- ['element', 'config:name', 'RasterSubdivisionX'],
- ['element', 'config:type', 'int'],
- ['data', '1']],
- ['tag', 'config:config-item',
- ['element', 'config:name', 'RasterSubdivisionY'],
- ['element', 'config:type', 'int'],
- ['data', '1']],
- ['tag', 'config:config-item',
- ['element', 'config:name', 'IsRasterAxisSynchronized'],
- ['element', 'config:type', 'boolean'],
- ['data', 'true']],
- ['tag', 'config:config-item',
- ['element', 'config:name', 'AutoCalculate'],
- ['element', 'config:type', 'boolean'],
- ['data', 'true']],
- ['tag', 'config:config-item',
- ['element', 'config:name', 'PrinterName'],
- ['element', 'config:type', 'string'],
- ['data', 'Generic Printer']],
- ['tag', 'config:config-item',
- ['element', 'config:name', 'PrinterSetup'],
- ['element', 'config:type', 'base64Binary'],
- ['data', 'YgH+/0dlbmVyaWMgUHJpbnRlcgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAU0dFTlBSVAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAWAAMAqAAAAAAA//8FAFZUAAAkbQAASm9iRGF0YSAxCnByaW50ZXI9R2VuZXJpYyBQcmludGVyCm9yaWVudGF0aW9uPVBvcnRyYWl0CmNvcGllcz0xCnNjYWxlPTEwMAptYXJnaW5kYWp1c3RtZW50PTAsMCwwLDAKY29sb3JkZXB0aD0yNApwc2xldmVsPTAKY29sb3JkZXZpY2U9MApQUERDb250ZXhEYXRhClBhZ2VTaXplOkxldHRlcgAA']],
- ['tag', 'config:config-item',
- ['element', 'config:name', 'ApplyUserData'],
- ['element', 'config:type', 'boolean'],
- ['data', 'true']],
- ['tag', 'config:config-item',
- ['element', 'config:name', 'CharacterCompressionType'],
- ['element', 'config:type', 'short'],
- ['data', '0']],
- ['tag', 'config:config-item',
- ['element', 'config:name', 'IsKernAsianPunctuation'],
- ['element', 'config:type', 'boolean'],
- ['data', 'false']],
- ['tag', 'config:config-item',
- ['element', 'config:name', 'SaveVersionOnClose'],
- ['element', 'config:type', 'boolean'],
- ['data', 'false']],
- ['tag', 'config:config-item',
- ['element', 'config:name', 'UpdateFromTemplate'],
- ['element', 'config:type', 'boolean'],
- ['data', 'false']],
- ['tag', 'config:config-item',
- ['element', 'config:name', 'AllowPrintJobCancel'],
- ['element', 'config:type', 'boolean'],
- ['data', 'true']],
- ['tag', 'config:config-item',
- ['element', 'config:name', 'LoadReadonly'],
- ['element', 'config:type', 'boolean'],
- ['data', 'false']]]]]
-
- # Generate content.xml XML data
- xml = XML()
- self.lines = xml.convert(self.data)
- self.filedata = '\n'.join(self.lines)
- # Return generated data
- return self.filedata
-
-
- def _ods_styles(self):
- "Generate ods styles.xml data"
- self.data = ['tag', 'office:document-styles',
- ['element', 'xmlns:office', 'urn:oasis:names:tc:opendocument:xmlns:office:1.0'],
- ['element', 'xmlns:style', 'urn:oasis:names:tc:opendocument:xmlns:style:1.0'],
- ['element', 'xmlns:text', 'urn:oasis:names:tc:opendocument:xmlns:text:1.0'],
- ['element', 'xmlns:table', 'urn:oasis:names:tc:opendocument:xmlns:table:1.0'],
- ['element', 'xmlns:draw', 'urn:oasis:names:tc:opendocument:xmlns:drawing:1.0'],
- ['element', 'xmlns:fo', 'urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0'],
- ['element', 'xmlns:xlink', 'http://www.w3.org/1999/xlink'],
- ['element', 'xmlns:dc', 'http://purl.org/dc/elements/1.1/'],
- ['element', 'xmlns:meta', 'urn:oasis:names:tc:opendocument:xmlns:meta:1.0'],
- ['element', 'xmlns:number', 'urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0'],
- ['element', 'xmlns:svg', 'urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0'],
- ['element', 'xmlns:chart', 'urn:oasis:names:tc:opendocument:xmlns:chart:1.0'],
- ['element', 'xmlns:dr3d', 'urn:oasis:names:tc:opendocument:xmlns:dr3d:1.0'],
- ['element', 'xmlns:math', 'http://www.w3.org/1998/Math/MathML'],
- ['element', 'xmlns:form', 'urn:oasis:names:tc:opendocument:xmlns:form:1.0'],
- ['element', 'xmlns:script', 'urn:oasis:names:tc:opendocument:xmlns:script:1.0'],
- ['element', 'xmlns:ooo', 'http://openoffice.org/2004/office'],
- ['element', 'xmlns:ooow', 'http://openoffice.org/2004/writer'],
- ['element', 'xmlns:oooc', 'http://openoffice.org/2004/calc'],
- ['element', 'xmlns:dom', 'http://www.w3.org/2001/xml-events'],
- ['element', 'office:version', '1.0'],
- ['tag', 'office:font-face-decls',
- ['tagline', 'style:font-face',
- ['element', 'style:name', 'DejaVu Sans'],
- ['element', 'svg:font-family', '&apos;DejaVu Sans&apos;'],
- ['element', 'style:font-pitch', 'variable']],
- ['tagline', 'style:font-face',
- ['element', 'style:name', 'Nimbus Sans L'],
- ['element', 'svg:font-family', '&apos;Nimbus Sans L&apos;'],
- ['element', 'style:font-family-generic', 'swiss'],
- ['element', 'style:font-pitch', 'variable']]],
- ['tag', 'office:styles',
- ['tag', 'style:default-style',
- ['element', 'style:family', 'table-cell'],
- ['tagline', 'style:table-cell-properties',
- ['element', 'style:decimal-places', '2']],
- ['tagline', 'style:paragraph-properties',
- ['element', 'style:tab-stop-distance', '0.5in']],
- ['tagline', 'style:text-properties',
- ['element', 'style:font-name', 'Nimbus Sans L'],
- ['element', 'fo:language', 'en'],
- ['element', 'fo:country', 'US'],
- ['element', 'style:font-name-asian', 'DejaVu Sans'],
- ['element', 'style:language-asian', 'none'],
- ['element', 'style:country-asian', 'none'],
- ['element', 'style:font-name-complex', 'DejaVu Sans'],
- ['element', 'style:language-complex', 'none'],
- ['element', 'style:country-complex', 'none']]],
- ['tag', 'number:number-style',
- ['element', 'style:name', 'N0'],
- ['tagline', 'number:number',
- ['element', 'number:min-integer-digits', '1']]],
- ['tag', 'number:currency-style',
- ['element', 'style:name', 'N104P0'],
- ['element', 'style:volatile', 'true'],
- ['tag', 'number:currency-symbol',
- ['element', 'number:language', 'en'],
- ['element', 'number:country', 'US'],
- ['data', '$']],
- ['tagline', 'number:number',
- ['element', 'number:decimal-places', '2'],
- ['element', 'number:min-integer-digits', '1'],
- ['element', 'number:grouping', 'true']]],
- ['tag', 'number:currency-style',
- ['element', 'style:name', 'N104'],
- ['tagline', 'style:text-properties',
- ['element', 'fo:color', '#ff0000']],
- ['tag', 'number:text',
- ['data', '-']],
- ['tag', 'number:currency-symbol',
- ['element', 'number:language', 'en'],
- ['element', 'number:country', 'US'],
- ['data', '$']],
- ['tagline', 'number:number',
- ['element', 'number:decimal-places', '2'],
- ['element', 'number:min-integer-digits', '1'],
- ['element', 'number:grouping', 'true']],
- ['tagline', 'style:map',
- ['element', 'style:condition', 'value()&gt;=0'],
- ['element', 'style:apply-style-name', 'N104P0']]],
- ['tagline', 'style:style',
- ['element', 'style:name', 'Default'],
- ['element', 'style:family', 'table-cell']],
- ['tag', 'style:style',
- ['element', 'style:name', 'Result'],
- ['element', 'style:family', 'table-cell'],
- ['element', 'style:parent-style-name', 'Default'],
- ['tagline', 'style:text-properties',
- ['element', 'fo:font-style', 'italic'],
- ['element', 'style:text-underline-style', 'solid'],
- ['element', 'style:text-underline-width', 'auto'],
- ['element', 'style:text-underline-color', 'font-color'],
- ['element', 'fo:font-weight', 'bold']]],
- ['tagline', 'style:style',
- ['element', 'style:name', 'Result2'],
- ['element', 'style:family', 'table-cell'],
- ['element', 'style:parent-style-name', 'Result'],
- ['element', 'style:data-style-name', 'N104']],
- ['tag', 'style:style',
- ['element', 'style:name', 'Heading'],
- ['element', 'style:family', 'table-cell'],
- ['element', 'style:parent-style-name', 'Default'],
- ['tagline', 'style:table-cell-properties',
- ['element', 'style:text-align-source', 'fix'],
- ['element', 'style:repeat-content', 'false']],
- ['tagline', 'style:paragraph-properties',
- ['element', 'fo:text-align', 'center']],
- ['tagline', 'style:text-properties',
- ['element', 'fo:font-size', '16pt'],
- ['element', 'fo:font-style', 'italic'],
- ['element', 'fo:font-weight', 'bold']]],
- ['tag', 'style:style',
- ['element', 'style:name', 'Heading1'],
- ['element', 'style:family', 'table-cell'],
- ['element', 'style:parent-style-name', 'Heading'],
- ['tagline', 'style:table-cell-properties',
- ['element', 'style:rotation-angle', '90']]]],
- ['tag', 'office:automatic-styles',
- ['tag', 'style:page-layout',
- ['element', 'style:name', 'pm1'],
- ['tagline', 'style:page-layout-properties',
- ['element', 'style:writing-mode', 'lr-tb']],
- ['tag', 'style:header-style',
- ['tagline', 'style:header-footer-properties',
- ['element', 'fo:min-height', '0.2957in'],
- ['element', 'fo:margin-left', '0in'],
- ['element', 'fo:margin-right', '0in'],
- ['element', 'fo:margin-bottom', '0.0984in']]],
- ['tag', 'style:footer-style',
- ['tagline', 'style:header-footer-properties',
- ['element', 'fo:min-height', '0.2957in'],
- ['element', 'fo:margin-left', '0in'],
- ['element', 'fo:margin-right', '0in'],
- ['element', 'fo:margin-top', '0.0984in']]]],
- ['tag', 'style:page-layout',
- ['element', 'style:name', 'pm2'],
- ['tagline', 'style:page-layout-properties',
- ['element', 'style:writing-mode', 'lr-tb']],
- ['tag', 'style:header-style',
- ['tag', 'style:header-footer-properties',
- ['element', 'fo:min-height', '0.2957in'],
- ['element', 'fo:margin-left', '0in'],
- ['element', 'fo:margin-right', '0in'],
- ['element', 'fo:margin-bottom', '0.0984in'],
- ['element', 'fo:border', '0.0346in solid #000000'],
- ['element', 'fo:padding', '0.0071in'],
- ['element', 'fo:background-color', '#c0c0c0'],
- ['tagline', 'style:background-image']]],
- ['tag', 'style:footer-style',
- ['tag', 'style:header-footer-properties',
- ['element', 'fo:min-height', '0.2957in'],
- ['element', 'fo:margin-left', '0in'],
- ['element', 'fo:margin-right', '0in'],
- ['element', 'fo:margin-top', '0.0984in'],
- ['element', 'fo:border', '0.0346in solid #000000'],
- ['element', 'fo:padding', '0.0071in'],
- ['element', 'fo:background-color', '#c0c0c0'],
- ['tagline', 'style:background-image']]]]],
- ['tag', 'office:master-styles',
- ['tag', 'style:master-page',
- ['element', 'style:name', 'Default'],
- ['element', 'style:page-layout-name', 'pm1'],
- ['tag', 'style:header',
- ['tag', 'text:p',
- ['data', '<text:sheet-name>???</text:sheet-name>']]],
- ['tagline', 'style:header-left',
- ['element', 'style:display', 'false']],
- ['tag', 'style:footer',
- ['tag', 'text:p',
- ['data', 'Page <text:page-number>1</text:page-number>']]],
- ['tagline', 'style:footer-left',
- ['element', 'style:display', 'false']]],
- ['tag', 'style:master-page',
- ['element', 'style:name', 'Report'],
- ['element', 'style:page-layout-name', 'pm2'],
- ['tag', 'style:header',
- ['tag', 'style:region-left',
- ['tag', 'text:p',
- ['data', '<text:sheet-name>???</text:sheet-name> (<text:title>???</text:title>)']]],
- ['tag', 'style:region-right',
- ['tag', 'text:p',
- ['data', '<text:date style:data-style-name="N2" text:date-value="2006-09-29">09/29/2006</text:date>, <text:time>13:02:56</text:time>']]]],
- ['tagline', 'style:header-left',
- ['element', 'style:display', 'false']],
- ['tag', 'style:footer',
- ['tag', 'text:p',
- ['data', 'Page <text:page-number>1</text:page-number> / <text:page-count>99</text:page-count>']]],
- ['tagline', 'style:footer-left',
- ['element', 'style:display', 'false']]]]]
-
-
- # Generate content.xml XML data
- xml = XML()
- self.lines = xml.convert(self.data)
- self.filedata = '\n'.join(self.lines)
- # Return generated data
- return self.filedata
-
-class Writer:
- "Writer Class - Used to create OpenDocument Format Writer Documents."
- def __init__(self):
- "Initialize ooolib Writer instance"
- # Default to no debugging
- self.debug = False
- self.meta = Meta('odt')
-
- def set_meta(self, metaname, value):
- "Set meta data in your document."
- self.meta.set_meta(metaname, value)
-
- def save(self, filename):
- """Save .odt document
-
- The save function saves the current .odt document.
- """
- if self.debug: print "Writing %s" % filename
- self.savefile = zipfile.ZipFile(filename, "w")
- if self.debug: print " meta.xml"
- self._zip_insert(self.savefile, "meta.xml", self.meta.get_meta())
- if self.debug: print " mimetype"
- self._zip_insert(self.savefile, "mimetype", "application/vnd.oasis.opendocument.text")
- if self.debug: print " META-INF/manifest.xml"
- self._zip_insert(self.savefile, "META-INF/manifest.xml", self._odt_manifest())
- if self.debug: print " content.xml"
- self._zip_insert(self.savefile, "content.xml", self._odt_content())
- if self.debug: print " settings.xml"
- # self._zip_insert(self.savefile, "settings.xml", self._odt_settings())
- if self.debug: print " styles.xml"
- # self._zip_insert(self.savefile, "styles.xml", self._odt_styles())
-
- # We need to close the file now that we are done creating it.
- self.savefile.close()
-
- def _zip_insert(self, file, filename, data):
- now = time.localtime(time.time())[:6]
- info = zipfile.ZipInfo(filename)
- info.date_time = now
- info.compress_type = zipfile.ZIP_DEFLATED
- file.writestr(info, data)
-
- def _odt_manifest(self):
- "Generate odt manifest.xml data"
-
- self.data = ['tag', 'manifest:manifest',
- ['element', 'xmlns:manifest', 'urn:oasis:names:tc:opendocument:xmlns:manifest:1.0'],
- ['tagline', 'manifest:file-entry',
- ['element', 'manifest:media-type', 'application/vnd.oasis.opendocument.text'],
- ['element', 'manifest:full-path', '/']],
- ['tagline', 'manifest:file-entry',
- ['element', 'manifest:media-type', 'text/xml'],
- ['element', 'manifest:full-path', 'content.xml']],
- ['tagline', 'manifest:file-entry',
- ['element', 'manifest:media-type', 'text/xml'],
- ['element', 'manifest:full-path', 'styles.xml']],
- ['tagline', 'manifest:file-entry',
- ['element', 'manifest:media-type', 'text/xml'],
- ['element', 'manifest:full-path', 'meta.xml']],
- ['tagline', 'manifest:file-entry',
- ['element', 'manifest:media-type', 'text/xml'],
- ['element', 'manifest:full-path', 'settings.xml']]]
-
- # Generate content.xml XML data
- xml = XML()
- self.lines = xml.convert(self.data)
- self.lines.insert(1, '<!DOCTYPE manifest:manifest PUBLIC "-//OpenOffice.org//DTD Manifest 1.0//EN" "Manifest.dtd">')
- self.filedata = '\n'.join(self.lines)
- # Return generated data
- return self.filedata
-
- def _odt_content(self):
- "Generate odt content.xml data"
-
- self.data = ['tag', 'office:document-content',
- ['element', 'xmlns:office', 'urn:oasis:names:tc:opendocument:xmlns:office:1.0'],
- ['element', 'xmlns:style', 'urn:oasis:names:tc:opendocument:xmlns:style:1.0'],
- ['element', 'xmlns:text', 'urn:oasis:names:tc:opendocument:xmlns:text:1.0'],
- ['element', 'xmlns:table', 'urn:oasis:names:tc:opendocument:xmlns:table:1.0'],
- ['element', 'xmlns:draw', 'urn:oasis:names:tc:opendocument:xmlns:drawing:1.0'],
- ['element', 'xmlns:fo', 'urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0'],
- ['element', 'xmlns:xlink', 'http://www.w3.org/1999/xlink'],
- ['element', 'xmlns:dc', 'http://purl.org/dc/elements/1.1/'],
- ['element', 'xmlns:meta', 'urn:oasis:names:tc:opendocument:xmlns:meta:1.0'],
- ['element', 'xmlns:number', 'urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0'],
- ['element', 'xmlns:svg', 'urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0'],
- ['element', 'xmlns:chart', 'urn:oasis:names:tc:opendocument:xmlns:chart:1.0'],
- ['element', 'xmlns:dr3d', 'urn:oasis:names:tc:opendocument:xmlns:dr3d:1.0'],
- ['element', 'xmlns:math', 'http://www.w3.org/1998/Math/MathML'],
- ['element', 'xmlns:form', 'urn:oasis:names:tc:opendocument:xmlns:form:1.0'],
- ['element', 'xmlns:script', 'urn:oasis:names:tc:opendocument:xmlns:script:1.0'],
- ['element', 'xmlns:ooo', 'http://openoffice.org/2004/office'],
- ['element', 'xmlns:ooow', 'http://openoffice.org/2004/writer'],
- ['element', 'xmlns:oooc', 'http://openoffice.org/2004/calc'],
- ['element', 'xmlns:dom', 'http://www.w3.org/2001/xml-events'],
- ['element', 'xmlns:xforms', 'http://www.w3.org/2002/xforms'],
- ['element', 'xmlns:xsd', 'http://www.w3.org/2001/XMLSchema'],
- ['element', 'xmlns:xsi', 'http://www.w3.org/2001/XMLSchema-instance'],
- ['element', 'office:version', '1.0'],
- ['tagline', 'office:scripts'],
- ['tag', 'office:font-face-decls',
- ['tagline', 'style:font-face',
- ['element', 'style:name', 'DejaVu Sans'],
- ['element', 'svg:font-family', '&apos;DejaVu Sans&apos;'],
- ['element', 'style:font-pitch', 'variable']],
+ "Returns an XML friendly copy of the data string"
+
+ data = u"{}".format(data) # This line thanks to Chris Ender (and updated for Py2/Py3 by Zdenek Bohm)
+
+ data = data.replace('&', '&amp;')
+ data = data.replace("'", '&apos;')
+ data = data.replace('"', '&quot;')
+ data = data.replace('<', '&lt;')
+ data = data.replace('>', '&gt;')
+ data = data.replace('\t', '<text:tab-stop/>')
+ data = data.replace('\n', '<text:line-break/>')
+ return data
+
+
+class XML(object):
+ "XML Class - Used to convert nested lists into XML"
+
+ def _xmldata(self, data):
+ data.pop(0) # data type
+ datavalue = data.pop(0)
+ outstring = '%s' % datavalue
+ return outstring
+
+ def _xmltag(self, data):
+ outstring = ''
+ # First two
+ data.pop(0) # datatype
+ dataname = data.pop(0)
+ outstring = '<%s' % dataname
+ # Element Section
+ element = 1
+ while(data):
+ # elements
+ newdata = data.pop(0)
+ if newdata[0] == 'element' and element:
+ newstring = self._xmlelement(newdata)
+ outstring = '%s %s' % (outstring, newstring)
+ continue
+ if newdata[0] != 'element' and element:
+ element = 0
+ outstring = '%s>' % outstring
+ if newdata[0] == 'tag' or newdata[0] == 'tagline':
+ outstring = '%s\n' % outstring
+ if newdata[0] == 'tag':
+ newstring = self._xmltag(newdata)
+ outstring = '%s%s' % (outstring, newstring)
+ continue
+ if newdata[0] == 'tagline':
+ newstring = self._xmltagline(newdata)
+ outstring = '%s%s' % (outstring, newstring)
+ continue
+ if newdata[0] == 'data':
+ newstring = self._xmldata(newdata)
+ outstring = '%s%s' % (outstring, newstring)
+ continue
+ if element:
+ element = 0
+ outstring = '%s>\n' % outstring
+ outstring = '%s</%s>\n' % (outstring, dataname)
+ return outstring
+
+ def _xmltagline(self, data):
+ outstring = ''
+ # First two
+ data.pop(0) # datatype
+ dataname = data.pop(0)
+ outstring = '<%s' % dataname
+ # Element Section
+ while(data):
+ # elements
+ newdata = data.pop(0)
+ if newdata[0] != 'element':
+ break
+ newstring = self._xmlelement(newdata)
+ outstring = '%s %s' % (outstring, newstring)
+ outstring = '%s/>\n' % outstring
+ # Non-Element Section should not exist
+ return outstring
+
+ def _xmlelement(self, data):
+ data.pop(0) # datatype
+ dataname = data.pop(0)
+ datavalue = data.pop(0)
+ outstring = '%s="%s"' % (dataname, datavalue)
+ return outstring
+
+ def convert(self, data):
+ """Convert nested lists into XML
+
+ The convert method takes a nested lists and converts them
+ into XML to be used in Open Document Format documents.
+ There are three types of lists that are recognized at this
+ time. They are as follows:
+
+ 'tag' - Tag opens a set of data that is eventually closed
+ with a similar tag.
+ List: ['tag', 'xml']
+ XML: <xml></xml>
+
+ 'tagline' - Taglines are similar to tags, except they open
+ and close themselves.
+ List: ['tagline', 'xml']
+ XML: <xml/>
+
+ 'element' - Elements are pieces of information stored in an
+ opening tag or tagline.
+ List: ['element', 'color', 'blue']
+ XML: color="blue"
+
+ 'data' - Data is plain text directly inserted into the XML
+ document.
+ List: ['data', 'hello']
+ XML: hello
+
+ Bring them all together for something like this.
+
+ Lists:
+ ['tag', 'xml', ['element', 'a', 'b'], ['tagline', 'xml2'],
+ ['data', 'asdf']]
+
+ XML:
+ <xml a="b"><xml2/>asdf</xml>
+ """
+ outlines = []
+ outlines.append('<?xml version="1.0" encoding="UTF-8"?>')
+ if isinstance(data, (list, tuple)) and len(data) > 0:
+ if data[0] == 'tag':
+ outlines.append(self._xmltag(data))
+ return outlines
+
+
+class Meta(object):
+ "Meta Data Class"
+
+ def __init__(self, doctype, debug=False):
+ self.doctype = doctype
+
+ # Set the debug mode
+ self.debug = debug
+
+ # The generator should always default to the version number
+ self.meta_generator = version()
+ self.meta_title = ''
+ self.meta_subject = ''
+ self.meta_description = ''
+ self.meta_keywords = []
+ self.meta_creator = 'ooolib-python'
+ self.meta_editor = ''
+ self.meta_user1_name = 'Info 1'
+ self.meta_user2_name = 'Info 2'
+ self.meta_user3_name = 'Info 3'
+ self.meta_user4_name = 'Info 4'
+ self.meta_user1_value = ''
+ self.meta_user2_value = ''
+ self.meta_user3_value = ''
+ self.meta_user4_value = ''
+ self.meta_creation_date = self.meta_time()
+
+ # Parser data
+ self.parser_element_list = []
+ self.parser_element = ""
+ self.parser_count = 0
+
+ def set_meta(self, metaname, value):
+ """Set meta data in your document.
+
+ Currently implemented metaname options are as follows:
+ 'creator' - The document author
+ """
+ if metaname == 'creator': self.meta_creator = value
+ if metaname == 'editor': self.meta_editor = value
+ if metaname == 'title': self.meta_title = value
+ if metaname == 'subject': self.meta_subject = value
+ if metaname == 'description': self.meta_description = value
+ if metaname == 'user1name': self.meta_user1_name = value
+ if metaname == 'user2name': self.meta_user2_name = value
+ if metaname == 'user3name': self.meta_user3_name = value
+ if metaname == 'user4name': self.meta_user4_name = value
+ if metaname == 'user1value': self.meta_user1_value = value
+ if metaname == 'user2value': self.meta_user2_value = value
+ if metaname == 'user3value': self.meta_user3_value = value
+ if metaname == 'user4value': self.meta_user4_value = value
+ if metaname == 'keyword':
+ if value not in self.meta_keywords:
+ self.meta_keywords.append(value)
+
+ def get_meta_value(self, metaname):
+ "Get meta data value for a given metaname."
+
+ if metaname == 'creator': return self.meta_creator
+ if metaname == 'editor': return self.meta_editor
+ if metaname == 'title': return self.meta_title
+ if metaname == 'subject': return self.meta_subject
+ if metaname == 'description': return self.meta_description
+ if metaname == 'user1name': return self.meta_user1_name
+ if metaname == 'user2name': return self.meta_user2_name
+ if metaname == 'user3name': return self.meta_user3_name
+ if metaname == 'user4name': return self.meta_user4_name
+ if metaname == 'user1value': return self.meta_user1_value
+ if metaname == 'user2value': return self.meta_user2_value
+ if metaname == 'user3value': return self.meta_user3_value
+ if metaname == 'user4value': return self.meta_user4_value
+ if metaname == 'keyword': return self.meta_keywords
+
+ def meta_time(self):
+ "Return time string in meta data format"
+ return "%04d-%02d-%02dT%02d:%02d:%02d" % time.localtime()[:6]
+
+ def parse_start_element(self, name, attrs):
+ if self.debug: print('* Start element: {}'.format(name))
+ self.parser_element_list.append(name)
+ self.parser_element = self.parser_element_list[-1]
+
+ # Need the meta name from the user-defined tags
+ if self.parser_element == "meta:user-defined":
+ self.parser_count += 1
+ # Set user-defined name
+ self.set_meta("user%dname" % self.parser_count, attrs['meta:name'])
+
+ # Debugging statements
+ if self.debug: print(" List: {}".format(self.parser_element_list))
+ if self.debug: print(" Attributes: {}".format(attrs))
+
+ def parse_end_element(self, name):
+ if self.debug: print('* End element: {}'.format(name))
+ if name != self.parser_element:
+ print("Tag Mismatch: '%s' != '%s'" % (name, self.parser_element))
+ self.parser_element_list.pop()
+
+ # Readjust parser_element_list and parser_element
+ if self.parser_element_list:
+ self.parser_element = self.parser_element_list[-1]
+ else:
+ self.parser_element = ""
+
+ def parse_char_data(self, data):
+ if self.debug: print(" Character data: {!r}".format(data))
+
+ # Collect Meta data fields
+ if self.parser_element == "dc:title":
+ self.set_meta("title", data)
+ if self.parser_element == "dc:description":
+ self.set_meta("description", data)
+ if self.parser_element == "dc:subject":
+ self.set_meta("subject", data)
+ if self.parser_element == "meta:initial-creator":
+ self.set_meta("creator", data)
+
+ # Try to maintain the same creation date
+ if self.parser_element == "meta:creation-date":
+ self.meta_creation_date = data
+
+ # The user defined fields need to be kept track of, parser_count does that
+ if self.parser_element == "meta:user-defined":
+ self.set_meta("user%dvalue" % self.parser_count, data)
+
+ def meta_parse(self, data):
+ "Parse Meta Data from a meta.xml file"
+
+ # Debugging statements
+ if self.debug:
+ # Sometimes it helps to see the document that was read from
+ print(data)
+ print("\n\n\n")
+
+ # Create parser
+ parser = xml.parsers.expat.ParserCreate()
+ # Set up parser callback functions
+ parser.StartElementHandler = self.parse_start_element
+ parser.EndElementHandler = self.parse_end_element
+ parser.CharacterDataHandler = self.parse_char_data
+
+ # Actually parse the data
+ parser.Parse(data, 1)
+
+ def get_meta(self):
+ "Generate meta.xml file data"
+ self.meta_date = self.meta_time()
+ self.data = ['tag', 'office:document-meta',
+ ['element', 'xmlns:office', 'urn:oasis:names:tc:opendocument:xmlns:office:1.0'],
+ ['element', 'xmlns:xlink', 'http://www.w3.org/1999/xlink'],
+ ['element', 'xmlns:dc', 'http://purl.org/dc/elements/1.1/'],
+ ['element', 'xmlns:meta', 'urn:oasis:names:tc:opendocument:xmlns:meta:1.0'],
+ ['element', 'xmlns:ooo', 'http://openoffice.org/2004/office'],
+ ['element', 'office:version', '1.0'],
+ ['tag', 'office:meta',
+ ['tag', 'meta:generator', # Was: 'OpenOffice.org/2.0$Linux OpenOffice.org_project/680m5$Build-9011'
+ ['data', self.meta_generator]], # Generator is set the the ooolib-python version.
+ ['tag', 'dc:title',
+ ['data', self.meta_title]], # This data is the document title
+ ['tag', 'dc:description',
+ ['data', self.meta_description]], # This data is the document description
+ ['tag', 'dc:subject',
+ ['data', self.meta_subject]], # This data is the document subject
+ ['tag', 'meta:initial-creator',
+ ['data', self.meta_creator]], # This data is the document creator
+ ['tag', 'meta:creation-date',
+ ['data', self.meta_creation_date]], # This is the original creation date of the document
+ ['tag', 'dc:creator',
+ ['data', self.meta_editor]], # This data is the document editor
+ ['tag', 'dc:date',
+ ['data', self.meta_date]], # This is the last modified date of the document
+ ['tag', 'dc:language',
+ ['data', 'en-US']], # We will probably always use en-US for language
+ ['tag', 'meta:editing-cycles',
+ ['data', '1']], # Edit cycles will probably always be 1 for generated documents
+ ['tag', 'meta:editing-duration',
+ ['data', 'PT0S']], # Editing duration is modified - creation date
+ ['tag', 'meta:user-defined',
+ ['element', 'meta:name', self.meta_user1_name],
+ ['data', self.meta_user1_value]],
+ ['tag', 'meta:user-defined',
+ ['element', 'meta:name', self.meta_user2_name],
+ ['data', self.meta_user2_value]],
+ ['tag', 'meta:user-defined',
+ ['element', 'meta:name', self.meta_user3_name],
+ ['data', self.meta_user3_value]],
+ ['tag', 'meta:user-defined',
+ ['element', 'meta:name', self.meta_user4_name],
+ ['data', self.meta_user4_value]]]]
+# ['tagline', 'meta:document-statistic',
+# ['element', 'meta:table-count', len(self.sheets)], # len(self.sheets) ?
+# ['element', 'meta:cell-count', '15']]]] # Not sure how to keep track
+
+ # Generate content.xml XML data
+ xmldoc = XML()
+ self.lines = xmldoc.convert(self.data)
+ self.filedata = '\n'.join(self.lines)
+ # Return generated data
+ return self.filedata
+
+
+class CalcStyles(object):
+ "Calc Style Management - Used to keep track of created styles."
+
+ def __init__(self):
+ self.style_config = {}
+ # Style Counters
+ self.style_table = 1
+ self.style_column = 1
+ self.style_row = 1
+ self.style_cell = 1
+ # Style Properties (Defaults) - To be used later
+ self.property_column_width_default = '0.8925in' # Default Column Width
+ self.property_row_height_default = '0.189in' # Default Row Height
+ # Set Defaults
+ self.property_column_width = '0.8925in' # Default Column Width
+ self.property_row_height = '0.189in' # Default Row Height
+ self.property_cell_bold = False # Bold off be default
+ self.property_cell_italic = False # Italic off be default
+ self.property_cell_underline = False # Underline off be default
+ self.property_cell_fg_color = 'default' # Text Color Default
+ self.property_cell_bg_color = 'default' # Cell Background Default
+ self.property_cell_bg_image = 'none' # Cell Background Default
+ self.property_cell_fontsize = '10' # Cell Font Size Default
+ self.property_cell_valign = 'default' # Vertial Alignment Default
+ self.property_cell_halign = 'default' # Horizantal Alignment Default
+ self.cardinal = ['top', 'right', 'bottom', 'left'] # four cardinal
+ self.property_cell_border = [False, False, False, False]
+ self.property_cell_padding = [False, False, False, False]
+ self.property_cell_wrap_option = False
+ self.property_cell_hyphenate = False
+
+ def get_next_style(self, style):
+ "Returns the next style code for the given style"
+ style_code = ""
+ if style == 'table':
+ style_code = 'ta%d' % self.style_table
+ self.style_table += 1
+ if style == 'column':
+ style_code = 'co%d' % self.style_column
+ self.style_column += 1
+ if style == 'row':
+ style_code = 'ro%d' % self.style_row
+ self.style_row += 1
+ if style == 'cell':
+ style_code = 'ce%d' % self.style_cell
+ self.style_cell += 1
+ return style_code
+
+ def set_property(self, style, name, value):
+ "Sets a property which will later be turned into a code"
+ if style == 'column':
+ if name == 'style:column-width': self.property_column_width = value
+ elif style == 'row':
+ if name == 'style:row-height': self.property_row_height = value
+ elif style == 'cell':
+ if isinstance(value, bool):
+ if name == 'bold':
+ self.property_cell_bold = value
+ elif name == 'italic':
+ self.property_cell_italic = value
+ elif name == 'underline':
+ self.property_cell_underline = value
+ if name == 'fontsize':
+ self.property_cell_fontsize = value
+ elif name == 'hyphenate':
+ self.property_cell_hyphenate = value
+ elif name == 'color':
+ self.property_cell_fg_color = 'default'
+ redata = re.search("^(#[\da-fA-F]{6})$", value)
+ if redata: self.property_cell_fg_color = value.lower()
+ elif name == 'background':
+ self.property_cell_bg_color = 'default'
+ redata = re.search("^(#[\da-fA-F]{6})$", value)
+ if redata: self.property_cell_bg_color = value.lower()
+ elif name == 'backgroundimage':
+ self.property_cell_bg_image = value
+ elif name == 'valign':
+ self.property_cell_valign = value
+ elif name == 'halign':
+ self.property_cell_halign = value
+ elif name == 'wrap-option':
+ self.property_cell_wrap_option = value
+
+ self.init_cell_cardinal('border', name, value)
+ self.init_cell_cardinal('padding', name, value)
+
+ def init_cell_cardinal(self, key, name, value):
+ "Init property border, padding"
+ property = getattr(self, 'property_cell_%s' % key)
+ if name == key:
+ setattr(self, 'property_cell_%s' % key, [value] * 4)
+ match = re.match('%s-(top|right|bottom|left)' % key, name)
+ if match:
+ property[self.cardinal.index(match.group(1))] = value
+
+ def get_style_code(self, style):
+ style_code = ""
+ if style == 'table':
+ style_code = "ta1"
+ if style == 'column':
+ style_data = tuple([style,
+ ('style:column-width', self.property_column_width)])
+ if style_data in self.style_config:
+ # Style Exists, return code
+ style_code = self.style_config[style_data]
+ else:
+ # Style does not exist, create code and return it
+ style_code = self.get_next_style(style)
+ self.style_config[style_data] = style_code
+ if style == 'row':
+ style_data = tuple([style,
+ ('style:row-height', self.property_row_height)])
+ if style_data in self.style_config:
+ # Style Exists, return code
+ style_code = self.style_config[style_data]
+ else:
+ # Style does not exist, create code and return it
+ style_code = self.get_next_style(style)
+ self.style_config[style_data] = style_code
+ if style == 'cell':
+ style_data = [style]
+ # Add additional styles
+ if self.property_cell_bold: style_data.append(('bold', True))
+ if self.property_cell_italic: style_data.append(('italic', True))
+ if self.property_cell_underline: style_data.append(('underline', True))
+ if self.property_cell_fontsize != '10':
+ style_data.append(('fontsize', self.property_cell_fontsize))
+ if self.property_cell_hyphenate:
+ style_data.append(('hyphenate', 'true'))
+ if self.property_cell_fg_color != 'default':
+ style_data.append(('color', self.property_cell_fg_color))
+ if self.property_cell_bg_color != 'default':
+ style_data.append(('background', self.property_cell_bg_color))
+ if self.property_cell_bg_image != 'none':
+ style_data.append(('backgroundimage', self.property_cell_bg_image))
+ if self.property_cell_valign != 'default':
+ style_data.append(('valign', self.property_cell_valign))
+ if self.property_cell_halign != 'default':
+ style_data.append(('halign', self.property_cell_halign))
+ if self.property_cell_wrap_option:
+ style_data.append(('wrap-option', self.property_cell_wrap_option))
+
+ style_data.extend(self.get_cardinal_style('border'))
+ style_data.extend(self.get_cardinal_style('padding'))
+
+ style_data = tuple(style_data)
+ if style_data in self.style_config:
+ # Style Exists, return code
+ style_code = self.style_config[style_data]
+ else:
+ # Style does not exist, create code and return it
+ style_code = self.get_next_style(style)
+ self.style_config[style_data] = style_code
+ return style_code
+
+ def get_cardinal_style(self, key):
+ "Get cardinal styles"
+ style_data = []
+ property = getattr(self, 'property_cell_%s' % key)
+ borders = tuple(set(property))
+ if not (borders[0] is False and len(borders) == 1):
+ if len(borders) == 1:
+ style_data.append((key, borders[0]))
+ else:
+ for pos in range(4):
+ value = property[pos]
+ if value:
+ style_data.append(('%s-%s' % (key, self.cardinal[pos]), value))
+ return style_data
+
+ def get_automatic_styles(self):
+ "Return 'office:automatic-styles' lists"
+ automatic_styles = ['tag', 'office:automatic-styles']
+
+ for style_data in self.style_config:
+ style_code = self.style_config[style_data]
+ style_data = list(style_data)
+ style = style_data.pop(0)
+
+ if style == 'column':
+ style_list = ['tag', 'style:style',
+ ['element', 'style:name', style_code], # Column 'co1' properties
+ ['element', 'style:family', 'table-column']]
+ tagline = ['tagline', 'style:table-column-properties',
+ ['element', 'fo:break-before', 'auto']] # unsure what break before means
+
+ for name, value in style_data:
+ if name == 'style:column-width':
+ tagline.append(['element', 'style:column-width', value])
+ style_list.append(tagline)
+ automatic_styles.append(style_list)
+
+ if style == 'row':
+ style_list = ['tag', 'style:style',
+ ['element', 'style:name', style_code], # Column 'ro1' properties
+ ['element', 'style:family', 'table-row']]
+ tagline = ['tagline', 'style:table-row-properties']
+
+ for name, value in style_data:
+ if name == 'style:row-height':
+ tagline.append(['element', 'style:row-height', value])
+ tagline.append(['element', 'fo:break-before', 'auto'])
+# tagline.append(['element', 'style:use-optimal-row-height', 'true']) # Overrides settings
+ style_list.append(tagline)
+ automatic_styles.append(style_list)
+
+ if style == 'cell':
+ style_list = ['tag', 'style:style',
+ ['element', 'style:name', style_code], # ce1 style
+ ['element', 'style:family', 'table-cell'], # cell
+ ['element', 'style:parent-style-name', 'Default']] # parent is Default
+
+ # Cell Properties
+ tagline = ['tag', 'style:table-cell-properties']
+ tagline_additional = []
+ for name, value in style_data:
+ if name == 'background':
+ tagline.append(['element', 'fo:background-color', value])
+ elif name == 'backgroundimage':
+ tagline.append(['element', 'fo:background-color', 'transparent'])
+ # Additional tags added later
+ bgimagetag = ['tagline', 'style:background-image']
+ bgimagetag.append(['element', 'xlink:href', value])
+ bgimagetag.append(['element', 'xlink:type', 'simple'])
+ bgimagetag.append(['element', 'xlink:actuate', 'onLoad'])
+ tagline_additional.append(bgimagetag)
+ elif name == 'valign':
+ if value in ['top', 'bottom', 'middle']:
+ tagline.append(['element', 'style:vertical-align', value])
+ elif name == 'halign':
+ tagline.append(['element', 'style:text-align-source', 'fix'])
+ if value in ['filled']:
+ tagline.append(['element', 'style:repeat-content', 'true'])
+ else:
+ tagline.append(['element', 'style:repeat-content', 'false'])
+ elif name == 'wrap-option':
+ tagline.append(['element', 'fo:wrap-option', value])
+ elif re.match('border', name):
+ tagline.append(['element', 'fo:%s' % name, value])
+ elif re.match('padding', name):
+ tagline.append(['element', 'fo:%s' % name, value])
+
+ # Add any additional internal tags
+ while tagline_additional:
+ tagadd = tagline_additional.pop(0)
+ tagline.append(tagadd)
+
+ style_list.append(tagline)
+
+ # Paragraph Properties
+ tagline = ['tagline', 'style:paragraph-properties']
+ tagline_valid = False
+ for name, value in style_data:
+ if name == 'halign':
+ tagline_valid = True
+ if value in ['center']:
+ tagline.append(['element', 'fo:text-align', 'center'])
+ if value in ['end', 'right']:
+ tagline.append(['element', 'fo:text-align', 'end'])
+ if value in ['start', 'filled', 'left']:
+ tagline.append(['element', 'fo:text-align', 'start'])
+ if value in ['justify']:
+ tagline.append(['element', 'fo:text-align', 'justify'])
+ # Conditionally add the tagline
+ if tagline_valid: style_list.append(tagline)
+
+ # Text Properties
+ tagline = ['tagline', 'style:text-properties']
+ for name, value in style_data:
+ if name == 'bold':
+ tagline.append(['element', 'fo:font-weight', 'bold'])
+ elif name == 'italic':
+ tagline.append(['element', 'fo:font-style', 'italic'])
+ elif name == 'underline':
+ tagline.append(['element', 'style:text-underline-style', 'solid'])
+ tagline.append(['element', 'style:text-underline-width', 'auto'])
+ tagline.append(['element', 'style:text-underline-color', 'font-color'])
+ elif name == 'color':
+ tagline.append(['element', 'fo:color', value])
+ elif name == 'fontsize':
+ tagline.append(['element', 'fo:font-size', '%spt' % value])
+ if name == 'hyphenate':
+ tagline.append(['element', 'fo:hyphenate', value])
+ style_list.append(tagline)
+
+ automatic_styles.append(style_list)
+
+ # Attach ta1 style
+ automatic_styles.append(['tag', 'style:style',
+ ['element', 'style:name', 'ta1'],
+ ['element', 'style:family', 'table'],
+ ['element', 'style:master-page-name', 'Default'],
+ ['tagline', 'style:table-properties',
+ ['element', 'table:display', 'true'],
+ ['element', 'style:writing-mode', 'lr-tb']]])
+
+ return automatic_styles
+
+
+class CalcSheet(object):
+ "Calc Sheet Class - Used to keep track of the data for an individual sheet."
+
+ def __init__(self, sheetname):
+ "Initialize a sheet"
+ self.sheet_name = sheetname
+ self.sheet_values = {}
+ self.sheet_config = {}
+ self.max_col = 0
+ self.max_row = 0
+
+ def get_sheet_dimensions(self):
+ "Returns the max column and row"
+ return (self.max_col, self.max_row)
+
+ def clean_formula(self, data):
+ "Returns a formula for use in ODF"
+ # Example Translations
+ # '=SUM(A1:A2)'
+ # datavalue = 'oooc:=SUM([.A1:.A2])'
+ # '=IF((A5>A4);A4;"")'
+ # datavalue = 'oooc:=IF(([.A5]&gt;[.A4]);[.A4];&quot;&quot;)'
+ data = clean_string(data)
+ redata = re.search('^=([A-Z]+)(\(.*)$', data)
+ if redata:
+ # funct is the function name. The rest if the string will be the funct_args
+ funct = redata.group(1)
+ funct_args = re.sub('([A-Z]+\d+)', '[.\\1]', redata.group(2))
+ data = 'oooc:=%s%s' % (funct, funct_args)
+ return data
+
+ def get_name(self):
+ "Returns the sheet name"
+ return self.sheet_name
+
+ def set_name(self, sheetname):
+ "Resets the sheet name"
+ self.sheet_name = sheetname
+
+ def get_sheet_values(self):
+ "Returns the sheet cell values"
+ return self.sheet_values
+
+ def get_sheet_value(self, col, row):
+ "Get the value contents of a cell"
+ cell = (col, row)
+ if cell in self.sheet_values:
+ return self.sheet_values[cell]
+ else:
+ return None
+
+ def get_sheet_config(self):
+ "Returns the sheet cell properties"
+ return self.sheet_config
+
+ def set_sheet_config(self, location, style_code):
+ "Sets Style Code for a given location"
+ self.sheet_config[location] = style_code
+
+ def set_sheet_value(self, cell, datatype, datavalue, formula_value='0'):
+ """Sets the value for a specific cell
+
+ cell must be in the format (col, row) where row and col are int.
+ Example: B5 would be written as (2, 5)
+ datatype must be one of 'string', 'float', 'formula'
+ datavalue should be a string
+ """
+ # Catch invalid data
+ if not isinstance(cell, tuple) or len(cell) != 2:
+ print("Invalid Cell")
+ return
+ (col, row) = cell
+ if not isinstance(col, int):
+ print("Invalid Cell")
+ return
+ if not isinstance(row, int):
+ print("Invalid Cell")
+ return
+ # Fix String Data
+ if datatype in ['string', 'annotation']:
+ datavalue = clean_string(datavalue)
+ # Fix Link Data. Link's value is a tuple containing (url, description)
+ if datatype == 'link':
+ url = clean_string(datavalue[0])
+ desc = clean_string(datavalue[1])
+ datavalue = (url, desc)
+ # Fix Formula Data
+ if datatype == 'formula':
+ datavalue = self.clean_formula(datavalue)
+ # Adjust maximum sizes
+ if col > self.max_col: self.max_col = col
+ if row > self.max_row: self.max_row = row
+ if datatype not in ('string', 'float', 'formula', 'annotation', 'link'):
+ # Set all unknown cell types to string
+ datatype = 'string'
+ datavalue = u"{}".format(datavalue)
+
+ # The following lines are taken directly from HPS
+ # self.sheet_values[cell] = (datatype, datavalue)
+ # HPS: Cell content is now a list of tuples instead of a tuple
+ # While storing here, store the cell contents first and the annotation next. While generating the XML reverse this
+ contents = self.sheet_values.get(cell, {'annotation': None, 'links': None, 'value': None})
+ if datatype == 'annotation':
+ contents['annotation'] = (datatype, datavalue)
+ elif datatype == 'link':
+ if contents['links']:
+ contents['links'][1].append(datavalue)
+ else:
+ contents['links'] = (datatype, [datavalue])
+ else:
+ contents['value'] = (datatype, datavalue)
+ if datatype == 'formula':
+ contents['formula_value'] = formula_value
+
+ self.sheet_values[cell] = contents
+
+ def get_lists(self):
+ "Returns nested lists for XML processing"
+ if self.max_col == 0 and self.max_row == 0:
+ sheet_lists = ['tag', 'table:table',
+ ['element', 'table:name', self.sheet_name], # Set the Sheet Name
+ ['element', 'table:style-name', 'ta1'],
+ ['element', 'table:print', 'false'],
+ ['tagline', 'table:table-column',
+ ['element', 'table:style-name', 'co1'],
+ ['element', 'table:default-cell-style-name', 'Default']],
+ ['tag', 'table:table-row',
+ ['element', 'table:style-name', 'ro1'],
+ ['tagline', 'table:table-cell']]]
+ else:
+ # Base Information
+ sheet_lists = ['tag', 'table:table',
+ ['element', 'table:name', self.sheet_name], # Set the sheet name
+ ['element', 'table:style-name', 'ta1'],
+ ['element', 'table:print', 'false']]
+
+# ['tagline', 'table:table-column',
+# ['element', 'table:style-name', 'co1'],
+# ['element', 'table:number-columns-repeated', self.max_col], # max_col? '2'
+# ['element', 'table:default-cell-style-name', 'Default']],
+
+ # Need to add column information
+ for col in range(1, self.max_col + 1):
+ location = ('col', col)
+ style_code = 'co1'
+ if location in self.sheet_config:
+ style_code = self.sheet_config[location]
+ sheet_lists.append(['tagline', 'table:table-column',
+ ['element', 'table:style-name', style_code],
+ ['element', 'table:default-cell-style-name', 'Default']])
+
+ # Need to create each row
+ for row in range(1, self.max_row + 1):
+ location = ('row', row)
+ style_code = 'ro1'
+ if location in self.sheet_config:
+ style_code = self.sheet_config[location]
+ rowlist = ['tag', 'table:table-row',
+ ['element', 'table:style-name', style_code]]
+ for col in range(1, self.max_col + 1):
+ cell = (col, row)
+ style_code = 'ce1' # Default all cells to ce1
+ if cell in self.sheet_config:
+ style_code = self.sheet_config[cell] # Lookup cell if available
+ if cell in self.sheet_values:
+ # (datatype, datavalue) = self.sheet_values[cell] # Marked for removal
+ collist = ['tag', 'table:table-cell']
+ if style_code != 'ce1':
+ collist.append(['element', 'table:style-name', style_code])
+
+ # Contents, annotations, and links added by HPS
+ contents = self.sheet_values[cell] # cell contents is a dictionary
+ if contents['value']:
+ (datatype, datavalue) = contents['value']
+ if datatype == 'float':
+ collist.append(['element', 'office:value-type', datatype])
+ collist.append(['element', 'office:value', datavalue])
+
+ if datatype == 'string':
+ collist.append(['element', 'office:value-type', datatype])
+ if datatype == 'formula':
+ collist.append(['element', 'table:formula', datavalue])
+ collist.append(['element', 'office:value-type', 'float'])
+ collist.append(['element', 'office:value', contents['formula_value']])
+ datavalue = contents['formula_value']
+ else:
+ datavalue = None
+
+ if contents['annotation']:
+ (annotype, annoval) = contents['annotation']
+ collist.append(['tag', 'office:annotation',
+ ['tag', 'text:p', ['data', annoval]]])
+
+ if contents['links']:
+ (linktype, linkvals) = contents['links']
+ # TODO: parse all urls, not only linkvals[0].
+ if datavalue:
+ collist.append(['tag', 'text:p', ['data', datavalue],
+ ['tag', 'text:a', ['element', 'xlink:href', linkvals[0][0]],
+ ['data', linkvals[0][1]]]])
+ else: # no value; just fill the link
+ collist.append(['tag', 'text:p',
+ ['tag', 'text:a', ['element', 'xlink:href', linkvals[0][0]],
+ ['data', linkvals[0][1]]]])
+ else:
+ if datavalue:
+ collist.append(['tag', 'text:p', ['data', datavalue]])
+ else:
+ collist = ['tagline', 'table:table-cell']
+ rowlist.append(collist)
+ sheet_lists.append(rowlist)
+ return sheet_lists
+
+
+class Calc(object):
+ "Calc Class - Used to create OpenDocument Format Calc Spreadsheets."
+
+ def __init__(self, sheetname=None, opendoc=None, debug=False):
+ "Initialize ooolib Calc instance"
+ # Default to no debugging
+ self.debug = debug
+ if not sheetname: sheetname = "Sheet1"
+ self.sheets = [CalcSheet(sheetname)] # The main sheet will be initially called 'Sheet1'
+ self.sheet_index = 0 # We initially start on the first sheet
+ self.styles = CalcStyles()
+ self.meta = Meta('ods')
+ self.styles.get_style_code('column') # Force generation of default column
+ self.styles.get_style_code('row') # Force generation of default row
+ self.styles.get_style_code('table') # Force generation of default table
+ self.styles.get_style_code('cell') # Force generation of default cell
+ self.manifest_files = [] # List of extra files included
+ self.manifest_index = 1 # Index of added manifest files
+
+ # Data Parsing
+ self.parser_element_list = []
+ self.parser_element = ""
+ self.parser_element_attrs = {}
+ self.parser_sheet_num = 0
+ self.parser_sheet_row = 0
+ self.parser_sheet_column = 0
+ self.parser_cell_repeats = 0
+ self.parser_cell_string_pending = False
+ self.parser_cell_string_line = ""
+
+ # See if we need to read a document
+ if opendoc:
+ # Verify that the document exists
+ if self.debug: print("Opening Document: %s" % opendoc)
+
+ # Okay, now we load the file
+ self.load(opendoc)
+
+ def debug_level(self, level):
+ """Set debug level:
+ True if you want debugging messages
+ False if you do not.
+ """
+ self.debug = level
+
+ def file_mimetype(self, filename):
+ "Determine the filetype from the filename"
+ parts = filename.lower().split('.')
+ ext = parts[-1]
+ if ext == 'png':
+ return ext, "image/png"
+ if ext == 'gif':
+ return ext, "image/gif"
+ return ext, "image/unknown"
+
+ def add_file(self, filename):
+ """Prepare a file for loading into ooolib
+
+ The filename should be the local filesystem name for
+ the file. The file is then prepared to be included in
+ the creation of the final document. The file needs to
+ remain in place so that it is available when the actual
+ document creation happens.
+ """
+ # mimetype set to (ext, filetype)
+ mimetype = self.file_mimetype(filename)
+ newname = "Pictures/%08d.%s" % (self.manifest_index, mimetype[0])
+ self.manifest_index += 1
+ filetype = mimetype[1]
+ self.manifest_files.append((filename, filetype, newname))
+ return newname
+
+ def set_meta(self, metaname, value):
+ "Set meta data in your document."
+ self.meta.set_meta(metaname, value)
+
+ def get_meta_value(self, metaname):
+ "Get meta data value for a given metaname"
+ return self.meta.get_meta_value(metaname)
+
+ def get_sheet_name(self):
+ "Returns the sheet name"
+ return self.sheets[self.sheet_index].get_name()
+
+ def get_sheet_dimensions(self):
+ "Returns the sheet dimensions in (cols, rows)"
+ return self.sheets[self.sheet_index].get_sheet_dimensions()
+
+ def set_column_property(self, column, name, value):
+ "Set Column Properties"
+ if name == 'width':
+ # column number column needs column-width set to value
+ self.styles.set_property('column', 'style:column-width', value)
+ style_code = self.styles.get_style_code('column')
+ self.sheets[self.sheet_index].set_sheet_config(('col', column), style_code)
+
+ def set_row_property(self, row, name, value):
+ "Set row Properties"
+ if name == 'height':
+ # row number row needs row-height set to value
+ self.styles.set_property('row', 'style:row-height', value)
+ style_code = self.styles.get_style_code('row')
+ self.sheets[self.sheet_index].set_sheet_config(('row', row), style_code)
+
+ def set_cell_property(self, name, value):
+ """Turn and off cell properties
+
+ Actual application of properties is handled by setting a value."""
+ # background images need to be handled a little differently
+ # because they need to also be inserted into the final document
+ if name == 'backgroundimage':
+ # Add file and modify value
+ value = self.add_file(value)
+ self.styles.set_property('cell', name, value)
+
+ def get_sheet_index(self):
+ "Return the current sheet index number"
+ return self.sheet_index
+
+ def set_sheet_index(self, index):
+ "Set the sheet index"
+ if isinstance(index, int):
+ if index >= 0 and index < len(self.sheets):
+ self.sheet_index = index
+ return self.sheet_index
+
+ def get_sheet_count(self):
+ "Returns the number of existing sheets"
+ return len(self.sheets)
+
+ def new_sheet(self, sheetname):
+ "Create a new sheet"
+ self.sheet_index = len(self.sheets)
+ self.sheets.append(CalcSheet(sheetname))
+ return self.sheet_index
+
+ def set_cell_value(self, col, row, datatype, value, formula_value='0'):
+ "Set the value for a given cell"
+ self.sheets[self.sheet_index].set_sheet_value((col, row), datatype, value, formula_value)
+ style_code = self.styles.get_style_code('cell')
+ self.sheets[self.sheet_index].set_sheet_config((col, row), style_code)
+
+ def get_cell_content(self, col, row):
+ "Get a cell content for a given cell. Content is a dict with keys annotation, value, link."
+ return self.sheets[self.sheet_index].get_sheet_value(col, row)
+
+ def get_cell_annotation(self, col, row):
+ "Get a cell annotation."
+ sheetvalue = self.get_cell_content(col, row)
+ return sheetvalue.get("annotation") if isinstance(sheetvalue, dict) else sheetvalue
+
+ def get_cell_links(self, col, row):
+ "Get a cell links. The links is list of tuples [(url, label), ...]."
+ sheetvalue = self.get_cell_content(col, row)
+ if isinstance(sheetvalue, dict):
+ link = sheetvalue.get("links")
+ return None if link is None else link[1]
+ return sheetvalue
+
+ def get_cell_value(self, col, row):
+ "Get a cell value tuple (type, value) for a given cell"
+ sheetvalue = self.get_cell_content(col, row)
+ return sheetvalue.get("value") if isinstance(sheetvalue, dict) else sheetvalue
+
+ def load(self, filename):
+ """Load .ods spreadsheet.
+
+ The load function loads data from a document into the current cells.
+ """
+ # Read in the important files
+
+ # meta.xml
+ data = self._zip_read(filename, "meta.xml")
+ self.meta.meta_parse(data)
+
+ # content.xml
+ data = self._zip_read(filename, "content.xml")
+ self.content_parse(data)
+
+ # settings.xml - I do not remember putting anything here
+ # styles.xml - I do not remember putting anything here
+
+ def parse_content_start_element(self, name, attrs):
+ if self.debug: print('* Start element: {} {}'.format(name, attrs))
+ self.parser_element_list.append(name)
+ self.parser_element = self.parser_element_list[-1]
+ self.parser_element_attrs = attrs
+
+ # Keep track of the current sheet number
+ if self.parser_element == 'table:table':
+ # Move to starting cell
+ self.parser_sheet_row = 0
+ self.parser_sheet_column = 0
+ # Increment the sheet number count
+ self.parser_sheet_num += 1
+ if self.parser_sheet_num - 1 != self.sheet_index:
+ # We are not on the first sheet and need to create a new sheet.
+ # We will automatically move to the new sheet
+ sheetname = "Sheet%d" % self.parser_sheet_num
+ if 'table:name' in attrs: sheetname = attrs['table:name']
+ self.new_sheet(sheetname)
+ else:
+ # We are on the first sheet and will need to overwrite the default name
+ sheetname = "Sheet%d" % self.parser_sheet_num
+ if 'table:name' in attrs: sheetname = attrs['table:name']
+ self.sheets[self.sheet_index].set_name(sheetname)
+
+ # Update the row numbers
+ if self.parser_element == 'table:table-row':
+ self.parser_sheet_row += 1
+ self.parser_sheet_column = 0
+
+ # Okay, now keep track of the sheet cell data
+ if self.parser_element == 'table:table-cell':
+ # By default it will repeat zero times
+ self.parser_cell_repeats = 0
+ # We must be in a new column
+ self.parser_sheet_column += 1
+ # Set some default values
+ datatype = ""
+ value = ""
+ # Get values from attrs hash
+ if 'office:value-type' in attrs: datatype = attrs['office:value-type']
+ if 'office:value' in attrs: value = attrs['office:value']
+ if 'table:formula' in attrs:
+ datatype = 'formula'
+ value = attrs['table:formula']
+ if datatype == 'string':
+ datatype = ""
+ self.parser_cell_string_pending = True
+ self.parser_cell_string_line = ""
+ if 'table:number-columns-repeated' in attrs:
+ self.parser_cell_repeats = int(attrs['table:number-columns-repeated']) - 1
+ # Set the cell value
+ if datatype:
+ # I should do this once per cell repeat above 0
+ for i in range(0, self.parser_cell_repeats + 1):
+ self.set_cell_value(self.parser_sheet_column + i, self.parser_sheet_row, datatype, value)
+
+ # There are lots of interesting cases with table:table-cell data. One problem is
+ # reading the number of embedded spaces correctly. This code should help us get
+ # the number of spaces out.
+
+ if self.parser_element == 'text:s':
+ # This means we have a number of spaces
+ count_num = 0
+ if 'text:c' in attrs:
+ count_alpha = attrs['text:c']
+ if count_alpha.isdigit():
+ count_num = int(count_alpha)
+ # I am not sure what to do if we do not have a string pending
+ if self.parser_cell_string_pending:
+ # Append the currect number of spaces to the end
+ self.parser_cell_string_line = "%s%s" % (self.parser_cell_string_line, ' ' * count_num)
+
+ if self.parser_element == 'text:tab-stop':
+ if self.parser_cell_string_pending:
+ self.parser_cell_string_line = "%s\t" % (self.parser_cell_string_line)
+
+ if self.parser_element == 'text:line-break':
+ if self.parser_cell_string_pending:
+ self.parser_cell_string_line = "%s\n" % (self.parser_cell_string_line)
+
+ # Debugging statements
+ if self.debug: print(" List: {}".format(self.parser_element_list))
+ if self.debug: print(" Attributes: {}".format(attrs))
+
+ def parse_content_end_element(self, name):
+ if self.debug: print('* End element: {}'.format(name))
+ if name != self.parser_element:
+ print("Tag Mismatch: '%s' != '%s'" % (name, self.parser_element))
+ self.parser_element_list.pop()
+
+ # If the element was text:p and we are in string mode
+ if self.parser_element == 'text:p':
+ if self.parser_cell_string_pending:
+ self.parser_cell_string_pending = False
+
+ # Take care of repeated cells
+ if self.parser_element == 'table:table-cell':
+ self.parser_sheet_column += self.parser_cell_repeats
+
+ # Readjust parser_element_list and parser_element
+ if self.parser_element_list:
+ self.parser_element = self.parser_element_list[-1]
+ else:
+ self.parser_element = ""
+
+ def parse_content_char_data(self, data):
+ if self.debug: print(" Character data: {!r}".format(data))
+
+ if self.parser_element in ('text:p', 'text:span', 'text:a'):
+ if self.parser_cell_string_pending:
+ # Set the string and leave string pending mode
+ # This does feel a little kludgy, but it does the job
+ text = self.parser_element_attrs['xlink:href'] if self.parser_element == 'text:a' else data
+ self.parser_cell_string_line = "%s%s" % (self.parser_cell_string_line, text)
+
+ # I should do this once per cell repeat above 0
+ for i in range(0, self.parser_cell_repeats + 1):
+ self.set_cell_value(
+ self.parser_sheet_column + i,
+ self.parser_sheet_row,
+ 'string',
+ self.parser_cell_string_line
+ )
+ if self.parser_element == 'text:a':
+ self.set_cell_value(
+ self.parser_sheet_column + i,
+ self.parser_sheet_row,
+ 'link',
+ (self.parser_element_attrs['xlink:href'], data)
+ )
+
+ def content_parse(self, data):
+ "Parse Content Data from a content.xml file"
+
+ # Debugging statements
+ if self.debug:
+ # Sometimes it helps to see the document that was read from
+ print(data)
+ print("\n\n\n")
+
+ # Create parser
+ parser = xml.parsers.expat.ParserCreate()
+ # Set up parser callback functions
+ parser.StartElementHandler = self.parse_content_start_element
+ parser.EndElementHandler = self.parse_content_end_element
+ parser.CharacterDataHandler = self.parse_content_char_data
+
+ # Actually parse the data
+ parser.Parse(data, 1)
+
+ def save(self, filename):
+ """Save .ods spreadsheet.
+
+ The save function saves the current cells and settings into a document.
+ """
+ if self.debug: print("Writing %s" % filename)
+ self.savefile = zipfile.ZipFile(filename, "w")
+ if self.debug: print(" meta.xml")
+ self._zip_insert(self.savefile, "meta.xml", self.meta.get_meta())
+ if self.debug: print(" mimetype")
+ self._zip_insert(self.savefile, "mimetype", "application/vnd.oasis.opendocument.spreadsheet")
+ if self.debug: print(" Configurations2/accelerator/current.xml")
+ self._zip_insert(self.savefile, "Configurations2/accelerator/current.xml", "")
+ if self.debug: print(" META-INF/manifest.xml")
+ self._zip_insert(self.savefile, "META-INF/manifest.xml", self._ods_manifest())
+ if self.debug: print(" content.xml")
+ self._zip_insert(self.savefile, "content.xml", self._ods_content())
+ if self.debug: print(" settings.xml")
+ self._zip_insert(self.savefile, "settings.xml", self._ods_settings())
+ if self.debug: print(" styles.xml")
+ self._zip_insert(self.savefile, "styles.xml", self._ods_styles())
+
+ # Add additional files if needed
+ for fileset in self.manifest_files:
+ (filename, filetype, newname) = fileset
+ # Read in the file
+ data = self._file_load(filename)
+ if self.debug: print(" Inserting '%s' as '%s'" % (filename, newname))
+ self._zip_insert_binary(self.savefile, newname, data)
+ self.savefile.close()
+
+ def _file_load(self, filename):
+ "Load a file"
+ return open(filename, "rb").read()
+
+ def _zip_insert_binary(self, fileobj, filename, data):
+ "Insert a binary file into the zip archive"
+ now = time.localtime(time.time())[:6]
+ info = zipfile.ZipInfo(filename)
+ info.date_time = now
+ info.compress_type = zipfile.ZIP_DEFLATED
+ fileobj.writestr(info, data)
+
+ def _zip_insert(self, fileobj, filename, data):
+ "Insert a file into the zip archive"
+
+ # zip seems to struggle with non-ascii characters
+ data = data.encode('utf-8')
+
+ now = time.localtime(time.time())[:6]
+ info = zipfile.ZipInfo(filename)
+ info.date_time = now
+ info.compress_type = zipfile.ZIP_DEFLATED
+ fileobj.writestr(info, data)
+
+ def _zip_read(self, fileobj, filename):
+ "Get the data from a file in the zip archive by filename"
+ zipdoc = zipfile.ZipFile(fileobj, "r")
+ data = zipdoc.read(filename)
+ # Need to close the file
+ zipdoc.close()
+ return data
+
+ def _ods_content(self):
+ "Generate ods content.xml data"
+
+ # This will list all of the sheets in the document
+ self.sheetdata = ['tag', 'office:spreadsheet']
+ for sheet in self.sheets:
+ if self.debug:
+ sheet_name = sheet.get_name()
+ print(" Creating Sheet '%s'" % sheet_name)
+ sheet_list = sheet.get_lists()
+ self.sheetdata.append(sheet_list)
+ # Automatic Styles
+ self.automatic_styles = self.styles.get_automatic_styles()
+
+ self.data = ['tag', 'office:document-content',
+ ['element', 'xmlns:office', 'urn:oasis:names:tc:opendocument:xmlns:office:1.0'],
+ ['element', 'xmlns:style', 'urn:oasis:names:tc:opendocument:xmlns:style:1.0'],
+ ['element', 'xmlns:text', 'urn:oasis:names:tc:opendocument:xmlns:text:1.0'],
+ ['element', 'xmlns:table', 'urn:oasis:names:tc:opendocument:xmlns:table:1.0'],
+ ['element', 'xmlns:draw', 'urn:oasis:names:tc:opendocument:xmlns:drawing:1.0'],
+ ['element', 'xmlns:fo', 'urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0'],
+ ['element', 'xmlns:xlink', 'http://www.w3.org/1999/xlink'],
+ ['element', 'xmlns:dc', 'http://purl.org/dc/elements/1.1/'],
+ ['element', 'xmlns:meta', 'urn:oasis:names:tc:opendocument:xmlns:meta:1.0'],
+ ['element', 'xmlns:number', 'urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0'],
+ ['element', 'xmlns:svg', 'urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0'],
+ ['element', 'xmlns:chart', 'urn:oasis:names:tc:opendocument:xmlns:chart:1.0'],
+ ['element', 'xmlns:dr3d', 'urn:oasis:names:tc:opendocument:xmlns:dr3d:1.0'],
+ ['element', 'xmlns:math', 'http://www.w3.org/1998/Math/MathML'],
+ ['element', 'xmlns:form', 'urn:oasis:names:tc:opendocument:xmlns:form:1.0'],
+ ['element', 'xmlns:script', 'urn:oasis:names:tc:opendocument:xmlns:script:1.0'],
+ ['element', 'xmlns:ooo', 'http://openoffice.org/2004/office'],
+ ['element', 'xmlns:ooow', 'http://openoffice.org/2004/writer'],
+ ['element', 'xmlns:oooc', 'http://openoffice.org/2004/calc'],
+ ['element', 'xmlns:dom', 'http://www.w3.org/2001/xml-events'],
+ ['element', 'xmlns:xforms', 'http://www.w3.org/2002/xforms'],
+ ['element', 'xmlns:xsd', 'http://www.w3.org/2001/XMLSchema'],
+ ['element', 'xmlns:xsi', 'http://www.w3.org/2001/XMLSchema-instance'],
+ ['element', 'office:version', '1.0'],
+ ['tagline', 'office:scripts'],
+ ['tag', 'office:font-face-decls',
+ ['tagline', 'style:font-face',
+ ['element', 'style:name', 'DejaVu Sans'],
+ ['element', 'svg:font-family', '&apos;DejaVu Sans&apos;'],
+ ['element', 'style:font-pitch', 'variable']],
+ ['tagline', 'style:font-face',
+ ['element', 'style:name', 'Nimbus Sans L'],
+ ['element', 'svg:font-family', '&apos;Nimbus Sans L&apos;'],
+ ['element', 'style:font-family-generic', 'swiss'],
+ ['element', 'style:font-pitch', 'variable']]],
+
+ # Automatic Styles
+ self.automatic_styles,
+
+ ['tag', 'office:body',
+ self.sheetdata]] # Sheets are generated from the CalcSheet class
+
+ # Generate content.xml XML data
+ xmldoc = XML()
+ self.lines = xmldoc.convert(self.data)
+ self.filedata = '\n'.join(self.lines)
+ # Return generated data
+ return self.filedata
+
+ def _ods_manifest(self):
+ "Generate ods manifest.xml data"
+ self.data = ['tag', 'manifest:manifest',
+ ['element', 'xmlns:manifest', 'urn:oasis:names:tc:opendocument:xmlns:manifest:1.0'],
+ ['tagline', 'manifest:file-entry',
+ ['element', 'manifest:media-type', 'application/vnd.oasis.opendocument.spreadsheet'],
+ ['element', 'manifest:full-path', '/']],
+ ['tagline', 'manifest:file-entry',
+ ['element', 'manifest:media-type', 'application/vnd.sun.xml.ui.configuration'],
+ ['element', 'manifest:full-path', 'Configurations2/']],
+ ['tagline', 'manifest:file-entry',
+ ['element', 'manifest:media-type', ''],
+ ['element', 'manifest:full-path', 'Configurations2/accelerator/']],
+ ['tagline', 'manifest:file-entry',
+ ['element', 'manifest:media-type', ''],
+ ['element', 'manifest:full-path', 'Configurations2/accelerator/current.xml']],
+ ['tagline', 'manifest:file-entry',
+ ['element', 'manifest:media-type', 'text/xml'],
+ ['element', 'manifest:full-path', 'content.xml']],
+ ['tagline', 'manifest:file-entry',
+ ['element', 'manifest:media-type', 'text/xml'],
+ ['element', 'manifest:full-path', 'styles.xml']],
+ ['tagline', 'manifest:file-entry',
+ ['element', 'manifest:media-type', 'text/xml'],
+ ['element', 'manifest:full-path', 'meta.xml']],
+ ['tagline', 'manifest:file-entry',
+ ['element', 'manifest:media-type', 'text/xml'],
+ ['element', 'manifest:full-path', 'settings.xml']]]
+
+ # Add additional files to manifest list
+ for fileset in self.manifest_files:
+ (filename, filetype, newname) = fileset
+ addfile = ['tagline', 'manifest:file-entry',
+ ['element', 'manifest:media-type', filetype],
+ ['element', 'manifest:full-path', newname]]
+ self.data.append(addfile)
+
+ # Generate content.xml XML data
+ xmldoc = XML()
+ self.lines = xmldoc.convert(self.data)
+ self.filedata = '\n'.join(self.lines)
+ # Return generated data
+ return self.filedata
+
+ def _ods_settings(self):
+ "Generate ods settings.xml data"
+ self.data = ['tag', 'office:document-settings',
+ ['element', 'xmlns:office', 'urn:oasis:names:tc:opendocument:xmlns:office:1.0'],
+ ['element', 'xmlns:xlink', 'http://www.w3.org/1999/xlink'],
+ ['element', 'xmlns:config', 'urn:oasis:names:tc:opendocument:xmlns:config:1.0'],
+ ['element', 'xmlns:ooo', 'http://openoffice.org/2004/office'],
+ ['element', 'office:version', '1.0'],
+ ['tag', 'office:settings',
+ ['tag', 'config:config-item-set',
+ ['element', 'config:name', 'ooo:view-settings'],
+ ['tag', 'config:config-item',
+ ['element', 'config:name', 'VisibleAreaTop'],
+ ['element', 'config:type', 'int'],
+ ['data', '0']],
+ ['tag', 'config:config-item',
+ ['element', 'config:name', 'VisibleAreaLeft'],
+ ['element', 'config:type', 'int'],
+ ['data', '0']],
+ ['tag', 'config:config-item',
+ ['element', 'config:name', 'VisibleAreaWidth'],
+ ['element', 'config:type', 'int'],
+ ['data', '6774']],
+ ['tag', 'config:config-item',
+ ['element', 'config:name', 'VisibleAreaHeight'],
+ ['element', 'config:type', 'int'],
+ ['data', '2389']],
+ ['tag', 'config:config-item-map-indexed',
+ ['element', 'config:name', 'Views'],
+ ['tag', 'config:config-item-map-entry',
+ ['tag', 'config:config-item',
+ ['element', 'config:name', 'ViewId'],
+ ['element', 'config:type', 'string'],
+ ['data', 'View1']],
+ ['tag', 'config:config-item-map-named',
+ ['element', 'config:name', 'Tables'],
+ ['tag', 'config:config-item-map-entry',
+ ['element', 'config:name', 'Sheet1'],
+ ['tag', 'config:config-item',
+ ['element', 'config:name', 'CursorPositionX'], # Cursor Position A
+ ['element', 'config:type', 'int'],
+ ['data', '0']],
+ ['tag', 'config:config-item',
+ ['element', 'config:name', 'CursorPositionY'], # Cursor Position 1
+ ['element', 'config:type', 'int'],
+ ['data', '0']],
+ ['tag', 'config:config-item',
+ ['element', 'config:name', 'HorizontalSplitMode'],
+ ['element', 'config:type', 'short'],
+ ['data', '0']],
+ ['tag', 'config:config-item',
+ ['element', 'config:name', 'VerticalSplitMode'],
+ ['element', 'config:type', 'short'],
+ ['data', '0']],
+ ['tag', 'config:config-item',
+ ['element', 'config:name', 'HorizontalSplitPosition'],
+ ['element', 'config:type', 'int'],
+ ['data', '0']],
+ ['tag', 'config:config-item',
+ ['element', 'config:name', 'VerticalSplitPosition'],
+ ['element', 'config:type', 'int'],
+ ['data', '0']],
+ ['tag', 'config:config-item',
+ ['element', 'config:name', 'ActiveSplitRange'],
+ ['element', 'config:type', 'short'],
+ ['data', '2']],
+ ['tag', 'config:config-item',
+ ['element', 'config:name', 'PositionLeft'],
+ ['element', 'config:type', 'int'],
+ ['data', '0']],
+ ['tag', 'config:config-item',
+ ['element', 'config:name', 'PositionRight'],
+ ['element', 'config:type', 'int'],
+ ['data', '0']],
+ ['tag', 'config:config-item',
+ ['element', 'config:name', 'PositionTop'],
+ ['element', 'config:type', 'int'],
+ ['data', '0']],
+ ['tag', 'config:config-item',
+ ['element', 'config:name', 'PositionBottom'],
+ ['element', 'config:type', 'int'],
+ ['data', '0']]]],
+ ['tag', 'config:config-item',
+ ['element', 'config:name', 'ActiveTable'],
+ ['element', 'config:type', 'string'],
+ ['data', 'Sheet1']],
+ ['tag', 'config:config-item',
+ ['element', 'config:name', 'HorizontalScrollbarWidth'],
+ ['element', 'config:type', 'int'],
+ ['data', '270']],
+ ['tag', 'config:config-item',
+ ['element', 'config:name', 'ZoomType'],
+ ['element', 'config:type', 'short'],
+ ['data', '0']],
+ ['tag', 'config:config-item',
+ ['element', 'config:name', 'ZoomValue'],
+ ['element', 'config:type', 'int'],
+ ['data', '100']],
+ ['tag', 'config:config-item',
+ ['element', 'config:name', 'PageViewZoomValue'],
+ ['element', 'config:type', 'int'],
+ ['data', '60']],
+ ['tag', 'config:config-item',
+ ['element', 'config:name', 'ShowPageBreakPreview'],
+ ['element', 'config:type', 'boolean'],
+ ['data', 'false']],
+ ['tag', 'config:config-item',
+ ['element', 'config:name', 'ShowZeroValues'],
+ ['element', 'config:type', 'boolean'],
+ ['data', 'true']],
+ ['tag', 'config:config-item',
+ ['element', 'config:name', 'ShowNotes'],
+ ['element', 'config:type', 'boolean'],
+ ['data', 'true']],
+ ['tag', 'config:config-item',
+ ['element', 'config:name', 'ShowGrid'],
+ ['element', 'config:type', 'boolean'],
+ ['data', 'true']],
+ ['tag', 'config:config-item',
+ ['element', 'config:name', 'GridColor'],
+ ['element', 'config:type', 'long'],
+ ['data', '12632256']],
+ ['tag', 'config:config-item',
+ ['element', 'config:name', 'ShowPageBreaks'],
+ ['element', 'config:type', 'boolean'],
+ ['data', 'true']],
+ ['tag', 'config:config-item',
+ ['element', 'config:name', 'HasColumnRowHeaders'],
+ ['element', 'config:type', 'boolean'],
+ ['data', 'true']],
+ ['tag', 'config:config-item',
+ ['element', 'config:name', 'HasSheetTabs'],
+ ['element', 'config:type', 'boolean'],
+ ['data', 'true']],
+ ['tag', 'config:config-item',
+ ['element', 'config:name', 'IsOutlineSymbolsSet'],
+ ['element', 'config:type', 'boolean'],
+ ['data', 'true']],
+ ['tag', 'config:config-item',
+ ['element', 'config:name', 'IsSnapToRaster'],
+ ['element', 'config:type', 'boolean'],
+ ['data', 'false']],
+ ['tag', 'config:config-item',
+ ['element', 'config:name', 'RasterIsVisible'],
+ ['element', 'config:type', 'boolean'],
+ ['data', 'false']],
+ ['tag', 'config:config-item',
+ ['element', 'config:name', 'RasterResolutionX'],
+ ['element', 'config:type', 'int'],
+ ['data', '1270']],
+ ['tag', 'config:config-item',
+ ['element', 'config:name', 'RasterResolutionY'],
+ ['element', 'config:type', 'int'],
+ ['data', '1270']],
+ ['tag', 'config:config-item',
+ ['element', 'config:name', 'RasterSubdivisionX'],
+ ['element', 'config:type', 'int'],
+ ['data', '1']],
+ ['tag', 'config:config-item',
+ ['element', 'config:name', 'RasterSubdivisionY'],
+ ['element', 'config:type', 'int'],
+ ['data', '1']],
+ ['tag', 'config:config-item',
+ ['element', 'config:name', 'IsRasterAxisSynchronized'],
+ ['element', 'config:type', 'boolean'],
+ ['data', 'true']]]]],
+ ['tag', 'config:config-item-set',
+ ['element', 'config:name', 'ooo:configuration-settings'],
+ ['tag', 'config:config-item',
+ ['element', 'config:name', 'ShowZeroValues'],
+ ['element', 'config:type', 'boolean'],
+ ['data', 'true']],
+ ['tag', 'config:config-item',
+ ['element', 'config:name', 'ShowNotes'],
+ ['element', 'config:type', 'boolean'],
+ ['data', 'true']],
+ ['tag', 'config:config-item',
+ ['element', 'config:name', 'ShowGrid'],
+ ['element', 'config:type', 'boolean'],
+ ['data', 'true']],
+ ['tag', 'config:config-item',
+ ['element', 'config:name', 'GridColor'],
+ ['element', 'config:type', 'long'],
+ ['data', '12632256']],
+ ['tag', 'config:config-item',
+ ['element', 'config:name', 'ShowPageBreaks'],
+ ['element', 'config:type', 'boolean'],
+ ['data', 'true']],
+ ['tag', 'config:config-item',
+ ['element', 'config:name', 'LinkUpdateMode'],
+ ['element', 'config:type', 'short'],
+ ['data', '3']],
+ ['tag', 'config:config-item',
+ ['element', 'config:name', 'HasColumnRowHeaders'],
+ ['element', 'config:type', 'boolean'],
+ ['data', 'true']],
+ ['tag', 'config:config-item',
+ ['element', 'config:name', 'HasSheetTabs'],
+ ['element', 'config:type', 'boolean'],
+ ['data', 'true']],
+ ['tag', 'config:config-item',
+ ['element', 'config:name', 'IsOutlineSymbolsSet'],
+ ['element', 'config:type', 'boolean'],
+ ['data', 'true']],
+ ['tag', 'config:config-item',
+ ['element', 'config:name', 'IsSnapToRaster'],
+ ['element', 'config:type', 'boolean'],
+ ['data', 'false']],
+ ['tag', 'config:config-item',
+ ['element', 'config:name', 'RasterIsVisible'],
+ ['element', 'config:type', 'boolean'],
+ ['data', 'false']],
+ ['tag', 'config:config-item',
+ ['element', 'config:name', 'RasterResolutionX'],
+ ['element', 'config:type', 'int'],
+ ['data', '1270']],
+ ['tag', 'config:config-item',
+ ['element', 'config:name', 'RasterResolutionY'],
+ ['element', 'config:type', 'int'],
+ ['data', '1270']],
+ ['tag', 'config:config-item',
+ ['element', 'config:name', 'RasterSubdivisionX'],
+ ['element', 'config:type', 'int'],
+ ['data', '1']],
+ ['tag', 'config:config-item',
+ ['element', 'config:name', 'RasterSubdivisionY'],
+ ['element', 'config:type', 'int'],
+ ['data', '1']],
+ ['tag', 'config:config-item',
+ ['element', 'config:name', 'IsRasterAxisSynchronized'],
+ ['element', 'config:type', 'boolean'],
+ ['data', 'true']],
+ ['tag', 'config:config-item',
+ ['element', 'config:name', 'AutoCalculate'],
+ ['element', 'config:type', 'boolean'],
+ ['data', 'true']],
+ ['tag', 'config:config-item',
+ ['element', 'config:name', 'PrinterName'],
+ ['element', 'config:type', 'string'],
+ ['data', 'Generic Printer']],
+ ['tag', 'config:config-item',
+ ['element', 'config:name', 'PrinterSetup'],
+ ['element', 'config:type', 'base64Binary'],
+ ['data', 'YgH+/0dlbmVyaWMgUHJpbnRlcgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAU0dFTlBSVAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAWAAMAqAAAAAAA//8FAFZUAAAkbQAASm9iRGF0YSAxCnByaW50ZXI9R2VuZXJpYyBQcmludGVyCm9yaWVudGF0aW9uPVBvcnRyYWl0CmNvcGllcz0xCnNjYWxlPTEwMAptYXJnaW5kYWp1c3RtZW50PTAsMCwwLDAKY29sb3JkZXB0aD0yNApwc2xldmVsPTAKY29sb3JkZXZpY2U9MApQUERDb250ZXhEYXRhClBhZ2VTaXplOkxldHRlcgAA']],
+ ['tag', 'config:config-item',
+ ['element', 'config:name', 'ApplyUserData'],
+ ['element', 'config:type', 'boolean'],
+ ['data', 'true']],
+ ['tag', 'config:config-item',
+ ['element', 'config:name', 'CharacterCompressionType'],
+ ['element', 'config:type', 'short'],
+ ['data', '0']],
+ ['tag', 'config:config-item',
+ ['element', 'config:name', 'IsKernAsianPunctuation'],
+ ['element', 'config:type', 'boolean'],
+ ['data', 'false']],
+ ['tag', 'config:config-item',
+ ['element', 'config:name', 'SaveVersionOnClose'],
+ ['element', 'config:type', 'boolean'],
+ ['data', 'false']],
+ ['tag', 'config:config-item',
+ ['element', 'config:name', 'UpdateFromTemplate'],
+ ['element', 'config:type', 'boolean'],
+ ['data', 'false']],
+ ['tag', 'config:config-item',
+ ['element', 'config:name', 'AllowPrintJobCancel'],
+ ['element', 'config:type', 'boolean'],
+ ['data', 'true']],
+ ['tag', 'config:config-item',
+ ['element', 'config:name', 'LoadReadonly'],
+ ['element', 'config:type', 'boolean'],
+ ['data', 'false']]]]]
+
+ # Generate content.xml XML data
+ xmldoc = XML()
+ self.lines = xmldoc.convert(self.data)
+ self.filedata = '\n'.join(self.lines)
+ # Return generated data
+ return self.filedata
+
+ def _ods_styles(self):
+ "Generate ods styles.xml data"
+ self.data = ['tag', 'office:document-styles',
+ ['element', 'xmlns:office', 'urn:oasis:names:tc:opendocument:xmlns:office:1.0'],
+ ['element', 'xmlns:style', 'urn:oasis:names:tc:opendocument:xmlns:style:1.0'],
+ ['element', 'xmlns:text', 'urn:oasis:names:tc:opendocument:xmlns:text:1.0'],
+ ['element', 'xmlns:table', 'urn:oasis:names:tc:opendocument:xmlns:table:1.0'],
+ ['element', 'xmlns:draw', 'urn:oasis:names:tc:opendocument:xmlns:drawing:1.0'],
+ ['element', 'xmlns:fo', 'urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0'],
+ ['element', 'xmlns:xlink', 'http://www.w3.org/1999/xlink'],
+ ['element', 'xmlns:dc', 'http://purl.org/dc/elements/1.1/'],
+ ['element', 'xmlns:meta', 'urn:oasis:names:tc:opendocument:xmlns:meta:1.0'],
+ ['element', 'xmlns:number', 'urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0'],
+ ['element', 'xmlns:svg', 'urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0'],
+ ['element', 'xmlns:chart', 'urn:oasis:names:tc:opendocument:xmlns:chart:1.0'],
+ ['element', 'xmlns:dr3d', 'urn:oasis:names:tc:opendocument:xmlns:dr3d:1.0'],
+ ['element', 'xmlns:math', 'http://www.w3.org/1998/Math/MathML'],
+ ['element', 'xmlns:form', 'urn:oasis:names:tc:opendocument:xmlns:form:1.0'],
+ ['element', 'xmlns:script', 'urn:oasis:names:tc:opendocument:xmlns:script:1.0'],
+ ['element', 'xmlns:ooo', 'http://openoffice.org/2004/office'],
+ ['element', 'xmlns:ooow', 'http://openoffice.org/2004/writer'],
+ ['element', 'xmlns:oooc', 'http://openoffice.org/2004/calc'],
+ ['element', 'xmlns:dom', 'http://www.w3.org/2001/xml-events'],
+ ['element', 'office:version', '1.0'],
+ ['tag', 'office:font-face-decls',
+ ['tagline', 'style:font-face',
+ ['element', 'style:name', 'DejaVu Sans'],
+ ['element', 'svg:font-family', '&apos;DejaVu Sans&apos;'],
+ ['element', 'style:font-pitch', 'variable']],
+ ['tagline', 'style:font-face',
+ ['element', 'style:name', 'Nimbus Sans L'],
+ ['element', 'svg:font-family', '&apos;Nimbus Sans L&apos;'],
+ ['element', 'style:font-family-generic', 'swiss'],
+ ['element', 'style:font-pitch', 'variable']]],
+ ['tag', 'office:styles',
+ ['tag', 'style:default-style',
+ ['element', 'style:family', 'table-cell'],
+ ['tagline', 'style:table-cell-properties',
+ ['element', 'style:decimal-places', '2']],
+ ['tagline', 'style:paragraph-properties',
+ ['element', 'style:tab-stop-distance', '0.5in']],
+ ['tagline', 'style:text-properties',
+ ['element', 'style:font-name', 'Nimbus Sans L'],
+ ['element', 'fo:language', 'en'],
+ ['element', 'fo:country', 'US'],
+ ['element', 'style:font-name-asian', 'DejaVu Sans'],
+ ['element', 'style:language-asian', 'none'],
+ ['element', 'style:country-asian', 'none'],
+ ['element', 'style:font-name-complex', 'DejaVu Sans'],
+ ['element', 'style:language-complex', 'none'],
+ ['element', 'style:country-complex', 'none']]],
+ ['tag', 'number:number-style',
+ ['element', 'style:name', 'N0'],
+ ['tagline', 'number:number',
+ ['element', 'number:min-integer-digits', '1']]],
+ ['tag', 'number:currency-style',
+ ['element', 'style:name', 'N104P0'],
+ ['element', 'style:volatile', 'true'],
+ ['tag', 'number:currency-symbol',
+ ['element', 'number:language', 'en'],
+ ['element', 'number:country', 'US'],
+ ['data', '$']],
+ ['tagline', 'number:number',
+ ['element', 'number:decimal-places', '2'],
+ ['element', 'number:min-integer-digits', '1'],
+ ['element', 'number:grouping', 'true']]],
+ ['tag', 'number:currency-style',
+ ['element', 'style:name', 'N104'],
+ ['tagline', 'style:text-properties',
+ ['element', 'fo:color', '#ff0000']],
+ ['tag', 'number:text',
+ ['data', '-']],
+ ['tag', 'number:currency-symbol',
+ ['element', 'number:language', 'en'],
+ ['element', 'number:country', 'US'],
+ ['data', '$']],
+ ['tagline', 'number:number',
+ ['element', 'number:decimal-places', '2'],
+ ['element', 'number:min-integer-digits', '1'],
+ ['element', 'number:grouping', 'true']],
+ ['tagline', 'style:map',
+ ['element', 'style:condition', 'value()&gt;=0'],
+ ['element', 'style:apply-style-name', 'N104P0']]],
+ ['tagline', 'style:style',
+ ['element', 'style:name', 'Default'],
+ ['element', 'style:family', 'table-cell']],
+ ['tag', 'style:style',
+ ['element', 'style:name', 'Result'],
+ ['element', 'style:family', 'table-cell'],
+ ['element', 'style:parent-style-name', 'Default'],
+ ['tagline', 'style:text-properties',
+ ['element', 'fo:font-style', 'italic'],
+ ['element', 'style:text-underline-style', 'solid'],
+ ['element', 'style:text-underline-width', 'auto'],
+ ['element', 'style:text-underline-color', 'font-color'],
+ ['element', 'fo:font-weight', 'bold']]],
+ ['tagline', 'style:style',
+ ['element', 'style:name', 'Result2'],
+ ['element', 'style:family', 'table-cell'],
+ ['element', 'style:parent-style-name', 'Result'],
+ ['element', 'style:data-style-name', 'N104']],
+ ['tag', 'style:style',
+ ['element', 'style:name', 'Heading'],
+ ['element', 'style:family', 'table-cell'],
+ ['element', 'style:parent-style-name', 'Default'],
+ ['tagline', 'style:table-cell-properties',
+ ['element', 'style:text-align-source', 'fix'],
+ ['element', 'style:repeat-content', 'false']],
+ ['tagline', 'style:paragraph-properties',
+ ['element', 'fo:text-align', 'center']],
+ ['tagline', 'style:text-properties',
+ ['element', 'fo:font-size', '16pt'],
+ ['element', 'fo:font-style', 'italic'],
+ ['element', 'fo:font-weight', 'bold']]],
+ ['tag', 'style:style',
+ ['element', 'style:name', 'Heading1'],
+ ['element', 'style:family', 'table-cell'],
+ ['element', 'style:parent-style-name', 'Heading'],
+ ['tagline', 'style:table-cell-properties',
+ ['element', 'style:rotation-angle', '90']]]],
+ ['tag', 'office:automatic-styles',
+ ['tag', 'style:page-layout',
+ ['element', 'style:name', 'pm1'],
+ ['tagline', 'style:page-layout-properties',
+ ['element', 'style:writing-mode', 'lr-tb']],
+ ['tag', 'style:header-style',
+ ['tagline', 'style:header-footer-properties',
+ ['element', 'fo:min-height', '0.2957in'],
+ ['element', 'fo:margin-left', '0in'],
+ ['element', 'fo:margin-right', '0in'],
+ ['element', 'fo:margin-bottom', '0.0984in']]],
+ ['tag', 'style:footer-style',
+ ['tagline', 'style:header-footer-properties',
+ ['element', 'fo:min-height', '0.2957in'],
+ ['element', 'fo:margin-left', '0in'],
+ ['element', 'fo:margin-right', '0in'],
+ ['element', 'fo:margin-top', '0.0984in']]]],
+ ['tag', 'style:page-layout',
+ ['element', 'style:name', 'pm2'],
+ ['tagline', 'style:page-layout-properties',
+ ['element', 'style:writing-mode', 'lr-tb']],
+ ['tag', 'style:header-style',
+ ['tag', 'style:header-footer-properties',
+ ['element', 'fo:min-height', '0.2957in'],
+ ['element', 'fo:margin-left', '0in'],
+ ['element', 'fo:margin-right', '0in'],
+ ['element', 'fo:margin-bottom', '0.0984in'],
+ ['element', 'fo:border', '0.0346in solid #000000'],
+ ['element', 'fo:padding', '0.0071in'],
+ ['element', 'fo:background-color', '#c0c0c0'],
+ ['tagline', 'style:background-image']]],
+ ['tag', 'style:footer-style',
+ ['tag', 'style:header-footer-properties',
+ ['element', 'fo:min-height', '0.2957in'],
+ ['element', 'fo:margin-left', '0in'],
+ ['element', 'fo:margin-right', '0in'],
+ ['element', 'fo:margin-top', '0.0984in'],
+ ['element', 'fo:border', '0.0346in solid #000000'],
+ ['element', 'fo:padding', '0.0071in'],
+ ['element', 'fo:background-color', '#c0c0c0'],
+ ['tagline', 'style:background-image']]]]],
+ ['tag', 'office:master-styles',
+ ['tag', 'style:master-page',
+ ['element', 'style:name', 'Default'],
+ ['element', 'style:page-layout-name', 'pm1'],
+ ['tag', 'style:header',
+ ['tag', 'text:p',
+ ['data', '<text:sheet-name>???</text:sheet-name>']]],
+ ['tagline', 'style:header-left',
+ ['element', 'style:display', 'false']],
+ ['tag', 'style:footer',
+ ['tag', 'text:p',
+ ['data', 'Page <text:page-number>1</text:page-number>']]],
+ ['tagline', 'style:footer-left',
+ ['element', 'style:display', 'false']]],
+ ['tag', 'style:master-page',
+ ['element', 'style:name', 'Report'],
+ ['element', 'style:page-layout-name', 'pm2'],
+ ['tag', 'style:header',
+ ['tag', 'style:region-left',
+ ['tag', 'text:p',
+ ['data', '<text:sheet-name>???</text:sheet-name> (<text:title>???</text:title>)']]],
+ ['tag', 'style:region-right',
+ ['tag', 'text:p',
+ ['data', '<text:date style:data-style-name="N2" text:date-value="2006-09-29">09/29/2006</text:date>, <text:time>13:02:56</text:time>']]]],
+ ['tagline', 'style:header-left',
+ ['element', 'style:display', 'false']],
+ ['tag', 'style:footer',
+ ['tag', 'text:p',
+ ['data', 'Page <text:page-number>1</text:page-number> / <text:page-count>99</text:page-count>']]],
+ ['tagline', 'style:footer-left',
+ ['element', 'style:display', 'false']]]]]
+
+ # Generate content.xml XML data
+ xmldoc = XML()
+ self.lines = xmldoc.convert(self.data)
+ self.filedata = '\n'.join(self.lines)
+ # Return generated data
+ return self.filedata
+
+
+class Writer(object):
+ "Writer Class - Used to create OpenDocument Format Writer Documents."
+
+ def __init__(self):
+ "Initialize ooolib Writer instance"
+ # Default to no debugging
+ self.debug = False
+ self.meta = Meta('odt')
+
+ def set_meta(self, metaname, value):
+ "Set meta data in your document."
+ self.meta.set_meta(metaname, value)
+
+ def save(self, filename):
+ """Save .odt document
+
+ The save function saves the current .odt document.
+ """
+ if self.debug: print("Writing %s" % filename)
+ self.savefile = zipfile.ZipFile(filename, "w")
+ if self.debug: print(" meta.xml")
+ self._zip_insert(self.savefile, "meta.xml", self.meta.get_meta())
+ if self.debug: print(" mimetype")
+ self._zip_insert(self.savefile, "mimetype", "application/vnd.oasis.opendocument.text")
+ if self.debug: print(" META-INF/manifest.xml")
+ self._zip_insert(self.savefile, "META-INF/manifest.xml", self._odt_manifest())
+ if self.debug: print(" content.xml")
+ self._zip_insert(self.savefile, "content.xml", self._odt_content())
+ if self.debug: print(" settings.xml")
+ # self._zip_insert(self.savefile, "settings.xml", self._odt_settings())
+ if self.debug: print(" styles.xml")
+ # self._zip_insert(self.savefile, "styles.xml", self._odt_styles())
+
+ # We need to close the file now that we are done creating it.
+ self.savefile.close()
+
+ def _zip_insert(self, fileobj, filename, data):
+ now = time.localtime(time.time())[:6]
+ info = zipfile.ZipInfo(filename)
+ info.date_time = now
+ info.compress_type = zipfile.ZIP_DEFLATED
+ fileobj.writestr(info, data)
+
+ def _odt_manifest(self):
+ "Generate odt manifest.xml data"
+
+ self.data = ['tag', 'manifest:manifest',
+ ['element', 'xmlns:manifest', 'urn:oasis:names:tc:opendocument:xmlns:manifest:1.0'],
+ ['tagline', 'manifest:file-entry',
+ ['element', 'manifest:media-type', 'application/vnd.oasis.opendocument.text'],
+ ['element', 'manifest:full-path', '/']],
+ ['tagline', 'manifest:file-entry',
+ ['element', 'manifest:media-type', 'text/xml'],
+ ['element', 'manifest:full-path', 'content.xml']],
+ ['tagline', 'manifest:file-entry',
+ ['element', 'manifest:media-type', 'text/xml'],
+ ['element', 'manifest:full-path', 'styles.xml']],
+ ['tagline', 'manifest:file-entry',
+ ['element', 'manifest:media-type', 'text/xml'],
+ ['element', 'manifest:full-path', 'meta.xml']],
+ ['tagline', 'manifest:file-entry',
+ ['element', 'manifest:media-type', 'text/xml'],
+ ['element', 'manifest:full-path', 'settings.xml']]]
+
+ # Generate content.xml XML data
+ xmldoc = XML()
+ self.lines = xmldoc.convert(self.data)
+ self.lines.insert(1, '<!DOCTYPE manifest:manifest PUBLIC "-//OpenOffice.org//DTD Manifest 1.0//EN" "Manifest.dtd">')
+ self.filedata = '\n'.join(self.lines)
+ # Return generated data
+ return self.filedata
+
+ def _odt_content(self):
+ "Generate odt content.xml data"
+
+ self.data = ['tag', 'office:document-content',
+ ['element', 'xmlns:office', 'urn:oasis:names:tc:opendocument:xmlns:office:1.0'],
+ ['element', 'xmlns:style', 'urn:oasis:names:tc:opendocument:xmlns:style:1.0'],
+ ['element', 'xmlns:text', 'urn:oasis:names:tc:opendocument:xmlns:text:1.0'],
+ ['element', 'xmlns:table', 'urn:oasis:names:tc:opendocument:xmlns:table:1.0'],
+ ['element', 'xmlns:draw', 'urn:oasis:names:tc:opendocument:xmlns:drawing:1.0'],
+ ['element', 'xmlns:fo', 'urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0'],
+ ['element', 'xmlns:xlink', 'http://www.w3.org/1999/xlink'],
+ ['element', 'xmlns:dc', 'http://purl.org/dc/elements/1.1/'],
+ ['element', 'xmlns:meta', 'urn:oasis:names:tc:opendocument:xmlns:meta:1.0'],
+ ['element', 'xmlns:number', 'urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0'],
+ ['element', 'xmlns:svg', 'urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0'],
+ ['element', 'xmlns:chart', 'urn:oasis:names:tc:opendocument:xmlns:chart:1.0'],
+ ['element', 'xmlns:dr3d', 'urn:oasis:names:tc:opendocument:xmlns:dr3d:1.0'],
+ ['element', 'xmlns:math', 'http://www.w3.org/1998/Math/MathML'],
+ ['element', 'xmlns:form', 'urn:oasis:names:tc:opendocument:xmlns:form:1.0'],
+ ['element', 'xmlns:script', 'urn:oasis:names:tc:opendocument:xmlns:script:1.0'],
+ ['element', 'xmlns:ooo', 'http://openoffice.org/2004/office'],
+ ['element', 'xmlns:ooow', 'http://openoffice.org/2004/writer'],
+ ['element', 'xmlns:oooc', 'http://openoffice.org/2004/calc'],
+ ['element', 'xmlns:dom', 'http://www.w3.org/2001/xml-events'],
+ ['element', 'xmlns:xforms', 'http://www.w3.org/2002/xforms'],
+ ['element', 'xmlns:xsd', 'http://www.w3.org/2001/XMLSchema'],
+ ['element', 'xmlns:xsi', 'http://www.w3.org/2001/XMLSchema-instance'],
+ ['element', 'office:version', '1.0'],
+ ['tagline', 'office:scripts'],
+ ['tag', 'office:font-face-decls',
+ ['tagline', 'style:font-face',
+ ['element', 'style:name', 'DejaVu Sans'],
+ ['element', 'svg:font-family', '&apos;DejaVu Sans&apos;'],
+ ['element', 'style:font-pitch', 'variable']],
['tagline', 'style:font-face',
- ['element', 'style:name', 'Nimbus Roman No9 L'],
- ['element', 'svg:font-family', '&apos;Nimbus Roman No9 L&apos;'],
- ['element', 'style:font-family-generic', 'roman'],
- ['element', 'style:font-pitch', 'variable']],
- ['tagline', 'style:font-face',
- ['element', 'style:name', 'Nimbus Sans L'],
- ['element', 'svg:font-family', '&apos;Nimbus Sans L&apos;'],
- ['element', 'style:font-family-generic', 'swiss'],
- ['element', 'style:font-pitch', 'variable']]],
- ['tagline', 'office:automatic-styles'],
- ['tag', 'office:body',
- ['tag', 'office:text',
- ['tagline', 'office:forms',
- ['element', 'form:automatic-focus', 'false'],
+ ['element', 'style:name', 'Nimbus Roman No9 L'],
+ ['element', 'svg:font-family', '&apos;Nimbus Roman No9 L&apos;'],
+ ['element', 'style:font-family-generic', 'roman'],
+ ['element', 'style:font-pitch', 'variable']],
+ ['tagline', 'style:font-face',
+ ['element', 'style:name', 'Nimbus Sans L'],
+ ['element', 'svg:font-family', '&apos;Nimbus Sans L&apos;'],
+ ['element', 'style:font-family-generic', 'swiss'],
+ ['element', 'style:font-pitch', 'variable']]],
+ ['tagline', 'office:automatic-styles'],
+ ['tag', 'office:body',
+ ['tag', 'office:text',
+ ['tagline', 'office:forms',
+ ['element', 'form:automatic-focus', 'false'],
['element', 'form:apply-design-mode', 'false']],
- ['tag', 'text:sequence-decls',
- ['tagline', 'text:sequence-decl',
- ['element', 'text:display-outline-level', '0'],
- ['element', 'text:name', 'Illustration']],
- ['tagline', 'text:sequence-decl',
- ['element', 'text:display-outline-level', '0'],
+ ['tag', 'text:sequence-decls',
+ ['tagline', 'text:sequence-decl',
+ ['element', 'text:display-outline-level', '0'],
+ ['element', 'text:name', 'Illustration']],
+ ['tagline', 'text:sequence-decl',
+ ['element', 'text:display-outline-level', '0'],
['element', 'text:name', 'Table']],
- ['tagline', 'text:sequence-decl',
- ['element', 'text:display-outline-level', '0'],
- ['element', 'text:name', 'Text']],
- ['tagline', 'text:sequence-decl',
- ['element', 'text:display-outline-level', '0'],
- ['element', 'text:name', 'Drawing']]],
- ['tagline', 'text:p',
- ['element', 'text:style-name', 'Standard']]]]]
-
- # Generate content.xml XML data
- xml = XML()
- self.lines = xml.convert(self.data)
- self.filedata = '\n'.join(self.lines)
- # Return generated data
- return self.filedata
-
-
+ ['tagline', 'text:sequence-decl',
+ ['element', 'text:display-outline-level', '0'],
+ ['element', 'text:name', 'Text']],
+ ['tagline', 'text:sequence-decl',
+ ['element', 'text:display-outline-level', '0'],
+ ['element', 'text:name', 'Drawing']]],
+ ['tagline', 'text:p',
+ ['element', 'text:style-name', 'Standard']]]]]
+
+ # Generate content.xml XML data
+ xmldoc = XML()
+ self.lines = xmldoc.convert(self.data)
+ self.filedata = '\n'.join(self.lines)
+ # Return generated data
+ return self.filedata
diff --git a/contrib/non-profit-audit-reports/readcsv.py b/contrib/non-profit-audit-reports/readcsv.py
index 67fc5663..932b4df9 100755
--- a/contrib/non-profit-audit-reports/readcsv.py
+++ b/contrib/non-profit-audit-reports/readcsv.py
@@ -1,4 +1,4 @@
-#!/usr/bin/python
+#!/usr/bin/env python3
# readcsv.py
# CSV reading technical study
#
@@ -23,9 +23,9 @@ import csv
dialects = csv.list_dialects()
for dialect in dialects:
- print 'dialect %s' % str(dialect)
+ print(f'dialect {dialect}')
-csvfile = open('tests/general-ledger.csv', 'rb')
+csvfile = open('test/general-ledger.csv', 'rb')
reader = csv.reader(csvfile, delimiter=',', quotechar='"')
for row in reader:
- print row
+ print(row)
diff --git a/python/demo.py b/python/demo.py
index b3fe1d04..2102afa5 100755
--- a/python/demo.py
+++ b/python/demo.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
import sys
from datetime import datetime
diff --git a/test/CheckBaselineTests.py b/test/CheckBaselineTests.py
index fa9fa2bc..9f52dd10 100755
--- a/test/CheckBaselineTests.py
+++ b/test/CheckBaselineTests.py
@@ -1,5 +1,4 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
+#!/usr/bin/env python3
import sys
import re
diff --git a/test/CheckComments.py b/test/CheckComments.py
index 446137b0..9bcfcbb3 100644
--- a/test/CheckComments.py
+++ b/test/CheckComments.py
@@ -1,20 +1,19 @@
+#!/usr/bin/env python3
+
import sys
import re
import os
+import argparse
-ok = 100.0
-if sys.argv[1] == '-l':
- ok = float(sys.argv[2])
- sys.argv = [sys.argv[0]] + sys.argv[3:]
-
-debug = False
-if sys.argv[1] == '-v':
- debug = True
- sys.argv = [sys.argv[0]] + sys.argv[2:]
+parser = argparse.ArgumentParser( prog='CheckComments')
+parser.add_argument('-v', '--debug', dest='debug', action='store_true')
+parser.add_argument('-l', '--limit', dest='ok', type=float, default=100.0)
+parser.add_argument('path', nargs='+', help='Path to source file to check comments')
+args = parser.parse_args()
errors = 0
-for path in sys.argv[1:]:
+for path in args.path:
other_depth = 0
brace_depth = 0
code_count = 0
@@ -26,6 +25,7 @@ for path in sys.argv[1:]:
ctor_dtor = False
linenum = 0
+ print(f'checking {path}')
fd = open(path, 'r')
for line in fd.readlines():
linenum += 1
@@ -48,48 +48,48 @@ for path in sys.argv[1:]:
match = re.search('(namespace|enum|class|struct|union)', line)
if match:
kind = match.group(1)
- if debug: print "kind =", kind
+ if args.debug: print("kind =", kind)
elif kind == "function":
match = re.search('(\S+)\(', line)
if match:
function_name = match.group(1)
long_comments[function_name] = comment_count
comment_count = 0
- if debug: print "name found %s" % function_name
+ if args.debug: print("name found %s" % function_name)
if re.search('{', line) and not re.search('@{', line):
if kind == "function":
brace_depth += 1
- if debug: print "brace_depth =", brace_depth
+ if args.debug: print("brace_depth =", brace_depth)
else:
other_depth += 1
kind = "function"
- if debug: print "other_depth =", other_depth
+ if args.debug: print("other_depth =", other_depth)
if re.search('}', line) and not re.search('@}', line):
if brace_depth > 0:
brace_depth -= 1
- if debug: print "brace_depth =", brace_depth
+ if args.debug: print("brace_depth =", brace_depth)
if brace_depth == 0:
- if debug: print "function done"
+ if args.debug: print("function done")
if function_name in long_comments:
comment_count += long_comments[function_name]
if code_count == 0:
- percent = ok
- print "%7s %4d/%4d %s:%d: %s" % \
+ percent = args.ok
+ print("%7s %4d/%4d %s:%d: %s" % \
("empty", comment_count, code_count,
os.path.basename(path), linenum,
- function_name)
+ function_name))
errors += 1
else:
percent = 100.0 * (float(comment_count) /
float(code_count))
- if percent < ok and not ctor_dtor:
- print "%6.0f%% %4d/%4d %s:%d: %s" % \
+ if percent < args.ok and not ctor_dtor:
+ print("%6.0f%% %4d/%4d %s:%d: %s" % \
(percent, comment_count, code_count,
os.path.basename(path), linenum,
- function_name)
+ function_name))
errors += 1
code_count = 0
comment_count = 0
@@ -98,7 +98,7 @@ for path in sys.argv[1:]:
ctor_dtor = False
else:
other_depth -= 1
- if debug: print "other_depth =", other_depth
+ if args.debug: print("other_depth =", other_depth)
if brace_depth > 0:
if re.search("TRACE_[CD]TOR", line):
diff --git a/test/CheckManpage.py b/test/CheckManpage.py
index 3da9d872..e519a00a 100755
--- a/test/CheckManpage.py
+++ b/test/CheckManpage.py
@@ -1,5 +1,4 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
+#!/usr/bin/env python3
import sys
import re
diff --git a/test/CheckOptions.py b/test/CheckOptions.py
index 3f08fb0d..faf1630e 100755
--- a/test/CheckOptions.py
+++ b/test/CheckOptions.py
@@ -1,5 +1,4 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
+#!/usr/bin/env python3
import re
import os
@@ -40,7 +39,7 @@ class CheckOptions (object):
for source_file in ['session', 'report']:
command.append(os.path.join(self.source, 'src', '%s.cc' % source_file))
try:
- output = subprocess.check_output(command).split('\n');
+ output = subprocess.check_output(command, universal_newlines=True).split('\n')
except subprocess.CalledProcessError:
output = ''
@@ -58,7 +57,7 @@ class CheckOptions (object):
def ledger_functions(self):
command = shlex.split('grep --no-filename fn_ %s' % (os.path.join(self.source, 'src', 'report.h')))
try:
- output = subprocess.check_output(command).split('\n');
+ output = subprocess.check_output(command, universal_newlines=True).split('\n');
except subprocess.CalledProcessError:
output = ''
@@ -74,7 +73,13 @@ class CheckOptions (object):
else:
options.remove(option)
known_alternates = self.find_alternates()
- self.unknown_options = {option for option in options if option not in known_alternates}
+ self.unknown_options = options - known_alternates
+ self.missing_options -= {
+ # The option --explicit is a no-op as of March 2020 and is
+ # therefore intentionally undocumented.
+ # For details see commit 43b07fbab3b4c144eca4a771524e59c531ffa074
+ 'explicit'
+ }
functions = self.find_functions(self.source_file)
for function in self.ledger_functions():
@@ -82,8 +87,8 @@ class CheckOptions (object):
self.missing_functions.add(function)
else:
functions.remove(function)
- known_functions = ['tag', 'has_tag', 'meta', 'has_meta']
- self.unknown_functions = {function for function in functions if function not in known_functions}
+ known_functions = {'tag', 'has_tag', 'meta', 'has_meta'}
+ self.unknown_functions = functions - known_functions
if len(self.missing_options):
print("Missing %s option entries for:%s%s\n" % (self.source_type, self.sep, self.sep.join(sorted(list(self.missing_options)))))
diff --git a/test/CheckTexinfo.py b/test/CheckTexinfo.py
index fa709e1b..82c7a286 100755
--- a/test/CheckTexinfo.py
+++ b/test/CheckTexinfo.py
@@ -1,5 +1,4 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
+#!/usr/bin/env python3
import sys
import re
diff --git a/test/ConfirmTests.py b/test/ConfirmTests.py
index e82479ed..0dc2b9f5 100755
--- a/test/ConfirmTests.py
+++ b/test/ConfirmTests.py
@@ -1,4 +1,4 @@
-#!/usr/bin/python
+#!/usr/bin/env python3
# This script confirms both that the register report "adds up", and that its
# final balance is the same as what the balance report shows.
@@ -56,8 +56,8 @@ def confirm_report(command):
if re.search(' -[VGB] ', command) and diff < 0.015:
diff = 0.0
if diff > 0.001:
- print("DISCREPANCY: %.3f (%.3f - %.3f) at line %d:" % \)
- (running_total - total, running_total, total, index)
+ print("DISCREPANCY: %.3f (%.3f - %.3f) at line %d:" % \
+ (running_total - total, running_total, total, index))
print(line,)
running_total = total
failure = True
@@ -78,8 +78,8 @@ def confirm_report(command):
diff = 0.0
if diff > 0.001:
print()
- print("DISCREPANCY: %.3f (%.3f - %.3f) between register and balance" % \)
- (balance_total - running_total, balance_total, running_total)
+ print("DISCREPANCY: %.3f (%.3f - %.3f) between register and balance" % \
+ (balance_total - running_total, balance_total, running_total))
print(last_line,)
failure = True
diff --git a/test/DocTests.py b/test/DocTests.py
index daecdd90..e12a4ae4 100755
--- a/test/DocTests.py
+++ b/test/DocTests.py
@@ -1,5 +1,4 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
+#!/usr/bin/env python3
from io import open
diff --git a/test/GenerateTests.py b/test/GenerateTests.py
index 2b966e35..9bd4a7bc 100755
--- a/test/GenerateTests.py
+++ b/test/GenerateTests.py
@@ -1,4 +1,4 @@
-#!/usr/bin/python
+#!/usr/bin/env python3
# This script confirms both that the register report "adds up", and that its
# final balance is the same as what the balance report shows.
diff --git a/test/LedgerHarness.py b/test/LedgerHarness.py
index 5051fc8b..0da32e8a 100755
--- a/test/LedgerHarness.py
+++ b/test/LedgerHarness.py
@@ -1,5 +1,4 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
+#!/usr/bin/env python3
import sys
import os
diff --git a/test/RegressTests.py b/test/RegressTests.py
index 849c4137..a3998f1f 100755
--- a/test/RegressTests.py
+++ b/test/RegressTests.py
@@ -1,5 +1,4 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
+#!/usr/bin/env python3
from io import open
diff --git a/test/convert.py b/test/convert.py
index 85e52701..a54edaac 100755
--- a/test/convert.py
+++ b/test/convert.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
# convert.py: This script converts a Boost.Test unit test into an
# equivalent Python unit test.
diff --git a/test/python/JournalTest.py b/test/python/JournalTest.py
index 84ad2f43..59442968 100644
--- a/test/python/JournalTest.py
+++ b/test/python/JournalTest.py
@@ -1,4 +1,4 @@
-# -*- coding: utf-8 -*-
+#!/usr/bin/env python3
import unittest
diff --git a/test/python/PostingTest.py b/test/python/PostingTest.py
index 0dd18112..f6b9bc04 100644
--- a/test/python/PostingTest.py
+++ b/test/python/PostingTest.py
@@ -1,4 +1,4 @@
-# -*- coding: utf-8 -*-
+#!/usr/bin/env python3
import unittest
import operator
diff --git a/test/python/TransactionTest.py b/test/python/TransactionTest.py
index c87f8c4b..12069389 100644
--- a/test/python/TransactionTest.py
+++ b/test/python/TransactionTest.py
@@ -1,4 +1,4 @@
-# -*- coding: utf-8 -*-
+#!/usr/bin/env python3
import unittest
import operator
diff --git a/test/regress/B21BF389.py b/test/regress/B21BF389.py
index 11208e91..e130b7b3 100644
--- a/test/regress/B21BF389.py
+++ b/test/regress/B21BF389.py
@@ -1,5 +1,4 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
+#!/usr/bin/env python3
import sys
import ledger
diff --git a/tools/average b/tools/average
index 0e95c1c5..4c1e4b43 100755
--- a/tools/average
+++ b/tools/average
@@ -1,4 +1,4 @@
-#!/usr/bin/python
+#!/usr/bin/env python3
import getopt
import time
@@ -18,9 +18,9 @@ length = 0.0
i = 0
while i < count:
begin = time.time()
- cmd = '"' + string.join(args, '" "') + '"';
+ cmd = '"' + '" "'.join(args) + '"';
os.system(cmd)
length += time.time() - begin
i += 1
-print >> sys.stderr, length / count
+print(length / count, file=sys.stderr)
diff --git a/tools/genuuid b/tools/genuuid
index 53fb7a0a..0ad5bd92 100755
--- a/tools/genuuid
+++ b/tools/genuuid
@@ -1,24 +1,27 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
import re
import sys
def scan_path(path):
bug = uuid = None
- with open(path, 'r') as fd:
- for line in fd:
- match = re.match('\*', line)
- if match:
- bug = uuid = None
+ try:
+ with open(path, 'r') as fd:
+ for line in fd:
+ match = re.match('\*', line)
+ if match:
+ bug = uuid = None
- match = re.search('\[\[bug:([0-9]+)\]\[#[0-9]+\]\]', line)
- if match:
- bug = match.group(1)
- elif bug:
- match = re.search(':ID:\s+(.+?)\s*$', line)
+ match = re.search('\[\[bug:([0-9]+)\]\[#[0-9]+\]\]', line)
if match:
- uuid = match.group(1)
- print "UPDATE bugs SET cf_uuid='%s' WHERE bug_id=%s;" % (uuid, bug)
+ bug = match.group(1)
+ elif bug:
+ match = re.search(':ID:\s+(.+?)\s*$', line)
+ if match:
+ uuid = match.group(1)
+ print(f"UPDATE bugs SET cf_uuid='{uuid}' WHERE bug_id={bug};")
+ except FileNotFoundError:
+ print(f'{path}: No such file or directory')
scan_path('/Users/johnw/src/ledger/plan/TODO')
scan_path('/Users/johnw/src/ledger/plan/TODO-3.0')