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