roo 1.13.2 → 2.0.0beta1

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