roo 1.9.3 → 1.9.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,503 +1,520 @@
1
- require 'rubygems'
2
- require 'fileutils'
3
- require 'zip/zipfilesystem'
4
- require 'date'
5
- require 'base64'
6
- require 'nokogiri'
7
-
8
- class Openoffice < GenericSpreadsheet
9
-
10
- @@nr = 0
11
-
12
- # initialization and opening of a spreadsheet file
13
- # values for packed: :zip
14
- def initialize(filename, packed=nil, file_warning=:error) #, create = false)
15
- @file_warning = file_warning
16
- super()
17
- @tmpdir = "oo_"+$$.to_s
18
- @tmpdir = File.join(ENV['ROO_TMP'], @tmpdir) if ENV['ROO_TMP']
19
- unless File.exists?(@tmpdir)
20
- FileUtils::mkdir(@tmpdir)
21
- end
22
- filename = open_from_uri(filename) if filename[0,7] == "http://"
23
- filename = unzip(filename) if packed and packed == :zip
24
- begin
25
- file_type_check(filename,'.ods','an openoffice')
26
- #if create and ! File.exists?(filename)
27
- # self.create_openoffice(filename)
28
- #end
29
- @cells_read = Hash.new
30
- #TODO: @cells_read[:default] = false
31
- @filename = filename
32
- unless File.file?(@filename)
33
- raise IOError, "file #{@filename} does not exist"
34
- end
35
- @@nr += 1
36
- @file_nr = @@nr
37
- extract_content
38
- file = File.new(File.join(@tmpdir, @file_nr.to_s+"_roo_content.xml"))
39
- #TODO: @doc = XML::Parser.io(file).parse
40
- @doc = Nokogiri::XML(file)
41
- file.close
42
- ensure
43
- #if ENV["roo_local"] != "thomas-p"
44
- FileUtils::rm_r(@tmpdir)
45
- #end
46
- end
47
- @default_sheet = self.sheets.first
48
- @cell = Hash.new
49
- @cell_type = Hash.new
50
- @formula = Hash.new
51
- @first_row = Hash.new
52
- @last_row = Hash.new
53
- @first_column = Hash.new
54
- @last_column = Hash.new
55
- @style = Hash.new
56
- @style_defaults = Hash.new { |h,k| h[k] = [] }
57
- @style_definitions = Hash.new
58
- @header_line = 1
59
- @labels = {}
60
- end
61
-
62
- # creates a new empty openoffice-spreadsheet file
63
- def create_openoffice(filename) #:nodoc:
64
- #TODO: a better way for creating the file contents
65
- # now you have to call mkbase64...rb to create an include file with all
66
- # the empty files in an openoffice zip-file
67
- load 'base64include.rb'
68
- # puts @@empty_spreadsheet
69
- f = File.open(filename,'wb')
70
- f.print(Base64.decode64(@@empty_spreadsheet))
71
- f.close
72
- end
73
-
74
- # Returns the content of a spreadsheet-cell.
75
- # (1,1) is the upper left corner.
76
- # (1,1), (1,'A'), ('A',1), ('a',1) all refers to the
77
- # cell at the first line and first row.
78
- def cell(row, col, sheet=nil)
79
- sheet = @default_sheet unless sheet
80
- read_cells(sheet) unless @cells_read[sheet]
81
- row,col = normalize(row,col)
82
- if celltype(row,col,sheet) == :date
83
- #TODO: yyyy,mm,dd = @cell[sheet][[row,col]].split('-')
84
- yyyy,mm,dd = @cell[sheet][[row,col]].to_s.split('-')
85
- return Date.new(yyyy.to_i,mm.to_i,dd.to_i)
86
- end
87
- @cell[sheet][[row,col]]
88
- end
89
-
90
- # Returns the formula at (row,col).
91
- # Returns nil if there is no formula.
92
- # The method #formula? checks if there is a formula.
93
- def formula(row,col,sheet=nil)
94
- sheet = @default_sheet unless sheet
95
- read_cells(sheet) unless @cells_read[sheet]
96
- row,col = normalize(row,col)
97
- if @formula[sheet][[row,col]] == nil
98
- return nil
99
- else
100
- return @formula[sheet][[row,col]]["oooc:".length..-1]
101
- end
102
- end
103
-
104
- # true, if there is a formula
105
- def formula?(row,col,sheet=nil)
106
- sheet = @default_sheet unless sheet
107
- read_cells(sheet) unless @cells_read[sheet]
108
- row,col = normalize(row,col)
109
- formula(row,col) != nil
110
- end
111
-
112
- class Font
113
- attr_accessor :bold, :italic, :underline
114
-
115
- def bold?
116
- @bold == 'bold'
117
- end
118
-
119
- def italic?
120
- @italic == 'italic'
121
- end
122
-
123
- def underline?
124
- @underline != nil
125
- end
126
- end
127
-
128
- # Given a cell, return the cell's style
129
- def font(row, col, sheet=nil)
130
- sheet = @default_sheet unless sheet
131
- read_cells(sheet) unless @cells_read[sheet]
132
- row,col = normalize(row,col)
133
- style_name = @style[sheet][[row,col]] || @style_defaults[sheet][col - 1] || 'Default'
134
- @style_definitions[style_name]
135
- end
136
-
137
- # set a cell to a certain value
138
- # (this will not be saved back to the spreadsheet file!)
139
- def set(row,col,value,sheet=nil) #:nodoc:
140
- sheet = @default_sheet unless sheet
141
- read_cells(sheet) unless @cells_read[sheet]
142
- row,col = normalize(row,col)
143
- set_value(row,col,value,sheet)
144
- if value.class == Fixnum
145
- set_type(row,col,:float,sheet)
146
- elsif value.class == String
147
- set_type(row,col,:string,sheet)
148
- elsif value.class == Float
149
- set_type(row,col,:string,sheet)
150
- else
151
- raise ArgumentError, "Type for "+value.to_s+" not set"
152
- end
153
- end
154
-
155
- # returns the type of a cell:
156
- # * :float
157
- # * :string
158
- # * :date
159
- # * :percentage
160
- # * :formula
161
- # * :time
162
- # * :datetime
163
- def celltype(row,col,sheet=nil)
164
- sheet = @default_sheet unless sheet
165
- read_cells(sheet) unless @cells_read[sheet]
166
- row,col = normalize(row,col)
167
- if @formula[sheet][[row,col]]
168
- return :formula
169
- else
170
- @cell_type[sheet][[row,col]]
171
- end
172
- end
173
-
174
- def sheets
175
- return_sheets = []
176
- #TODO: @doc.find("//*[local-name()='table']").each do |sheet|
177
- @doc.xpath("//*[local-name()='table']").each do |sheet|
178
- #TODO: return_sheets << sheet.attributes['name']
179
- return_sheets << sheet['name']
180
- end
181
- return_sheets
182
- end
183
-
184
- # version of the openoffice document
185
- # at 2007 this is always "1.0"
186
- def officeversion
187
- oo_version
188
- @officeversion
189
- end
190
-
191
- # shows the internal representation of all cells
192
- # mainly for debugging purposes
193
- def to_s(sheet=nil)
194
- sheet = @default_sheet unless sheet
195
- read_cells(sheet) unless @cells_read[sheet]
196
- @cell[sheet].inspect
197
- end
198
-
199
- # save spreadsheet
200
- def save #:nodoc:
201
- 42
202
- end
203
-
204
- # returns each formula in the selected sheet as an array of elements
205
- # [row, col, formula]
206
- def formulas(sheet=nil)
207
- theformulas = Array.new
208
- sheet = @default_sheet unless sheet
209
- read_cells(sheet) unless @cells_read[sheet]
210
- first_row(sheet).upto(last_row(sheet)) {|row|
211
- first_column(sheet).upto(last_column(sheet)) {|col|
212
- if formula?(row,col,sheet)
213
- f = [row, col, formula(row,col,sheet)]
214
- theformulas << f
215
- end
216
- }
217
- }
218
- theformulas
219
- end
220
-
221
- # returns the row,col values of the labelled cell
222
- # (nil,nil) if label is not defined
223
- # sheet parameter is not really needed because label names are global
224
- # to the whole spreadsheet
225
- def label(labelname,sheet=nil)
226
- sheet = @default_sheet unless sheet
227
- read_cells(sheet) unless @cells_read[sheet]
228
- if @labels.has_key? labelname
229
- return @labels[labelname][1].to_i,
230
- GenericSpreadsheet.letter_to_number(@labels[labelname][2]),
231
- @labels[labelname][0]
232
- else
233
- return nil,nil,nil
234
- end
235
- end
236
-
237
- private
238
-
239
- # read the version of the OO-Version
240
- def oo_version
241
- #TODO: @doc.find("//*[local-name()='document-content']").each do |office|
242
- @doc.xpath("//*[local-name()='document-content']").each do |office|
243
- @officeversion = office.attributes['version'].to_s
244
- end
245
- end
246
-
247
- # helper function to set the internal representation of cells
248
- def set_cell_values(sheet,x,y,i,v,vt,formula,table_cell,str_v,style_name)
249
- key = [y,x+i]
250
- @cell_type[sheet] = {} unless @cell_type[sheet]
251
- @cell_type[sheet][key] = Openoffice.oo_type_2_roo_type(vt)
252
- @formula[sheet] = {} unless @formula[sheet]
253
- @formula[sheet][key] = formula if formula
254
- @cell[sheet] = {} unless @cell[sheet]
255
- @style[sheet] = {} unless @style[sheet]
256
- @style[sheet][key] = style_name
257
- case @cell_type[sheet][key]
258
- when :float
259
- @cell[sheet][key] = v.to_f
260
- when :string
261
- @cell[sheet][key] = str_v
262
- when :date
263
- #TODO: if table_cell.attributes['date-value'].size != "XXXX-XX-XX".size
264
- if table_cell.attributes['date-value'].to_s.size != "XXXX-XX-XX".size
265
- #-- dann ist noch eine Uhrzeit vorhanden
266
- #-- "1961-11-21T12:17:18"
267
- @cell[sheet][key] = DateTime.parse(table_cell.attributes['date-value'].to_s)
268
- @cell_type[sheet][key] = :datetime
269
- else
270
- @cell[sheet][key] = table_cell.attributes['date-value']
271
- end
272
- when :percentage
273
- @cell[sheet][key] = v.to_f
274
- when :time
275
- hms = v.split(':')
276
- @cell[sheet][key] = hms[0].to_i*3600 + hms[1].to_i*60 + hms[2].to_i
277
- else
278
- @cell[sheet][key] = v
279
- end
280
- end
281
-
282
- # read all cells in the selected sheet
283
- #--
284
- # the following construct means '4 blanks'
285
- # some content <text:s text:c="3"/>
286
- #++
287
- def read_cells(sheet=nil)
288
- sheet = @default_sheet unless sheet
289
- sheet_found = false
290
- raise ArgumentError, "Error: sheet '#{sheet||'nil'}' not valid" if @default_sheet == nil and sheet==nil
291
- raise RangeError unless self.sheets.include? sheet
292
-
293
- #-
294
- # Labels
295
- # should be factored out in separate method because labels are global
296
- # to the whole spreadsheet file (and not to specific sheet)
297
- #+
298
- @doc.xpath("//table:named-range").each do |ne|
299
- #-
300
- # $Sheet1.$C$5
301
- #+
302
- name = ne.attribute('name').to_s
303
- sheetname,coords = ne.attribute('cell-range-address').to_s.split('.')
304
- col = coords.split('$')[1]
305
- row = coords.split('$')[2]
306
- sheetname = sheetname[1..-1] if sheetname[0,1] == '$'
307
- @labels[name] = [sheetname,row,col]
308
- end
309
-
310
- #TODO: @doc.find("//*[local-name()='table']").each do |ws|
311
- @doc.xpath("//*[local-name()='table']").each do |ws|
312
- #TODO: if sheet == ws.attributes['name']
313
- if sheet == ws['name']
314
- sheet_found = true
315
- col = 1
316
- row = 1
317
- #TODO: ws.each_element do |table_element|
318
- ws.children.each do |table_element|
319
- case table_element.name
320
- when 'table-column'
321
- @style_defaults[sheet] << table_element.attributes['default-cell-style-name']
322
- when 'table-row'
323
- if table_element.attributes['number-rows-repeated']
324
- #TODO: skip_row = table_element.attributes['number-rows-repeated'].to_i
325
- skip_row = table_element.attributes['number-rows-repeated'].to_s.to_i
326
- row = row + skip_row - 1
327
- end
328
- #TODO: table_element.each_element do |cell|
329
- table_element.children.each do |cell|
330
- #TODO: skip_col = cell.attributes['number-columns-repeated']
331
- skip_col = cell['number-columns-repeated']
332
- #TODO: formula = cell.attributes['formula']
333
- formula = cell['formula']
334
- #TODO: vt = cell.attributes['value-type']
335
- vt = cell['value-type']
336
- #TODO: v = cell.attributes['value']
337
- v = cell['value']
338
- #TODO: style_name = cell.attributes['style-name']
339
- style_name = cell['style-name']
340
- if vt == 'string'
341
- str_v = ''
342
- # insert \n if there is more than one paragraph
343
- para_count = 0
344
- #TODO: cell.each_element do |str|
345
- cell.children.each do |str|
346
- if str.name == 'p'
347
- v = str.content
348
- str_v += "\n" if para_count > 0
349
- para_count += 1
350
- if str.children.size > 1
351
- str_v += children_to_string(str.children)
352
- else
353
- str.children.each do |child|
354
- str_v += child.content #.text
355
- end
356
- end
357
- str_v.gsub!(/&apos;/,"'") # special case not supported by unescapeHTML
358
- str_v = CGI.unescapeHTML(str_v)
359
- end # == 'p'
360
- end
361
- elsif vt == 'time'
362
- #TODO: cell.each_element do |str|
363
- cell.children.each do |str|
364
- if str.name == 'p'
365
- v = str.content
366
- end
367
- end
368
- elsif vt == '' or vt == nil
369
- #
370
- elsif vt == 'date'
371
- #
372
- elsif vt == 'percentage'
373
- #
374
- elsif vt == 'float'
375
- #
376
- elsif vt == 'boolean'
377
- v = cell.attributes['boolean-value'].to_s
378
- else
379
- # raise "unknown type #{vt}"
380
- end
381
- if skip_col
382
- if v != nil or cell.attributes['date-value']
383
- 0.upto(skip_col.to_i-1) do |i|
384
- set_cell_values(sheet,col,row,i,v,vt,formula,cell,str_v,style_name)
385
- end
386
- end
387
- col += (skip_col.to_i - 1)
388
- end # if skip
389
- set_cell_values(sheet,col,row,0,v,vt,formula,cell,str_v,style_name)
390
- col += 1
391
- end
392
- row += 1
393
- col = 1
394
- end
395
- end
396
- end
397
- end
398
-
399
- #TODO: @doc.find("//*[local-name()='automatic-styles']").each do |style|
400
- @doc.xpath("//*[local-name()='automatic-styles']").each do |style|
401
- read_styles(style)
402
- end
403
- if !sheet_found
404
- raise RangeError
405
- end
406
- @cells_read[sheet] = true
407
- end
408
-
409
- def read_styles(style_elements)
410
- @style_definitions['Default'] = Openoffice::Font.new
411
- style_elements.each do |style|
412
- next unless style.name == 'style'
413
- style_name = style.attributes['name']
414
- style.each do |properties|
415
- font = Openoffice::Font.new
416
- font.bold = properties.attributes['font-weight']
417
- font.italic = properties.attributes['font-style']
418
- font.underline = properties.attributes['text-underline-style']
419
- @style_definitions[style_name] = font
420
- end
421
- end
422
- end
423
-
424
- # Checks if the default_sheet exists. If not an RangeError exception is
425
- # raised
426
- def check_default_sheet
427
- sheet_found = false
428
- raise ArgumentError, "Error: default_sheet not set" if @default_sheet == nil
429
- sheet_found = true if sheets.include?(@default_sheet)
430
- if ! sheet_found
431
- raise RangeError, "sheet '#{@default_sheet}' not found"
432
- end
433
- end
434
-
435
- def process_zipfile(zip, path='')
436
- if zip.file.file? path
437
- if path == "content.xml"
438
- open(File.join(@tmpdir, @file_nr.to_s+'_roo_content.xml'),'wb') {|f|
439
- f << zip.read(path)
440
- }
441
- end
442
- else
443
- unless path.empty?
444
- path += '/'
445
- end
446
- zip.dir.foreach(path) do |filename|
447
- process_zipfile(zip, path+filename)
448
- end
449
- end
450
- end
451
-
452
- def extract_content
453
- Zip::ZipFile.open(@filename) do |zip|
454
- process_zipfile(zip)
455
- end
456
- end
457
-
458
- def set_value(row,col,value,sheet=nil)
459
- sheet = @default_value unless sheet
460
- @cell[sheet][[row,col]] = value
461
- end
462
-
463
- def set_type(row,col,type,sheet=nil)
464
- sheet = @default_value unless sheet
465
- @cell_type[sheet][[row,col]] = type
466
- end
467
-
468
- A_ROO_TYPE = {
469
- "float" => :float,
470
- "string" => :string,
471
- "date" => :date,
472
- "percentage" => :percentage,
473
- "time" => :time,
474
- }
475
-
476
- def Openoffice.oo_type_2_roo_type(ootype)
477
- return A_ROO_TYPE[ootype]
478
- end
479
-
480
- # helper method to convert compressed spaces and other elements within
481
- # an text into a string
482
- def children_to_string(children)
483
- result = ''
484
- children.each {|child|
485
- if child.text?
486
- result = result + child.content
487
- else
488
- if child.name == 's'
489
- compressed_spaces = child.attributes['c'].to_s.to_i
490
- # no explicit number means a count of 1:
491
- if compressed_spaces == 0
492
- compressed_spaces = 1
493
- end
494
- result = result + " "*compressed_spaces
495
- else
496
- result = result + child.content
497
- end
498
- end
499
- }
500
- result
501
- end
502
-
503
- end # class
1
+ require 'rubygems'
2
+ require 'fileutils'
3
+ require 'zip/zipfilesystem'
4
+ require 'date'
5
+ require 'base64'
6
+ require 'nokogiri'
7
+ require 'cgi'
8
+
9
+ class Openoffice < GenericSpreadsheet
10
+
11
+ @@nr = 0
12
+
13
+ # initialization and opening of a spreadsheet file
14
+ # values for packed: :zip
15
+ def initialize(filename, packed=nil, file_warning=:error, tmpdir=nil) #, create = false)
16
+ @file_warning = file_warning
17
+ super()
18
+ @tmpdir = "oo_"+$$.to_s
19
+ @tmpdir = File.join(ENV['ROO_TMP'], @tmpdir) if ENV['ROO_TMP']
20
+ @tmpdir = File.join(tmpdir, @tmpdir) if tmpdir
21
+ unless File.exists?(@tmpdir)
22
+ FileUtils::mkdir(@tmpdir)
23
+ end
24
+ filename = open_from_uri(filename) if filename[0,7] == "http://"
25
+ filename = unzip(filename) if packed and packed == :zip
26
+ begin
27
+ begin
28
+ file_type_check(filename,'.ods','an openoffice')
29
+ rescue TypeError
30
+ FileUtils::rm_r @tmpdir
31
+ raise
32
+ end
33
+ #if create and ! File.exists?(filename)
34
+ # self.create_openoffice(filename)
35
+ #end
36
+ @cells_read = Hash.new
37
+ #TODO: @cells_read[:default] = false
38
+ @filename = filename
39
+ unless File.file?(@filename)
40
+ raise IOError, "file #{@filename} does not exist"
41
+ end
42
+ @@nr += 1
43
+ @file_nr = @@nr
44
+ extract_content
45
+ file = File.new(File.join(@tmpdir, @file_nr.to_s+"_roo_content.xml"))
46
+ @doc = Nokogiri::XML(file)
47
+ file.close
48
+ ensure
49
+ unless Dir.glob(@tmpdir).empty?
50
+ FileUtils::rm_r(@tmpdir)
51
+ end
52
+ end
53
+ @default_sheet = self.sheets.first
54
+ @cell = Hash.new
55
+ @cell_type = Hash.new
56
+ @formula = Hash.new
57
+ @first_row = Hash.new
58
+ @last_row = Hash.new
59
+ @first_column = Hash.new
60
+ @last_column = Hash.new
61
+ @style = Hash.new
62
+ @style_defaults = Hash.new { |h,k| h[k] = [] }
63
+ @style_definitions = Hash.new
64
+ @header_line = 1
65
+ @labels = {}
66
+ end
67
+
68
+ def method_missing(m,*args)
69
+ # is method name a label name
70
+ if @labels.has_key?(m.to_s)
71
+ row,col = label(m.to_s)
72
+ cell(row,col)
73
+ else
74
+ # call super for methods like #a1
75
+ super
76
+ end
77
+ end
78
+
79
+ # creates a new empty openoffice-spreadsheet file
80
+ def create_openoffice(filename) #:nodoc:
81
+ #TODO: a better way for creating the file contents
82
+ # now you have to call mkbase64...rb to create an include file with all
83
+ # the empty files in an openoffice zip-file
84
+ load 'base64include.rb'
85
+ # puts @@empty_spreadsheet
86
+ f = File.open(filename,'wb')
87
+ f.print(Base64.decode64(@@empty_spreadsheet))
88
+ f.close
89
+ end
90
+
91
+ # Returns the content of a spreadsheet-cell.
92
+ # (1,1) is the upper left corner.
93
+ # (1,1), (1,'A'), ('A',1), ('a',1) all refers to the
94
+ # cell at the first line and first row.
95
+ def cell(row, col, sheet=nil)
96
+ sheet = @default_sheet unless sheet
97
+ read_cells(sheet) unless @cells_read[sheet]
98
+ row,col = normalize(row,col)
99
+ if celltype(row,col,sheet) == :date
100
+ #TODO: yyyy,mm,dd = @cell[sheet][[row,col]].split('-')
101
+ yyyy,mm,dd = @cell[sheet][[row,col]].to_s.split('-')
102
+ return Date.new(yyyy.to_i,mm.to_i,dd.to_i)
103
+ end
104
+ @cell[sheet][[row,col]]
105
+ end
106
+
107
+ # Returns the formula at (row,col).
108
+ # Returns nil if there is no formula.
109
+ # The method #formula? checks if there is a formula.
110
+ def formula(row,col,sheet=nil)
111
+ sheet = @default_sheet unless sheet
112
+ read_cells(sheet) unless @cells_read[sheet]
113
+ row,col = normalize(row,col)
114
+ if @formula[sheet][[row,col]] == nil
115
+ return nil
116
+ else
117
+ return @formula[sheet][[row,col]]["oooc:".length..-1]
118
+ end
119
+ end
120
+
121
+ # true, if there is a formula
122
+ def formula?(row,col,sheet=nil)
123
+ sheet = @default_sheet unless sheet
124
+ read_cells(sheet) unless @cells_read[sheet]
125
+ row,col = normalize(row,col)
126
+ formula(row,col) != nil
127
+ end
128
+
129
+ class Font
130
+ attr_accessor :bold, :italic, :underline
131
+
132
+ def bold?
133
+ @bold == 'bold'
134
+ end
135
+
136
+ def italic?
137
+ @italic == 'italic'
138
+ end
139
+
140
+ def underline?
141
+ @underline != nil
142
+ end
143
+ end
144
+
145
+ # Given a cell, return the cell's style
146
+ def font(row, col, sheet=nil)
147
+ sheet = @default_sheet unless sheet
148
+ read_cells(sheet) unless @cells_read[sheet]
149
+ row,col = normalize(row,col)
150
+ style_name = @style[sheet][[row,col]] || @style_defaults[sheet][col - 1] || 'Default'
151
+ @style_definitions[style_name]
152
+ end
153
+
154
+ # set a cell to a certain value
155
+ # (this will not be saved back to the spreadsheet file!)
156
+ def set(row,col,value,sheet=nil) #:nodoc:
157
+ sheet = @default_sheet unless sheet
158
+ read_cells(sheet) unless @cells_read[sheet]
159
+ row,col = normalize(row,col)
160
+ set_value(row,col,value,sheet)
161
+ if value.class == Fixnum
162
+ set_type(row,col,:float,sheet)
163
+ elsif value.class == String
164
+ set_type(row,col,:string,sheet)
165
+ elsif value.class == Float
166
+ set_type(row,col,:string,sheet)
167
+ else
168
+ raise ArgumentError, "Type for "+value.to_s+" not set"
169
+ end
170
+ end
171
+
172
+ # returns the type of a cell:
173
+ # * :float
174
+ # * :string
175
+ # * :date
176
+ # * :percentage
177
+ # * :formula
178
+ # * :time
179
+ # * :datetime
180
+ def celltype(row,col,sheet=nil)
181
+ sheet = @default_sheet unless sheet
182
+ read_cells(sheet) unless @cells_read[sheet]
183
+ row,col = normalize(row,col)
184
+ if @formula[sheet][[row,col]]
185
+ return :formula
186
+ else
187
+ @cell_type[sheet][[row,col]]
188
+ end
189
+ end
190
+
191
+ def sheets
192
+ return_sheets = []
193
+ #TODO: @doc.find("//*[local-name()='table']").each do |sheet|
194
+ @doc.xpath("//*[local-name()='table']").each do |sheet|
195
+ #TODO: return_sheets << sheet.attributes['name']
196
+ return_sheets << sheet['name']
197
+ end
198
+ return_sheets
199
+ end
200
+
201
+ # version of the openoffice document
202
+ # at 2007 this is always "1.0"
203
+ def officeversion
204
+ oo_version
205
+ @officeversion
206
+ end
207
+
208
+ # shows the internal representation of all cells
209
+ # mainly for debugging purposes
210
+ def to_s(sheet=nil)
211
+ sheet = @default_sheet unless sheet
212
+ read_cells(sheet) unless @cells_read[sheet]
213
+ @cell[sheet].inspect
214
+ end
215
+
216
+ # save spreadsheet
217
+ def save #:nodoc:
218
+ 42
219
+ end
220
+
221
+ # returns each formula in the selected sheet as an array of elements
222
+ # [row, col, formula]
223
+ def formulas(sheet=nil)
224
+ theformulas = Array.new
225
+ sheet = @default_sheet unless sheet
226
+ read_cells(sheet) unless @cells_read[sheet]
227
+ first_row(sheet).upto(last_row(sheet)) {|row|
228
+ first_column(sheet).upto(last_column(sheet)) {|col|
229
+ if formula?(row,col,sheet)
230
+ f = [row, col, formula(row,col,sheet)]
231
+ theformulas << f
232
+ end
233
+ }
234
+ }
235
+ theformulas
236
+ end
237
+
238
+ # returns the row,col values of the labelled cell
239
+ # (nil,nil) if label is not defined
240
+ # sheet parameter is not really needed because label names are global
241
+ # to the whole spreadsheet
242
+ def label(labelname,sheet=nil)
243
+ sheet = @default_sheet unless sheet
244
+ read_cells(sheet) unless @cells_read[sheet]
245
+ if @labels.has_key? labelname
246
+ return @labels[labelname][1].to_i,
247
+ GenericSpreadsheet.letter_to_number(@labels[labelname][2]),
248
+ @labels[labelname][0]
249
+ else
250
+ return nil,nil,nil
251
+ end
252
+ end
253
+
254
+ private
255
+
256
+ # read the version of the OO-Version
257
+ def oo_version
258
+ #TODO: @doc.find("//*[local-name()='document-content']").each do |office|
259
+ @doc.xpath("//*[local-name()='document-content']").each do |office|
260
+ @officeversion = office.attributes['version'].to_s
261
+ end
262
+ end
263
+
264
+ # helper function to set the internal representation of cells
265
+ def set_cell_values(sheet,x,y,i,v,vt,formula,table_cell,str_v,style_name)
266
+ key = [y,x+i]
267
+ @cell_type[sheet] = {} unless @cell_type[sheet]
268
+ @cell_type[sheet][key] = Openoffice.oo_type_2_roo_type(vt)
269
+ @formula[sheet] = {} unless @formula[sheet]
270
+ @formula[sheet][key] = formula if formula
271
+ @cell[sheet] = {} unless @cell[sheet]
272
+ @style[sheet] = {} unless @style[sheet]
273
+ @style[sheet][key] = style_name
274
+ case @cell_type[sheet][key]
275
+ when :float
276
+ @cell[sheet][key] = v.to_f
277
+ when :string
278
+ @cell[sheet][key] = str_v
279
+ when :date
280
+ #TODO: if table_cell.attributes['date-value'].size != "XXXX-XX-XX".size
281
+ if table_cell.attributes['date-value'].to_s.size != "XXXX-XX-XX".size
282
+ #-- dann ist noch eine Uhrzeit vorhanden
283
+ #-- "1961-11-21T12:17:18"
284
+ @cell[sheet][key] = DateTime.parse(table_cell.attributes['date-value'].to_s)
285
+ @cell_type[sheet][key] = :datetime
286
+ else
287
+ @cell[sheet][key] = table_cell.attributes['date-value']
288
+ end
289
+ when :percentage
290
+ @cell[sheet][key] = v.to_f
291
+ when :time
292
+ hms = v.split(':')
293
+ @cell[sheet][key] = hms[0].to_i*3600 + hms[1].to_i*60 + hms[2].to_i
294
+ else
295
+ @cell[sheet][key] = v
296
+ end
297
+ end
298
+
299
+ # read all cells in the selected sheet
300
+ #--
301
+ # the following construct means '4 blanks'
302
+ # some content <text:s text:c="3"/>
303
+ #++
304
+ def read_cells(sheet=nil)
305
+ sheet = @default_sheet unless sheet
306
+ sheet_found = false
307
+ raise ArgumentError, "Error: sheet '#{sheet||'nil'}' not valid" if @default_sheet == nil and sheet==nil
308
+ raise RangeError unless self.sheets.include? sheet
309
+
310
+ #-
311
+ # Labels
312
+ # should be factored out in separate method because labels are global
313
+ # to the whole spreadsheet file (and not to specific sheet)
314
+ #+
315
+ @doc.xpath("//table:named-range").each do |ne|
316
+ #-
317
+ # $Sheet1.$C$5
318
+ #+
319
+ name = ne.attribute('name').to_s
320
+ sheetname,coords = ne.attribute('cell-range-address').to_s.split('.')
321
+ col = coords.split('$')[1]
322
+ row = coords.split('$')[2]
323
+ sheetname = sheetname[1..-1] if sheetname[0,1] == '$'
324
+ @labels[name] = [sheetname,row,col]
325
+ end
326
+
327
+ #TODO: @doc.find("//*[local-name()='table']").each do |ws|
328
+ @doc.xpath("//*[local-name()='table']").each do |ws|
329
+ #TODO: if sheet == ws.attributes['name']
330
+ if sheet == ws['name']
331
+ sheet_found = true
332
+ col = 1
333
+ row = 1
334
+ #TODO: ws.each_element do |table_element|
335
+ ws.children.each do |table_element|
336
+ case table_element.name
337
+ when 'table-column'
338
+ @style_defaults[sheet] << table_element.attributes['default-cell-style-name']
339
+ when 'table-row'
340
+ if table_element.attributes['number-rows-repeated']
341
+ #TODO: skip_row = table_element.attributes['number-rows-repeated'].to_i
342
+ skip_row = table_element.attributes['number-rows-repeated'].to_s.to_i
343
+ row = row + skip_row - 1
344
+ end
345
+ #TODO: table_element.each_element do |cell|
346
+ table_element.children.each do |cell|
347
+ #TODO: skip_col = cell.attributes['number-columns-repeated']
348
+ skip_col = cell['number-columns-repeated']
349
+ #TODO: formula = cell.attributes['formula']
350
+ formula = cell['formula']
351
+ #TODO: vt = cell.attributes['value-type']
352
+ vt = cell['value-type']
353
+ #TODO: v = cell.attributes['value']
354
+ v = cell['value']
355
+ #TODO: style_name = cell.attributes['style-name']
356
+ style_name = cell['style-name']
357
+ if vt == 'string'
358
+ str_v = ''
359
+ # insert \n if there is more than one paragraph
360
+ para_count = 0
361
+ #TODO: cell.each_element do |str|
362
+ cell.children.each do |str|
363
+ if str.name == 'p'
364
+ v = str.content
365
+ str_v += "\n" if para_count > 0
366
+ para_count += 1
367
+ if str.children.size > 1
368
+ str_v += children_to_string(str.children)
369
+ else
370
+ str.children.each do |child|
371
+ str_v += child.content #.text
372
+ end
373
+ end
374
+ str_v.gsub!(/&apos;/,"'") # special case not supported by unescapeHTML
375
+ str_v = CGI.unescapeHTML(str_v)
376
+ end # == 'p'
377
+ end
378
+ elsif vt == 'time'
379
+ #TODO: cell.each_element do |str|
380
+ cell.children.each do |str|
381
+ if str.name == 'p'
382
+ v = str.content
383
+ end
384
+ end
385
+ elsif vt == '' or vt == nil
386
+ #
387
+ elsif vt == 'date'
388
+ #
389
+ elsif vt == 'percentage'
390
+ #
391
+ elsif vt == 'float'
392
+ #
393
+ elsif vt == 'boolean'
394
+ v = cell.attributes['boolean-value'].to_s
395
+ else
396
+ # raise "unknown type #{vt}"
397
+ end
398
+ if skip_col
399
+ if v != nil or cell.attributes['date-value']
400
+ 0.upto(skip_col.to_i-1) do |i|
401
+ set_cell_values(sheet,col,row,i,v,vt,formula,cell,str_v,style_name)
402
+ end
403
+ end
404
+ col += (skip_col.to_i - 1)
405
+ end # if skip
406
+ set_cell_values(sheet,col,row,0,v,vt,formula,cell,str_v,style_name)
407
+ col += 1
408
+ end
409
+ row += 1
410
+ col = 1
411
+ end
412
+ end
413
+ end
414
+ end
415
+
416
+ #TODO: @doc.find("//*[local-name()='automatic-styles']").each do |style|
417
+ @doc.xpath("//*[local-name()='automatic-styles']").each do |style|
418
+ read_styles(style)
419
+ end
420
+ if !sheet_found
421
+ raise RangeError
422
+ end
423
+ @cells_read[sheet] = true
424
+ end
425
+
426
+ def read_styles(style_elements)
427
+ @style_definitions['Default'] = Openoffice::Font.new
428
+ style_elements.each do |style|
429
+ next unless style.name == 'style'
430
+ style_name = style.attributes['name']
431
+ style.each do |properties|
432
+ font = Openoffice::Font.new
433
+ font.bold = properties.attributes['font-weight']
434
+ font.italic = properties.attributes['font-style']
435
+ font.underline = properties.attributes['text-underline-style']
436
+ @style_definitions[style_name] = font
437
+ end
438
+ end
439
+ end
440
+
441
+ # Checks if the default_sheet exists. If not an RangeError exception is
442
+ # raised
443
+ def check_default_sheet
444
+ sheet_found = false
445
+ raise ArgumentError, "Error: default_sheet not set" if @default_sheet == nil
446
+ sheet_found = true if sheets.include?(@default_sheet)
447
+ if ! sheet_found
448
+ raise RangeError, "sheet '#{@default_sheet}' not found"
449
+ end
450
+ end
451
+
452
+ def process_zipfile(zip, path='')
453
+ if zip.file.file? path
454
+ if path == "content.xml"
455
+ open(File.join(@tmpdir, @file_nr.to_s+'_roo_content.xml'),'wb') {|f|
456
+ f << zip.read(path)
457
+ }
458
+ end
459
+ else
460
+ unless path.empty?
461
+ path += '/'
462
+ end
463
+ zip.dir.foreach(path) do |filename|
464
+ process_zipfile(zip, path+filename)
465
+ end
466
+ end
467
+ end
468
+
469
+ def extract_content
470
+ Zip::ZipFile.open(@filename) do |zip|
471
+ process_zipfile(zip)
472
+ end
473
+ end
474
+
475
+ def set_value(row,col,value,sheet=nil)
476
+ sheet = @default_value unless sheet
477
+ @cell[sheet][[row,col]] = value
478
+ end
479
+
480
+ def set_type(row,col,type,sheet=nil)
481
+ sheet = @default_value unless sheet
482
+ @cell_type[sheet][[row,col]] = type
483
+ end
484
+
485
+ A_ROO_TYPE = {
486
+ "float" => :float,
487
+ "string" => :string,
488
+ "date" => :date,
489
+ "percentage" => :percentage,
490
+ "time" => :time,
491
+ }
492
+
493
+ def Openoffice.oo_type_2_roo_type(ootype)
494
+ return A_ROO_TYPE[ootype]
495
+ end
496
+
497
+ # helper method to convert compressed spaces and other elements within
498
+ # an text into a string
499
+ def children_to_string(children)
500
+ result = ''
501
+ children.each {|child|
502
+ if child.text?
503
+ result = result + child.content
504
+ else
505
+ if child.name == 's'
506
+ compressed_spaces = child.attributes['c'].to_s.to_i
507
+ # no explicit number means a count of 1:
508
+ if compressed_spaces == 0
509
+ compressed_spaces = 1
510
+ end
511
+ result = result + " "*compressed_spaces
512
+ else
513
+ result = result + child.content
514
+ end
515
+ end
516
+ }
517
+ result
518
+ end
519
+
520
+ end # class