roo 0.6.1 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
data/History.txt CHANGED
@@ -1,3 +1,12 @@
1
+ == 0.7.0 2007-11-23
2
+ * 6 enhancements:
3
+ * Openoffice/Excel: the most methods can be called with an option 'sheet'
4
+ parameter which will be used instead of the default sheet
5
+ * Excel: improved the speed of CVS output
6
+ * Openoffice/Excel: new method #column
7
+ * Openoffice/Excel: new method #find
8
+ * Openoffice/Excel: new method #info
9
+ * better exception if a spreadsheet file does not exist
1
10
  == 0.6.1 2007-10-06
2
11
  * 2 enhancements:
3
12
  * Openoffice: percentage-values are now treated as numbers (not strings)
data/lib/roo/excel.rb CHANGED
@@ -2,8 +2,23 @@ require 'rubygems'
2
2
  gem 'parseexcel', '>= 0.5.2'
3
3
  require 'parseexcel'
4
4
 
5
+ module Spreadsheet
6
+ module ParseExcel
7
+ class Worksheet
8
+ include Enumerable
9
+ attr_reader :min_row, :max_row, :min_col, :max_col
10
+ end
11
+ end
12
+ end
13
+
14
+
15
+ # Class Excel is derived from class Openoffice. It implements almost all methods
16
+ # from the Openoffice class in the same way.
17
+ # Parameter packed: :zip - File is a zip-file
5
18
  class Excel < Openoffice
6
19
 
20
+ EXCEL_NO_FORMULAS = 'formulas are not supported for excel spreadsheets'
21
+
7
22
  def initialize(filename, packed = nil)
8
23
  @tmpdir = "oo_"+$$.to_s
9
24
  unless File.exists?(@tmpdir)
@@ -15,28 +30,35 @@ class Excel < Openoffice
15
30
  warn "are you sure, this is an excel file?"
16
31
  end
17
32
  @filename = filename
33
+ unless File.file?(@filename)
34
+ raise IOError, "file #{@filename} does not exist"
35
+ end
18
36
  @workbook = Spreadsheet::ParseExcel.parse(filename)
19
37
  @default_sheet = nil
20
38
  # no need to set default_sheet if there is only one sheet in the document
21
39
  if self.sheets.size == 1
22
40
  @default_sheet = self.sheets.first
23
41
  end
24
- @first_row = @last_row = @first_column = @last_column = nil
42
+ # @first_row = @last_row = @first_column = @last_column = nil
43
+ #if ENV["roo_local"] != "thomas-p"
44
+ FileUtils::rm_r(@tmpdir)
45
+ #end
46
+ @first_row = Hash.new
47
+ @last_row = Hash.new
48
+ @first_column = Hash.new
49
+ @last_column = Hash.new
50
+ @cells_read = Hash.new
25
51
  end
26
52
 
27
53
  def sheets
28
- #if DateTime.now < Date.new(2007,6,10)
29
- # return ["Tabelle1", "Name of Sheet 2", "Sheet3"]
30
- #else
31
- result = []
32
- 0.upto(@workbook.sheet_count - 1) do |i|
33
- # TODO: is there a better way to do conversion?
34
- result << Iconv.new('utf-8','unicode').iconv(
35
- @workbook.worksheet(i).name
36
- )
37
- end
38
- return result
39
- #end
54
+ result = []
55
+ 0.upto(@workbook.sheet_count - 1) do |i|
56
+ # TODO: is there a better way to do conversion?
57
+ result << Iconv.new('utf-8','unicode').iconv(
58
+ @workbook.worksheet(i).name
59
+ )
60
+ end
61
+ return result
40
62
  end
41
63
 
42
64
  # sets the working sheet (1,2,3,..)
@@ -50,14 +72,15 @@ class Excel < Openoffice
50
72
  else
51
73
  raise TypeError, "what are you trying to set as default sheet?"
52
74
  end
53
- @first_row = @last_row = @first_column = @last_column = nil
54
- @cells_read = false
75
+ @first_row[n] = @last_row[n] = @first_column[n] = @last_column[n] = nil
76
+ @cells_read[n] = false
55
77
  end
56
78
 
57
79
  # returns the content of a cell. The upper left corner is (1,1) or ('A',1)
58
- def cell(row,col)
80
+ def cell(row,col,sheet=nil)
81
+ sheet = @default_sheet unless sheet
59
82
  row,col = normalize(row,col)
60
- worksheet = @workbook.worksheet(sheet_no(@default_sheet))
83
+ worksheet = @workbook.worksheet(sheet_no(sheet))
61
84
  skip = 0
62
85
  line = 1
63
86
  worksheet.each(skip) { |row_par|
@@ -72,90 +95,163 @@ class Excel < Openoffice
72
95
  when :text then return cell.to_s('utf-8')
73
96
  when :date then return cell.date
74
97
  else
75
- return cell.to_s('utf-8')
98
+ return nil # cell.to_s('utf-8')
76
99
  end
77
100
  end
78
101
  line += 1
79
102
  }
80
103
  end
81
104
 
82
- # returns the type of a cell: "float", "string", "date"
83
- def celltype(row,col)
105
+ # returns the type of a cell: :float, :string, :date
106
+ def celltype(row,col,sheet=nil)
107
+ default_sheet_check if sheet == nil
108
+ sheet = @default_sheet unless sheet
84
109
  row,col = normalize(row,col)
85
- default_sheet_check
86
- worksheet = @workbook.worksheet(sheet_no(@default_sheet))
110
+
111
+ worksheet = @workbook.worksheet(sheet_no(sheet))
87
112
  skip = 0
88
113
  line = 1
89
114
  worksheet.each(skip) { |row_par|
90
115
  if line == row
116
+ return nil unless row_par
91
117
  cell = row_par.at(col-1)
92
118
  return nil unless cell
93
119
  case cell.type
94
- when :numeric then return :float
95
- when :text then return :string
96
- when :date then return :date
97
- else return cell.type.to_sym
120
+ when :numeric
121
+ return :float
122
+ when :text
123
+ return :string
124
+ when :date
125
+ return :date
126
+ else
127
+ return cell.type.to_sym
98
128
  end
99
129
  end
100
130
  line += 1
101
131
  }
102
132
  end
103
133
 
104
- # return this row a an array off cells
105
- def row(rownumber)
134
+ # returns all values in this row as an array
135
+ # row numbers are 1,2,3,... like in the spreadsheet
136
+ def row(rownumber,sheet=nil)
137
+ sheet = @default_sheet unless sheet
106
138
  default_sheet_check
107
- worksheet = @workbook.worksheet(sheet_no(@default_sheet))
108
- therow = worksheet.row(rownumber-1)
139
+ worksheet = @workbook.worksheet(sheet_no(sheet))
140
+ #therow = worksheet.row(rownumber-1)
109
141
  result = []
110
- therow.each {|cell|
111
- case cell.type
112
- when :numeric then result << cell.to_i
113
- when :text then result << cell.to_s('utf-8')
114
- when :date then result << cell.date
142
+ worksheet.row(rownumber-1).each {|cell|
143
+ if cell
144
+ case cell.type
145
+ when :numeric then result << cell.to_i
146
+ when :text then result << cell.to_s('utf-8')
147
+ when :date then result << cell.date
148
+ else
149
+ result << cell.to_s('utf-8')
150
+ end
115
151
  else
116
- return result << cell.to_s('utf-8')
152
+ result << nil
117
153
  end
118
154
  }
119
155
  return result
120
156
  end
121
157
 
158
+ # returns all values in this column as an array
159
+ # column numbers are 1,2,3,... like in the spreadsheet
160
+ def column(columnnumber,sheet=nil)
161
+ if columnnumber.class == String
162
+ columnnumber = Openoffice.letter_to_number(columnnumber)
163
+ end
164
+ sheet = @default_sheet unless sheet
165
+ default_sheet_check
166
+ worksheet = @workbook.worksheet(sheet_no(sheet))
167
+ skip = 0
168
+ result = []
169
+ worksheet.each(skip) { |row_par|
170
+ if defined? row_par.at(columnnumber-1)
171
+ cell = row_par.at(columnnumber-1)
172
+ #if defined? cell = row_par.at(columnnumber-1)
173
+ if cell
174
+ case cell.type
175
+ when :numeric then result << cell.to_i
176
+ when :text then result << cell.to_s('utf-8')
177
+ when :date then result << cell.date
178
+ else
179
+ result << cell.to_s('utf-8')
180
+ end
181
+ else
182
+ result << nil
183
+ end
184
+ else
185
+ result << nil
186
+ end
187
+ }
188
+ result
189
+ end
190
+
122
191
  # returns the first non empty column
123
- def first_column
124
- return @first_column if @first_column
125
- fr, lr, fc, lc = get_firsts_lasts
192
+ def first_column(sheet=nil)
193
+ sheet = @default_sheet unless sheet
194
+ return @first_column[sheet] if @first_column[sheet]
195
+ fr, lr, fc, lc = get_firsts_lasts(sheet)
126
196
  fc
127
197
  end
128
198
 
129
199
  # returns the last non empty column
130
- def last_column
131
- return @last_column if @last_column
132
- fr, lr, fc, lc = get_firsts_lasts
200
+ def last_column(sheet=nil)
201
+ sheet = @default_sheet unless sheet
202
+ return @last_column[sheet] if @last_column[sheet]
203
+ fr, lr, fc, lc = get_firsts_lasts(sheet)
133
204
  lc
134
205
  end
135
206
 
136
207
  # returns the first non empty row
137
- def first_row
138
- return @first_row if @first_row
139
- fr, lr, fc, lc = get_firsts_lasts
208
+ def first_row(sheet=nil)
209
+ sheet = @default_sheet unless sheet
210
+ return @first_row[sheet] if @first_row[sheet]
211
+ fr, lr, fc, lc = get_firsts_lasts(sheet)
140
212
  fr
141
213
  end
142
214
 
143
215
  # returns the last non empty row
144
- def last_row
145
- return @last_row if @last_row
146
- fr, lr, fc, lc = get_firsts_lasts
216
+ def last_row(sheet=nil)
217
+ sheet = @default_sheet unless sheet
218
+ return @last_row[sheet] if @last_row[sheet]
219
+ fr, lr, fc, lc = get_firsts_lasts(sheet)
147
220
  lr
148
221
  end
149
222
 
150
223
  # true if a cell is empty
151
- def empty?(row, col)
224
+ def empty?(row, col, sheet=nil)
225
+ sheet = @default_sheet unless sheet
152
226
  row,col = normalize(row,col)
153
- return true if row < first_row || row > last_row || col < first_column || col > last_column
154
- return true unless cell(row, col)
155
- return true if celltype(row, col) == "string" && cell(row, col) == ""
227
+ return true if row < first_row(sheet) || row > last_row(sheet) || col < first_column(sheet) || col > last_column(sheet)
228
+ return true unless cell(row, col, sheet)
229
+ return true if celltype(row, col, sheet) == "string" && cell(row, col, sheet) == ""
156
230
  false
157
231
  end
158
232
 
233
+ # first non-empty column as a letter
234
+ def first_column_as_letter(sheet=nil)
235
+ sheet = @default_sheet unless sheet
236
+ Openoffice.number_to_letter(first_column(sheet))
237
+ end
238
+
239
+ # last non-empty column as a letter
240
+ def last_column_as_letter(sheet=nil)
241
+ sheet = @default_sheet unless sheet
242
+ Openoffice.number_to_letter(last_column(sheet))
243
+ end
244
+
245
+ def formula(row,col,sheet=nil)
246
+ raise EXCEL_NO_FORMULAS
247
+ end
248
+ def formula?(row,col,sheet=nil)
249
+ raise EXCEL_NO_FORMULAS
250
+ end
251
+ def formulas(sheet=nil)
252
+ raise EXCEL_NO_FORMULAS
253
+ end
254
+
159
255
  private
160
256
 
161
257
  # check if default_sheet was set
@@ -164,14 +260,33 @@ private
164
260
  end
165
261
 
166
262
  # determine the first and last boundaries
167
- def get_firsts_lasts
263
+ def get_firsts_lasts(sheet=nil)
264
+ sheet = @default_sheet unless sheet
168
265
  fr = fc = 999_999
169
266
  lr = lc = -999_999
170
- worksheet = @workbook.worksheet(sheet_no(@default_sheet))
267
+ #TODO: worksheet = @workbook.worksheet(sheet_no(@default_sheet))
268
+ worksheet = @workbook.worksheet(sheet_no(sheet))
269
+ if false #=============================================================
270
+ if @filename == "test/false_encoding.xls"
271
+ #assert_instance_of(Spreadsheet::ParseExcel::Worksheet, worksheet)
272
+ p worksheet.class
273
+
274
+ p worksheet.methods
275
+ p '### min_row: '+worksheet.min_row.to_s
276
+ p '### max_row: '+worksheet.max_row.to_s
277
+ p '### min_col: '+worksheet.min_col.to_s
278
+ p '### max_col: '+worksheet.max_col.to_s
279
+ end
280
+ if @filename == "test/false_encoding.xls"
281
+ p worksheet
282
+ end
171
283
  skip = 0
172
284
  line = 1
173
285
  worksheet.each(skip) { |row_par|
174
286
  if row_par
287
+ if @filename == "test/false_encoding.xls"
288
+ p row_par
289
+ end
175
290
  row_par.each_with_index {|cell,i|
176
291
  # nicht beruecksichtigen, wenn nil und vorher noch nichts war
177
292
  if cell
@@ -184,10 +299,23 @@ private
184
299
  end
185
300
  line += 1
186
301
  }
187
- @first_row = fr
188
- @last_row = lr
189
- @first_column = fc
190
- @last_column = lc
302
+ end #=============================================================
303
+ fr = worksheet.min_row + 1
304
+ lr = worksheet.max_row + 1
305
+ fc = worksheet.min_col + 1
306
+ lc = worksheet.max_col + 1
307
+ # 2007-11-05 BEGIN
308
+ # parsexcel liefert (mir unverstaendlich) eine Zeile als letzte Zeile
309
+ # zurueck, die aber leer ist. Deshalb Korrekturfunktion, die wirklich
310
+ # die letzte nicht leere Zeile liefert.
311
+ while empty_row? row(lr,sheet)
312
+ lr -= 1
313
+ end
314
+ # 2007-11-05 END
315
+ @first_row[sheet] = fr
316
+ @last_row[sheet] = lr
317
+ @first_column[sheet] = fc
318
+ @last_column[sheet] = lc
191
319
  return fr, lr, fc, lc
192
320
  end
193
321
 
@@ -204,4 +332,48 @@ private
204
332
  raise StandardError, "sheet '#{name}' not found"
205
333
  end
206
334
 
335
+ def write_csv_content(file=nil,sheet=nil)
336
+ file = STDOUT unless file
337
+ sheet = @default_sheet unless sheet
338
+ worksheet = @workbook.worksheet(sheet_no(sheet))
339
+ skip = 0
340
+ worksheet.each(skip) { |row_par|
341
+ 1.upto(row_par.length) {|col|
342
+ file.print(",") if col > 1
343
+ cell = row_par.at(col-1)
344
+ unless cell
345
+ empty = true
346
+ else
347
+ case cell.type
348
+ when :numeric
349
+ onecelltype = :float
350
+ onecell = cell.to_f
351
+ when :text
352
+ onecelltype = :string
353
+ onecell = cell.to_s('utf-8')
354
+ when :date
355
+ onecelltype = :date
356
+ onecell = cell.date
357
+ else
358
+ onecelltype = nil
359
+ onecell = nil
360
+ end
361
+ end
362
+ file.print one_cell_output(onecelltype,onecell,empty)
363
+ }
364
+ file.print("\n")
365
+ }
366
+ end
367
+
368
+ def empty_row?(row)
369
+ content = false
370
+ row.each {|elem|
371
+ if elem != ''
372
+ #if elem.class == String and elem.size > 0
373
+ content = true
374
+ end
375
+ }
376
+ ! content
377
+ end
378
+
207
379
  end
@@ -4,16 +4,19 @@ require 'rexml/document'
4
4
  require 'fileutils'
5
5
  require 'zip/zipfilesystem'
6
6
  require 'date'
7
- #require 'llip'
8
7
  require 'base64'
9
- require 'logger'
10
8
 
11
- #require 'lib/roo/spreadsheetparser'
9
+ # The class Openoffice defines the basic functionality for the access to
10
+ # spreadsheet documents. Other classes like _Excel_ are defined as subclasses
11
+ # of this class.
12
12
 
13
13
  class Openoffice
14
14
 
15
15
  @@nr = 0
16
16
 
17
+ # sets the line with attribute names (default: 1)
18
+ attr_accessor :header_line
19
+
17
20
  # initialization and opening of a spreadsheet file
18
21
  # values for packed: :zip
19
22
  def initialize(filename, packed=nil) #, create = false)
@@ -29,17 +32,18 @@ class Openoffice
29
32
  #if create and ! File.exists?(filename)
30
33
  # self.create_openoffice(filename)
31
34
  #end
32
- @cells_read = false
35
+ @cells_read = Hash.new
36
+ #TODO: @cells_read[:default] = false
33
37
  @filename = filename
38
+ unless File.file?(@filename)
39
+ raise IOError, "file #{@filename} does not exist"
40
+ end
34
41
  @@nr += 1
35
42
  @file_nr = @@nr
36
43
  extract_content
37
44
  file = File.new(File.join(@tmpdir, @file_nr.to_s+"_roo_content.xml"))
38
45
  @doc = REXML::Document.new file
39
46
  file.close
40
- @cell = Hash.new
41
- @cell_type = Hash.new
42
- @formula = Hash.new
43
47
  #if ENV["roo_local"] != "thomas-p"
44
48
  FileUtils::rm_r(@tmpdir)
45
49
  #end
@@ -48,15 +52,18 @@ class Openoffice
48
52
  if self.sheets.size == 1
49
53
  @default_sheet = self.sheets.first
50
54
  end
51
- @first_column = @last_column = nil
52
- @first_row = @last_row = nil
53
- # trap('INT') {
54
- # FileUtils::rm_r(@tmpdir)
55
- # }
55
+ @cell = Hash.new
56
+ @cell_type = Hash.new
57
+ @formula = Hash.new
58
+ @first_row = Hash.new
59
+ @last_row = Hash.new
60
+ @first_column = Hash.new
61
+ @last_column = Hash.new
62
+ @header_line = 1
56
63
  end
57
64
 
58
65
  # creates a new empty openoffice-spreadsheet file
59
- def create_openoffice(filename)
66
+ def create_openoffice(filename) #:nodoc:
60
67
  #TODO: a better way for creating the file contents
61
68
  # now you have to call mkbase64...rb to create an include file with all
62
69
  # the empty files in an openoffice zip-file
@@ -69,70 +76,80 @@ class Openoffice
69
76
 
70
77
  # reopens and read a spreadsheet document
71
78
  def reload
72
- default_sheet = @default_sheet
79
+ ds = @default_sheet
73
80
  initialize(@filename)
74
- self.default_sheet = default_sheet
75
- @first_row = @last_row = @first_column = @last_column = nil
81
+ self.default_sheet = ds
82
+ #@first_row = @last_row = @first_column = @last_column = nil
76
83
  end
77
84
 
78
- # returns the content of a spreadsheet-cell
79
- # (1,1) is the upper left corner
85
+ # Returns the content of a spreadsheet-cell.
86
+ # (1,1) is the upper left corner.
80
87
  # (1,1), (1,'A'), ('A',1), ('a',1) all refers to the
81
- # cell at first line, first row
82
- def cell(row,col)
83
- read_cells unless @cells_read
88
+ # cell at the first line and first row.
89
+ def cell(row, col, sheet=nil)
90
+ sheet = @default_sheet unless sheet
91
+ read_cells(sheet) unless @cells_read[sheet]
84
92
  row,col = normalize(row,col)
85
- if celltype(row,col) == :date
86
- yyyy,mm,dd = @cell["#{row},#{col}"].split('-')
93
+ if celltype(row,col,sheet) == :date
94
+ yyyy,mm,dd = @cell[sheet]["#{row},#{col}"].split('-')
87
95
  return Date.new(yyyy.to_i,mm.to_i,dd.to_i)
88
96
  end
89
- @cell["#{row},#{col}"]
97
+ @cell[sheet]["#{row},#{col}"]
90
98
  end
91
99
 
92
- # returns the formula at (row,col)
93
- # nil if there is no formula
94
- def formula(row,col)
95
- read_cells unless @cells_read
100
+ # Returns the formula at (row,col).
101
+ # Returns nil if there is no formula.
102
+ # The method Openoffice#formula? checks if there is a formula.
103
+ def formula(row,col,sheet=nil)
104
+ sheet = @default_sheet unless sheet
105
+ read_cells(sheet) unless @cells_read[sheet]
96
106
  row,col = normalize(row,col)
97
- if @formula["#{row},#{col}"] == nil
107
+ if @formula[sheet]["#{row},#{col}"] == nil
98
108
  return nil
99
109
  else
100
- return @formula["#{row},#{col}"]["oooc:".length..-1]
110
+ return @formula[sheet]["#{row},#{col}"]["oooc:".length..-1]
101
111
  end
102
112
  end
103
113
 
104
114
  # true, if there is a formula
105
- def formula?(row,col)
106
- read_cells unless @cells_read
115
+ def formula?(row,col,sheet=nil)
116
+ sheet = @default_sheet unless sheet
117
+ read_cells unless @cells_read[sheet]
107
118
  row,col = normalize(row,col)
108
119
  formula(row,col) != nil
109
120
  end
110
121
 
111
122
  # set a cell to a certain value
112
123
  # (this will not be saved back to the spreadsheet file!)
113
- def set(row,col,value)
114
- read_cells unless @cells_read
124
+ def set(row,col,value,sheet=nil) #:nodoc:
125
+ sheet = @default_sheet unless sheet
126
+ read_cells(sheet) unless @cells_read[sheet]
115
127
  row,col = normalize(row,col)
116
- set_value(row,col,value)
128
+ set_value(row,col,value,sheet)
117
129
  if value.class == Fixnum
118
- set_type(row,col,:float)
130
+ set_type(row,col,:float,sheet)
119
131
  elsif value.class == String
120
- set_type(row,col,:string)
132
+ set_type(row,col,:string,sheet)
121
133
  elsif value.class == Float
122
- set_type(row,col,:string)
134
+ set_type(row,col,:string,sheet)
123
135
  else
124
- raise ArgumentError, "Typ fuer "+value.to_s+" nicht gesetzt"
136
+ raise ArgumentError, "Type for "+value.to_s+" not set"
125
137
  end
126
138
  end
127
139
 
128
- # returns the open-office type of a cell
129
- def celltype(row,col)
130
- read_cells unless @cells_read
140
+ # returns the type of a cell:
141
+ # * :float
142
+ # * :string,
143
+ # * :date
144
+ # * :percentage
145
+ def celltype(row,col,sheet=nil)
146
+ sheet = @default_sheet unless sheet
147
+ read_cells(sheet) unless @cells_read[sheet]
131
148
  row,col = normalize(row,col)
132
- if @formula["#{row},#{col}"]
149
+ if @formula[sheet]["#{row},#{col}"]
133
150
  return :formula
134
151
  else
135
- @cell_type["#{row},#{col}"]
152
+ @cell_type[sheet]["#{row},#{col}"]
136
153
  end
137
154
  end
138
155
 
@@ -175,12 +192,9 @@ class Openoffice
175
192
  raise TypeError, "what are you trying to set as default sheet?"
176
193
  end
177
194
  @default_sheet = sheet
178
- @first_row = @last_row = @first_column = @last_column = nil
179
- @cells_read = false
180
- @cell = Hash.new
181
- @cell_type = Hash.new
182
- @formula = Hash.new
183
195
  check_default_sheet
196
+ @first_row[sheet] = @last_row[sheet] = @first_column[sheet] = @last_column[sheet] = nil
197
+ @cells_read[sheet] = false
184
198
  end
185
199
 
186
200
  alias set_default_sheet default_sheet=
@@ -195,18 +209,20 @@ class Openoffice
195
209
 
196
210
  # shows the internal representation of all cells
197
211
  # mainly for debugging purposes
198
- def to_s
199
- read_cells unless @cells_read
200
- @cell.inspect
212
+ def to_s(sheet=nil)
213
+ sheet = @default_sheet unless sheet
214
+ read_cells(sheet) unless @cells_read[sheet]
215
+ @cell[sheet].inspect
201
216
  end
202
217
 
203
218
  # returns all values in this row as an array
204
219
  # row numbers are 1,2,3,... like in the spreadsheet
205
- def row(rownumber)
206
- read_cells unless @cells_read
220
+ def row(rownumber,sheet=nil)
221
+ sheet = @default_sheet unless sheet
222
+ read_cells(sheet) unless @cells_read[sheet]
207
223
  result = []
208
224
  tmp_arr = []
209
- @cell.each_pair {|key,value|
225
+ @cell[sheet].each_pair {|key,value|
210
226
  y,x = key.split(',')
211
227
  x = x.to_i
212
228
  y = y.to_i
@@ -220,105 +236,129 @@ class Openoffice
220
236
  end
221
237
  result
222
238
  end
223
-
239
+
240
+ # returns all values in this column as an array
241
+ # column numbers are 1,2,3,... like in the spreadsheet
242
+ def column(columnnumber,sheet=nil)
243
+ if columnnumber.class == String
244
+ columnnumber = Openoffice.letter_to_number(columnnumber)
245
+ end
246
+ sheet = @default_sheet unless sheet
247
+ read_cells(sheet) unless @cells_read[sheet]
248
+ result = []
249
+ first_row(sheet).upto(last_row(sheet)) do |row|
250
+ result << cell(row,columnnumber,sheet)
251
+ end
252
+ result
253
+ end
254
+
224
255
  # returns the number of the last non-empty row
225
- def last_row
226
- read_cells unless @cells_read
227
- if @last_row
228
- return @last_row
256
+ def last_row(sheet=nil)
257
+ sheet = @default_sheet unless sheet
258
+ read_cells(sheet) unless @cells_read[sheet]
259
+ if @last_row[sheet]
260
+ return @last_row[sheet]
229
261
  end
230
262
  impossible_value = 0
231
263
  result = impossible_value
232
- @cell.each_pair {|key,value|
264
+ @cell[sheet].each_pair {|key,value|
233
265
  y,x = key.split(',')
234
266
  y = y.to_i
235
267
  result = [result, y].max if value
236
268
  }
237
269
  result = nil if result == impossible_value
238
- @last_row = result
270
+ @last_row[sheet] = result
239
271
  result
240
272
  end
241
273
 
242
274
  # returns the number of the last non-empty column
243
- def last_column
244
- read_cells unless @cells_read
245
- if @last_column
246
- return @last_column
275
+ def last_column(sheet=nil)
276
+ sheet = @default_sheet unless sheet
277
+ read_cells(sheet) unless @cells_read[sheet]
278
+ if @last_column[sheet]
279
+ return @last_column[sheet]
247
280
  end
248
281
  impossible_value = 0
249
282
  result = impossible_value
250
- @cell.each_pair {|key,value|
283
+ @cell[sheet].each_pair {|key,value|
251
284
  y,x = key.split(',')
252
285
  x = x.to_i
253
286
  result = [result, x].max if value
254
287
  }
255
288
  result = nil if result == impossible_value
256
- @last_column = result
289
+ @last_column[sheet] = result
257
290
  result
258
291
  end
259
292
 
260
293
  # returns the number of the first non-empty row
261
- def first_row
262
- read_cells unless @cells_read
263
- if @first_row
264
- return @first_row
294
+ def first_row(sheet=nil)
295
+ if sheet == nil
296
+ sheet = @default_sheet
297
+ end
298
+ read_cells(sheet) unless @cells_read[sheet]
299
+ if @first_row[sheet]
300
+ return @first_row[sheet]
265
301
  end
266
302
  impossible_value = 999_999 # more than a spreadsheet can hold
267
303
  result = impossible_value
268
- @cell.each_pair {|key,value|
304
+ @cell[sheet].each_pair {|key,value|
269
305
  y,x = key.split(',')
270
306
  y = y.to_i
271
307
  result = [result, y].min if value
272
308
  }
273
309
  result = nil if result == impossible_value
274
- @first_row = result
310
+ @first_row[sheet] = result
275
311
  result
276
312
  end
277
313
 
278
314
  # returns the number of the first non-empty column
279
- def first_column
280
- read_cells unless @cells_read
281
- if @first_column
282
- return @first_column
315
+ def first_column(sheet=nil)
316
+ if sheet == nil
317
+ sheet = @default_sheet
318
+ end
319
+ read_cells(sheet) unless @cells_read[sheet]
320
+ if @first_column[sheet]
321
+ return @first_column[sheet]
283
322
  end
284
323
  impossible_value = 999_999 # more than a spreadsheet can hold
285
324
  result = impossible_value
286
- @cell.each_pair {|key,value|
325
+ @cell[sheet].each_pair {|key,value|
287
326
  y,x = key.split(',')
288
327
  x = x.to_i
289
328
  result = [result, x].min if value
290
329
  }
291
330
  result = nil if result == impossible_value
292
- @first_column = result
331
+ @first_column[sheet] = result
293
332
  result
294
333
  end
295
334
 
296
335
  # first non-empty column as a letter
297
- def first_column_as_letter
298
- Openoffice.number_to_letter(first_column)
336
+ def first_column_as_letter(sheet=nil)
337
+ Openoffice.number_to_letter(first_column(sheet))
299
338
  end
300
339
 
301
340
  # last non-empty column as a letter
302
- def last_column_as_letter
303
- Openoffice.number_to_letter(last_column)
341
+ def last_column_as_letter(sheet=nil)
342
+ Openoffice.number_to_letter(last_column(sheet))
304
343
  end
305
344
 
306
345
  # true if cell is empty
307
- def empty?(row, col)
308
- read_cells unless @cells_read
309
- return true unless cell(row, col)
310
- return true if celltype(row, col) == :string && cell(row, col).empty?
346
+ def empty?(row, col, sheet=nil)
347
+ sheet = @default_sheet unless sheet
348
+ read_cells(sheet) unless @cells_read[sheet]
349
+ return true unless cell(row, col, sheet)
350
+ return true if celltype(row, col, sheet) == :string && cell(row, col, sheet).empty?
311
351
  false
312
352
  end
313
353
 
314
354
  # save spreadsheet
315
- def save
355
+ def save #:nodoc:
316
356
  42
317
357
  end
318
358
 
319
359
  # evaluate the formula at this cell
320
360
  # experimental: DO NOT USE THIS!
321
- def solve(row,col)
361
+ def solve(row,col) #:nodoc:
322
362
  parser = SpreadsheetParser.new
323
363
  visitor = Visitor.new
324
364
  #puts cell(row,col)
@@ -332,13 +372,14 @@ class Openoffice
332
372
 
333
373
  # returns each formula in the selected sheet as an array of elements
334
374
  # [row, col, formula]
335
- def formulas
375
+ def formulas(sheet=nil)
336
376
  theformulas = Array.new
337
- read_cells unless @cells_read
338
- first_row.upto(last_row) {|row|
339
- first_column.upto(last_column) {|col|
340
- if formula?(row,col)
341
- f = [row, col, formula(row,col)]
377
+ sheet = @default_sheet unless sheet
378
+ read_cells(sheet) unless @cells_read[sheet]
379
+ first_row(sheet).upto(last_row(sheet)) {|row|
380
+ first_column(sheet).upto(last_column(sheet)) {|col|
381
+ if formula?(row,col,sheet)
382
+ f = [row, col, formula(row,col,sheet)]
342
383
  theformulas << f
343
384
  end
344
385
  }
@@ -349,19 +390,20 @@ class Openoffice
349
390
  # returns a rectangular area (default: all cells) as yaml-output
350
391
  # you can add additional attributes with the prefix parameter like:
351
392
  # oo.to_yaml({"file"=>"flightdata_2007-06-26", "sheet" => "1"})
352
- def to_yaml(prefix={}, from_row=nil, from_column=nil, to_row=nil, to_column=nil)
393
+ def to_yaml(prefix={}, from_row=nil, from_column=nil, to_row=nil, to_column=nil,sheet=nil)
394
+ sheet = @default_sheet unless sheet
353
395
  result = "--- \n"
354
- (from_row||first_row).upto(to_row||last_row) do |row|
355
- (from_column||first_column).upto(to_column||last_column) do |col|
356
- unless self.empty?(row,col)
396
+ (from_row||first_row(sheet)).upto(to_row||last_row(sheet)) do |row|
397
+ (from_column||first_column(sheet)).upto(to_column||last_column(sheet)) do |col|
398
+ unless empty?(row,col,sheet)
357
399
  result << "cell_#{row}_#{col}: \n"
358
400
  prefix.each {|k,v|
359
401
  result << " #{k}: #{v} \n"
360
402
  }
361
403
  result << " row: #{row} \n"
362
404
  result << " col: #{col} \n"
363
- result << " celltype: #{self.celltype(row,col)} \n"
364
- result << " value: #{self.cell(row,col)} \n"
405
+ result << " celltype: #{self.celltype(row,col,sheet)} \n"
406
+ result << " value: #{self.cell(row,col,sheet)} \n"
365
407
  end
366
408
  end
367
409
  end
@@ -369,20 +411,120 @@ class Openoffice
369
411
  end
370
412
 
371
413
  # write the current spreadsheet to stdout or into a file
372
- def to_csv(filename=nil)
414
+ def to_csv(filename=nil,sheet=nil)
415
+ sheet = @default_sheet unless sheet
373
416
  if filename
374
417
  file = File.open(filename,"w") # do |file|
375
- write_csv_content(file)
418
+ write_csv_content(file,sheet)
376
419
  file.close
377
420
  else
378
- write_csv_content
421
+ write_csv_content(STDOUT,sheet)
379
422
  end
380
423
  true
381
424
  end
382
425
 
383
- protected
426
+ # find a row either by row number or a condition
427
+ # Caution: this works only within the default sheet -> set default_sheet before you call this method
428
+ # (experimental. see examples in the test_roo.rb file)
429
+ def find(*args)
430
+ result_array = false
431
+ args.each {|arg,val|
432
+ if arg.class == Hash
433
+ arg.each { |hkey,hval|
434
+ if hkey == :array and hval == true
435
+ result_array = true
436
+ end
437
+ }
438
+ end
439
+ }
440
+ column_with = {}
441
+ 1.upto(last_column) do |col|
442
+ column_with[cell(@header_line,col)] = col
443
+ end
444
+ result = Array.new
445
+ #-- id
446
+ if args[0].class == Fixnum
447
+ rownum = args[0]
448
+ tmp = {}
449
+ 1.upto(self.row(rownum).size) {|j|
450
+ x = ''
451
+ column_with.each { |key,val|
452
+ if val == j
453
+ x = key
454
+ end
455
+ }
456
+ tmp[x] = cell(rownum,j)
457
+ }
458
+ result = [ tmp ] # row(rownum)
459
+ #-- :all
460
+ elsif args[0] == :all
461
+ if args[1].class == Hash
462
+ args[1].each {|key,val|
463
+ if key == :conditions
464
+ column_with = {}
465
+ 1.upto(last_column) do |col|
466
+ column_with[cell(@header_line,col)] = col
467
+ end
468
+ conditions = val
469
+ first_row.upto(last_row) do |i|
470
+ # are all conditions met?
471
+ found = 1
472
+ conditions.each { |key,val|
473
+ if cell(i,column_with[key]) == val
474
+ found *= 1
475
+ else
476
+ found *= 0
477
+ end
478
+ }
479
+ # p self.row(i) if found > 0
480
+ if found > 0
481
+ tmp = {}
482
+ 1.upto(self.row(i).size) {|j|
483
+ x = ''
484
+ column_with.each { |key,val|
485
+ if val == j
486
+ x = key
487
+ end
488
+ }
489
+ tmp[x] = cell(i,j)
490
+ }
491
+ if result_array
492
+ result << self.row(i)
493
+ else
494
+ result << tmp
495
+ end
496
+ end
497
+ end
498
+ end # :conditions
499
+ }
500
+ end
501
+ end
502
+ result
503
+ end
504
+
505
+ # Returns information of the spreadsheet document and all sheets within
506
+ # this document.
507
+ def info
508
+ result = "File: #{@filename}\n"+
509
+ "Number of sheets: #{sheets.size}\n"+
510
+ "Sheets: #{sheets.map{|sheet| sheet+", "}.to_s[0..-3]}\n"
511
+ n = 1
512
+ sheets.each {|sheet|
513
+ self.default_sheet = sheet
514
+ result << "Sheet " + n.to_s + ":\n"
515
+ result << " First row: #{first_row}\n"
516
+ result << " Last row: #{last_row}\n"
517
+ result << " First column: #{Openoffice.number_to_letter(first_column)}\n"
518
+ result << " Last column: #{Openoffice.number_to_letter(last_column)}"
519
+ result << "\n" if sheet != sheets.last
520
+ n += 1
521
+ }
522
+ result
523
+ end
384
524
 
385
- def process_zipfile_packed(zip, path='')
525
+ private
526
+
527
+ def process_zipfile_packed(zip, path='')
386
528
  ret=nil
387
529
  if zip.file.file? path
388
530
  # extract and return filename
@@ -405,7 +547,6 @@ protected
405
547
  ret
406
548
  end
407
549
 
408
- private
409
550
  def unzip(filename)
410
551
  ret = nil
411
552
  Zip::ZipFile.open(filename) do |zip|
@@ -416,27 +557,31 @@ private
416
557
 
417
558
  # read the version of the OO-Version
418
559
  def oo_version
419
- sheet_found = false
560
+ #sheet_found = false
420
561
  @doc.each_element do |oo_document|
421
562
  @officeversion = oo_document.attributes['version']
422
563
  end
423
564
  end
424
565
 
425
- def belegen(x,y,i,v,vt,formula,tr,str_v)
426
- @cell_type["#{y},#{x+i}"] = Openoffice.oo_type_2_roo_type(vt)
427
- @formula["#{y},#{x+i}"] = formula if formula
428
- case @cell_type["#{y},#{x+i}"]
566
+ def set_cell_values(sheet,x,y,i,v,vt,formula,tr,str_v)
567
+ key = "#{y},#{x+i}"
568
+ @cell_type[sheet] = {} unless @cell_type[sheet]
569
+ @cell_type[sheet][key] = Openoffice.oo_type_2_roo_type(vt)
570
+ @formula[sheet] = {} unless @formula[sheet]
571
+ @formula[sheet][key] = formula if formula
572
+ @cell[sheet] = {} unless @cell[sheet]
573
+ case @cell_type[sheet][key]
429
574
  when :float
430
- @cell["#{y},#{x+i}"] = v.to_f
575
+ @cell[sheet][key] = v.to_f
431
576
  when :string
432
- @cell["#{y},#{x+i}"] = str_v
577
+ @cell[sheet][key] = str_v
433
578
  when :date
434
- @cell["#{y},#{x+i}"] = tr.attributes['date-value']
579
+ @cell[sheet][key] = tr.attributes['date-value']
435
580
  when :percentage
436
- @cell["#{y},#{x+i}"] = v.to_f
581
+ @cell[sheet][key] = v.to_f
437
582
  else
438
- @cell["#{y},#{x+i}"] = v
439
- end
583
+ @cell[sheet][key] = v
584
+ end
440
585
  end
441
586
 
442
587
  # read all cells in the selected sheet
@@ -444,9 +589,10 @@ private
444
589
  # the following construct means '4 blanks'
445
590
  # some content <text:s text:c="3"/>
446
591
  #++
447
- def read_cells
592
+ def read_cells(sheet=nil)
593
+ sheet = @default_sheet unless sheet
448
594
  sheet_found = false
449
- raise ArgumentError, "Error: default_sheet not set" if @default_sheet == nil
595
+ raise ArgumentError, "Error: sheet '#{sheet||'nil'}' not valid" if @default_sheet == nil and sheet==nil
450
596
  oo_document_count = 0
451
597
  @doc.each_element do |oo_document|
452
598
  # @officeversion = oo_document.attributes['version']
@@ -459,7 +605,7 @@ private
459
605
  if be.name == "spreadsheet"
460
606
  be.each_element do |se|
461
607
  if se.name == "table"
462
- if se.attributes['name']==@default_sheet
608
+ if se.attributes['name']==sheet #@default_sheet
463
609
  sheet_found = true
464
610
  x=1
465
611
  y=1
@@ -502,12 +648,12 @@ private
502
648
  if skip
503
649
  if v != nil or tr.attributes['date-value']
504
650
  0.upto(skip.to_i-1) do |i|
505
- belegen(x,y,i,v,vt,formula,tr,str_v)
651
+ set_cell_values(sheet,x,y,i,v,vt,formula,tr,str_v)
506
652
  end
507
653
  end
508
654
  x += (skip.to_i - 1)
509
655
  end # if skip
510
- belegen(x,y,0,v,vt,formula,tr,str_v)
656
+ set_cell_values(sheet,x,y,0,v,vt,formula,tr,str_v)
511
657
  x += 1
512
658
  end
513
659
  end
@@ -523,10 +669,10 @@ private
523
669
  end
524
670
  end
525
671
  end
526
- if sheet_found == false
672
+ if !sheet_found
527
673
  raise RangeError
528
674
  end
529
- @cells_read = true
675
+ @cells_read[sheet] = true
530
676
  end
531
677
 
532
678
  # Checks if the default_sheet exists. If not an RangeError exception is
@@ -615,7 +761,9 @@ private
615
761
  result = 0
616
762
  while letters && letters.length > 0
617
763
  character = letters[0,1].upcase
618
- num = "ABCDEFGHIJKLMNOPQRSTUVWXYZ".index(character)+1
764
+ num = "ABCDEFGHIJKLMNOPQRSTUVWXYZ".index(character)
765
+ raise ArgumentError, "invalid column character '#{letters[0,1]}'" if num == nil
766
+ num += 1
619
767
  result = result * 26 + num
620
768
  letters = letters[1..-1]
621
769
  end
@@ -623,12 +771,14 @@ private
623
771
  end
624
772
 
625
773
 
626
- def set_value(row,col,value)
627
- @cell["#{row},#{col}"] = value
774
+ def set_value(row,col,value,sheet=nil)
775
+ sheet = @default_value unless sheet
776
+ @cell[sheet]["#{row},#{col}"] = value
628
777
  end
629
778
 
630
- def set_type(row,col,type)
631
- @cell_type["#{row},#{col}"] = type
779
+ def set_type(row,col,type,sheet=nil)
780
+ sheet = @default_value unless sheet
781
+ @cell_type[sheet]["#{row},#{col}"] = type
632
782
  end
633
783
 
634
784
  A_ROO_TYPE = {
@@ -658,39 +808,65 @@ private
658
808
  File.join(@tmpdir, File.basename(uri))
659
809
  end
660
810
 
661
- def write_csv_content(file=nil)
811
+ def write_csv_content(file=nil,sheet=nil)
662
812
  file = STDOUT unless file
663
- first_row.upto(last_row) do |row|
664
- cells = []
665
- 1.upto(last_column) do |col|
813
+ if first_row # sheet is not empty
814
+ first_row(sheet).upto(last_row(sheet)) do |row|
815
+ 1.upto(last_column(sheet)) do |col|
666
816
  file.print(",") if col > 1
667
- onecell = cell(row,col)
668
- onecelltype = celltype(row,col)
669
- if empty?(row,col)
670
- file.print('')
817
+ onecell = cell(row,col,sheet)
818
+ onecelltype = celltype(row,col,sheet)
819
+ file.print one_cell_output(onecelltype,onecell,empty?(row,col,sheet))
820
+ end
821
+ file.print("\n")
822
+ end # sheet not empty
823
+ end
824
+ end
825
+
826
+ def one_cell_output(onecelltype,onecell,empty)
827
+ str = ""
828
+ if empty
829
+ str += ''
830
+ else
831
+ case onecelltype
832
+ when :string
833
+ if onecell == ""
834
+ str << ''
835
+ else
836
+ onecell.gsub!(/"/,'""')
837
+ str << ('"'+onecell+'"')
838
+ end
839
+ when :float,:percentage
840
+ if onecell == onecell.to_i
841
+ str << onecell.to_i.to_s
842
+ else
843
+ str << onecell.to_s
844
+ end
845
+ when :formula
846
+ if onecell.class == String
847
+ if onecell == ""
848
+ str << ''
671
849
  else
672
- case onecelltype
673
- when :string
674
- if onecell == ""
675
- file.print('')
676
- else
677
- onecell.gsub!(/"/,'""')
678
- file.print('"'+onecell+'"')
679
- end
680
- when :float
681
- if onecell == onecell.to_i
682
- file.print onecell.to_i
683
- else
684
- file.print onecell
685
- end
686
- else
687
- raise "unhandled celltype "+celltype.to_s
688
- end
850
+ onecell.gsub!(/"/,'""')
851
+ str << '"'+onecell+'"'
852
+ end
853
+ elsif onecell.class == Float
854
+ if onecell == onecell.to_i
855
+ str << onecell.to_i.to_s
856
+ else
857
+ str << onecell.to_s
689
858
  end
690
- cells << onecell
859
+ else
860
+ raise "unhandled onecell-class "+onecell.class.to_s
691
861
  end
692
- file.print("\n")
862
+ when :date
863
+ str << '"'+onecell.to_s+'"'
864
+ else
865
+ raise "unhandled celltype "+onecelltype.to_s
693
866
  end
867
+ end
868
+ #cells << onecell
869
+ str
694
870
  end
695
871
 
696
872
  # helper method to convert compressed spaces and other elements within