roo 2.8.3 → 2.10.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1e41bf12228c4a168e8df0a0cbf5b94eceaa89a1f5d174f3a3588a1a965d49f4
4
- data.tar.gz: 69d749c2667ba4efbdcd7257550ba380d6ba0250633d439ae738673e6d2b5924
3
+ metadata.gz: cd6f8267a04fcec20134f5170360fdd0259369b0c8b319100d0304c95964a6f5
4
+ data.tar.gz: 6e33716242265c9c02a7cc42b22690f03114cf12bcbbe846055040fede3cd790
5
5
  SHA512:
6
- metadata.gz: 14c8d1e69b31e1be16c8845c686e596c108e9c4251e4d5ee75471dd8ea325c2aacdcf1fa4b10f6262cd840bd063de5b11432439a9bce1b82b4c350566981bb48
7
- data.tar.gz: 7a01fefbe77485af0252384dcd20770e0c993c29b916be55736a86fcdc8de710d99709fb09d95f4d628a2b3819069c3aba5df28f3488a5f519753b854c82cd48
6
+ metadata.gz: 65dd59afe1dfda800c7e88547f305dbd1eb295c5715fd23dc5123618307205ae1a0c0740511739d2880b4d91cabca10fad152d522cca96a2694f8e3c1cccbd39
7
+ data.tar.gz: 9d239b22ee226fc3466d2445127dc2c4fc50c488249171b0b86dc17a4a41779e543e83ed3def1fe9fae26ddc67608a81733483e3757b32156d822abdbf2bc974
@@ -0,0 +1,15 @@
1
+ name: Changelog
2
+
3
+ on:
4
+ pull_request:
5
+ types: [opened, synchronize, reopened, ready_for_review, labeled, unlabeled]
6
+
7
+ jobs:
8
+ changelog:
9
+ runs-on: ubuntu-latest
10
+ steps:
11
+ - uses: actions/checkout@v2
12
+ - uses: amoniacou/changelog-enforcer@v1.4.0
13
+ with:
14
+ changeLogPath: 'CHANGELOG.md'
15
+ skipLabel: 'Skip-Changelog'
@@ -0,0 +1,34 @@
1
+ name: Ruby
2
+ on:
3
+ push:
4
+ branches:
5
+ - master
6
+ pull_request:
7
+ branches:
8
+ - master
9
+ jobs:
10
+ build:
11
+ runs-on: ubuntu-latest
12
+ strategy:
13
+ fail-fast: false
14
+ matrix:
15
+ ruby:
16
+ - '2.7'
17
+ - '3.0'
18
+ - '3.1'
19
+ - ruby-head
20
+ - jruby-9.3.3.0
21
+ include:
22
+ - ruby: ruby-head
23
+ env:
24
+ RUBYOPT: '--jit'
25
+ steps:
26
+ - uses: actions/checkout@v2
27
+ - uses: ruby/setup-ruby@v1
28
+ with:
29
+ ruby-version: ${{ matrix.ruby }}
30
+ bundler-cache: true
31
+ - run: bundle exec rake
32
+ env:
33
+ LONG_RUN: true
34
+
data/CHANGELOG.md CHANGED
@@ -1,4 +1,29 @@
1
- ## Unreleased
1
+ ## [2.10.1] 2024-01-17
2
+
3
+ ### Changed/Added
4
+ - Prevent warnings on Ruby 3.1 if finalizer is called twice [586](https://github.com/roo-rb/roo/pull/586)
5
+ - Fix Roo::Base#each_with_pagename degraded at [576](https://github.com/roo-rb/roo/pull/576) [583](https://github.com/roo-rb/roo/pull/583)
6
+
7
+ ## [2.10.0] 2023-02-07
8
+
9
+ ### Changed/Added
10
+ - Fix gsub! usage for open office documents on a frozen string [581](https://github.com/roo-rb/roo/pull/581)
11
+ - Add support for boolean values in open office files that were generated via Google Sheets [580](https://github.com/roo-rb/roo/pull/580)
12
+ - Roo::Base#each_with_pagename returns Enumerator Object [576](https://github.com/roo-rb/roo/pull/576)
13
+
14
+ ## [2.9.0] 2022-03-19
15
+
16
+ ### Changed/Added
17
+ - Ruby 3.x Support [555](https://github.com/roo-rb/roo/pull/555)
18
+ - Ignore all richdata at 'xl/richData' of XSLX [552](https://github.com/roo-rb/roo/pull/552)
19
+ - Only copy if cell is present when `expand_merged_ranges: true` [557](https://github.com/roo-rb/roo/pull/557)
20
+ - Fixes issue where the contents of hidden sheet was returned when parsing visible sheets only. [536](https://github.com/roo-rb/roo/pull/536)
21
+ - Add formats [525](https://github.com/roo-rb/roo/pull/525)
22
+ - Fix warnings caused by Ruby 2.7 update [530](https://github.com/roo-rb/roo/pull/530)
23
+ - Add formats [525](https://github.com/roo-rb/roo/pull/525)
24
+
25
+ ### Removed
26
+ - Support for ruby 2.4, 2.5, 2.6(excluded jRuby)
2
27
 
3
28
  ## [2.8.3] 2020-02-03
4
29
  ### Changed/Added
data/Gemfile CHANGED
@@ -10,6 +10,7 @@ group :test do
10
10
  gem 'simplecov', '>= 0.9.0', require: false
11
11
  gem 'coveralls', require: false
12
12
  gem "minitest-reporters"
13
+ gem 'webrick' if RUBY_VERSION >= '3.0.0'
13
14
  end
14
15
 
15
16
  group :local_development do
data/README.md CHANGED
@@ -18,26 +18,41 @@ Install as a gem
18
18
  Or add it to your Gemfile
19
19
 
20
20
  ```ruby
21
- gem "roo", "~> 2.8.0"
21
+ gem "roo", "~> 2.10.0"
22
22
  ```
23
23
  ## Usage
24
24
 
25
- Opening a spreadsheet
25
+ ### Opening a spreadsheet
26
26
 
27
+ You can use the `Roo::Spreadsheet` class so `roo` automatically detects which [parser class](https://github.com/roo-rb/roo/blob/master/lib/roo.rb#L17) to use for you.
27
28
  ```ruby
28
29
  require 'roo'
29
30
 
30
- xlsx = Roo::Spreadsheet.open('./new_prices.xlsx')
31
- xlsx = Roo::Excelx.new("./new_prices.xlsx")
31
+ file_name = './new_prices.xlsx'
32
+ xlsx = Roo::Spreadsheet.open(file_name)
33
+ xlsx.info
34
+ # => Returns basic info about the spreadsheet file
35
+ ```
36
+
37
+ ``Roo::Spreadsheet.open`` can accept both string paths and ``File`` instances. Also, you can provide the extension of the file as an option:
32
38
 
33
- # Use the extension option if the extension is ambiguous.
34
- xlsx = Roo::Spreadsheet.open('./rails_temp_upload', extension: :xlsx)
39
+ ```ruby
40
+ require 'roo'
35
41
 
42
+ file_name = './rails_temp_upload'
43
+ xlsx = Roo::Spreadsheet.open(file_name, extension: :xlsx)
36
44
  xlsx.info
37
45
  # => Returns basic info about the spreadsheet file
38
46
  ```
39
47
 
40
- ``Roo::Spreadsheet.open`` can accept both paths and ``File`` instances.
48
+ On the other hand, if you know what the file extension is, you can use the specific parser class instead:
49
+ ```ruby
50
+ require 'roo'
51
+
52
+ xlsx = Roo::Excelx.new("./new_prices.xlsx")
53
+ xlsx.info
54
+ # => Returns basic info about the spreadsheet file
55
+ ```
41
56
 
42
57
  ### Working with sheets
43
58
 
@@ -249,7 +264,7 @@ ods.formula('A', 2)
249
264
  csv = Roo::CSV.new("mycsv.csv")
250
265
  ```
251
266
 
252
- 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.
267
+ 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.
253
268
 
254
269
  For instance, you can load tab-delimited files (``.tsv``), and you can use a particular encoding when opening the file.
255
270
 
@@ -262,6 +277,18 @@ csv = Roo::CSV.new("mytsv.tsv", csv_options: {col_sep: "\t"})
262
277
  csv = Roo::CSV.new("mycsv.csv", csv_options: {encoding: Encoding::ISO_8859_1})
263
278
  ```
264
279
 
280
+ You can also open csv files through the Roo::Spreadsheet class (useful if you accept both CSV and Excel types from a user file upload, for example).
281
+
282
+ ```ruby
283
+ # Load a spreadsheet from a file path
284
+ # Roo figures out the right parser based on file extension
285
+ spreadsheet = Roo::Spreadsheet.open(csv_or_xlsx_file)
286
+
287
+ # Load a csv and auto-strip the BOM (byte order mark)
288
+ # csv files saved from MS Excel typically have the BOM marker at the beginning of the file
289
+ spreadsheet = Roo::Spreadsheet.open("mycsv.csv", { csv_options: { encoding: 'bom|utf-8' } })
290
+ ```
291
+
265
292
  ## Upgrading from Roo 1.13.x
266
293
  If you use ``.xls`` or Google spreadsheets, you will need to install ``roo-xls`` or ``roo-google`` to continue using that functionality.
267
294
 
@@ -271,7 +298,7 @@ Roo's public methods have stayed relatively consistent between 1.13.x and 2.0.0,
271
298
 
272
299
  ## Contributing
273
300
  ### Features
274
- 1. Fork it ( https://github.com/[my-github-username]/roo/fork )
301
+ 1. Fork it ( https://github.com/roo-rb/roo/fork )
275
302
  2. Install it (`bundle install --with local_development`)
276
303
  3. Create your feature branch (`git checkout -b my-new-feature`)
277
304
  4. Commit your changes (`git commit -am 'My new feature'`)
data/lib/roo/base.rb CHANGED
@@ -250,6 +250,8 @@ class Roo::Base
250
250
 
251
251
  # iterate through all worksheets of a document
252
252
  def each_with_pagename
253
+ return to_enum(:each_with_pagename) { sheets.size } unless block_given?
254
+
253
255
  sheets.each do |s|
254
256
  yield sheet(s, true)
255
257
  end
@@ -544,7 +546,7 @@ class Roo::Base
544
546
  tempfilename = File.join(tmpdir, find_basename(uri))
545
547
  begin
546
548
  File.open(tempfilename, "wb") do |file|
547
- open(uri, "User-Agent" => "Ruby/#{RUBY_VERSION}") do |net|
549
+ URI.open(uri, "User-Agent" => "Ruby/#{RUBY_VERSION}") do |net|
548
550
  file.write(net.read)
549
551
  end
550
552
  end
data/lib/roo/csv.rb CHANGED
@@ -90,17 +90,23 @@ module Roo
90
90
  def each_row(options, &block)
91
91
  if uri?(filename)
92
92
  each_row_using_tempdir(options, &block)
93
- elsif is_stream?(filename_or_stream)
94
- ::CSV.new(filename_or_stream, options).each(&block)
95
93
  else
96
- ::CSV.foreach(filename, options, &block)
94
+ csv_foreach(filename_or_stream, options, &block)
97
95
  end
98
96
  end
99
97
 
100
98
  def each_row_using_tempdir(options, &block)
101
99
  ::Dir.mktmpdir(Roo::TEMP_PREFIX, ENV["ROO_TMP"]) do |tmpdir|
102
100
  tmp_filename = download_uri(filename, tmpdir)
103
- ::CSV.foreach(tmp_filename, options, &block)
101
+ csv_foreach(tmp_filename, options, &block)
102
+ end
103
+ end
104
+
105
+ def csv_foreach(path_or_io, options, &block)
106
+ if is_stream?(path_or_io)
107
+ ::CSV.new(path_or_io, **options).each(&block)
108
+ else
109
+ ::CSV.foreach(path_or_io, **options, &block)
104
110
  end
105
111
  end
106
112
 
@@ -48,10 +48,10 @@ module Roo
48
48
  when /^(0+)$/ then "%0#{$1.size}d"
49
49
  when /^0\.(0+)$/ then "%.#{$1.size}f"
50
50
  when '#,##0' then number_format('%.0f')
51
- when '#,##0.00' then number_format('%.2f')
51
+ when /^#,##0.(0+)$/ then number_format("%.#{$1.size}f")
52
52
  when '0%'
53
53
  proc do |number|
54
- Kernel.format('%d%%', number.to_f * 100)
54
+ Kernel.format('%.0f%%', number.to_f * 100)
55
55
  end
56
56
  when '0.00%'
57
57
  proc do |number|
@@ -64,7 +64,18 @@ module Roo
64
64
  when '#,##0.00;[Red](#,##0.00)' then number_format('%.2f', '[Red](%.2f)')
65
65
  # FIXME: not quite sure what the format should look like in this case.
66
66
  when '##0.0E+0' then '%.1E'
67
+ when "_-* #,##0.00\\ _€_-;\\-* #,##0.00\\ _€_-;_-* \"-\"??\\ _€_-;_-@_-" then number_format('%.2f', '-%.2f')
67
68
  when '@' then proc { |number| number }
69
+ when /^(?:_\()?"([^"]*)"(?:\* )?([^_]+)/
70
+ proc do |number|
71
+ formatted_number = generate_formatter($2).call(number)
72
+ "#{$1}#{formatted_number}"
73
+ end
74
+ when /^_[- \(]\[\$([^-]*)[^#@]+([^_]+)/
75
+ proc do |number|
76
+ formatted_number = generate_formatter($2).call(number)
77
+ "#{$1}#{formatted_number}"
78
+ end
68
79
  else
69
80
  raise "Unknown format: #{format.inspect}"
70
81
  end
@@ -13,7 +13,7 @@ module Roo
13
13
  super
14
14
  @format = excelx_type.last
15
15
  @datetime = create_datetime(base_date, value)
16
- @value = link ? Roo::Link.new(link, value) : (value.to_f * 86_400).to_i
16
+ @value = link ? Roo::Link.new(link, value) : (value.to_f * 86_400).round.to_i
17
17
  end
18
18
 
19
19
  def formatted_value
@@ -101,12 +101,7 @@ module Roo
101
101
  cell_xml_children.each do |cell|
102
102
  case cell.name
103
103
  when 'is'
104
- content = +""
105
- cell.children.each do |inline_str|
106
- if inline_str.name == 't'
107
- content << inline_str.content
108
- end
109
- end
104
+ content = cell.search('t').map(&:content).join
110
105
  unless content.empty?
111
106
  return Excelx::Cell.cell_class(:string).new(content, formula, style, hyperlink, coordinate)
112
107
  end
@@ -197,11 +192,12 @@ module Roo
197
192
  # Extract merged ranges from xml
198
193
  merges = {}
199
194
  doc.xpath('/worksheet/mergeCells/mergeCell').each do |mergecell_xml|
200
- tl, br = mergecell_xml["ref"].split(/:/).map { |ref| ::Roo::Utils.ref_to_key(ref) }
201
- for row in tl[0]..br[0] do
202
- for col in tl[1]..br[1] do
203
- next if row == tl[0] && col == tl[1]
204
- merges[[row, col]] = tl
195
+ src, dst = mergecell_xml["ref"].split(/:/).map { |ref| ::Roo::Utils.ref_to_key(ref) }
196
+ next unless cells[src]
197
+ for row in src[0]..dst[0] do
198
+ for col in src[1]..dst[1] do
199
+ next if row == src[0] && col == src[1]
200
+ merges[[row, col]] = src
205
201
  end
206
202
  end
207
203
  end
@@ -215,10 +211,19 @@ module Roo
215
211
  extracted_cells = {}
216
212
  empty_cell = @options[:empty_cell]
217
213
 
218
- doc.xpath('/worksheet/sheetData/row/c').each do |cell_xml|
219
- coordinate = ::Roo::Utils.extract_coordinate(cell_xml["r"])
220
- cell = cell_from_xml(cell_xml, hyperlinks(relationships)[coordinate], coordinate, empty_cell)
221
- extracted_cells[coordinate] = cell if cell
214
+ doc.xpath('/worksheet/sheetData/row').each.with_index(1) do |row_xml, ycoord|
215
+ row_xml.xpath('c').each.with_index(1) do |cell_xml, xcoord|
216
+ r = cell_xml['r']
217
+ coordinate =
218
+ if r.nil?
219
+ ::Roo::Excelx::Coordinate.new(ycoord, xcoord)
220
+ else
221
+ ::Roo::Utils.extract_coordinate(r)
222
+ end
223
+
224
+ cell = cell_from_xml(cell_xml, hyperlinks(relationships)[coordinate], coordinate, empty_cell)
225
+ extracted_cells[coordinate] = cell if cell
226
+ end
222
227
  end
223
228
 
224
229
  expand_merged_ranges(extracted_cells) if @options[:expand_merged_ranges]
@@ -32,6 +32,7 @@ module Roo
32
32
  doc.xpath('//definedName').each_with_object({}) do |defined_name, hash|
33
33
  # "Sheet1!$C$5"
34
34
  sheet, coordinates = defined_name.text.split('!$', 2)
35
+ next unless coordinates
35
36
  col, row = coordinates.split('$')
36
37
  name = defined_name['name']
37
38
  hash[name] = Label.new(name, sheet, row, col)
data/lib/roo/excelx.rb CHANGED
@@ -60,15 +60,16 @@ module Roo
60
60
  @filename = local_filename(filename_or_stream, @tmpdir, packed)
61
61
  process_zipfile(@filename || filename_or_stream)
62
62
 
63
- @sheet_names = workbook.sheets.map do |sheet|
64
- unless options[:only_visible_sheets] && sheet['state'] == 'hidden'
65
- sheet['name']
66
- end
67
- end.compact
63
+ @sheet_names = []
68
64
  @sheets = []
69
65
  @sheets_by_name = {}
70
- @sheet_names.each_with_index do |sheet_name, n|
71
- @sheets_by_name[sheet_name] = @sheets[n] = Sheet.new(sheet_name, @shared, n, sheet_options)
66
+
67
+ workbook.sheets.each_with_index do |sheet, index|
68
+ next if options[:only_visible_sheets] && sheet['state'] == 'hidden'
69
+
70
+ sheet_name = sheet['name']
71
+ @sheet_names << sheet_name
72
+ @sheets_by_name[sheet_name] = @sheets[index] = Sheet.new(sheet_name, @shared, index, sheet_options)
72
73
  end
73
74
 
74
75
  if cell_max
@@ -428,6 +429,11 @@ module Roo
428
429
  entries.each do |entry|
429
430
  path =
430
431
  case entry.name.downcase
432
+ when /richdata/
433
+ # FIXME: Ignore richData as parsing is not implemented yet and can cause
434
+ # Zip::DestinationFileExistsError when including a second "styles.xml" entry
435
+ # see http://schemas.microsoft.com/office/spreadsheetml/2017/richdata2
436
+ nil
431
437
  when /sharedstrings.xml$/
432
438
  "#{@tmpdir}/roo_sharedStrings.xml"
433
439
  when /styles.xml$/
@@ -423,7 +423,10 @@ module Roo
423
423
  @style[sheet][key] = style_name
424
424
  case @cell_type[sheet][key]
425
425
  when :float
426
- @cell[sheet][key] = (table_cell.attributes['value'].to_s.include?(".") || table_cell.children.first.text.include?(".")) ? v.to_f : v.to_i
426
+ value = (table_cell.attributes['value'].to_s.include?(".") || table_cell.children.first.text.include?(".")) ? v.to_f : v.to_i
427
+ value = 'true' if formula == '=TRUE()'
428
+ value = 'false' if formula == '=FALSE()'
429
+ @cell[sheet][key] = value
427
430
  when :percentage
428
431
  @cell[sheet][key] = v.to_f
429
432
  when :string
@@ -517,7 +520,7 @@ module Roo
517
520
  str_v += child.content #.text
518
521
  end
519
522
  end
520
- str_v.gsub!(/&apos;/, "'") # special case not supported by unescapeHTML
523
+ str_v = str_v.gsub(/&apos;/, "'") # special case not supported by unescapeHTML
521
524
  str_v = CGI.unescapeHTML(str_v)
522
525
  end # == 'p'
523
526
  end
@@ -24,8 +24,14 @@ module Roo
24
24
  options[:file_warning] = :ignore
25
25
  extension.tr('.', '').downcase.to_sym
26
26
  else
27
- res = ::File.extname((path =~ /\A#{::URI::DEFAULT_PARSER.make_regexp}\z/) ? ::URI.parse(::URI.encode(path)).path : path)
28
- res.tr('.', '').downcase.to_sym
27
+ parsed_path =
28
+ if path =~ /\A#{::URI::DEFAULT_PARSER.make_regexp}\z/
29
+ # path is 7th match
30
+ Regexp.last_match[7]
31
+ else
32
+ path
33
+ end
34
+ ::File.extname(parsed_path).tr('.', '').downcase.to_sym
29
35
  end
30
36
  end
31
37
  end
data/lib/roo/tempdir.rb CHANGED
@@ -4,7 +4,10 @@ module Roo
4
4
  if @tempdirs && (dirs_to_remove = @tempdirs[object_id])
5
5
  @tempdirs.delete(object_id)
6
6
  dirs_to_remove.each do |dir|
7
- ::FileUtils.remove_entry(dir)
7
+ # Pass force=true to avoid an exception (and thus warnings in Ruby 3.1) if dir has
8
+ # already been removed. This can occur when the finalizer is called both in a forked
9
+ # child process and in the parent.
10
+ ::FileUtils.remove_entry(dir, true)
8
11
  end
9
12
  end
10
13
  end
data/lib/roo/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Roo
2
- VERSION = "2.8.3"
2
+ VERSION = "2.10.1"
3
3
  end
data/roo.gemspec CHANGED
@@ -17,12 +17,19 @@ Gem::Specification.new do |spec|
17
17
  spec.files.reject! { |fn| fn.include?('test/files') }
18
18
  spec.require_paths = ['lib']
19
19
 
20
- spec.required_ruby_version = ">= 2.3.0"
20
+ if defined?(RUBY_ENGINE) && RUBY_ENGINE == 'jruby'
21
+ spec.required_ruby_version = ">= 2.6.0"
22
+ else
23
+ spec.required_ruby_version = ">= 2.7.0"
24
+ end
21
25
 
22
26
  spec.add_dependency 'nokogiri', '~> 1'
23
27
  spec.add_dependency 'rubyzip', '>= 1.3.0', '< 3.0.0'
24
28
 
25
- spec.add_development_dependency 'rake', '~> 10.1'
29
+ spec.add_development_dependency 'rake'
26
30
  spec.add_development_dependency 'minitest', '~> 5.4', '>= 5.4.3'
27
31
  spec.add_development_dependency 'rack', '~> 1.6', '< 2.0.0'
32
+ if RUBY_VERSION >= '3.0.0'
33
+ spec.add_development_dependency 'matrix'
34
+ end
28
35
  end
@@ -182,6 +182,26 @@ describe Roo::Base do
182
182
  end
183
183
  end
184
184
 
185
+ describe '#each_with_pagename' do
186
+ context 'when block given' do
187
+ it 'iterate with sheet and sheet_name' do
188
+ sheet_names = []
189
+ spreadsheet.each_with_pagename do |sheet_name, sheet|
190
+ sheet_names << sheet_name
191
+ end
192
+ expect(sheet_names).to eq ['my_sheet', 'blank sheet']
193
+ end
194
+ end
195
+
196
+ context 'when called without block' do
197
+ it 'should return an enumerator with all the rows' do
198
+ each_with_pagename = spreadsheet.each_with_pagename
199
+ expect(each_with_pagename).to be_a(Enumerator)
200
+ expect(each_with_pagename.to_a.last).to eq([spreadsheet.default_sheet, spreadsheet])
201
+ end
202
+ end
203
+ end
204
+
185
205
  describe '#each' do
186
206
  it 'should return an enumerator with all the rows' do
187
207
  each = spreadsheet.each
@@ -0,0 +1,15 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe Roo::Excelx::Cell::Time do
4
+ it "should set the cell value to the correct number of seconds" do
5
+ value = 0.05513888888888888 # '1:19:24'
6
+ excelx_type = [:numeric_or_formula, "h:mm:ss"]
7
+ base_timestamp = Date.new(1899, 12, 30).to_time.to_i
8
+ time_cell = Roo::Excelx::Cell::Time.new(value, nil, excelx_type, 1, nil, base_timestamp, nil)
9
+ expect(time_cell.value).to eq(1*60*60 + 19*60 + 24) # '1:19:24' in seconds
10
+ # use case from https://github.com/roo-rb/roo/issues/310
11
+ value = 0.523761574074074 # '12:34:13' in seconds
12
+ time_cell = Roo::Excelx::Cell::Time.new(value, nil, excelx_type, 1, nil, base_timestamp, nil)
13
+ expect(time_cell.value).to eq(12*60*60 + 34*60 + 13) # 12:34:13 in seconds
14
+ end
15
+ end
@@ -86,6 +86,14 @@ describe Roo::Excelx do
86
86
  end
87
87
  end
88
88
 
89
+ describe 'for a workbook with hidden sheets' do
90
+ let(:path) { 'test/files/hidden_sheets.xlsx' }
91
+
92
+ it 'returns the cell contents from the visible sheet' do
93
+ expect(Roo::Excelx.new(path, only_visible_sheets: true).cell('A', 1)).to eq "visible sheet 1"
94
+ end
95
+ end
96
+
89
97
  describe '#parse' do
90
98
  let(:path) { 'test/files/numeric-link.xlsx' }
91
99
 
@@ -318,6 +326,30 @@ describe Roo::Excelx do
318
326
  expect(subject.formatted_value(4, 1)).to eq '05010'
319
327
  end
320
328
  end
329
+
330
+ context 'contains US currency' do
331
+ let(:path) { 'test/files/currency-us.xlsx' }
332
+
333
+ it 'returns a zero-padded number' do
334
+ expect(subject.formatted_value(4, 1)).to eq '$20.51'
335
+ end
336
+ end
337
+
338
+ context 'contains euro currency' do
339
+ let(:path) { 'test/files/currency-euro.xlsx' }
340
+
341
+ it 'returns a zero-padded number' do
342
+ expect(subject.formatted_value(4, 1)).to eq '€20.51'
343
+ end
344
+ end
345
+
346
+ context 'contains uk currency' do
347
+ let(:path) { 'test/files/currency-uk.xlsx' }
348
+
349
+ it 'returns a zero-padded number' do
350
+ expect(subject.formatted_value(4, 1)).to eq '£20.51'
351
+ end
352
+ end
321
353
  end
322
354
 
323
355
  describe '#row' do
@@ -601,6 +633,17 @@ describe Roo::Excelx do
601
633
  end
602
634
  end
603
635
 
636
+ describe 'opening a file with filters' do
637
+ let(:path) { 'test/files/wrong_coordinates.xlsx' }
638
+ subject(:xlsx) do
639
+ Roo::Spreadsheet.open(path)
640
+ end
641
+
642
+ it 'should properly extract defined_names' do
643
+ expect(subject.sheet(0).workbook.defined_names.length).to eq(1)
644
+ end
645
+ end
646
+
604
647
  describe 'images' do
605
648
  let(:path) { 'test/files/images.xlsx' }
606
649
 
@@ -653,4 +696,4 @@ describe 'Roo::Excelx with options set' do
653
696
  end
654
697
  end
655
698
  end
656
- end
699
+ end
@@ -25,7 +25,7 @@ describe Roo::Spreadsheet do
25
25
  let(:filename) { tempfile.path }
26
26
 
27
27
  it 'loads the proper type' do
28
- expect(Roo::CSV).to receive(:new).with(filename, file_warning: :ignore).and_call_original
28
+ expect(Roo::CSV).to receive(:new).with(filename, {file_warning: :ignore}).and_call_original
29
29
  expect(Roo::Spreadsheet.open(tempfile, extension: :csv)).to be_a(Roo::CSV)
30
30
  end
31
31
  end
@@ -43,15 +43,6 @@ RSpec.describe ::Roo::Utils do
43
43
  end
44
44
  end
45
45
 
46
- context '.split_coordinate' do
47
- it "returns the expected result" do
48
- expect(described_class.split_coordinate('A1')).to eq [1, 1]
49
- expect(described_class.split_coordinate('B2')).to eq [2, 2]
50
- expect(described_class.split_coordinate('R2')).to eq [2, 18]
51
- expect(described_class.split_coordinate('AR31')).to eq [31, 18 + 26]
52
- end
53
- end
54
-
55
46
  context '.extract_coordinate' do
56
47
  it "returns the expected result" do
57
48
  expect(described_class.extract_coordinate('A1')).to eq [1, 1]
@@ -60,7 +60,7 @@ class TestAttrReaderDefault < Minitest::Test
60
60
  object.instance_variable_defined?(attr_name) ? object.instance_variable_get(attr_name) : nil
61
61
  else
62
62
  object.public_send(attr_name)
63
- end
63
+ end
64
64
 
65
65
  if expected_value
66
66
  assert_equal expected_value, value
@@ -35,6 +35,11 @@ class TestRooExcelxCellNumber < Minitest::Test
35
35
  assert_kind_of(Float, cell.value)
36
36
  end
37
37
 
38
+ def test_rounded_percent_formatted_value
39
+ cell = Roo::Excelx::Cell::Number.new '0.569999999995', nil, ['0%'], nil, nil, nil
40
+ assert_equal('57%', cell.formatted_value)
41
+ end
42
+
38
43
  def test_formats_with_negative_numbers
39
44
  [
40
45
  ['#,##0 ;(#,##0)', '(1,042)'],
@@ -66,6 +71,7 @@ class TestRooExcelxCellNumber < Minitest::Test
66
71
  ['0.000000000', '1042.000000000'],
67
72
  ['#,##0', '1,042'],
68
73
  ['#,##0.00', '1,042.00'],
74
+ ['#,##0.000', '1,042.000'],
69
75
  ['0%', '104200%'],
70
76
  ['0.00%', '104200.00%'],
71
77
  ['0.00E+00', '1.04E+03'],
@@ -74,6 +80,7 @@ class TestRooExcelxCellNumber < Minitest::Test
74
80
  ['#,##0.00;(#,##0.00)', '1,042.00'],
75
81
  ['#,##0.00;[Red](#,##0.00)', '1,042.00'],
76
82
  ['##0.0E+0', '1.0E+03'],
83
+ ["_-* #,##0.00\\ _€_-;\\-* #,##0.00\\ _€_-;_-* \"-\"??\\ _€_-;_-@_-", '1,042.00'],
77
84
  ['@', '1042']
78
85
  ].each do |style_format, result|
79
86
  cell = Roo::Excelx::Cell::Number.new '1042', nil, [style_format], nil, nil, nil
@@ -36,6 +36,27 @@ module TestAccesingFiles
36
36
  end
37
37
  end
38
38
 
39
+ def test_finalize_twice
40
+ skip if defined? JRUBY_VERSION
41
+
42
+ instance = Class.new { include Roo::Tempdir }.new
43
+
44
+ tempdir = instance.make_tempdir(instance, "my_temp_prefix", nil)
45
+ assert File.exist?(tempdir), "Expected #{tempdir} to initially exist"
46
+
47
+ pid = Process.fork do
48
+ # Inside the forked process finalize does not affect the parent process's state, but does
49
+ # delete the tempfile on disk
50
+ instance.finalize_tempdirs(instance.object_id)
51
+ end
52
+
53
+ Process.wait(pid)
54
+ refute File.exist?(tempdir), "Expected #{tempdir} to have been cleaned up by child process"
55
+
56
+ instance.finalize_tempdirs(instance.object_id)
57
+ refute File.exist?(tempdir), "Expected #{tempdir} to still have been cleaned up"
58
+ end
59
+
39
60
  def test_cleanup_on_error
40
61
  # NOTE: This test was occasionally failing because when it started running
41
62
  # other tests would have already added folders to the temp directory,
@@ -133,6 +133,20 @@ class TestRworkbookExcelx < Minitest::Test
133
133
  end
134
134
  end
135
135
 
136
+ def test_expand_merged_range_doesnt_insert_nil_values
137
+ options = { expand_merged_ranges: true }
138
+ xlsx = roo_class.new(File.join(TESTDIR, "merged_ranges.xlsx"), options)
139
+
140
+ refute_includes xlsx.sheet_for(0).cells.values, nil, "`nil` was copied into the cells hash from an empty merged range"
141
+ end
142
+
143
+ def test_expand_merged_range_doesnt_raise_issue_506
144
+ # Issue 506 sent an example test.xlsx file that would raise an error upon parsing.
145
+ xl = Roo::Spreadsheet.open(File.join(TESTDIR, "expand_merged_ranges_issue_506.xlsx"), expand_merged_ranges: true)
146
+ data = xl.parse(one: /one/i, two: /two/i, clean: true)
147
+ assert_equal [{:one=>"John", :two=>"Johnson"}, {:one=>"Sam", :two=>nil}, {:one=>"Dave", :two=>nil}], data
148
+ end
149
+
136
150
  def test_noexpand_merged_range
137
151
  xlsx = roo_class.new(File.join(TESTDIR, "merged_ranges.xlsx"))
138
152
 
@@ -320,6 +334,22 @@ class TestRworkbookExcelx < Minitest::Test
320
334
  end
321
335
  end
322
336
 
337
+ def test_parsing_xlsx_with_richtext
338
+ xlsx = roo_class.new(File.join(TESTDIR, "richtext_example.xlsx"))
339
+
340
+ assert_equal "Example richtext", xlsx.cell("a", 1)
341
+ assert_equal "Example richtext", xlsx.cell("b", 1)
342
+ end
343
+
344
+ def test_implicit_coordinates
345
+ xlsx = roo_class.new(File.join(TESTDIR, 'implicit_coordinates.xlsx'))
346
+
347
+ assert_equal 'Test', xlsx.cell('a', 1)
348
+ assert_equal 'A2', xlsx.cell('a', 2)
349
+ assert_equal 'B2', xlsx.cell(2, 2)
350
+ assert_equal 'C2', xlsx.cell('c', 2)
351
+ end
352
+
323
353
  def roo_class
324
354
  Roo::Excelx
325
355
  end
data/test/test_roo.rb CHANGED
@@ -274,6 +274,24 @@ class TestRoo < Minitest::Test
274
274
  end
275
275
  end
276
276
 
277
+ def test_cell_boolean_from_google_sheets
278
+ with_each_spreadsheet(:name=>'boolean-from-google-sheets', :format=>[:openoffice, :excelx]) do |oo|
279
+ if oo.class == Roo::Excelx
280
+ assert_equal true, oo.cell(1, 1), "failure in #{oo.class}"
281
+ assert_equal false, oo.cell(2, 1), "failure in #{oo.class}"
282
+
283
+ cell = oo.sheet_for(oo.default_sheet).cells[[1, 1,]]
284
+ assert_equal 'TRUE', cell.formatted_value
285
+
286
+ cell = oo.sheet_for(oo.default_sheet).cells[[2, 1,]]
287
+ assert_equal 'FALSE', cell.formatted_value
288
+ else
289
+ assert_equal "true", oo.cell(1,1), "failure in #{oo.class}"
290
+ assert_equal "false", oo.cell(2,1), "failure in #{oo.class}"
291
+ end
292
+ end
293
+ end
294
+
277
295
  def test_cell_multiline
278
296
  with_each_spreadsheet(:name=>'paragraph', :format=>[:openoffice, :excelx]) do |oo|
279
297
  assert_equal "This is a test\nof a multiline\nCell", oo.cell(1,1)
@@ -282,6 +300,18 @@ class TestRoo < Minitest::Test
282
300
  end
283
301
  end
284
302
 
303
+ def test_apostrophe_replacement
304
+ with_each_spreadsheet(:name=>'apostrophe', :format=>[:openoffice]) do |oo|
305
+ assert_equal "'", oo.cell(1,1)
306
+ end
307
+ end
308
+
309
+ def test_frozen_string_usage
310
+ with_each_spreadsheet(:name=>'frozen_string', :format=>[:openoffice]) do |oo|
311
+ assert_equal "", oo.cell(1,1)
312
+ end
313
+ end
314
+
285
315
  def test_row_whitespace
286
316
  # auf dieses Dokument habe ich keinen Zugriff TODO:
287
317
  # TODO: No access to document whitespace?
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: roo
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.8.3
4
+ version: 2.10.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Thomas Preymesser
@@ -10,10 +10,10 @@ authors:
10
10
  - Oleksandr Simonov
11
11
  - Steven Daniels
12
12
  - Anmol Chopra
13
- autorequire:
13
+ autorequire:
14
14
  bindir: bin
15
15
  cert_chain: []
16
- date: 2020-02-03 00:00:00.000000000 Z
16
+ date: 2024-01-18 00:00:00.000000000 Z
17
17
  dependencies:
18
18
  - !ruby/object:Gem::Dependency
19
19
  name: nokogiri
@@ -53,16 +53,16 @@ dependencies:
53
53
  name: rake
54
54
  requirement: !ruby/object:Gem::Requirement
55
55
  requirements:
56
- - - "~>"
56
+ - - ">="
57
57
  - !ruby/object:Gem::Version
58
- version: '10.1'
58
+ version: '0'
59
59
  type: :development
60
60
  prerelease: false
61
61
  version_requirements: !ruby/object:Gem::Requirement
62
62
  requirements:
63
- - - "~>"
63
+ - - ">="
64
64
  - !ruby/object:Gem::Version
65
- version: '10.1'
65
+ version: '0'
66
66
  - !ruby/object:Gem::Dependency
67
67
  name: minitest
68
68
  requirement: !ruby/object:Gem::Requirement
@@ -103,6 +103,20 @@ dependencies:
103
103
  - - "<"
104
104
  - !ruby/object:Gem::Version
105
105
  version: 2.0.0
106
+ - !ruby/object:Gem::Dependency
107
+ name: matrix
108
+ requirement: !ruby/object:Gem::Requirement
109
+ requirements:
110
+ - - ">="
111
+ - !ruby/object:Gem::Version
112
+ version: '0'
113
+ type: :development
114
+ prerelease: false
115
+ version_requirements: !ruby/object:Gem::Requirement
116
+ requirements:
117
+ - - ">="
118
+ - !ruby/object:Gem::Version
119
+ version: '0'
106
120
  description: |-
107
121
  Roo can access the contents of various spreadsheet files. It can handle
108
122
  * OpenOffice
@@ -119,10 +133,11 @@ files:
119
133
  - ".codeclimate.yml"
120
134
  - ".github/issue_template.md"
121
135
  - ".github/pull_request_template.md"
136
+ - ".github/workflows/pull-request.yml"
137
+ - ".github/workflows/ruby.yml"
122
138
  - ".gitignore"
123
139
  - ".rubocop.yml"
124
140
  - ".simplecov"
125
- - ".travis.yml"
126
141
  - CHANGELOG.md
127
142
  - Gemfile
128
143
  - Guardfile
@@ -181,6 +196,7 @@ files:
181
196
  - spec/helpers.rb
182
197
  - spec/lib/roo/base_spec.rb
183
198
  - spec/lib/roo/csv_spec.rb
199
+ - spec/lib/roo/excelx/cell/time_spec.rb
184
200
  - spec/lib/roo/excelx/format_spec.rb
185
201
  - spec/lib/roo/excelx/relationships_spec.rb
186
202
  - spec/lib/roo/excelx/sheet_doc_spec.rb
@@ -225,7 +241,7 @@ homepage: https://github.com/roo-rb/roo
225
241
  licenses:
226
242
  - MIT
227
243
  metadata: {}
228
- post_install_message:
244
+ post_install_message:
229
245
  rdoc_options: []
230
246
  require_paths:
231
247
  - lib
@@ -233,15 +249,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
233
249
  requirements:
234
250
  - - ">="
235
251
  - !ruby/object:Gem::Version
236
- version: 2.3.0
252
+ version: 2.7.0
237
253
  required_rubygems_version: !ruby/object:Gem::Requirement
238
254
  requirements:
239
255
  - - ">="
240
256
  - !ruby/object:Gem::Version
241
257
  version: '0'
242
258
  requirements: []
243
- rubygems_version: 3.0.6
244
- signing_key:
259
+ rubygems_version: 3.3.26
260
+ signing_key:
245
261
  specification_version: 4
246
262
  summary: Roo can access the contents of various spreadsheet files.
247
263
  test_files: []
data/.travis.yml DELETED
@@ -1,22 +0,0 @@
1
- language: ruby
2
- rvm:
3
- - 2.3
4
- - 2.4
5
- - 2.5
6
- - 2.6
7
- - ruby-head
8
- - jruby-9.1.6.0
9
- env:
10
- - LONG_RUN=true
11
- matrix:
12
- include:
13
- - rvm: 2.6
14
- env: RUBYOPT=--jit LONG_RUN=true
15
- - rvm: ruby-head
16
- env: RUBYOPT=--jit LONG_RUN=true
17
- allow_failures:
18
- - rvm: ruby-head
19
- - rvm: ruby-head
20
- env: RUBYOPT=--jit LONG_RUN=true
21
- - rvm: jruby-9.1.6.0
22
- bundler_args: --without local_development