roo 2.8.1 → 2.9.0

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: 62db88823f6fadaa7e33bbc16a68f3ed67f85d7676cbc51340d48c6867622187
4
- data.tar.gz: ada9c23708c44a4c97e145918022170fd425f0a651e56b1748be2c258f41fe26
3
+ metadata.gz: ae372e5e7ed6c045de6911c323a805fa4f60c68c2e8cd96358db03a01ff3beef
4
+ data.tar.gz: 6a2f09854519bd5d1b233aafcd1cf58d29343dc48e3cc735498ea2696785fd24
5
5
  SHA512:
6
- metadata.gz: ebb7cc89869d46e4bbf36166c00d86462878e1c54676a1439537f1499ed4c388254e4c1b89197487a96e3905f89cf601e6e3b8f7c0db977844b478de8938d58a
7
- data.tar.gz: 885465f444f35ffd2ce68e0963d87ab1457234df91c16012d60d58f38f0eda6ad0822867fe44623921f8c0184465c1477f4484c80183b692f0106ecc235cd8e1
6
+ metadata.gz: 5dae710a3f90d9d7038b0c4813ac0b400e6c587c2cf00be9239e27294f60046b2748b090207fce2ebaa3139a3adc681d0063bec9c1e6ee7c91cde51de1ae5861
7
+ data.tar.gz: 34691a95ee37cf9af6640c2090d2542db2a84558829a42b4ecab13870e2d9e73ee1d0c243cb01d536d5709158cb746bf69b1e0d0a0888226f5906bf41566e542
@@ -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,5 +1,31 @@
1
1
  ## Unreleased
2
2
 
3
+ ## [2.9.0] 2022-03-19
4
+
5
+ ### Changed/Added
6
+ - Ruby 3.x Support [555](https://github.com/roo-rb/roo/pull/555)
7
+ - Ignore all richdata at 'xl/richData' of XSLX [552](https://github.com/roo-rb/roo/pull/552)
8
+ - Only copy if cell is present when `expand_merged_ranges: true` [557](https://github.com/roo-rb/roo/pull/557)
9
+ - Fixes issue where the contents of hidden sheet was returned when parsing visible sheets only. [536](https://github.com/roo-rb/roo/pull/536)
10
+ - Add formats [525](https://github.com/roo-rb/roo/pull/525)
11
+ - Fix warnings caused by Ruby 2.7 update [530](https://github.com/roo-rb/roo/pull/530)
12
+ - Add formats [525](https://github.com/roo-rb/roo/pull/525)
13
+
14
+ ### Removed
15
+ - Support for ruby 2.4, 2.5, 2.6(excluded jRuby)
16
+
17
+ ## [2.8.3] 2020-02-03
18
+ ### Changed/Added
19
+ - Updated rubyzip version. Now minimal version is 1.3.0 [515](https://github.com/roo-rb/roo/pull/515) - [CVE-2019-16892](https://github.com/rubyzip/rubyzip/pull/403)
20
+
21
+ ## [2.8.2] 2019-02-01
22
+ ### Changed/Added
23
+ - Support range cell for Excelx's links [490](https://github.com/roo-rb/roo/pull/490)
24
+ - Skip `extract_hyperlinks` if not required [488](https://github.com/roo-rb/roo/pull/488)
25
+
26
+ ### Fixed
27
+ - Fixed error for invalid link [492](https://github.com/roo-rb/roo/pull/492)
28
+
3
29
  ## [2.8.1] 2019-01-21
4
30
  ### Fixed
5
31
  - Fixed error if excelx's cell have empty children [487](https://github.com/roo-rb/roo/pull/487)
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,7 +18,7 @@ Install as a gem
18
18
  Or add it to your Gemfile
19
19
 
20
20
  ```ruby
21
- gem "roo", "~> 2.7.0"
21
+ gem "roo", "~> 2.8.0"
22
22
  ```
23
23
  ## Usage
24
24
 
@@ -249,7 +249,7 @@ ods.formula('A', 2)
249
249
  csv = Roo::CSV.new("mycsv.csv")
250
250
  ```
251
251
 
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.
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.
253
253
 
254
254
  For instance, you can load tab-delimited files (``.tsv``), and you can use a particular encoding when opening the file.
255
255
 
@@ -262,6 +262,18 @@ csv = Roo::CSV.new("mytsv.tsv", csv_options: {col_sep: "\t"})
262
262
  csv = Roo::CSV.new("mycsv.csv", csv_options: {encoding: Encoding::ISO_8859_1})
263
263
  ```
264
264
 
265
+ 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).
266
+
267
+ ```ruby
268
+ # Load a spreadsheet from a file path
269
+ # Roo figures out the right parser based on file extension
270
+ spreadsheet = Roo::Spreadsheet.open(csv_or_xlsx_file)
271
+
272
+ # Load a csv and auto-strip the BOM (byte order mark)
273
+ # csv files saved from MS Excel typically have the BOM marker at the beginning of the file
274
+ spreadsheet = Roo::Spreadsheet.open("mycsv.csv", { csv_options: { encoding: 'bom|utf-8' } })
275
+ ```
276
+
265
277
  ## Upgrading from Roo 1.13.x
266
278
  If you use ``.xls`` or Google spreadsheets, you will need to install ``roo-xls`` or ``roo-google`` to continue using that functionality.
267
279
 
@@ -271,7 +283,7 @@ Roo's public methods have stayed relatively consistent between 1.13.x and 2.0.0,
271
283
 
272
284
  ## Contributing
273
285
  ### Features
274
- 1. Fork it ( https://github.com/[my-github-username]/roo/fork )
286
+ 1. Fork it ( https://github.com/roo-rb/roo/fork )
275
287
  2. Install it (`bundle install --with local_development`)
276
288
  3. Create your feature branch (`git checkout -b my-new-feature`)
277
289
  4. Commit your changes (`git commit -am 'My new feature'`)
data/lib/roo/base.rb CHANGED
@@ -544,7 +544,7 @@ class Roo::Base
544
544
  tempfilename = File.join(tmpdir, find_basename(uri))
545
545
  begin
546
546
  File.open(tempfilename, "wb") do |file|
547
- open(uri, "User-Agent" => "Ruby/#{RUBY_VERSION}") do |net|
547
+ URI.open(uri, "User-Agent" => "Ruby/#{RUBY_VERSION}") do |net|
548
548
  file.write(net.read)
549
549
  end
550
550
  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,7 +48,7 @@ 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
54
  Kernel.format('%d%%', number.to_f * 100)
@@ -64,6 +64,7 @@ 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 }
68
69
  else
69
70
  raise "Unknown format: #{format.inspect}"
@@ -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
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'roo/excelx/extractor'
2
4
 
3
5
  module Roo
@@ -11,10 +13,16 @@ module Roo
11
13
  @relationships ||= extract_relationships
12
14
  end
13
15
 
16
+ def include_type?(type)
17
+ to_a.any? do |_, rel|
18
+ rel["Type"]&.include? type
19
+ end
20
+ end
21
+
14
22
  private
15
23
 
16
24
  def extract_relationships
17
- return [] unless doc_exists?
25
+ return {} unless doc_exists?
18
26
 
19
27
  doc.xpath('/Relationships/Relationship').each_with_object({}) do |rel, hash|
20
28
  hash[rel['Id']] = rel
@@ -22,7 +22,7 @@ module Roo
22
22
 
23
23
  def hyperlinks(relationships)
24
24
  # If you're sure you're not going to need this hyperlinks you can discard it
25
- @hyperlinks ||= if @options[:no_hyperlinks]
25
+ @hyperlinks ||= if @options[:no_hyperlinks] || !relationships.include_type?("hyperlink")
26
26
  {}
27
27
  else
28
28
  extract_hyperlinks(relationships)
@@ -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
@@ -185,7 +180,10 @@ module Roo
185
180
  if relationship = relationships[hyperlink['id']]
186
181
  target_link = relationship['Target']
187
182
  target_link += "##{hyperlink['location']}" if hyperlink['location']
188
- hash[::Roo::Utils.ref_to_key(hyperlink["ref"].to_s)] = target_link
183
+
184
+ Roo::Utils.coordinates_in_range(hyperlink["ref"].to_s) do |coord|
185
+ hash[coord] = target_link
186
+ end
189
187
  end
190
188
  end
191
189
  end
@@ -194,11 +192,12 @@ module Roo
194
192
  # Extract merged ranges from xml
195
193
  merges = {}
196
194
  doc.xpath('/worksheet/mergeCells/mergeCell').each do |mergecell_xml|
197
- tl, br = mergecell_xml["ref"].split(/:/).map { |ref| ::Roo::Utils.ref_to_key(ref) }
198
- for row in tl[0]..br[0] do
199
- for col in tl[1]..br[1] do
200
- next if row == tl[0] && col == tl[1]
201
- 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
202
201
  end
203
202
  end
204
203
  end
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$/
@@ -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/utils.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Roo
2
4
  module Utils
3
5
  extend self
@@ -41,7 +43,7 @@ module Roo
41
43
 
42
44
  # convert a number to something like 'AB' (1 => 'A', 2 => 'B', ...)
43
45
  def number_to_letter(num)
44
- result = ""
46
+ result = +""
45
47
 
46
48
  until num.zero?
47
49
  num, index = (num - 1).divmod(26)
@@ -73,6 +75,25 @@ module Roo
73
75
  (x2 - (x1 - 1)) * (y2 - (y1 - 1))
74
76
  end
75
77
 
78
+ def coordinates_in_range(str)
79
+ return to_enum(:coordinates_in_range, str) unless block_given?
80
+ coordinates = str.split(":", 2).map! { |s| extract_coordinate s }
81
+
82
+ case coordinates.size
83
+ when 1
84
+ yield coordinates[0]
85
+ when 2
86
+ tl, br = coordinates
87
+ rows = tl.row..br.row
88
+ cols = tl.column..br.column
89
+ rows.each do |row|
90
+ cols.each do |column|
91
+ yield Excelx::Coordinate.new(row, column)
92
+ end
93
+ end
94
+ end
95
+ end
96
+
76
97
  def load_xml(path)
77
98
  ::File.open(path, 'rb') do |file|
78
99
  ::Nokogiri::XML(file)
data/lib/roo/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Roo
2
- VERSION = "2.8.1"
2
+ VERSION = "2.9.0"
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
- spec.add_dependency 'rubyzip', '>= 1.2.1', '< 2.0.0'
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
@@ -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
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "spec_helper"
4
+
5
+ describe Roo::Excelx::Relationships do
6
+ subject(:relationships) { Roo::Excelx::Relationships.new Roo::Excelx.new(path).rels_files[0] }
7
+
8
+ describe "#include_type?" do
9
+ [
10
+ ["with hyperlink type", "test/files/link.xlsx", true, false],
11
+ ["with nil path", "test/files/Bibelbund.xlsx", false, false],
12
+ ["with comments type", "test/files/comments-google.xlsx", false, true],
13
+ ].each do |context_desc, file_path, hyperlink_value, comments_value|
14
+ context context_desc do
15
+ let(:path) { file_path }
16
+
17
+ it "should return #{hyperlink_value} for hyperlink" do
18
+ expect(subject.include_type?("hyperlink")).to be hyperlink_value
19
+ end
20
+
21
+ it "should return #{hyperlink_value} for link" do
22
+ expect(subject.include_type?("link")).to be hyperlink_value
23
+ end
24
+
25
+ it "should return false for hypelink" do
26
+ expect(subject.include_type?("hypelink")).to be false
27
+ end
28
+
29
+ it "should return false for coment" do
30
+ expect(subject.include_type?("coment")).to be false
31
+ end
32
+
33
+ it "should return #{comments_value} for comments" do
34
+ expect(subject.include_type?("comments")).to be comments_value
35
+ end
36
+
37
+ it "should return #{comments_value} for comment" do
38
+ expect(subject.include_type?("comment")).to be comments_value
39
+ end
40
+ end
41
+ end
42
+ end
43
+ 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
 
@@ -379,9 +387,34 @@ describe Roo::Excelx do
379
387
  expect(subject.hyperlink?(1, 1)).to eq true
380
388
  expect(subject.hyperlink?(1, 2)).to eq false
381
389
  end
390
+
391
+ context 'defined on cell range' do
392
+ let(:path) { 'test/files/cell-range-link.xlsx' }
393
+
394
+ it 'returns the expected result' do
395
+ [[false]*3, *[[true, true, false]]*4, [false]*3].each.with_index(1) do |row, row_index|
396
+ row.each.with_index(1) do |value, col_index|
397
+ expect(subject.hyperlink?(row_index, col_index)).to eq(value)
398
+ end
399
+ end
400
+ end
401
+ end
382
402
  end
383
403
 
384
404
  describe '#hyperlink' do
405
+ context 'defined on cell range' do
406
+ let(:path) { 'test/files/cell-range-link.xlsx' }
407
+
408
+ it 'returns the expected result' do
409
+ link = "http://www.google.com"
410
+ [[nil]*3, *[[link, link, nil]]*4, [nil]*3].each.with_index(1) do |row, row_index|
411
+ row.each.with_index(1) do |value, col_index|
412
+ expect(subject.hyperlink(row_index, col_index)).to eq(value)
413
+ end
414
+ end
415
+ end
416
+ end
417
+
385
418
  context 'without location' do
386
419
  let(:path) { 'test/files/link.xlsx' }
387
420
 
@@ -628,4 +661,4 @@ describe 'Roo::Excelx with options set' do
628
661
  end
629
662
  end
630
663
  end
631
- end
664
+ 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]
@@ -90,6 +81,19 @@ RSpec.describe ::Roo::Utils do
90
81
  end
91
82
  end
92
83
 
84
+ context '.coordinates_in_range' do
85
+ it "returns the expected result" do
86
+ expect(described_class.coordinates_in_range('').to_a).to eq []
87
+ expect(described_class.coordinates_in_range('B2').to_a).to eq [[2, 2]]
88
+ expect(described_class.coordinates_in_range('D2:G3').to_a).to eq [[2, 4], [2, 5], [2, 6], [2, 7], [3, 4], [3, 5], [3, 6], [3, 7]]
89
+ expect(described_class.coordinates_in_range('G3:D2').to_a).to eq []
90
+ end
91
+
92
+ it "raises an error when appropriate" do
93
+ expect { described_class.coordinates_in_range('D2:G3:I5').to_a }.to raise_error(ArgumentError)
94
+ end
95
+ end
96
+
93
97
  context '.load_xml' do
94
98
  it 'returns the expected result' do
95
99
  expect(described_class.load_xml('test/files/sheet1.xml')).to be_a(Nokogiri::XML::Document)
@@ -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
@@ -66,6 +66,7 @@ class TestRooExcelxCellNumber < Minitest::Test
66
66
  ['0.000000000', '1042.000000000'],
67
67
  ['#,##0', '1,042'],
68
68
  ['#,##0.00', '1,042.00'],
69
+ ['#,##0.000', '1,042.000'],
69
70
  ['0%', '104200%'],
70
71
  ['0.00%', '104200.00%'],
71
72
  ['0.00E+00', '1.04E+03'],
@@ -74,6 +75,7 @@ class TestRooExcelxCellNumber < Minitest::Test
74
75
  ['#,##0.00;(#,##0.00)', '1,042.00'],
75
76
  ['#,##0.00;[Red](#,##0.00)', '1,042.00'],
76
77
  ['##0.0E+0', '1.0E+03'],
78
+ ["_-* #,##0.00\\ _€_-;\\-* #,##0.00\\ _€_-;_-* \"-\"??\\ _€_-;_-@_-", '1,042.00'],
77
79
  ['@', '1042']
78
80
  ].each do |style_format, result|
79
81
  cell = Roo::Excelx::Cell::Number.new '1042', nil, [style_format], nil, nil, nil
@@ -3,7 +3,7 @@ require "test_helper"
3
3
  class TestRworkbookExcelx < Minitest::Test
4
4
  def test_download_uri_with_invalid_host
5
5
  assert_raises(RuntimeError) do
6
- Roo::Excelx.new("http://example.com/file.xlsx")
6
+ Roo::Excelx.new("http://examples.com/file.xlsx")
7
7
  end
8
8
  end
9
9
 
@@ -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
 
@@ -302,6 +316,11 @@ class TestRworkbookExcelx < Minitest::Test
302
316
  end
303
317
  end
304
318
 
319
+ def test_handles_link_without_hyperlink
320
+ workbook = Roo::Spreadsheet.open(File.join(TESTDIR, "bad_link.xlsx"))
321
+ assert_equal "Test", workbook.cell(1, 1)
322
+ end
323
+
305
324
  # Excel has two base date formats one from 1900 and the other from 1904.
306
325
  # see #test_base_dates_in_excel
307
326
  def test_base_dates_in_excelx
@@ -315,6 +334,13 @@ class TestRworkbookExcelx < Minitest::Test
315
334
  end
316
335
  end
317
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
+
318
344
  def roo_class
319
345
  Roo::Excelx
320
346
  end
@@ -15,7 +15,7 @@ class TestRooOpenOffice < Minitest::Test
15
15
 
16
16
  def test_download_uri_with_invalid_host
17
17
  assert_raises(RuntimeError) do
18
- roo_class.new("http://example.com/file.ods")
18
+ roo_class.new("http://examples.com/file.ods")
19
19
  end
20
20
  end
21
21
 
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.1
4
+ version: 2.9.0
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: 2019-01-21 00:00:00.000000000 Z
16
+ date: 2022-03-19 00:00:00.000000000 Z
17
17
  dependencies:
18
18
  - !ruby/object:Gem::Dependency
19
19
  name: nokogiri
@@ -35,34 +35,34 @@ dependencies:
35
35
  requirements:
36
36
  - - ">="
37
37
  - !ruby/object:Gem::Version
38
- version: 1.2.1
38
+ version: 1.3.0
39
39
  - - "<"
40
40
  - !ruby/object:Gem::Version
41
- version: 2.0.0
41
+ version: 3.0.0
42
42
  type: :runtime
43
43
  prerelease: false
44
44
  version_requirements: !ruby/object:Gem::Requirement
45
45
  requirements:
46
46
  - - ">="
47
47
  - !ruby/object:Gem::Version
48
- version: 1.2.1
48
+ version: 1.3.0
49
49
  - - "<"
50
50
  - !ruby/object:Gem::Version
51
- version: 2.0.0
51
+ version: 3.0.0
52
52
  - !ruby/object:Gem::Dependency
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,10 @@ files:
119
133
  - ".codeclimate.yml"
120
134
  - ".github/issue_template.md"
121
135
  - ".github/pull_request_template.md"
136
+ - ".github/workflows/ruby.yml"
122
137
  - ".gitignore"
123
138
  - ".rubocop.yml"
124
139
  - ".simplecov"
125
- - ".travis.yml"
126
140
  - CHANGELOG.md
127
141
  - Gemfile
128
142
  - Guardfile
@@ -181,7 +195,9 @@ files:
181
195
  - spec/helpers.rb
182
196
  - spec/lib/roo/base_spec.rb
183
197
  - spec/lib/roo/csv_spec.rb
198
+ - spec/lib/roo/excelx/cell/time_spec.rb
184
199
  - spec/lib/roo/excelx/format_spec.rb
200
+ - spec/lib/roo/excelx/relationships_spec.rb
185
201
  - spec/lib/roo/excelx/sheet_doc_spec.rb
186
202
  - spec/lib/roo/excelx_spec.rb
187
203
  - spec/lib/roo/libreoffice_spec.rb
@@ -224,7 +240,7 @@ homepage: https://github.com/roo-rb/roo
224
240
  licenses:
225
241
  - MIT
226
242
  metadata: {}
227
- post_install_message:
243
+ post_install_message:
228
244
  rdoc_options: []
229
245
  require_paths:
230
246
  - lib
@@ -232,16 +248,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
232
248
  requirements:
233
249
  - - ">="
234
250
  - !ruby/object:Gem::Version
235
- version: 2.3.0
251
+ version: 2.7.0
236
252
  required_rubygems_version: !ruby/object:Gem::Requirement
237
253
  requirements:
238
254
  - - ">="
239
255
  - !ruby/object:Gem::Version
240
256
  version: '0'
241
257
  requirements: []
242
- rubyforge_project:
243
- rubygems_version: 2.7.7
244
- signing_key:
258
+ rubygems_version: 3.3.3
259
+ signing_key:
245
260
  specification_version: 4
246
261
  summary: Roo can access the contents of various spreadsheet files.
247
262
  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