Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactoring simplifying classes #368

Merged
merged 2 commits into from
Dec 31, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
191 changes: 93 additions & 98 deletions lib/roo/csv.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
require 'csv'
require 'time'
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
Expand All @@ -9,124 +9,119 @@
#
# You can pass options to the underlying CSV parse operation, via the
# :csv_options option.
#

class Roo::CSV < Roo::Base
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

attr_reader :filename
def cell(row, col, sheet = nil)
sheet ||= default_sheet
read_cells(sheet)
@cell[normalize(row, col)]
end

# 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 celltype(row, col, sheet = nil)
sheet ||= default_sheet
read_cells(sheet)
@cell_type[normalize(row, col)]
end

def cell(row, col, sheet=nil)
sheet ||= default_sheet
read_cells(sheet)
@cell[normalize(row,col)]
end
def cell_postprocessing(_row, _col, value)
value
end

def celltype(row, col, sheet=nil)
sheet ||= default_sheet
read_cells(sheet)
@cell_type[normalize(row,col)]
end
def csv_options
@options[:csv_options] || {}
end

def cell_postprocessing(row,col,value)
value
end
def set_value(row, col, value, _sheet)
@cell[[row, col]] = value
end

def csv_options
@options[:csv_options] || {}
end
def set_type(row, col, type, _sheet)
@cell_type[[row, col]] = type
end

def set_value(row, col, value, _sheet)
@cell[[row, col]] = value
end
private

def set_type(row, col, type, _sheet)
@cell_type[[row, col]] = type
end
TYPE_MAP = {
String => :string,
Float => :float,
Date => :date,
DateTime => :datetime,
}

private
def celltype_class(value)
TYPE_MAP[value.class]
end

TYPE_MAP = {
String => :string,
Float => :float,
Date => :date,
DateTime => :datetime,
}
def read_cells(sheet = default_sheet)
sheet ||= default_sheet
return if @cells_read[sheet]
set_row_count(sheet)
set_column_count(sheet)
row_num = 1

each_row csv_options do |row|
row.each_with_index do |elem, col_num|
coordinate = [row_num, col_num + 1]
@cell[coordinate] = elem
@cell_type[coordinate] = celltype_class(elem)
end
row_num += 1
end

def celltype_class(value)
TYPE_MAP[value.class]
end
@cells_read[sheet] = true
end

def each_row(options, &block)
if uri?(filename)
::Dir.mktmpdir(Roo::TEMP_PREFIX, ENV['ROO_TMP']) do |tmpdir|
tmp_filename = download_uri(filename, tmpdir)
CSV.foreach(tmp_filename, options, &block)
def each_row(options, &block)
if uri?(filename)
each_row_using_temp_dir(filename)
elsif is_stream?(filename_or_stream)
::CSV.new(filename_or_stream, options).each(&block)
else
::CSV.foreach(filename, options, &block)
end
elsif is_stream?(filename_or_stream)
CSV.new(filename_or_stream, options).each(&block)
else
CSV.foreach(filename, options, &block)
end
end

def read_cells(sheet = default_sheet)
sheet ||= default_sheet
return if @cells_read[sheet]
@first_row[sheet] = 1
@last_row[sheet] = 0
@first_column[sheet] = 1
@last_column[sheet] = 1
rownum = 1
each_row csv_options do |row|
row.each_with_index do |elem,i|
@cell[[rownum,i+1]] = cell_postprocessing rownum,i+1, elem
@cell_type[[rownum,i+1]] = celltype_class @cell[[rownum,i+1]]
if i+1 > @last_column[sheet]
@last_column[sheet] += 1
end
def each_row_using_tempdir
::Dir.mktmpdir(Roo::TEMP_PREFIX, ENV["ROO_TMP"]) do |tmpdir|
tmp_filename = download_uri(filename, tmpdir)
::CSV.foreach(tmp_filename, options, &block)
end
rownum += 1
@last_row[sheet] += 1
end
@cells_read[sheet] = true
#-- adjust @first_row if neccessary
while !row(@first_row[sheet]).any? and @first_row[sheet] < @last_row[sheet]
@first_row[sheet] += 1
end
#-- adjust @last_row if neccessary
while !row(@last_row[sheet]).any? and @last_row[sheet] and
@last_row[sheet] > @first_row[sheet]
@last_row[sheet] -= 1
end
#-- adjust @first_column if neccessary
while !column(@first_column[sheet]).any? and
@first_column[sheet] and
@first_column[sheet] < @last_column[sheet]
@first_column[sheet] += 1

def set_row_count(sheet)
@first_row[sheet] = 1
@last_row[sheet] = ::CSV.readlines(@filename).size
@last_row[sheet] = @first_row[sheet] if @last_row[sheet].zero?

nil
end
#-- adjust @last_column if neccessary
while !column(@last_column[sheet]).any? and
@last_column[sheet] and
@last_column[sheet] > @first_column[sheet]
@last_column[sheet] -= 1

def set_column_count(sheet)
@first_column[sheet] = 1
@last_column[sheet] = (::CSV.readlines(@filename).first || []).size
@last_column[sheet] = @first_column[sheet] if @last_column[sheet].zero?

nil
end
end

def clean_sheet(sheet)
read_cells(sheet)
def clean_sheet(sheet)
read_cells(sheet)

@cell.each_pair do |coord, value|
@cell[coord] = sanitize_value(value) if value.is_a?(::String)
end

@cell.each_pair do |coord, value|
@cell[coord] = sanitize_value(value) if value.is_a?(::String)
@cleaned[sheet] = true
end

@cleaned[sheet] = true
alias_method :filename_or_stream, :filename
end

alias_method :filename_or_stream, :filename
end
2 changes: 1 addition & 1 deletion lib/roo/excelx.rb
Original file line number Diff line number Diff line change
Expand Up @@ -223,7 +223,7 @@ def empty?(row, col, sheet = nil)
sheet = sheet_for(sheet)
key = normalize(row, col)
cell = sheet.cells[key]
!cell || cell.empty? || (cell.type == :string && cell.value.empty?) ||
!cell || cell.empty? ||
(row < sheet.first_row || row > sheet.last_row || col < sheet.first_column || col > sheet.last_column)
end

Expand Down
66 changes: 38 additions & 28 deletions lib/roo/excelx/cell/datetime.rb
Original file line number Diff line number Diff line change
Expand Up @@ -32,14 +32,9 @@ def initialize(value, formula, excelx_type, style, link, base_date, coordinate)
#
# Returns a String representation of a cell's value.
def formatted_value
date_regex = /(?<date>[dmy]+[\-\/][dmy]+([\-\/][dmy]+)?)/
time_regex = /(?<time>(\[?[h]\]?+:)?[m]+(:?ss|:?s)?)/

formatter = @format.downcase.split(' ').map do |part|
if part[date_regex] == part
part.gsub(/#{DATE_FORMATS.keys.join('|')}/, DATE_FORMATS)
elsif part[time_regex]
part.gsub(/#{TIME_FORMATS.keys.join('|')}/, TIME_FORMATS)
if (parsed_format = parse_date_or_time_format(part))
parsed_format
else
warn 'Unable to parse custom format. Using "YYYY-mm-dd HH:MM:SS" format.'
return @value.strftime('%F %T')
Expand All @@ -51,35 +46,50 @@ def formatted_value

private

def parse_date_or_time_format(part)
date_regex = /(?<date>[dmy]+[\-\/][dmy]+([\-\/][dmy]+)?)/
time_regex = /(?<time>(\[?[h]\]?+:)?[m]+(:?ss|:?s)?)/

if part[date_regex] == part
formats = DATE_FORMATS
elsif part[time_regex]
formats = TIME_FORMATS
else
return false
end

part.gsub(/#{formats.keys.join('|')}/, formats)
end

DATE_FORMATS = {
'yyyy'.freeze => '%Y'.freeze, # Year: 2000
'yy'.freeze => '%y'.freeze, # Year: 00
'yyyy' => '%Y', # Year: 2000
'yy' => '%y', # Year: 00
# mmmmm => J-D
'mmmm'.freeze => '%B'.freeze, # Month: January
'mmm'.freeze => '%^b'.freeze, # Month: JAN
'mm'.freeze => '%m'.freeze, # Month: 01
'm'.freeze => '%-m'.freeze, # Month: 1
'dddd'.freeze => '%A'.freeze, # Day of the Week: Sunday
'ddd'.freeze => '%^a'.freeze, # Day of the Week: SUN
'dd'.freeze => '%d'.freeze, # Day of the Month: 01
'd'.freeze => '%-d'.freeze, # Day of the Month: 1
'mmmm' => '%B', # Month: January
'mmm' => '%^b', # Month: JAN
'mm' => '%m', # Month: 01
'm' => '%-m', # Month: 1
'dddd' => '%A', # Day of the Week: Sunday
'ddd' => '%^a', # Day of the Week: SUN
'dd' => '%d', # Day of the Month: 01
'd' => '%-d' # Day of the Month: 1
# '\\\\'.freeze => ''.freeze, # NOTE: Fixes a custom format's output.
}

TIME_FORMATS = {
'hh'.freeze => '%H'.freeze, # Hour (24): 01
'h'.freeze => '%-k'.freeze, # Hour (24): 1
'hh' => '%H', # Hour (24): 01
'h' => '%-k'.freeze, # Hour (24): 1
# 'hh'.freeze => '%I'.freeze, # Hour (12): 08
# 'h'.freeze => '%-l'.freeze, # Hour (12): 8
'mm'.freeze => '%M'.freeze, # Minute: 01
'mm' => '%M', # Minute: 01
# FIXME: is this used? Seems like 'm' is used for month, not minute.
'm'.freeze => '%-M'.freeze, # Minute: 1
'ss'.freeze => '%S'.freeze, # Seconds: 01
's'.freeze => '%-S'.freeze, # Seconds: 1
'am/pm'.freeze => '%p'.freeze, # Meridian: AM
'000'.freeze => '%3N'.freeze, # Fractional Seconds: thousandth.
'00'.freeze => '%2N'.freeze, # Fractional Seconds: hundredth.
'0'.freeze => '%1N'.freeze, # Fractional Seconds: tenths.
'm' => '%-M', # Minute: 1
'ss' => '%S', # Seconds: 01
's' => '%-S', # Seconds: 1
'am/pm' => '%p', # Meridian: AM
'000' => '%3N', # Fractional Seconds: thousandth.
'00' => '%2N', # Fractional Seconds: hundredth.
'0' => '%1N' # Fractional Seconds: tenths.
}

def create_datetime(base_date, value)
Expand All @@ -93,7 +103,7 @@ def create_datetime(base_date, value)
def round_datetime(datetime_string)
/(?<yyyy>\d+)-(?<mm>\d+)-(?<dd>\d+) (?<hh>\d+):(?<mi>\d+):(?<ss>\d+.\d+)/ =~ datetime_string

::Time.new(yyyy.to_i, mm.to_i, dd.to_i, hh.to_i, mi.to_i, ss.to_r).round(0)
::Time.new(yyyy, mm, dd, hh, mi, ss.to_r).round(0)
end
end
end
Expand Down
41 changes: 18 additions & 23 deletions lib/roo/excelx/cell/number.rb
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ def formatted_value
if formatter.is_a? Proc
formatter.call(@cell_value)
elsif zero_padded_number?
"%0#{@format.size}d"% @cell_value
"%0#{@format.size}d" % @cell_value
else
Kernel.format(formatter, @cell_value)
end
Expand All @@ -46,35 +46,19 @@ def formats
'0' => '%.0f',
'0.00' => '%.2f',
'0.000000' => '%.6f',
'#,##0' => proc do |number|
Kernel.format('%.0f', number).reverse.gsub(/(\d{3})(?=\d)/, '\\1,').reverse
end,
'#,##0.00' => proc do |number|
Kernel.format('%.2f', number).reverse.gsub(/(\d{3})(?=\d)/, '\\1,').reverse
end,
'#,##0' => number_format('%.0f'),
'#,##0.00' => number_format('%.2f'),
'0%' => proc do |number|
Kernel.format('%d%', number.to_f * 100)
end,
'0.00%' => proc do |number|
Kernel.format('%.2f%', number.to_f * 100)
end,
'0.00E+00' => '%.2E',
'#,##0 ;(#,##0)' => proc do |number|
formatter = number.to_i > 0 ? '%.0f' : '(%.0f)'
Kernel.format(formatter, number.to_f.abs).reverse.gsub(/(\d{3})(?=\d)/, '\\1,').reverse
end,
'#,##0 ;[Red](#,##0)' => proc do |number|
formatter = number.to_i > 0 ? '%.0f' : '[Red](%.0f)'
Kernel.format(formatter, number.to_f.abs).reverse.gsub(/(\d{3})(?=\d)/, '\\1,').reverse
end,
'#,##0.00;(#,##0.00)' => proc do |number|
formatter = number.to_i > 0 ? '%.2f' : '(%.2f)'
Kernel.format(formatter, number.to_f.abs).reverse.gsub(/(\d{3})(?=\d)/, '\\1,').reverse
end,
'#,##0.00;[Red](#,##0.00)' => proc do |number|
formatter = number.to_i > 0 ? '%.2f' : '[Red](%.2f)'
Kernel.format(formatter, number.to_f.abs).reverse.gsub(/(\d{3})(?=\d)/, '\\1,').reverse
end,
'#,##0 ;(#,##0)' => number_format('%.0f', '(%.0f)'),
'#,##0 ;[Red](#,##0)' => number_format('%.0f', '[Red](%.0f)'),
'#,##0.00;(#,##0.00)' => number_format('%.2f', '(%.2f)'),
'#,##0.00;[Red](#,##0.00)' => number_format('%.2f', '[Red](%.2f)'),
# FIXME: not quite sure what the format should look like in this case.
'##0.0E+0' => '%.1E',
'@' => proc { |number| number }
Expand All @@ -83,6 +67,17 @@ def formats

private

def number_format(formatter, negative_formatter = nil)
proc do |number|
if negative_formatter
formatter = number.to_i > 0 ? formatter : negative_formatter
number = number.to_f.abs
end

Kernel.format(formatter, number).reverse.gsub(/(\d{3})(?=\d)/, '\\1,').reverse
end
end

def zero_padded_number?
@format[/0+/] == @format
end
Expand Down
Loading