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
data/lib/roo/base.rb CHANGED
@@ -1,51 +1,40 @@
1
- # encoding: utf-8
2
-
3
- require 'tmpdir'
4
- require 'stringio'
5
-
6
- begin
7
- require 'zip/zipfilesystem'
8
- Roo::ZipFile = Zip::ZipFile
9
- rescue LoadError
10
- # For rubyzip >= 1.0.0
11
- require 'zip/filesystem'
12
- Roo::ZipFile = Zip::File
13
- end
1
+ require "tmpdir"
2
+ require "stringio"
3
+ require "nokogiri"
4
+ require "roo/utils"
5
+ require "roo/formatters/base"
6
+ require "roo/formatters/csv"
7
+ require "roo/formatters/matrix"
8
+ require "roo/formatters/xml"
9
+ require "roo/formatters/yaml"
14
10
 
15
11
  # Base class for all other types of spreadsheets
16
12
  class Roo::Base
17
13
  include Enumerable
14
+ include Roo::Formatters::Base
15
+ include Roo::Formatters::CSV
16
+ include Roo::Formatters::Matrix
17
+ include Roo::Formatters::XML
18
+ include Roo::Formatters::YAML
18
19
 
19
- TEMP_PREFIX = "oo_"
20
+ MAX_ROW_COL = 999_999
21
+ MIN_ROW_COL = 0
20
22
 
21
- attr_reader :default_sheet, :headers
23
+ attr_reader :headers
22
24
 
23
25
  # sets the line with attribute names (default: 1)
24
26
  attr_accessor :header_line
25
27
 
26
- protected
27
-
28
- def self.split_coordinate(str)
29
- letter,number = Roo::Base.split_coord(str)
30
- x = letter_to_number(letter)
31
- y = number
32
- return y, x
28
+ def self.TEMP_PREFIX
29
+ warn "[DEPRECATION] please access TEMP_PREFIX via Roo::TEMP_PREFIX"
30
+ Roo::TEMP_PREFIX
33
31
  end
34
32
 
35
- def self.split_coord(s)
36
- if s =~ /([a-zA-Z]+)([0-9]+)/
37
- letter = $1
38
- number = $2.to_i
39
- else
40
- raise ArgumentError
41
- end
42
- return letter, number
33
+ def self.finalize(object_id)
34
+ proc { finalize_tempdirs(object_id) }
43
35
  end
44
36
 
45
-
46
- public
47
-
48
- def initialize(filename, options={}, file_warning=:error, tmpdir=nil)
37
+ def initialize(filename, options = {}, _file_warning = :error, _tmpdir = nil)
49
38
  @filename = filename
50
39
  @options = options
51
40
 
@@ -59,158 +48,81 @@ class Roo::Base
59
48
  @last_column = {}
60
49
 
61
50
  @header_line = 1
62
- @default_sheet = self.sheets.first
63
- @header_line = 1
51
+ end
52
+
53
+ def close
54
+ if self.class.respond_to?(:finalize_tempdirs)
55
+ self.class.finalize_tempdirs(object_id)
56
+ end
57
+
58
+ instance_variables.each do |instance_variable|
59
+ instance_variable_set(instance_variable, nil)
60
+ end
61
+
62
+ nil
63
+ end
64
+
65
+ def default_sheet
66
+ @default_sheet ||= sheets.first
64
67
  end
65
68
 
66
69
  # sets the working sheet in the document
67
- # 'sheet' can be a number (1 = first sheet) or the name of a sheet.
70
+ # 'sheet' can be a number (0 = first sheet) or the name of a sheet.
68
71
  def default_sheet=(sheet)
69
72
  validate_sheet!(sheet)
70
- @default_sheet = sheet
73
+ @default_sheet = sheet.is_a?(String) ? sheet : sheets[sheet]
71
74
  @first_row[sheet] = @last_row[sheet] = @first_column[sheet] = @last_column[sheet] = nil
72
75
  @cells_read[sheet] = false
73
76
  end
74
77
 
75
78
  # first non-empty column as a letter
76
- def first_column_as_letter(sheet=nil)
77
- Roo::Base.number_to_letter(first_column(sheet))
79
+ def first_column_as_letter(sheet = default_sheet)
80
+ ::Roo::Utils.number_to_letter(first_column(sheet))
78
81
  end
79
82
 
80
83
  # last non-empty column as a letter
81
- def last_column_as_letter(sheet=nil)
82
- Roo::Base.number_to_letter(last_column(sheet))
84
+ def last_column_as_letter(sheet = default_sheet)
85
+ ::Roo::Utils.number_to_letter(last_column(sheet))
86
+ end
87
+
88
+ # Set first/last row/column for sheet
89
+ def first_last_row_col_for_sheet(sheet)
90
+ @first_last_row_cols ||= {}
91
+ @first_last_row_cols[sheet] ||= begin
92
+ result = collect_last_row_col_for_sheet(sheet)
93
+ {
94
+ first_row: result[:first_row] == MAX_ROW_COL ? nil : result[:first_row],
95
+ first_column: result[:first_column] == MAX_ROW_COL ? nil : result[:first_column],
96
+ last_row: result[:last_row] == MIN_ROW_COL ? nil : result[:last_row],
97
+ last_column: result[:last_column] == MIN_ROW_COL ? nil : result[:last_column]
98
+ }
99
+ end
83
100
  end
84
101
 
85
- # returns the number of the first non-empty row
86
- def first_row(sheet=nil)
87
- sheet ||= @default_sheet
88
- read_cells(sheet)
89
- if @first_row[sheet]
90
- return @first_row[sheet]
91
- end
92
- impossible_value = 999_999 # more than a spreadsheet can hold
93
- result = impossible_value
94
- @cell[sheet].each_pair {|key,value|
95
- y = key.first.to_i # _to_string(key).split(',')
96
- result = [result, y].min if value
97
- } if @cell[sheet]
98
- result = nil if result == impossible_value
99
- @first_row[sheet] = result
100
- result
101
- end
102
-
103
- # returns the number of the last non-empty row
104
- def last_row(sheet=nil)
105
- sheet ||= @default_sheet
106
- read_cells(sheet)
107
- if @last_row[sheet]
108
- return @last_row[sheet]
109
- end
110
- impossible_value = 0
111
- result = impossible_value
112
- @cell[sheet].each_pair {|key,value|
113
- y = key.first.to_i # _to_string(key).split(',')
114
- result = [result, y].max if value
115
- } if @cell[sheet]
116
- result = nil if result == impossible_value
117
- @last_row[sheet] = result
118
- result
119
- end
120
-
121
- # returns the number of the first non-empty column
122
- def first_column(sheet=nil)
123
- sheet ||= @default_sheet
124
- read_cells(sheet)
125
- if @first_column[sheet]
126
- return @first_column[sheet]
127
- end
128
- impossible_value = 999_999 # more than a spreadsheet can hold
129
- result = impossible_value
130
- @cell[sheet].each_pair {|key,value|
131
- x = key.last.to_i # _to_string(key).split(',')
132
- result = [result, x].min if value
133
- } if @cell[sheet]
134
- result = nil if result == impossible_value
135
- @first_column[sheet] = result
136
- result
137
- end
138
-
139
- # returns the number of the last non-empty column
140
- def last_column(sheet=nil)
141
- sheet ||= @default_sheet
142
- read_cells(sheet)
143
- if @last_column[sheet]
144
- return @last_column[sheet]
145
- end
146
- impossible_value = 0
147
- result = impossible_value
148
- @cell[sheet].each_pair {|key,value|
149
- x = key.last.to_i # _to_string(key).split(',')
150
- result = [result, x].max if value
151
- } if @cell[sheet]
152
- result = nil if result == impossible_value
153
- @last_column[sheet] = result
154
- result
155
- end
156
-
157
- # returns a rectangular area (default: all cells) as yaml-output
158
- # you can add additional attributes with the prefix parameter like:
159
- # oo.to_yaml({"file"=>"flightdata_2007-06-26", "sheet" => "1"})
160
- def to_yaml(prefix={}, from_row=nil, from_column=nil, to_row=nil, to_column=nil,sheet=nil)
161
- sheet ||= @default_sheet
162
- result = "--- \n"
163
- return '' unless first_row # empty result if there is no first_row in a sheet
164
-
165
- (from_row||first_row(sheet)).upto(to_row||last_row(sheet)) do |row|
166
- (from_column||first_column(sheet)).upto(to_column||last_column(sheet)) do |col|
167
- unless empty?(row,col,sheet)
168
- result << "cell_#{row}_#{col}: \n"
169
- prefix.each {|k,v|
170
- result << " #{k}: #{v} \n"
171
- }
172
- result << " row: #{row} \n"
173
- result << " col: #{col} \n"
174
- result << " celltype: #{self.celltype(row,col,sheet)} \n"
175
- if self.celltype(row,col,sheet) == :time
176
- result << " value: #{Roo::Base.integer_to_timestring( self.cell(row,col,sheet))} \n"
177
- else
178
- result << " value: #{self.cell(row,col,sheet)} \n"
179
- end
180
- end
181
- end
182
- end
183
- result
102
+ # Collect first/last row/column from sheet
103
+ def collect_last_row_col_for_sheet(sheet)
104
+ first_row = first_column = MAX_ROW_COL
105
+ last_row = last_column = MIN_ROW_COL
106
+ @cell[sheet].each_pair do |key, value|
107
+ next unless value
108
+ first_row = [first_row, key.first.to_i].min
109
+ last_row = [last_row, key.first.to_i].max
110
+ first_column = [first_column, key.last.to_i].min
111
+ last_column = [last_column, key.last.to_i].max
112
+ end if @cell[sheet]
113
+ { first_row: first_row, first_column: first_column, last_row: last_row, last_column: last_column }
184
114
  end
185
115
 
186
- # write the current spreadsheet to stdout or into a file
187
- def to_csv(filename=nil,sheet=nil)
188
- sheet ||= @default_sheet
189
- if filename
190
- File.open(filename,"w") do |file|
191
- write_csv_content(file,sheet)
192
- end
193
- return true
194
- else
195
- sio = StringIO.new
196
- write_csv_content(sio,sheet)
197
- sio.rewind
198
- return sio.read
116
+ %i(first_row last_row first_column last_column).each do |key|
117
+ ivar = "@#{key}".to_sym
118
+ define_method(key) do |sheet = default_sheet|
119
+ read_cells(sheet)
120
+ instance_variable_get(ivar)[sheet] ||= first_last_row_col_for_sheet(sheet)[key]
199
121
  end
200
122
  end
201
123
 
202
- # returns a matrix object from the whole sheet or a rectangular area of a sheet
203
- def to_matrix(from_row=nil, from_column=nil, to_row=nil, to_column=nil,sheet=nil)
204
- require 'matrix'
205
-
206
- sheet ||= @default_sheet
207
- return Matrix.empty unless first_row
208
-
209
- Matrix.rows((from_row||first_row(sheet)).upto(to_row||last_row(sheet)).map do |row|
210
- (from_column||first_column(sheet)).upto(to_column||last_column(sheet)).map do |col|
211
- cell(row,col,sheet)
212
- end
213
- end)
124
+ def inspect
125
+ "<##{self.class}:#{object_id.to_s(8)} #{instance_variables.join(' ')}>"
214
126
  end
215
127
 
216
128
  # find a row either by row number or a condition
@@ -218,98 +130,68 @@ class Roo::Base
218
130
  # (experimental. see examples in the test_roo.rb file)
219
131
  def find(*args) # :nodoc
220
132
  options = (args.last.is_a?(Hash) ? args.pop : {})
221
- result_array = options[:array]
222
- header_for = Hash[1.upto(last_column).map do |col|
223
- [col, cell(@header_line,col)]
224
- end]
225
- #-- id
226
- if args[0].class == Fixnum
227
- rownum = args[0]
228
- if @header_line
229
- [Hash[1.upto(self.row().size).map {|j|
230
- [header_for.fetch(j), cell(rownum,j)]
231
- }]]
232
- else
233
- self.row(rownum).size.times.map {|j|
234
- cell(rownum,j + 1)
235
- }
236
- end
237
- #-- :all
238
- elsif args[0] == :all
239
- rows = first_row.upto(last_row)
240
-
241
- # are all conditions met?
242
- if (conditions = options[:conditions]) && !conditions.empty?
243
- column_with = header_for.invert
244
- rows = rows.select do |i|
245
- conditions.all? { |key,val| cell(i,column_with[key]) == val }
246
- end
247
- end
248
133
 
249
- rows.map do |i|
250
- if result_array
251
- self.row(i)
252
- else
253
- Hash[1.upto(self.row(i).size).map do |j|
254
- [header_for.fetch(j), cell(i,j)]
255
- end]
256
- end
257
- end
134
+ case args[0]
135
+ when Integer
136
+ find_by_row(args[0])
137
+ when :all
138
+ find_by_conditions(options)
139
+ else
140
+ fail ArgumentError, "unexpected arg #{args[0].inspect}, pass a row index or :all"
258
141
  end
259
142
  end
260
143
 
261
144
  # returns all values in this row as an array
262
145
  # row numbers are 1,2,3,... like in the spreadsheet
263
- def row(rownumber,sheet=nil)
264
- sheet ||= @default_sheet
146
+ def row(row_number, sheet = default_sheet)
265
147
  read_cells(sheet)
266
148
  first_column(sheet).upto(last_column(sheet)).map do |col|
267
- cell(rownumber,col,sheet)
149
+ cell(row_number, col, sheet)
268
150
  end
269
151
  end
270
152
 
271
153
  # returns all values in this column as an array
272
154
  # column numbers are 1,2,3,... like in the spreadsheet
273
- def column(columnnumber,sheet=nil)
274
- if columnnumber.class == String
275
- columnnumber = Roo::Excel.letter_to_number(columnnumber)
155
+ def column(column_number, sheet = default_sheet)
156
+ if column_number.is_a?(::String)
157
+ column_number = ::Roo::Utils.letter_to_number(column_number)
276
158
  end
277
- sheet ||= @default_sheet
278
159
  read_cells(sheet)
279
160
  first_row(sheet).upto(last_row(sheet)).map do |row|
280
- cell(row,columnnumber,sheet)
161
+ cell(row, column_number, sheet)
281
162
  end
282
163
  end
283
164
 
284
165
  # set a cell to a certain value
285
166
  # (this will not be saved back to the spreadsheet file!)
286
- def set(row,col,value,sheet=nil) #:nodoc:
287
- sheet ||= @default_sheet
167
+ def set(row, col, value, sheet = default_sheet) #:nodoc:
288
168
  read_cells(sheet)
289
- row, col = normalize(row,col)
290
- cell_type = case value
291
- when Fixnum then :float
292
- when String, Float then :string
293
- else
294
- raise ArgumentError, "Type for #{value} not set"
295
- end
169
+ row, col = normalize(row, col)
170
+ cell_type = cell_type_by_value(value)
171
+ set_value(row, col, value, sheet)
172
+ set_type(row, col, cell_type, sheet)
173
+ end
296
174
 
297
- set_value(row,col,value,sheet)
298
- set_type(row,col,cell_type,sheet)
175
+ def cell_type_by_value(value)
176
+ case value
177
+ when Integer then :float
178
+ when String, Float then :string
179
+ else
180
+ fail ArgumentError, "Type for #{value} not set"
181
+ end
299
182
  end
300
183
 
301
184
  # reopens and read a spreadsheet document
302
185
  def reload
303
- ds = @default_sheet
186
+ ds = default_sheet
304
187
  reinitialize
305
188
  self.default_sheet = ds
306
189
  end
307
190
 
308
191
  # true if cell is empty
309
- def empty?(row, col, sheet=nil)
310
- sheet ||= @default_sheet
192
+ def empty?(row, col, sheet = default_sheet)
311
193
  read_cells(sheet)
312
- row,col = normalize(row,col)
194
+ row, col = normalize(row, col)
313
195
  contents = cell(row, col, sheet)
314
196
  !contents || (celltype(row, col, sheet) == :string && contents.empty?) \
315
197
  || (row < first_row(sheet) || row > last_row(sheet) || col < first_column(sheet) || col > last_column(sheet))
@@ -319,66 +201,40 @@ class Roo::Base
319
201
  # this document.
320
202
  def info
321
203
  without_changing_default_sheet do
322
- result = "File: #{File.basename(@filename)}\n"+
323
- "Number of sheets: #{sheets.size}\n"+
204
+ result = "File: #{File.basename(@filename)}\n"\
205
+ "Number of sheets: #{sheets.size}\n"\
324
206
  "Sheets: #{sheets.join(', ')}\n"
325
207
  n = 1
326
- sheets.each {|sheet|
208
+ sheets.each do |sheet|
327
209
  self.default_sheet = sheet
328
210
  result << "Sheet " + n.to_s + ":\n"
329
- unless first_row
330
- result << " - empty -"
331
- else
211
+ if first_row
332
212
  result << " First row: #{first_row}\n"
333
213
  result << " Last row: #{last_row}\n"
334
- result << " First column: #{Roo::Base.number_to_letter(first_column)}\n"
335
- result << " Last column: #{Roo::Base.number_to_letter(last_column)}"
214
+ result << " First column: #{::Roo::Utils.number_to_letter(first_column)}\n"
215
+ result << " Last column: #{::Roo::Utils.number_to_letter(last_column)}"
216
+ else
217
+ result << " - empty -"
336
218
  end
337
219
  result << "\n" if sheet != sheets.last
338
220
  n += 1
339
- }
221
+ end
340
222
  result
341
223
  end
342
224
  end
343
225
 
344
- # returns an XML representation of all sheets of a spreadsheet file
345
- def to_xml
346
- Nokogiri::XML::Builder.new do |xml|
347
- xml.spreadsheet {
348
- self.sheets.each do |sheet|
349
- self.default_sheet = sheet
350
- xml.sheet(:name => sheet) { |x|
351
- if first_row and last_row and first_column and last_column
352
- # sonst gibt es Fehler bei leeren Blaettern
353
- first_row.upto(last_row) do |row|
354
- first_column.upto(last_column) do |col|
355
- unless empty?(row,col)
356
- x.cell(cell(row,col),
357
- :row =>row,
358
- :column => col,
359
- :type => celltype(row,col))
360
- end
361
- end
362
- end
363
- end
364
- }
365
- end
366
- }
367
- end.to_xml
368
- end
369
-
370
226
  # when a method like spreadsheet.a42 is called
371
227
  # convert it to a call of spreadsheet.cell('a',42)
372
228
  def method_missing(m, *args)
373
229
  # #aa42 => #cell('aa',42)
374
230
  # #aa42('Sheet1') => #cell('aa',42,'Sheet1')
375
- if m =~ /^([a-z]+)(\d)$/
376
- col = Roo::Base.letter_to_number($1)
377
- row = $2.to_i
231
+ if m =~ /^([a-z]+)(\d+)$/
232
+ col = ::Roo::Utils.letter_to_number(Regexp.last_match[1])
233
+ row = Regexp.last_match[2].to_i
378
234
  if args.empty?
379
- cell(row,col)
235
+ cell(row, col)
380
236
  else
381
- cell(row,col,args.first)
237
+ cell(row, col, args.first)
382
238
  end
383
239
  else
384
240
  super
@@ -387,15 +243,17 @@ class Roo::Base
387
243
 
388
244
  # access different worksheets by calling spreadsheet.sheet(1)
389
245
  # or spreadsheet.sheet('SHEETNAME')
390
- def sheet(index,name=false)
391
- @default_sheet = String === index ? index : self.sheets[index]
392
- name ? [@default_sheet,self] : self
246
+ def sheet(index, name = false)
247
+ self.default_sheet = index.is_a?(::String) ? index : sheets[index]
248
+ name ? [default_sheet, self] : self
393
249
  end
394
250
 
395
251
  # iterate through all worksheets of a document
396
252
  def each_with_pagename
397
- self.sheets.each do |s|
398
- yield sheet(s,true)
253
+ return to_enum(:each_with_pagename) { sheets.size } unless block_given?
254
+
255
+ sheets.each do |s|
256
+ yield sheet(s, true)
399
257
  end
400
258
  end
401
259
 
@@ -409,7 +267,6 @@ class Roo::Base
409
267
  # such as :price => '^(Cost|Price)'
410
268
  # case insensitive by default
411
269
 
412
-
413
270
  # by using the :header_search option, you can query for headers
414
271
  # and return a hash of every row with the keys set to the header result
415
272
  # for example:
@@ -420,116 +277,85 @@ class Roo::Base
420
277
  # * is the wildcard character
421
278
 
422
279
  # you can also pass in a :clean => true option to strip the sheet of
423
- # odd unicode characters and white spaces around columns
280
+ # control characters and white spaces around columns
281
+
282
+ def each(options = {})
283
+ return to_enum(:each, options) unless block_given?
424
284
 
425
- def each(options={})
426
285
  if options.empty?
427
286
  1.upto(last_row) do |line|
428
287
  yield row(line)
429
288
  end
430
289
  else
431
- if options[:clean]
432
- options.delete(:clean)
433
- @cleaned ||= {}
434
- @cleaned[@default_sheet] || clean_sheet(@default_sheet)
435
- end
436
-
437
- if options[:header_search]
438
- @headers = nil
439
- @header_line = row_with(options[:header_search])
440
- elsif [:first_row,true].include?(options[:headers])
441
- @headers = []
442
- row(first_row).each_with_index {|x,i| @headers << [x,i + 1]}
443
- else
444
- set_headers(options)
445
- end
446
-
290
+ clean_sheet_if_need(options)
291
+ search_or_set_header(options)
447
292
  headers = @headers ||
448
- Hash[(first_column..last_column).map do |col|
449
- [cell(@header_line,col), col]
450
- end]
293
+ (first_column..last_column).each_with_object({}) do |col, hash|
294
+ hash[cell(@header_line, col)] = col
295
+ end
451
296
 
452
297
  @header_line.upto(last_row) do |line|
453
- yield(Hash[headers.map {|k,v| [k,cell(line,v)]}])
298
+ yield(headers.each_with_object({}) { |(k, v), hash| hash[k] = cell(line, v) })
454
299
  end
455
300
  end
456
301
  end
457
302
 
458
- def parse(options={})
459
- ary = []
460
- if block_given?
461
- each(options) {|row| ary << yield(row)}
462
- else
463
- each(options) {|row| ary << row}
303
+ def parse(options = {})
304
+ results = each(options).map do |row|
305
+ block_given? ? yield(row) : row
464
306
  end
465
- ary
307
+
308
+ options[:headers] == true ? results : results.drop(1)
466
309
  end
467
310
 
468
- def row_with(query,return_headers=false)
469
- query.map! {|x| Array(x.split('*'))}
311
+ def row_with(query, return_headers = false)
470
312
  line_no = 0
313
+ closest_mismatched_headers = []
471
314
  each do |row|
472
315
  line_no += 1
473
- # makes sure headers is the first part of wildcard search for priority
474
- # ex. if UPC and SKU exist for UPC*SKU search, UPC takes the cake
475
- headers = query.map do |q|
476
- q.map {|i| row.grep(/#{i}/i)[0]}.compact[0]
477
- end.compact
478
-
316
+ headers = query.map { |q| row.grep(q)[0] }.compact
479
317
  if headers.length == query.length
480
318
  @header_line = line_no
481
319
  return return_headers ? headers : line_no
482
- elsif line_no > 100
483
- raise "Couldn't find header row."
320
+ else
321
+ closest_mismatched_headers = headers if headers.length > closest_mismatched_headers.length
322
+ if line_no > 100
323
+ break
324
+ end
484
325
  end
485
326
  end
327
+ missing_headers = query.select { |q| closest_mismatched_headers.grep(q).empty? }
328
+ raise Roo::HeaderRowNotFoundError, missing_headers
486
329
  end
487
330
 
488
331
  protected
489
332
 
490
- def load_xml(path)
491
- File.open(path) do |file|
492
- Nokogiri::XML(file)
333
+ def file_type_check(filename, exts, name, warning_level, packed = nil)
334
+ if packed == :zip
335
+ # spreadsheet.ods.zip => spreadsheet.ods
336
+ # Decompression is not performed here, only the 'zip' extension
337
+ # is removed from the file.
338
+ filename = File.basename(filename, File.extname(filename))
493
339
  end
494
- end
495
340
 
496
- def file_type_check(filename, ext, name, warning_level, packed=nil)
497
- new_expression = {
498
- '.ods' => 'Roo::OpenOffice.new',
499
- '.xls' => 'Roo::Excel.new',
500
- '.xlsx' => 'Roo::Excelx.new',
501
- '.csv' => 'Roo::CSV.new',
502
- '.xml' => 'Roo::Excel2003XML.new',
503
- }
504
- if packed == :zip
505
- # lalala.ods.zip => lalala.ods
506
- # hier wird KEIN unzip gemacht, sondern nur der Name der Datei
507
- # getestet, falls es eine gepackte Datei ist.
508
- filename = File.basename(filename,File.extname(filename))
509
- end
510
- case ext
511
- when '.ods', '.xls', '.xlsx', '.csv', '.xml'
512
- correct_class = "use #{new_expression[ext]} to handle #{ext} spreadsheet files. This has #{File.extname(filename).downcase}"
341
+ if uri?(filename) && (qs_begin = filename.rindex("?"))
342
+ filename = filename[0..qs_begin - 1]
343
+ end
344
+ exts = Array(exts)
345
+
346
+ return if exts.include?(File.extname(filename).downcase)
347
+
348
+ case warning_level
349
+ when :error
350
+ warn file_type_warning_message(filename, exts)
351
+ fail TypeError, "#{filename} is not #{name} file"
352
+ when :warning
353
+ warn "are you sure, this is #{name} spreadsheet file?"
354
+ warn file_type_warning_message(filename, exts)
355
+ when :ignore
356
+ # ignore
513
357
  else
514
- raise "unknown file type: #{ext}"
515
- end
516
-
517
- if uri?(filename) && qs_begin = filename.rindex('?')
518
- filename = filename[0..qs_begin-1]
519
- end
520
- if File.extname(filename).downcase != ext
521
- case warning_level
522
- when :error
523
- warn correct_class
524
- raise TypeError, "#{filename} is not #{name} file"
525
- when :warning
526
- warn "are you sure, this is #{name} spreadsheet file?"
527
- warn correct_class
528
- when :ignore
529
- # ignore
530
- else
531
- raise "#{warning_level} illegal state of file_warning"
532
- end
358
+ fail "#{warning_level} illegal state of file_warning"
533
359
  end
534
360
  end
535
361
 
@@ -538,8 +364,8 @@ class Roo::Base
538
364
  # Diese Methode ist eine temp. Loesung, um zu erforschen, ob der
539
365
  # Zugriff mit numerischen Keys schneller ist.
540
366
  def key_to_num(str)
541
- r,c = str.split(',')
542
- [r.to_i,c.to_i]
367
+ r, c = str.split(",")
368
+ [r.to_i, c.to_i]
543
369
  end
544
370
 
545
371
  # see: key_to_num
@@ -547,8 +373,83 @@ class Roo::Base
547
373
  "#{arr[0]},#{arr[1]}"
548
374
  end
549
375
 
376
+ def is_stream?(filename_or_stream)
377
+ filename_or_stream.respond_to?(:seek)
378
+ end
379
+
550
380
  private
551
381
 
382
+ def clean_sheet_if_need(options)
383
+ return unless options[:clean]
384
+ options.delete(:clean)
385
+ @cleaned ||= {}
386
+ clean_sheet(default_sheet) unless @cleaned[default_sheet]
387
+ end
388
+
389
+ def search_or_set_header(options)
390
+ if options[:header_search]
391
+ @headers = nil
392
+ @header_line = row_with(options[:header_search])
393
+ elsif [:first_row, true].include?(options[:headers])
394
+ @headers = []
395
+ row(first_row).each_with_index { |x, i| @headers << [x, i + 1] }
396
+ else
397
+ set_headers(options)
398
+ end
399
+ end
400
+
401
+ def local_filename(filename, tmpdir, packed)
402
+ return if is_stream?(filename)
403
+ filename = download_uri(filename, tmpdir) if uri?(filename)
404
+ filename = unzip(filename, tmpdir) if packed == :zip
405
+
406
+ fail IOError, "file #{filename} does not exist" unless File.file?(filename)
407
+
408
+ filename
409
+ end
410
+
411
+ def file_type_warning_message(filename, exts)
412
+ *rest, last_ext = exts
413
+ ext_list = rest.any? ? "#{rest.join(', ')} or #{last_ext}" : last_ext
414
+ "use #{Roo::CLASS_FOR_EXTENSION.fetch(last_ext.sub('.', '').to_sym)}.new to handle #{ext_list} spreadsheet files. This has #{File.extname(filename).downcase}"
415
+ rescue KeyError
416
+ raise "unknown file types: #{ext_list}"
417
+ end
418
+
419
+ def find_by_row(row_index)
420
+ row_index += (header_line - 1) if @header_line
421
+
422
+ row(row_index).size.times.map do |cell_index|
423
+ cell(row_index, cell_index + 1)
424
+ end
425
+ end
426
+
427
+ def find_by_conditions(options)
428
+ rows = first_row.upto(last_row)
429
+ header_for = 1.upto(last_column).each_with_object({}) do |col, hash|
430
+ hash[col] = cell(@header_line, col)
431
+ end
432
+
433
+ # are all conditions met?
434
+ conditions = options[:conditions]
435
+ if conditions && !conditions.empty?
436
+ column_with = header_for.invert
437
+ rows = rows.select do |i|
438
+ conditions.all? { |key, val| cell(i, column_with[key]) == val }
439
+ end
440
+ end
441
+
442
+ if options[:array]
443
+ rows.map { |i| row(i) }
444
+ else
445
+ rows.map do |i|
446
+ 1.upto(row(i).size).each_with_object({}) do |j, hash|
447
+ hash[header_for.fetch(j)] = cell(i, j)
448
+ end
449
+ end
450
+ end
451
+ end
452
+
552
453
  def without_changing_default_sheet
553
454
  original_default_sheet = default_sheet
554
455
  yield
@@ -560,77 +461,94 @@ class Roo::Base
560
461
  initialize(@filename)
561
462
  end
562
463
 
563
- def make_tmpdir(tmp_root = nil)
564
- Dir.mktmpdir(TEMP_PREFIX, tmp_root || ENV['ROO_TMP']) do |tmpdir|
565
- yield tmpdir
464
+ def find_basename(filename)
465
+ if uri?(filename)
466
+ require "uri"
467
+ uri = URI.parse filename
468
+ File.basename(uri.path)
469
+ elsif !is_stream?(filename)
470
+ File.basename(filename)
471
+ end
472
+ end
473
+
474
+ def make_tmpdir(prefix = nil, root = nil, &block)
475
+ warn "[DEPRECATION] extend Roo::Tempdir and use its .make_tempdir instead"
476
+ prefix = "#{Roo::TEMP_PREFIX}#{prefix}"
477
+ root ||= ENV["ROO_TMP"]
478
+
479
+ if block_given?
480
+ # folder is deleted at end of block
481
+ ::Dir.mktmpdir(prefix, root, &block)
482
+ else
483
+ self.class.make_tempdir(self, prefix, root)
566
484
  end
567
485
  end
568
486
 
569
487
  def clean_sheet(sheet)
570
488
  read_cells(sheet)
571
- @cell[sheet].each_pair do |coord,value|
572
- if String === value
573
- @cell[sheet][coord] = sanitize_value(value)
574
- end
489
+ @cell[sheet].each_pair do |coord, value|
490
+ @cell[sheet][coord] = sanitize_value(value) if value.is_a?(::String)
575
491
  end
576
492
  @cleaned[sheet] = true
577
493
  end
578
494
 
579
495
  def sanitize_value(v)
580
- v.strip.unpack('U*').select {|b| b < 127}.pack('U*')
496
+ v.gsub(/[[:cntrl:]]|^[\p{Space}]+|[\p{Space}]+$/, "")
581
497
  end
582
498
 
583
- def set_headers(hash={})
499
+ def set_headers(hash = {})
584
500
  # try to find header row with all values or give an error
585
501
  # then create new hash by indexing strings and keeping integers for header array
586
- @headers = row_with(hash.values,true)
587
- @headers = Hash[hash.keys.zip(@headers.map {|x| header_index(x)})]
502
+ header_row = row_with(hash.values, true)
503
+ @headers = {}
504
+ hash.each_with_index do |(key, _), index|
505
+ @headers[key] = header_index(header_row[index])
506
+ end
588
507
  end
589
508
 
590
509
  def header_index(query)
591
510
  row(@header_line).index(query) + first_column
592
511
  end
593
512
 
594
- def set_value(row,col,value,sheet=nil)
595
- sheet ||= @default_sheet
596
- @cell[sheet][[row,col]] = value
513
+ def set_value(row, col, value, sheet = default_sheet)
514
+ @cell[sheet][[row, col]] = value
597
515
  end
598
516
 
599
- def set_type(row,col,type,sheet=nil)
600
- sheet ||= @default_sheet
601
- @cell_type[sheet][[row,col]] = type
517
+ def set_type(row, col, type, sheet = default_sheet)
518
+ @cell_type[sheet][[row, col]] = type
602
519
  end
603
520
 
604
521
  # converts cell coordinate to numeric values of row,col
605
- def normalize(row,col)
606
- if row.class == String
607
- if col.class == Fixnum
522
+ def normalize(row, col)
523
+ if row.is_a?(::String)
524
+ if col.is_a?(::Integer)
608
525
  # ('A',1):
609
526
  # ('B', 5) -> (5, 2)
610
527
  row, col = col, row
611
528
  else
612
- raise ArgumentError
529
+ fail ArgumentError
613
530
  end
614
531
  end
615
- if col.class == String
616
- col = Roo::Base.letter_to_number(col)
617
- end
618
- return row,col
532
+
533
+ col = ::Roo::Utils.letter_to_number(col) if col.is_a?(::String)
534
+
535
+ [row, col]
619
536
  end
620
537
 
621
538
  def uri?(filename)
622
- filename.start_with?("http://", "https://")
539
+ filename.start_with?("http://", "https://", "ftp://")
540
+ rescue
541
+ false
623
542
  end
624
543
 
625
544
  def download_uri(uri, tmpdir)
626
- require 'open-uri'
627
- tempfilename = File.join(tmpdir, File.basename(uri))
628
- response = ''
545
+ require "open-uri"
546
+ tempfilename = File.join(tmpdir, find_basename(uri))
629
547
  begin
630
- File.open(tempfilename,"wb") do |file|
631
- open(uri, "User-Agent" => "Ruby/#{RUBY_VERSION}") { |net|
548
+ File.open(tempfilename, "wb") do |file|
549
+ URI.open(uri, "User-Agent" => "Ruby/#{RUBY_VERSION}") do |net|
632
550
  file.write(net.read)
633
- }
551
+ end
634
552
  end
635
553
  rescue OpenURI::HTTPError
636
554
  raise "could not open #{uri}"
@@ -640,49 +558,16 @@ class Roo::Base
640
558
 
641
559
  def open_from_stream(stream, tmpdir)
642
560
  tempfilename = File.join(tmpdir, "spreadsheet")
643
- File.open(tempfilename,"wb") do |file|
561
+ File.open(tempfilename, "wb") do |file|
644
562
  file.write(stream[7..-1])
645
563
  end
646
564
  File.join(tmpdir, "spreadsheet")
647
565
  end
648
566
 
649
- LETTERS = %w{A B C D E F G H I J K L M N O P Q R S T U V W X Y Z}
650
-
651
- # convert a number to something like 'AB' (1 => 'A', 2 => 'B', ...)
652
- def self.number_to_letter(n)
653
- letters=""
654
- if n > 26
655
- while n % 26 == 0 && n != 0
656
- letters << 'Z'
657
- n = (n - 26) / 26
658
- end
659
- while n > 0
660
- num = n%26
661
- letters = LETTERS[num-1] + letters
662
- n = (n / 26)
663
- end
664
- else
665
- letters = LETTERS[n-1]
666
- end
667
- letters
668
- end
669
-
670
- # convert letters like 'AB' to a number ('A' => 1, 'B' => 2, ...)
671
- def self.letter_to_number(letters)
672
- result = 0
673
- while letters && letters.length > 0
674
- character = letters[0,1].upcase
675
- num = LETTERS.index(character)
676
- raise ArgumentError, "invalid column character '#{letters[0,1]}'" if num == nil
677
- num += 1
678
- result = result * 26 + num
679
- letters = letters[1..-1]
680
- end
681
- result
682
- end
683
-
684
567
  def unzip(filename, tmpdir)
685
- Roo::ZipFile.open(filename) do |zip|
568
+ require "zip/filesystem"
569
+
570
+ Zip::File.open(filename) do |zip|
686
571
  process_zipfile_packed(zip, tmpdir)
687
572
  end
688
573
  end
@@ -691,106 +576,34 @@ class Roo::Base
691
576
  def validate_sheet!(sheet)
692
577
  case sheet
693
578
  when nil
694
- raise ArgumentError, "Error: sheet 'nil' not valid"
695
- when Fixnum
696
- self.sheets.fetch(sheet-1) do
697
- raise RangeError, "sheet index #{sheet} not found"
579
+ fail ArgumentError, "Error: sheet 'nil' not valid"
580
+ when Integer
581
+ sheets.fetch(sheet) do
582
+ fail RangeError, "sheet index #{sheet} not found"
698
583
  end
699
584
  when String
700
- if !sheets.include? sheet
701
- raise RangeError, "sheet '#{sheet}' not found"
585
+ unless sheets.include?(sheet)
586
+ fail RangeError, "sheet '#{sheet}' not found"
702
587
  end
703
588
  else
704
- raise TypeError, "not a valid sheet type: #{sheet.inspect}"
589
+ fail TypeError, "not a valid sheet type: #{sheet.inspect}"
705
590
  end
706
591
  end
707
592
 
708
- def process_zipfile_packed(zip, tmpdir, path='')
593
+ def process_zipfile_packed(zip, tmpdir, path = "")
709
594
  if zip.file.file? path
710
595
  # extract and return filename
711
- File.open(File.join(tmpdir, path),"wb") do |file|
596
+ File.open(File.join(tmpdir, path), "wb") do |file|
712
597
  file.write(zip.read(path))
713
598
  end
714
599
  File.join(tmpdir, path)
715
600
  else
716
- ret=nil
717
- path += '/' unless path.empty?
601
+ ret = nil
602
+ path += "/" unless path.empty?
718
603
  zip.dir.foreach(path) do |filename|
719
604
  ret = process_zipfile_packed(zip, tmpdir, path + filename)
720
605
  end
721
606
  ret
722
607
  end
723
608
  end
724
-
725
- # Write all cells to the csv file. File can be a filename or nil. If the this
726
- # parameter is nil the output goes to STDOUT
727
- def write_csv_content(file=nil,sheet=nil)
728
- file ||= STDOUT
729
- if first_row(sheet) # sheet is not empty
730
- 1.upto(last_row(sheet)) do |row|
731
- 1.upto(last_column(sheet)) do |col|
732
- file.print(",") if col > 1
733
- file.print cell_to_csv(row,col,sheet)
734
- end
735
- file.print("\n")
736
- end # sheet not empty
737
- end
738
- end
739
-
740
- # The content of a cell in the csv output
741
- def cell_to_csv(row, col, sheet)
742
- if empty?(row,col,sheet)
743
- ''
744
- else
745
- onecell = cell(row,col,sheet)
746
-
747
- case celltype(row,col,sheet)
748
- when :string
749
- unless onecell.empty?
750
- %{"#{onecell.gsub(/"/,'""')}"}
751
- end
752
- when :boolean
753
- %{"#{onecell.gsub(/"/,'""').downcase}"}
754
- when :float, :percentage
755
- if onecell == onecell.to_i
756
- onecell.to_i.to_s
757
- else
758
- onecell.to_s
759
- end
760
- when :formula
761
- case onecell
762
- when String
763
- unless onecell.empty?
764
- %{"#{onecell.gsub(/"/,'""')}"}
765
- end
766
- when Float
767
- if onecell == onecell.to_i
768
- onecell.to_i.to_s
769
- else
770
- onecell.to_s
771
- end
772
- when DateTime
773
- onecell.to_s
774
- else
775
- raise "unhandled onecell-class #{onecell.class}"
776
- end
777
- when :date, :datetime
778
- onecell.to_s
779
- when :time
780
- Roo::Base.integer_to_timestring(onecell)
781
- else
782
- raise "unhandled celltype #{celltype(row,col,sheet)}"
783
- end || ""
784
- end
785
- end
786
-
787
- # converts an integer value to a time string like '02:05:06'
788
- def self.integer_to_timestring(content)
789
- h = (content/3600.0).floor
790
- content = content - h*3600
791
- m = (content/60.0).floor
792
- content = content - m*60
793
- s = content
794
- sprintf("%02d:%02d:%02d",h,m,s)
795
- end
796
609
  end