roo 2.1.1 → 2.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 53d37d09a786f9536fd5bb00c7cacf9888c8b032
4
- data.tar.gz: 284974b328b26668c58e3b952a12b8777439037f
3
+ metadata.gz: 0a61e2a1f190d2a30554f2bed45557c31573761e
4
+ data.tar.gz: 3bebd2370044ab8c20911368291932337201bb73
5
5
  SHA512:
6
- metadata.gz: 4b0e6d58d52600710fc5feb49e7483fc81e4bca65fa9013a3fa1004f5526107392b02f982ae981676544e301056a0246b4b85e38d3959565174c247ce49695a9
7
- data.tar.gz: 5c3beb6580bece8aa730d1f29b6316889ebb7ce5a7003d8f654edcb1c9800a9f939021fd0d88749489ffdee6ebf496739664e5fede43aeb0067a2a16ad46f2f5
6
+ metadata.gz: e57f90c7aad4b738399b3b3ff0e00b06cbe5fd9fd7ae68c8540f6086521d925db17a8219cacceb656012c3e2f10d814218d95a7ac2c188958c83c4c9425e5f4b
7
+ data.tar.gz: f1fe2348405fcd5097465d761ba60b321cfd52d45957eef61fecf8d6a43a7afd59afeb11e873318f6188dd0b117219b93b87e9ec3d99e9421a2f9defbb195cb2
data/CHANGELOG.md CHANGED
@@ -1,3 +1,20 @@
1
+ ## [2.2.0] - 2015-10-31
2
+ ### Added
3
+ - Added support for returning Integers values to Roo::OpenOffice [#258](https://github.com/roo-rb/roo/pull/258)
4
+ - A missing Header Raises `Roo::HeaderRowNotFoundError` [#247](https://github.com/roo-rb/roo/pull/247)
5
+ - Roo::Excelx::Shared class to pass shared data to Roo::Excelx sheets [#220](https://github.com/roo-rb/roo/pull/220)
6
+ - Proper Type support to Roo::Excelx [#240](https://github.com/roo-rb/roo/pull/240)
7
+ - Added Roo::HeaderRowNotFoundError [#247](https://github.com/roo-rb/roo/pull/247)
8
+
9
+ ### Changed
10
+ - Made spelling/grammar corrections in the README[260](https://github.com/roo-rb/roo/pull/260)
11
+ - Moved Roo::Excelx::Format module [#259](https://github.com/roo-rb/roo/pull/259)
12
+ - Updated README with details about `Roo::Excelx#each_with_streaming` method [#250](https://github.com/roo-rb/roo/pull/250)
13
+
14
+ ### Fixed
15
+ - Fixed Base64 not found issue in Open Office [#267](https://github.com/roo-rb/roo/pull/267)
16
+ - Fixed Regexp to allow method access to cells with multiple digits [#255](https://github.com/roo-rb/roo/pull/255), [#268](https://github.com/roo-rb/roo/pull/268)
17
+
1
18
  ## [2.1.1] - 2015-08-02
2
19
  ### Fixed invalid new lines with _x000D_ character[#231](https://github.com/roo-rb/roo/pull/231)
3
20
  ### Fixed missing URI issue. [#245](https://github.com/roo-rb/roo/pull/245)
data/Guardfile CHANGED
@@ -3,7 +3,7 @@
3
3
 
4
4
  guard :minitest, test_folders: ['test'] do
5
5
  watch(%r{^test/(.*)\/?test_(.*)\.rb$})
6
- watch(%r{^lib/(.*/)?([^/]+)\.rb$}) { |m| "test/#{m[1]}test_#{m[2]}.rb" }
6
+ watch(%r{^lib/(.*/)?([^/]+)\.rb$}) { |m| "test/#{m[1].to_s.sub('roo/', '')}test_#{m[2]}.rb" }
7
7
  watch(%r{^test/test_helper\.rb$}) { 'test' }
8
8
  end
9
9
 
@@ -21,4 +21,3 @@ guard :rspec, cmd: 'bundle exec rspec' do
21
21
  watch('spec/spec_helper.rb') { "spec" }
22
22
  watch(%r{^spec/support/(.+)\.rb$}) { "spec" }
23
23
  end
24
-
data/README.md CHANGED
@@ -151,6 +151,26 @@ xlsx.each_row_streaming do |row|
151
151
  end
152
152
  ```
153
153
 
154
+ By default blank cells will be excluded from the array. To keep them, use the option pad_cells = true. (They will be set to nil in the array)
155
+ ```ruby
156
+ xlsx.each_row_streaming(pad_cells: true) do |row|
157
+ puts row.inspect # Array of Excelx::Cell objects
158
+ end
159
+ ```
160
+
161
+ To stream only some of the rows, you can use the ```max_rows``` and ```offset```options.
162
+ ```ruby
163
+ xlsx.each_row_streaming(offset: 1) do |row| # Will exclude first (inevitably header) row
164
+ puts row.inspect # Array of Excelx::Cell objects
165
+ end
166
+ ```
167
+
168
+ ```ruby
169
+ xlsx.each_row_streaming(max_rows: 3) do |row| # Will yield 4 rows (it's automatically incremented by 1) after the supplied offset.
170
+ puts row.inspect # Array of Excelx::Cell objects
171
+ end
172
+ ```
173
+
154
174
  Iterate over each row
155
175
 
156
176
  ```ruby
@@ -182,7 +202,7 @@ xlsx.formula('A', 2)
182
202
 
183
203
  ### OpenOffice / LibreOffice Support
184
204
 
185
- Roo::OpenOffice supports for encrypted OpenOffice spreadsheets.
205
+ Roo::OpenOffice has support for encrypted OpenOffice spreadsheets.
186
206
 
187
207
  ```ruby
188
208
  # Load an encrypted OpenOffice Spreadsheet
@@ -210,7 +230,7 @@ ods.formula('A', 2)
210
230
  s = Roo::CSV.new("mycsv.csv")
211
231
  ```
212
232
 
213
- Because Roo uses the [standard CSV library](), and you can use options available to that library to parse csv files. You can pass options using the ``csv_options`` key.
233
+ Because Roo uses the [standard CSV library](), you can use options available to that library to parse csv files. You can pass options using the ``csv_options`` key.
214
234
 
215
235
  For instance, you can load tab-delimited files (``.tsv``), and you can use a particular encoding when opening the file.
216
236
 
data/lib/roo.rb CHANGED
@@ -1,4 +1,5 @@
1
1
  require 'roo/constants'
2
+ require 'roo/errors'
2
3
  require 'roo/spreadsheet'
3
4
  require 'roo/base'
4
5
 
data/lib/roo/base.rb CHANGED
@@ -303,7 +303,7 @@ class Roo::Base
303
303
  def method_missing(m, *args)
304
304
  # #aa42 => #cell('aa',42)
305
305
  # #aa42('Sheet1') => #cell('aa',42,'Sheet1')
306
- if m =~ /^([a-z]+)(\d)$/
306
+ if m =~ /^([a-z]+)(\d+)$/
307
307
  col = ::Roo::Utils.letter_to_number(Regexp.last_match[1])
308
308
  row = Regexp.last_match[2].to_i
309
309
  if args.empty?
@@ -392,10 +392,10 @@ class Roo::Base
392
392
  @header_line = line_no
393
393
  return return_headers ? headers : line_no
394
394
  elsif line_no > 100
395
- fail "Couldn't find header row."
395
+ raise Roo::HeaderRowNotFoundError
396
396
  end
397
397
  end
398
- fail "Couldn't find header row."
398
+ raise Roo::HeaderRowNotFoundError
399
399
  end
400
400
 
401
401
  protected
@@ -680,47 +680,47 @@ class Roo::Base
680
680
 
681
681
  # The content of a cell in the csv output
682
682
  def cell_to_csv(row, col, sheet)
683
- if empty?(row, col, sheet)
684
- ''
685
- else
686
- onecell = cell(row, col, sheet)
687
-
688
- case celltype(row, col, sheet)
689
- when :string
683
+ return '' if empty?(row, col, sheet)
684
+
685
+ onecell = cell(row, col, sheet)
686
+
687
+ case celltype(row, col, sheet)
688
+ when :string
689
+ %("#{onecell.gsub('"', '""')}") unless onecell.empty?
690
+ when :boolean
691
+ # TODO: this only works for excelx
692
+ onecell = self.sheet_for(sheet).cells[[row, col]].formatted_value
693
+ %("#{onecell.gsub('"', '""').downcase}")
694
+ when :float, :percentage
695
+ if onecell == onecell.to_i
696
+ onecell.to_i.to_s
697
+ else
698
+ onecell.to_s
699
+ end
700
+ when :formula
701
+ case onecell
702
+ when String
690
703
  %("#{onecell.gsub('"', '""')}") unless onecell.empty?
691
- when :boolean
692
- %("#{onecell.gsub('"', '""').downcase}")
693
- when :float, :percentage
704
+ when Float
694
705
  if onecell == onecell.to_i
695
706
  onecell.to_i.to_s
696
707
  else
697
708
  onecell.to_s
698
709
  end
699
- when :formula
700
- case onecell
701
- when String
702
- %("#{onecell.gsub('"', '""')}") unless onecell.empty?
703
- when Float
704
- if onecell == onecell.to_i
705
- onecell.to_i.to_s
706
- else
707
- onecell.to_s
708
- end
709
- when DateTime
710
- onecell.to_s
711
- else
712
- fail "unhandled onecell-class #{onecell.class}"
713
- end
714
- when :date, :datetime
710
+ when DateTime
715
711
  onecell.to_s
716
- when :time
717
- integer_to_timestring(onecell)
718
- when :link
719
- %("#{onecell.url.gsub('"', '""')}")
720
712
  else
721
- fail "unhandled celltype #{celltype(row, col, sheet)}"
722
- end || ''
723
- end
713
+ fail "unhandled onecell-class #{onecell.class}"
714
+ end
715
+ when :date, :datetime
716
+ onecell.to_s
717
+ when :time
718
+ integer_to_timestring(onecell)
719
+ when :link
720
+ %("#{onecell.url.gsub('"', '""')}")
721
+ else
722
+ fail "unhandled celltype #{celltype(row, col, sheet)}"
723
+ end || ''
724
724
  end
725
725
 
726
726
  # converts an integer value to a time string like '02:05:06'
data/lib/roo/errors.rb ADDED
@@ -0,0 +1,9 @@
1
+ module Roo
2
+ # A base error class for Roo. Most errors thrown by Roo should inherit from
3
+ # this class.
4
+ class Error < StandardError; end
5
+
6
+ # Raised when Roo cannot find a header row that matches the given column
7
+ # name(s).
8
+ class HeaderRowNotFoundError < Error; end
9
+ end
data/lib/roo/excelx.rb CHANGED
@@ -2,9 +2,13 @@ require 'nokogiri'
2
2
  require 'zip/filesystem'
3
3
  require 'roo/link'
4
4
  require 'roo/utils'
5
+ require 'forwardable'
5
6
 
6
7
  module Roo
7
8
  class Excelx < Roo::Base
9
+ extend Forwardable
10
+
11
+ require 'roo/excelx/shared'
8
12
  require 'roo/excelx/workbook'
9
13
  require 'roo/excelx/shared_strings'
10
14
  require 'roo/excelx/styles'
@@ -13,68 +17,10 @@ module Roo
13
17
  require 'roo/excelx/relationships'
14
18
  require 'roo/excelx/comments'
15
19
  require 'roo/excelx/sheet_doc'
16
-
17
- module Format
18
- EXCEPTIONAL_FORMATS = {
19
- 'h:mm am/pm' => :date,
20
- 'h:mm:ss am/pm' => :date
21
- }
22
-
23
- STANDARD_FORMATS = {
24
- 0 => 'General'.freeze,
25
- 1 => '0'.freeze,
26
- 2 => '0.00'.freeze,
27
- 3 => '#,##0'.freeze,
28
- 4 => '#,##0.00'.freeze,
29
- 9 => '0%'.freeze,
30
- 10 => '0.00%'.freeze,
31
- 11 => '0.00E+00'.freeze,
32
- 12 => '# ?/?'.freeze,
33
- 13 => '# ??/??'.freeze,
34
- 14 => 'mm-dd-yy'.freeze,
35
- 15 => 'd-mmm-yy'.freeze,
36
- 16 => 'd-mmm'.freeze,
37
- 17 => 'mmm-yy'.freeze,
38
- 18 => 'h:mm AM/PM'.freeze,
39
- 19 => 'h:mm:ss AM/PM'.freeze,
40
- 20 => 'h:mm'.freeze,
41
- 21 => 'h:mm:ss'.freeze,
42
- 22 => 'm/d/yy h:mm'.freeze,
43
- 37 => '#,##0 ;(#,##0)'.freeze,
44
- 38 => '#,##0 ;[Red](#,##0)'.freeze,
45
- 39 => '#,##0.00;(#,##0.00)'.freeze,
46
- 40 => '#,##0.00;[Red](#,##0.00)'.freeze,
47
- 45 => 'mm:ss'.freeze,
48
- 46 => '[h]:mm:ss'.freeze,
49
- 47 => 'mmss.0'.freeze,
50
- 48 => '##0.0E+0'.freeze,
51
- 49 => '@'.freeze
52
- }
53
-
54
- def to_type(format)
55
- format = format.to_s.downcase
56
- if (type = EXCEPTIONAL_FORMATS[format])
57
- type
58
- elsif format.include?('#')
59
- :float
60
- elsif !format.match(/d+(?![\]])/).nil? || format.include?('y')
61
- if format.include?('h') || format.include?('s')
62
- :datetime
63
- else
64
- :date
65
- end
66
- elsif format.include?('h') || format.include?('s')
67
- :time
68
- elsif format.include?('%')
69
- :percentage
70
- else
71
- :float
72
- end
73
- end
74
-
75
- module_function :to_type
76
- end
20
+ require 'roo/excelx/coordinate'
21
+ require 'roo/excelx/format'
77
22
 
23
+ delegate [:styles, :workbook, :shared_strings, :rels_files, :sheet_files, :comments_files] => :@shared
78
24
  ExceedsMaxError = Class.new(StandardError)
79
25
 
80
26
  # initialization and opening of a spreadsheet file
@@ -94,9 +40,8 @@ module Roo
94
40
  end
95
41
 
96
42
  @tmpdir = make_tmpdir(basename, options[:tmpdir_root])
43
+ @shared = Shared.new(@tmpdir)
97
44
  @filename = local_filename(filename_or_stream, @tmpdir, packed)
98
- @comments_files = []
99
- @rels_files = []
100
45
  process_zipfile(@filename || filename_or_stream)
101
46
 
102
47
  @sheet_names = workbook.sheets.map do |sheet|
@@ -106,7 +51,7 @@ module Roo
106
51
  end.compact
107
52
  @sheets = []
108
53
  @sheets_by_name = Hash[@sheet_names.map.with_index do |sheet_name, n|
109
- @sheets[n] = Sheet.new(sheet_name, @rels_files[n], @sheet_files[n], @comments_files[n], styles, shared_strings, workbook, sheet_options)
54
+ @sheets[n] = Sheet.new(sheet_name, @shared, n, sheet_options)
110
55
  [sheet_name, @sheets[n]]
111
56
  end]
112
57
 
@@ -187,7 +132,7 @@ module Roo
187
132
  def set(row, col, value, sheet = nil) #:nodoc:
188
133
  key = normalize(row, col)
189
134
  cell_type = cell_type_by_value(value)
190
- sheet_for(sheet).cells[key] = Cell.new(value, cell_type, nil, cell_type, value, nil, nil, nil, Cell::Coordinate.new(row, col))
135
+ sheet_for(sheet).cells[key] = Cell.new(value, cell_type, nil, cell_type, value, nil, nil, nil, Coordinate.new(row, col))
191
136
  end
192
137
 
193
138
  # Returns the formula at (row,col).
@@ -239,14 +184,14 @@ module Roo
239
184
  # Note: this is only available within the Excelx class
240
185
  def excelx_type(row, col, sheet = nil)
241
186
  key = normalize(row, col)
242
- safe_send(sheet_for(sheet).cells[key], :excelx_type)
187
+ safe_send(sheet_for(sheet).cells[key], :cell_type)
243
188
  end
244
189
 
245
190
  # returns the internal value of an excelx cell
246
191
  # Note: this is only available within the Excelx class
247
192
  def excelx_value(row, col, sheet = nil)
248
193
  key = normalize(row, col)
249
- safe_send(sheet_for(sheet).cells[key], :excelx_value)
194
+ safe_send(sheet_for(sheet).cells[key], :cell_value)
250
195
  end
251
196
 
252
197
  # returns the internal format of an excel cell
@@ -259,8 +204,8 @@ module Roo
259
204
  sheet = sheet_for(sheet)
260
205
  key = normalize(row, col)
261
206
  cell = sheet.cells[key]
262
- !cell || !cell.value || (cell.type == :string && cell.value.empty?) \
263
- || (row < sheet.first_row || row > sheet.last_row || col < sheet.first_column || col > sheet.last_column)
207
+ !cell || cell.empty? || (cell.type == :string && cell.value.empty?) ||
208
+ (row < sheet.first_row || row > sheet.last_row || col < sheet.first_column || col > sheet.last_column)
264
209
  end
265
210
 
266
211
  # shows the internal representation of all cells
@@ -404,6 +349,7 @@ module Roo
404
349
  name = sheets[id]
405
350
  entry = entries.find { |e| e.name =~ /#{name}$/ }
406
351
  path = "#{tmpdir}/roo_sheet#{i + 1}"
352
+ sheet_files << path
407
353
  @sheet_files << path
408
354
  entry.extract(path)
409
355
  end
@@ -457,19 +403,21 @@ module Roo
457
403
  # sheet's comment file is in the sheet1.xml.rels file. SEE
458
404
  # ECMA-376 12.3.3 in "Ecma Office Open XML Part 1".
459
405
  nr = Regexp.last_match[1].to_i
460
- @comments_files[nr - 1] = "#{@tmpdir}/roo_comments#{nr}"
406
+ comments_files[nr - 1] = "#{@tmpdir}/roo_comments#{nr}"
461
407
  when /sheet([0-9]+).xml.rels$/
462
408
  # FIXME: Roo seems to use sheet[\d].xml.rels for hyperlinks only, but
463
409
  # it also stores the location for sharedStrings, comments,
464
410
  # drawings, etc.
465
411
  nr = Regexp.last_match[1].to_i
466
- @rels_files[nr - 1] = "#{@tmpdir}/roo_rels#{nr}"
412
+ rels_files[nr - 1] = "#{@tmpdir}/roo_rels#{nr}"
467
413
  end
468
414
 
469
415
  entry.extract(path) if path
470
416
  end
471
417
  end
472
418
 
419
+ # NOTE: To reduce memory, styles, shared_strings, workbook can be class
420
+ # variables in a Shared module.
473
421
  def styles
474
422
  @styles ||= Styles.new(File.join(@tmpdir, 'roo_styles.xml'))
475
423
  end
@@ -1,4 +1,12 @@
1
1
  require 'date'
2
+ require 'roo/excelx/cell/base'
3
+ require 'roo/excelx/cell/boolean'
4
+ require 'roo/excelx/cell/datetime'
5
+ require 'roo/excelx/cell/date'
6
+ require 'roo/excelx/cell/empty'
7
+ require 'roo/excelx/cell/number'
8
+ require 'roo/excelx/cell/string'
9
+ require 'roo/excelx/cell/time'
2
10
 
3
11
  module Roo
4
12
  class Excelx
@@ -6,7 +14,9 @@ module Roo
6
14
  attr_reader :type, :formula, :value, :excelx_type, :excelx_value, :style, :hyperlink, :coordinate
7
15
  attr_writer :value
8
16
 
17
+ # DEPRECATED: Please use Cell.create_cell instead.
9
18
  def initialize(value, type, formula, excelx_type, excelx_value, style, hyperlink, base_date, coordinate)
19
+ warn '[DEPRECATION] `Cell.new` is deprecated. Please use `Cell.create_cell` instead.'
10
20
  @type = type
11
21
  @formula = formula
12
22
  @base_date = base_date if [:date, :datetime].include?(@type)
@@ -29,10 +39,29 @@ module Roo
29
39
  end
30
40
  end
31
41
 
42
+ def self.create_cell(type, *values)
43
+ case type
44
+ when :string
45
+ Cell::String.new(*values)
46
+ when :boolean
47
+ Cell::Boolean.new(*values)
48
+ when :number
49
+ Cell::Number.new(*values)
50
+ when :date
51
+ Cell::Date.new(*values)
52
+ when :datetime
53
+ Cell::DateTime.new(*values)
54
+ when :time
55
+ Cell::Time.new(*values)
56
+ end
57
+ end
58
+
59
+ # Deprecated: use Roo::Excelx::Coordinate instead.
32
60
  class Coordinate
33
61
  attr_accessor :row, :column
34
62
 
35
63
  def initialize(row, column)
64
+ warn '[DEPRECATION] `Roo::Excel::Cell::Coordinate` is deprecated. Please use `Roo::Excelx::Coordinate` instead.'
36
65
  @row, @column = row, column
37
66
  end
38
67
  end
@@ -57,20 +86,20 @@ module Roo
57
86
  def create_date(date)
58
87
  yyyy, mm, dd = date.strftime('%Y-%m-%d').split('-')
59
88
 
60
- Date.new(yyyy.to_i, mm.to_i, dd.to_i)
89
+ ::Date.new(yyyy.to_i, mm.to_i, dd.to_i)
61
90
  end
62
91
 
63
92
  def create_datetime(date)
64
93
  datetime_string = date.strftime('%Y-%m-%d %H:%M:%S.%N')
65
94
  t = round_datetime(datetime_string)
66
95
 
67
- DateTime.civil(t.year, t.month, t.day, t.hour, t.min, t.sec)
96
+ ::DateTime.civil(t.year, t.month, t.day, t.hour, t.min, t.sec)
68
97
  end
69
98
 
70
99
  def round_datetime(datetime_string)
71
100
  /(?<yyyy>\d+)-(?<mm>\d+)-(?<dd>\d+) (?<hh>\d+):(?<mi>\d+):(?<ss>\d+.\d+)/ =~ datetime_string
72
101
 
73
- Time.new(yyyy.to_i, mm.to_i, dd.to_i, hh.to_i, mi.to_i, ss.to_r).round(0)
102
+ ::Time.new(yyyy.to_i, mm.to_i, dd.to_i, hh.to_i, mi.to_i, ss.to_r).round(0)
74
103
  end
75
104
  end
76
105
  end