roo 1.13.2 → 2.0.1

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