roo 1.13.2 → 2.0.0beta1
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 +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
|