# Base class for all other types of spreadsheets class GenericSpreadsheet attr_reader :default_sheet # converts cell coordinate to numeric values of row,col def normalize(row,col) if row.class == String if col.class == Fixnum # ('A',1): # ('B', 5) -> (5, 2) row, col = col, row else raise ArgumentError end end if col.class == String col = GenericSpreadsheet.letter_to_number(col) end return row,col end # true if cell is empty def empty?(row, col, sheet=nil) #def excel_empty?(row, col, sheet=nil) sheet = @default_sheet unless sheet read_cells(sheet) unless @cells_read[sheet] or self.class == Excel row,col = normalize(row,col) return true unless cell(row, col, sheet) return true if celltype(row, col, sheet) == :string && cell(row, col, sheet).empty? #false #end # true if a cell is empty #def oo_empty?(row, col, sheet=nil) #sheet = @default_sheet unless sheet #read_cells(sheet) unless @cells_read[sheet] #row,col = normalize(row,col) return true if row < first_row(sheet) || row > last_row(sheet) || col < first_column(sheet) || col > last_column(sheet) #return true unless cell(row, col, sheet) # return true if celltype(row, col, sheet) == "string" && cell(row, col, sheet) == "" #return true if celltype(row, col, sheet) == :string && cell(row, col, sheet) == "" false end # reopens and read a spreadsheet document def reload ds = @default_sheet initialize(@filename) if self.class == Openoffice or self.class == Excel initialize(@spreadsheetkey,@user,@password) if self.class == Google self.default_sheet = ds #@first_row = @last_row = @first_column = @last_column = nil end # Returns information of the spreadsheet document and all sheets within # this document. def info # $log.debug(self.class.to_s+"#info started") result = "File: #{@filename}\n"+ "Number of sheets: #{sheets.size}\n"+ "Sheets: #{sheets.map{|sheet| sheet+", "}.to_s[0..-3]}\n" n = 1 # $log.debug(sheets.inspect) sheets.each {|sheet| # $log.debug("Info fuer Sheet=#{sheet}") self.default_sheet = sheet # $log.debug("nach default_sheet=") result << "Sheet " + n.to_s + ":\n" result << " First row: #{first_row}\n" # $log.debug("nach first_row") result << " Last row: #{last_row}\n" # $log.debug("nach last_row") result << " First column: #{GenericSpreadsheet.number_to_letter(first_column)}\n" # $log.debug("nach first_column") result << " Last column: #{GenericSpreadsheet.number_to_letter(last_column)}" # $log.debug("nach last_column") result << "\n" if sheet != sheets.last n += 1 } # $log.debug(self.class.to_s+"#info ended") result end # returns a rectangular area (default: all cells) as yaml-output # you can add additional attributes with the prefix parameter like: # oo.to_yaml({"file"=>"flightdata_2007-06-26", "sheet" => "1"}) def to_yaml(prefix={}, from_row=nil, from_column=nil, to_row=nil, to_column=nil,sheet=nil) sheet = @default_sheet unless sheet result = "--- \n" (from_row||first_row(sheet)).upto(to_row||last_row(sheet)) do |row| (from_column||first_column(sheet)).upto(to_column||last_column(sheet)) do |col| unless empty?(row,col,sheet) result << "cell_#{row}_#{col}: \n" prefix.each {|k,v| result << " #{k}: #{v} \n" } result << " row: #{row} \n" result << " col: #{col} \n" result << " celltype: #{self.celltype(row,col,sheet)} \n" result << " value: #{self.cell(row,col,sheet)} \n" end end end result end # recursively removes the current temporary directory # this is only needed if you work with zipped files or files via the web def remove_tmp if File.exists?(@tmpdir) FileUtils::rm_r(@tmpdir) end end # first non-empty column as a letter def first_column_as_letter(sheet=nil) GenericSpreadsheet.number_to_letter(first_column(sheet)) end # last non-empty column as a letter def last_column_as_letter(sheet=nil) GenericSpreadsheet.number_to_letter(last_column(sheet)) end def open_from_uri(uri) require 'open-uri' ; tempfilename = File.join(@tmpdir, File.basename(uri)) f = File.open(tempfilename,"wb") begin open(uri) do |net| f.write(net.read) end rescue raise "could not open #{uri}" end f.close File.join(@tmpdir, File.basename(uri)) end # returns the number of the last non-empty row def last_row(sheet=nil) sheet = @default_sheet unless sheet read_cells(sheet) unless @cells_read[sheet] if @last_row[sheet] return @last_row[sheet] end impossible_value = 0 result = impossible_value @cell[sheet].each_pair {|key,value| y,x = key.split(',') y = y.to_i result = [result, y].max if value } result = nil if result == impossible_value @last_row[sheet] = result result end # returns the number of the last non-empty column def last_column(sheet=nil) # $log.debug("#{self.class.to_s}#last_column(#{sheet})") sheet = @default_sheet unless sheet read_cells(sheet) unless @cells_read[sheet] if @last_column[sheet] # $log.debug("last_column of sheet #{sheet} already set") return @last_column[sheet] end # $log.debug("last_column of sheet #{sheet} not yet set") impossible_value = 0 result = impossible_value @cell[sheet].each_pair {|key,value| y,x = key.split(',') x = x.to_i result = [result, x].max if value } result = nil if result == impossible_value @last_column[sheet] = result result end # returns the number of the first non-empty row def first_row(sheet=nil) if sheet == nil sheet = @default_sheet end read_cells(sheet) unless @cells_read[sheet] if @first_row[sheet] return @first_row[sheet] end impossible_value = 999_999 # more than a spreadsheet can hold result = impossible_value @cell[sheet].each_pair {|key,value| y,x = key.split(',') y = y.to_i result = [result, y].min if value } result = nil if result == impossible_value @first_row[sheet] = result result end # returns the number of the first non-empty column def first_column(sheet=nil) if sheet == nil sheet = @default_sheet end read_cells(sheet) unless @cells_read[sheet] if @first_column[sheet] return @first_column[sheet] end impossible_value = 999_999 # more than a spreadsheet can hold result = impossible_value @cell[sheet].each_pair {|key,value| y,x = key.split(',') x = x.to_i result = [result, x].min if value } result = nil if result == impossible_value @first_column[sheet] = result result end # convert a number to something like this: 'AB' def GenericSpreadsheet.number_to_letter(n) letters="" while n > 0 num = n%26 letters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"[num-1,1] + letters n = n.div(26) end letters end # convert letters like 'AB' to a number def GenericSpreadsheet.letter_to_number(letters) result = 0 while letters && letters.length > 0 character = letters[0,1].upcase num = "ABCDEFGHIJKLMNOPQRSTUVWXYZ".index(character) raise ArgumentError, "invalid column character '#{letters[0,1]}'" if num == nil num += 1 result = result * 26 + num letters = letters[1..-1] end result end protected def unzip(filename) ret = nil Zip::ZipFile.open(filename) do |zip| ret = process_zipfile_packed zip end ret end # helper method def after(d) if DateTime.now > d yield end end # helper method def before(d) if DateTime.now <= d yield end end private def initialize end def process_zipfile_packed(zip, path='') ret=nil if zip.file.file? path # extract and return filename @tmpdir = "oo_"+$$.to_s unless File.exists?(@tmpdir) FileUtils::mkdir(@tmpdir) end file = File.open(File.join(@tmpdir, path),"wb") file.write(zip.read(path)) file.close return File.join(@tmpdir, path) else unless path.empty? path += '/' end zip.dir.foreach(path) do |filename| ret = process_zipfile_packed(zip, path + filename) end end ret end end