roo 1.13.2 → 2.7.0
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/.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
|