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