rspreadsheet 0.0.2 → 0.0.4

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
  SHA1:
3
- metadata.gz: 587955d52821df6027e5a5585c3b11ddda4e8612
4
- data.tar.gz: ed1c87deafcc001da840c29f5fbab0ab5ee215ae
3
+ metadata.gz: 919b62abc1e0fafb23ec3d83f731dd7cf60e65ca
4
+ data.tar.gz: 4a6ee4722c4b84fa550ddcd2cb5a189c11542ed2
5
5
  SHA512:
6
- metadata.gz: cc258fa3660d01c25f5670fe5e29e55cf9e49451cb3d55dd96356255fc9b22433f81b338abcb3da55d44659fa2e814659e1c379903ae15170a629687166715cd
7
- data.tar.gz: f509167f9de879478bc49b56606132795163e7dfaf893acb89321f11a975ae2311245f48d881532e64c9b32b9270f0a63f881b346bf818052b2aecd91490939a
6
+ metadata.gz: ff81a47706c13f2649d6002a4a40a91bf1712b24912b03774c82deefadaceb5fc241f3cb05c15bb7e172aa2a902dc6473625de44094fb400c32ce748c907e57f
7
+ data.tar.gz: dc961d15eb6bd8f0058b8e1139ea87a4edbce6790e693f9f456e1247d39fab85d9a8fc8937f11a87c59f2e4722ce65372e1a198256a2c855ba17a6c87c3362f9
data/.gitignore CHANGED
@@ -38,3 +38,4 @@ build/
38
38
 
39
39
  # specific to the project
40
40
  /*.ods
41
+ /spec/testfile1/
data/DEVEL_BLOG.md CHANGED
@@ -1,22 +1,39 @@
1
- See [GUIDE.md#conventions] for syntax conventions.
1
+ See [GUIDE.md](GUIDE.md#conventions) for syntax conventions.
2
2
 
3
3
  ## Ideas/wishlist
4
4
 
5
+ * In future inntroduce syntax like ``sheet.range('C3:E4')`` for mass operations. Also maybe ``sheet.cells('C3')`` or ``sheet.cells(3, 'C')`` etc.
5
6
  * Trying to make row Enumerable - perhaps skipping empty or undefined cells.
6
7
  * Accessors for nonempty/defined cells.
7
8
  * Maybe insted two syntaxes for accessing cell, we make both of them do the same and return Proxy object which would act either as value or cell.
8
- * Allow any of these?
9
- * book['Spring 2014'] in place of book.worksheets('Spring 2014')
9
+ * Allow any of these:
10
+ * ``book['Spring 2014']`` as alternative to ``book.worksheets('Spring 2014')`` ?
11
+ * ``sheet.cells.F13`` as alternative to ``sheet.cells[14,5]`` ?
10
12
 
11
13
  ## Developing this gem
12
14
 
13
- ### How to test
14
-
15
- * by running <code>guard</code> in terminal you will get tested any changes as soon as they are summitted
15
+ ### Automated testing
16
16
 
17
+ * ``guard`` will get tested any changes as soon as they are summitted
18
+ * ``rake spec`` runs all test manually
17
19
 
18
20
  ### Automated utilities
19
21
 
20
- * [travis-ci](https://travis-ci.org/gorn/rspreadsheet/jobs/25375065) provides automated testing
22
+ * [travis-ci](https://travis-ci.org/gorn/rspreadsheet) provides automated testing
21
23
  * [github](https://github.com/gorn/rspreadsheet) hosts the repository where you can get the code
22
24
  * [coverals](https://coveralls.io/r/gorn/rspreadsheet) checks how well source is covered by tests
25
+
26
+ ### Local manual testing and releasing (to github released, ).
27
+
28
+ * ``rake build`` - builds the gem to pkg directory.
29
+ * ``sudo rake install`` - If this fails with "mkmf.rb can't find header files for ruby at /usr/lib/ruby/include/ruby.h" you may want to ``sudo aptitude install ruby-dev``
30
+ * Now can locally and manually use/test the gem. This should not be replacement for automated testing.
31
+ * ``rake release`` - creates a version tag in git and pushes the code to github + Rubygems. After this is succesfull the new version appears as release in Github and RubyGems.
32
+
33
+ alternative way using ``gem`` command
34
+
35
+ gem build rspreadsheet.gemspec
36
+ sudo gem install rspreadsheet-x.y.z.gem
37
+ gem push rspreadsheet-x.y.z.gem
38
+
39
+
data/GUIDE.md CHANGED
@@ -8,42 +8,43 @@ require 'rspreadsheet'
8
8
  book = Rspreadsheet.new
9
9
  sheet = book.create_worksheet 'Top icecreams'
10
10
 
11
- sheet[0,0] = 'My top 5'
12
- p sheet[0,0].class # => String
13
- p sheet[0,0] # => "My top 5"
11
+ sheet[1,1] = 'My top 5'
12
+ p sheet[1,1].class # => String
13
+ p sheet[1,1] # => "My top 5"
14
14
 
15
15
  # These are all the same values - alternative syntax
16
16
  p sheet.A1
17
- p sheet[0,0]
18
- p sheet.cells[0,0].value
19
- p sheet.rows[0].cells[0].value
17
+ p sheet[1,1]
18
+ p sheet.cells(0,0).value
19
+ p sheet.rows(0).cells(0).value
20
20
 
21
21
  # How to inspect/manipulate the Cell object
22
- sheet.cells[0,0] # => Rspreadsheet::Cell
23
- sheet.cells[0,0].format
24
- sheet.cells[0,0].format.size = 15
25
- sheet.cells[0,0].format.weight = bold
26
- p sheet.cells[0,0].format.bold? # => true
22
+ sheet.cells(1,1) # => Rspreadsheet::Cell
23
+ sheet.cells(1,1).format
24
+ sheet.cells(1,1).format.size = 15
25
+ sheet.cells(1,1).format.weight = bold
26
+ p sheet.cells(1,1).format.bold? # => true
27
27
 
28
28
  # There are the same assigmenents
29
29
  sheet.A1 = value
30
- sheet[0,0]= value
31
- sheet.cells[0,0].value = value
30
+ sheet[1,1]= value
31
+ sheet.cells(1,1).value = value
32
32
 
33
33
  p sheet.A1.class # => Rspreadsheet::Cell
34
34
 
35
- # build the top ten list
36
- (1..5).each { |i| sheet[i,0] = i }
37
- sheet.columns[0].format.bold = true
38
- sheet.cells[1,1..5] = ['Vanilla', 'Pistacia', 'Chocolate', 'Annanas', 'Strawbery']
35
+ # build the top five list
36
+ (1..5).each { |i| sheet[i,1] = i }
37
+ sheet.columns(1).format.bold = true
38
+ sheet.cells[2,1..5] = ['Vanilla', 'Pistacia', 'Chocolate', 'Annanas', 'Strawbery']
39
39
 
40
- sheet.columns[1][1..3].format.color = :red
40
+ sheet.columns(1).cells(1).format.color = :red
41
41
 
42
42
  book.save
43
43
 
44
44
  ```
45
45
  ## Conventions
46
- * with numeric coordinates row always comes before col as in [row,col]
46
+ * all coordinates and arrays are 1-based (spreadsheet world is 1-based, ruby is 0-based do I had to make a decision. I intend to make an global option for this, but in early stage I need to keep things simple.
47
+ * with numeric coordinates row always comes before col as in (row,col)
47
48
  * with alphanumerical col always comes before row as in F12
48
- * Shorter syntax worksheet[x,y] returns value, longer syntax worksheet.cells[x,y] return cell objects. This allows to work conviniently with values using short syntax and access the cell object if needed (for formatting for example).
49
+ * Shorter syntax worksheet[x,y] returns value, longer syntax worksheet.cells(x,y) return cell objects. This allows to work conviniently with values using short syntax and access the cell object if needed (for formatting for example).
49
50
 
data/README.md CHANGED
@@ -6,7 +6,9 @@ Manipulating spreadsheets with Ruby. Read, modify, write or create new OpenDocum
6
6
 
7
7
  ## Contibutions, ideas and wishes welcomed
8
8
 
9
- Please submit issues and/or fork the repository if you have more ideas, wishes, etc ... If you find a bug you are welcomed to submit a pull request preferably including the failing test.
9
+ If you need any help or find a bug please [submit an issue](https://github.com/gorn/rspreadsheet/issues) here. I appreciate any feedback and even if you can not help with code, it is interesting for me to hear from you. Different people have different needs and I want to hear about them. If you are a programmer and you have any ideas, wishes, etc you are welcomed to fork the repository and submit a pull request preferably including a failing test.
10
+
11
+ **Beware: this gem is still in alpha stage (however I use it and it works for what I need).** Currently I am experimenting with syntax to get the feeling what could be the best.
10
12
 
11
13
  ## Examples of usage
12
14
 
@@ -41,7 +43,7 @@ Add this line to your application's Gemfile:
41
43
 
42
44
  gem 'rspreadsheet'
43
45
 
44
- And then execute:
46
+ And then execute (if this fails install bundle gem):
45
47
 
46
48
  $ bundle
47
49
 
@@ -40,6 +40,13 @@ class Cell
40
40
  @value=avalue
41
41
  self
42
42
  end
43
+ def xml
44
+ self.source_node.to_s
45
+ end
46
+ def value_xml
47
+ # self.source_node.children.first.to_s
48
+ self.source_node.children.first.children.first.to_s
49
+ end
43
50
  def coordinates
44
51
  [row,col]
45
52
  end
@@ -8,22 +8,9 @@ class Row
8
8
  def initialize(workbook,rowi)
9
9
  @rowi = rowi
10
10
  @workbook = workbook
11
- @rowcells = RowCells.new(workbook,rowi)
12
11
  end
13
- def cells
14
- @rowcells
15
- end
16
- end
17
-
18
- # this allows the row.cells[c] syntax
19
- # this object is result of row.cells
20
- class RowCells
21
- def initialize(workbook,rowi)
22
- @rowi = rowi
23
- @workbook = workbook
24
- end
25
- def [] coli
26
- @workbook.cells[@rowi,coli]
12
+ def cells(coli)
13
+ @workbook.cells(@rowi,coli)
27
14
  end
28
15
  end
29
16
 
@@ -0,0 +1,32 @@
1
+ module Rspreadsheet
2
+
3
+ # this module contains methods used bz several objects
4
+ module Tools
5
+ # converts cell adress like 'F12' to pair od integers [row,col]
6
+ def self.convert_cell_address(*coords)
7
+ if coords.length == 1
8
+ coords[0].match(/^([A-Z]{1,3})(\d{1,8})$/)
9
+ colname = $~[1]
10
+ rowname = $~[2]
11
+ elsif coords.length == 2
12
+ colname = coords[0]
13
+ rowname = coords[1]
14
+ else
15
+ raise 'Wrong number of arguments'
16
+ end
17
+
18
+ colname=colname.rjust(3,'@')
19
+ col = (colname[-1].ord-64)+(colname[-2].ord-64)*26+(colname[-3].ord-64)*26*26
20
+ row = rowname.to_i
21
+ return [row,col]
22
+ end
23
+
24
+ # this object represents array which can contain repeated values
25
+ # inspired valuely by http://www-users.cs.umn.edu/~saad/software/SPARSKIT/paper.ps
26
+ class SparseRepeatedArray < Array
27
+
28
+
29
+ end
30
+ end
31
+
32
+ end
@@ -1,3 +1,3 @@
1
1
  module Rspreadsheet
2
- VERSION = "0.0.2"
2
+ VERSION = "0.0.4"
3
3
  end
@@ -1,69 +1,64 @@
1
1
  require 'rspreadsheet/row'
2
+ require 'rspreadsheet/tools'
2
3
  require 'forwardable'
3
4
 
4
5
  module Rspreadsheet
5
6
 
6
- module Tools
7
- ## converts cell adress like 'F12' to pair od integers [row,col]
8
- def self.convert_cell_address(*coords)
9
- if coords.length == 1
10
- coords.match(/^([A-Z]{1,3})(\d{1,8})$/)
11
- colname = $~[1]
12
- rowname = $~[2]
13
- elsif coords.length == 2
14
- colname = coords[0]
15
- rowname = coords[1]
16
- else
17
- raise 'Wrong number of arguments'
18
- end
19
-
20
- colname=colname.rjust(3,'@')
21
- col = (colname[-1].ord-65)+(colname[-2].ord-64)*26+(colname[-3].ord-64)*26*26
22
- row = rowname.to_i-1
23
- return [row,col]
24
- end
25
- end
26
-
27
7
  class Worksheet
28
8
  attr_accessor :name
29
9
  extend Forwardable
30
- def_delegators :@worksheetcells, :nonemptycells
10
+ def_delegators :nonemptycells
31
11
 
32
12
  def initialize(source_node=nil)
33
13
  @source_node = source_node
34
- @worksheetcells=WorksheetCells.new
35
- rowi = 0
14
+ ## initialize cells
15
+ @cells = Hash.new do |hash, coords|
16
+ # we create empty cell and place it to hash, we do not have to check whether there is a cell in XML already, because it would be in hash as well
17
+ hash[coords]=Cell.new(coords[0],coords[1])
18
+ # TODO: create XML empty node here or upon save?
19
+ end
20
+ rowi = 1
36
21
  unless @source_node.nil?
37
22
  @source_node.elements.select{ |node| node.name == 'table-row'}.each do |row_source_node|
38
- coli = 0
23
+ coli = 1
39
24
  row_source_node.elements.select{ |node| node.name == 'table-cell'}.each do |cell_source_node|
40
- cells.initialize_cell(rowi,coli,cell_source_node)
25
+ initialize_cell(rowi,coli,cell_source_node)
41
26
  coli += 1
42
27
  end
43
28
  rowi += 1
44
29
  end
45
30
  end
31
+ ## initialize rows
32
+ @spredsheetrows=Array.new()
33
+ end
34
+ def initialize_cell(r,c,source_node)
35
+ @cells[[r,c]]=Cell.new(r,c,source_node)
36
+ end
37
+ def cells(r,c)
38
+ @cells[[r,c]]
39
+ end
40
+ def nonemptycells
41
+ @cells.values
46
42
  end
47
- def cells
48
- @worksheetcells
43
+ def rows(rowi)
44
+ @spredsheetrows[rowi] ||= Row.new(self,rowi)
49
45
  end
46
+ ## syntactic sugar follows
50
47
  def [](r,c)
51
- cells[r,c].value
48
+ cells(r,c).value
52
49
  end
53
50
  def []=(r,c,avalue)
54
- cells[r,c].value=avalue
55
- end
56
- def rows
57
- WorksheetRows.new(self)
51
+ cells(r,c).value=avalue
58
52
  end
53
+ # allows syntax like sheet.F15
59
54
  def method_missing method_name, *args, &block
60
55
  if method_name.to_s.match(/^([A-Z]{1,3})(\d{1,8})(=?)$/)
61
- row,col = Tools.convert_cell_address($~[1],$~[2])
56
+ row,col = Rspreadsheet::Tools.convert_cell_address($~[1],$~[2])
62
57
  assignchar = $~[3]
63
58
  if assignchar == '='
64
- self.cells[row,col].value = args.first
59
+ self.cells(row,col).value = args.first
65
60
  else
66
- self.cells[row,col].value
61
+ self.cells(row,col).value
67
62
  end
68
63
  else
69
64
  super
@@ -71,42 +66,4 @@ class Worksheet
71
66
  end
72
67
  end
73
68
 
74
- # this allows the sheet.cells[r,c] syntax
75
- # this object is result of sheet.cells
76
- class WorksheetCells
77
- def initialize
78
- @cells = Hash.new do |hash, coords|
79
- # we create empty cell and place it to hash, we do not have to check whether there is a cell in XML already, because it would be in hash as well
80
- hash[coords]=Cell.new(coords[0],coords[1])
81
- # TODO: create XML empty node here or upon save?
82
- end
83
- end
84
- def [](r,c)
85
- cells_object(r,c)
86
- end
87
- def nonemptycells
88
- @cells.values
89
- end
90
-
91
- ### internal
92
- def cells_object(r,c)
93
- @cells[[r,c]]
94
- end
95
- def initialize_cell(r,c,source_node)
96
- @cells[[r,c]]=Cell.new(r,c,source_node)
97
- end
98
- end
99
-
100
- # this allows the sheet.rows[r] syntax
101
- # this object is result of sheet.rows
102
- class WorksheetRows
103
- def initialize(aworkbook)
104
- @workbook = aworkbook
105
- @spredsheetrows=Array.new()
106
- end
107
- def [] rowi
108
- @spredsheetrows[rowi] ||= Row.new(@workbook,rowi)
109
- end
110
- end
111
-
112
69
  end
@@ -2,6 +2,27 @@ require 'spec_helper'
2
2
 
3
3
  $test_filename = './spec/testfile1.ods'
4
4
 
5
+ describe Rspreadsheet::Tools do
6
+ it '=Converts correctly cell adresses' do
7
+ Rspreadsheet::Tools.convert_cell_address('A1') [0].should == 1
8
+ Rspreadsheet::Tools.convert_cell_address('A1')[1].should == 1
9
+ Rspreadsheet::Tools.convert_cell_address('C5')[0].should == 5
10
+ Rspreadsheet::Tools.convert_cell_address('C5')[1].should == 3
11
+ end
12
+ end
13
+
14
+ describe Rspreadsheet::Tools::SparseRepeatedArray do
15
+ before do
16
+ # @a = Rspreadsheet::Tools::SparseRepeatedArray.new()
17
+ end
18
+ # Float::INFINITY
19
+ its 'set values can be read back unchanged' do
20
+ pending
21
+ @a[3..5]='test'
22
+ @a[4].should == 'test'
23
+ end
24
+ end
25
+
5
26
  describe Rspreadsheet do
6
27
  it 'can open ods testfile and reads its content correctly' do
7
28
  book = Rspreadsheet.new($test_filename)
@@ -9,10 +30,10 @@ describe Rspreadsheet do
9
30
  book.worksheets[0].class.should == Rspreadsheet::Worksheet
10
31
  s = book.worksheets[0]
11
32
  (1..10).each do |i|
12
- s[i-1,0].should === i
33
+ s[i,1].should === i
13
34
  end
14
- s[0,1].should === 'text'
15
- s[1,1].should === Date.new(2014,1,1)
35
+ s[1,2].should === 'text'
36
+ s[2,2].should === Date.new(2014,1,1)
16
37
  end
17
38
  it 'can open and save file, and saved file has same cells as original' do
18
39
  tmp_filename = '/tmp/testfile1.ods' # first delete temp file
@@ -48,9 +69,9 @@ describe Rspreadsheet do
48
69
  tmp_filename = '/tmp/testfile1.ods' # first delete temp file
49
70
  File.delete(tmp_filename) if File.exists?(tmp_filename)
50
71
  book = Rspreadsheet.new($test_filename) # than open test file
51
- book.worksheets[0][0,0].should_not == 'xyzxyz'
52
- book.worksheets[0][0,0]='xyzxyz'
53
- book.worksheets[0][0,0].should == 'xyzxyz'
72
+ book.worksheets[0][1,1].should_not == 'xyzxyz'
73
+ book.worksheets[0][1,1]='xyzxyz'
74
+ book.worksheets[0][1,1].should == 'xyzxyz'
54
75
  book.save(tmp_filename) # and save it as temp file
55
76
 
56
77
  # now compare them
@@ -80,52 +101,61 @@ describe Rspreadsheet::Cell do
80
101
  @sheet2 = book2.worksheets[0]
81
102
  end
82
103
  it 'contains good row and col coordinates' do
83
- @cell = @sheet1.cells[1,3]
104
+ @cell = @sheet1.cells(1,3)
84
105
  @cell.row.should == 1
85
106
  @cell.col.should == 3
86
107
  @cell.coordinates.should == [1,3]
87
108
 
88
- @cell = @sheet2.cells[0,1]
89
- @cell.row.should == 0
90
- @cell.col.should == 1
91
- @cell.coordinates.should == [0,1]
92
- end
93
- it 'can be referenced by more vars and both are synchromized' do
94
- @cell = @sheet1.cells[0,0]
95
- @sheet1[0,0] = 'novinka'
109
+ @cell = @sheet2.cells(7,2)
110
+ @cell.row.should == 7
111
+ @cell.col.should == 2
112
+ @cell.coordinates.should == [7,2]
113
+ end
114
+ it 'can be referenced by more vars and both are synchronized' do
115
+ @cell = @sheet1.cells(1,1)
116
+ @sheet1[1,1] = 'novinka'
96
117
  @cell.value.should == 'novinka'
97
118
  end
98
- it 'can be modyfied by more ways and all are identical' do
99
- @cell = @sheet1.cells[2,2]
119
+ it 'can be modified by more ways and all are identical' do
120
+ @cell = @sheet1.cells(2,2)
100
121
  @sheet1[2,2] = 'zaprve'
101
122
  @cell.value.should == 'zaprve'
102
- @sheet1.cells[2,2].value = 'zadruhe'
123
+ @sheet1.cells(2,2).value = 'zadruhe'
103
124
  @cell.value.should == 'zadruhe'
104
- @sheet1.C3 = 'zatreti'
125
+ @sheet1.B2 = 'zatreti'
105
126
  @cell.value.should == 'zatreti'
106
127
  end
128
+ it 'can include links' do
129
+ @sheet2.A12.should == '[http://example.org/]'
130
+ end
131
+ it 'contains good row and col coordinates even after table:number-columns-repeated cells' do
132
+ pending
133
+ @cell = @sheet2.cells(13,5)
134
+ @cell.value.should == 'afterrepeated'
135
+ @cell.row.should == 13
136
+ @cell.col.should == 5
137
+ end
107
138
  end
108
139
 
109
-
110
140
  describe Rspreadsheet::Worksheet do
111
141
  before do
112
142
  book = Rspreadsheet.new
113
143
  @sheet = book.create_worksheet
114
144
  end
115
145
  it 'remembers the value stored to A1 cell' do
116
- @sheet[0,0].should == nil
117
- @sheet[0,0] = 'test text'
118
- @sheet[0,0].class.should == String
119
- @sheet[0,0].should == 'test text'
146
+ @sheet[1,1].should == nil
147
+ @sheet[1,1] = 'test text'
148
+ @sheet[1,1].class.should == String
149
+ @sheet[1,1].should == 'test text'
120
150
  end
121
151
  it 'value stored to A1 is accesible using different syntax' do
122
- @sheet[0,0] = 'test text'
123
- @sheet[0,0].should == 'test text'
124
- @sheet.cells[0,0].value.should == 'test text'
152
+ @sheet[1,1] = 'test text'
153
+ @sheet[1,1].should == 'test text'
154
+ @sheet.cells(1,1).value.should == 'test text'
125
155
  end
126
156
  it 'makes Cell object accessible' do
127
- @sheet.cells[0,0].value = 'test text'
128
- @sheet.cells[0,0].class.should == Rspreadsheet::Cell
157
+ @sheet.cells(1,1).value = 'test text'
158
+ @sheet.cells(1,1).class.should == Rspreadsheet::Cell
129
159
  end
130
160
  it 'has name, which can be changed and is remembered' do
131
161
  @sheet.name.should be(nil)
@@ -144,11 +174,9 @@ describe Rspreadsheet::Row do
144
174
  it 'allows access to cells in a row' do
145
175
  (2..5).each { |i| @sheet1[7,i] = i }
146
176
  (2..5).each { |i|
147
- a = @sheet1.rows
148
- b = a[7]
149
- c = b.cells
150
- d = c[i]
151
- d.value.should == i
177
+ a = @sheet1.rows(7)
178
+ c = a.cells(i)
179
+ c.value.should == i
152
180
  }
153
181
  end
154
182
  end
data/spec/testfile1.ods CHANGED
Binary file
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rspreadsheet
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.2
4
+ version: 0.0.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jakub A.Těšínský
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-07-24 00:00:00.000000000 Z
11
+ date: 2014-07-26 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: libxml-ruby
@@ -175,6 +175,7 @@ files:
175
175
  - lib/rspreadsheet/cell.rb
176
176
  - lib/rspreadsheet/empty_file_template.ods
177
177
  - lib/rspreadsheet/row.rb
178
+ - lib/rspreadsheet/tools.rb
178
179
  - lib/rspreadsheet/version.rb
179
180
  - lib/rspreadsheet/workbook.rb
180
181
  - lib/rspreadsheet/worksheet.rb