roo 1.13.2 → 2.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +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
|