roo 1.13.2 → 2.7.0

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 (216) hide show
  1. checksums.yaml +4 -4
  2. data/.codeclimate.yml +17 -0
  3. data/.github/ISSUE_TEMPLATE +10 -0
  4. data/.gitignore +11 -0
  5. data/.simplecov +4 -0
  6. data/.travis.yml +17 -0
  7. data/CHANGELOG.md +626 -0
  8. data/Gemfile +17 -12
  9. data/Gemfile_ruby2 +30 -0
  10. data/Guardfile +23 -0
  11. data/LICENSE +3 -1
  12. data/README.md +285 -0
  13. data/Rakefile +23 -23
  14. data/examples/roo_soap_client.rb +28 -31
  15. data/examples/roo_soap_server.rb +4 -6
  16. data/examples/write_me.rb +9 -10
  17. data/lib/roo/base.rb +298 -495
  18. data/lib/roo/constants.rb +5 -0
  19. data/lib/roo/csv.rb +127 -113
  20. data/lib/roo/errors.rb +11 -0
  21. data/lib/roo/excelx/cell/base.rb +94 -0
  22. data/lib/roo/excelx/cell/boolean.rb +27 -0
  23. data/lib/roo/excelx/cell/date.rb +28 -0
  24. data/lib/roo/excelx/cell/datetime.rb +111 -0
  25. data/lib/roo/excelx/cell/empty.rb +19 -0
  26. data/lib/roo/excelx/cell/number.rb +87 -0
  27. data/lib/roo/excelx/cell/string.rb +19 -0
  28. data/lib/roo/excelx/cell/time.rb +43 -0
  29. data/lib/roo/excelx/cell.rb +106 -0
  30. data/lib/roo/excelx/comments.rb +55 -0
  31. data/lib/roo/excelx/coordinate.rb +12 -0
  32. data/lib/roo/excelx/extractor.rb +21 -0
  33. data/lib/roo/excelx/format.rb +64 -0
  34. data/lib/roo/excelx/relationships.rb +25 -0
  35. data/lib/roo/excelx/shared.rb +32 -0
  36. data/lib/roo/excelx/shared_strings.rb +157 -0
  37. data/lib/roo/excelx/sheet.rb +112 -0
  38. data/lib/roo/excelx/sheet_doc.rb +211 -0
  39. data/lib/roo/excelx/styles.rb +64 -0
  40. data/lib/roo/excelx/workbook.rb +59 -0
  41. data/lib/roo/excelx.rb +376 -602
  42. data/lib/roo/font.rb +17 -0
  43. data/lib/roo/formatters/base.rb +15 -0
  44. data/lib/roo/formatters/csv.rb +84 -0
  45. data/lib/roo/formatters/matrix.rb +23 -0
  46. data/lib/roo/formatters/xml.rb +31 -0
  47. data/lib/roo/formatters/yaml.rb +40 -0
  48. data/lib/roo/libre_office.rb +4 -0
  49. data/lib/roo/link.rb +34 -0
  50. data/lib/roo/open_office.rb +626 -0
  51. data/lib/roo/spreadsheet.rb +22 -23
  52. data/lib/roo/tempdir.rb +21 -0
  53. data/lib/roo/utils.rb +78 -0
  54. data/lib/roo/version.rb +3 -0
  55. data/lib/roo.rb +23 -24
  56. data/roo.gemspec +21 -204
  57. data/spec/helpers.rb +5 -0
  58. data/spec/lib/roo/base_spec.rb +229 -3
  59. data/spec/lib/roo/csv_spec.rb +38 -11
  60. data/spec/lib/roo/excelx/format_spec.rb +7 -6
  61. data/spec/lib/roo/excelx_spec.rb +510 -11
  62. data/spec/lib/roo/libreoffice_spec.rb +16 -6
  63. data/spec/lib/roo/openoffice_spec.rb +30 -8
  64. data/spec/lib/roo/spreadsheet_spec.rb +60 -12
  65. data/spec/lib/roo/utils_spec.rb +106 -0
  66. data/spec/spec_helper.rb +7 -6
  67. data/test/all_ss.rb +12 -11
  68. data/test/excelx/cell/test_base.rb +63 -0
  69. data/test/excelx/cell/test_boolean.rb +36 -0
  70. data/test/excelx/cell/test_date.rb +38 -0
  71. data/test/excelx/cell/test_datetime.rb +45 -0
  72. data/test/excelx/cell/test_empty.rb +7 -0
  73. data/test/excelx/cell/test_number.rb +74 -0
  74. data/test/excelx/cell/test_string.rb +28 -0
  75. data/test/excelx/cell/test_time.rb +30 -0
  76. data/test/formatters/test_csv.rb +119 -0
  77. data/test/formatters/test_matrix.rb +76 -0
  78. data/test/formatters/test_xml.rb +74 -0
  79. data/test/formatters/test_yaml.rb +20 -0
  80. data/test/roo/test_csv.rb +52 -0
  81. data/test/roo/test_excelx.rb +186 -0
  82. data/test/roo/test_libre_office.rb +9 -0
  83. data/test/roo/test_open_office.rb +126 -0
  84. data/test/test_helper.rb +73 -53
  85. data/test/test_roo.rb +1211 -2292
  86. metadata +119 -298
  87. data/CHANGELOG +0 -417
  88. data/Gemfile.lock +0 -78
  89. data/README.markdown +0 -126
  90. data/VERSION +0 -1
  91. data/lib/roo/excel.rb +0 -355
  92. data/lib/roo/excel2003xml.rb +0 -300
  93. data/lib/roo/google.rb +0 -292
  94. data/lib/roo/openoffice.rb +0 -496
  95. data/lib/roo/roo_rails_helper.rb +0 -83
  96. data/lib/roo/worksheet.rb +0 -18
  97. data/scripts/txt2html +0 -67
  98. data/spec/lib/roo/excel2003xml_spec.rb +0 -15
  99. data/spec/lib/roo/excel_spec.rb +0 -17
  100. data/spec/lib/roo/google_spec.rb +0 -64
  101. data/test/files/1900_base.xls +0 -0
  102. data/test/files/1900_base.xlsx +0 -0
  103. data/test/files/1904_base.xls +0 -0
  104. data/test/files/1904_base.xlsx +0 -0
  105. data/test/files/Bibelbund.csv +0 -3741
  106. data/test/files/Bibelbund.ods +0 -0
  107. data/test/files/Bibelbund.xls +0 -0
  108. data/test/files/Bibelbund.xlsx +0 -0
  109. data/test/files/Bibelbund.xml +0 -62518
  110. data/test/files/Bibelbund1.ods +0 -0
  111. data/test/files/Pfand_from_windows_phone.xlsx +0 -0
  112. data/test/files/bad_excel_date.xls +0 -0
  113. data/test/files/bbu.ods +0 -0
  114. data/test/files/bbu.xls +0 -0
  115. data/test/files/bbu.xlsx +0 -0
  116. data/test/files/bbu.xml +0 -152
  117. data/test/files/bode-v1.ods.zip +0 -0
  118. data/test/files/bode-v1.xls.zip +0 -0
  119. data/test/files/boolean.csv +0 -2
  120. data/test/files/boolean.ods +0 -0
  121. data/test/files/boolean.xls +0 -0
  122. data/test/files/boolean.xlsx +0 -0
  123. data/test/files/boolean.xml +0 -112
  124. data/test/files/borders.ods +0 -0
  125. data/test/files/borders.xls +0 -0
  126. data/test/files/borders.xlsx +0 -0
  127. data/test/files/borders.xml +0 -144
  128. data/test/files/bug-numbered-sheet-names.xlsx +0 -0
  129. data/test/files/bug-row-column-fixnum-float.xls +0 -0
  130. data/test/files/bug-row-column-fixnum-float.xml +0 -127
  131. data/test/files/comments.ods +0 -0
  132. data/test/files/comments.xls +0 -0
  133. data/test/files/comments.xlsx +0 -0
  134. data/test/files/csvtypes.csv +0 -1
  135. data/test/files/datetime.ods +0 -0
  136. data/test/files/datetime.xls +0 -0
  137. data/test/files/datetime.xlsx +0 -0
  138. data/test/files/datetime.xml +0 -142
  139. data/test/files/datetime_floatconv.xls +0 -0
  140. data/test/files/datetime_floatconv.xml +0 -148
  141. data/test/files/dreimalvier.ods +0 -0
  142. data/test/files/emptysheets.ods +0 -0
  143. data/test/files/emptysheets.xls +0 -0
  144. data/test/files/emptysheets.xlsx +0 -0
  145. data/test/files/emptysheets.xml +0 -105
  146. data/test/files/excel2003.xml +0 -21140
  147. data/test/files/false_encoding.xls +0 -0
  148. data/test/files/false_encoding.xml +0 -132
  149. data/test/files/file_item_error.xlsx +0 -0
  150. data/test/files/formula.ods +0 -0
  151. data/test/files/formula.xls +0 -0
  152. data/test/files/formula.xlsx +0 -0
  153. data/test/files/formula.xml +0 -134
  154. data/test/files/formula_parse_error.xls +0 -0
  155. data/test/files/formula_parse_error.xml +0 -1833
  156. data/test/files/formula_string_error.xlsx +0 -0
  157. data/test/files/html-escape.ods +0 -0
  158. data/test/files/link.xls +0 -0
  159. data/test/files/link.xlsx +0 -0
  160. data/test/files/matrix.ods +0 -0
  161. data/test/files/matrix.xls +0 -0
  162. data/test/files/named_cells.ods +0 -0
  163. data/test/files/named_cells.xls +0 -0
  164. data/test/files/named_cells.xlsx +0 -0
  165. data/test/files/no_spreadsheet_file.txt +0 -1
  166. data/test/files/numbers1.csv +0 -18
  167. data/test/files/numbers1.ods +0 -0
  168. data/test/files/numbers1.xls +0 -0
  169. data/test/files/numbers1.xlsx +0 -0
  170. data/test/files/numbers1.xml +0 -312
  171. data/test/files/numeric-link.xlsx +0 -0
  172. data/test/files/only_one_sheet.ods +0 -0
  173. data/test/files/only_one_sheet.xls +0 -0
  174. data/test/files/only_one_sheet.xlsx +0 -0
  175. data/test/files/only_one_sheet.xml +0 -67
  176. data/test/files/paragraph.ods +0 -0
  177. data/test/files/paragraph.xls +0 -0
  178. data/test/files/paragraph.xlsx +0 -0
  179. data/test/files/paragraph.xml +0 -127
  180. data/test/files/prova.xls +0 -0
  181. data/test/files/ric.ods +0 -0
  182. data/test/files/simple_spreadsheet.ods +0 -0
  183. data/test/files/simple_spreadsheet.xls +0 -0
  184. data/test/files/simple_spreadsheet.xlsx +0 -0
  185. data/test/files/simple_spreadsheet.xml +0 -225
  186. data/test/files/simple_spreadsheet_from_italo.ods +0 -0
  187. data/test/files/simple_spreadsheet_from_italo.xls +0 -0
  188. data/test/files/simple_spreadsheet_from_italo.xml +0 -242
  189. data/test/files/so_datetime.csv +0 -7
  190. data/test/files/style.ods +0 -0
  191. data/test/files/style.xls +0 -0
  192. data/test/files/style.xlsx +0 -0
  193. data/test/files/style.xml +0 -154
  194. data/test/files/time-test.csv +0 -2
  195. data/test/files/time-test.ods +0 -0
  196. data/test/files/time-test.xls +0 -0
  197. data/test/files/time-test.xlsx +0 -0
  198. data/test/files/time-test.xml +0 -131
  199. data/test/files/type_excel.ods +0 -0
  200. data/test/files/type_excel.xlsx +0 -0
  201. data/test/files/type_excelx.ods +0 -0
  202. data/test/files/type_excelx.xls +0 -0
  203. data/test/files/type_openoffice.xls +0 -0
  204. data/test/files/type_openoffice.xlsx +0 -0
  205. data/test/files/whitespace.ods +0 -0
  206. data/test/files/whitespace.xls +0 -0
  207. data/test/files/whitespace.xlsx +0 -0
  208. data/test/files/whitespace.xml +0 -184
  209. data/test/rm_sub_test.rb +0 -12
  210. data/test/rm_test.rb +0 -7
  211. data/test/test_generic_spreadsheet.rb +0 -259
  212. data/website/index.html +0 -385
  213. data/website/index.txt +0 -423
  214. data/website/javascripts/rounded_corners_lite.inc.js +0 -285
  215. data/website/stylesheets/screen.css +0 -130
  216. data/website/template.rhtml +0 -48
@@ -0,0 +1,5 @@
1
+ module Roo
2
+ 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.".freeze
3
+ ROO_EXCELML_NOTICE = "Excel SpreadsheetML support has been extracted to roo-xls. Install roo-xls to use Roo::Excel2003XML.".freeze
4
+ ROO_GOOGLE_NOTICE = "Google support has been extracted to roo-google. Install roo-google to use Roo::Google.".freeze
5
+ end
data/lib/roo/csv.rb CHANGED
@@ -1,113 +1,127 @@
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
+ 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
+ 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
21
+
22
+ def cell(row, col, sheet = nil)
23
+ sheet ||= default_sheet
24
+ read_cells(sheet)
25
+ @cell[normalize(row, col)]
26
+ end
27
+
28
+ def celltype(row, col, sheet = nil)
29
+ sheet ||= default_sheet
30
+ read_cells(sheet)
31
+ @cell_type[normalize(row, col)]
32
+ end
33
+
34
+ def cell_postprocessing(_row, _col, value)
35
+ value
36
+ end
37
+
38
+ def csv_options
39
+ @options[:csv_options] || {}
40
+ end
41
+
42
+ def set_value(row, col, value, _sheet)
43
+ @cell[[row, col]] = value
44
+ end
45
+
46
+ def set_type(row, col, type, _sheet)
47
+ @cell_type[[row, col]] = type
48
+ end
49
+
50
+ private
51
+
52
+ TYPE_MAP = {
53
+ String => :string,
54
+ Float => :float,
55
+ Date => :date,
56
+ DateTime => :datetime,
57
+ }
58
+
59
+ def celltype_class(value)
60
+ TYPE_MAP[value.class]
61
+ end
62
+
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)
75
+ end
76
+ row_num += 1
77
+ end
78
+
79
+ @cells_read[sheet] = true
80
+ end
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
90
+ end
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
97
+ end
98
+
99
+ def set_row_count(sheet)
100
+ @first_row[sheet] = 1
101
+ @last_row[sheet] = ::CSV.readlines(@filename).size
102
+ @last_row[sheet] = @first_row[sheet] if @last_row[sheet].zero?
103
+
104
+ nil
105
+ end
106
+
107
+ def set_column_count(sheet)
108
+ @first_column[sheet] = 1
109
+ @last_column[sheet] = (::CSV.readlines(@filename).first || []).size
110
+ @last_column[sheet] = @first_column[sheet] if @last_column[sheet].zero?
111
+
112
+ nil
113
+ end
114
+
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
121
+
122
+ @cleaned[sheet] = true
123
+ end
124
+
125
+ alias_method :filename_or_stream, :filename
126
+ end
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