roo 2.3.0 → 2.10.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +5 -5
- data/.codeclimate.yml +17 -0
- data/.github/issue_template.md +16 -0
- data/.github/pull_request_template.md +14 -0
- data/.github/workflows/pull-request.yml +15 -0
- data/.github/workflows/ruby.yml +34 -0
- data/.gitignore +4 -0
- data/.rubocop.yml +186 -0
- data/CHANGELOG.md +148 -0
- data/Gemfile +4 -4
- data/LICENSE +2 -0
- data/README.md +84 -27
- data/Rakefile +1 -1
- data/lib/roo/base.rb +111 -237
- data/lib/roo/constants.rb +5 -3
- data/lib/roo/csv.rb +106 -85
- data/lib/roo/errors.rb +2 -0
- data/lib/roo/excelx/cell/base.rb +26 -12
- data/lib/roo/excelx/cell/boolean.rb +9 -6
- data/lib/roo/excelx/cell/date.rb +7 -7
- data/lib/roo/excelx/cell/datetime.rb +50 -44
- data/lib/roo/excelx/cell/empty.rb +3 -2
- data/lib/roo/excelx/cell/number.rb +60 -47
- data/lib/roo/excelx/cell/string.rb +3 -3
- data/lib/roo/excelx/cell/time.rb +17 -16
- data/lib/roo/excelx/cell.rb +11 -7
- data/lib/roo/excelx/comments.rb +3 -3
- data/lib/roo/excelx/coordinate.rb +11 -4
- data/lib/roo/excelx/extractor.rb +20 -3
- data/lib/roo/excelx/format.rb +38 -31
- data/lib/roo/excelx/images.rb +26 -0
- data/lib/roo/excelx/relationships.rb +12 -4
- data/lib/roo/excelx/shared.rb +10 -3
- data/lib/roo/excelx/shared_strings.rb +113 -9
- data/lib/roo/excelx/sheet.rb +49 -10
- data/lib/roo/excelx/sheet_doc.rb +101 -48
- data/lib/roo/excelx/styles.rb +4 -4
- data/lib/roo/excelx/workbook.rb +8 -3
- data/lib/roo/excelx.rb +85 -42
- data/lib/roo/formatters/base.rb +15 -0
- data/lib/roo/formatters/csv.rb +84 -0
- data/lib/roo/formatters/matrix.rb +23 -0
- data/lib/roo/formatters/xml.rb +31 -0
- data/lib/roo/formatters/yaml.rb +40 -0
- data/lib/roo/helpers/default_attr_reader.rb +20 -0
- data/lib/roo/helpers/weak_instance_cache.rb +41 -0
- data/lib/roo/open_office.rb +41 -27
- data/lib/roo/spreadsheet.rb +8 -2
- data/lib/roo/tempdir.rb +24 -0
- data/lib/roo/utils.rb +76 -26
- data/lib/roo/version.rb +1 -1
- data/lib/roo.rb +5 -0
- data/roo.gemspec +22 -12
- data/spec/lib/roo/base_spec.rb +65 -3
- data/spec/lib/roo/csv_spec.rb +19 -0
- data/spec/lib/roo/excelx/cell/time_spec.rb +15 -0
- data/spec/lib/roo/excelx/relationships_spec.rb +43 -0
- data/spec/lib/roo/excelx/sheet_doc_spec.rb +11 -0
- data/spec/lib/roo/excelx_spec.rb +237 -5
- data/spec/lib/roo/openoffice_spec.rb +2 -2
- data/spec/lib/roo/spreadsheet_spec.rb +1 -1
- data/spec/lib/roo/strict_spec.rb +43 -0
- data/spec/lib/roo/utils_spec.rb +22 -9
- data/spec/lib/roo/weak_instance_cache_spec.rb +92 -0
- data/spec/lib/roo_spec.rb +0 -0
- data/spec/spec_helper.rb +2 -7
- data/test/excelx/cell/test_attr_reader_default.rb +72 -0
- data/test/excelx/cell/test_base.rb +6 -2
- data/test/excelx/cell/test_boolean.rb +1 -3
- data/test/excelx/cell/test_date.rb +1 -6
- data/test/excelx/cell/test_datetime.rb +7 -10
- data/test/excelx/cell/test_empty.rb +12 -2
- data/test/excelx/cell/test_number.rb +28 -4
- data/test/excelx/cell/test_string.rb +21 -3
- data/test/excelx/cell/test_time.rb +7 -10
- data/test/excelx/test_coordinate.rb +51 -0
- data/test/formatters/test_csv.rb +136 -0
- data/test/formatters/test_matrix.rb +76 -0
- data/test/formatters/test_xml.rb +78 -0
- data/test/formatters/test_yaml.rb +20 -0
- data/test/helpers/test_accessing_files.rb +81 -0
- data/test/helpers/test_comments.rb +43 -0
- data/test/helpers/test_formulas.rb +9 -0
- data/test/helpers/test_labels.rb +103 -0
- data/test/helpers/test_sheets.rb +55 -0
- data/test/helpers/test_styles.rb +62 -0
- data/test/roo/test_base.rb +182 -0
- data/test/roo/test_csv.rb +88 -0
- data/test/roo/test_excelx.rb +360 -0
- data/test/roo/test_libre_office.rb +9 -0
- data/test/roo/test_open_office.rb +289 -0
- data/test/test_helper.rb +129 -14
- data/test/test_roo.rb +60 -1765
- metadata +91 -21
- data/.travis.yml +0 -14
data/lib/roo/base.rb
CHANGED
@@ -1,23 +1,39 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
require
|
4
|
-
require
|
5
|
-
require
|
6
|
-
require
|
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"
|
7
10
|
|
8
11
|
# Base class for all other types of spreadsheets
|
9
12
|
class Roo::Base
|
10
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
|
11
19
|
|
12
|
-
|
13
|
-
|
14
|
-
MIN_ROW_COL = 0.freeze
|
20
|
+
MAX_ROW_COL = 999_999
|
21
|
+
MIN_ROW_COL = 0
|
15
22
|
|
16
23
|
attr_reader :headers
|
17
24
|
|
18
25
|
# sets the line with attribute names (default: 1)
|
19
26
|
attr_accessor :header_line
|
20
27
|
|
28
|
+
def self.TEMP_PREFIX
|
29
|
+
warn "[DEPRECATION] please access TEMP_PREFIX via Roo::TEMP_PREFIX"
|
30
|
+
Roo::TEMP_PREFIX
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.finalize(object_id)
|
34
|
+
proc { finalize_tempdirs(object_id) }
|
35
|
+
end
|
36
|
+
|
21
37
|
def initialize(filename, options = {}, _file_warning = :error, _tmpdir = nil)
|
22
38
|
@filename = filename
|
23
39
|
@options = options
|
@@ -32,14 +48,17 @@ class Roo::Base
|
|
32
48
|
@last_column = {}
|
33
49
|
|
34
50
|
@header_line = 1
|
35
|
-
rescue => e # clean up any temp files, but only if an error was raised
|
36
|
-
close
|
37
|
-
raise e
|
38
51
|
end
|
39
52
|
|
40
53
|
def close
|
41
|
-
|
42
|
-
|
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
|
+
|
43
62
|
nil
|
44
63
|
end
|
45
64
|
|
@@ -48,10 +67,10 @@ class Roo::Base
|
|
48
67
|
end
|
49
68
|
|
50
69
|
# sets the working sheet in the document
|
51
|
-
# 'sheet' can be a number (
|
70
|
+
# 'sheet' can be a number (0 = first sheet) or the name of a sheet.
|
52
71
|
def default_sheet=(sheet)
|
53
72
|
validate_sheet!(sheet)
|
54
|
-
@default_sheet = sheet
|
73
|
+
@default_sheet = sheet.is_a?(String) ? sheet : sheets[sheet]
|
55
74
|
@first_row[sheet] = @last_row[sheet] = @first_column[sheet] = @last_column[sheet] = nil
|
56
75
|
@cells_read[sheet] = false
|
57
76
|
end
|
@@ -84,7 +103,7 @@ class Roo::Base
|
|
84
103
|
def collect_last_row_col_for_sheet(sheet)
|
85
104
|
first_row = first_column = MAX_ROW_COL
|
86
105
|
last_row = last_column = MIN_ROW_COL
|
87
|
-
@cell[sheet].each_pair do|key, value|
|
106
|
+
@cell[sheet].each_pair do |key, value|
|
88
107
|
next unless value
|
89
108
|
first_row = [first_row, key.first.to_i].min
|
90
109
|
last_row = [last_row, key.first.to_i].max
|
@@ -94,80 +113,12 @@ class Roo::Base
|
|
94
113
|
{ first_row: first_row, first_column: first_column, last_row: last_row, last_column: last_column }
|
95
114
|
end
|
96
115
|
|
97
|
-
%
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
end # end
|
103
|
-
EOS
|
104
|
-
end
|
105
|
-
|
106
|
-
# returns a rectangular area (default: all cells) as yaml-output
|
107
|
-
# you can add additional attributes with the prefix parameter like:
|
108
|
-
# oo.to_yaml({"file"=>"flightdata_2007-06-26", "sheet" => "1"})
|
109
|
-
def to_yaml(prefix = {}, from_row = nil, from_column = nil, to_row = nil, to_column = nil, sheet = default_sheet)
|
110
|
-
return '' unless first_row # empty result if there is no first_row in a sheet
|
111
|
-
|
112
|
-
from_row ||= first_row(sheet)
|
113
|
-
to_row ||= last_row(sheet)
|
114
|
-
from_column ||= first_column(sheet)
|
115
|
-
to_column ||= last_column(sheet)
|
116
|
-
|
117
|
-
result = "--- \n"
|
118
|
-
from_row.upto(to_row) do |row|
|
119
|
-
from_column.upto(to_column) do |col|
|
120
|
-
next if empty?(row, col, sheet)
|
121
|
-
|
122
|
-
result << "cell_#{row}_#{col}: \n"
|
123
|
-
prefix.each do|k, v|
|
124
|
-
result << " #{k}: #{v} \n"
|
125
|
-
end
|
126
|
-
result << " row: #{row} \n"
|
127
|
-
result << " col: #{col} \n"
|
128
|
-
result << " celltype: #{celltype(row, col, sheet)} \n"
|
129
|
-
value = cell(row, col, sheet)
|
130
|
-
if celltype(row, col, sheet) == :time
|
131
|
-
value = integer_to_timestring(value)
|
132
|
-
end
|
133
|
-
result << " value: #{value} \n"
|
134
|
-
end
|
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]
|
135
121
|
end
|
136
|
-
|
137
|
-
result
|
138
|
-
end
|
139
|
-
|
140
|
-
# write the current spreadsheet to stdout or into a file
|
141
|
-
def to_csv(filename = nil, separator = ',', sheet = default_sheet)
|
142
|
-
if filename
|
143
|
-
File.open(filename, 'w') do |file|
|
144
|
-
write_csv_content(file, sheet, separator)
|
145
|
-
end
|
146
|
-
true
|
147
|
-
else
|
148
|
-
sio = ::StringIO.new
|
149
|
-
write_csv_content(sio, sheet, separator)
|
150
|
-
sio.rewind
|
151
|
-
sio.read
|
152
|
-
end
|
153
|
-
end
|
154
|
-
|
155
|
-
# returns a matrix object from the whole sheet or a rectangular area of a sheet
|
156
|
-
def to_matrix(from_row = nil, from_column = nil, to_row = nil, to_column = nil, sheet = default_sheet)
|
157
|
-
require 'matrix'
|
158
|
-
|
159
|
-
return Matrix.empty unless first_row
|
160
|
-
|
161
|
-
from_row ||= first_row(sheet)
|
162
|
-
to_row ||= last_row(sheet)
|
163
|
-
from_column ||= first_column(sheet)
|
164
|
-
to_column ||= last_column(sheet)
|
165
|
-
|
166
|
-
Matrix.rows(from_row.upto(to_row).map do |row|
|
167
|
-
from_column.upto(to_column).map do |col|
|
168
|
-
cell(row, col, sheet)
|
169
|
-
end
|
170
|
-
end)
|
171
122
|
end
|
172
123
|
|
173
124
|
def inspect
|
@@ -181,7 +132,7 @@ class Roo::Base
|
|
181
132
|
options = (args.last.is_a?(Hash) ? args.pop : {})
|
182
133
|
|
183
134
|
case args[0]
|
184
|
-
when
|
135
|
+
when Integer
|
185
136
|
find_by_row(args[0])
|
186
137
|
when :all
|
187
138
|
find_by_conditions(options)
|
@@ -223,7 +174,7 @@ class Roo::Base
|
|
223
174
|
|
224
175
|
def cell_type_by_value(value)
|
225
176
|
case value
|
226
|
-
when
|
177
|
+
when Integer then :float
|
227
178
|
when String, Float then :string
|
228
179
|
else
|
229
180
|
fail ArgumentError, "Type for #{value} not set"
|
@@ -254,16 +205,16 @@ class Roo::Base
|
|
254
205
|
"Number of sheets: #{sheets.size}\n"\
|
255
206
|
"Sheets: #{sheets.join(', ')}\n"
|
256
207
|
n = 1
|
257
|
-
sheets.each do|sheet|
|
208
|
+
sheets.each do |sheet|
|
258
209
|
self.default_sheet = sheet
|
259
|
-
result <<
|
210
|
+
result << "Sheet " + n.to_s + ":\n"
|
260
211
|
if first_row
|
261
212
|
result << " First row: #{first_row}\n"
|
262
213
|
result << " Last row: #{last_row}\n"
|
263
214
|
result << " First column: #{::Roo::Utils.number_to_letter(first_column)}\n"
|
264
215
|
result << " Last column: #{::Roo::Utils.number_to_letter(last_column)}"
|
265
216
|
else
|
266
|
-
result <<
|
217
|
+
result << " - empty -"
|
267
218
|
end
|
268
219
|
result << "\n" if sheet != sheets.last
|
269
220
|
n += 1
|
@@ -272,32 +223,6 @@ class Roo::Base
|
|
272
223
|
end
|
273
224
|
end
|
274
225
|
|
275
|
-
# returns an XML representation of all sheets of a spreadsheet file
|
276
|
-
def to_xml
|
277
|
-
Nokogiri::XML::Builder.new do |xml|
|
278
|
-
xml.spreadsheet do
|
279
|
-
sheets.each do |sheet|
|
280
|
-
self.default_sheet = sheet
|
281
|
-
xml.sheet(name: sheet) do |x|
|
282
|
-
if first_row && last_row && first_column && last_column
|
283
|
-
# sonst gibt es Fehler bei leeren Blaettern
|
284
|
-
first_row.upto(last_row) do |row|
|
285
|
-
first_column.upto(last_column) do |col|
|
286
|
-
next if empty?(row, col)
|
287
|
-
|
288
|
-
x.cell(cell(row, col),
|
289
|
-
row: row,
|
290
|
-
column: col,
|
291
|
-
type: celltype(row, col))
|
292
|
-
end
|
293
|
-
end
|
294
|
-
end
|
295
|
-
end
|
296
|
-
end
|
297
|
-
end
|
298
|
-
end.to_xml
|
299
|
-
end
|
300
|
-
|
301
226
|
# when a method like spreadsheet.a42 is called
|
302
227
|
# convert it to a call of spreadsheet.cell('a',42)
|
303
228
|
def method_missing(m, *args)
|
@@ -325,6 +250,8 @@ class Roo::Base
|
|
325
250
|
|
326
251
|
# iterate through all worksheets of a document
|
327
252
|
def each_with_pagename
|
253
|
+
return to_enum(:each_with_pagename) { sheets.size } unless block_given?
|
254
|
+
|
328
255
|
sheets.each do |s|
|
329
256
|
yield sheet(s, true)
|
330
257
|
end
|
@@ -363,39 +290,42 @@ class Roo::Base
|
|
363
290
|
clean_sheet_if_need(options)
|
364
291
|
search_or_set_header(options)
|
365
292
|
headers = @headers ||
|
366
|
-
|
367
|
-
[cell(@header_line, col)
|
368
|
-
end
|
293
|
+
(first_column..last_column).each_with_object({}) do |col, hash|
|
294
|
+
hash[cell(@header_line, col)] = col
|
295
|
+
end
|
369
296
|
|
370
297
|
@header_line.upto(last_row) do |line|
|
371
|
-
yield(
|
298
|
+
yield(headers.each_with_object({}) { |(k, v), hash| hash[k] = cell(line, v) })
|
372
299
|
end
|
373
300
|
end
|
374
301
|
end
|
375
302
|
|
376
303
|
def parse(options = {})
|
377
|
-
|
378
|
-
|
379
|
-
yield(row) if block_given?
|
380
|
-
ary << row
|
304
|
+
results = each(options).map do |row|
|
305
|
+
block_given? ? yield(row) : row
|
381
306
|
end
|
382
|
-
|
307
|
+
|
308
|
+
options[:headers] == true ? results : results.drop(1)
|
383
309
|
end
|
384
310
|
|
385
311
|
def row_with(query, return_headers = false)
|
386
312
|
line_no = 0
|
313
|
+
closest_mismatched_headers = []
|
387
314
|
each do |row|
|
388
315
|
line_no += 1
|
389
316
|
headers = query.map { |q| row.grep(q)[0] }.compact
|
390
|
-
|
391
317
|
if headers.length == query.length
|
392
318
|
@header_line = line_no
|
393
319
|
return return_headers ? headers : line_no
|
394
|
-
|
395
|
-
|
320
|
+
else
|
321
|
+
closest_mismatched_headers = headers if headers.length > closest_mismatched_headers.length
|
322
|
+
if line_no > 100
|
323
|
+
break
|
324
|
+
end
|
396
325
|
end
|
397
326
|
end
|
398
|
-
|
327
|
+
missing_headers = query.select { |q| closest_mismatched_headers.grep(q).empty? }
|
328
|
+
raise Roo::HeaderRowNotFoundError, missing_headers
|
399
329
|
end
|
400
330
|
|
401
331
|
protected
|
@@ -408,7 +338,7 @@ class Roo::Base
|
|
408
338
|
filename = File.basename(filename, File.extname(filename))
|
409
339
|
end
|
410
340
|
|
411
|
-
if uri?(filename) && (qs_begin = filename.rindex(
|
341
|
+
if uri?(filename) && (qs_begin = filename.rindex("?"))
|
412
342
|
filename = filename[0..qs_begin - 1]
|
413
343
|
end
|
414
344
|
exts = Array(exts)
|
@@ -434,7 +364,7 @@ class Roo::Base
|
|
434
364
|
# Diese Methode ist eine temp. Loesung, um zu erforschen, ob der
|
435
365
|
# Zugriff mit numerischen Keys schneller ist.
|
436
366
|
def key_to_num(str)
|
437
|
-
r, c = str.split(
|
367
|
+
r, c = str.split(",")
|
438
368
|
[r.to_i, c.to_i]
|
439
369
|
end
|
440
370
|
|
@@ -449,10 +379,6 @@ class Roo::Base
|
|
449
379
|
|
450
380
|
private
|
451
381
|
|
452
|
-
def track_tmpdir!(tmpdir)
|
453
|
-
(@tmpdirs ||= []) << tmpdir
|
454
|
-
end
|
455
|
-
|
456
382
|
def clean_sheet_if_need(options)
|
457
383
|
return unless options[:clean]
|
458
384
|
options.delete(:clean)
|
@@ -500,9 +426,9 @@ class Roo::Base
|
|
500
426
|
|
501
427
|
def find_by_conditions(options)
|
502
428
|
rows = first_row.upto(last_row)
|
503
|
-
header_for =
|
504
|
-
[col
|
505
|
-
end
|
429
|
+
header_for = 1.upto(last_column).each_with_object({}) do |col, hash|
|
430
|
+
hash[col] = cell(@header_line, col)
|
431
|
+
end
|
506
432
|
|
507
433
|
# are all conditions met?
|
508
434
|
conditions = options[:conditions]
|
@@ -517,9 +443,9 @@ class Roo::Base
|
|
517
443
|
rows.map { |i| row(i) }
|
518
444
|
else
|
519
445
|
rows.map do |i|
|
520
|
-
|
521
|
-
[header_for.fetch(j)
|
522
|
-
end
|
446
|
+
1.upto(row(i).size).each_with_object({}) do |j, hash|
|
447
|
+
hash[header_for.fetch(j)] = cell(i, j)
|
448
|
+
end
|
523
449
|
end
|
524
450
|
end
|
525
451
|
end
|
@@ -535,11 +461,26 @@ class Roo::Base
|
|
535
461
|
initialize(@filename)
|
536
462
|
end
|
537
463
|
|
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
|
+
|
538
474
|
def make_tmpdir(prefix = nil, root = nil, &block)
|
539
|
-
|
475
|
+
warn "[DEPRECATION] extend Roo::Tempdir and use its .make_tempdir instead"
|
476
|
+
prefix = "#{Roo::TEMP_PREFIX}#{prefix}"
|
477
|
+
root ||= ENV["ROO_TMP"]
|
540
478
|
|
541
|
-
|
542
|
-
|
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)
|
543
484
|
end
|
544
485
|
end
|
545
486
|
|
@@ -552,14 +493,17 @@ class Roo::Base
|
|
552
493
|
end
|
553
494
|
|
554
495
|
def sanitize_value(v)
|
555
|
-
v.gsub(/[[:cntrl:]]|^[\p{Space}]+|[\p{Space}]+$/,
|
496
|
+
v.gsub(/[[:cntrl:]]|^[\p{Space}]+|[\p{Space}]+$/, "")
|
556
497
|
end
|
557
498
|
|
558
499
|
def set_headers(hash = {})
|
559
500
|
# try to find header row with all values or give an error
|
560
501
|
# then create new hash by indexing strings and keeping integers for header array
|
561
|
-
|
562
|
-
@headers =
|
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
|
563
507
|
end
|
564
508
|
|
565
509
|
def header_index(query)
|
@@ -577,7 +521,7 @@ class Roo::Base
|
|
577
521
|
# converts cell coordinate to numeric values of row,col
|
578
522
|
def normalize(row, col)
|
579
523
|
if row.is_a?(::String)
|
580
|
-
if col.is_a?(::
|
524
|
+
if col.is_a?(::Integer)
|
581
525
|
# ('A',1):
|
582
526
|
# ('B', 5) -> (5, 2)
|
583
527
|
row, col = col, row
|
@@ -592,17 +536,17 @@ class Roo::Base
|
|
592
536
|
end
|
593
537
|
|
594
538
|
def uri?(filename)
|
595
|
-
filename.start_with?(
|
539
|
+
filename.start_with?("http://", "https://", "ftp://")
|
596
540
|
rescue
|
597
541
|
false
|
598
542
|
end
|
599
543
|
|
600
544
|
def download_uri(uri, tmpdir)
|
601
|
-
require
|
602
|
-
tempfilename = File.join(tmpdir,
|
545
|
+
require "open-uri"
|
546
|
+
tempfilename = File.join(tmpdir, find_basename(uri))
|
603
547
|
begin
|
604
|
-
File.open(tempfilename,
|
605
|
-
open(uri,
|
548
|
+
File.open(tempfilename, "wb") do |file|
|
549
|
+
URI.open(uri, "User-Agent" => "Ruby/#{RUBY_VERSION}") do |net|
|
606
550
|
file.write(net.read)
|
607
551
|
end
|
608
552
|
end
|
@@ -613,15 +557,15 @@ class Roo::Base
|
|
613
557
|
end
|
614
558
|
|
615
559
|
def open_from_stream(stream, tmpdir)
|
616
|
-
tempfilename = File.join(tmpdir,
|
617
|
-
File.open(tempfilename,
|
560
|
+
tempfilename = File.join(tmpdir, "spreadsheet")
|
561
|
+
File.open(tempfilename, "wb") do |file|
|
618
562
|
file.write(stream[7..-1])
|
619
563
|
end
|
620
|
-
File.join(tmpdir,
|
564
|
+
File.join(tmpdir, "spreadsheet")
|
621
565
|
end
|
622
566
|
|
623
567
|
def unzip(filename, tmpdir)
|
624
|
-
require
|
568
|
+
require "zip/filesystem"
|
625
569
|
|
626
570
|
Zip::File.open(filename) do |zip|
|
627
571
|
process_zipfile_packed(zip, tmpdir)
|
@@ -633,8 +577,8 @@ class Roo::Base
|
|
633
577
|
case sheet
|
634
578
|
when nil
|
635
579
|
fail ArgumentError, "Error: sheet 'nil' not valid"
|
636
|
-
when
|
637
|
-
sheets.fetch(sheet
|
580
|
+
when Integer
|
581
|
+
sheets.fetch(sheet) do
|
638
582
|
fail RangeError, "sheet index #{sheet} not found"
|
639
583
|
end
|
640
584
|
when String
|
@@ -646,90 +590,20 @@ class Roo::Base
|
|
646
590
|
end
|
647
591
|
end
|
648
592
|
|
649
|
-
def process_zipfile_packed(zip, tmpdir, path =
|
593
|
+
def process_zipfile_packed(zip, tmpdir, path = "")
|
650
594
|
if zip.file.file? path
|
651
595
|
# extract and return filename
|
652
|
-
File.open(File.join(tmpdir, path),
|
596
|
+
File.open(File.join(tmpdir, path), "wb") do |file|
|
653
597
|
file.write(zip.read(path))
|
654
598
|
end
|
655
599
|
File.join(tmpdir, path)
|
656
600
|
else
|
657
601
|
ret = nil
|
658
|
-
path +=
|
602
|
+
path += "/" unless path.empty?
|
659
603
|
zip.dir.foreach(path) do |filename|
|
660
604
|
ret = process_zipfile_packed(zip, tmpdir, path + filename)
|
661
605
|
end
|
662
606
|
ret
|
663
607
|
end
|
664
608
|
end
|
665
|
-
|
666
|
-
# Write all cells to the csv file. File can be a filename or nil. If the this
|
667
|
-
# parameter is nil the output goes to STDOUT
|
668
|
-
def write_csv_content(file = nil, sheet = nil, separator = ',')
|
669
|
-
file ||= STDOUT
|
670
|
-
return unless first_row(sheet) # The sheet is empty
|
671
|
-
|
672
|
-
1.upto(last_row(sheet)) do |row|
|
673
|
-
1.upto(last_column(sheet)) do |col|
|
674
|
-
file.print(separator) if col > 1
|
675
|
-
file.print cell_to_csv(row, col, sheet)
|
676
|
-
end
|
677
|
-
file.print("\n")
|
678
|
-
end
|
679
|
-
end
|
680
|
-
|
681
|
-
# The content of a cell in the csv output
|
682
|
-
def cell_to_csv(row, col, sheet)
|
683
|
-
return '' if empty?(row, col, sheet)
|
684
|
-
|
685
|
-
onecell = cell(row, col, sheet)
|
686
|
-
|
687
|
-
case celltype(row, col, sheet)
|
688
|
-
when :string
|
689
|
-
%("#{onecell.gsub('"', '""')}") unless onecell.empty?
|
690
|
-
when :boolean
|
691
|
-
# TODO: this only works for excelx
|
692
|
-
onecell = self.sheet_for(sheet).cells[[row, col]].formatted_value
|
693
|
-
%("#{onecell.gsub('"', '""').downcase}")
|
694
|
-
when :float, :percentage
|
695
|
-
if onecell == onecell.to_i
|
696
|
-
onecell.to_i.to_s
|
697
|
-
else
|
698
|
-
onecell.to_s
|
699
|
-
end
|
700
|
-
when :formula
|
701
|
-
case onecell
|
702
|
-
when String
|
703
|
-
%("#{onecell.gsub('"', '""')}") unless onecell.empty?
|
704
|
-
when Float
|
705
|
-
if onecell == onecell.to_i
|
706
|
-
onecell.to_i.to_s
|
707
|
-
else
|
708
|
-
onecell.to_s
|
709
|
-
end
|
710
|
-
when DateTime
|
711
|
-
onecell.to_s
|
712
|
-
else
|
713
|
-
fail "unhandled onecell-class #{onecell.class}"
|
714
|
-
end
|
715
|
-
when :date, :datetime
|
716
|
-
onecell.to_s
|
717
|
-
when :time
|
718
|
-
integer_to_timestring(onecell)
|
719
|
-
when :link
|
720
|
-
%("#{onecell.url.gsub('"', '""')}")
|
721
|
-
else
|
722
|
-
fail "unhandled celltype #{celltype(row, col, sheet)}"
|
723
|
-
end || ''
|
724
|
-
end
|
725
|
-
|
726
|
-
# converts an integer value to a time string like '02:05:06'
|
727
|
-
def integer_to_timestring(content)
|
728
|
-
h = (content / 3600.0).floor
|
729
|
-
content -= h * 3600
|
730
|
-
m = (content / 60.0).floor
|
731
|
-
content -= m * 60
|
732
|
-
s = content
|
733
|
-
sprintf('%02d:%02d:%02d', h, m, s)
|
734
|
-
end
|
735
609
|
end
|
data/lib/roo/constants.rb
CHANGED
@@ -1,5 +1,7 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
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."
|
3
|
-
ROO_EXCELML_NOTICE = "Excel SpreadsheetML support has been extracted to roo-xls. Install roo-xls to use Roo::Excel2003XML."
|
4
|
-
ROO_GOOGLE_NOTICE = "Google support has been extracted to roo-google. Install roo-google to use Roo::Google."
|
4
|
+
ROO_EXCEL_NOTICE = "Excel support has been extracted to roo-xls due to its dependency on the GPL'd spreadsheet gem. Install roo-xls to use Roo::Excel."
|
5
|
+
ROO_EXCELML_NOTICE = "Excel SpreadsheetML support has been extracted to roo-xls. Install roo-xls to use Roo::Excel2003XML."
|
6
|
+
ROO_GOOGLE_NOTICE = "Google support has been extracted to roo-google. Install roo-google to use Roo::Google."
|
5
7
|
end
|