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