roo 1.13.1 → 2.10.1
Sign up to get free protection for your applications and to get access to all the features.
- 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 +11 -0
- data/.rubocop.yml +186 -0
- data/.simplecov +4 -0
- data/CHANGELOG.md +702 -0
- data/Gemfile +18 -12
- data/Guardfile +23 -0
- data/LICENSE +5 -1
- data/README.md +328 -0
- data/Rakefile +23 -23
- data/examples/roo_soap_client.rb +28 -31
- data/examples/roo_soap_server.rb +4 -6
- data/examples/write_me.rb +9 -10
- data/lib/roo/base.rb +317 -504
- data/lib/roo/constants.rb +7 -0
- data/lib/roo/csv.rb +141 -113
- data/lib/roo/errors.rb +11 -0
- data/lib/roo/excelx/cell/base.rb +108 -0
- data/lib/roo/excelx/cell/boolean.rb +30 -0
- data/lib/roo/excelx/cell/date.rb +28 -0
- data/lib/roo/excelx/cell/datetime.rb +107 -0
- data/lib/roo/excelx/cell/empty.rb +20 -0
- data/lib/roo/excelx/cell/number.rb +99 -0
- data/lib/roo/excelx/cell/string.rb +19 -0
- data/lib/roo/excelx/cell/time.rb +44 -0
- data/lib/roo/excelx/cell.rb +110 -0
- data/lib/roo/excelx/comments.rb +55 -0
- data/lib/roo/excelx/coordinate.rb +19 -0
- data/lib/roo/excelx/extractor.rb +39 -0
- data/lib/roo/excelx/format.rb +71 -0
- data/lib/roo/excelx/images.rb +26 -0
- data/lib/roo/excelx/relationships.rb +33 -0
- data/lib/roo/excelx/shared.rb +39 -0
- data/lib/roo/excelx/shared_strings.rb +151 -0
- data/lib/roo/excelx/sheet.rb +151 -0
- data/lib/roo/excelx/sheet_doc.rb +257 -0
- data/lib/roo/excelx/styles.rb +64 -0
- data/lib/roo/excelx/workbook.rb +64 -0
- data/lib/roo/excelx.rb +407 -601
- data/lib/roo/font.rb +17 -0
- 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/libre_office.rb +4 -0
- data/lib/roo/link.rb +34 -0
- data/lib/roo/open_office.rb +631 -0
- data/lib/roo/spreadsheet.rb +28 -23
- data/lib/roo/tempdir.rb +24 -0
- data/lib/roo/utils.rb +128 -0
- data/lib/roo/version.rb +3 -0
- data/lib/roo.rb +26 -24
- data/roo.gemspec +29 -202
- data/spec/helpers.rb +5 -0
- data/spec/lib/roo/base_spec.rb +291 -3
- data/spec/lib/roo/csv_spec.rb +38 -11
- data/spec/lib/roo/excelx/cell/time_spec.rb +15 -0
- data/spec/lib/roo/excelx/format_spec.rb +7 -6
- 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 +682 -6
- data/spec/lib/roo/libreoffice_spec.rb +16 -6
- data/spec/lib/roo/openoffice_spec.rb +30 -8
- data/spec/lib/roo/spreadsheet_spec.rb +60 -12
- data/spec/lib/roo/strict_spec.rb +43 -0
- data/spec/lib/roo/utils_spec.rb +119 -0
- data/spec/lib/roo/weak_instance_cache_spec.rb +92 -0
- data/spec/lib/roo_spec.rb +0 -0
- data/spec/spec_helper.rb +7 -6
- data/test/all_ss.rb +12 -11
- data/test/excelx/cell/test_attr_reader_default.rb +72 -0
- data/test/excelx/cell/test_base.rb +68 -0
- data/test/excelx/cell/test_boolean.rb +36 -0
- data/test/excelx/cell/test_date.rb +38 -0
- data/test/excelx/cell/test_datetime.rb +45 -0
- data/test/excelx/cell/test_empty.rb +18 -0
- data/test/excelx/cell/test_number.rb +90 -0
- data/test/excelx/cell/test_string.rb +48 -0
- data/test/excelx/cell/test_time.rb +30 -0
- 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 +123 -59
- data/test/test_roo.rb +392 -2292
- metadata +153 -296
- data/CHANGELOG +0 -412
- data/Gemfile.lock +0 -78
- data/README.markdown +0 -126
- data/VERSION +0 -1
- data/lib/roo/excel.rb +0 -355
- data/lib/roo/excel2003xml.rb +0 -300
- data/lib/roo/google.rb +0 -292
- data/lib/roo/openoffice.rb +0 -496
- data/lib/roo/roo_rails_helper.rb +0 -83
- data/lib/roo/worksheet.rb +0 -18
- data/scripts/txt2html +0 -67
- data/spec/lib/roo/excel2003xml_spec.rb +0 -15
- data/spec/lib/roo/excel_spec.rb +0 -17
- data/spec/lib/roo/google_spec.rb +0 -64
- data/test/files/1900_base.xls +0 -0
- data/test/files/1900_base.xlsx +0 -0
- data/test/files/1904_base.xls +0 -0
- data/test/files/1904_base.xlsx +0 -0
- data/test/files/Bibelbund.csv +0 -3741
- data/test/files/Bibelbund.ods +0 -0
- data/test/files/Bibelbund.xls +0 -0
- data/test/files/Bibelbund.xlsx +0 -0
- data/test/files/Bibelbund.xml +0 -62518
- data/test/files/Bibelbund1.ods +0 -0
- data/test/files/Pfand_from_windows_phone.xlsx +0 -0
- data/test/files/bad_excel_date.xls +0 -0
- data/test/files/bbu.ods +0 -0
- data/test/files/bbu.xls +0 -0
- data/test/files/bbu.xlsx +0 -0
- data/test/files/bbu.xml +0 -152
- data/test/files/bode-v1.ods.zip +0 -0
- data/test/files/bode-v1.xls.zip +0 -0
- data/test/files/boolean.csv +0 -2
- data/test/files/boolean.ods +0 -0
- data/test/files/boolean.xls +0 -0
- data/test/files/boolean.xlsx +0 -0
- data/test/files/boolean.xml +0 -112
- data/test/files/borders.ods +0 -0
- data/test/files/borders.xls +0 -0
- data/test/files/borders.xlsx +0 -0
- data/test/files/borders.xml +0 -144
- data/test/files/bug-numbered-sheet-names.xlsx +0 -0
- data/test/files/bug-row-column-fixnum-float.xls +0 -0
- data/test/files/bug-row-column-fixnum-float.xml +0 -127
- data/test/files/comments.ods +0 -0
- data/test/files/comments.xls +0 -0
- data/test/files/comments.xlsx +0 -0
- data/test/files/csvtypes.csv +0 -1
- data/test/files/datetime.ods +0 -0
- data/test/files/datetime.xls +0 -0
- data/test/files/datetime.xlsx +0 -0
- data/test/files/datetime.xml +0 -142
- data/test/files/datetime_floatconv.xls +0 -0
- data/test/files/datetime_floatconv.xml +0 -148
- data/test/files/dreimalvier.ods +0 -0
- data/test/files/emptysheets.ods +0 -0
- data/test/files/emptysheets.xls +0 -0
- data/test/files/emptysheets.xlsx +0 -0
- data/test/files/emptysheets.xml +0 -105
- data/test/files/excel2003.xml +0 -21140
- data/test/files/false_encoding.xls +0 -0
- data/test/files/false_encoding.xml +0 -132
- data/test/files/file_item_error.xlsx +0 -0
- data/test/files/formula.ods +0 -0
- data/test/files/formula.xls +0 -0
- data/test/files/formula.xlsx +0 -0
- data/test/files/formula.xml +0 -134
- data/test/files/formula_parse_error.xls +0 -0
- data/test/files/formula_parse_error.xml +0 -1833
- data/test/files/formula_string_error.xlsx +0 -0
- data/test/files/html-escape.ods +0 -0
- data/test/files/link.xls +0 -0
- data/test/files/link.xlsx +0 -0
- data/test/files/matrix.ods +0 -0
- data/test/files/matrix.xls +0 -0
- data/test/files/named_cells.ods +0 -0
- data/test/files/named_cells.xls +0 -0
- data/test/files/named_cells.xlsx +0 -0
- data/test/files/no_spreadsheet_file.txt +0 -1
- data/test/files/numbers1.csv +0 -18
- data/test/files/numbers1.ods +0 -0
- data/test/files/numbers1.xls +0 -0
- data/test/files/numbers1.xlsx +0 -0
- data/test/files/numbers1.xml +0 -312
- data/test/files/only_one_sheet.ods +0 -0
- data/test/files/only_one_sheet.xls +0 -0
- data/test/files/only_one_sheet.xlsx +0 -0
- data/test/files/only_one_sheet.xml +0 -67
- data/test/files/paragraph.ods +0 -0
- data/test/files/paragraph.xls +0 -0
- data/test/files/paragraph.xlsx +0 -0
- data/test/files/paragraph.xml +0 -127
- data/test/files/prova.xls +0 -0
- data/test/files/ric.ods +0 -0
- data/test/files/simple_spreadsheet.ods +0 -0
- data/test/files/simple_spreadsheet.xls +0 -0
- data/test/files/simple_spreadsheet.xlsx +0 -0
- data/test/files/simple_spreadsheet.xml +0 -225
- data/test/files/simple_spreadsheet_from_italo.ods +0 -0
- data/test/files/simple_spreadsheet_from_italo.xls +0 -0
- data/test/files/simple_spreadsheet_from_italo.xml +0 -242
- data/test/files/so_datetime.csv +0 -7
- data/test/files/style.ods +0 -0
- data/test/files/style.xls +0 -0
- data/test/files/style.xlsx +0 -0
- data/test/files/style.xml +0 -154
- data/test/files/time-test.csv +0 -2
- data/test/files/time-test.ods +0 -0
- data/test/files/time-test.xls +0 -0
- data/test/files/time-test.xlsx +0 -0
- data/test/files/time-test.xml +0 -131
- data/test/files/type_excel.ods +0 -0
- data/test/files/type_excel.xlsx +0 -0
- data/test/files/type_excelx.ods +0 -0
- data/test/files/type_excelx.xls +0 -0
- data/test/files/type_openoffice.xls +0 -0
- data/test/files/type_openoffice.xlsx +0 -0
- data/test/files/whitespace.ods +0 -0
- data/test/files/whitespace.xls +0 -0
- data/test/files/whitespace.xlsx +0 -0
- data/test/files/whitespace.xml +0 -184
- data/test/rm_sub_test.rb +0 -12
- data/test/rm_test.rb +0 -7
- data/test/test_generic_spreadsheet.rb +0 -259
- data/website/index.html +0 -385
- data/website/index.txt +0 -423
- data/website/javascripts/rounded_corners_lite.inc.js +0 -285
- data/website/stylesheets/screen.css +0 -130
- data/website/template.rhtml +0 -48
data/lib/roo/base.rb
CHANGED
@@ -1,51 +1,40 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
require
|
4
|
-
require
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
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
|
-
|
20
|
+
MAX_ROW_COL = 999_999
|
21
|
+
MIN_ROW_COL = 0
|
20
22
|
|
21
|
-
attr_reader :
|
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
|
-
|
27
|
-
|
28
|
-
|
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.
|
36
|
-
|
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
|
-
|
63
|
-
|
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 (
|
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=
|
77
|
-
Roo::
|
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=
|
82
|
-
Roo::
|
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
|
-
#
|
86
|
-
def
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
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
|
-
|
187
|
-
|
188
|
-
sheet
|
189
|
-
|
190
|
-
|
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
|
-
|
203
|
-
|
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
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
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(
|
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(
|
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(
|
274
|
-
if
|
275
|
-
|
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,
|
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=
|
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 =
|
291
|
-
|
292
|
-
|
293
|
-
|
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
|
-
|
298
|
-
|
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 =
|
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=
|
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
|
208
|
+
sheets.each do |sheet|
|
327
209
|
self.default_sheet = sheet
|
328
210
|
result << "Sheet " + n.to_s + ":\n"
|
329
|
-
|
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::
|
335
|
-
result << " Last column: #{Roo::
|
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::
|
377
|
-
row =
|
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
|
-
|
392
|
-
name ? [
|
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
|
-
|
398
|
-
|
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
|
-
#
|
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
|
-
|
432
|
-
|
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
|
-
|
449
|
-
|
450
|
-
|
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(
|
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
|
-
|
460
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
483
|
-
|
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
|
491
|
-
|
492
|
-
|
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
|
-
|
497
|
-
|
498
|
-
|
499
|
-
|
500
|
-
|
501
|
-
|
502
|
-
|
503
|
-
|
504
|
-
|
505
|
-
|
506
|
-
|
507
|
-
|
508
|
-
|
509
|
-
|
510
|
-
|
511
|
-
|
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
|
-
|
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
|
564
|
-
|
565
|
-
|
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
|
-
|
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.
|
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
|
-
|
587
|
-
@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
|
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=
|
595
|
-
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=
|
600
|
-
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.
|
607
|
-
if col.
|
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
|
-
|
529
|
+
fail ArgumentError
|
613
530
|
end
|
614
531
|
end
|
615
|
-
|
616
|
-
|
617
|
-
|
618
|
-
|
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
|
627
|
-
tempfilename = File.join(tmpdir,
|
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}")
|
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
|
-
|
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
|
-
|
695
|
-
when
|
696
|
-
|
697
|
-
|
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
|
-
|
701
|
-
|
585
|
+
unless sheets.include?(sheet)
|
586
|
+
fail RangeError, "sheet '#{sheet}' not found"
|
702
587
|
end
|
703
588
|
else
|
704
|
-
|
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 +=
|
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
|