roo 2.0.1 → 2.7.1
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.
- checksums.yaml +4 -4
- data/.codeclimate.yml +17 -0
- data/.github/ISSUE_TEMPLATE +10 -0
- data/.gitignore +4 -0
- data/.travis.yml +10 -6
- data/CHANGELOG.md +116 -1
- data/Gemfile +3 -4
- data/Gemfile_ruby2 +30 -0
- data/Guardfile +1 -2
- data/README.md +56 -22
- data/Rakefile +1 -1
- data/lib/roo/base.rb +108 -245
- data/lib/roo/constants.rb +5 -0
- data/lib/roo/csv.rb +94 -87
- data/lib/roo/errors.rb +11 -0
- data/lib/roo/excelx/cell/base.rb +94 -0
- data/lib/roo/excelx/cell/boolean.rb +27 -0
- data/lib/roo/excelx/cell/date.rb +28 -0
- data/lib/roo/excelx/cell/datetime.rb +111 -0
- data/lib/roo/excelx/cell/empty.rb +19 -0
- data/lib/roo/excelx/cell/number.rb +87 -0
- data/lib/roo/excelx/cell/string.rb +19 -0
- data/lib/roo/excelx/cell/time.rb +43 -0
- data/lib/roo/excelx/cell.rb +33 -4
- data/lib/roo/excelx/comments.rb +33 -0
- data/lib/roo/excelx/coordinate.rb +12 -0
- data/lib/roo/excelx/extractor.rb +3 -4
- data/lib/roo/excelx/format.rb +64 -0
- data/lib/roo/excelx/shared.rb +32 -0
- data/lib/roo/excelx/shared_strings.rb +124 -4
- data/lib/roo/excelx/sheet.rb +12 -7
- data/lib/roo/excelx/sheet_doc.rb +108 -97
- data/lib/roo/excelx/styles.rb +1 -1
- data/lib/roo/excelx.rb +61 -103
- data/lib/roo/formatters/base.rb +15 -0
- data/lib/roo/formatters/csv.rb +84 -0
- data/lib/roo/formatters/matrix.rb +23 -0
- data/lib/roo/formatters/xml.rb +31 -0
- data/lib/roo/formatters/yaml.rb +40 -0
- data/lib/roo/libre_office.rb +1 -2
- data/lib/roo/link.rb +21 -2
- data/lib/roo/open_office.rb +468 -523
- data/lib/roo/spreadsheet.rb +3 -1
- data/lib/roo/tempdir.rb +21 -0
- data/lib/roo/utils.rb +7 -7
- data/lib/roo/version.rb +1 -1
- data/lib/roo.rb +8 -3
- data/roo.gemspec +2 -1
- data/spec/helpers.rb +5 -0
- data/spec/lib/roo/base_spec.rb +229 -0
- data/spec/lib/roo/csv_spec.rb +19 -0
- data/spec/lib/roo/excelx_spec.rb +97 -11
- data/spec/lib/roo/openoffice_spec.rb +18 -1
- data/spec/lib/roo/spreadsheet_spec.rb +20 -0
- data/spec/lib/roo/utils_spec.rb +1 -1
- data/spec/spec_helper.rb +5 -5
- data/test/all_ss.rb +12 -11
- data/test/excelx/cell/test_base.rb +63 -0
- data/test/excelx/cell/test_boolean.rb +36 -0
- data/test/excelx/cell/test_date.rb +38 -0
- data/test/excelx/cell/test_datetime.rb +45 -0
- data/test/excelx/cell/test_empty.rb +7 -0
- data/test/excelx/cell/test_number.rb +74 -0
- data/test/excelx/cell/test_string.rb +28 -0
- data/test/excelx/cell/test_time.rb +30 -0
- data/test/formatters/test_csv.rb +119 -0
- data/test/formatters/test_matrix.rb +76 -0
- data/test/formatters/test_xml.rb +78 -0
- data/test/formatters/test_yaml.rb +20 -0
- data/test/helpers/test_accessing_files.rb +60 -0
- data/test/helpers/test_comments.rb +43 -0
- data/test/helpers/test_formulas.rb +9 -0
- data/test/helpers/test_labels.rb +103 -0
- data/test/helpers/test_sheets.rb +55 -0
- data/test/helpers/test_styles.rb +62 -0
- data/test/roo/test_base.rb +182 -0
- data/test/roo/test_csv.rb +60 -0
- data/test/roo/test_excelx.rb +325 -0
- data/test/roo/test_libre_office.rb +9 -0
- data/test/roo/test_open_office.rb +289 -0
- data/test/test_helper.rb +116 -18
- data/test/test_roo.rb +362 -2088
- metadata +70 -4
- data/test/test_generic_spreadsheet.rb +0 -237
data/lib/roo/csv.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
|
-
require
|
2
|
-
require
|
1
|
+
require "csv"
|
2
|
+
require "time"
|
3
3
|
|
4
4
|
# The CSV class can read csv files (must be separated with commas) which then
|
5
5
|
# can be handled like spreadsheets. This means you can access cells like A5
|
@@ -9,112 +9,119 @@ require 'time'
|
|
9
9
|
#
|
10
10
|
# You can pass options to the underlying CSV parse operation, via the
|
11
11
|
# :csv_options option.
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
12
|
+
module Roo
|
13
|
+
class CSV < Roo::Base
|
14
|
+
attr_reader :filename
|
15
|
+
|
16
|
+
# Returns an array with the names of the sheets. In CSV class there is only
|
17
|
+
# one dummy sheet, because a csv file cannot have more than one sheet.
|
18
|
+
def sheets
|
19
|
+
["default"]
|
20
|
+
end
|
17
21
|
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
22
|
+
def cell(row, col, sheet = nil)
|
23
|
+
sheet ||= default_sheet
|
24
|
+
read_cells(sheet)
|
25
|
+
@cell[normalize(row, col)]
|
26
|
+
end
|
23
27
|
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
28
|
+
def celltype(row, col, sheet = nil)
|
29
|
+
sheet ||= default_sheet
|
30
|
+
read_cells(sheet)
|
31
|
+
@cell_type[normalize(row, col)]
|
32
|
+
end
|
29
33
|
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
@cell_type[normalize(row,col)]
|
34
|
-
end
|
34
|
+
def cell_postprocessing(_row, _col, value)
|
35
|
+
value
|
36
|
+
end
|
35
37
|
|
36
|
-
|
37
|
-
|
38
|
-
|
38
|
+
def csv_options
|
39
|
+
@options[:csv_options] || {}
|
40
|
+
end
|
39
41
|
|
40
|
-
|
41
|
-
|
42
|
-
|
42
|
+
def set_value(row, col, value, _sheet)
|
43
|
+
@cell[[row, col]] = value
|
44
|
+
end
|
43
45
|
|
44
|
-
|
46
|
+
def set_type(row, col, type, _sheet)
|
47
|
+
@cell_type[[row, col]] = type
|
48
|
+
end
|
45
49
|
|
46
|
-
|
47
|
-
String => :string,
|
48
|
-
Float => :float,
|
49
|
-
Date => :date,
|
50
|
-
DateTime => :datetime,
|
51
|
-
}
|
50
|
+
private
|
52
51
|
|
53
|
-
|
54
|
-
|
55
|
-
|
52
|
+
TYPE_MAP = {
|
53
|
+
String => :string,
|
54
|
+
Float => :float,
|
55
|
+
Date => :date,
|
56
|
+
DateTime => :datetime,
|
57
|
+
}
|
56
58
|
|
57
|
-
|
58
|
-
|
59
|
-
make_tmpdir do |tmpdir|
|
60
|
-
tmp_filename = download_uri(filename, tmpdir)
|
61
|
-
CSV.foreach(tmp_filename, options, &block)
|
62
|
-
end
|
63
|
-
else
|
64
|
-
CSV.foreach(filename, options, &block)
|
59
|
+
def celltype_class(value)
|
60
|
+
TYPE_MAP[value.class]
|
65
61
|
end
|
66
|
-
end
|
67
62
|
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
if i+1 > @last_column[sheet]
|
81
|
-
@last_column[sheet] += 1
|
63
|
+
def read_cells(sheet = default_sheet)
|
64
|
+
sheet ||= default_sheet
|
65
|
+
return if @cells_read[sheet]
|
66
|
+
set_row_count(sheet)
|
67
|
+
set_column_count(sheet)
|
68
|
+
row_num = 1
|
69
|
+
|
70
|
+
each_row csv_options do |row|
|
71
|
+
row.each_with_index do |elem, col_num|
|
72
|
+
coordinate = [row_num, col_num + 1]
|
73
|
+
@cell[coordinate] = elem
|
74
|
+
@cell_type[coordinate] = celltype_class(elem)
|
82
75
|
end
|
76
|
+
row_num += 1
|
83
77
|
end
|
84
|
-
|
85
|
-
@
|
78
|
+
|
79
|
+
@cells_read[sheet] = true
|
86
80
|
end
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
81
|
+
|
82
|
+
def each_row(options, &block)
|
83
|
+
if uri?(filename)
|
84
|
+
each_row_using_temp_dir(filename)
|
85
|
+
elsif is_stream?(filename_or_stream)
|
86
|
+
::CSV.new(filename_or_stream, options).each(&block)
|
87
|
+
else
|
88
|
+
::CSV.foreach(filename, options, &block)
|
89
|
+
end
|
91
90
|
end
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
91
|
+
|
92
|
+
def each_row_using_tempdir
|
93
|
+
::Dir.mktmpdir(Roo::TEMP_PREFIX, ENV["ROO_TMP"]) do |tmpdir|
|
94
|
+
tmp_filename = download_uri(filename, tmpdir)
|
95
|
+
::CSV.foreach(tmp_filename, options, &block)
|
96
|
+
end
|
96
97
|
end
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
@
|
98
|
+
|
99
|
+
def set_row_count(sheet)
|
100
|
+
@first_row[sheet] = 1
|
101
|
+
@last_row[sheet] = ::CSV.readlines(@filename, csv_options).size
|
102
|
+
@last_row[sheet] = @first_row[sheet] if @last_row[sheet].zero?
|
103
|
+
|
104
|
+
nil
|
102
105
|
end
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
@last_column[sheet]
|
106
|
+
|
107
|
+
def set_column_count(sheet)
|
108
|
+
@first_column[sheet] = 1
|
109
|
+
@last_column[sheet] = (::CSV.readlines(@filename, csv_options).first || []).size
|
110
|
+
@last_column[sheet] = @first_column[sheet] if @last_column[sheet].zero?
|
111
|
+
|
112
|
+
nil
|
108
113
|
end
|
109
|
-
end
|
110
114
|
|
111
|
-
|
112
|
-
|
115
|
+
def clean_sheet(sheet)
|
116
|
+
read_cells(sheet)
|
117
|
+
|
118
|
+
@cell.each_pair do |coord, value|
|
119
|
+
@cell[coord] = sanitize_value(value) if value.is_a?(::String)
|
120
|
+
end
|
113
121
|
|
114
|
-
|
115
|
-
@cell[coord] = sanitize_value(value) if value.is_a?(::String)
|
122
|
+
@cleaned[sheet] = true
|
116
123
|
end
|
117
124
|
|
118
|
-
|
125
|
+
alias_method :filename_or_stream, :filename
|
119
126
|
end
|
120
127
|
end
|
data/lib/roo/errors.rb
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
module Roo
|
2
|
+
# A base error class for Roo. Most errors thrown by Roo should inherit from
|
3
|
+
# this class.
|
4
|
+
class Error < StandardError; end
|
5
|
+
|
6
|
+
# Raised when Roo cannot find a header row that matches the given column
|
7
|
+
# name(s).
|
8
|
+
class HeaderRowNotFoundError < Error; end
|
9
|
+
|
10
|
+
class FileNotFound < Error; end
|
11
|
+
end
|
@@ -0,0 +1,94 @@
|
|
1
|
+
module Roo
|
2
|
+
class Excelx
|
3
|
+
class Cell
|
4
|
+
class Base
|
5
|
+
attr_reader :cell_type, :cell_value, :value
|
6
|
+
|
7
|
+
# FIXME: I think style should be deprecated. Having a style attribute
|
8
|
+
# for a cell doesn't really accomplish much. It seems to be used
|
9
|
+
# when you want to export to excelx.
|
10
|
+
attr_reader :style
|
11
|
+
|
12
|
+
|
13
|
+
# FIXME: Updating a cell's value should be able tochange the cell's type,
|
14
|
+
# but that isn't currently possible. This will cause weird bugs
|
15
|
+
# when one changes the value of a Number cell to a String. e.g.
|
16
|
+
#
|
17
|
+
# cell = Cell::Number(*args)
|
18
|
+
# cell.value = 'Hello'
|
19
|
+
# cell.formatted_value # => Some unexpected value
|
20
|
+
#
|
21
|
+
# Here are two possible solutions to such issues:
|
22
|
+
# 1. Don't allow a cell's value to be updated. Use a method like
|
23
|
+
# `Sheet.update_cell` instead. The simple solution.
|
24
|
+
# 2. When `cell.value = ` is called, use injection to try and
|
25
|
+
# change the type of cell on the fly. But deciding what type
|
26
|
+
# of value to pass to `cell.value=`. isn't always obvious. e.g.
|
27
|
+
# `cell.value = Time.now` should convert a cell to a DateTime,
|
28
|
+
# not a Time cell. Time cells would be hard to recognize because
|
29
|
+
# they are integers. This approach would require a significant
|
30
|
+
# change to the code as written. The complex solution.
|
31
|
+
#
|
32
|
+
# If the first solution is used, then this method should be
|
33
|
+
# deprecated.
|
34
|
+
attr_writer :value
|
35
|
+
|
36
|
+
def initialize(value, formula, excelx_type, style, link, coordinate)
|
37
|
+
@link = !!link
|
38
|
+
@cell_value = value
|
39
|
+
@cell_type = excelx_type
|
40
|
+
@formula = formula
|
41
|
+
@style = style
|
42
|
+
@coordinate = coordinate
|
43
|
+
@type = :base
|
44
|
+
@value = link? ? Roo::Link.new(link, value) : value
|
45
|
+
end
|
46
|
+
|
47
|
+
def type
|
48
|
+
if formula?
|
49
|
+
:formula
|
50
|
+
elsif link?
|
51
|
+
:link
|
52
|
+
else
|
53
|
+
@type
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def formula?
|
58
|
+
!!@formula
|
59
|
+
end
|
60
|
+
|
61
|
+
def link?
|
62
|
+
!!@link
|
63
|
+
end
|
64
|
+
|
65
|
+
alias_method :formatted_value, :value
|
66
|
+
|
67
|
+
def to_s
|
68
|
+
formatted_value
|
69
|
+
end
|
70
|
+
|
71
|
+
# DEPRECATED: Please use link instead.
|
72
|
+
def hyperlink
|
73
|
+
warn '[DEPRECATION] `hyperlink` is deprecated. Please use `link` instead.'
|
74
|
+
end
|
75
|
+
|
76
|
+
# DEPRECATED: Please use cell_value instead.
|
77
|
+
def excelx_value
|
78
|
+
warn '[DEPRECATION] `excelx_value` is deprecated. Please use `cell_value` instead.'
|
79
|
+
cell_value
|
80
|
+
end
|
81
|
+
|
82
|
+
# DEPRECATED: Please use cell_type instead.
|
83
|
+
def excelx_type
|
84
|
+
warn '[DEPRECATION] `excelx_type` is deprecated. Please use `cell_type` instead.'
|
85
|
+
cell_type
|
86
|
+
end
|
87
|
+
|
88
|
+
def empty?
|
89
|
+
false
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module Roo
|
2
|
+
class Excelx
|
3
|
+
class Cell
|
4
|
+
class Boolean < Cell::Base
|
5
|
+
attr_reader :value, :formula, :format, :cell_type, :cell_value, :link, :coordinate
|
6
|
+
|
7
|
+
def initialize(value, formula, style, link, coordinate)
|
8
|
+
super(value, formula, nil, style, link, coordinate)
|
9
|
+
@type = @cell_type = :boolean
|
10
|
+
@value = link? ? Roo::Link.new(link, value) : create_boolean(value)
|
11
|
+
end
|
12
|
+
|
13
|
+
def formatted_value
|
14
|
+
value ? 'TRUE'.freeze : 'FALSE'.freeze
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def create_boolean(value)
|
20
|
+
# FIXME: Using a boolean will cause methods like Base#to_csv to fail.
|
21
|
+
# Roo is using some method to ignore false/nil values.
|
22
|
+
value.to_i == 1 ? true : false
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'date'
|
2
|
+
|
3
|
+
module Roo
|
4
|
+
class Excelx
|
5
|
+
class Cell
|
6
|
+
class Date < Roo::Excelx::Cell::DateTime
|
7
|
+
attr_reader :value, :formula, :format, :cell_type, :cell_value, :link, :coordinate
|
8
|
+
|
9
|
+
def initialize(value, formula, excelx_type, style, link, base_date, coordinate)
|
10
|
+
# NOTE: Pass all arguments to the parent class, DateTime.
|
11
|
+
super
|
12
|
+
@type = :date
|
13
|
+
@format = excelx_type.last
|
14
|
+
@value = link? ? Roo::Link.new(link, value) : create_date(base_date, value)
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def create_date(base_date, value)
|
20
|
+
date = base_date + value.to_i
|
21
|
+
yyyy, mm, dd = date.strftime('%Y-%m-%d').split('-')
|
22
|
+
|
23
|
+
::Date.new(yyyy.to_i, mm.to_i, dd.to_i)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,111 @@
|
|
1
|
+
require 'date'
|
2
|
+
|
3
|
+
module Roo
|
4
|
+
class Excelx
|
5
|
+
class Cell
|
6
|
+
class DateTime < Cell::Base
|
7
|
+
attr_reader :value, :formula, :format, :cell_value, :link, :coordinate
|
8
|
+
|
9
|
+
def initialize(value, formula, excelx_type, style, link, base_date, coordinate)
|
10
|
+
super(value, formula, excelx_type, style, link, coordinate)
|
11
|
+
@type = :datetime
|
12
|
+
@format = excelx_type.last
|
13
|
+
@value = link? ? Roo::Link.new(link, value) : create_datetime(base_date, value)
|
14
|
+
end
|
15
|
+
|
16
|
+
# Public: Returns formatted value for a datetime. Format's can be an
|
17
|
+
# standard excel format, or a custom format.
|
18
|
+
#
|
19
|
+
# Standard formats follow certain conventions. Date fields for
|
20
|
+
# days, months, and years are separated with hyhens or
|
21
|
+
# slashes ("-", /") (e.g. 01-JAN, 1/13/15). Time fields for
|
22
|
+
# hours, minutes, and seconds are separated with a colon (e.g.
|
23
|
+
# 12:45:01).
|
24
|
+
#
|
25
|
+
# If a custom format follows those conventions, then the custom
|
26
|
+
# format will be used for the a cell's formatted value.
|
27
|
+
# Otherwise, the formatted value will be in the following
|
28
|
+
# format: 'YYYY-mm-dd HH:MM:SS' (e.g. "2015-07-10 20:33:15").
|
29
|
+
#
|
30
|
+
# Examples
|
31
|
+
# formatted_value #=> '01-JAN'
|
32
|
+
#
|
33
|
+
# Returns a String representation of a cell's value.
|
34
|
+
def formatted_value
|
35
|
+
formatter = @format.downcase.split(' ').map do |part|
|
36
|
+
if (parsed_format = parse_date_or_time_format(part))
|
37
|
+
parsed_format
|
38
|
+
else
|
39
|
+
warn 'Unable to parse custom format. Using "YYYY-mm-dd HH:MM:SS" format.'
|
40
|
+
return @value.strftime('%F %T')
|
41
|
+
end
|
42
|
+
end.join(' ')
|
43
|
+
|
44
|
+
@value.strftime(formatter)
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
|
49
|
+
def parse_date_or_time_format(part)
|
50
|
+
date_regex = /(?<date>[dmy]+[\-\/][dmy]+([\-\/][dmy]+)?)/
|
51
|
+
time_regex = /(?<time>(\[?[h]\]?+:)?[m]+(:?ss|:?s)?)/
|
52
|
+
|
53
|
+
if part[date_regex] == part
|
54
|
+
formats = DATE_FORMATS
|
55
|
+
elsif part[time_regex]
|
56
|
+
formats = TIME_FORMATS
|
57
|
+
else
|
58
|
+
return false
|
59
|
+
end
|
60
|
+
|
61
|
+
part.gsub(/#{formats.keys.join('|')}/, formats)
|
62
|
+
end
|
63
|
+
|
64
|
+
DATE_FORMATS = {
|
65
|
+
'yyyy' => '%Y', # Year: 2000
|
66
|
+
'yy' => '%y', # Year: 00
|
67
|
+
# mmmmm => J-D
|
68
|
+
'mmmm' => '%B', # Month: January
|
69
|
+
'mmm' => '%^b', # Month: JAN
|
70
|
+
'mm' => '%m', # Month: 01
|
71
|
+
'm' => '%-m', # Month: 1
|
72
|
+
'dddd' => '%A', # Day of the Week: Sunday
|
73
|
+
'ddd' => '%^a', # Day of the Week: SUN
|
74
|
+
'dd' => '%d', # Day of the Month: 01
|
75
|
+
'd' => '%-d' # Day of the Month: 1
|
76
|
+
# '\\\\'.freeze => ''.freeze, # NOTE: Fixes a custom format's output.
|
77
|
+
}
|
78
|
+
|
79
|
+
TIME_FORMATS = {
|
80
|
+
'hh' => '%H', # Hour (24): 01
|
81
|
+
'h' => '%-k'.freeze, # Hour (24): 1
|
82
|
+
# 'hh'.freeze => '%I'.freeze, # Hour (12): 08
|
83
|
+
# 'h'.freeze => '%-l'.freeze, # Hour (12): 8
|
84
|
+
'mm' => '%M', # Minute: 01
|
85
|
+
# FIXME: is this used? Seems like 'm' is used for month, not minute.
|
86
|
+
'm' => '%-M', # Minute: 1
|
87
|
+
'ss' => '%S', # Seconds: 01
|
88
|
+
's' => '%-S', # Seconds: 1
|
89
|
+
'am/pm' => '%p', # Meridian: AM
|
90
|
+
'000' => '%3N', # Fractional Seconds: thousandth.
|
91
|
+
'00' => '%2N', # Fractional Seconds: hundredth.
|
92
|
+
'0' => '%1N' # Fractional Seconds: tenths.
|
93
|
+
}
|
94
|
+
|
95
|
+
def create_datetime(base_date, value)
|
96
|
+
date = base_date + value.to_f.round(6)
|
97
|
+
datetime_string = date.strftime('%Y-%m-%d %H:%M:%S.%N')
|
98
|
+
t = round_datetime(datetime_string)
|
99
|
+
|
100
|
+
::DateTime.civil(t.year, t.month, t.day, t.hour, t.min, t.sec)
|
101
|
+
end
|
102
|
+
|
103
|
+
def round_datetime(datetime_string)
|
104
|
+
/(?<yyyy>\d+)-(?<mm>\d+)-(?<dd>\d+) (?<hh>\d+):(?<mi>\d+):(?<ss>\d+.\d+)/ =~ datetime_string
|
105
|
+
|
106
|
+
::Time.new(yyyy, mm, dd, hh, mi, ss.to_r).round(0)
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
|
2
|
+
module Roo
|
3
|
+
class Excelx
|
4
|
+
class Cell
|
5
|
+
class Empty < Cell::Base
|
6
|
+
attr_reader :value, :formula, :format, :cell_type, :cell_value, :hyperlink, :coordinate
|
7
|
+
|
8
|
+
def initialize(coordinate)
|
9
|
+
@value = @formula = @format = @cell_type = @cell_value = @hyperlink = nil
|
10
|
+
@coordinate = coordinate
|
11
|
+
end
|
12
|
+
|
13
|
+
def empty?
|
14
|
+
true
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
module Roo
|
2
|
+
class Excelx
|
3
|
+
class Cell
|
4
|
+
class Number < Cell::Base
|
5
|
+
attr_reader :value, :formula, :format, :cell_value, :link, :coordinate
|
6
|
+
|
7
|
+
def initialize(value, formula, excelx_type, style, link, coordinate)
|
8
|
+
super
|
9
|
+
# FIXME: change @type to number. This will break brittle tests.
|
10
|
+
# FIXME: Excelx_type is an array, but the first value isn't used.
|
11
|
+
@type = :float
|
12
|
+
@format = excelx_type.last
|
13
|
+
@value = link? ? Roo::Link.new(link, value) : create_numeric(value)
|
14
|
+
end
|
15
|
+
|
16
|
+
def create_numeric(number)
|
17
|
+
return number if Excelx::ERROR_VALUES.include?(number)
|
18
|
+
case @format
|
19
|
+
when /%/
|
20
|
+
Float(number)
|
21
|
+
when /\.0/
|
22
|
+
Float(number)
|
23
|
+
else
|
24
|
+
(number.include?('.') || (/\A[-+]?\d+E[-+]\d+\z/i =~ number)) ? Float(number) : Integer(number)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def formatted_value
|
29
|
+
return @cell_value if Excelx::ERROR_VALUES.include?(@cell_value)
|
30
|
+
|
31
|
+
formatter = formats[@format]
|
32
|
+
if formatter.is_a? Proc
|
33
|
+
formatter.call(@cell_value)
|
34
|
+
elsif zero_padded_number?
|
35
|
+
"%0#{@format.size}d" % @cell_value
|
36
|
+
else
|
37
|
+
Kernel.format(formatter, @cell_value)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def formats
|
42
|
+
# FIXME: numbers can be other colors besides red:
|
43
|
+
# [BLACK], [BLUE], [CYAN], [GREEN], [MAGENTA], [RED], [WHITE], [YELLOW], [COLOR n]
|
44
|
+
{
|
45
|
+
'General' => '%.0f',
|
46
|
+
'0' => '%.0f',
|
47
|
+
'0.00' => '%.2f',
|
48
|
+
'0.000000' => '%.6f',
|
49
|
+
'#,##0' => number_format('%.0f'),
|
50
|
+
'#,##0.00' => number_format('%.2f'),
|
51
|
+
'0%' => proc do |number|
|
52
|
+
Kernel.format('%d%', number.to_f * 100)
|
53
|
+
end,
|
54
|
+
'0.00%' => proc do |number|
|
55
|
+
Kernel.format('%.2f%', number.to_f * 100)
|
56
|
+
end,
|
57
|
+
'0.00E+00' => '%.2E',
|
58
|
+
'#,##0 ;(#,##0)' => number_format('%.0f', '(%.0f)'),
|
59
|
+
'#,##0 ;[Red](#,##0)' => number_format('%.0f', '[Red](%.0f)'),
|
60
|
+
'#,##0.00;(#,##0.00)' => number_format('%.2f', '(%.2f)'),
|
61
|
+
'#,##0.00;[Red](#,##0.00)' => number_format('%.2f', '[Red](%.2f)'),
|
62
|
+
# FIXME: not quite sure what the format should look like in this case.
|
63
|
+
'##0.0E+0' => '%.1E',
|
64
|
+
'@' => proc { |number| number }
|
65
|
+
}
|
66
|
+
end
|
67
|
+
|
68
|
+
private
|
69
|
+
|
70
|
+
def number_format(formatter, negative_formatter = nil)
|
71
|
+
proc do |number|
|
72
|
+
if negative_formatter
|
73
|
+
formatter = number.to_i > 0 ? formatter : negative_formatter
|
74
|
+
number = number.to_f.abs
|
75
|
+
end
|
76
|
+
|
77
|
+
Kernel.format(formatter, number).reverse.gsub(/(\d{3})(?=\d)/, '\\1,').reverse
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def zero_padded_number?
|
82
|
+
@format[/0+/] == @format
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Roo
|
2
|
+
class Excelx
|
3
|
+
class Cell
|
4
|
+
class String < Cell::Base
|
5
|
+
attr_reader :value, :formula, :format, :cell_type, :cell_value, :link, :coordinate
|
6
|
+
|
7
|
+
def initialize(value, formula, style, link, coordinate)
|
8
|
+
super(value, formula, nil, style, link, coordinate)
|
9
|
+
@type = @cell_type = :string
|
10
|
+
@value = link? ? Roo::Link.new(link, value) : value
|
11
|
+
end
|
12
|
+
|
13
|
+
def empty?
|
14
|
+
value.empty?
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
require 'date'
|
2
|
+
|
3
|
+
module Roo
|
4
|
+
class Excelx
|
5
|
+
class Cell
|
6
|
+
class Time < Roo::Excelx::Cell::DateTime
|
7
|
+
attr_reader :value, :formula, :format, :cell_value, :link, :coordinate
|
8
|
+
|
9
|
+
def initialize(value, formula, excelx_type, style, link, base_date, coordinate)
|
10
|
+
# NOTE: Pass all arguments to DateTime super class.
|
11
|
+
super
|
12
|
+
@type = :time
|
13
|
+
@format = excelx_type.last
|
14
|
+
@datetime = create_datetime(base_date, value)
|
15
|
+
@value = link? ? Roo::Link.new(link, value) : (value.to_f * 86_400).to_i
|
16
|
+
end
|
17
|
+
|
18
|
+
def formatted_value
|
19
|
+
formatter = @format.gsub(/#{TIME_FORMATS.keys.join('|')}/, TIME_FORMATS)
|
20
|
+
@datetime.strftime(formatter)
|
21
|
+
end
|
22
|
+
|
23
|
+
alias_method :to_s, :formatted_value
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
# def create_datetime(base_date, value)
|
28
|
+
# date = base_date + value.to_f.round(6)
|
29
|
+
# datetime_string = date.strftime('%Y-%m-%d %H:%M:%S.%N')
|
30
|
+
# t = round_datetime(datetime_string)
|
31
|
+
#
|
32
|
+
# ::DateTime.civil(t.year, t.month, t.day, t.hour, t.min, t.sec)
|
33
|
+
# end
|
34
|
+
|
35
|
+
# def round_datetime(datetime_string)
|
36
|
+
# /(?<yyyy>\d+)-(?<mm>\d+)-(?<dd>\d+) (?<hh>\d+):(?<mi>\d+):(?<ss>\d+.\d+)/ =~ datetime_string
|
37
|
+
#
|
38
|
+
# ::Time.new(yyyy.to_i, mm.to_i, dd.to_i, hh.to_i, mi.to_i, ss.to_r).round(0)
|
39
|
+
# end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|