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