roo 1.9.3 → 1.9.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -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