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.
Files changed (236) hide show
  1. checksums.yaml +5 -5
  2. data/.codeclimate.yml +17 -0
  3. data/.github/issue_template.md +16 -0
  4. data/.github/pull_request_template.md +14 -0
  5. data/.github/workflows/pull-request.yml +15 -0
  6. data/.github/workflows/ruby.yml +34 -0
  7. data/.gitignore +11 -0
  8. data/.rubocop.yml +186 -0
  9. data/.simplecov +4 -0
  10. data/CHANGELOG.md +702 -0
  11. data/Gemfile +18 -12
  12. data/Guardfile +23 -0
  13. data/LICENSE +5 -1
  14. data/README.md +328 -0
  15. data/Rakefile +23 -23
  16. data/examples/roo_soap_client.rb +28 -31
  17. data/examples/roo_soap_server.rb +4 -6
  18. data/examples/write_me.rb +9 -10
  19. data/lib/roo/base.rb +317 -504
  20. data/lib/roo/constants.rb +7 -0
  21. data/lib/roo/csv.rb +141 -113
  22. data/lib/roo/errors.rb +11 -0
  23. data/lib/roo/excelx/cell/base.rb +108 -0
  24. data/lib/roo/excelx/cell/boolean.rb +30 -0
  25. data/lib/roo/excelx/cell/date.rb +28 -0
  26. data/lib/roo/excelx/cell/datetime.rb +107 -0
  27. data/lib/roo/excelx/cell/empty.rb +20 -0
  28. data/lib/roo/excelx/cell/number.rb +99 -0
  29. data/lib/roo/excelx/cell/string.rb +19 -0
  30. data/lib/roo/excelx/cell/time.rb +44 -0
  31. data/lib/roo/excelx/cell.rb +110 -0
  32. data/lib/roo/excelx/comments.rb +55 -0
  33. data/lib/roo/excelx/coordinate.rb +19 -0
  34. data/lib/roo/excelx/extractor.rb +39 -0
  35. data/lib/roo/excelx/format.rb +71 -0
  36. data/lib/roo/excelx/images.rb +26 -0
  37. data/lib/roo/excelx/relationships.rb +33 -0
  38. data/lib/roo/excelx/shared.rb +39 -0
  39. data/lib/roo/excelx/shared_strings.rb +151 -0
  40. data/lib/roo/excelx/sheet.rb +151 -0
  41. data/lib/roo/excelx/sheet_doc.rb +257 -0
  42. data/lib/roo/excelx/styles.rb +64 -0
  43. data/lib/roo/excelx/workbook.rb +64 -0
  44. data/lib/roo/excelx.rb +407 -601
  45. data/lib/roo/font.rb +17 -0
  46. data/lib/roo/formatters/base.rb +15 -0
  47. data/lib/roo/formatters/csv.rb +84 -0
  48. data/lib/roo/formatters/matrix.rb +23 -0
  49. data/lib/roo/formatters/xml.rb +31 -0
  50. data/lib/roo/formatters/yaml.rb +40 -0
  51. data/lib/roo/helpers/default_attr_reader.rb +20 -0
  52. data/lib/roo/helpers/weak_instance_cache.rb +41 -0
  53. data/lib/roo/libre_office.rb +4 -0
  54. data/lib/roo/link.rb +34 -0
  55. data/lib/roo/open_office.rb +631 -0
  56. data/lib/roo/spreadsheet.rb +28 -23
  57. data/lib/roo/tempdir.rb +24 -0
  58. data/lib/roo/utils.rb +128 -0
  59. data/lib/roo/version.rb +3 -0
  60. data/lib/roo.rb +26 -24
  61. data/roo.gemspec +29 -203
  62. data/spec/helpers.rb +5 -0
  63. data/spec/lib/roo/base_spec.rb +291 -3
  64. data/spec/lib/roo/csv_spec.rb +38 -11
  65. data/spec/lib/roo/excelx/cell/time_spec.rb +15 -0
  66. data/spec/lib/roo/excelx/format_spec.rb +7 -6
  67. data/spec/lib/roo/excelx/relationships_spec.rb +43 -0
  68. data/spec/lib/roo/excelx/sheet_doc_spec.rb +11 -0
  69. data/spec/lib/roo/excelx_spec.rb +672 -11
  70. data/spec/lib/roo/libreoffice_spec.rb +16 -6
  71. data/spec/lib/roo/openoffice_spec.rb +30 -8
  72. data/spec/lib/roo/spreadsheet_spec.rb +60 -12
  73. data/spec/lib/roo/strict_spec.rb +43 -0
  74. data/spec/lib/roo/utils_spec.rb +119 -0
  75. data/spec/lib/roo/weak_instance_cache_spec.rb +92 -0
  76. data/spec/lib/roo_spec.rb +0 -0
  77. data/spec/spec_helper.rb +7 -6
  78. data/test/all_ss.rb +12 -11
  79. data/test/excelx/cell/test_attr_reader_default.rb +72 -0
  80. data/test/excelx/cell/test_base.rb +68 -0
  81. data/test/excelx/cell/test_boolean.rb +36 -0
  82. data/test/excelx/cell/test_date.rb +38 -0
  83. data/test/excelx/cell/test_datetime.rb +45 -0
  84. data/test/excelx/cell/test_empty.rb +18 -0
  85. data/test/excelx/cell/test_number.rb +90 -0
  86. data/test/excelx/cell/test_string.rb +48 -0
  87. data/test/excelx/cell/test_time.rb +30 -0
  88. data/test/excelx/test_coordinate.rb +51 -0
  89. data/test/formatters/test_csv.rb +136 -0
  90. data/test/formatters/test_matrix.rb +76 -0
  91. data/test/formatters/test_xml.rb +78 -0
  92. data/test/formatters/test_yaml.rb +20 -0
  93. data/test/helpers/test_accessing_files.rb +81 -0
  94. data/test/helpers/test_comments.rb +43 -0
  95. data/test/helpers/test_formulas.rb +9 -0
  96. data/test/helpers/test_labels.rb +103 -0
  97. data/test/helpers/test_sheets.rb +55 -0
  98. data/test/helpers/test_styles.rb +62 -0
  99. data/test/roo/test_base.rb +182 -0
  100. data/test/roo/test_csv.rb +88 -0
  101. data/test/roo/test_excelx.rb +360 -0
  102. data/test/roo/test_libre_office.rb +9 -0
  103. data/test/roo/test_open_office.rb +289 -0
  104. data/test/test_helper.rb +123 -59
  105. data/test/test_roo.rb +392 -2292
  106. metadata +153 -298
  107. data/CHANGELOG +0 -417
  108. data/Gemfile.lock +0 -78
  109. data/README.markdown +0 -126
  110. data/VERSION +0 -1
  111. data/lib/roo/excel.rb +0 -355
  112. data/lib/roo/excel2003xml.rb +0 -300
  113. data/lib/roo/google.rb +0 -292
  114. data/lib/roo/openoffice.rb +0 -496
  115. data/lib/roo/roo_rails_helper.rb +0 -83
  116. data/lib/roo/worksheet.rb +0 -18
  117. data/scripts/txt2html +0 -67
  118. data/spec/lib/roo/excel2003xml_spec.rb +0 -15
  119. data/spec/lib/roo/excel_spec.rb +0 -17
  120. data/spec/lib/roo/google_spec.rb +0 -64
  121. data/test/files/1900_base.xls +0 -0
  122. data/test/files/1900_base.xlsx +0 -0
  123. data/test/files/1904_base.xls +0 -0
  124. data/test/files/1904_base.xlsx +0 -0
  125. data/test/files/Bibelbund.csv +0 -3741
  126. data/test/files/Bibelbund.ods +0 -0
  127. data/test/files/Bibelbund.xls +0 -0
  128. data/test/files/Bibelbund.xlsx +0 -0
  129. data/test/files/Bibelbund.xml +0 -62518
  130. data/test/files/Bibelbund1.ods +0 -0
  131. data/test/files/Pfand_from_windows_phone.xlsx +0 -0
  132. data/test/files/bad_excel_date.xls +0 -0
  133. data/test/files/bbu.ods +0 -0
  134. data/test/files/bbu.xls +0 -0
  135. data/test/files/bbu.xlsx +0 -0
  136. data/test/files/bbu.xml +0 -152
  137. data/test/files/bode-v1.ods.zip +0 -0
  138. data/test/files/bode-v1.xls.zip +0 -0
  139. data/test/files/boolean.csv +0 -2
  140. data/test/files/boolean.ods +0 -0
  141. data/test/files/boolean.xls +0 -0
  142. data/test/files/boolean.xlsx +0 -0
  143. data/test/files/boolean.xml +0 -112
  144. data/test/files/borders.ods +0 -0
  145. data/test/files/borders.xls +0 -0
  146. data/test/files/borders.xlsx +0 -0
  147. data/test/files/borders.xml +0 -144
  148. data/test/files/bug-numbered-sheet-names.xlsx +0 -0
  149. data/test/files/bug-row-column-fixnum-float.xls +0 -0
  150. data/test/files/bug-row-column-fixnum-float.xml +0 -127
  151. data/test/files/comments.ods +0 -0
  152. data/test/files/comments.xls +0 -0
  153. data/test/files/comments.xlsx +0 -0
  154. data/test/files/csvtypes.csv +0 -1
  155. data/test/files/datetime.ods +0 -0
  156. data/test/files/datetime.xls +0 -0
  157. data/test/files/datetime.xlsx +0 -0
  158. data/test/files/datetime.xml +0 -142
  159. data/test/files/datetime_floatconv.xls +0 -0
  160. data/test/files/datetime_floatconv.xml +0 -148
  161. data/test/files/dreimalvier.ods +0 -0
  162. data/test/files/emptysheets.ods +0 -0
  163. data/test/files/emptysheets.xls +0 -0
  164. data/test/files/emptysheets.xlsx +0 -0
  165. data/test/files/emptysheets.xml +0 -105
  166. data/test/files/excel2003.xml +0 -21140
  167. data/test/files/false_encoding.xls +0 -0
  168. data/test/files/false_encoding.xml +0 -132
  169. data/test/files/file_item_error.xlsx +0 -0
  170. data/test/files/formula.ods +0 -0
  171. data/test/files/formula.xls +0 -0
  172. data/test/files/formula.xlsx +0 -0
  173. data/test/files/formula.xml +0 -134
  174. data/test/files/formula_parse_error.xls +0 -0
  175. data/test/files/formula_parse_error.xml +0 -1833
  176. data/test/files/formula_string_error.xlsx +0 -0
  177. data/test/files/html-escape.ods +0 -0
  178. data/test/files/link.xls +0 -0
  179. data/test/files/link.xlsx +0 -0
  180. data/test/files/matrix.ods +0 -0
  181. data/test/files/matrix.xls +0 -0
  182. data/test/files/named_cells.ods +0 -0
  183. data/test/files/named_cells.xls +0 -0
  184. data/test/files/named_cells.xlsx +0 -0
  185. data/test/files/no_spreadsheet_file.txt +0 -1
  186. data/test/files/numbers1.csv +0 -18
  187. data/test/files/numbers1.ods +0 -0
  188. data/test/files/numbers1.xls +0 -0
  189. data/test/files/numbers1.xlsx +0 -0
  190. data/test/files/numbers1.xml +0 -312
  191. data/test/files/numeric-link.xlsx +0 -0
  192. data/test/files/only_one_sheet.ods +0 -0
  193. data/test/files/only_one_sheet.xls +0 -0
  194. data/test/files/only_one_sheet.xlsx +0 -0
  195. data/test/files/only_one_sheet.xml +0 -67
  196. data/test/files/paragraph.ods +0 -0
  197. data/test/files/paragraph.xls +0 -0
  198. data/test/files/paragraph.xlsx +0 -0
  199. data/test/files/paragraph.xml +0 -127
  200. data/test/files/prova.xls +0 -0
  201. data/test/files/ric.ods +0 -0
  202. data/test/files/simple_spreadsheet.ods +0 -0
  203. data/test/files/simple_spreadsheet.xls +0 -0
  204. data/test/files/simple_spreadsheet.xlsx +0 -0
  205. data/test/files/simple_spreadsheet.xml +0 -225
  206. data/test/files/simple_spreadsheet_from_italo.ods +0 -0
  207. data/test/files/simple_spreadsheet_from_italo.xls +0 -0
  208. data/test/files/simple_spreadsheet_from_italo.xml +0 -242
  209. data/test/files/so_datetime.csv +0 -7
  210. data/test/files/style.ods +0 -0
  211. data/test/files/style.xls +0 -0
  212. data/test/files/style.xlsx +0 -0
  213. data/test/files/style.xml +0 -154
  214. data/test/files/time-test.csv +0 -2
  215. data/test/files/time-test.ods +0 -0
  216. data/test/files/time-test.xls +0 -0
  217. data/test/files/time-test.xlsx +0 -0
  218. data/test/files/time-test.xml +0 -131
  219. data/test/files/type_excel.ods +0 -0
  220. data/test/files/type_excel.xlsx +0 -0
  221. data/test/files/type_excelx.ods +0 -0
  222. data/test/files/type_excelx.xls +0 -0
  223. data/test/files/type_openoffice.xls +0 -0
  224. data/test/files/type_openoffice.xlsx +0 -0
  225. data/test/files/whitespace.ods +0 -0
  226. data/test/files/whitespace.xls +0 -0
  227. data/test/files/whitespace.xlsx +0 -0
  228. data/test/files/whitespace.xml +0 -184
  229. data/test/rm_sub_test.rb +0 -12
  230. data/test/rm_test.rb +0 -7
  231. data/test/test_generic_spreadsheet.rb +0 -259
  232. data/website/index.html +0 -385
  233. data/website/index.txt +0 -423
  234. data/website/javascripts/rounded_corners_lite.inc.js +0 -285
  235. data/website/stylesheets/screen.css +0 -130
  236. 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
- require 'csv'
2
- require 'time'
3
-
4
- # The CSV class can read csv files (must be separated with commas) which then
5
- # can be handled like spreadsheets. This means you can access cells like A5
6
- # within these files.
7
- # The CSV class provides only string objects. If you want conversions to other
8
- # types you have to do it yourself.
9
- #
10
- # You can pass options to the underlying CSV parse operation, via the
11
- # :csv_options option.
12
- #
13
-
14
- class Roo::CSV < Roo::Base
15
- def initialize(filename, options = {})
16
- super
17
- end
18
-
19
- attr_reader :filename
20
-
21
- # Returns an array with the names of the sheets. In CSV class there is only
22
- # one dummy sheet, because a csv file cannot have more than one sheet.
23
- def sheets
24
- ['default']
25
- end
26
-
27
- def cell(row, col, sheet=nil)
28
- sheet ||= @default_sheet
29
- read_cells(sheet)
30
- @cell[normalize(row,col)]
31
- end
32
-
33
- def celltype(row, col, sheet=nil)
34
- sheet ||= @default_sheet
35
- read_cells(sheet)
36
- @cell_type[normalize(row,col)]
37
- end
38
-
39
- def cell_postprocessing(row,col,value)
40
- value
41
- end
42
-
43
- def csv_options
44
- @options[:csv_options] || {}
45
- end
46
-
47
- private
48
-
49
- TYPE_MAP = {
50
- String => :string,
51
- Float => :float,
52
- Date => :date,
53
- DateTime => :datetime,
54
- }
55
-
56
- def celltype_class(value)
57
- TYPE_MAP[value.class]
58
- end
59
-
60
- def each_row(options, &block)
61
- if uri?(filename)
62
- make_tmpdir do |tmpdir|
63
- tmp_filename = download_uri(filename, tmpdir)
64
- CSV.foreach(tmp_filename, options, &block)
65
- end
66
- else
67
- CSV.foreach(filename, options, &block)
68
- end
69
- end
70
-
71
- def read_cells(sheet=nil)
72
- sheet ||= @default_sheet
73
- return if @cells_read[sheet]
74
- @first_row[sheet] = 1
75
- @last_row[sheet] = 0
76
- @first_column[sheet] = 1
77
- @last_column[sheet] = 1
78
- rownum = 1
79
- each_row csv_options do |row|
80
- row.each_with_index do |elem,i|
81
- @cell[[rownum,i+1]] = cell_postprocessing rownum,i+1, elem
82
- @cell_type[[rownum,i+1]] = celltype_class @cell[[rownum,i+1]]
83
- if i+1 > @last_column[sheet]
84
- @last_column[sheet] += 1
85
- end
86
- end
87
- rownum += 1
88
- @last_row[sheet] += 1
89
- end
90
- @cells_read[sheet] = true
91
- #-- adjust @first_row if neccessary
92
- while !row(@first_row[sheet]).any? and @first_row[sheet] < @last_row[sheet]
93
- @first_row[sheet] += 1
94
- end
95
- #-- adjust @last_row if neccessary
96
- while !row(@last_row[sheet]).any? and @last_row[sheet] and
97
- @last_row[sheet] > @first_row[sheet]
98
- @last_row[sheet] -= 1
99
- end
100
- #-- adjust @first_column if neccessary
101
- while !column(@first_column[sheet]).any? and
102
- @first_column[sheet] and
103
- @first_column[sheet] < @last_column[sheet]
104
- @first_column[sheet] += 1
105
- end
106
- #-- adjust @last_column if neccessary
107
- while !column(@last_column[sheet]).any? and
108
- @last_column[sheet] and
109
- @last_column[sheet] > @first_column[sheet]
110
- @last_column[sheet] -= 1
111
- end
112
- end
113
- end
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