roo 2.7.1 → 2.8.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (64) hide show
  1. checksums.yaml +5 -5
  2. data/.github/issue_template.md +16 -0
  3. data/.github/pull_request_template.md +14 -0
  4. data/.rubocop.yml +186 -0
  5. data/.travis.yml +12 -7
  6. data/CHANGELOG.md +31 -2
  7. data/LICENSE +2 -0
  8. data/README.md +25 -12
  9. data/lib/roo.rb +4 -1
  10. data/lib/roo/base.rb +65 -56
  11. data/lib/roo/constants.rb +5 -3
  12. data/lib/roo/csv.rb +20 -12
  13. data/lib/roo/excelx.rb +42 -16
  14. data/lib/roo/excelx/cell.rb +10 -6
  15. data/lib/roo/excelx/cell/base.rb +26 -12
  16. data/lib/roo/excelx/cell/boolean.rb +9 -6
  17. data/lib/roo/excelx/cell/date.rb +7 -7
  18. data/lib/roo/excelx/cell/datetime.rb +14 -18
  19. data/lib/roo/excelx/cell/empty.rb +3 -2
  20. data/lib/roo/excelx/cell/number.rb +35 -34
  21. data/lib/roo/excelx/cell/string.rb +3 -3
  22. data/lib/roo/excelx/cell/time.rb +4 -3
  23. data/lib/roo/excelx/comments.rb +3 -3
  24. data/lib/roo/excelx/coordinate.rb +11 -4
  25. data/lib/roo/excelx/extractor.rb +21 -3
  26. data/lib/roo/excelx/format.rb +38 -31
  27. data/lib/roo/excelx/images.rb +26 -0
  28. data/lib/roo/excelx/relationships.rb +3 -3
  29. data/lib/roo/excelx/shared.rb +10 -3
  30. data/lib/roo/excelx/shared_strings.rb +9 -15
  31. data/lib/roo/excelx/sheet.rb +49 -10
  32. data/lib/roo/excelx/sheet_doc.rb +86 -48
  33. data/lib/roo/excelx/styles.rb +3 -3
  34. data/lib/roo/excelx/workbook.rb +7 -3
  35. data/lib/roo/helpers/default_attr_reader.rb +20 -0
  36. data/lib/roo/helpers/weak_instance_cache.rb +41 -0
  37. data/lib/roo/open_office.rb +8 -6
  38. data/lib/roo/spreadsheet.rb +1 -1
  39. data/lib/roo/utils.rb +48 -19
  40. data/lib/roo/version.rb +1 -1
  41. data/roo.gemspec +13 -11
  42. data/spec/lib/roo/base_spec.rb +45 -3
  43. data/spec/lib/roo/excelx_spec.rb +125 -31
  44. data/spec/lib/roo/strict_spec.rb +43 -0
  45. data/spec/lib/roo/utils_spec.rb +12 -3
  46. data/spec/lib/roo/weak_instance_cache_spec.rb +92 -0
  47. data/spec/lib/roo_spec.rb +0 -0
  48. data/test/excelx/cell/test_attr_reader_default.rb +72 -0
  49. data/test/excelx/cell/test_base.rb +5 -0
  50. data/test/excelx/cell/test_datetime.rb +6 -6
  51. data/test/excelx/cell/test_empty.rb +11 -0
  52. data/test/excelx/cell/test_number.rb +9 -0
  53. data/test/excelx/cell/test_string.rb +20 -0
  54. data/test/excelx/cell/test_time.rb +4 -4
  55. data/test/excelx/test_coordinate.rb +51 -0
  56. data/test/formatters/test_csv.rb +17 -0
  57. data/test/formatters/test_xml.rb +4 -4
  58. data/test/roo/test_base.rb +2 -2
  59. data/test/roo/test_csv.rb +28 -0
  60. data/test/test_helper.rb +13 -0
  61. data/test/test_roo.rb +7 -7
  62. metadata +21 -11
  63. data/.github/ISSUE_TEMPLATE +0 -10
  64. data/Gemfile_ruby2 +0 -30
@@ -0,0 +1,92 @@
1
+ require 'spec_helper'
2
+
3
+ if RUBY_PLATFORM == "java"
4
+ require 'java'
5
+ java_import 'java.lang.System'
6
+ end
7
+
8
+ describe Roo::Helpers::WeakInstanceCache do
9
+ let(:klass) do
10
+ Class.new do
11
+ include Roo::Helpers::WeakInstanceCache
12
+
13
+ def memoized_data
14
+ instance_cache(:@memoized_data) do
15
+ "Some Costly Operation #{rand(1000)}" * 1_000
16
+ end
17
+ end
18
+ end
19
+ end
20
+
21
+ subject do
22
+ klass.new
23
+ end
24
+
25
+ it 'should be lazy' do
26
+ expect(subject.instance_variables).to_not include(:@memoized_data)
27
+ data = subject.memoized_data
28
+ expect(subject.instance_variables).to include(:@memoized_data)
29
+ end
30
+
31
+
32
+ it 'should be memoized' do
33
+ data = subject.memoized_data
34
+ expect(subject.memoized_data).to equal(data)
35
+ end
36
+
37
+ it 'should recalculate after GC' do
38
+ expect(subject.instance_variables).to_not include(:@memoized_data)
39
+ GC.disable
40
+ subject.memoized_data && nil
41
+ expect(subject.instance_variables).to include(:@memoized_data)
42
+
43
+ force_gc
44
+ expect(subject.instance_variables).to_not include(:@memoized_data)
45
+ GC.disable
46
+ subject.memoized_data && nil
47
+ expect(subject.instance_variables).to include(:@memoized_data)
48
+ end
49
+
50
+ it 'must remove instance variable' do
51
+ expect(subject.instance_variables).to_not include(:@memoized_data)
52
+ GC.disable
53
+ subject.memoized_data && nil
54
+ expect(subject.instance_variables).to include(:@memoized_data)
55
+
56
+ force_gc
57
+ expect(subject.instance_variables).to_not include(:@memoized_data)
58
+ end
59
+
60
+ context '#inspect must not raise' do
61
+ it 'before calculation' do
62
+ expect{subject.inspect}.to_not raise_error
63
+ end
64
+ it 'after calculation' do
65
+ GC.disable
66
+ subject.memoized_data && nil
67
+ expect{subject.inspect}.to_not raise_error
68
+ expect(subject.inspect).to include("Some Costly Operation")
69
+ force_gc
70
+ end
71
+ it 'after GC' do
72
+ subject.memoized_data && nil
73
+ force_gc
74
+ expect(subject.instance_variables).to_not include(:@memoized_data)
75
+ expect{subject.inspect}.to_not raise_error
76
+ expect(subject.inspect).to_not include("Some Costly Operation")
77
+ end
78
+ end
79
+
80
+ if RUBY_PLATFORM == "java"
81
+ def force_gc
82
+ System.gc
83
+ sleep(0.1)
84
+ end
85
+ else
86
+ def force_gc
87
+ GC.start(full_mark: true, immediate_sweep: true)
88
+ sleep(0.1)
89
+ GC.start(full_mark: true, immediate_sweep: true)
90
+ end
91
+ end
92
+ end
File without changes
@@ -0,0 +1,72 @@
1
+ require "test_helper"
2
+
3
+ class TestAttrReaderDefault < Minitest::Test
4
+ def base
5
+ Roo::Excelx::Cell::Base
6
+ end
7
+
8
+ def boolean
9
+ Roo::Excelx::Cell::Boolean
10
+ end
11
+
12
+ def class_date
13
+ Roo::Excelx::Cell::Date
14
+ end
15
+
16
+ def datetime
17
+ Roo::Excelx::Cell::DateTime
18
+ end
19
+
20
+ def empty
21
+ Roo::Excelx::Cell::Empty
22
+ end
23
+
24
+ def number
25
+ Roo::Excelx::Cell::Number
26
+ end
27
+
28
+ def string
29
+ Roo::Excelx::Cell::String
30
+ end
31
+
32
+ def base_date
33
+ ::Date.new(1899, 12, 30)
34
+ end
35
+
36
+ def base_timestamp
37
+ ::Date.new(1899, 12, 30).to_datetime.to_time.to_i
38
+ end
39
+
40
+ def class_time
41
+ Roo::Excelx::Cell::Time
42
+ end
43
+
44
+ def test_cell_default_values
45
+ assert_values base.new(nil, nil, [], 1, nil, nil), default_type: :base, :@default_type => nil, style: 1, :@style => nil
46
+ assert_values boolean.new("1", nil, nil, nil, nil), default_type: :boolean, :@default_type => nil, cell_type: :boolean, :@cell_type => nil
47
+ assert_values class_date.new("41791", nil, [:numeric_or_formula, "mm-dd-yy"], 6, nil, base_date, nil), default_type: :date, :@default_type => nil
48
+ assert_values class_time.new("0.521", nil, [:numeric_or_formula, "hh:mm"], 6, nil, base_timestamp, nil), default_type: :time, :@default_type => nil
49
+ assert_values datetime.new("41791.521", nil, [:numeric_or_formula, "mm-dd-yy hh:mm"], 6, nil, base_timestamp, nil), default_type: :datetime, :@default_type => nil
50
+ assert_values empty.new(nil), default_type: nil, :@default_type => nil, style: nil, :@style => nil
51
+ assert_values number.new("42", nil, ["0"], nil, nil, nil), default_type: :float, :@default_type => nil
52
+ assert_values string.new("1", nil, nil, nil, nil), default_type: :string, :@default_type => nil, cell_type: :string, :@cell_type => nil
53
+
54
+ assert_values base.new(nil, nil, [], 2, nil, nil), style: 2, :@style => 2
55
+ end
56
+
57
+ def assert_values(object, value_hash)
58
+ value_hash.each do |attr_name, expected_value|
59
+ value = if attr_name.to_s.include?("@")
60
+ object.instance_variable_defined?(attr_name) ? object.instance_variable_get(attr_name) : nil
61
+ else
62
+ object.public_send(attr_name)
63
+ end
64
+
65
+ if expected_value
66
+ assert_equal expected_value, value
67
+ else
68
+ assert_nil value
69
+ end
70
+ end
71
+ end
72
+ end
@@ -25,6 +25,11 @@ class TestRooExcelxCellBase < Minitest::Test
25
25
  refute cell.empty?
26
26
  end
27
27
 
28
+ def test_presence
29
+ cell = base.new(value, nil, [], nil, nil, nil)
30
+ assert_equal cell, cell.presence
31
+ end
32
+
28
33
  def test_cell_type_is_formula
29
34
  formula = true
30
35
  cell = base.new(value, formula, [], nil, nil, nil)
@@ -2,12 +2,12 @@ require 'test_helper'
2
2
 
3
3
  class TestRooExcelxCellDateTime < Minitest::Test
4
4
  def test_cell_value_is_datetime
5
- cell = datetime.new('30000.323212', nil, ['mm-dd-yy'], nil, nil, base_date, nil)
5
+ cell = datetime.new('30000.323212', nil, ['mm-dd-yy'], nil, nil, base_timestamp, nil)
6
6
  assert_kind_of ::DateTime, cell.value
7
7
  end
8
8
 
9
9
  def test_cell_type_is_datetime
10
- cell = datetime.new('30000.323212', nil, [], nil, nil, base_date, nil)
10
+ cell = datetime.new('30000.323212', nil, [], nil, nil, base_timestamp, nil)
11
11
  assert_equal :datetime, cell.type
12
12
  end
13
13
 
@@ -19,7 +19,7 @@ class TestRooExcelxCellDateTime < Minitest::Test
19
19
  ['mmm-yy', 'JAN-15'],
20
20
  ['m/d/yy h:mm', '1/25/15 8:15']
21
21
  ].each do |format, formatted_value|
22
- cell = datetime.new '42029.34375', nil, [format], nil, nil, base_date, nil
22
+ cell = datetime.new '42029.34375', nil, [format], nil, nil, base_timestamp, nil
23
23
  assert_equal formatted_value, cell.formatted_value
24
24
  end
25
25
  end
@@ -30,7 +30,7 @@ class TestRooExcelxCellDateTime < Minitest::Test
30
30
  ['h:mm:ss000 mm/yy', '8:15:00000 01/15'],
31
31
  ['mmm yyy', '2015-01-25 08:15:00']
32
32
  ].each do |format, formatted_value|
33
- cell = datetime.new '42029.34375', nil, [format], nil, nil, base_date, nil
33
+ cell = datetime.new '42029.34375', nil, [format], nil, nil, base_timestamp, nil
34
34
  assert_equal formatted_value, cell.formatted_value
35
35
  end
36
36
  end
@@ -39,7 +39,7 @@ class TestRooExcelxCellDateTime < Minitest::Test
39
39
  Roo::Excelx::Cell::DateTime
40
40
  end
41
41
 
42
- def base_date
43
- Date.new(1899, 12, 30)
42
+ def base_timestamp
43
+ DateTime.new(1899, 12, 30).to_time.to_i
44
44
  end
45
45
  end
@@ -4,4 +4,15 @@ class TestRooExcelxCellEmpty < Minitest::Test
4
4
  def empty
5
5
  Roo::Excelx::Cell::Empty
6
6
  end
7
+
8
+ def test_empty?
9
+ cell = empty.new(nil)
10
+ assert_same true, cell.empty?
11
+ end
12
+
13
+ def test_nil_presence
14
+ cell = empty.new(nil)
15
+ assert_nil cell.presence
16
+ end
17
+
7
18
  end
@@ -25,6 +25,11 @@ class TestRooExcelxCellNumber < Minitest::Test
25
25
  assert_kind_of(Float, cell.value)
26
26
  end
27
27
 
28
+ def test_very_simple_scientific_notation
29
+ cell = Roo::Excelx::Cell::Number.new '1e6', nil, ['0'], nil, nil, nil
30
+ assert_kind_of(Float, cell.value)
31
+ end
32
+
28
33
  def test_percent
29
34
  cell = Roo::Excelx::Cell::Number.new '42.1', nil, ['0.00%'], nil, nil, nil
30
35
  assert_kind_of(Float, cell.value)
@@ -53,8 +58,12 @@ class TestRooExcelxCellNumber < Minitest::Test
53
58
  def test_formats
54
59
  [
55
60
  ['General', '1042'],
61
+ ['GENERAL', '1042'],
56
62
  ['0', '1042'],
63
+ ['000000', '001042'],
57
64
  ['0.00', '1042.00'],
65
+ ['0.0000', '1042.0000'],
66
+ ['0.000000000', '1042.000000000'],
58
67
  ['#,##0', '1,042'],
59
68
  ['#,##0.00', '1,042.00'],
60
69
  ['0%', '104200%'],
@@ -25,4 +25,24 @@ class TestRooExcelxCellString < Minitest::Test
25
25
  cell = string.new '0', nil, nil, nil, nil
26
26
  assert_equal '0', cell.value
27
27
  end
28
+
29
+ def test_not_empty?
30
+ cell = string.new '1', nil, nil, nil, nil
31
+ assert_equal false, cell.empty?
32
+ end
33
+
34
+ def test_empty?
35
+ cell = string.new '', nil, nil, nil, nil
36
+ assert_equal true, cell.empty?
37
+ end
38
+
39
+ def test_presence
40
+ cell = string.new '1', nil, nil, nil, nil
41
+ assert_equal cell, cell.presence
42
+ end
43
+
44
+ def test_nil_presence
45
+ cell = string.new '', nil, nil, nil, nil
46
+ assert_nil cell.presence
47
+ end
28
48
  end
@@ -5,8 +5,8 @@ class TestRooExcelxCellTime < Minitest::Test
5
5
  Roo::Excelx::Cell::Time
6
6
  end
7
7
 
8
- def base_date
9
- Date.new(1899, 12, 30)
8
+ def base_timestamp
9
+ DateTime.new(1899, 12, 30).to_time.to_i
10
10
  end
11
11
 
12
12
  def test_formatted_value
@@ -18,13 +18,13 @@ class TestRooExcelxCellTime < Minitest::Test
18
18
  ['[h]:mm:ss', '[1]:48:09'],
19
19
  ['mmss.0', '4809.0'] # Cell::Time always get rounded to the nearest second.
20
20
  ].each do |style_format, result|
21
- cell = roo_time.new(value, nil, [:numeric_or_formula, style_format], 6, nil, base_date, nil)
21
+ cell = roo_time.new(value, nil, [:numeric_or_formula, style_format], 6, nil, base_timestamp, nil)
22
22
  assert_equal result, cell.formatted_value, "Style=#{style_format} is not properly formatted"
23
23
  end
24
24
  end
25
25
 
26
26
  def test_value
27
- cell = roo_time.new('0.0751', nil, [:numeric_or_formula, 'h:mm'], 6, nil, base_date, nil)
27
+ cell = roo_time.new('0.0751', nil, [:numeric_or_formula, 'h:mm'], 6, nil, base_timestamp, nil)
28
28
  assert_kind_of Integer, cell.value
29
29
  end
30
30
  end
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "test_helper"
4
+
5
+ class TestRooExcelxCoordinate < Minitest::Test
6
+ def row
7
+ 10
8
+ end
9
+
10
+ def column
11
+ 20
12
+ end
13
+
14
+ def coordinate
15
+ Roo::Excelx::Coordinate.new(row, column)
16
+ end
17
+
18
+ def array
19
+ [row, column]
20
+ end
21
+
22
+ def test_row
23
+ assert_same row, coordinate.row
24
+ end
25
+
26
+ def test_column
27
+ assert_same column, coordinate.column
28
+ end
29
+
30
+ def test_frozen?
31
+ assert coordinate.frozen?
32
+ end
33
+
34
+ def test_equality
35
+ hash = {}
36
+ hash[coordinate] = true
37
+ assert hash.key?(coordinate)
38
+ assert hash.key?(array)
39
+ end
40
+
41
+ def test_expand
42
+ r, c = coordinate
43
+ assert_same row, r
44
+ assert_same column, c
45
+ end
46
+
47
+ def test_aref
48
+ assert_same row, coordinate[0]
49
+ assert_same column, coordinate[1]
50
+ end
51
+ end
@@ -100,6 +100,23 @@ class TestRooFormatterCSV < Minitest::Test
100
100
  end
101
101
  end
102
102
 
103
+ def test_bug_datetime_offset_change
104
+ # DO NOT REMOVE Asia/Calcutta
105
+ [nil, "US/Eastern", "US/Pacific", "Asia/Calcutta"].each do |zone|
106
+ with_timezone(zone) do
107
+ with_each_spreadsheet(name: "datetime_timezone_ist_offset_change", format: %i[excelx openoffice libreoffice]) do |workbook|
108
+ Dir.mktmpdir do |tempdir|
109
+ datetime_csv_file = File.join(tempdir, "datetime_timezone_ist_offset_change.csv")
110
+
111
+ assert workbook.to_csv(datetime_csv_file)
112
+ assert File.exist?(datetime_csv_file)
113
+ assert_equal "", file_diff("#{TESTDIR}/so_datetime_timezone_ist_offset_change.csv", datetime_csv_file)
114
+ end
115
+ end
116
+ end
117
+ end
118
+ end
119
+
103
120
  def test_true_class
104
121
  assert_equal "true", cell_to_csv(1, 1)
105
122
  end
@@ -16,7 +16,7 @@ class TestRooFormatterXML < Minitest::Test
16
16
  all_cells = init_all_cells(workbook, sheetname)
17
17
  cells = xml_sheet.children.reject(&:text?)
18
18
 
19
- assert_equal sheetname, xml_sheet.attribute("name").value
19
+ assert_equal sheetname, xml_sheet["name"]
20
20
  assert_equal all_cells.size, cells.size
21
21
 
22
22
  cells.each_with_index do |cell, i|
@@ -27,10 +27,10 @@ class TestRooFormatterXML < Minitest::Test
27
27
  all_cells[i][:type],
28
28
  ]
29
29
  result = [
30
- cell.attribute("row").value,
31
- cell.attribute("column").value,
30
+ cell["row"],
31
+ cell["column"],
32
32
  cell.text,
33
- cell.attribute("type").value,
33
+ cell["type"],
34
34
  ]
35
35
 
36
36
  assert_equal expected, result
@@ -63,11 +63,11 @@ class TestRooBase < Minitest::Test
63
63
  workbook.default_sheet = workbook.sheets.first
64
64
  result = workbook.find 20
65
65
  assert result
66
- assert_equal "Brief aus dem Sekretariat", result[0]["TITEL"]
66
+ assert_equal "Brief aus dem Sekretariat", result[0]
67
67
 
68
68
  result = workbook.find 22
69
69
  assert result
70
- assert_equal "Brief aus dem Skretariat. Tagung in Amberg/Opf.", result[0]["TITEL"]
70
+ assert_equal "Brief aus dem Skretariat. Tagung in Amberg/Opf.", result[0]
71
71
  end
72
72
  end
73
73
 
@@ -13,6 +13,34 @@ class TestRooCSV < Minitest::Test
13
13
  end
14
14
  end
15
15
 
16
+ def test_download_uri_with_query_string
17
+ file = filename("simple_spreadsheet")
18
+ port = 12_347
19
+ url = "#{local_server(port)}/#{file}?query-param=value"
20
+
21
+ start_local_server(file, port) do
22
+ csv = roo_class.new(url)
23
+ assert_equal "Task 1", csv.cell("f", 4)
24
+ assert_equal 1, csv.first_row
25
+ assert_equal 13, csv.last_row
26
+ assert_equal 1, csv.first_column
27
+ assert_equal 6, csv.last_column
28
+ end
29
+ end
30
+
31
+ def test_open_stream
32
+ file = filename("Bibelbund")
33
+ file_contents = File.read File.join(TESTDIR, file)
34
+ stream = StringIO.new(file_contents)
35
+ csv = roo_class.new(stream)
36
+
37
+ assert_equal "Aktuelle Seite", csv.cell("h", 12)
38
+ assert_equal 1, csv.first_row
39
+ assert_equal 3735, csv.last_row
40
+ assert_equal 1, csv.first_column
41
+ assert_equal 8, csv.last_column
42
+ end
43
+
16
44
  def test_nil_rows_and_lines_csv
17
45
  # x_123
18
46
  oo = Roo::CSV.new(File.join(TESTDIR,'Bibelbund.csv'))