roo 1.13.2 → 2.0.0beta1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (171) 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 +21 -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.rb +18 -24
  15. data/lib/roo/base.rb +303 -388
  16. data/lib/roo/csv.rb +120 -113
  17. data/lib/roo/excelx.rb +452 -484
  18. data/lib/roo/excelx/comments.rb +24 -0
  19. data/lib/roo/excelx/extractor.rb +20 -0
  20. data/lib/roo/excelx/relationships.rb +26 -0
  21. data/lib/roo/excelx/shared_strings.rb +40 -0
  22. data/lib/roo/excelx/sheet_doc.rb +202 -0
  23. data/lib/roo/excelx/styles.rb +62 -0
  24. data/lib/roo/excelx/workbook.rb +59 -0
  25. data/lib/roo/font.rb +17 -0
  26. data/lib/roo/libre_office.rb +5 -0
  27. data/lib/roo/link.rb +15 -0
  28. data/lib/roo/{openoffice.rb → open_office.rb} +678 -496
  29. data/lib/roo/spreadsheet.rb +20 -23
  30. data/lib/roo/utils.rb +78 -0
  31. data/lib/roo/version.rb +3 -0
  32. data/roo.gemspec +20 -204
  33. data/spec/lib/roo/base_spec.rb +1 -4
  34. data/spec/lib/roo/csv_spec.rb +21 -13
  35. data/spec/lib/roo/excelx/format_spec.rb +7 -6
  36. data/spec/lib/roo/excelx_spec.rb +388 -11
  37. data/spec/lib/roo/libreoffice_spec.rb +16 -6
  38. data/spec/lib/roo/openoffice_spec.rb +2 -8
  39. data/spec/lib/roo/spreadsheet_spec.rb +40 -12
  40. data/spec/lib/roo/utils_spec.rb +106 -0
  41. data/spec/spec_helper.rb +2 -1
  42. data/test/test_generic_spreadsheet.rb +19 -67
  43. data/test/test_helper.rb +9 -56
  44. data/test/test_roo.rb +252 -477
  45. metadata +63 -302
  46. data/Gemfile.lock +0 -78
  47. data/README.markdown +0 -126
  48. data/VERSION +0 -1
  49. data/lib/roo/excel.rb +0 -355
  50. data/lib/roo/excel2003xml.rb +0 -300
  51. data/lib/roo/google.rb +0 -292
  52. data/lib/roo/roo_rails_helper.rb +0 -83
  53. data/lib/roo/worksheet.rb +0 -18
  54. data/spec/lib/roo/excel2003xml_spec.rb +0 -15
  55. data/spec/lib/roo/excel_spec.rb +0 -17
  56. data/spec/lib/roo/google_spec.rb +0 -64
  57. data/test/files/1900_base.xls +0 -0
  58. data/test/files/1900_base.xlsx +0 -0
  59. data/test/files/1904_base.xls +0 -0
  60. data/test/files/1904_base.xlsx +0 -0
  61. data/test/files/Bibelbund.csv +0 -3741
  62. data/test/files/Bibelbund.ods +0 -0
  63. data/test/files/Bibelbund.xls +0 -0
  64. data/test/files/Bibelbund.xlsx +0 -0
  65. data/test/files/Bibelbund.xml +0 -62518
  66. data/test/files/Bibelbund1.ods +0 -0
  67. data/test/files/Pfand_from_windows_phone.xlsx +0 -0
  68. data/test/files/bad_excel_date.xls +0 -0
  69. data/test/files/bbu.ods +0 -0
  70. data/test/files/bbu.xls +0 -0
  71. data/test/files/bbu.xlsx +0 -0
  72. data/test/files/bbu.xml +0 -152
  73. data/test/files/bode-v1.ods.zip +0 -0
  74. data/test/files/bode-v1.xls.zip +0 -0
  75. data/test/files/boolean.csv +0 -2
  76. data/test/files/boolean.ods +0 -0
  77. data/test/files/boolean.xls +0 -0
  78. data/test/files/boolean.xlsx +0 -0
  79. data/test/files/boolean.xml +0 -112
  80. data/test/files/borders.ods +0 -0
  81. data/test/files/borders.xls +0 -0
  82. data/test/files/borders.xlsx +0 -0
  83. data/test/files/borders.xml +0 -144
  84. data/test/files/bug-numbered-sheet-names.xlsx +0 -0
  85. data/test/files/bug-row-column-fixnum-float.xls +0 -0
  86. data/test/files/bug-row-column-fixnum-float.xml +0 -127
  87. data/test/files/comments.ods +0 -0
  88. data/test/files/comments.xls +0 -0
  89. data/test/files/comments.xlsx +0 -0
  90. data/test/files/csvtypes.csv +0 -1
  91. data/test/files/datetime.ods +0 -0
  92. data/test/files/datetime.xls +0 -0
  93. data/test/files/datetime.xlsx +0 -0
  94. data/test/files/datetime.xml +0 -142
  95. data/test/files/datetime_floatconv.xls +0 -0
  96. data/test/files/datetime_floatconv.xml +0 -148
  97. data/test/files/dreimalvier.ods +0 -0
  98. data/test/files/emptysheets.ods +0 -0
  99. data/test/files/emptysheets.xls +0 -0
  100. data/test/files/emptysheets.xlsx +0 -0
  101. data/test/files/emptysheets.xml +0 -105
  102. data/test/files/excel2003.xml +0 -21140
  103. data/test/files/false_encoding.xls +0 -0
  104. data/test/files/false_encoding.xml +0 -132
  105. data/test/files/file_item_error.xlsx +0 -0
  106. data/test/files/formula.ods +0 -0
  107. data/test/files/formula.xls +0 -0
  108. data/test/files/formula.xlsx +0 -0
  109. data/test/files/formula.xml +0 -134
  110. data/test/files/formula_parse_error.xls +0 -0
  111. data/test/files/formula_parse_error.xml +0 -1833
  112. data/test/files/formula_string_error.xlsx +0 -0
  113. data/test/files/html-escape.ods +0 -0
  114. data/test/files/link.xls +0 -0
  115. data/test/files/link.xlsx +0 -0
  116. data/test/files/matrix.ods +0 -0
  117. data/test/files/matrix.xls +0 -0
  118. data/test/files/named_cells.ods +0 -0
  119. data/test/files/named_cells.xls +0 -0
  120. data/test/files/named_cells.xlsx +0 -0
  121. data/test/files/no_spreadsheet_file.txt +0 -1
  122. data/test/files/numbers1.csv +0 -18
  123. data/test/files/numbers1.ods +0 -0
  124. data/test/files/numbers1.xls +0 -0
  125. data/test/files/numbers1.xlsx +0 -0
  126. data/test/files/numbers1.xml +0 -312
  127. data/test/files/numeric-link.xlsx +0 -0
  128. data/test/files/only_one_sheet.ods +0 -0
  129. data/test/files/only_one_sheet.xls +0 -0
  130. data/test/files/only_one_sheet.xlsx +0 -0
  131. data/test/files/only_one_sheet.xml +0 -67
  132. data/test/files/paragraph.ods +0 -0
  133. data/test/files/paragraph.xls +0 -0
  134. data/test/files/paragraph.xlsx +0 -0
  135. data/test/files/paragraph.xml +0 -127
  136. data/test/files/prova.xls +0 -0
  137. data/test/files/ric.ods +0 -0
  138. data/test/files/simple_spreadsheet.ods +0 -0
  139. data/test/files/simple_spreadsheet.xls +0 -0
  140. data/test/files/simple_spreadsheet.xlsx +0 -0
  141. data/test/files/simple_spreadsheet.xml +0 -225
  142. data/test/files/simple_spreadsheet_from_italo.ods +0 -0
  143. data/test/files/simple_spreadsheet_from_italo.xls +0 -0
  144. data/test/files/simple_spreadsheet_from_italo.xml +0 -242
  145. data/test/files/so_datetime.csv +0 -7
  146. data/test/files/style.ods +0 -0
  147. data/test/files/style.xls +0 -0
  148. data/test/files/style.xlsx +0 -0
  149. data/test/files/style.xml +0 -154
  150. data/test/files/time-test.csv +0 -2
  151. data/test/files/time-test.ods +0 -0
  152. data/test/files/time-test.xls +0 -0
  153. data/test/files/time-test.xlsx +0 -0
  154. data/test/files/time-test.xml +0 -131
  155. data/test/files/type_excel.ods +0 -0
  156. data/test/files/type_excel.xlsx +0 -0
  157. data/test/files/type_excelx.ods +0 -0
  158. data/test/files/type_excelx.xls +0 -0
  159. data/test/files/type_openoffice.xls +0 -0
  160. data/test/files/type_openoffice.xlsx +0 -0
  161. data/test/files/whitespace.ods +0 -0
  162. data/test/files/whitespace.xls +0 -0
  163. data/test/files/whitespace.xlsx +0 -0
  164. data/test/files/whitespace.xml +0 -184
  165. data/test/rm_sub_test.rb +0 -12
  166. data/test/rm_test.rb +0 -7
  167. data/website/index.html +0 -385
  168. data/website/index.txt +0 -423
  169. data/website/javascripts/rounded_corners_lite.inc.js +0 -285
  170. data/website/stylesheets/screen.css +0 -130
  171. data/website/template.rhtml +0 -48
@@ -1,11 +1,10 @@
1
1
  require 'roo'
2
2
  require 'soap/rpc/standaloneServer'
3
3
 
4
- NS = "spreadsheetserver" # name of your service = namespace
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("./Ferien-de.ods")
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 = 12321
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
 
@@ -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 "where do you live? "
8
+ print 'where do you live? '
9
9
  my_location = gets.chomp
10
- print "your message? (if left blank, only your name and location will be inserted) "
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+" "+"Greetings from #{my_name} (#{my_location})"
20
+ text = Time.now.to_s + ' ' + "Greetings from #{my_name} (#{my_location})"
21
21
  else
22
- text = Time.now.to_s+" "+"#{my_message} from #{my_name} (#{my_location})"
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" if !success
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
- VERSION = '1.12.1'
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 :Libreoffice
8
- warn "`Roo::Libreoffice` has been deprecated. Use `Roo::LibreOffice` instead."
9
- LibreOffice
10
- when :Openoffice
11
- warn "`Roo::Openoffice` has been deprecated. Use `Roo::OpenOffice` instead."
12
- OpenOffice
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
@@ -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_'
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,10 @@ 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
+ 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=nil)
77
- Roo::Base.number_to_letter(first_column(sheet))
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=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
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
- # 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
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
- # 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
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=nil)
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
- (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)
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 {|k,v|
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: #{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"
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=nil)
188
- sheet ||= @default_sheet
131
+ def to_csv(filename = nil, separator = ',', sheet = default_sheet)
189
132
  if filename
190
- File.open(filename,"w") do |file|
191
- write_csv_content(file,sheet)
133
+ File.open(filename, 'w') do |file|
134
+ write_csv_content(file, sheet, separator)
192
135
  end
193
- return true
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
- return sio.read
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=nil)
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
- 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)
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
- 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
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(rownumber,sheet=nil)
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(rownumber,col,sheet)
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(columnnumber,sheet=nil)
274
- if columnnumber.class == String
275
- columnnumber = Roo::Excel.letter_to_number(columnnumber)
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,columnnumber,sheet)
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=nil) #:nodoc:
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 = 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
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
- set_value(row,col,value,sheet)
298
- set_type(row,col,cell_type,sheet)
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 = @default_sheet
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=nil)
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 {|sheet|
247
+ sheets.each do|sheet|
327
248
  self.default_sheet = sheet
328
- result << "Sheet " + n.to_s + ":\n"
249
+ result << 'Sheet ' + n.to_s + ":\n"
329
250
  unless first_row
330
- result << " - empty -"
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::Base.number_to_letter(first_column)}\n"
335
- result << " Last column: #{Roo::Base.number_to_letter(last_column)}"
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
- self.sheets.each do |sheet|
268
+ xml.spreadsheet do
269
+ sheets.each do |sheet|
349
270
  self.default_sheet = sheet
350
- xml.sheet(:name => sheet) { |x|
351
- if first_row and last_row and first_column and last_column
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
- :row =>row,
358
- :column => col,
359
- :type => celltype(row,col))
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::Base.letter_to_number($1)
377
- row = $2.to_i
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
- @default_sheet = String === index ? index : self.sheets[index]
392
- name ? [@default_sheet,self] : self
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
- self.sheets.each do |s|
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
- # odd unicode characters and white spaces around columns
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
- if options[:clean]
432
- options.delete(:clean)
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
- Hash[(first_column..last_column).map do |col|
449
- [cell(@header_line,col), col]
450
- end]
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
- if block_given?
461
- each(options) {|row| ary << yield(row)}
462
- else
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
- # 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
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
- raise "Couldn't find header row."
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 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
- }
398
+ def file_type_check(filename, ext, name, warning_level, packed = nil)
504
399
  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}"
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 correct_class
524
- raise TypeError, "#{filename} is not #{name} file"
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 correct_class
416
+ warn file_type_warning_message(filename, ext)
528
417
  when :ignore
529
418
  # ignore
530
419
  else
531
- raise "#{warning_level} illegal state of file_warning"
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(tmp_root = nil)
564
- Dir.mktmpdir(TEMP_PREFIX, tmp_root || ENV['ROO_TMP']) do |tmpdir|
565
- yield tmpdir
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
- if String === value
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.strip.unpack('U*').select {|b| b < 127}.pack('U*')
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=nil)
595
- sheet ||= @default_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=nil)
600
- sheet ||= @default_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.class == String
607
- if col.class == Fixnum
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
- raise ArgumentError
561
+ fail ArgumentError
613
562
  end
614
563
  end
615
- if col.class == String
616
- col = Roo::Base.letter_to_number(col)
564
+ if col.is_a?(::String)
565
+ col = ::Roo::Utils.letter_to_number(col)
617
566
  end
618
- return row,col
567
+ [row, col]
619
568
  end
620
569
 
621
570
  def uri?(filename)
622
- filename.start_with?("http://", "https://")
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,"wb") do |file|
631
- open(uri, "User-Agent" => "Ruby/#{RUBY_VERSION}") { |net|
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, "spreadsheet")
643
- File.open(tempfilename,"wb") do |file|
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, "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
596
+ File.join(tmpdir, 'spreadsheet')
682
597
  end
683
598
 
684
599
  def unzip(filename, tmpdir)
685
- Roo::ZipFile.open(filename) do |zip|
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
- raise ArgumentError, "Error: sheet 'nil' not valid"
611
+ fail ArgumentError, "Error: sheet 'nil' not valid"
695
612
  when Fixnum
696
- self.sheets.fetch(sheet-1) do
697
- raise RangeError, "sheet index #{sheet} not found"
613
+ sheets.fetch(sheet - 1) do
614
+ fail RangeError, "sheet index #{sheet} not found"
698
615
  end
699
616
  when String
700
- if !sheets.include? sheet
701
- raise RangeError, "sheet '#{sheet}' not found"
617
+ unless sheets.include? sheet
618
+ fail RangeError, "sheet '#{sheet}' not found"
702
619
  end
703
620
  else
704
- raise TypeError, "not a valid sheet type: #{sheet.inspect}"
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),"wb") do |file|
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(",") if col > 1
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
- %{"#{onecell.gsub(/"/,'""').downcase}"}
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
- raise "unhandled onecell-class #{onecell.class}"
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
- Roo::Base.integer_to_timestring(onecell)
693
+ integer_to_timestring(onecell)
694
+ when :link
695
+ %("#{onecell.url.tr('"', '""')}")
781
696
  else
782
- raise "unhandled celltype #{celltype(row,col,sheet)}"
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 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
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("%02d:%02d:%02d",h,m,s)
709
+ sprintf('%02d:%02d:%02d', h, m, s)
795
710
  end
796
711
  end