1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141
|
# frozen_string_literal: true
require "csv"
require "time"
# The CSV class can read csv files (must be separated with commas) which then
# can be handled like spreadsheets. This means you can access cells like A5
# within these files.
# The CSV class provides only string objects. If you want conversions to other
# types you have to do it yourself.
#
# You can pass options to the underlying CSV parse operation, via the
# :csv_options option.
module Roo
class CSV < Roo::Base
attr_reader :filename
# Returns an array with the names of the sheets. In CSV class there is only
# one dummy sheet, because a csv file cannot have more than one sheet.
def sheets
["default"]
end
def cell(row, col, sheet = nil)
sheet ||= default_sheet
read_cells(sheet)
@cell[normalize(row, col)]
end
def celltype(row, col, sheet = nil)
sheet ||= default_sheet
read_cells(sheet)
@cell_type[normalize(row, col)]
end
def cell_postprocessing(_row, _col, value)
value
end
def csv_options
@options[:csv_options] || {}
end
def set_value(row, col, value, _sheet)
@cell[[row, col]] = value
end
def set_type(row, col, type, _sheet)
@cell_type[[row, col]] = type
end
private
TYPE_MAP = {
String => :string,
Float => :float,
Date => :date,
DateTime => :datetime,
}
def celltype_class(value)
TYPE_MAP[value.class]
end
def read_cells(sheet = default_sheet)
sheet ||= default_sheet
return if @cells_read[sheet]
row_num = 0
max_col_num = 0
each_row csv_options do |row|
row_num += 1
col_num = 0
row.each do |elem|
col_num += 1
coordinate = [row_num, col_num]
@cell[coordinate] = elem
@cell_type[coordinate] = celltype_class(elem)
end
max_col_num = col_num if col_num > max_col_num
end
set_row_count(sheet, row_num)
set_column_count(sheet, max_col_num)
@cells_read[sheet] = true
end
def each_row(options, &block)
if uri?(filename)
each_row_using_tempdir(options, &block)
else
csv_foreach(filename_or_stream, options, &block)
end
end
def each_row_using_tempdir(options, &block)
::Dir.mktmpdir(Roo::TEMP_PREFIX, ENV["ROO_TMP"]) do |tmpdir|
tmp_filename = download_uri(filename, tmpdir)
csv_foreach(tmp_filename, options, &block)
end
end
def csv_foreach(path_or_io, options, &block)
if is_stream?(path_or_io)
::CSV.new(path_or_io, **options).each(&block)
else
::CSV.foreach(path_or_io, **options, &block)
end
end
def set_row_count(sheet, last_row)
@first_row[sheet] = 1
@last_row[sheet] = last_row
@last_row[sheet] = @first_row[sheet] if @last_row[sheet].zero?
nil
end
def set_column_count(sheet, last_col)
@first_column[sheet] = 1
@last_column[sheet] = last_col
@last_column[sheet] = @first_column[sheet] if @last_column[sheet].zero?
nil
end
def clean_sheet(sheet)
read_cells(sheet)
@cell.each_pair do |coord, value|
@cell[coord] = sanitize_value(value) if value.is_a?(::String)
end
@cleaned[sheet] = true
end
alias_method :filename_or_stream, :filename
end
end
|