roo 1.13.2 → 2.7.0

Sign up to get free protection for your applications and to get access to all the features.
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