roo 1.13.2 → 2.10.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 +5 -5
- data/.codeclimate.yml +17 -0
- data/.github/issue_template.md +16 -0
- data/.github/pull_request_template.md +14 -0
- data/.github/workflows/pull-request.yml +15 -0
- data/.github/workflows/ruby.yml +34 -0
- data/.gitignore +11 -0
- data/.rubocop.yml +186 -0
- data/.simplecov +4 -0
- data/CHANGELOG.md +702 -0
- data/Gemfile +18 -12
- data/Guardfile +23 -0
- data/LICENSE +5 -1
- data/README.md +328 -0
- data/Rakefile +23 -23
- data/examples/roo_soap_client.rb +28 -31
- data/examples/roo_soap_server.rb +4 -6
- data/examples/write_me.rb +9 -10
- data/lib/roo/base.rb +317 -504
- data/lib/roo/constants.rb +7 -0
- data/lib/roo/csv.rb +141 -113
- data/lib/roo/errors.rb +11 -0
- data/lib/roo/excelx/cell/base.rb +108 -0
- data/lib/roo/excelx/cell/boolean.rb +30 -0
- data/lib/roo/excelx/cell/date.rb +28 -0
- data/lib/roo/excelx/cell/datetime.rb +107 -0
- data/lib/roo/excelx/cell/empty.rb +20 -0
- data/lib/roo/excelx/cell/number.rb +99 -0
- data/lib/roo/excelx/cell/string.rb +19 -0
- data/lib/roo/excelx/cell/time.rb +44 -0
- data/lib/roo/excelx/cell.rb +110 -0
- data/lib/roo/excelx/comments.rb +55 -0
- data/lib/roo/excelx/coordinate.rb +19 -0
- data/lib/roo/excelx/extractor.rb +39 -0
- data/lib/roo/excelx/format.rb +71 -0
- data/lib/roo/excelx/images.rb +26 -0
- data/lib/roo/excelx/relationships.rb +33 -0
- data/lib/roo/excelx/shared.rb +39 -0
- data/lib/roo/excelx/shared_strings.rb +151 -0
- data/lib/roo/excelx/sheet.rb +151 -0
- data/lib/roo/excelx/sheet_doc.rb +257 -0
- data/lib/roo/excelx/styles.rb +64 -0
- data/lib/roo/excelx/workbook.rb +64 -0
- data/lib/roo/excelx.rb +407 -601
- data/lib/roo/font.rb +17 -0
- 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/helpers/default_attr_reader.rb +20 -0
- data/lib/roo/helpers/weak_instance_cache.rb +41 -0
- data/lib/roo/libre_office.rb +4 -0
- data/lib/roo/link.rb +34 -0
- data/lib/roo/open_office.rb +631 -0
- data/lib/roo/spreadsheet.rb +28 -23
- data/lib/roo/tempdir.rb +24 -0
- data/lib/roo/utils.rb +128 -0
- data/lib/roo/version.rb +3 -0
- data/lib/roo.rb +26 -24
- data/roo.gemspec +29 -203
- data/spec/helpers.rb +5 -0
- data/spec/lib/roo/base_spec.rb +291 -3
- data/spec/lib/roo/csv_spec.rb +38 -11
- data/spec/lib/roo/excelx/cell/time_spec.rb +15 -0
- data/spec/lib/roo/excelx/format_spec.rb +7 -6
- data/spec/lib/roo/excelx/relationships_spec.rb +43 -0
- data/spec/lib/roo/excelx/sheet_doc_spec.rb +11 -0
- data/spec/lib/roo/excelx_spec.rb +672 -11
- data/spec/lib/roo/libreoffice_spec.rb +16 -6
- data/spec/lib/roo/openoffice_spec.rb +30 -8
- data/spec/lib/roo/spreadsheet_spec.rb +60 -12
- data/spec/lib/roo/strict_spec.rb +43 -0
- data/spec/lib/roo/utils_spec.rb +119 -0
- data/spec/lib/roo/weak_instance_cache_spec.rb +92 -0
- data/spec/lib/roo_spec.rb +0 -0
- data/spec/spec_helper.rb +7 -6
- data/test/all_ss.rb +12 -11
- data/test/excelx/cell/test_attr_reader_default.rb +72 -0
- data/test/excelx/cell/test_base.rb +68 -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 +18 -0
- data/test/excelx/cell/test_number.rb +90 -0
- data/test/excelx/cell/test_string.rb +48 -0
- data/test/excelx/cell/test_time.rb +30 -0
- data/test/excelx/test_coordinate.rb +51 -0
- data/test/formatters/test_csv.rb +136 -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 +81 -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 +88 -0
- data/test/roo/test_excelx.rb +360 -0
- data/test/roo/test_libre_office.rb +9 -0
- data/test/roo/test_open_office.rb +289 -0
- data/test/test_helper.rb +123 -59
- data/test/test_roo.rb +392 -2292
- metadata +153 -298
- data/CHANGELOG +0 -417
- data/Gemfile.lock +0 -78
- data/README.markdown +0 -126
- data/VERSION +0 -1
- data/lib/roo/excel.rb +0 -355
- data/lib/roo/excel2003xml.rb +0 -300
- data/lib/roo/google.rb +0 -292
- data/lib/roo/openoffice.rb +0 -496
- data/lib/roo/roo_rails_helper.rb +0 -83
- data/lib/roo/worksheet.rb +0 -18
- data/scripts/txt2html +0 -67
- data/spec/lib/roo/excel2003xml_spec.rb +0 -15
- data/spec/lib/roo/excel_spec.rb +0 -17
- data/spec/lib/roo/google_spec.rb +0 -64
- data/test/files/1900_base.xls +0 -0
- data/test/files/1900_base.xlsx +0 -0
- data/test/files/1904_base.xls +0 -0
- data/test/files/1904_base.xlsx +0 -0
- data/test/files/Bibelbund.csv +0 -3741
- data/test/files/Bibelbund.ods +0 -0
- data/test/files/Bibelbund.xls +0 -0
- data/test/files/Bibelbund.xlsx +0 -0
- data/test/files/Bibelbund.xml +0 -62518
- data/test/files/Bibelbund1.ods +0 -0
- data/test/files/Pfand_from_windows_phone.xlsx +0 -0
- data/test/files/bad_excel_date.xls +0 -0
- data/test/files/bbu.ods +0 -0
- data/test/files/bbu.xls +0 -0
- data/test/files/bbu.xlsx +0 -0
- data/test/files/bbu.xml +0 -152
- data/test/files/bode-v1.ods.zip +0 -0
- data/test/files/bode-v1.xls.zip +0 -0
- data/test/files/boolean.csv +0 -2
- data/test/files/boolean.ods +0 -0
- data/test/files/boolean.xls +0 -0
- data/test/files/boolean.xlsx +0 -0
- data/test/files/boolean.xml +0 -112
- data/test/files/borders.ods +0 -0
- data/test/files/borders.xls +0 -0
- data/test/files/borders.xlsx +0 -0
- data/test/files/borders.xml +0 -144
- data/test/files/bug-numbered-sheet-names.xlsx +0 -0
- data/test/files/bug-row-column-fixnum-float.xls +0 -0
- data/test/files/bug-row-column-fixnum-float.xml +0 -127
- data/test/files/comments.ods +0 -0
- data/test/files/comments.xls +0 -0
- data/test/files/comments.xlsx +0 -0
- data/test/files/csvtypes.csv +0 -1
- data/test/files/datetime.ods +0 -0
- data/test/files/datetime.xls +0 -0
- data/test/files/datetime.xlsx +0 -0
- data/test/files/datetime.xml +0 -142
- data/test/files/datetime_floatconv.xls +0 -0
- data/test/files/datetime_floatconv.xml +0 -148
- data/test/files/dreimalvier.ods +0 -0
- data/test/files/emptysheets.ods +0 -0
- data/test/files/emptysheets.xls +0 -0
- data/test/files/emptysheets.xlsx +0 -0
- data/test/files/emptysheets.xml +0 -105
- data/test/files/excel2003.xml +0 -21140
- data/test/files/false_encoding.xls +0 -0
- data/test/files/false_encoding.xml +0 -132
- data/test/files/file_item_error.xlsx +0 -0
- data/test/files/formula.ods +0 -0
- data/test/files/formula.xls +0 -0
- data/test/files/formula.xlsx +0 -0
- data/test/files/formula.xml +0 -134
- data/test/files/formula_parse_error.xls +0 -0
- data/test/files/formula_parse_error.xml +0 -1833
- data/test/files/formula_string_error.xlsx +0 -0
- data/test/files/html-escape.ods +0 -0
- data/test/files/link.xls +0 -0
- data/test/files/link.xlsx +0 -0
- data/test/files/matrix.ods +0 -0
- data/test/files/matrix.xls +0 -0
- data/test/files/named_cells.ods +0 -0
- data/test/files/named_cells.xls +0 -0
- data/test/files/named_cells.xlsx +0 -0
- data/test/files/no_spreadsheet_file.txt +0 -1
- data/test/files/numbers1.csv +0 -18
- data/test/files/numbers1.ods +0 -0
- data/test/files/numbers1.xls +0 -0
- data/test/files/numbers1.xlsx +0 -0
- data/test/files/numbers1.xml +0 -312
- data/test/files/numeric-link.xlsx +0 -0
- data/test/files/only_one_sheet.ods +0 -0
- data/test/files/only_one_sheet.xls +0 -0
- data/test/files/only_one_sheet.xlsx +0 -0
- data/test/files/only_one_sheet.xml +0 -67
- data/test/files/paragraph.ods +0 -0
- data/test/files/paragraph.xls +0 -0
- data/test/files/paragraph.xlsx +0 -0
- data/test/files/paragraph.xml +0 -127
- data/test/files/prova.xls +0 -0
- data/test/files/ric.ods +0 -0
- data/test/files/simple_spreadsheet.ods +0 -0
- data/test/files/simple_spreadsheet.xls +0 -0
- data/test/files/simple_spreadsheet.xlsx +0 -0
- data/test/files/simple_spreadsheet.xml +0 -225
- data/test/files/simple_spreadsheet_from_italo.ods +0 -0
- data/test/files/simple_spreadsheet_from_italo.xls +0 -0
- data/test/files/simple_spreadsheet_from_italo.xml +0 -242
- data/test/files/so_datetime.csv +0 -7
- data/test/files/style.ods +0 -0
- data/test/files/style.xls +0 -0
- data/test/files/style.xlsx +0 -0
- data/test/files/style.xml +0 -154
- data/test/files/time-test.csv +0 -2
- data/test/files/time-test.ods +0 -0
- data/test/files/time-test.xls +0 -0
- data/test/files/time-test.xlsx +0 -0
- data/test/files/time-test.xml +0 -131
- data/test/files/type_excel.ods +0 -0
- data/test/files/type_excel.xlsx +0 -0
- data/test/files/type_excelx.ods +0 -0
- data/test/files/type_excelx.xls +0 -0
- data/test/files/type_openoffice.xls +0 -0
- data/test/files/type_openoffice.xlsx +0 -0
- data/test/files/whitespace.ods +0 -0
- data/test/files/whitespace.xls +0 -0
- data/test/files/whitespace.xlsx +0 -0
- data/test/files/whitespace.xml +0 -184
- data/test/rm_sub_test.rb +0 -12
- data/test/rm_test.rb +0 -7
- data/test/test_generic_spreadsheet.rb +0 -259
- data/website/index.html +0 -385
- data/website/index.txt +0 -423
- data/website/javascripts/rounded_corners_lite.inc.js +0 -285
- data/website/stylesheets/screen.css +0 -130
- data/website/template.rhtml +0 -48
@@ -0,0 +1,7 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Roo
|
4
|
+
ROO_EXCEL_NOTICE = "Excel support has been extracted to roo-xls due to its dependency on the GPL'd spreadsheet gem. Install roo-xls to use Roo::Excel."
|
5
|
+
ROO_EXCELML_NOTICE = "Excel SpreadsheetML support has been extracted to roo-xls. Install roo-xls to use Roo::Excel2003XML."
|
6
|
+
ROO_GOOGLE_NOTICE = "Google support has been extracted to roo-google. Install roo-google to use Roo::Google."
|
7
|
+
end
|
data/lib/roo/csv.rb
CHANGED
@@ -1,113 +1,141 @@
|
|
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
|
-
end
|
112
|
-
|
113
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "csv"
|
4
|
+
require "time"
|
5
|
+
|
6
|
+
# The CSV class can read csv files (must be separated with commas) which then
|
7
|
+
# can be handled like spreadsheets. This means you can access cells like A5
|
8
|
+
# within these files.
|
9
|
+
# The CSV class provides only string objects. If you want conversions to other
|
10
|
+
# types you have to do it yourself.
|
11
|
+
#
|
12
|
+
# You can pass options to the underlying CSV parse operation, via the
|
13
|
+
# :csv_options option.
|
14
|
+
module Roo
|
15
|
+
class CSV < Roo::Base
|
16
|
+
attr_reader :filename
|
17
|
+
|
18
|
+
# Returns an array with the names of the sheets. In CSV class there is only
|
19
|
+
# one dummy sheet, because a csv file cannot have more than one sheet.
|
20
|
+
def sheets
|
21
|
+
["default"]
|
22
|
+
end
|
23
|
+
|
24
|
+
def cell(row, col, sheet = nil)
|
25
|
+
sheet ||= default_sheet
|
26
|
+
read_cells(sheet)
|
27
|
+
@cell[normalize(row, col)]
|
28
|
+
end
|
29
|
+
|
30
|
+
def celltype(row, col, sheet = nil)
|
31
|
+
sheet ||= default_sheet
|
32
|
+
read_cells(sheet)
|
33
|
+
@cell_type[normalize(row, col)]
|
34
|
+
end
|
35
|
+
|
36
|
+
def cell_postprocessing(_row, _col, value)
|
37
|
+
value
|
38
|
+
end
|
39
|
+
|
40
|
+
def csv_options
|
41
|
+
@options[:csv_options] || {}
|
42
|
+
end
|
43
|
+
|
44
|
+
def set_value(row, col, value, _sheet)
|
45
|
+
@cell[[row, col]] = value
|
46
|
+
end
|
47
|
+
|
48
|
+
def set_type(row, col, type, _sheet)
|
49
|
+
@cell_type[[row, col]] = type
|
50
|
+
end
|
51
|
+
|
52
|
+
private
|
53
|
+
|
54
|
+
TYPE_MAP = {
|
55
|
+
String => :string,
|
56
|
+
Float => :float,
|
57
|
+
Date => :date,
|
58
|
+
DateTime => :datetime,
|
59
|
+
}
|
60
|
+
|
61
|
+
def celltype_class(value)
|
62
|
+
TYPE_MAP[value.class]
|
63
|
+
end
|
64
|
+
|
65
|
+
def read_cells(sheet = default_sheet)
|
66
|
+
sheet ||= default_sheet
|
67
|
+
return if @cells_read[sheet]
|
68
|
+
row_num = 0
|
69
|
+
max_col_num = 0
|
70
|
+
|
71
|
+
each_row csv_options do |row|
|
72
|
+
row_num += 1
|
73
|
+
col_num = 0
|
74
|
+
|
75
|
+
row.each do |elem|
|
76
|
+
col_num += 1
|
77
|
+
coordinate = [row_num, col_num]
|
78
|
+
@cell[coordinate] = elem
|
79
|
+
@cell_type[coordinate] = celltype_class(elem)
|
80
|
+
end
|
81
|
+
|
82
|
+
max_col_num = col_num if col_num > max_col_num
|
83
|
+
end
|
84
|
+
|
85
|
+
set_row_count(sheet, row_num)
|
86
|
+
set_column_count(sheet, max_col_num)
|
87
|
+
@cells_read[sheet] = true
|
88
|
+
end
|
89
|
+
|
90
|
+
def each_row(options, &block)
|
91
|
+
if uri?(filename)
|
92
|
+
each_row_using_tempdir(options, &block)
|
93
|
+
else
|
94
|
+
csv_foreach(filename_or_stream, options, &block)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
def each_row_using_tempdir(options, &block)
|
99
|
+
::Dir.mktmpdir(Roo::TEMP_PREFIX, ENV["ROO_TMP"]) do |tmpdir|
|
100
|
+
tmp_filename = download_uri(filename, tmpdir)
|
101
|
+
csv_foreach(tmp_filename, options, &block)
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
def csv_foreach(path_or_io, options, &block)
|
106
|
+
if is_stream?(path_or_io)
|
107
|
+
::CSV.new(path_or_io, **options).each(&block)
|
108
|
+
else
|
109
|
+
::CSV.foreach(path_or_io, **options, &block)
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
def set_row_count(sheet, last_row)
|
114
|
+
@first_row[sheet] = 1
|
115
|
+
@last_row[sheet] = last_row
|
116
|
+
@last_row[sheet] = @first_row[sheet] if @last_row[sheet].zero?
|
117
|
+
|
118
|
+
nil
|
119
|
+
end
|
120
|
+
|
121
|
+
def set_column_count(sheet, last_col)
|
122
|
+
@first_column[sheet] = 1
|
123
|
+
@last_column[sheet] = last_col
|
124
|
+
@last_column[sheet] = @first_column[sheet] if @last_column[sheet].zero?
|
125
|
+
|
126
|
+
nil
|
127
|
+
end
|
128
|
+
|
129
|
+
def clean_sheet(sheet)
|
130
|
+
read_cells(sheet)
|
131
|
+
|
132
|
+
@cell.each_pair do |coord, value|
|
133
|
+
@cell[coord] = sanitize_value(value) if value.is_a?(::String)
|
134
|
+
end
|
135
|
+
|
136
|
+
@cleaned[sheet] = true
|
137
|
+
end
|
138
|
+
|
139
|
+
alias_method :filename_or_stream, :filename
|
140
|
+
end
|
141
|
+
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,108 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "roo/helpers/default_attr_reader"
|
4
|
+
|
5
|
+
module Roo
|
6
|
+
class Excelx
|
7
|
+
class Cell
|
8
|
+
class Base
|
9
|
+
extend Roo::Helpers::DefaultAttrReader
|
10
|
+
attr_reader :cell_type, :cell_value, :value
|
11
|
+
|
12
|
+
# FIXME: I think style should be deprecated. Having a style attribute
|
13
|
+
# for a cell doesn't really accomplish much. It seems to be used
|
14
|
+
# when you want to export to excelx.
|
15
|
+
attr_reader_with_default default_type: :base, style: 1
|
16
|
+
|
17
|
+
|
18
|
+
# FIXME: Updating a cell's value should be able tochange the cell's type,
|
19
|
+
# but that isn't currently possible. This will cause weird bugs
|
20
|
+
# when one changes the value of a Number cell to a String. e.g.
|
21
|
+
#
|
22
|
+
# cell = Cell::Number(*args)
|
23
|
+
# cell.value = 'Hello'
|
24
|
+
# cell.formatted_value # => Some unexpected value
|
25
|
+
#
|
26
|
+
# Here are two possible solutions to such issues:
|
27
|
+
# 1. Don't allow a cell's value to be updated. Use a method like
|
28
|
+
# `Sheet.update_cell` instead. The simple solution.
|
29
|
+
# 2. When `cell.value = ` is called, use injection to try and
|
30
|
+
# change the type of cell on the fly. But deciding what type
|
31
|
+
# of value to pass to `cell.value=`. isn't always obvious. e.g.
|
32
|
+
# `cell.value = Time.now` should convert a cell to a DateTime,
|
33
|
+
# not a Time cell. Time cells would be hard to recognize because
|
34
|
+
# they are integers. This approach would require a significant
|
35
|
+
# change to the code as written. The complex solution.
|
36
|
+
#
|
37
|
+
# If the first solution is used, then this method should be
|
38
|
+
# deprecated.
|
39
|
+
attr_writer :value
|
40
|
+
|
41
|
+
def initialize(value, formula, excelx_type, style, link, coordinate)
|
42
|
+
@cell_value = value
|
43
|
+
@cell_type = excelx_type if excelx_type
|
44
|
+
@formula = formula if formula
|
45
|
+
@style = style unless style == 1
|
46
|
+
@coordinate = coordinate
|
47
|
+
@value = link ? Roo::Link.new(link, value) : value
|
48
|
+
end
|
49
|
+
|
50
|
+
def type
|
51
|
+
if formula?
|
52
|
+
:formula
|
53
|
+
elsif link?
|
54
|
+
:link
|
55
|
+
else
|
56
|
+
default_type
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def formula?
|
61
|
+
!!(defined?(@formula) && @formula)
|
62
|
+
end
|
63
|
+
|
64
|
+
def link?
|
65
|
+
Roo::Link === @value
|
66
|
+
end
|
67
|
+
|
68
|
+
alias_method :formatted_value, :value
|
69
|
+
|
70
|
+
def to_s
|
71
|
+
formatted_value
|
72
|
+
end
|
73
|
+
|
74
|
+
# DEPRECATED: Please use link? instead.
|
75
|
+
def hyperlink
|
76
|
+
warn '[DEPRECATION] `hyperlink` is deprecated. Please use `link?` instead.'
|
77
|
+
link?
|
78
|
+
end
|
79
|
+
|
80
|
+
# DEPRECATED: Please use link? instead.
|
81
|
+
def link
|
82
|
+
warn '[DEPRECATION] `link` is deprecated. Please use `link?` instead.'
|
83
|
+
link?
|
84
|
+
end
|
85
|
+
|
86
|
+
# DEPRECATED: Please use cell_value instead.
|
87
|
+
def excelx_value
|
88
|
+
warn '[DEPRECATION] `excelx_value` is deprecated. Please use `cell_value` instead.'
|
89
|
+
cell_value
|
90
|
+
end
|
91
|
+
|
92
|
+
# DEPRECATED: Please use cell_type instead.
|
93
|
+
def excelx_type
|
94
|
+
warn '[DEPRECATION] `excelx_type` is deprecated. Please use `cell_type` instead.'
|
95
|
+
cell_type
|
96
|
+
end
|
97
|
+
|
98
|
+
def empty?
|
99
|
+
false
|
100
|
+
end
|
101
|
+
|
102
|
+
def presence
|
103
|
+
empty? ? nil : self
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Roo
|
4
|
+
class Excelx
|
5
|
+
class Cell
|
6
|
+
class Boolean < Cell::Base
|
7
|
+
attr_reader :value, :formula, :format, :cell_value, :coordinate
|
8
|
+
|
9
|
+
attr_reader_with_default default_type: :boolean, cell_type: :boolean
|
10
|
+
|
11
|
+
def initialize(value, formula, style, link, coordinate)
|
12
|
+
super(value, formula, nil, style, nil, coordinate)
|
13
|
+
@value = link ? Roo::Link.new(link, value) : create_boolean(value)
|
14
|
+
end
|
15
|
+
|
16
|
+
def formatted_value
|
17
|
+
value ? 'TRUE' : 'FALSE'
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def create_boolean(value)
|
23
|
+
# FIXME: Using a boolean will cause methods like Base#to_csv to fail.
|
24
|
+
# Roo is using some method to ignore false/nil values.
|
25
|
+
value.to_i == 1
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
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, :coordinate
|
8
|
+
|
9
|
+
attr_reader_with_default default_type: :date
|
10
|
+
|
11
|
+
def initialize(value, formula, excelx_type, style, link, base_date, coordinate)
|
12
|
+
# NOTE: Pass all arguments to the parent class, DateTime.
|
13
|
+
super
|
14
|
+
@format = excelx_type.last
|
15
|
+
@value = link ? Roo::Link.new(link, value) : create_date(base_date, value)
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def create_datetime(_,_); end
|
21
|
+
|
22
|
+
def create_date(base_date, value)
|
23
|
+
base_date + value.to_i
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,107 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'date'
|
4
|
+
|
5
|
+
module Roo
|
6
|
+
class Excelx
|
7
|
+
class Cell
|
8
|
+
class DateTime < Cell::Base
|
9
|
+
SECONDS_IN_DAY = 60 * 60 * 24
|
10
|
+
|
11
|
+
attr_reader :value, :formula, :format, :cell_value, :coordinate
|
12
|
+
|
13
|
+
attr_reader_with_default default_type: :datetime
|
14
|
+
|
15
|
+
def initialize(value, formula, excelx_type, style, link, base_timestamp, coordinate)
|
16
|
+
super(value, formula, excelx_type, style, nil, coordinate)
|
17
|
+
@format = excelx_type.last
|
18
|
+
@value = link ? Roo::Link.new(link, value) : create_datetime(base_timestamp, value)
|
19
|
+
end
|
20
|
+
|
21
|
+
# Public: Returns formatted value for a datetime. Format's can be an
|
22
|
+
# standard excel format, or a custom format.
|
23
|
+
#
|
24
|
+
# Standard formats follow certain conventions. Date fields for
|
25
|
+
# days, months, and years are separated with hyhens or
|
26
|
+
# slashes ("-", /") (e.g. 01-JAN, 1/13/15). Time fields for
|
27
|
+
# hours, minutes, and seconds are separated with a colon (e.g.
|
28
|
+
# 12:45:01).
|
29
|
+
#
|
30
|
+
# If a custom format follows those conventions, then the custom
|
31
|
+
# format will be used for the a cell's formatted value.
|
32
|
+
# Otherwise, the formatted value will be in the following
|
33
|
+
# format: 'YYYY-mm-dd HH:MM:SS' (e.g. "2015-07-10 20:33:15").
|
34
|
+
#
|
35
|
+
# Examples
|
36
|
+
# formatted_value #=> '01-JAN'
|
37
|
+
#
|
38
|
+
# Returns a String representation of a cell's value.
|
39
|
+
def formatted_value
|
40
|
+
formatter = @format.downcase.split(' ').map do |part|
|
41
|
+
if (parsed_format = parse_date_or_time_format(part))
|
42
|
+
parsed_format
|
43
|
+
else
|
44
|
+
warn 'Unable to parse custom format. Using "YYYY-mm-dd HH:MM:SS" format.'
|
45
|
+
return @value.strftime('%F %T')
|
46
|
+
end
|
47
|
+
end.join(' ')
|
48
|
+
|
49
|
+
@value.strftime(formatter)
|
50
|
+
end
|
51
|
+
|
52
|
+
private
|
53
|
+
|
54
|
+
def parse_date_or_time_format(part)
|
55
|
+
date_regex = /(?<date>[dmy]+[\-\/][dmy]+([\-\/][dmy]+)?)/
|
56
|
+
time_regex = /(?<time>(\[?[h]\]?+:)?[m]+(:?ss|:?s)?)/
|
57
|
+
|
58
|
+
if part[date_regex] == part
|
59
|
+
formats = DATE_FORMATS
|
60
|
+
elsif part[time_regex]
|
61
|
+
formats = TIME_FORMATS
|
62
|
+
else
|
63
|
+
return false
|
64
|
+
end
|
65
|
+
|
66
|
+
part.gsub(/#{formats.keys.join('|')}/, formats)
|
67
|
+
end
|
68
|
+
|
69
|
+
DATE_FORMATS = {
|
70
|
+
'yyyy' => '%Y', # Year: 2000
|
71
|
+
'yy' => '%y', # Year: 00
|
72
|
+
# mmmmm => J-D
|
73
|
+
'mmmm' => '%B', # Month: January
|
74
|
+
'mmm' => '%^b', # Month: JAN
|
75
|
+
'mm' => '%m', # Month: 01
|
76
|
+
'm' => '%-m', # Month: 1
|
77
|
+
'dddd' => '%A', # Day of the Week: Sunday
|
78
|
+
'ddd' => '%^a', # Day of the Week: SUN
|
79
|
+
'dd' => '%d', # Day of the Month: 01
|
80
|
+
'd' => '%-d' # Day of the Month: 1
|
81
|
+
# '\\\\'.freeze => ''.freeze, # NOTE: Fixes a custom format's output.
|
82
|
+
}
|
83
|
+
|
84
|
+
TIME_FORMATS = {
|
85
|
+
'hh' => '%H', # Hour (24): 01
|
86
|
+
'h' => '%-k', # Hour (24): 1
|
87
|
+
# 'hh'.freeze => '%I'.freeze, # Hour (12): 08
|
88
|
+
# 'h'.freeze => '%-l'.freeze, # Hour (12): 8
|
89
|
+
'mm' => '%M', # Minute: 01
|
90
|
+
# FIXME: is this used? Seems like 'm' is used for month, not minute.
|
91
|
+
'm' => '%-M', # Minute: 1
|
92
|
+
'ss' => '%S', # Seconds: 01
|
93
|
+
's' => '%-S', # Seconds: 1
|
94
|
+
'am/pm' => '%p', # Meridian: AM
|
95
|
+
'000' => '%3N', # Fractional Seconds: thousandth.
|
96
|
+
'00' => '%2N', # Fractional Seconds: hundredth.
|
97
|
+
'0' => '%1N' # Fractional Seconds: tenths.
|
98
|
+
}
|
99
|
+
|
100
|
+
def create_datetime(base_timestamp, value)
|
101
|
+
timestamp = (base_timestamp + (value.to_f.round(6) * SECONDS_IN_DAY)).round(0)
|
102
|
+
::Time.at(timestamp).utc.to_datetime
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
@@ -0,0 +1,20 @@
|
|
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, :coordinate
|
7
|
+
|
8
|
+
attr_reader_with_default default_type: nil, style: nil
|
9
|
+
|
10
|
+
def initialize(coordinate)
|
11
|
+
@coordinate = coordinate
|
12
|
+
end
|
13
|
+
|
14
|
+
def empty?
|
15
|
+
true
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,99 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Roo
|
4
|
+
class Excelx
|
5
|
+
class Cell
|
6
|
+
class Number < Cell::Base
|
7
|
+
attr_reader :value, :formula, :format, :cell_value, :coordinate
|
8
|
+
|
9
|
+
# FIXME: change default_type to number. This will break brittle tests.
|
10
|
+
attr_reader_with_default default_type: :float
|
11
|
+
|
12
|
+
def initialize(value, formula, excelx_type, style, link, coordinate)
|
13
|
+
super
|
14
|
+
# FIXME: Excelx_type is an array, but the first value isn't used.
|
15
|
+
@format = excelx_type.last
|
16
|
+
@value = link ? Roo::Link.new(link, value) : create_numeric(value)
|
17
|
+
end
|
18
|
+
|
19
|
+
def create_numeric(number)
|
20
|
+
return number if Excelx::ERROR_VALUES.include?(number)
|
21
|
+
case @format
|
22
|
+
when /%/
|
23
|
+
Float(number)
|
24
|
+
when /\.0/
|
25
|
+
Float(number)
|
26
|
+
else
|
27
|
+
(number.include?('.') || (/\A[-+]?\d+E[-+]?\d+\z/i =~ number)) ? Float(number) : Integer(number, 10)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def formatted_value
|
32
|
+
return @cell_value if Excelx::ERROR_VALUES.include?(@cell_value)
|
33
|
+
|
34
|
+
formatter = generate_formatter(@format)
|
35
|
+
if formatter.is_a? Proc
|
36
|
+
formatter.call(@cell_value)
|
37
|
+
else
|
38
|
+
Kernel.format(formatter, @cell_value)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def generate_formatter(format)
|
43
|
+
# FIXME: numbers can be other colors besides red:
|
44
|
+
# [BLACK], [BLUE], [CYAN], [GREEN], [MAGENTA], [RED], [WHITE], [YELLOW], [COLOR n]
|
45
|
+
case format
|
46
|
+
when /^General$/i then '%.0f'
|
47
|
+
when '0' then '%.0f'
|
48
|
+
when /^(0+)$/ then "%0#{$1.size}d"
|
49
|
+
when /^0\.(0+)$/ then "%.#{$1.size}f"
|
50
|
+
when '#,##0' then number_format('%.0f')
|
51
|
+
when /^#,##0.(0+)$/ then number_format("%.#{$1.size}f")
|
52
|
+
when '0%'
|
53
|
+
proc do |number|
|
54
|
+
Kernel.format('%.0f%%', number.to_f * 100)
|
55
|
+
end
|
56
|
+
when '0.00%'
|
57
|
+
proc do |number|
|
58
|
+
Kernel.format('%.2f%%', number.to_f * 100)
|
59
|
+
end
|
60
|
+
when '0.00E+00' then '%.2E'
|
61
|
+
when '#,##0 ;(#,##0)' then number_format('%.0f', '(%.0f)')
|
62
|
+
when '#,##0 ;[Red](#,##0)' then number_format('%.0f', '[Red](%.0f)')
|
63
|
+
when '#,##0.00;(#,##0.00)' then number_format('%.2f', '(%.2f)')
|
64
|
+
when '#,##0.00;[Red](#,##0.00)' then number_format('%.2f', '[Red](%.2f)')
|
65
|
+
# FIXME: not quite sure what the format should look like in this case.
|
66
|
+
when '##0.0E+0' then '%.1E'
|
67
|
+
when "_-* #,##0.00\\ _€_-;\\-* #,##0.00\\ _€_-;_-* \"-\"??\\ _€_-;_-@_-" then number_format('%.2f', '-%.2f')
|
68
|
+
when '@' then proc { |number| number }
|
69
|
+
when /^(?:_\()?"([^"]*)"(?:\* )?([^_]+)/
|
70
|
+
proc do |number|
|
71
|
+
formatted_number = generate_formatter($2).call(number)
|
72
|
+
"#{$1}#{formatted_number}"
|
73
|
+
end
|
74
|
+
when /^_[- \(]\[\$([^-]*)[^#@]+([^_]+)/
|
75
|
+
proc do |number|
|
76
|
+
formatted_number = generate_formatter($2).call(number)
|
77
|
+
"#{$1}#{formatted_number}"
|
78
|
+
end
|
79
|
+
else
|
80
|
+
raise "Unknown format: #{format.inspect}"
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
private
|
85
|
+
|
86
|
+
def number_format(formatter, negative_formatter = nil)
|
87
|
+
proc do |number|
|
88
|
+
if negative_formatter
|
89
|
+
formatter = number.to_i > 0 ? formatter : negative_formatter
|
90
|
+
number = number.to_f.abs
|
91
|
+
end
|
92
|
+
|
93
|
+
Kernel.format(formatter, number).reverse.gsub(/(\d{3})(?=\d)/, '\\1,').reverse
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|