multisplit
[iramuteq] / ooolib.py
1 # -*- coding: utf-8 -*-
2 #modification pour python 3 : Laurent Mérat, 6x7 - mai 2020
3
4 "ooolib-python - Copyright (C) 2006-2009 Joseph Colton"
5
6 # PEUT-ETRE une MISE A JOUR ???
7
8 # ooolib-python - Python module for creating Open Document Format documents.
9 # Copyright (C) 2006-2009  Joseph Colton
10
11 # This library is free software; you can redistribute it and/or
12 # modify it under the terms of the GNU Lesser General Public
13 # License as published by the Free Software Foundation; either
14 # version 2.1 of the License, or (at your option) any later version.
15
16 # This library is distributed in the hope that it will be useful,
17 # but WITHOUT ANY WARRANTY; without even the implied warranty of
18 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
19 # Lesser General Public License for more details.
20
21 # You should have received a copy of the GNU Lesser General Public
22 # License along with this library; if not, write to the Free Software
23 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA
24
25 # You can contact me by email at josephcolton@gmail.com
26
27 # Import Standard Modules
28 import zipfile           # Needed for reading/writing documents
29 import time
30 import sys
31 import glob
32 import os
33 import re
34 import xml.parsers.expat # Needed for parsing documents
35
36 def version_number():
37     "Get the ooolib-python version number"
38     return "0.0.17"
39
40 def version():
41     "Get the ooolib-python version"
42     return "ooolib-python-%s" % version_number()
43
44 def clean_string(data):
45     "Returns an XML friendly copy of the data string"
46     data = str(data) # This line thanks to Chris Ender
47     data = data.replace('&', '&')
48     data = data.replace("'", ''')
49     data = data.replace('"', '"')
50     data = data.replace('<', '&lt;')
51     data = data.replace('>', '&gt;')
52     data = data.replace('\t', '<text:tab-stop/>')
53     data = data.replace('\n', '<text:line-break/>')
54     return data
55
56
57 class XML:
58     "XML Class - Used to convert nested lists into XML"
59
60     def __init__(self):
61         "Initialize ooolib XML instance"
62         pass
63
64     def _xmldata(self, data):
65         datatype = data.pop(0)
66         datavalue = data.pop(0)
67         outstring = '%s' % datavalue
68         return outstring
69
70     def _xmltag(self, data):
71         outstring = ''
72         # First two
73         datatype = data.pop(0)
74         dataname = data.pop(0)
75         outstring = '<%s' % dataname
76         # Element Section
77         element = 1
78         while(data):
79             # elements
80             newdata = data.pop(0)
81             if (newdata[0] == 'element' and element):
82                 newstring = self._xmlelement(newdata)
83                 outstring = '%s %s' % (outstring, newstring)
84                 continue
85             if (newdata[0] != 'element' and element):
86                 element = 0
87                 outstring = '%s>' % outstring
88                 if (newdata[0] == 'tag' or newdata[0] == 'tagline'):
89                     outstring = '%s\n' % outstring
90             if (newdata[0] == 'tag'):
91                 newstring = self._xmltag(newdata)
92                 outstring = '%s%s' % (outstring, newstring)
93                 continue
94             if (newdata[0] == 'tagline'):
95                 newstring = self._xmltagline(newdata)
96                 outstring = '%s%s' % (outstring, newstring)
97                 continue
98             if (newdata[0] == 'data'):
99                 newstring = self._xmldata(newdata)
100                 outstring = '%s%s' % (outstring, newstring)
101                 continue
102         if (element):
103             element = 0
104             outstring = '%s>\n' % outstring
105         outstring = '%s</%s>\n' % (outstring, dataname)
106         return outstring
107
108     def _xmltagline(self, data):
109         outstring = ''
110         # First two
111         datatype = data.pop(0)
112         dataname = data.pop(0)
113         outstring = '<%s' % dataname
114         # Element Section
115         while(data):
116             # elements
117             newdata = data.pop(0)
118             if (newdata[0] != 'element'): break
119             newstring = self._xmlelement(newdata)
120             outstring = '%s %s' % (outstring, newstring)
121         outstring = '%s/>\n' % outstring
122         # Non-Element Section should not exist
123         return outstring
124
125     def _xmlelement(self, data):
126         datatype = data.pop(0)
127         dataname = data.pop(0)
128         datavalue = data.pop(0)
129         outstring = '%s="%s"' % (dataname, datavalue)    
130         return outstring
131
132     def convert(self, data):
133         """Convert nested lists into XML
134
135         The convert method takes a nested lists and converts them
136         into XML to be used in Open Document Format documents.
137         There are three types of lists that are recognized at this
138         time.  They are as follows:
139
140         'tag' - Tag opens a set of data that is eventually closed
141         with a similar tag.
142         List: ['tag', 'xml']
143         XML: <xml></xml>
144
145         'tagline' - Taglines are similar to tags, except they open
146         and close themselves.
147         List: ['tagline', 'xml']
148         XML: <xml/>
149
150         'element' - Elements are pieces of information stored in an
151         opening tag or tagline.
152         List: ['element', 'color', 'blue']
153         XML: color="blue"
154
155         'data' - Data is plain text directly inserted into the XML
156         document.
157         List: ['data', 'hello']
158         XML: hello
159
160         Bring them all together for something like this.
161
162         Lists:
163         ['tag', 'xml', ['element', 'a', 'b'], ['tagline', 'xml2'], 
164         ['data', 'asdf']]
165
166         XML:
167         <xml a="b"><xml2/>asdf</xml>
168         """
169         outlines = []
170         outlines.append('<?xml version="1.0" encoding="UTF-8"?>')
171         if (type(data) == type([]) and len(data) > 0):
172             if data[0] == 'tag': outlines.append(self._xmltag(data))
173         return outlines
174
175
176 class Meta:
177     "Meta Data Class"
178
179     def __init__(self, doctype, debug=False):
180         self.doctype = doctype
181         # Set the debug mode
182         self.debug = debug
183         # The generator should always default to the version number
184         self.meta_generator = version()
185         self.meta_title = ''
186         self.meta_subject = ''
187         self.meta_description = ''
188         self.meta_keywords = []
189         self.meta_creator = 'ooolib-python'
190         self.meta_editor = ''
191         self.meta_user1_name = 'Info 1'
192         self.meta_user2_name = 'Info 2'
193         self.meta_user3_name = 'Info 3'
194         self.meta_user4_name = 'Info 4'
195         self.meta_user1_value = ''
196         self.meta_user2_value = ''
197         self.meta_user3_value = ''
198         self.meta_user4_value = ''
199         self.meta_creation_date = self.meta_time()
200         # Parser data
201         self.parser_element_list = []
202         self.parser_element = ""
203         self.parser_count = 0
204
205     def set_meta(self, metaname, value):
206         """Set meta data in your document.
207
208         Currently implemented metaname options are as follows:
209         'creator' - The document author
210         """
211         if metaname == 'creator': self.meta_creator = value
212         if metaname == 'editor': self.meta_editor = value
213         if metaname == 'title': self.meta_title = value
214         if metaname == 'subject': self.meta_subject = value
215         if metaname == 'description': self.meta_description = value
216         if metaname == 'user1name': self.meta_user1_name = value
217         if metaname == 'user2name': self.meta_user2_name = value
218         if metaname == 'user3name': self.meta_user3_name = value
219         if metaname == 'user4name': self.meta_user4_name = value
220         if metaname == 'user1value': self.meta_user1_value = value
221         if metaname == 'user2value': self.meta_user2_value = value
222         if metaname == 'user3value': self.meta_user3_value = value
223         if metaname == 'user4value': self.meta_user4_value = value
224         if metaname == 'keyword':
225             if value not in self.meta_keywords:
226                 self.meta_keywords.append(value)
227
228     def get_meta_value(self, metaname):
229         "Get meta data value for a given metaname."
230         if metaname == 'creator': return self.meta_creator
231         if metaname == 'editor': return self.meta_editor
232         if metaname == 'title': return self.meta_title
233         if metaname == 'subject': return self.meta_subject
234         if metaname == 'description': return self.meta_description
235         if metaname == 'user1name': return self.meta_user1_name
236         if metaname == 'user2name': return self.meta_user2_name
237         if metaname == 'user3name': return self.meta_user3_name
238         if metaname == 'user4name': return self.meta_user4_name
239         if metaname == 'user1value': return self.meta_user1_value
240         if metaname == 'user2value': return self.meta_user2_value
241         if metaname == 'user3value': return self.meta_user3_value
242         if metaname == 'user4value': return self.meta_user4_value
243         if metaname == 'keyword': return self.meta_keywords
244
245     def meta_time(self):
246         "Return time string in meta data format"
247         t = time.localtime()
248         stamp = "%04d-%02d-%02dT%02d:%02d:%02d" % (t[0], t[1], t[2], t[3], t[4], t[5])
249         return stamp
250
251     def parse_start_element(self, name, attrs):
252         if self.debug: print('* Start element:', name)
253         self.parser_element_list.append(name)
254         self.parser_element = self.parser_element_list[-1]
255         # Need the meta name from the user-defined tags
256         if (self.parser_element == "meta:user-defined"):
257             self.parser_count += 1
258             # Set user-defined name
259             self.set_meta("user%dname" % self.parser_count, attrs['meta:name'])
260         # Debugging statements
261         if self.debug: print("  List: ", self.parser_element_list)
262         if self.debug: print("  Attributes: ", attrs)
263
264     def parse_end_element(self, name):
265         if self.debug: print('* End element:', name)
266         if name != self.parser_element:
267             print("Tag Mismatch: '%s' != '%s'" % (name, self.parser_element))
268         self.parser_element_list.pop()
269         # Readjust parser_element_list and parser_element
270         if (self.parser_element_list):
271             self.parser_element = self.parser_element_list[-1]
272         else:
273             self.parser_element = ""
274
275     def parse_char_data(self, data):
276         if self.debug:
277             print("  Character data: ", repr(data))
278         # Collect Meta data fields
279         if (self.parser_element == "dc:title"):
280             self.set_meta("title", data)
281         if (self.parser_element == "dc:description"):
282             self.set_meta("description", data)
283         if (self.parser_element == "dc:subject"):
284             self.set_meta("subject", data)
285         if (self.parser_element == "meta:initial-creator"):
286             self.set_meta("creator", data)
287         # Try to maintain the same creation date
288         if (self.parser_element == "meta:creation-date"):
289             self.meta_creation_date = data
290         # The user defined fields need to be kept track of, parser_count does that
291         if (self.parser_element == "meta:user-defined"):
292             self.set_meta("user%dvalue" % self.parser_count, data)
293
294     def meta_parse(self, data):
295         "Parse Meta Data from a meta.xml file"
296         # Debugging statements
297         if self.debug:
298             # Sometimes it helps to see the document that was read from
299             print(data)
300             print("\n\n\n")
301         # Create parser
302         parser = xml.parsers.expat.ParserCreate()
303         # Set up parser callback functions
304         parser.StartElementHandler = self.parse_start_element
305         parser.EndElementHandler = self.parse_end_element
306         parser.CharacterDataHandler = self.parse_char_data
307         # Actually parse the data
308         parser.Parse(data, 1)
309
310     def get_meta(self):
311         "Generate meta.xml file data"
312         self.meta_date = self.meta_time()
313         self.data = ['tag', 'office:document-meta',
314           ['element', 'xmlns:office', 'urn:oasis:names:tc:opendocument:xmlns:office:1.0'],
315           ['element', 'xmlns:xlink', 'http://www.w3.org/1999/xlink'],
316           ['element', 'xmlns:dc', 'http://purl.org/dc/elements/1.1/'],
317           ['element', 'xmlns:meta', 'urn:oasis:names:tc:opendocument:xmlns:meta:1.0'],
318           ['element', 'xmlns:ooo', 'http://openoffice.org/2004/office'],
319           ['element', 'office:version', '1.0'],
320           ['tag', 'office:meta',
321             ['tag', 'meta:generator',             # Was: 'OpenOffice.org/2.0$Linux OpenOffice.org_project/680m5$Build-9011'
322               ['data', self.meta_generator]],     # Generator is set the the ooolib-python version.
323             ['tag', 'dc:title',
324               ['data', self.meta_title]],         # This data is the document title
325             ['tag', 'dc:description',
326               ['data', self.meta_description]],   # This data is the document description
327             ['tag', 'dc:subject',
328               ['data', self.meta_subject]],       # This data is the document subject
329             ['tag', 'meta:initial-creator',
330               ['data', self.meta_creator]],       # This data is the document creator
331             ['tag', 'meta:creation-date',
332               ['data', self.meta_creation_date]], # This is the original creation date of the document
333             ['tag', 'dc:creator',
334               ['data', self.meta_editor]],        # This data is the document editor
335             ['tag', 'dc:date',
336               ['data', self.meta_date]],          # This is the last modified date of the document
337             ['tag', 'dc:language',
338               ['data', 'en-US']],                 # We will probably always use en-US for language
339             ['tag', 'meta:editing-cycles',
340               ['data', '1']],                     # Edit cycles will probably always be 1 for generated documents
341             ['tag', 'meta:editing-duration',
342               ['data', 'PT0S']],                  # Editing duration is modified - creation date
343             ['tag', 'meta:user-defined',
344               ['element', 'meta:name', self.meta_user1_name],
345               ['data', self.meta_user1_value]],
346             ['tag', 'meta:user-defined',
347               ['element', 'meta:name', self.meta_user2_name],
348               ['data', self.meta_user2_value]],
349             ['tag', 'meta:user-defined',
350               ['element', 'meta:name', self.meta_user3_name],
351               ['data', self.meta_user3_value]],
352             ['tag', 'meta:user-defined',
353               ['element', 'meta:name', self.meta_user4_name],
354               ['data', self.meta_user4_value]]]]
355 #            ['tagline', 'meta:document-statistic',
356 #              ['element', 'meta:table-count', len(self.sheets)], # len(self.sheets) ?
357 #              ['element', 'meta:cell-count', '15']]]] # Not sure how to keep track
358         # Generate content.xml XML data
359         xml = XML()
360         self.lines = xml.convert(self.data)
361         self.filedata = '\n'.join(self.lines)
362         # Return generated data
363         return self.filedata
364
365
366 class CalcStyles:
367     "Calc Style Management - Used to keep track of created styles."
368
369     def __init__(self):
370         self.style_config = {}
371         # Style Counters
372         self.style_table = 1
373         self.style_column = 1
374         self.style_row = 1
375         self.style_cell = 1
376         # Style Properties (Defaults) - To be used later
377         self.property_column_width_default = '0.8925in' # Default Column Width
378         self.property_row_height_default = '0.189in'    # Default Row Height
379         # Set Defaults
380         self.property_column_width = '0.8925in' # Default Column Width
381         self.property_row_height = '0.189in'    # Default Row Height
382         self.property_cell_bold = False         # Bold off be default
383         self.property_cell_italic = False       # Italic off be default
384         self.property_cell_underline = False    # Underline off be default
385         self.property_cell_fg_color = 'default' # Text Color Default
386         self.property_cell_bg_color = 'default' # Cell Background Default
387         self.property_cell_bg_image = 'none'    # Cell Background Default
388         self.property_cell_fontsize = '10'      # Cell Font Size Default
389         self.property_cell_valign = 'default'    # Vertial Alignment Default
390         self.property_cell_halign = 'default'    # Horizantal Alignment Default
391
392     def get_next_style(self, style):
393         "Returns the next style code for the given style"
394         style_code = ""
395         if style == 'table':
396             style_code = 'ta%d' % self.style_table
397             self.style_table+=1
398         if style == 'column':
399             style_code = 'co%d' % self.style_column
400             self.style_column+=1
401         if style == 'row':
402             style_code = 'ro%d' % self.style_row
403             self.style_row+=1
404         if style == 'cell':
405             style_code = 'ce%d' % self.style_cell
406             self.style_cell+=1
407         return style_code
408
409     def set_property(self, style, name, value):
410         "Sets a property which will later be turned into a code"
411         if style == 'table':
412             pass
413         if style == 'column':
414             if name == 'style:column-width': self.property_column_width = value
415         if style == 'row':
416             if name == 'style:row-height': self.property_row_height = value
417         if style == 'cell':
418             if name == 'bold' and type(value) == type(True): self.property_cell_bold = value
419             if name == 'italic' and type(value) == type(True): self.property_cell_italic = value
420             if name == 'underline' and type(value) == type(True): self.property_cell_underline = value
421             if name == 'fontsize': self.property_cell_fontsize = value
422             if name == 'color':
423                 self.property_cell_fg_color = 'default'
424                 redata = re.search("^(#[\da-fA-F]{6})$", value)
425                 if redata: self.property_cell_fg_color = value.lower()
426             if name == 'background':
427                 self.property_cell_bg_color = 'default'
428                 redata = re.search("^(#[\da-fA-F]{6})$", value)
429                 if redata: self.property_cell_bg_color = value.lower()
430             if name == 'backgroundimage':
431                 self.property_cell_bg_image = value
432             if name == 'valign':
433                 self.property_cell_valign = value
434             if name == 'halign':
435                 self.property_cell_halign = value
436
437     def get_style_code(self, style):
438         style_code = ""
439         if style == 'table':
440             style_code = "ta1"
441         if style == 'column':
442             style_data = tuple([style,
443                 ('style:column-width', self.property_column_width)])
444             if style_data in self.style_config:
445                 # Style Exists, return code
446                 style_code = self.style_config[style_data]
447             else:
448                 # Style does not exist, create code and return it
449                 style_code = self.get_next_style(style)
450                 self.style_config[style_data] = style_code
451         if style == 'row':
452             style_data = tuple([style,
453                 ('style:row-height', self.property_row_height)])
454             if style_data in self.style_config:
455                 # Style Exists, return code
456                 style_code = self.style_config[style_data]
457             else:
458                 # Style does not exist, create code and return it
459                 style_code = self.get_next_style(style)
460                 self.style_config[style_data] = style_code
461         if style == 'cell':
462             style_data = [style]
463             # Add additional styles
464             if self.property_cell_bold: style_data.append(('bold', True))
465             if self.property_cell_italic: style_data.append(('italic', True))
466             if self.property_cell_underline: style_data.append(('underline', True))
467             if self.property_cell_fontsize != '10':
468                 style_data.append(('fontsize', self.property_cell_fontsize))
469             if self.property_cell_fg_color != 'default':
470                 style_data.append(('color', self.property_cell_fg_color))
471             if self.property_cell_bg_color != 'default':
472                 style_data.append(('background', self.property_cell_bg_color))
473             if self.property_cell_bg_image != 'none':
474                 style_data.append(('backgroundimage', self.property_cell_bg_image))
475             if self.property_cell_valign != 'default':
476                 style_data.append(('valign', self.property_cell_valign))
477             if self.property_cell_halign != 'default':
478                 style_data.append(('halign', self.property_cell_halign))
479             style_data = tuple(style_data)
480             if style_data in self.style_config:
481                 # Style Exists, return code
482                 style_code = self.style_config[style_data]
483             else:
484                 # Style does not exist, create code and return it
485                 style_code = self.get_next_style(style)
486                 self.style_config[style_data] = style_code
487         return style_code
488
489     def get_automatic_styles(self):
490         "Return 'office:automatic-styles' lists"
491         automatic_styles = ['tag', 'office:automatic-styles']
492         for style_data in self.style_config:
493             style_code = self.style_config[style_data]
494             style_data = list(style_data)
495             style = style_data.pop(0)
496             if style == 'column':
497                 style_list = ['tag', 'style:style',
498                   ['element', 'style:name', style_code],            # Column 'co1' properties
499                   ['element', 'style:family', 'table-column']]
500                 tagline = ['tagline', 'style:table-column-properties',
501                   ['element', 'fo:break-before', 'auto']]      # unsure what break before means
502                 for set in style_data:
503                     name, value = set
504                     if name == 'style:column-width':
505                         tagline.append(['element', 'style:column-width', value])
506                 style_list.append(tagline)
507                 automatic_styles.append(style_list)
508             if style == 'row':
509                 style_list = ['tag', 'style:style',
510                   ['element', 'style:name', style_code],            # Column 'ro1' properties
511                   ['element', 'style:family', 'table-row']]
512                 tagline = ['tagline', 'style:table-row-properties']
513                 for set in style_data:
514                     name, value = set
515                     if name == 'style:row-height':
516                         tagline.append(['element', 'style:row-height', value])
517                 tagline.append(['element', 'fo:break-before', 'auto'])
518 #                tagline.append(['element', 'style:use-optimal-row-height', 'true']) # Overrides settings
519                 style_list.append(tagline)
520                 automatic_styles.append(style_list)
521             if style == 'cell':
522                 style_list = ['tag', 'style:style',
523                     ['element', 'style:name', style_code],             # ce1 style
524                   ['element', 'style:family', 'table-cell'],         # cell
525                   ['element', 'style:parent-style-name', 'Default']] # parent is Default
526                 # Cell Properties
527                 tagline = ['tag', 'style:table-cell-properties']
528                 tagline_additional = []
529                 for set in style_data:
530                     name, value = set
531                     if name == 'background':
532                         tagline.append(['element', 'fo:background-color', value])
533                     if name == 'backgroundimage':
534                         tagline.append(['element', 'fo:background-color', 'transparent'])
535                         # Additional tags added later
536                         bgimagetag = ['tagline', 'style:background-image']
537                         bgimagetag.append(['element', 'xlink:href', value])
538                         bgimagetag.append(['element', 'xlink:type', 'simple'])
539                         bgimagetag.append(['element', 'xlink:actuate', 'onLoad'])
540                         tagline_additional.append(bgimagetag)
541                     if name == 'valign':
542                         if value in ['top', 'bottom', 'middle']: 
543                             tagline.append(['element', 'style:vertical-align', value])
544                     if name == 'halign':
545                         tagline.append(['element', 'style:text-align-source', 'fix'])
546                         if value in ['filled']:
547                             tagline.append(['element', 'style:repeat-content', 'true'])
548                         else:
549                             tagline.append(['element', 'style:repeat-content', 'false'])
550                 # Add any additional internal tags
551                 while tagline_additional:
552                     tagadd = tagline_additional.pop(0)
553                     tagline.append(tagadd)
554                 style_list.append(tagline)
555                 # Paragraph Properties
556                 tagline = ['tagline', 'style:paragraph-properties']
557                 tagline_valid = False
558                 for set in style_data:
559                     name, value = set
560                     if name == 'halign':
561                         tagline_valid = True
562                         if value in ['center']:
563                             tagline.append(['element', 'fo:text-align', 'center'])
564                         if value in ['end', 'right']:
565                             tagline.append(['element', 'fo:text-align', 'end'])
566                         if value in ['start', 'filled', 'left']:
567                             tagline.append(['element', 'fo:text-align', 'start'])
568                         if value in ['justify']:
569                             tagline.append(['element', 'fo:text-align', 'justify'])
570                 # Conditionally add the tagline
571                 if tagline_valid:
572                     style_list.append(tagline)
573                 # Text Properties
574                 tagline = ['tagline', 'style:text-properties']
575                 for set in style_data:
576                     name, value = set
577                     if name == 'bold':
578                         tagline.append(['element', 'fo:font-weight', 'bold'])
579                     if name == 'italic':
580                         tagline.append(['element', 'fo:font-style', 'italic'])
581                     if name == 'underline':
582                         tagline.append(['element', 'style:text-underline-style', 'solid'])
583                         tagline.append(['element', 'style:text-underline-width', 'auto'])
584                         tagline.append(['element', 'style:text-underline-color', 'font-color'])
585                     if name == 'color':
586                         tagline.append(['element', 'fo:color', value])
587                     if name == 'fontsize':
588                         tagline.append(['element', 'fo:font-size', '%spt' % value])
589                 style_list.append(tagline)
590                 automatic_styles.append(style_list)
591         # Attach ta1 style
592         automatic_styles.append(['tag', 'style:style',
593           ['element', 'style:name', 'ta1'],
594           ['element', 'style:family', 'table'],
595           ['element', 'style:master-page-name', 'Default'],
596           ['tagline', 'style:table-properties',
597             ['element', 'table:display', 'true'],
598             ['element', 'style:writing-mode', 'lr-tb']]])
599         return automatic_styles
600
601
602 class CalcSheet:
603     "Calc Sheet Class - Used to keep track of the data for an individual sheet."
604
605     def __init__(self, sheetname):
606         "Initialize a sheet"
607         self.sheet_name = sheetname
608         self.sheet_values = {}
609         self.sheet_config = {}
610         self.max_col = 0
611         self.max_row = 0
612
613     def get_sheet_dimensions(self):
614         "Returns the max column and row"
615         return (self.max_col, self.max_row)
616
617     def clean_formula(self, data):
618         "Returns a formula for use in ODF"
619         # Example Translations
620         # '=SUM(A1:A2)'
621         # datavalue = 'oooc:=SUM([.A1:.A2])'
622         # '=IF((A5>A4);A4;"")'
623         # datavalue = 'oooc:=IF(([.A5]&gt;[.A4]);[.A4];&quot;&quot;)'
624         data = str(data)
625         data = clean_string(data)
626         redata = re.search('^=([A-Z]+)(\(.*)$', data)
627         if redata:
628             # funct is the function name.  The rest if the string will be the functArgs
629             funct = redata.group(1)
630             functArgs = redata.group(2)
631             # Search for cell lebels and replace them
632             reList = re.findall('([A-Z]+\d+)', functArgs)
633             # sort and keep track so we do not do a cell more than once
634             reList.sort()
635             lastVar = ''
636             while reList:
637                 # Replace each cell label
638                 curVar = reList.pop()
639                 if curVar == lastVar: continue
640                 lastVar = curVar
641                 functArgs = functArgs.replace(curVar, '[.%s]' % curVar)
642             data = 'oooc:=%s%s' % (funct, functArgs)
643         return data
644
645     def get_name(self):
646         "Returns the sheet name"
647         return self.sheet_name
648
649     def set_name(self, sheetname):
650         "Resets the sheet name"
651         self.sheet_name = sheetname
652
653     def get_sheet_values(self):
654         "Returns the sheet cell values"
655         return self.sheet_values
656
657     def get_sheet_value(self, col, row):
658         "Get the value contents of a cell"
659         cell = (col, row)
660         if cell in self.sheet_values:
661             return self.sheet_values[cell]
662         else:
663             return None
664
665     def get_sheet_config(self):
666         "Returns the sheet cell properties"
667         return self.sheet_config
668
669     def set_sheet_config(self, location, style_code):
670         "Sets Style Code for a given location"
671         self.sheet_config[location] = style_code
672
673     def set_sheet_value(self, cell, datatype, datavalue):
674         """Sets the value for a specific cell
675
676         cell must be in the format (col, row) where row and col are int.
677         Example: B5 would be written as (2, 5)
678         datatype must be one of 'string', 'float', 'formula'
679         datavalue should be a string
680         """
681         # Catch invalid data
682         if type(cell) != type(()) or len(cell) != 2:
683             print("Invalid Cell")
684             return
685         (col, row) = cell
686         if type(col) != type(1):
687             print("Invalid Cell")
688             return
689         if type(row) != type(1):
690             print("Invalid Cell")
691             return
692         # Fix String Data
693         if datatype in ['string', 'annotation']:
694             datavalue = clean_string(datavalue)
695         # Fix Link Data. Link's value is a tuple containing (url, description)
696         if (datatype == 'link'):
697             url = clean_string(datavalue[0])
698             desc = clean_string(datavalue[1])
699             datavalue = (url, desc)
700         # Fix Formula Data
701         if datatype == 'formula':
702             datavalue = self.clean_formula(datavalue)
703         # Adjust maximum sizes
704         if col > self.max_col: self.max_col = col
705         if row > self.max_row: self.max_row = row
706         datatype = str(datatype)
707         if (datatype not in ['string', 'float', 'formula', 'annotation', 'link']):
708             # Set all unknown cell types to string
709             datatype = 'string'
710             datavalue = str(datavalue)
711         # The following lines are taken directly from HPS
712         # self.sheet_values[cell] = (datatype, datavalue)
713         # HPS: Cell content is now a list of tuples instead of a tuple
714         # While storing here, store the cell contents first and the annotation next. While generating the XML reverse this
715         contents = self.sheet_values.get(cell, {'annotation':None,'link':None, 'value':None})
716         if datatype == 'annotation':
717             contents['annotation'] = (datatype, datavalue)
718         elif datatype == 'link':
719             contents['link'] = (datatype, datavalue)
720         else:
721             contents['value'] = (datatype, datavalue)
722         self.sheet_values[cell] = contents
723
724     def get_lists(self):
725         "Returns nested lists for XML processing"
726         if (self.max_col == 0 and self.max_row == 0):
727             sheet_lists = ['tag', 'table:table',
728               ['element', 'table:name', self.sheet_name], # Set the Sheet Name
729               ['element', 'table:style-name', 'ta1'],
730               ['element', 'table:print', 'false'],
731               ['tagline', 'table:table-column',
732                 ['element', 'table:style-name', 'co1'],
733                 ['element', 'table:default-cell-style-name', 'Default']],
734                 ['tag', 'table:table-row',
735                   ['element', 'table:style-name', 'ro1'],
736                   ['tagline', 'table:table-cell']]]
737         else:
738             # Base Information
739             sheet_lists = ['tag', 'table:table',
740               ['element', 'table:name', self.sheet_name], # Set the sheet name
741               ['element', 'table:style-name', 'ta1'],
742               ['element', 'table:print', 'false']]
743 #              ['tagline', 'table:table-column',
744 #                ['element', 'table:style-name', 'co1'],
745 #                ['element', 'table:number-columns-repeated', self.max_col], # max_col? '2'
746 #                ['element', 'table:default-cell-style-name', 'Default']],
747             # Need to add column information            
748             for col in range(1, self.max_col+1):
749                 location = ('col', col)
750                 style_code = 'co1'
751                 if location in self.sheet_config:
752                     style_code = self.sheet_config[location]
753                 sheet_lists.append(['tagline', 'table:table-column',
754                   ['element', 'table:style-name', style_code],
755                   ['element', 'table:default-cell-style-name', 'Default']])
756             # Need to create each row
757             for row in range(1, self.max_row + 1):
758                 location = ('row', row)
759                 style_code = 'ro1'
760                 if location in self.sheet_config:
761                     style_code = self.sheet_config[location]                
762                 rowlist = ['tag', 'table:table-row',
763                   ['element', 'table:style-name', style_code]]
764                 for col in range(1, self.max_col + 1):
765                     cell = (col, row)
766                     style_code = 'ce1'                           # Default all cells to ce1
767                     if cell in self.sheet_config:
768                         style_code = self.sheet_config[cell] # Lookup cell if available
769                     if cell in self.sheet_values:
770                         # (datatype, datavalue) = self.sheet_values[cell] # Marked for removal
771                         collist = ['tag', 'table:table-cell']
772                         if style_code != 'ce1':
773                             collist.append(['element', 'table:style-name', style_code])
774                         # Contents, annotations, and links added by HPS
775                         contents = self.sheet_values[cell] # cell contents is a dictionary
776                         if contents['value']:
777                             (datatype, datavalue) = contents['value']
778                             if datatype == 'float':
779                                 collist.append(['element', 'office:value-type', datatype])
780                                 collist.append(['element', 'office:value', datavalue])
781                             if datatype == 'string':
782                                 collist.append(['element', 'office:value-type', datatype])
783                             if datatype == 'formula':
784                                 collist.append(['element', 'table:formula', datavalue])
785                                 collist.append(['element', 'office:value-type', 'float'])
786                                 collist.append(['element', 'office:value', '0'])
787                                 datavalue = '0'
788                         else:
789                             datavalue = None
790                         if contents['annotation']:
791                             (annotype, annoval) = contents['annotation']
792                             collist.append(['tag', 'office:annotation',
793                               ['tag', 'text:p', ['data', annoval]]])
794                         if contents['link']:
795                             (linktype, linkval) = contents['link']
796                             if datavalue:
797                                 collist.append(['tag', 'text:p', ['data', datavalue],
798                                   ['tag', 'text:a', ['element', 'xlink:href', linkval[0]],
799                                   ['data', linkval[1]]]])
800                             else: # no value; just fill the link
801                                 collist.append(['tag', 'text:p',
802                                   ['tag', 'text:a', ['element', 'xlink:href', linkval[0]],
803                                   ['data', linkval[1]]]])
804                         else:
805                             if datavalue:
806                                 collist.append(['tag', 'text:p', ['data', datavalue]])
807                     else:
808                         collist = ['tagline', 'table:table-cell']
809                     rowlist.append(collist)
810                 sheet_lists.append(rowlist)
811         return sheet_lists
812
813
814 class Calc:
815     "Calc Class - Used to create OpenDocument Format Calc Spreadsheets."
816
817     def __init__(self, sheetname=None, opendoc=None, debug=False):
818         "Initialize ooolib Calc instance"
819         # Default to no debugging
820         self.debug = debug
821         if not sheetname: sheetname = "Sheet1"
822         self.sheets = [CalcSheet(sheetname)]  # The main sheet will be initially called 'Sheet1'
823         self.sheet_index = 0                  # We initially start on the first sheet
824         self.styles = CalcStyles()
825         self.meta = Meta('ods')
826         self.styles.get_style_code('column')  # Force generation of default column
827         self.styles.get_style_code('row')     # Force generation of default row
828         self.styles.get_style_code('table')   # Force generation of default table
829         self.styles.get_style_code('cell')    # Force generation of default cell
830         self.manifest_files = []              # List of extra files included
831         self.manifest_index = 1               # Index of added manifest files
832         # Data Parsing
833         self.parser_element_list = []
834         self.parser_element = ""
835         self.parser_sheet_num = 0
836         self.parser_sheet_row = 0
837         self.parser_sheet_column = 0
838         self.parser_cell_repeats = 0
839         self.parser_cell_string_pending = False
840         self.parser_cell_string_line = ""
841         # See if we need to read a document
842         if opendoc:
843             # Verify that the document exists
844             if self.debug: print("Opening Document: %s" % opendoc)
845             # Okay, now we load the file
846             self.load(opendoc)
847
848     def debug_level(self, level):
849         """Set debug level:
850         True if you want debugging messages
851         False if you do not.
852         """
853         self.debug = level
854
855     def file_mimetype(self, filename):
856         "Determine the filetype from the filename"
857         parts = filename.lower().split('.')
858         ext = parts[-1]
859         if (ext == 'png'): return (ext, "image/png")
860         if (ext == 'gif'): return (ext, "image/gif")
861         return (ext, "image/unknown")
862
863     def add_file(self, filename):
864         """Prepare a file for loading into ooolib
865
866         The filename should be the local filesystem name for
867         the file.  The file is then prepared to be included in
868         the creation of the final document.  The file needs to
869         remain in place so that it is available when the actual
870         document creation happens.
871         """
872         # mimetype set to (ext, filetype)
873         mimetype = self.file_mimetype(filename)
874         newname = "Pictures/%08d.%s" % (self.manifest_index, mimetype[0])
875         self.manifest_index += 1
876         filetype = mimetype[1]
877         self.manifest_files.append((filename, filetype, newname))
878         return newname
879
880     def set_meta(self, metaname, value):
881         "Set meta data in your document."
882         self.meta.set_meta(metaname, value)
883
884     def get_meta_value(self, metaname):
885         "Get meta data value for a given metaname"
886         return self.meta.get_meta_value(metaname)
887
888     def get_sheet_name(self):
889         "Returns the sheet name"
890         return self.sheets[self.sheet_index].get_name()
891
892     def get_sheet_dimensions(self):
893         "Returns the sheet dimensions in (cols, rows)"
894         return self.sheets[self.sheet_index].get_sheet_dimensions()
895
896     def set_column_property(self, column, name, value):
897         "Set Column Properties"
898         if name == 'width':
899             # column number column needs column-width set to value
900             self.styles.set_property('column', 'style:column-width', value)
901             style_code = self.styles.get_style_code('column')
902             self.sheets[self.sheet_index].set_sheet_config(('col', column), style_code)
903
904     def set_row_property(self, row, name, value):
905         "Set row Properties"
906         if name == 'height':
907             # row number row needs row-height set to value
908             self.styles.set_property('row', 'style:row-height', value)
909             style_code = self.styles.get_style_code('row')
910             self.sheets[self.sheet_index].set_sheet_config(('row', row), style_code)
911
912     def set_cell_property(self, name, value):
913         """Turn and off cell properties
914
915         Actual application of properties is handled by setting a value."""
916         # background images need to be handled a little differently
917         # because they need to also be inserted into the final document
918         if (name == 'backgroundimage'):
919             # Add file and modify value
920             value = self.add_file(value)
921         self.styles.set_property('cell', name, value)
922
923     def get_sheet_index(self):
924         "Return the current sheet index number"
925         return self.sheet_index
926
927     def set_sheet_index(self, index):
928         "Set the sheet index"
929         if type(index) == type(1):
930             if index >= 0 and index < len(self.sheets):
931                 self.sheet_index = index
932         return self.sheet_index
933
934     def get_sheet_count(self):
935         "Returns the number of existing sheets"
936         return len(self.sheets)
937
938     def new_sheet(self, sheetname):
939         "Create a new sheet"
940         self.sheet_index = len(self.sheets)
941         self.sheets.append(CalcSheet(sheetname))
942         return self.sheet_index
943
944     def set_cell_value(self, col, row, datatype, value):
945         "Set the value for a given cell"
946         self.sheets[self.sheet_index].set_sheet_value((col, row), datatype, value)
947         style_code = self.styles.get_style_code('cell')
948         self.sheets[self.sheet_index].set_sheet_config((col, row), style_code)
949
950     def get_cell_value(self, col, row):
951         "Get a cell value tuple (type, value) for a given cell"
952         sheetvalue = self.sheets[self.sheet_index].get_sheet_value(col, row)
953         # We stop here if there is no value for sheetvalue
954         if sheetvalue == None: return sheetvalue
955         # Now check to see if we have a value tuple
956         if 'value' in sheetvalue:
957             return sheetvalue['value']
958         else:
959             return None
960
961     def load(self, filename):
962         """Load .ods spreadsheet.
963
964         The load function loads data from a document into the current cells.
965         """
966         # Read in the important files
967         # meta.xml
968         data = self._zip_read(filename, "meta.xml")
969         self.meta.meta_parse(data)        
970         # content.xml
971         data = self._zip_read(filename, "content.xml")
972         self.content_parse(data)
973         # settings.xml - I do not remember putting anything here
974         # styles.xml - I do not remember putting anything here
975
976     def parse_content_start_element(self, name, attrs):
977         if self.debug: print('* Start element:', name)
978         self.parser_element_list.append(name)
979         self.parser_element = self.parser_element_list[-1]
980         # Keep track of the current sheet number
981         if (self.parser_element == 'table:table'):
982             # Move to starting cell
983             self.parser_sheet_row = 0
984             self.parser_sheet_column = 0
985             # Increment the sheet number count
986             self.parser_sheet_num += 1
987             if (self.parser_sheet_num - 1 != self.sheet_index):
988                 # We are not on the first sheet and need to create a new sheet.
989                 # We will automatically move to the new sheet
990                 sheetname = "Sheet%d" % self.parser_sheet_num
991                 if 'table:name' in attrs: sheetname = attrs['table:name']
992                 self.new_sheet(sheetname)
993             else:
994                 # We are on the first sheet and will need to overwrite the default name
995                 sheetname = "Sheet%d" % self.parser_sheet_num
996                 if 'table:name' in attrs: sheetname = attrs['table:name']
997                 self.sheets[self.sheet_index].set_name(sheetname)
998         # Update the row numbers
999         if (self.parser_element == 'table:table-row'):
1000             self.parser_sheet_row += 1
1001             self.parser_sheet_column = 0
1002         # Okay, now keep track of the sheet cell data
1003         if (self.parser_element == 'table:table-cell'):
1004             # By default it will repeat zero times
1005             self.parser_cell_repeats = 0
1006             # We must be in a new column
1007             self.parser_sheet_column += 1
1008             # Set some default values
1009             datatype = ""
1010             value = ""
1011             # Get values from attrs hash
1012             if 'office:value-type' in attrs: datatype = attrs['office:value-type']
1013             if 'office:value' in attrs: value = attrs['office:value']
1014             if 'table:formula' in attrs:
1015                 datatype = 'formula'
1016                 value = attrs['table:formula']
1017             if datatype == 'string':
1018                 datatype = ""
1019                 self.parser_cell_string_pending = True
1020                 self.parser_cell_string_line = ""
1021             if 'table:number-columns-repeated' in attrs:
1022                 self.parser_cell_repeats = int(attrs['table:number-columns-repeated']) - 1
1023             # Set the cell value
1024             if datatype:
1025                 # I should do this once per cell repeat above 0
1026                 for i in range(0, self.parser_cell_repeats+1):
1027                     self.set_cell_value(self.parser_sheet_column+i, self.parser_sheet_row, datatype, value)
1028         # There are lots of interesting cases with table:table-cell data.  One problem is
1029         # reading the number of embedded spaces correctly.  This code should help us get
1030         # the number of spaces out.
1031         if (self.parser_element == 'text:s'):
1032             # This means we have a number of spaces
1033             count_num = 0
1034             if 'text:c' in attrs:
1035                 count_alpha = attrs['text:c']
1036                 if (count_alpha.isdigit()):
1037                     count_num = int(count_alpha)
1038             # I am not sure what to do if we do not have a string pending
1039             if (self.parser_cell_string_pending == True):
1040                 # Append the currect number of spaces to the end
1041                 self.parser_cell_string_line = "%s%s" % (self.parser_cell_string_line, ' '*count_num)
1042         if (self.parser_element == 'text:tab-stop'):
1043             if (self.parser_cell_string_pending == True):
1044                 self.parser_cell_string_line = "%s\t" % (self.parser_cell_string_line)
1045         if (self.parser_element == 'text:line-break'):
1046             if (self.parser_cell_string_pending == True):
1047                 self.parser_cell_string_line = "%s\n" % (self.parser_cell_string_line)
1048         # Debugging statements
1049         if self.debug: print("  List: ", self.parser_element_list)
1050         if self.debug: print("  Attributes: ", attrs)
1051
1052     def parse_content_end_element(self, name):
1053         if self.debug: print('* End element:', name)
1054         if name != self.parser_element:
1055             print("Tag Mismatch: '%s' != '%s'" % (name, self.parser_element))
1056         self.parser_element_list.pop()
1057         # If the element was text:p and we are in string mode
1058         if (self.parser_element == 'text:p'):
1059             if (self.parser_cell_string_pending):
1060                 self.parser_cell_string_pending = False
1061         # Take care of repeated cells
1062         if (self.parser_element == 'table:table-cell'):
1063             self.parser_sheet_column += self.parser_cell_repeats
1064         # Readjust parser_element_list and parser_element
1065         if (self.parser_element_list):
1066             self.parser_element = self.parser_element_list[-1]
1067         else:
1068             self.parser_element = ""
1069
1070     def parse_content_char_data(self, data):
1071         if self.debug:
1072             print("  Character data: ", repr(data))
1073         if (self.parser_element == 'text:p' or self.parser_element == 'text:span'):
1074             if (self.parser_cell_string_pending):
1075                 # Set the string and leave string pending mode
1076                 # This does feel a little kludgy, but it does the job
1077                 self.parser_cell_string_line = "%s%s" % (self.parser_cell_string_line, data)
1078                 # I should do this once per cell repeat above 0
1079                 for i in range(0, self.parser_cell_repeats+1):
1080                     self.set_cell_value(self.parser_sheet_column+i, self.parser_sheet_row,
1081                             'string', self.parser_cell_string_line)
1082
1083     def content_parse(self, data):
1084         "Parse Content Data from a content.xml file"
1085         # Debugging statements
1086         if self.debug:
1087             # Sometimes it helps to see the document that was read from
1088             print(data)
1089             print("\n\n\n")
1090         # Create parser
1091         parser = xml.parsers.expat.ParserCreate()
1092         # Set up parser callback functions
1093         parser.StartElementHandler = self.parse_content_start_element
1094         parser.EndElementHandler = self.parse_content_end_element
1095         parser.CharacterDataHandler = self.parse_content_char_data
1096         # Actually parse the data
1097         parser.Parse(data, 1)
1098
1099     def save(self, filename):
1100         """Save .ods spreadsheet.
1101
1102         The save function saves the current cells and settings into a document.
1103         """
1104         if self.debug: print("Writing %s" % filename)
1105         self.savefile = zipfile.ZipFile(filename, "w")
1106         if self.debug: print("  meta.xml")
1107         self._zip_insert(self.savefile, "meta.xml", self.meta.get_meta())
1108         if self.debug: print("  mimetype")
1109         self._zip_insert(self.savefile, "mimetype", "application/vnd.oasis.opendocument.spreadsheet")
1110         if self.debug: print("  Configurations2/accelerator/current.xml")
1111         self._zip_insert(self.savefile, "Configurations2/accelerator/current.xml", "")
1112         if self.debug: print("  META-INF/manifest.xml")
1113         self._zip_insert(self.savefile, "META-INF/manifest.xml", self._ods_manifest())
1114         if self.debug: print("  content.xml")
1115         self._zip_insert(self.savefile, "content.xml", self._ods_content())
1116         if self.debug: print("  settings.xml")
1117         self._zip_insert(self.savefile, "settings.xml", self._ods_settings())
1118         if self.debug: print("  styles.xml")
1119         self._zip_insert(self.savefile, "styles.xml", self._ods_styles())
1120         # Add additional files if needed
1121         for fileset in self.manifest_files:
1122             (filename, filetype, newname) = fileset
1123             # Read in the file
1124             data = self._file_load(filename)
1125             if self.debug: print("  Inserting '%s' as '%s'" % (filename, newname))
1126             self._zip_insert_binary(self.savefile, newname, data)
1127
1128     def _file_load(self, filename):
1129         "Load a file"
1130         file = open(filename, "r")
1131         data = file.read()
1132         file.close()
1133         return data
1134
1135     def _zip_insert_binary(self, file, filename, data):
1136         "Insert a binary file into the zip archive"
1137         now = time.localtime(time.time())[:6]
1138         info = zipfile.ZipInfo(filename)
1139         info.date_time = now
1140         info.compress_type = zipfile.ZIP_DEFLATED
1141         file.writestr(info, data)
1142
1143     def _zip_insert(self, file, filename, data):
1144         "Insert a file into the zip archive"
1145         # zip seems to struggle with non-ascii characters
1146         data = data.encode('utf-8')
1147         now = time.localtime(time.time())[:6]
1148         info = zipfile.ZipInfo(filename)
1149         info.date_time = now
1150         info.compress_type = zipfile.ZIP_DEFLATED
1151         file.writestr(info, data)
1152
1153     def _zip_read(self, file, filename):
1154         "Get the data from a file in the zip archive by filename"
1155         file = zipfile.ZipFile(file, "r")
1156         data = file.read(filename)
1157         # Need to close the file
1158         file.close()
1159         return data
1160
1161     def _ods_content(self):
1162         "Generate ods content.xml data"
1163         # This will list all of the sheets in the document
1164         self.sheetdata = ['tag', 'office:spreadsheet']
1165         for sheet in self.sheets:
1166             if self.debug:    
1167                 sheet_name = sheet.get_name()
1168                 print("    Creating Sheet '%s'" % sheet_name)
1169             sheet_list = sheet.get_lists()
1170             self.sheetdata.append(sheet_list)
1171         # Automatic Styles
1172         self.automatic_styles = self.styles.get_automatic_styles()
1173         self.data = ['tag', 'office:document-content',
1174           ['element', 'xmlns:office', 'urn:oasis:names:tc:opendocument:xmlns:office:1.0'],
1175           ['element', 'xmlns:style', 'urn:oasis:names:tc:opendocument:xmlns:style:1.0'],
1176           ['element', 'xmlns:text', 'urn:oasis:names:tc:opendocument:xmlns:text:1.0'],
1177           ['element', 'xmlns:table', 'urn:oasis:names:tc:opendocument:xmlns:table:1.0'],
1178           ['element', 'xmlns:draw', 'urn:oasis:names:tc:opendocument:xmlns:drawing:1.0'],
1179           ['element', 'xmlns:fo', 'urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0'],
1180           ['element', 'xmlns:xlink', 'http://www.w3.org/1999/xlink'],
1181           ['element', 'xmlns:dc', 'http://purl.org/dc/elements/1.1/'],
1182           ['element', 'xmlns:meta', 'urn:oasis:names:tc:opendocument:xmlns:meta:1.0'],
1183           ['element', 'xmlns:number', 'urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0'],
1184           ['element', 'xmlns:svg', 'urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0'],
1185           ['element', 'xmlns:chart', 'urn:oasis:names:tc:opendocument:xmlns:chart:1.0'],
1186           ['element', 'xmlns:dr3d', 'urn:oasis:names:tc:opendocument:xmlns:dr3d:1.0'],
1187           ['element', 'xmlns:math', 'http://www.w3.org/1998/Math/MathML'],
1188           ['element', 'xmlns:form', 'urn:oasis:names:tc:opendocument:xmlns:form:1.0'],
1189           ['element', 'xmlns:script', 'urn:oasis:names:tc:opendocument:xmlns:script:1.0'],
1190           ['element', 'xmlns:ooo', 'http://openoffice.org/2004/office'],
1191           ['element', 'xmlns:ooow', 'http://openoffice.org/2004/writer'],
1192           ['element', 'xmlns:oooc', 'http://openoffice.org/2004/calc'],
1193           ['element', 'xmlns:dom', 'http://www.w3.org/2001/xml-events'],
1194           ['element', 'xmlns:xforms', 'http://www.w3.org/2002/xforms'],
1195           ['element', 'xmlns:xsd', 'http://www.w3.org/2001/XMLSchema'],
1196           ['element', 'xmlns:xsi', 'http://www.w3.org/2001/XMLSchema-instance'],
1197           ['element', 'office:version', '1.0'],
1198           ['tagline', 'office:scripts'],
1199           ['tag', 'office:font-face-decls',
1200             ['tagline', 'style:font-face',
1201               ['element', 'style:name', 'DejaVu Sans'],
1202               ['element', 'svg:font-family', '&apos;DejaVu Sans&apos;'],
1203               ['element', 'style:font-pitch', 'variable']],
1204             ['tagline', 'style:font-face',
1205               ['element', 'style:name', 'Nimbus Sans L'],
1206               ['element', 'svg:font-family', '&apos;Nimbus Sans L&apos;'],
1207               ['element', 'style:font-family-generic', 'swiss'],
1208               ['element', 'style:font-pitch', 'variable']]],
1209         # Automatic Styles
1210         self.automatic_styles,
1211           ['tag', 'office:body',
1212             self.sheetdata]]                                      # Sheets are generated from the CalcSheet class
1213         # Generate content.xml XML data
1214         xml = XML()
1215         self.lines = xml.convert(self.data)
1216         self.filedata = '\n'.join(self.lines)
1217         # Return generated data
1218         return self.filedata
1219
1220     def _ods_manifest(self):
1221         "Generate ods manifest.xml data"
1222         self.data = ['tag', 'manifest:manifest',
1223           ['element', 'xmlns:manifest', 'urn:oasis:names:tc:opendocument:xmlns:manifest:1.0'],
1224           ['tagline', 'manifest:file-entry',
1225             ['element', 'manifest:media-type', 'application/vnd.oasis.opendocument.spreadsheet'],
1226             ['element', 'manifest:full-path', '/']],
1227           ['tagline', 'manifest:file-entry',
1228             ['element', 'manifest:media-type', 'application/vnd.sun.xml.ui.configuration'],
1229             ['element', 'manifest:full-path', 'Configurations2/']],
1230           ['tagline', 'manifest:file-entry',
1231             ['element', 'manifest:media-type', ''],
1232             ['element', 'manifest:full-path', 'Configurations2/accelerator/']],
1233           ['tagline', 'manifest:file-entry',
1234             ['element', 'manifest:media-type', ''],
1235             ['element', 'manifest:full-path', 'Configurations2/accelerator/current.xml']],
1236           ['tagline', 'manifest:file-entry',
1237             ['element', 'manifest:media-type', 'text/xml'],
1238             ['element', 'manifest:full-path', 'content.xml']],
1239           ['tagline', 'manifest:file-entry',
1240             ['element', 'manifest:media-type', 'text/xml'],
1241             ['element', 'manifest:full-path', 'styles.xml']],
1242           ['tagline', 'manifest:file-entry',
1243             ['element', 'manifest:media-type', 'text/xml'],
1244             ['element', 'manifest:full-path', 'meta.xml']],
1245           ['tagline', 'manifest:file-entry',
1246             ['element', 'manifest:media-type', 'text/xml'],
1247             ['element', 'manifest:full-path', 'settings.xml']]]
1248         # Add additional files to manifest list
1249         for fileset in self.manifest_files:
1250             (filename, filetype, newname) = fileset
1251             addfile = ['tagline', 'manifest:file-entry',
1252                    ['element', 'manifest:media-type', filetype],
1253                    ['element', 'manifest:full-path', newname]]
1254             self.data.append(addfile)
1255         # Generate content.xml XML data
1256         xml = XML()
1257         self.lines = xml.convert(self.data)
1258         self.filedata = '\n'.join(self.lines)
1259         # Return generated data
1260         return self.filedata
1261
1262     def _ods_settings(self):
1263         "Generate ods settings.xml data"
1264         self.data = ['tag', 'office:document-settings',
1265           ['element', 'xmlns:office', 'urn:oasis:names:tc:opendocument:xmlns:office:1.0'],
1266           ['element', 'xmlns:xlink', 'http://www.w3.org/1999/xlink'],
1267           ['element', 'xmlns:config', 'urn:oasis:names:tc:opendocument:xmlns:config:1.0'],
1268           ['element', 'xmlns:ooo', 'http://openoffice.org/2004/office'],
1269           ['element', 'office:version', '1.0'],
1270           ['tag', 'office:settings',
1271             ['tag', 'config:config-item-set',
1272               ['element', 'config:name', 'ooo:view-settings'],
1273               ['tag', 'config:config-item',
1274                 ['element', 'config:name', 'VisibleAreaTop'],
1275                 ['element', 'config:type', 'int'],
1276                 ['data', '0']],
1277               ['tag', 'config:config-item',
1278                 ['element', 'config:name', 'VisibleAreaLeft'],
1279                 ['element', 'config:type', 'int'],
1280                 ['data', '0']],
1281               ['tag', 'config:config-item',
1282                 ['element', 'config:name', 'VisibleAreaWidth'],
1283                 ['element', 'config:type', 'int'],
1284                 ['data', '6774']],
1285               ['tag', 'config:config-item',
1286                 ['element', 'config:name', 'VisibleAreaHeight'],
1287                 ['element', 'config:type', 'int'],
1288                 ['data', '2389']],
1289               ['tag', 'config:config-item-map-indexed',
1290                 ['element', 'config:name', 'Views'],
1291                 ['tag', 'config:config-item-map-entry',
1292                   ['tag', 'config:config-item',
1293                     ['element', 'config:name', 'ViewId'],
1294                     ['element', 'config:type', 'string'],
1295                     ['data', 'View1']],
1296                   ['tag', 'config:config-item-map-named',
1297                     ['element', 'config:name', 'Tables'],
1298                     ['tag', 'config:config-item-map-entry',
1299                       ['element', 'config:name', 'Sheet1'],
1300                       ['tag', 'config:config-item',
1301                         ['element', 'config:name', 'CursorPositionX'], # Cursor Position A
1302                         ['element', 'config:type', 'int'],
1303                         ['data', '0']],
1304                       ['tag', 'config:config-item',
1305                         ['element', 'config:name', 'CursorPositionY'], # Cursor Position 1
1306                         ['element', 'config:type', 'int'],
1307                         ['data', '0']],
1308                       ['tag', 'config:config-item',
1309                         ['element', 'config:name', 'HorizontalSplitMode'],
1310                         ['element', 'config:type', 'short'],
1311                         ['data', '0']],
1312                       ['tag', 'config:config-item',
1313                         ['element', 'config:name', 'VerticalSplitMode'],
1314                         ['element', 'config:type', 'short'],
1315                         ['data', '0']],
1316                       ['tag', 'config:config-item',
1317                         ['element', 'config:name', 'HorizontalSplitPosition'],
1318                         ['element', 'config:type', 'int'],
1319                         ['data', '0']],
1320                       ['tag', 'config:config-item',
1321                         ['element', 'config:name', 'VerticalSplitPosition'],
1322                         ['element', 'config:type', 'int'],
1323                         ['data', '0']],
1324                       ['tag', 'config:config-item',
1325                         ['element', 'config:name', 'ActiveSplitRange'],
1326                         ['element', 'config:type', 'short'],
1327                         ['data', '2']],
1328                       ['tag', 'config:config-item',
1329                         ['element', 'config:name', 'PositionLeft'],
1330                         ['element', 'config:type', 'int'],
1331                         ['data', '0']],
1332                       ['tag', 'config:config-item',
1333                         ['element', 'config:name', 'PositionRight'],
1334                         ['element', 'config:type', 'int'],
1335                         ['data', '0']],
1336                       ['tag', 'config:config-item',
1337                         ['element', 'config:name', 'PositionTop'],
1338                         ['element', 'config:type', 'int'],
1339                         ['data', '0']],
1340                       ['tag', 'config:config-item',
1341                         ['element', 'config:name', 'PositionBottom'],
1342                         ['element', 'config:type', 'int'],
1343                         ['data', '0']]]],
1344                   ['tag', 'config:config-item',
1345                     ['element', 'config:name', 'ActiveTable'],
1346                     ['element', 'config:type', 'string'],
1347                     ['data', 'Sheet1']],
1348                   ['tag', 'config:config-item',
1349                     ['element', 'config:name', 'HorizontalScrollbarWidth'],
1350                     ['element', 'config:type', 'int'],
1351                     ['data', '270']],
1352                   ['tag', 'config:config-item',
1353                     ['element', 'config:name', 'ZoomType'],
1354                     ['element', 'config:type', 'short'],
1355                     ['data', '0']],
1356                   ['tag', 'config:config-item',
1357                     ['element', 'config:name', 'ZoomValue'],
1358                     ['element', 'config:type', 'int'],
1359                     ['data', '100']],
1360                   ['tag', 'config:config-item',
1361                     ['element', 'config:name', 'PageViewZoomValue'],
1362                     ['element', 'config:type', 'int'],
1363                     ['data', '60']],
1364                   ['tag', 'config:config-item',
1365                     ['element', 'config:name', 'ShowPageBreakPreview'],
1366                     ['element', 'config:type', 'boolean'],
1367                     ['data', 'false']],
1368                   ['tag', 'config:config-item',
1369                     ['element', 'config:name', 'ShowZeroValues'],
1370                     ['element', 'config:type', 'boolean'],
1371                     ['data', 'true']],
1372                   ['tag', 'config:config-item',
1373                     ['element', 'config:name', 'ShowNotes'],
1374                     ['element', 'config:type', 'boolean'],
1375                     ['data', 'true']],
1376                   ['tag', 'config:config-item',
1377                     ['element', 'config:name', 'ShowGrid'],
1378                     ['element', 'config:type', 'boolean'],
1379                     ['data', 'true']],
1380                   ['tag', 'config:config-item',
1381                     ['element', 'config:name', 'GridColor'],
1382                     ['element', 'config:type', 'long'],
1383                     ['data', '12632256']],
1384                   ['tag', 'config:config-item',
1385                     ['element', 'config:name', 'ShowPageBreaks'],
1386                     ['element', 'config:type', 'boolean'],
1387                     ['data', 'true']],
1388                   ['tag', 'config:config-item',
1389                     ['element', 'config:name', 'HasColumnRowHeaders'],
1390                     ['element', 'config:type', 'boolean'],
1391                     ['data', 'true']],
1392                   ['tag', 'config:config-item',
1393                     ['element', 'config:name', 'HasSheetTabs'],
1394                     ['element', 'config:type', 'boolean'],
1395                     ['data', 'true']],
1396                   ['tag', 'config:config-item',
1397                     ['element', 'config:name', 'IsOutlineSymbolsSet'],
1398                     ['element', 'config:type', 'boolean'],
1399                     ['data', 'true']],
1400                   ['tag', 'config:config-item',
1401                     ['element', 'config:name', 'IsSnapToRaster'],
1402                     ['element', 'config:type', 'boolean'],
1403                     ['data', 'false']],
1404                   ['tag', 'config:config-item',
1405                     ['element', 'config:name', 'RasterIsVisible'],
1406                     ['element', 'config:type', 'boolean'],
1407                     ['data', 'false']],
1408                   ['tag', 'config:config-item',
1409                     ['element', 'config:name', 'RasterResolutionX'],
1410                     ['element', 'config:type', 'int'],
1411                     ['data', '1270']],
1412                   ['tag', 'config:config-item',
1413                     ['element', 'config:name', 'RasterResolutionY'],
1414                     ['element', 'config:type', 'int'],
1415                     ['data', '1270']],
1416                   ['tag', 'config:config-item',
1417                     ['element', 'config:name', 'RasterSubdivisionX'],
1418                     ['element', 'config:type', 'int'],
1419                     ['data', '1']],
1420                   ['tag', 'config:config-item',
1421                     ['element', 'config:name', 'RasterSubdivisionY'],
1422                     ['element', 'config:type', 'int'],
1423                     ['data', '1']],
1424                   ['tag', 'config:config-item',
1425                     ['element', 'config:name', 'IsRasterAxisSynchronized'],
1426                     ['element', 'config:type', 'boolean'],
1427                     ['data', 'true']]]]],
1428             ['tag', 'config:config-item-set',
1429               ['element', 'config:name', 'ooo:configuration-settings'],
1430               ['tag', 'config:config-item',
1431                 ['element', 'config:name', 'ShowZeroValues'],
1432                 ['element', 'config:type', 'boolean'],
1433                 ['data', 'true']],
1434               ['tag', 'config:config-item',
1435                 ['element', 'config:name', 'ShowNotes'],
1436                 ['element', 'config:type', 'boolean'],
1437                 ['data', 'true']],
1438               ['tag', 'config:config-item',
1439                 ['element', 'config:name', 'ShowGrid'],
1440                 ['element', 'config:type', 'boolean'],
1441                 ['data', 'true']],
1442               ['tag', 'config:config-item',
1443                 ['element', 'config:name', 'GridColor'],
1444                 ['element', 'config:type', 'long'],
1445                 ['data', '12632256']],
1446               ['tag', 'config:config-item',
1447                 ['element', 'config:name', 'ShowPageBreaks'],
1448                 ['element', 'config:type', 'boolean'],
1449                 ['data', 'true']],
1450               ['tag', 'config:config-item',
1451                 ['element', 'config:name', 'LinkUpdateMode'],
1452                 ['element', 'config:type', 'short'],
1453                 ['data', '3']],
1454               ['tag', 'config:config-item',
1455                 ['element', 'config:name', 'HasColumnRowHeaders'],
1456                 ['element', 'config:type', 'boolean'],
1457                 ['data', 'true']],
1458               ['tag', 'config:config-item',
1459                 ['element', 'config:name', 'HasSheetTabs'],
1460                 ['element', 'config:type', 'boolean'],
1461                 ['data', 'true']],
1462               ['tag', 'config:config-item',
1463                 ['element', 'config:name', 'IsOutlineSymbolsSet'],
1464                 ['element', 'config:type', 'boolean'],
1465                 ['data', 'true']],
1466               ['tag', 'config:config-item',
1467                 ['element', 'config:name', 'IsSnapToRaster'],
1468                 ['element', 'config:type', 'boolean'],
1469                 ['data', 'false']],
1470               ['tag', 'config:config-item',
1471                 ['element', 'config:name', 'RasterIsVisible'],
1472                 ['element', 'config:type', 'boolean'],
1473                 ['data', 'false']],
1474               ['tag', 'config:config-item',
1475                 ['element', 'config:name', 'RasterResolutionX'],
1476                 ['element', 'config:type', 'int'],
1477                 ['data', '1270']],
1478               ['tag', 'config:config-item',
1479                 ['element', 'config:name', 'RasterResolutionY'],
1480                 ['element', 'config:type', 'int'],
1481                 ['data', '1270']],
1482               ['tag', 'config:config-item',
1483                 ['element', 'config:name', 'RasterSubdivisionX'],
1484                 ['element', 'config:type', 'int'],
1485                 ['data', '1']],
1486               ['tag', 'config:config-item',
1487                 ['element', 'config:name', 'RasterSubdivisionY'],
1488                 ['element', 'config:type', 'int'],
1489                 ['data', '1']],
1490               ['tag', 'config:config-item',
1491                 ['element', 'config:name', 'IsRasterAxisSynchronized'],
1492                 ['element', 'config:type', 'boolean'],
1493                 ['data', 'true']],
1494               ['tag', 'config:config-item',
1495                 ['element', 'config:name', 'AutoCalculate'],
1496                 ['element', 'config:type', 'boolean'],
1497                 ['data', 'true']],
1498               ['tag', 'config:config-item',
1499                 ['element', 'config:name', 'PrinterName'],
1500                 ['element', 'config:type', 'string'],
1501                 ['data', 'Generic Printer']],
1502               ['tag', 'config:config-item',
1503                 ['element', 'config:name', 'PrinterSetup'],
1504                 ['element', 'config:type', 'base64Binary'],
1505                 ['data', 'YgH+/0dlbmVyaWMgUHJpbnRlcgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAU0dFTlBSVAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAWAAMAqAAAAAAA//8FAFZUAAAkbQAASm9iRGF0YSAxCnByaW50ZXI9R2VuZXJpYyBQcmludGVyCm9yaWVudGF0aW9uPVBvcnRyYWl0CmNvcGllcz0xCnNjYWxlPTEwMAptYXJnaW5kYWp1c3RtZW50PTAsMCwwLDAKY29sb3JkZXB0aD0yNApwc2xldmVsPTAKY29sb3JkZXZpY2U9MApQUERDb250ZXhEYXRhClBhZ2VTaXplOkxldHRlcgAA']],
1506               ['tag', 'config:config-item',
1507                 ['element', 'config:name', 'ApplyUserData'],
1508                 ['element', 'config:type', 'boolean'],
1509                 ['data', 'true']],
1510               ['tag', 'config:config-item',
1511                 ['element', 'config:name', 'CharacterCompressionType'],
1512                 ['element', 'config:type', 'short'],
1513                 ['data', '0']],
1514               ['tag', 'config:config-item',
1515                 ['element', 'config:name', 'IsKernAsianPunctuation'],
1516                 ['element', 'config:type', 'boolean'],
1517                 ['data', 'false']],
1518               ['tag', 'config:config-item',
1519                 ['element', 'config:name', 'SaveVersionOnClose'],
1520                 ['element', 'config:type', 'boolean'],
1521                 ['data', 'false']],
1522               ['tag', 'config:config-item',
1523                 ['element', 'config:name', 'UpdateFromTemplate'],
1524                 ['element', 'config:type', 'boolean'],
1525                 ['data', 'false']],
1526               ['tag', 'config:config-item',
1527                 ['element', 'config:name', 'AllowPrintJobCancel'],
1528                 ['element', 'config:type', 'boolean'],
1529                 ['data', 'true']],
1530               ['tag', 'config:config-item',
1531                 ['element', 'config:name', 'LoadReadonly'],
1532                 ['element', 'config:type', 'boolean'],
1533                 ['data', 'false']]]]]
1534         # Generate content.xml XML data
1535         xml = XML()
1536         self.lines = xml.convert(self.data)
1537         self.filedata = '\n'.join(self.lines)
1538         # Return generated data
1539         return self.filedata
1540
1541     def _ods_styles(self):
1542         "Generate ods styles.xml data"
1543         self.data = ['tag', 'office:document-styles',
1544           ['element', 'xmlns:office', 'urn:oasis:names:tc:opendocument:xmlns:office:1.0'],
1545           ['element', 'xmlns:style', 'urn:oasis:names:tc:opendocument:xmlns:style:1.0'],
1546           ['element', 'xmlns:text', 'urn:oasis:names:tc:opendocument:xmlns:text:1.0'],
1547           ['element', 'xmlns:table', 'urn:oasis:names:tc:opendocument:xmlns:table:1.0'],
1548           ['element', 'xmlns:draw', 'urn:oasis:names:tc:opendocument:xmlns:drawing:1.0'],
1549           ['element', 'xmlns:fo', 'urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0'],
1550           ['element', 'xmlns:xlink', 'http://www.w3.org/1999/xlink'],
1551           ['element', 'xmlns:dc', 'http://purl.org/dc/elements/1.1/'],
1552           ['element', 'xmlns:meta', 'urn:oasis:names:tc:opendocument:xmlns:meta:1.0'],
1553           ['element', 'xmlns:number', 'urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0'],
1554           ['element', 'xmlns:svg', 'urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0'],
1555           ['element', 'xmlns:chart', 'urn:oasis:names:tc:opendocument:xmlns:chart:1.0'],
1556           ['element', 'xmlns:dr3d', 'urn:oasis:names:tc:opendocument:xmlns:dr3d:1.0'],
1557           ['element', 'xmlns:math', 'http://www.w3.org/1998/Math/MathML'],
1558           ['element', 'xmlns:form', 'urn:oasis:names:tc:opendocument:xmlns:form:1.0'],
1559           ['element', 'xmlns:script', 'urn:oasis:names:tc:opendocument:xmlns:script:1.0'],
1560           ['element', 'xmlns:ooo', 'http://openoffice.org/2004/office'],
1561           ['element', 'xmlns:ooow', 'http://openoffice.org/2004/writer'],
1562           ['element', 'xmlns:oooc', 'http://openoffice.org/2004/calc'],
1563           ['element', 'xmlns:dom', 'http://www.w3.org/2001/xml-events'],
1564           ['element', 'office:version', '1.0'],
1565           ['tag', 'office:font-face-decls',
1566             ['tagline', 'style:font-face',
1567               ['element', 'style:name', 'DejaVu Sans'],
1568               ['element', 'svg:font-family', '&apos;DejaVu Sans&apos;'],
1569               ['element', 'style:font-pitch', 'variable']],
1570             ['tagline', 'style:font-face',
1571               ['element', 'style:name', 'Nimbus Sans L'],
1572               ['element', 'svg:font-family', '&apos;Nimbus Sans L&apos;'],
1573               ['element', 'style:font-family-generic', 'swiss'],
1574               ['element', 'style:font-pitch', 'variable']]],
1575           ['tag', 'office:styles',
1576             ['tag', 'style:default-style',
1577               ['element', 'style:family', 'table-cell'],
1578               ['tagline', 'style:table-cell-properties',
1579                 ['element', 'style:decimal-places', '2']],
1580               ['tagline', 'style:paragraph-properties',
1581                 ['element', 'style:tab-stop-distance', '0.5in']],
1582               ['tagline', 'style:text-properties',
1583                 ['element', 'style:font-name', 'Nimbus Sans L'],
1584                 ['element', 'fo:language', 'en'],
1585                 ['element', 'fo:country', 'US'],
1586                 ['element', 'style:font-name-asian', 'DejaVu Sans'],
1587                 ['element', 'style:language-asian', 'none'],
1588                 ['element', 'style:country-asian', 'none'],
1589                 ['element', 'style:font-name-complex', 'DejaVu Sans'],
1590                 ['element', 'style:language-complex', 'none'],
1591                 ['element', 'style:country-complex', 'none']]],
1592             ['tag', 'number:number-style',
1593               ['element', 'style:name', 'N0'],
1594               ['tagline', 'number:number',
1595                 ['element', 'number:min-integer-digits', '1']]],
1596             ['tag', 'number:currency-style',
1597               ['element', 'style:name', 'N104P0'],
1598               ['element', 'style:volatile', 'true'],
1599               ['tag', 'number:currency-symbol',
1600                 ['element', 'number:language', 'en'],
1601                 ['element', 'number:country', 'US'],
1602                 ['data', '$']],
1603               ['tagline', 'number:number',
1604                 ['element', 'number:decimal-places', '2'],
1605                 ['element', 'number:min-integer-digits', '1'],
1606                 ['element', 'number:grouping', 'true']]],
1607             ['tag', 'number:currency-style',
1608               ['element', 'style:name', 'N104'],
1609               ['tagline', 'style:text-properties',
1610                 ['element', 'fo:color', '#ff0000']],
1611               ['tag', 'number:text',
1612                 ['data', '-']],
1613               ['tag', 'number:currency-symbol',
1614                 ['element', 'number:language', 'en'],
1615                 ['element', 'number:country', 'US'],
1616                 ['data', '$']],
1617               ['tagline', 'number:number', 
1618                 ['element', 'number:decimal-places', '2'],
1619                 ['element', 'number:min-integer-digits', '1'],
1620                 ['element', 'number:grouping', 'true']],
1621               ['tagline', 'style:map',
1622                 ['element', 'style:condition', 'value()&gt;=0'],
1623                 ['element', 'style:apply-style-name', 'N104P0']]],
1624             ['tagline', 'style:style',
1625               ['element', 'style:name', 'Default'],
1626               ['element', 'style:family', 'table-cell']],
1627             ['tag', 'style:style',
1628               ['element', 'style:name', 'Result'],
1629               ['element', 'style:family', 'table-cell'],
1630               ['element', 'style:parent-style-name', 'Default'],
1631               ['tagline', 'style:text-properties',
1632                 ['element', 'fo:font-style', 'italic'],
1633                 ['element', 'style:text-underline-style', 'solid'],
1634                 ['element', 'style:text-underline-width', 'auto'],
1635                 ['element', 'style:text-underline-color', 'font-color'],
1636                 ['element', 'fo:font-weight', 'bold']]],
1637             ['tagline', 'style:style',
1638               ['element', 'style:name', 'Result2'],
1639               ['element', 'style:family', 'table-cell'],
1640               ['element', 'style:parent-style-name', 'Result'],
1641               ['element', 'style:data-style-name', 'N104']],
1642             ['tag', 'style:style',
1643               ['element', 'style:name', 'Heading'],
1644               ['element', 'style:family', 'table-cell'],
1645               ['element', 'style:parent-style-name', 'Default'],
1646               ['tagline', 'style:table-cell-properties',
1647                 ['element', 'style:text-align-source', 'fix'],
1648                 ['element', 'style:repeat-content', 'false']],
1649               ['tagline', 'style:paragraph-properties',
1650                 ['element', 'fo:text-align', 'center']],
1651               ['tagline', 'style:text-properties',
1652                 ['element', 'fo:font-size', '16pt'],
1653                 ['element', 'fo:font-style', 'italic'],
1654                 ['element', 'fo:font-weight', 'bold']]],
1655             ['tag', 'style:style',
1656               ['element', 'style:name', 'Heading1'],
1657               ['element', 'style:family', 'table-cell'],
1658               ['element', 'style:parent-style-name', 'Heading'],
1659               ['tagline', 'style:table-cell-properties',
1660                 ['element', 'style:rotation-angle', '90']]]],
1661           ['tag', 'office:automatic-styles',
1662             ['tag', 'style:page-layout',
1663               ['element', 'style:name', 'pm1'],
1664               ['tagline', 'style:page-layout-properties',
1665                 ['element', 'style:writing-mode', 'lr-tb']],
1666               ['tag', 'style:header-style',
1667                 ['tagline', 'style:header-footer-properties',
1668                   ['element', 'fo:min-height', '0.2957in'],
1669                   ['element', 'fo:margin-left', '0in'],
1670                   ['element', 'fo:margin-right', '0in'],
1671                   ['element', 'fo:margin-bottom', '0.0984in']]],
1672               ['tag', 'style:footer-style',
1673                 ['tagline', 'style:header-footer-properties',
1674                   ['element', 'fo:min-height', '0.2957in'],
1675                   ['element', 'fo:margin-left', '0in'],
1676                   ['element', 'fo:margin-right', '0in'],
1677                   ['element', 'fo:margin-top', '0.0984in']]]],
1678             ['tag', 'style:page-layout',
1679               ['element', 'style:name', 'pm2'],
1680               ['tagline', 'style:page-layout-properties',
1681                 ['element', 'style:writing-mode', 'lr-tb']],
1682               ['tag', 'style:header-style',
1683                 ['tag', 'style:header-footer-properties',
1684                   ['element', 'fo:min-height', '0.2957in'],
1685                   ['element', 'fo:margin-left', '0in'],
1686                   ['element', 'fo:margin-right', '0in'],
1687                   ['element', 'fo:margin-bottom', '0.0984in'],
1688                   ['element', 'fo:border', '0.0346in solid #000000'],
1689                   ['element', 'fo:padding', '0.0071in'],
1690                   ['element', 'fo:background-color', '#c0c0c0'],
1691                   ['tagline', 'style:background-image']]],
1692               ['tag', 'style:footer-style',
1693                 ['tag', 'style:header-footer-properties',
1694                   ['element', 'fo:min-height', '0.2957in'],
1695                   ['element', 'fo:margin-left', '0in'],
1696                   ['element', 'fo:margin-right', '0in'],
1697                   ['element', 'fo:margin-top', '0.0984in'],
1698                   ['element', 'fo:border', '0.0346in solid #000000'],
1699                   ['element', 'fo:padding', '0.0071in'],
1700                   ['element', 'fo:background-color', '#c0c0c0'],
1701                   ['tagline', 'style:background-image']]]]],
1702           ['tag', 'office:master-styles',
1703             ['tag', 'style:master-page',
1704               ['element', 'style:name', 'Default'],
1705               ['element', 'style:page-layout-name', 'pm1'],
1706               ['tag', 'style:header',
1707                 ['tag', 'text:p',
1708                   ['data', '<text:sheet-name>???</text:sheet-name>']]],
1709               ['tagline', 'style:header-left',
1710                 ['element', 'style:display', 'false']],
1711               ['tag', 'style:footer',
1712                 ['tag', 'text:p',
1713                   ['data', 'Page <text:page-number>1</text:page-number>']]],
1714               ['tagline', 'style:footer-left',
1715                 ['element', 'style:display', 'false']]],
1716             ['tag', 'style:master-page',
1717               ['element', 'style:name', 'Report'],
1718               ['element', 'style:page-layout-name', 'pm2'],
1719               ['tag', 'style:header',
1720                 ['tag', 'style:region-left',
1721                   ['tag', 'text:p',
1722                     ['data', '<text:sheet-name>???</text:sheet-name> (<text:title>???</text:title>)']]],
1723                 ['tag', 'style:region-right',
1724                   ['tag', 'text:p',
1725                     ['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>']]]],
1726               ['tagline', 'style:header-left',
1727                 ['element', 'style:display', 'false']],
1728               ['tag', 'style:footer',
1729                 ['tag', 'text:p',
1730                   ['data', 'Page <text:page-number>1</text:page-number> / <text:page-count>99</text:page-count>']]],
1731               ['tagline', 'style:footer-left',
1732                 ['element', 'style:display', 'false']]]]]
1733         # Generate content.xml XML data
1734         xml = XML()
1735         self.lines = xml.convert(self.data)
1736         self.filedata = '\n'.join(self.lines)
1737         # Return generated data
1738         return self.filedata
1739
1740 class Writer:
1741     "Writer Class - Used to create OpenDocument Format Writer Documents."
1742
1743     def __init__(self):
1744         "Initialize ooolib Writer instance"
1745         # Default to no debugging
1746         self.debug = False
1747         self.meta = Meta('odt')
1748
1749     def set_meta(self, metaname, value):
1750         "Set meta data in your document."
1751         self.meta.set_meta(metaname, value)
1752
1753     def save(self, filename):
1754         """Save .odt document
1755
1756         The save function saves the current .odt document.
1757         """
1758         if self.debug: print("Writing %s" % filename)
1759         self.savefile = zipfile.ZipFile(filename, "w")
1760         if self.debug: print("  meta.xml")
1761         self._zip_insert(self.savefile, "meta.xml", self.meta.get_meta())
1762         if self.debug: print("  mimetype")
1763         self._zip_insert(self.savefile, "mimetype", "application/vnd.oasis.opendocument.text")
1764         if self.debug: print("  META-INF/manifest.xml")
1765         self._zip_insert(self.savefile, "META-INF/manifest.xml", self._odt_manifest())
1766         if self.debug: print("  content.xml")
1767         self._zip_insert(self.savefile, "content.xml", self._odt_content())
1768         if self.debug: print("  settings.xml")
1769         # self._zip_insert(self.savefile, "settings.xml", self._odt_settings())
1770         if self.debug: print("  styles.xml")
1771         # self._zip_insert(self.savefile, "styles.xml", self._odt_styles())
1772         # We need to close the file now that we are done creating it.
1773         self.savefile.close()
1774
1775     def _zip_insert(self, file, filename, data):
1776         now = time.localtime(time.time())[:6]
1777         info = zipfile.ZipInfo(filename)
1778         info.date_time = now
1779         info.compress_type = zipfile.ZIP_DEFLATED
1780         file.writestr(info, data)
1781
1782     def _odt_manifest(self):
1783         "Generate odt manifest.xml data"
1784         self.data = ['tag', 'manifest:manifest',
1785           ['element', 'xmlns:manifest', 'urn:oasis:names:tc:opendocument:xmlns:manifest:1.0'],
1786           ['tagline', 'manifest:file-entry',
1787             ['element', 'manifest:media-type', 'application/vnd.oasis.opendocument.text'],
1788             ['element', 'manifest:full-path', '/']],
1789           ['tagline', 'manifest:file-entry',
1790             ['element', 'manifest:media-type', 'text/xml'],
1791             ['element', 'manifest:full-path', 'content.xml']],
1792           ['tagline', 'manifest:file-entry',
1793             ['element', 'manifest:media-type', 'text/xml'],
1794             ['element', 'manifest:full-path', 'styles.xml']],
1795           ['tagline', 'manifest:file-entry',
1796             ['element', 'manifest:media-type', 'text/xml'],
1797             ['element', 'manifest:full-path', 'meta.xml']],
1798           ['tagline', 'manifest:file-entry',
1799             ['element', 'manifest:media-type', 'text/xml'],
1800             ['element', 'manifest:full-path', 'settings.xml']]]
1801         # Generate content.xml XML data
1802         xml = XML()
1803         self.lines = xml.convert(self.data)
1804         self.lines.insert(1, '<!DOCTYPE manifest:manifest PUBLIC "-//OpenOffice.org//DTD Manifest 1.0//EN" "Manifest.dtd">')
1805         self.filedata = '\n'.join(self.lines)
1806         # Return generated data
1807         return self.filedata
1808
1809     def _odt_content(self):
1810         "Generate odt content.xml data"
1811         self.data = ['tag', 'office:document-content',
1812           ['element', 'xmlns:office', 'urn:oasis:names:tc:opendocument:xmlns:office:1.0'],
1813           ['element', 'xmlns:style', 'urn:oasis:names:tc:opendocument:xmlns:style:1.0'],
1814           ['element', 'xmlns:text', 'urn:oasis:names:tc:opendocument:xmlns:text:1.0'],
1815           ['element', 'xmlns:table', 'urn:oasis:names:tc:opendocument:xmlns:table:1.0'],
1816                   ['element', 'xmlns:draw', 'urn:oasis:names:tc:opendocument:xmlns:drawing:1.0'],
1817           ['element', 'xmlns:fo', 'urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0'],
1818           ['element', 'xmlns:xlink', 'http://www.w3.org/1999/xlink'],
1819           ['element', 'xmlns:dc', 'http://purl.org/dc/elements/1.1/'],
1820           ['element', 'xmlns:meta', 'urn:oasis:names:tc:opendocument:xmlns:meta:1.0'],
1821           ['element', 'xmlns:number', 'urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0'],
1822           ['element', 'xmlns:svg', 'urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0'],
1823           ['element', 'xmlns:chart', 'urn:oasis:names:tc:opendocument:xmlns:chart:1.0'],
1824           ['element', 'xmlns:dr3d', 'urn:oasis:names:tc:opendocument:xmlns:dr3d:1.0'],
1825           ['element', 'xmlns:math', 'http://www.w3.org/1998/Math/MathML'],
1826           ['element', 'xmlns:form', 'urn:oasis:names:tc:opendocument:xmlns:form:1.0'],
1827           ['element', 'xmlns:script', 'urn:oasis:names:tc:opendocument:xmlns:script:1.0'],
1828           ['element', 'xmlns:ooo', 'http://openoffice.org/2004/office'],
1829           ['element', 'xmlns:ooow', 'http://openoffice.org/2004/writer'],
1830           ['element', 'xmlns:oooc', 'http://openoffice.org/2004/calc'],
1831           ['element', 'xmlns:dom', 'http://www.w3.org/2001/xml-events'],
1832           ['element', 'xmlns:xforms', 'http://www.w3.org/2002/xforms'],
1833           ['element', 'xmlns:xsd', 'http://www.w3.org/2001/XMLSchema'],
1834           ['element', 'xmlns:xsi', 'http://www.w3.org/2001/XMLSchema-instance'],
1835           ['element', 'office:version', '1.0'],
1836           ['tagline', 'office:scripts'],
1837           ['tag', 'office:font-face-decls',
1838             ['tagline', 'style:font-face',
1839               ['element', 'style:name', 'DejaVu Sans'],
1840               ['element', 'svg:font-family', '&apos;DejaVu Sans&apos;'],
1841               ['element', 'style:font-pitch', 'variable']],
1842                     ['tagline', 'style:font-face',
1843               ['element', 'style:name', 'Nimbus Roman No9 L'],
1844               ['element', 'svg:font-family', '&apos;Nimbus Roman No9 L&apos;'],
1845               ['element', 'style:font-family-generic', 'roman'],
1846               ['element', 'style:font-pitch', 'variable']],
1847             ['tagline', 'style:font-face',
1848               ['element', 'style:name', 'Nimbus Sans L'],
1849               ['element', 'svg:font-family', '&apos;Nimbus Sans L&apos;'],
1850               ['element', 'style:font-family-generic', 'swiss'],
1851               ['element', 'style:font-pitch', 'variable']]],
1852           ['tagline', 'office:automatic-styles'],
1853           ['tag', 'office:body',
1854             ['tag', 'office:text',
1855               ['tagline', 'office:forms',
1856                 ['element', 'form:automatic-focus', 'false'],
1857                         ['element', 'form:apply-design-mode', 'false']],
1858               ['tag', 'text:sequence-decls',
1859                 ['tagline', 'text:sequence-decl',
1860                   ['element', 'text:display-outline-level', '0'],
1861                   ['element', 'text:name', 'Illustration']],
1862                 ['tagline', 'text:sequence-decl',
1863                   ['element', 'text:display-outline-level', '0'],
1864                           ['element', 'text:name', 'Table']],
1865                 ['tagline', 'text:sequence-decl',
1866                   ['element', 'text:display-outline-level', '0'],
1867                   ['element', 'text:name', 'Text']],
1868                 ['tagline', 'text:sequence-decl',
1869                   ['element', 'text:display-outline-level', '0'],
1870                   ['element', 'text:name', 'Drawing']]],
1871               ['tagline', 'text:p',
1872                 ['element', 'text:style-name', 'Standard']]]]]
1873         # Generate content.xml XML data
1874         xml = XML()
1875         self.lines = xml.convert(self.data)
1876         self.filedata = '\n'.join(self.lines)
1877         # Return generated data
1878         return self.filedata