roo 1.13.2 → 2.0.1

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