roo 0.6.1 → 0.7.0

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.
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