rspreadsheet 0.2.3 → 0.2.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.yardopts +5 -0
- data/CHANGELOG.md +3 -0
- data/DEVEL_BLOG.md +13 -16
- data/Guardfile +18 -2
- data/README.md +7 -1
- data/lib/class_extensions.rb +12 -0
- data/lib/rspreadsheet.rb +1 -0
- data/lib/rspreadsheet/cell.rb +87 -19
- data/lib/rspreadsheet/row.rb +59 -25
- data/lib/rspreadsheet/tools.rb +48 -12
- data/lib/rspreadsheet/version.rb +1 -1
- data/lib/rspreadsheet/workbook.rb +29 -17
- data/lib/rspreadsheet/worksheet.rb +32 -16
- data/lib/rspreadsheet/xml_tied.rb +18 -9
- data/spec/cell_spec.rb +24 -10
- data/spec/row_spec.rb +8 -5
- data/spec/rspreadsheet_spec.rb +12 -16
- data/spec/spec_helper.rb +9 -0
- data/spec/tools_spec.rb +45 -21
- data/spec/workbook_spec.rb +20 -7
- data/spec/worksheet_spec.rb +17 -11
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 02a08c37680f411ec24be8451e70504c2efcaef5
|
4
|
+
data.tar.gz: 1ed4f196aa6891c9a2ba78631a099f03b3e9263f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b617514fbf0909eaf9eb3c42fb5794cb8b1182fbf0302c255faa9a6b0ef1aa9a75f6a8489c339f858ef341c605d4ac87d6b5895ca713bcbdeddad346ae395467
|
7
|
+
data.tar.gz: 4acb190f8388ecf64d42b6a063ea4b5198803d2d316f7486831f571490dda2c3a001cdd701b8a7cc660e7146d4c448e159cd203e36a77a943aee0965dbb52090
|
data/.yardopts
ADDED
data/CHANGELOG.md
ADDED
data/DEVEL_BLOG.md
CHANGED
@@ -2,14 +2,9 @@ See [GUIDE.md](GUIDE.md#conventions) for syntax conventions.
|
|
2
2
|
|
3
3
|
## Ideas/wishlist
|
4
4
|
|
5
|
-
* What should be returned when asking for row/cell outside used range? Currently it creates new row/cell on fly, but maybe it should only return nil, so the user needs to insert apropriate rows/cells himself before using them. However it spoils little bit syntax like spreadsheet.rows(5).cells(3) because if rows returns nil, that would fail and ugly constructs like spreadsheet.andand.rows(5).andand.cells(3) would be needed. The only way this could be acoided is by using something like "UncreatedRow" object. This concern falls to category "clash of worlds" like the indexing issue (0 vs 1 based) and many others. - now it returns "UncreatedRow/Cell" which is detached upon value assignement.
|
6
5
|
* In future inntroduce syntax like ``sheet.range('C3:E4')`` for mass operations. Also maybe ``sheet.cells('C3')`` or ``sheet.cells(3, 'C')`` etc.
|
7
6
|
* Trying to make row Enumerable - perhaps skipping empty or undefined cells.
|
8
|
-
* Accessors for nonempty/defined cells.
|
9
7
|
* 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.
|
10
|
-
* Allow any of these:
|
11
|
-
* ``book['Spring 2014']`` as alternative to ``book.worksheets('Spring 2014')`` ?
|
12
|
-
* ``sheet.cells('F13')`` as alternative to ``sheet.cells(14,5)`` ?
|
13
8
|
* Document that there is a little distinction betwean RSpreadsheet and RSpreadsheet::Workbook. The former delegates everythink to the other.
|
14
9
|
* allow `book.worskheets.first` syntax
|
15
10
|
* allow `sheet.cells.sum { |cell| cell.value }
|
@@ -37,22 +32,24 @@ Guiding ideas
|
|
37
32
|
* [github](https://github.com/gorn/rspreadsheet) hosts the repository where you can get the code
|
38
33
|
* [coverals](https://coveralls.io/r/gorn/rspreadsheet) checks how well source is covered by tests
|
39
34
|
|
40
|
-
### Local
|
35
|
+
### Local testing and releasing (to github and rubygems).
|
36
|
+
|
37
|
+
1. make changes
|
38
|
+
2. test if all tests pass (run `bundle exex guard` to test automatically). If not go to 1
|
39
|
+
3. build and install locally
|
40
|
+
* ``rake build`` - builds the gem to pkg directory.
|
41
|
+
* ``sudo rake install`` - installs gem to local system [^1]
|
42
|
+
4. Now can locally and manually use/test the gem. This should not be replacement for automated testing. If you make some changes, go to 1.
|
43
|
+
5. When happy, increment the version number and `git add .; git commit -am'commit message'; git push`
|
44
|
+
6. ``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.
|
45
|
+
|
46
|
+
gem alternativa to points 3-6
|
41
47
|
|
42
48
|
gem build rspreadsheet.gemspec -\ These two lines together are in install.sh
|
43
49
|
sudo gem install rspreadsheet-x.y.z.gem -/ which should be invoked from parent directory
|
44
50
|
gem push rspreadsheet-x.y.z.gem releases the gem, do not forgot to update version in rspreadsheet.gemspec before doing this
|
45
51
|
|
46
|
-
|
47
|
-
|
48
|
-
1. build and install locally
|
49
|
-
* ``rake build`` - builds the gem to pkg directory.
|
50
|
-
* ``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``
|
51
|
-
* Now can locally and manually use/test the gem. This should not be replacement for automated testing. If you make some changes, repeat step 1.
|
52
|
-
* When happy, increment the version number and deploy in next step.
|
53
|
-
* ``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.
|
54
|
-
|
55
|
-
|
52
|
+
[^1]: 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``
|
56
53
|
|
57
54
|
### Naming conventions
|
58
55
|
|
data/Guardfile
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
|
1
|
+
def watch_all
|
2
2
|
# watch /lib/rspreadsheet/ files
|
3
3
|
watch(%r{^lib/rspreadsheet/(.+).rb$}) do |m|
|
4
4
|
"spec/#{m[1]}_spec.rb"
|
@@ -8,4 +8,20 @@ guard 'rspec' do
|
|
8
8
|
watch(%r{^spec/(.+).rb$}) do |m|
|
9
9
|
"spec/#{m[1]}.rb"
|
10
10
|
end
|
11
|
-
end
|
11
|
+
end
|
12
|
+
|
13
|
+
# classical part
|
14
|
+
scope group: :normal
|
15
|
+
|
16
|
+
group :normal do
|
17
|
+
guard 'rspec' do watch_all end
|
18
|
+
end
|
19
|
+
|
20
|
+
# see http://stackoverflow.com/questions/18501471/guard-how-to-run-specific-tags-from-w-in-guards-console
|
21
|
+
group :focus do
|
22
|
+
guard 'rspec', cli: '--tag focus' do watch_all end
|
23
|
+
end
|
24
|
+
|
25
|
+
#group :f do
|
26
|
+
# guard 'rspec', cli: '--tag focus' do watch_all end
|
27
|
+
#end
|
data/README.md
CHANGED
@@ -40,7 +40,7 @@ sheet.rows(5).cells.sum{ |cell| cell.value }
|
|
40
40
|
|
41
41
|
total = 0
|
42
42
|
sheet.rows.each do |row|
|
43
|
-
puts "Sponsor #{row[1]} with email #{row
|
43
|
+
puts "Sponsor #{row[1]} with email #{row[2]} has donated #{row[3]} USD."
|
44
44
|
total += row[3]
|
45
45
|
end
|
46
46
|
puts "Totally fundraised #{total} USD"
|
@@ -97,3 +97,9 @@ One of the main ideas is that the manipulation with OpenDOcument files should be
|
|
97
97
|
4. Push to the branch (`git push origin my-new-feature`)
|
98
98
|
5. Create new Pull Request
|
99
99
|
|
100
|
+
## Further reading
|
101
|
+
|
102
|
+
* [Advanced Guide](GUIDE.md) how to use the gem
|
103
|
+
* [Code documentation](http://www.rubydoc.info/github/gorn/rspreadsheet) is hosted on [rubydoc.info](http://www.rubydoc.info/)
|
104
|
+
* [Changelog](CHANGELOG.md)
|
105
|
+
* [Documentation for developers](DEVEL_BLOG.md) containing ideas for future development and documentation on testing tools
|
data/lib/class_extensions.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# @private
|
2
|
+
|
1
3
|
class LibXML::XML::Node
|
2
4
|
def elements
|
3
5
|
result = []
|
@@ -34,4 +36,14 @@ class LibXML::XML::Node
|
|
34
36
|
def equals?(node2) #TODO redefine == with this
|
35
37
|
self.simplification_of?(node2) and node2.simplification_of?(self)
|
36
38
|
end
|
39
|
+
end
|
40
|
+
|
41
|
+
class Array
|
42
|
+
def sum(identity = 0, &block)
|
43
|
+
if block_given?
|
44
|
+
map(&block).sum(identity)
|
45
|
+
else
|
46
|
+
inject { |sum, element| sum + element } || identity
|
47
|
+
end
|
48
|
+
end
|
37
49
|
end
|
data/lib/rspreadsheet.rb
CHANGED
data/lib/rspreadsheet/cell.rb
CHANGED
@@ -1,13 +1,30 @@
|
|
1
|
+
# @markup markdown
|
2
|
+
# @author Jakub Tesinsky
|
3
|
+
# @titel rspreadsheet Cell
|
4
|
+
|
1
5
|
require 'andand'
|
2
6
|
require 'rspreadsheet/xml_tied'
|
3
7
|
|
8
|
+
|
9
|
+
|
4
10
|
module Rspreadsheet
|
5
|
-
|
11
|
+
|
12
|
+
###
|
13
|
+
# Represents a cell in spreadsheet which has coordinates, contains value, formula and can be formated.
|
14
|
+
# You can get this object like this (suppose that @worksheet contains {Rspreadsheet::Worksheet} object)
|
15
|
+
#
|
16
|
+
# @worksheet.cells(5,2)
|
17
|
+
#
|
18
|
+
# Note that when using syntax like `@worksheet[5,2]` or `@worksheet.B5` you won't get this object, but rather the value of the cell.
|
19
|
+
|
6
20
|
class Cell < XMLTiedItem
|
7
21
|
attr_accessor :worksheet, :coli, :rowi
|
8
|
-
|
9
|
-
|
10
|
-
def xml_options; {:xml_items_node_name =>
|
22
|
+
# `xml_options[:xml_items_node_name]` gives the name of the tag representing cell
|
23
|
+
# `xml_options[:number-columns-repeated]` gives the name of the previous tag which sais how many times the item is repeated
|
24
|
+
def xml_options; {:xml_items_node_name => 'table-cell', :xml_repeated_attribute => 'number-columns-repeated'} end
|
25
|
+
|
26
|
+
## defining abstract methods from XMLTiedItem
|
27
|
+
# returns parent XMLTiedArray object of myself (XMLTiedItem)
|
11
28
|
def parent; row end
|
12
29
|
def index; @coli end
|
13
30
|
def set_index(value); @coli=value end
|
@@ -59,21 +76,21 @@ class Cell < XMLTiedItem
|
|
59
76
|
set_type_attribute('float')
|
60
77
|
remove_all_value_attributes_and_content(xmlnode)
|
61
78
|
Tools.set_ns_attribute(xmlnode,'office','value', avalue.to_s)
|
62
|
-
xmlnode << Tools.
|
79
|
+
xmlnode << Tools.prepare_ns_node('text','p', avalue.to_f.to_s)
|
63
80
|
when gt == String then
|
64
81
|
set_type_attribute('string')
|
65
82
|
remove_all_value_attributes_and_content(xmlnode)
|
66
|
-
xmlnode << Tools.
|
83
|
+
xmlnode << Tools.prepare_ns_node('text','p', avalue.to_s)
|
67
84
|
when gt == Date then
|
68
85
|
set_type_attribute('date')
|
69
86
|
remove_all_value_attributes_and_content(xmlnode)
|
70
87
|
Tools.set_ns_attribute(xmlnode,'office','date-value', avalue.strftime('%Y-%m-%d'))
|
71
|
-
xmlnode << Tools.
|
88
|
+
xmlnode << Tools.prepare_ns_node('text','p', avalue.strftime('%Y-%m-%d'))
|
72
89
|
when gt == :percentage
|
73
90
|
set_type_attribute('percentage')
|
74
91
|
remove_all_value_attributes_and_content(xmlnode)
|
75
92
|
Tools.set_ns_attribute(xmlnode,'office','value', '%0.2d%' % avalue.to_f)
|
76
|
-
xmlnode << Tools.
|
93
|
+
xmlnode << Tools.prepare_ns_node('text','p', (avalue.to_f*100).round.to_s+'%')
|
77
94
|
end
|
78
95
|
else
|
79
96
|
raise "Unknown cell mode #{self.mode}"
|
@@ -165,22 +182,72 @@ class Cell < XMLTiedItem
|
|
165
182
|
end
|
166
183
|
|
167
184
|
# proxy object to allow cell.format syntax. Also handles all logic for formats.
|
185
|
+
# @private
|
168
186
|
class CellFormat
|
169
|
-
attr_reader :bold
|
170
187
|
def initialize(cell)
|
171
|
-
@bold = false
|
172
188
|
@cell = cell
|
173
189
|
end
|
174
190
|
def cellnode; @cell.xmlnode end
|
175
|
-
|
176
|
-
|
177
|
-
end
|
178
|
-
def
|
179
|
-
def
|
180
|
-
def
|
181
|
-
def
|
182
|
-
|
183
|
-
|
191
|
+
|
192
|
+
# text style attribute readers
|
193
|
+
def bold; get_text_style_node_attribute('font-weight') == 'bold' end
|
194
|
+
def italic; get_text_style_node_attribute('font-style') == 'italic' end
|
195
|
+
def color; get_text_style_node_attribute('color') end
|
196
|
+
def font_size; get_text_style_node_attribute('font-size') end
|
197
|
+
def get_text_style_node_attribute(attribute_name)
|
198
|
+
text_style_node.nil? ? nil : Tools.get_ns_attribute_value(text_style_node,'fo',attribute_name)
|
199
|
+
end
|
200
|
+
def background_color; get_cell_style_node_attribute('background-color') end
|
201
|
+
def get_cell_style_node_attribute(attribute_name)
|
202
|
+
cell_style_node.nil? ? nil : Tools.get_ns_attribute_value(cell_style_node,'fo',attribute_name)
|
203
|
+
end
|
204
|
+
|
205
|
+
# text style attribute writers
|
206
|
+
def bold=(value); set_text_style_node_attribute('font-weight', value ? 'bold' : 'normal') end
|
207
|
+
def italic=(value); set_text_style_node_attribute('font-style', value ? 'italic' : 'normal') end
|
208
|
+
def color=(value); set_text_style_node_attribute('color', value) end
|
209
|
+
def font_size=(value);set_text_style_node_attribute('font-size', value) end
|
210
|
+
def set_text_style_node_attribute(attribute_name,value)
|
211
|
+
if text_style_node.nil?
|
212
|
+
self.create_text_style_node
|
213
|
+
raise 'Style node was not correctly initialized' if text_style_node.nil?
|
214
|
+
end
|
215
|
+
Tools.set_ns_attribute(text_style_node,'fo',attribute_name,value)
|
216
|
+
end
|
217
|
+
def background_color=(value); set_cell_style_node_attribute('background-color', value) end
|
218
|
+
def set_cell_style_node_attribute(attribute_name,value)
|
219
|
+
if cell_style_node.nil?
|
220
|
+
self.create_cell_style_node
|
221
|
+
raise 'Style node was not correctly initialized' if cell_style_node.nil?
|
222
|
+
end
|
223
|
+
Tools.set_ns_attribute(cell_style_node,'fo',attribute_name,value)
|
224
|
+
end
|
225
|
+
|
226
|
+
# @!group initialization of style related nodes, if they do not exist
|
227
|
+
def create_text_style_node
|
228
|
+
create_style_node if style_name.nil? or style_node.nil?
|
229
|
+
raise 'text_style_node already exists' unless text_style_node.nil?
|
230
|
+
style_node << Tools.prepare_ns_node('style','text-properties')
|
231
|
+
end
|
232
|
+
def create_cell_style_node
|
233
|
+
create_style_node if style_name.nil? or style_node.nil?
|
234
|
+
raise 'cell_style_node already exists' unless cell_style_node.nil?
|
235
|
+
style_node << Tools.prepare_ns_node('style','table-cell-properties')
|
236
|
+
end
|
237
|
+
def create_style_node
|
238
|
+
if style_name.nil?
|
239
|
+
proposed_style_name = unused_cell_style_name
|
240
|
+
Tools.set_ns_attribute(cellnode,'table','style-name',proposed_style_name)
|
241
|
+
raise 'Style name was not correctly initialized' if style_name!=proposed_style_name
|
242
|
+
end
|
243
|
+
anode = Tools.prepare_ns_node('style','style')
|
244
|
+
Tools.set_ns_attribute(anode, 'style', 'name', proposed_style_name)
|
245
|
+
Tools.set_ns_attribute(anode, 'style', 'family', 'table-cell')
|
246
|
+
Tools.set_ns_attribute(anode, 'style', 'parent-style-name', 'Default')
|
247
|
+
automatic_styles_node << anode
|
248
|
+
raise 'Style node was not correctly initialized' if style_node.nil?
|
249
|
+
end
|
250
|
+
|
184
251
|
def unused_cell_style_name
|
185
252
|
last = cellnode.doc.root.find('./office:automatic-styles/style:style').
|
186
253
|
collect {|node| node['name']}.
|
@@ -188,6 +255,7 @@ class CellFormat
|
|
188
255
|
compact.max
|
189
256
|
"ce#{last+1}"
|
190
257
|
end
|
258
|
+
def automatic_styles_node; cellnode.doc.root.find("./office:automatic-styles").first end
|
191
259
|
def style_name; Tools.get_ns_attribute_value(cellnode,'table','style-name') end
|
192
260
|
def style_node; cellnode.doc.root.find("./office:automatic-styles/style:style[@style:name=\"#{style_name}\"]").first end
|
193
261
|
def text_style_node; cellnode.doc.root.find("./office:automatic-styles/style:style[@style:name=\"#{style_name}\"]/style:text-properties").first end
|
data/lib/rspreadsheet/row.rb
CHANGED
@@ -1,45 +1,57 @@
|
|
1
1
|
require 'rspreadsheet/cell'
|
2
2
|
require 'rspreadsheet/xml_tied'
|
3
3
|
|
4
|
-
# Currently this is only syntax sugar for cells and contains no functionality
|
5
4
|
|
6
5
|
module Rspreadsheet
|
7
6
|
|
7
|
+
# Represents a row in a spreadsheet which has coordinates, contains value, formula and can be formated.
|
8
|
+
# You can get this object like this (suppose that @worksheet contains {Rspreadsheet::Worksheet} object)
|
9
|
+
#
|
10
|
+
# @row = @worksheet.rows(5)
|
11
|
+
#
|
12
|
+
# Mostly you will this object to access row cells
|
13
|
+
#
|
14
|
+
# @row.cells(2) # identical to @worksheet.rows(5).cells(2)
|
15
|
+
# @row[2] # identical to @worksheet[5,2] or @row.cells(2)
|
16
|
+
#
|
17
|
+
# or manipulate rows
|
18
|
+
#
|
19
|
+
# @row.add_row_above # adds empty row above
|
20
|
+
# @row.delete # deletes row
|
21
|
+
#
|
22
|
+
# and shifts all other rows down/up appropriatelly.
|
23
|
+
|
8
24
|
class Row < XMLTiedItem
|
9
25
|
include XMLTiedArray
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
26
|
+
## @return [Worksheet] worksheet which contains the row
|
27
|
+
# @!attribute [r] worksheet
|
28
|
+
attr_reader :worksheet
|
29
|
+
## @return [Integer] row index of the row
|
30
|
+
# @!attribute [r] rowi
|
31
|
+
attr_reader :rowi
|
15
32
|
|
16
33
|
def initialize(aworksheet,arowi)
|
17
34
|
@worksheet = aworksheet
|
18
35
|
@rowi = arowi
|
19
36
|
@itemcache = Hash.new #TODO: move to module XMLTiedArray
|
20
37
|
end
|
38
|
+
|
21
39
|
def xmlnode; parent.find_my_subnode_respect_repeated(index, xml_options) end
|
22
40
|
|
23
|
-
|
24
|
-
def
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
#
|
29
|
-
def prepare_subitem(coli); Cell.new(@worksheet,@rowi,coli) end
|
30
|
-
def cells(*params)
|
31
|
-
raise 'Invalid row reference' if invalid_reference?
|
32
|
-
case params.length
|
33
|
-
when 0 then subitems_array
|
34
|
-
when 1 then subitem(params[0])
|
35
|
-
else raise Exception.new('Wrong number of arguments.')
|
36
|
-
end
|
37
|
-
end
|
38
|
-
# syntactis sugar to cells and its values
|
41
|
+
# @!group Syntactic sugar
|
42
|
+
def cells(*params); subitems(*params) end
|
43
|
+
|
44
|
+
## @return [String or Float or Date] value of the cell
|
45
|
+
# @param coli [Integer] colum index of the cell
|
46
|
+
# returns value of the cell at column `coli`
|
39
47
|
def [](coli); cells(coli).value end
|
48
|
+
## @param coli [Integer] colum index of the cell
|
49
|
+
# @param avalue [String or Float or Date] colum index of the cell
|
50
|
+
# sets value of the cell at column `coli`
|
40
51
|
def []=(coli,avalue); cells(coli).value=avalue end
|
41
|
-
|
42
|
-
|
52
|
+
# @!endgroup
|
53
|
+
|
54
|
+
# @!group Other methods
|
43
55
|
def style_name=(value);
|
44
56
|
detach_if_needed
|
45
57
|
Tools.set_ns_attribute(xmlnode,'table','style-name',value)
|
@@ -56,10 +68,32 @@ class Row < XMLTiedItem
|
|
56
68
|
end
|
57
69
|
end
|
58
70
|
alias :used_range :range
|
59
|
-
|
71
|
+
# Inserts row above itself (and shifts itself and all following rows down)
|
72
|
+
def add_row_above
|
73
|
+
parent.add_row_above(rowi)
|
74
|
+
end
|
75
|
+
def cellvalues
|
76
|
+
cells.collect{|c| c.value}
|
77
|
+
end
|
78
|
+
|
79
|
+
# @!group Private methods, which should not be called directly
|
80
|
+
# @private
|
81
|
+
# shifts internal represetation of row by diff. This should not be called directly
|
82
|
+
# by user, it is only used by XMLTiedArray as hook when shifting around rows
|
83
|
+
def _shift_by(diff)
|
60
84
|
super
|
61
85
|
@itemcache.each_value{ |cell| cell.set_rowi(@rowi) }
|
62
86
|
end
|
87
|
+
|
88
|
+
private
|
89
|
+
# @!group XMLTiedArray related methods
|
90
|
+
def subitem_xml_options; {:xml_items_node_name => 'table-cell', :xml_repeated_attribute => 'number-columns-repeated'} end
|
91
|
+
def prepare_subitem(coli); Cell.new(@worksheet,@rowi,coli) end
|
92
|
+
# @!group XMLTiedItem related methods and extensions
|
93
|
+
def xml_options; {:xml_items_node_name => 'table-row', :xml_repeated_attribute => 'number-rows-repeated'} end
|
94
|
+
def parent; @worksheet end
|
95
|
+
def index; @rowi end
|
96
|
+
def set_index(value); @rowi=value end
|
63
97
|
end
|
64
98
|
|
65
99
|
# class Row
|
data/lib/rspreadsheet/tools.rb
CHANGED
@@ -2,31 +2,59 @@ module Rspreadsheet
|
|
2
2
|
|
3
3
|
# this module contains methods used bz several objects
|
4
4
|
module Tools
|
5
|
+
def self.only_letters?(x); x.kind_of?(String) and x.match(/^[A-Za-z]*$/) != nil end
|
6
|
+
def self.kind_of_integer?(x)
|
7
|
+
(x.kind_of?(Numeric) and x.to_i==x) or
|
8
|
+
(x.kind_of?(String) and x.match(/^\d*(\.0*)?$/) != nil)
|
9
|
+
end
|
10
|
+
|
5
11
|
# converts cell adress like 'F12' to pair od integers [row,col]
|
6
12
|
def self.convert_cell_address_to_coordinates(*addr)
|
7
13
|
if addr.length == 1
|
8
|
-
addr[0].match(/^([A-
|
14
|
+
addr[0].match(/^([A-Za-z]{1,3})(\d{1,8})$/)
|
9
15
|
colname = $~[1]
|
10
|
-
rowname = $~[2]
|
16
|
+
rowname = $~[2].to_i
|
11
17
|
elsif addr.length == 2
|
12
|
-
|
13
|
-
|
18
|
+
a = addr[0]; b = addr[1]
|
19
|
+
if a.kind_of?(Integer) and b.kind_of?(Integer) # most common case first
|
20
|
+
colname,rowname = b,a
|
21
|
+
elsif only_letters?(a)
|
22
|
+
if kind_of_integer?(b)
|
23
|
+
colname,rowname = a,b.to_i
|
24
|
+
else
|
25
|
+
raise 'Wrong parameters - first is letters, but the seconds is not digits only'
|
26
|
+
end
|
27
|
+
elsif kind_of_integer?(a)
|
28
|
+
if only_letters?(b)
|
29
|
+
colname,rowname = b,a.to_i
|
30
|
+
elsif kind_of_integer?(b)
|
31
|
+
colname,rowname = b.to_i,a.to_i
|
32
|
+
else
|
33
|
+
raise 'Wrong second out of two paremeters - mix of digits and numbers'
|
34
|
+
end
|
35
|
+
else
|
36
|
+
raise 'Wrong first out of two paremeters - mix of digits and numbers'
|
37
|
+
end
|
14
38
|
else
|
15
39
|
raise 'Wrong number of arguments'
|
16
40
|
end
|
17
41
|
|
18
42
|
## first possibility how to implement it
|
19
|
-
#
|
20
|
-
#
|
43
|
+
# colname=colname.rjust(3,'@')
|
44
|
+
# col = (colname[-1].ord-64)+(colname[-2].ord-64)*26+(colname[-3].ord-64)*26*26
|
21
45
|
|
22
46
|
## second possibility how to implement it
|
23
47
|
# col=(colname.to_i(36)-('A'*colname.size).to_i(36)).to_s(36).to_i(26)+('1'*colname.size).to_i(26)
|
24
48
|
|
49
|
+
if colname.kind_of?(Integer)
|
50
|
+
col=colname
|
51
|
+
else
|
25
52
|
## third possibility how to implement it (second one little shortened)
|
26
|
-
|
27
|
-
|
53
|
+
s=colname.size
|
54
|
+
col=(colname.upcase.to_i(36)-(36**s-1).div(3.5)).to_s(36).to_i(26)+(26**s-1)/25
|
55
|
+
end
|
28
56
|
|
29
|
-
row = rowname
|
57
|
+
row = rowname
|
30
58
|
return [row,col]
|
31
59
|
end
|
32
60
|
def self.convert_cell_coordinates_to_address(*coords)
|
@@ -50,6 +78,9 @@ module Tools
|
|
50
78
|
end
|
51
79
|
def self.c2a(*x); convert_cell_coordinates_to_address(*x) end
|
52
80
|
def self.a2c(*x); convert_cell_address_to_coordinates(*x) end
|
81
|
+
# Finds {LibXML::XML::Namespace} object by its prefix. It knows all OpenDocument commonly used namespaces.
|
82
|
+
# @return [LibXML::XML::Namespace] namespace object
|
83
|
+
# @param prefix [String] namespace prefix
|
53
84
|
def self.get_namespace(prefix)
|
54
85
|
ns_array = {
|
55
86
|
'office'=>"urn:oasis:names:tc:opendocument:xmlns:office:1.0",
|
@@ -114,8 +145,12 @@ module Tools
|
|
114
145
|
nil
|
115
146
|
end
|
116
147
|
end
|
117
|
-
def self.get_ns_attribute(node,ns_prefix,key)
|
118
|
-
|
148
|
+
def self.get_ns_attribute(node,ns_prefix,key,default=:undefined_default)
|
149
|
+
if default==:undefined_default
|
150
|
+
node.attributes.get_attribute_ns(Tools.get_namespace(ns_prefix).href,key)
|
151
|
+
else
|
152
|
+
node.nil? ? default : node.attributes.get_attribute_ns(Tools.get_namespace(ns_prefix).href,key) || default
|
153
|
+
end
|
119
154
|
end
|
120
155
|
def self.get_ns_attribute_value(node,ns_prefix,key)
|
121
156
|
Tools.get_ns_attribute(node,ns_prefix,key).andand.value
|
@@ -124,13 +159,14 @@ module Tools
|
|
124
159
|
node.attributes.get_attribute_ns(Tools.get_namespace(ns_prefix).href,key)
|
125
160
|
attr.remove! unless attr.nil?
|
126
161
|
end
|
127
|
-
def self.
|
162
|
+
def self.prepare_ns_node(ns_prefix,nodename,value=nil)
|
128
163
|
LibXML::XML::Node.new(nodename,value, Tools.get_namespace(ns_prefix))
|
129
164
|
end
|
130
165
|
end
|
131
166
|
|
132
167
|
end
|
133
168
|
|
169
|
+
# @private
|
134
170
|
class Range
|
135
171
|
def size
|
136
172
|
res = self.end-self.begin+1
|
data/lib/rspreadsheet/version.rb
CHANGED
@@ -3,10 +3,10 @@ require 'libxml'
|
|
3
3
|
|
4
4
|
module Rspreadsheet
|
5
5
|
class Workbook
|
6
|
-
attr_reader :
|
6
|
+
attr_reader :filename
|
7
7
|
attr_reader :xmlnode # debug
|
8
8
|
def initialize(afilename=nil)
|
9
|
-
@worksheets=
|
9
|
+
@worksheets=[]
|
10
10
|
@filename = afilename
|
11
11
|
@content_xml = Zip::File.open(@filename || './lib/rspreadsheet/empty_file_template.ods') do |zip|
|
12
12
|
LibXML::XML::Document.io zip.get_input_stream('content.xml')
|
@@ -30,34 +30,46 @@ class Workbook
|
|
30
30
|
end
|
31
31
|
end
|
32
32
|
end
|
33
|
+
def xmldoc; @xmlnode.doc end
|
34
|
+
|
35
|
+
#@!group Worskheets methods
|
33
36
|
def create_worksheet_from_node(source_node)
|
34
37
|
sheet = Worksheet.new(source_node)
|
35
38
|
register_worksheet(sheet)
|
36
39
|
return sheet
|
37
40
|
end
|
38
|
-
def
|
41
|
+
def create_worksheet(name = "Strana #{worksheets_count}")
|
39
42
|
sheet = Worksheet.new(name)
|
40
43
|
register_worksheet(sheet)
|
41
44
|
return sheet
|
42
45
|
end
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
+
# @return [Integer] number of sheets in the workbook
|
47
|
+
def worksheets_count; @worksheets.length end
|
48
|
+
# @return [String] names of sheets in the workbook
|
49
|
+
def worksheet_names; @worksheets.collect{ |ws| ws.name } end
|
50
|
+
# @param [Integer,String]
|
51
|
+
# @return [Worskheet] worksheet with given index or name
|
52
|
+
def worksheets(index_or_name)
|
53
|
+
case index_or_name
|
54
|
+
when Integer then begin
|
55
|
+
case index_or_name
|
56
|
+
when 0 then nil
|
57
|
+
when 1..Float::INFINITY then @worksheets[index_or_name-1]
|
58
|
+
when -Float::INFINITY..-1 then @worksheets[index_or_name] # zaporne indexy znamenaji pocitani zezadu
|
59
|
+
end
|
60
|
+
end
|
61
|
+
when String then @worksheets.select{|ws| ws.name == index_or_name}.first
|
62
|
+
when NilClass then nil
|
63
|
+
else raise 'method worksheets requires Integer index of the sheet or its String name'
|
64
|
+
end
|
46
65
|
end
|
66
|
+
def [](index_or_name); self.worksheets(index_or_name) end
|
67
|
+
|
68
|
+
private
|
47
69
|
def register_worksheet(worksheet)
|
48
70
|
index = worksheets_count+1
|
49
|
-
@worksheets[index]=worksheet
|
50
|
-
@worksheets[worksheet.name]=worksheet unless worksheet.name.nil?
|
71
|
+
@worksheets[index-1]=worksheet
|
51
72
|
@xmlnode << worksheet.xmlnode if worksheet.xmlnode.doc != @xmlnode.doc
|
52
73
|
end
|
53
|
-
def worksheets_count
|
54
|
-
@worksheets.keys.select{ |k| k.kind_of? Numeric }.size #TODO: ?? max
|
55
|
-
end
|
56
|
-
def worksheet_names
|
57
|
-
@worksheets.keys.reject{ |k| k.kind_of? Numeric }
|
58
|
-
end
|
59
|
-
def xmldoc
|
60
|
-
@xmlnode.doc
|
61
|
-
end
|
62
74
|
end
|
63
75
|
end
|
@@ -6,7 +6,7 @@ module Rspreadsheet
|
|
6
6
|
|
7
7
|
class Worksheet
|
8
8
|
include XMLTiedArray
|
9
|
-
attr_accessor :
|
9
|
+
attr_accessor :xmlnode
|
10
10
|
def subitem_xml_options; {:xml_items_node_name => 'table-row', :xml_repeated_attribute => 'number-rows-repeated'} end
|
11
11
|
|
12
12
|
def initialize(xmlnode_or_sheet_name)
|
@@ -16,12 +16,17 @@ class Worksheet
|
|
16
16
|
when LibXML::XML::Node
|
17
17
|
@xmlnode = xmlnode_or_sheet_name
|
18
18
|
when String
|
19
|
-
@xmlnode = Tools.
|
20
|
-
|
19
|
+
@xmlnode = Tools.prepare_ns_node('table','table')
|
20
|
+
self.name = xmlnode_or_sheet_name
|
21
21
|
else raise 'Provide name or xml node to create a Worksheet object'
|
22
22
|
end
|
23
23
|
end
|
24
24
|
|
25
|
+
# name of the worksheet
|
26
|
+
# @returns [String]
|
27
|
+
def name; Tools.get_ns_attribute_value(@xmlnode,'table','name') end
|
28
|
+
def name=(value); Tools.set_ns_attribute(@xmlnode,'table','name', value) end
|
29
|
+
|
25
30
|
def rowxmlnode(rowi)
|
26
31
|
find_my_subnode_respect_repeated(rowi, {:xml_items_node_name => 'table-row', :xml_repeated_attribute => 'number-rows-repeated'})
|
27
32
|
end
|
@@ -30,13 +35,13 @@ class Worksheet
|
|
30
35
|
find_first_unused_index_respect_repeated({:xml_items_node_name => 'table-row', :xml_repeated_attribute => 'number-rows-repeated'})
|
31
36
|
end
|
32
37
|
|
33
|
-
def
|
34
|
-
|
38
|
+
def add_row_above(arowi)
|
39
|
+
add_empty_subitem_before(arowi)
|
35
40
|
end
|
36
41
|
|
37
42
|
def insert_cell_before(arowi,acoli)
|
38
43
|
detach_row_in_xml(arowi)
|
39
|
-
rows(arowi).
|
44
|
+
rows(arowi).add_empty_subitem_before(acoli)
|
40
45
|
end
|
41
46
|
|
42
47
|
def detach_row_in_xml(rowi)
|
@@ -47,22 +52,33 @@ class Worksheet
|
|
47
52
|
used_rows_range.collect{ |rowi| rows(rowi).nonemptycells }.flatten
|
48
53
|
end
|
49
54
|
|
50
|
-
|
51
|
-
def rows(
|
55
|
+
#@!group XMLTiedArray connected methods
|
56
|
+
def rows(*params); subitems(*params) end
|
52
57
|
def prepare_subitem(rowi); Row.new(self,rowi) end
|
53
58
|
def rowcache; @itemcache end
|
54
59
|
|
55
|
-
|
56
|
-
|
57
|
-
|
60
|
+
#@!group How to get to cells? (syntactic sugar)
|
61
|
+
# Returns value of the cell given either by row,column integer coordinates of by address.
|
62
|
+
# @param [(Integer,Integer), String] either row and column of the cell (i.e. 3,5) or a string containing it address i.e. 'F12'
|
63
|
+
def [](*params)
|
64
|
+
cells(*params).andand.value
|
58
65
|
end
|
59
|
-
|
60
|
-
|
66
|
+
# Aets value of the cell given either by row,column integer coordinates of by address. It also sets the type of the cell according to type of the value. For details #see Cell.value=
|
67
|
+
def []=(*params)
|
68
|
+
cells(*params[0..-2]).andand.value = params.last
|
61
69
|
end
|
62
|
-
|
63
|
-
|
70
|
+
# Returns a Cell object placed in row and column or on a Cell on string address
|
71
|
+
# @param [(Integer,Integer), String] either row and column of the cell (i.e. 3,5) or a string containing it address i.e. 'F12'
|
72
|
+
def cells(*params)
|
73
|
+
case params.length
|
74
|
+
when 0 then raise 'Not implemented yet' #TODO: return list of all cells
|
75
|
+
when 1..2
|
76
|
+
r,c = Rspreadsheet::Tools.a2c(*params)
|
77
|
+
rows(r).andand.cells(c)
|
78
|
+
else raise Exception.new('Wrong number of arguments.')
|
79
|
+
end
|
64
80
|
end
|
65
|
-
#
|
81
|
+
# Allows syntax like sheet.F15. TO catch errors easier, allows only up to three uppercase letters in colum part, althought it won't be necessarry to restrict.
|
66
82
|
def method_missing method_name, *args, &block
|
67
83
|
if method_name.to_s.match(/^([A-Z]{1,3})(\d{1,8})(=?)$/)
|
68
84
|
row,col = Rspreadsheet::Tools.convert_cell_address_to_coordinates($~[1],$~[2])
|
@@ -1,11 +1,13 @@
|
|
1
1
|
module Rspreadsheet
|
2
2
|
|
3
|
+
# @private
|
3
4
|
class XMLTied
|
4
5
|
def xml
|
5
6
|
xmlnode.to_s
|
6
7
|
end
|
7
8
|
end
|
8
9
|
|
10
|
+
# @private
|
9
11
|
# abstrac class. All successort MUST implement: set_index,xml_options,parent,index
|
10
12
|
class XMLTiedItem < XMLTied
|
11
13
|
def mode
|
@@ -15,7 +17,7 @@ class XMLTiedItem < XMLTied
|
|
15
17
|
else :regular
|
16
18
|
end
|
17
19
|
end
|
18
|
-
def repeated; (Tools.get_ns_attribute_value(xmlnode, 'table', xml_repeated_attribute) || 1 ).to_i end
|
20
|
+
def repeated; (Tools.get_ns_attribute_value(xmlnode, 'table', xml_options[:xml_repeated_attribute]) || 1 ).to_i end
|
19
21
|
def repeated?; mode==:repeated || mode==:outbound end
|
20
22
|
alias :is_repeated? :repeated?
|
21
23
|
def xmlnode
|
@@ -34,7 +36,7 @@ class XMLTiedItem < XMLTied
|
|
34
36
|
parent.detach_my_subnode_respect_repeated(index, xml_options)
|
35
37
|
self
|
36
38
|
end
|
37
|
-
def
|
39
|
+
def _shift_by(diff)
|
38
40
|
set_index(index + diff)
|
39
41
|
end
|
40
42
|
def range
|
@@ -59,12 +61,14 @@ class XMLTiedItem < XMLTied
|
|
59
61
|
parent.delete_subitem(index)
|
60
62
|
invalidate_myself
|
61
63
|
end
|
64
|
+
|
62
65
|
end
|
63
66
|
|
64
67
|
# abstrac class. All successort MUST implement: prepare_subitem
|
65
68
|
# terminology
|
66
69
|
# item, subitem is object from @itemcache (quite often subclass of XMLTiedItem)
|
67
70
|
# node, subnode is LibXML::XML::Node object
|
71
|
+
# @private
|
68
72
|
|
69
73
|
module XMLTiedArray
|
70
74
|
attr_reader :itemcache
|
@@ -96,6 +100,14 @@ module XMLTiedArray
|
|
96
100
|
end
|
97
101
|
end
|
98
102
|
|
103
|
+
def subitems(*params)
|
104
|
+
case params.length
|
105
|
+
when 0 then subitems_array
|
106
|
+
when 1 then subitem(params[0])
|
107
|
+
else raise Exception.new('Wrong number of arguments.')
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
99
111
|
def subitems_array
|
100
112
|
(1..(find_first_unused_index_respect_repeated(subitem_xml_options)-1)).collect do |i|
|
101
113
|
subitem(i)
|
@@ -184,15 +196,12 @@ module XMLTiedArray
|
|
184
196
|
return index+1
|
185
197
|
end
|
186
198
|
|
187
|
-
def
|
188
|
-
insert_subitem_before_with_options(aindex,subitem_xml_options)
|
189
|
-
end
|
190
|
-
def insert_subitem_before_with_options(aindex,options)
|
199
|
+
def add_empty_subitem_before(aindex)
|
191
200
|
@itemcache.keys.sort.reverse.select{|i| i>=aindex }.each do |i|
|
192
201
|
@itemcache[i+1]=@itemcache.delete(i)
|
193
|
-
@itemcache[i+1].
|
202
|
+
@itemcache[i+1]._shift_by(1)
|
194
203
|
end
|
195
|
-
insert_my_subnode_before_respect_repeated(aindex,
|
204
|
+
insert_my_subnode_before_respect_repeated(aindex,subitem_xml_options) # nyní vlož node do xml
|
196
205
|
@itemcache[aindex] = subitem(aindex)
|
197
206
|
end
|
198
207
|
|
@@ -204,7 +213,7 @@ module XMLTiedArray
|
|
204
213
|
@itemcache.delete(aindex)
|
205
214
|
@itemcache.keys.sort.select{|i| i>=aindex+1 }.each do |i|
|
206
215
|
@itemcache[i-1]=@itemcache.delete(i)
|
207
|
-
@itemcache[i-1].
|
216
|
+
@itemcache[i-1]._shift_by(-1)
|
208
217
|
end
|
209
218
|
end
|
210
219
|
|
data/spec/cell_spec.rb
CHANGED
@@ -4,9 +4,8 @@ describe Rspreadsheet::Cell do
|
|
4
4
|
before do
|
5
5
|
book1 = Rspreadsheet.new
|
6
6
|
@sheet1 = book1.create_worksheet
|
7
|
-
@c = @sheet1.cells(1,1)
|
8
7
|
book2 = Rspreadsheet.new($test_filename)
|
9
|
-
@sheet2 = book2.worksheets
|
8
|
+
@sheet2 = book2.worksheets(1)
|
10
9
|
end
|
11
10
|
it 'contains good row and col coordinates' do
|
12
11
|
@cell = @sheet1.cells(1,3)
|
@@ -186,8 +185,6 @@ describe Rspreadsheet::Cell do
|
|
186
185
|
@cell.xmlnode.attributes['style-name'].should_not be_nil
|
187
186
|
end
|
188
187
|
it 'can set formats of the cells' do
|
189
|
-
skip 'not implemented yet'; pending
|
190
|
-
=begin
|
191
188
|
@cell = @sheet2.cells(1,1)
|
192
189
|
# bold
|
193
190
|
@cell.format.bold.should be_falsey
|
@@ -204,12 +201,12 @@ describe Rspreadsheet::Cell do
|
|
204
201
|
# background_color
|
205
202
|
@cell.format.background_color.should be_nil
|
206
203
|
@cell.format.background_color = '#AABBCC'
|
204
|
+
@cell.format.style_name.should_not eq 'cell'
|
207
205
|
@cell.format.background_color.should eq '#AABBCC'
|
208
206
|
# font_size
|
209
207
|
@cell.format.font_size.should be_nil
|
210
208
|
@cell.format.font_size = '11pt'
|
211
209
|
@cell.format.font_size.should eq '11pt'
|
212
|
-
=end
|
213
210
|
end
|
214
211
|
it 'method cells without arguments returns array of cells' do
|
215
212
|
@a = @sheet2.rows(1).cells
|
@@ -221,7 +218,7 @@ describe Rspreadsheet::Cell do
|
|
221
218
|
@sheet1.cells(2,2).detach
|
222
219
|
@cell = @sheet1.cells(2,2)
|
223
220
|
@cell.rowi.should == 2
|
224
|
-
@sheet1.
|
221
|
+
@sheet1.add_row_above(1)
|
225
222
|
@cell.rowi.should == 3
|
226
223
|
end
|
227
224
|
it 'switches to invalid_reference cell when deleted' do
|
@@ -251,10 +248,27 @@ describe Rspreadsheet::Cell do
|
|
251
248
|
expect(@cell.inspect).to include('abcde','::Cell','6','2','row')
|
252
249
|
end
|
253
250
|
it 'stores date correctly' do
|
254
|
-
@
|
255
|
-
@
|
256
|
-
@
|
257
|
-
@
|
251
|
+
@cell = @sheet1.cells(1,1)
|
252
|
+
@cell.value= Date.parse('2014-01-02')
|
253
|
+
@cell.value.year.should eq 2014
|
254
|
+
@cell.value.month.should eq 1
|
255
|
+
@cell.value.day.should eq 2
|
256
|
+
end
|
257
|
+
it 'can be addressed by even more ways and all are identical' do
|
258
|
+
@cell = @sheet1.cells(2,2)
|
259
|
+
@sheet1.cells('B2').value = 'zaseste'
|
260
|
+
@sheet1.cells('B2').value.should == 'zaseste'
|
261
|
+
@cell.value.should == 'zaseste'
|
262
|
+
@sheet1.cells(2,'B').value.should == 'zaseste'
|
263
|
+
@sheet1.cells(2,'B').value = 'zasedme'
|
264
|
+
@cell.value.should == 'zasedme'
|
265
|
+
@sheet1['B2'].should == 'zasedme'
|
266
|
+
@sheet1['B2'] = 'zaosme'
|
267
|
+
@cell.value.should == 'zaosme'
|
268
|
+
|
269
|
+
@sheet2.cells('F2').should be @sheet2.cells(2,6)
|
270
|
+
@sheet2.cells('BA177').should be @sheet2.cells(177,53)
|
271
|
+
@sheet2.cells('ADA2').should be @sheet2.cells(2,781)
|
258
272
|
end
|
259
273
|
end
|
260
274
|
|
data/spec/row_spec.rb
CHANGED
@@ -3,7 +3,7 @@ require 'spec_helper'
|
|
3
3
|
describe Rspreadsheet::Row do
|
4
4
|
before do
|
5
5
|
@sheet1 = Rspreadsheet.new.create_worksheet
|
6
|
-
@sheet2 = Rspreadsheet.new($test_filename).worksheets
|
6
|
+
@sheet2 = Rspreadsheet.new($test_filename).worksheets(1)
|
7
7
|
end
|
8
8
|
it 'allows access to cells in a row' do
|
9
9
|
@row = @sheet2.rows(1)
|
@@ -106,7 +106,7 @@ describe Rspreadsheet::Row do
|
|
106
106
|
end
|
107
107
|
it 'can open ods testfile and read its content' do
|
108
108
|
book = Rspreadsheet.new($test_filename)
|
109
|
-
s = book.worksheets
|
109
|
+
s = book.worksheets(1)
|
110
110
|
(1..10).each do |i|
|
111
111
|
s.rows(i).should be_kind_of(Rspreadsheet::Row)
|
112
112
|
s.rows(i).repeated.should == 1
|
@@ -168,14 +168,17 @@ describe Rspreadsheet::Row do
|
|
168
168
|
@row.range.should == (16..19)
|
169
169
|
@row.rowi.should == 16
|
170
170
|
|
171
|
-
@sheet1.
|
171
|
+
@sheet1.add_row_above(7)
|
172
172
|
@sheet1.rows(17).range.should == (17..20)
|
173
173
|
@row.range.should == (17..20)
|
174
174
|
@row.rowi.should == 17
|
175
|
-
@sheet1.rows(17).should equal(@row)
|
175
|
+
@sheet1.rows(17).should equal(@row)
|
176
|
+
|
177
|
+
@row.add_row_above
|
178
|
+
@row.rowi.should == 18
|
176
179
|
end
|
177
180
|
it 'inserted has correct class' do # based on real error
|
178
|
-
@sheet2.
|
181
|
+
@sheet2.add_row_above(1)
|
179
182
|
@sheet2.rows(1).should be_kind_of(Rspreadsheet::Row)
|
180
183
|
end
|
181
184
|
it 'can be deleted' do
|
data/spec/rspreadsheet_spec.rb
CHANGED
@@ -3,7 +3,7 @@ require 'spec_helper'
|
|
3
3
|
describe Rspreadsheet do
|
4
4
|
it 'can open ods testfile and reads its content correctly' do
|
5
5
|
book = Rspreadsheet.new($test_filename)
|
6
|
-
s = book.worksheets
|
6
|
+
s = book.worksheets(1)
|
7
7
|
(1..10).each do |i|
|
8
8
|
s[i,1].should === i
|
9
9
|
end
|
@@ -18,8 +18,8 @@ describe Rspreadsheet do
|
|
18
18
|
|
19
19
|
book1 = Rspreadsheet.new($test_filename) # now open both again
|
20
20
|
book2 = Rspreadsheet.new(tmp_filename)
|
21
|
-
@sheet1 = book1.worksheets
|
22
|
-
@sheet2 = book2.worksheets
|
21
|
+
@sheet1 = book1.worksheets(1)
|
22
|
+
@sheet2 = book2.worksheets(1)
|
23
23
|
|
24
24
|
@sheet1.nonemptycells.each do |cell| # and test identity
|
25
25
|
@sheet2[cell.rowi,cell.coli].should == cell.value
|
@@ -48,9 +48,9 @@ describe Rspreadsheet do
|
|
48
48
|
tmp_filename = '/tmp/testfile1.ods' # first delete temp file
|
49
49
|
File.delete(tmp_filename) if File.exists?(tmp_filename)
|
50
50
|
book = Rspreadsheet.new($test_filename) # than open test file
|
51
|
-
book.worksheets
|
52
|
-
book.worksheets
|
53
|
-
book.worksheets
|
51
|
+
book.worksheets(1).rows(1).cells(1).value.should_not == 'xyzxyz'
|
52
|
+
book.worksheets(1).rows(1).cells(1).value ='xyzxyz'
|
53
|
+
book.worksheets(1).rows(1).cells(1).value.should == 'xyzxyz'
|
54
54
|
|
55
55
|
book.save(tmp_filename) # and save it as temp file
|
56
56
|
|
@@ -71,11 +71,8 @@ describe Rspreadsheet do
|
|
71
71
|
book.create_worksheet
|
72
72
|
end
|
73
73
|
it 'examples from README file are working' do
|
74
|
-
skip 'not implemented yet'; pending
|
75
|
-
=begin
|
76
|
-
|
77
74
|
book = Rspreadsheet.open($test_filename)
|
78
|
-
sheet = book.worksheets
|
75
|
+
sheet = book.worksheets(1)
|
79
76
|
sheet.B5 = 'cell value'
|
80
77
|
|
81
78
|
sheet.B5.should eq 'cell value'
|
@@ -91,16 +88,15 @@ describe Rspreadsheet do
|
|
91
88
|
sheet.cells(5,2).format.background_color = '#FF0000'
|
92
89
|
}.not_to raise_error
|
93
90
|
|
94
|
-
sheet.rows(4).cellvalues.sum.should eq 4+7*4
|
95
|
-
sheet.rows(
|
91
|
+
sheet.rows(4).cellvalues.sum{|val| val.to_f}.should eq 4+7*4
|
92
|
+
sheet.rows(4).cells.sum{ |cell| cell.value.to_f }.should eq 4+7*4
|
96
93
|
|
97
94
|
total = 0
|
98
95
|
sheet.rows.each do |row|
|
99
|
-
expect {"Sponsor #{row[1]} with email #{row
|
100
|
-
total += row[1]
|
96
|
+
expect {"Sponsor #{row[1]} with email #{row[2]} has donated #{row[3]} USD." }.not_to raise_error
|
97
|
+
total += row[1].to_f
|
101
98
|
end
|
102
|
-
total.should eq
|
103
|
-
=end
|
99
|
+
total.should eq 55
|
104
100
|
end
|
105
101
|
end
|
106
102
|
|
data/spec/spec_helper.rb
CHANGED
@@ -1,11 +1,20 @@
|
|
1
|
+
|
2
|
+
# This tells Bundler only load gems in side gemspec (not the locally installed ones). See http://stackoverflow.com/questions/4398262/setup-rspec-to-test-a-gem-not-rails
|
3
|
+
require 'bundler/setup'
|
4
|
+
Bundler.setup
|
5
|
+
|
1
6
|
RSpec.configure do |c|
|
2
7
|
c.fail_fast = true
|
3
8
|
# c.warnings = true
|
9
|
+
c.treat_symbols_as_metadata_keys_with_true_values = true # so i can run individual test just by appending :focus to them
|
4
10
|
end
|
5
11
|
|
12
|
+
# this enables Coveralls
|
6
13
|
require 'coveralls'
|
7
14
|
Coveralls.wear!
|
8
15
|
|
16
|
+
# some variables used everywhere
|
9
17
|
$test_filename = './spec/testfile1.ods'
|
10
18
|
|
19
|
+
# require my gem
|
11
20
|
require 'rspreadsheet'
|
data/spec/tools_spec.rb
CHANGED
@@ -1,25 +1,49 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
|
-
describe Rspreadsheet::Tools do
|
4
|
-
|
5
|
-
Rspreadsheet::Tools
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
3
|
+
describe Rspreadsheet::Tools, :focus do
|
4
|
+
before do
|
5
|
+
@tools = Rspreadsheet::Tools
|
6
|
+
end
|
7
|
+
it 'converts correctly cell adresses to coordinates' do
|
8
|
+
@tools.convert_cell_address_to_coordinates('A1').should == [1,1]
|
9
|
+
@tools.convert_cell_address_to_coordinates('C17').should == [17,3]
|
10
|
+
@tools.convert_cell_address_to_coordinates('AM1048576').should == [1048576,39]
|
11
|
+
@tools.convert_cell_address_to_coordinates('Am1048576').should == [1048576,39]
|
12
|
+
@tools.convert_cell_address_to_coordinates('aDa2').should == [2,781]
|
13
|
+
@tools.convert_cell_address_to_coordinates('Zz1').should == [1,702]
|
14
|
+
@tools.a2c('AdA2').should == [2,781]
|
15
|
+
@tools.a2c('ADA','2').should == [2,781]
|
16
|
+
end
|
17
|
+
it 'converts correctly cell coordinates to adresses' do
|
18
|
+
@tools.convert_cell_coordinates_to_address([1,1]).should == 'A1'
|
19
|
+
@tools.convert_cell_coordinates_to_address([17,3]).should == 'C17'
|
20
|
+
@tools.convert_cell_coordinates_to_address([1,27]).should == 'AA1'
|
21
|
+
@tools.convert_cell_coordinates_to_address([1,39]).should == 'AM1'
|
22
|
+
@tools.convert_cell_coordinates_to_address([1,53]).should == 'BA1'
|
23
|
+
@tools.convert_cell_coordinates_to_address([1,702]).should == 'ZZ1'
|
24
|
+
@tools.convert_cell_coordinates_to_address([2,703]).should == 'AAA2'
|
25
|
+
@tools.convert_cell_coordinates_to_address([1048576,39]).should == 'AM1048576'
|
26
|
+
@tools.convert_cell_coordinates_to_address([2,781]).should == 'ADA2'
|
27
|
+
@tools.c2a([2,781]).should == 'ADA2'
|
28
|
+
@tools.c2a(2,781).should == 'ADA2'
|
29
|
+
end
|
30
|
+
it 'conversions c2a and a2c are inverse of each other' do
|
31
|
+
(1..200).each { |i| @tools.a2c(@tools.c2a(1,i*10)).should == [1,i*10] }
|
32
|
+
end
|
33
|
+
it 'raises exception when given rubbisch' do
|
34
|
+
expect{ @tools.a2c('A1A') }.to raise_error
|
35
|
+
expect{ @tools.a2c('1A11') }.to raise_error
|
36
|
+
expect{ @tools.a2c('1A11') }.to raise_error
|
37
|
+
end
|
38
|
+
it 'converts correctly cell adresses given by components to coordinates' do
|
39
|
+
@tools.a2c('A','1').should eq [1,1]
|
40
|
+
@tools.a2c('C','17').should eq [17,3]
|
41
|
+
@tools.a2c('17','C',).should eq [17,3]
|
42
|
+
@tools.a2c('17','ZZ',).should eq [17,702]
|
43
|
+
end
|
44
|
+
it 'given two numbers converts them correctly even when "hidden" in strings' do
|
45
|
+
@tools.a2c('3','17').should eq [3,17]
|
46
|
+
@tools.a2c(21,'11.0').should eq [21,11]
|
47
|
+
@tools.a2c('23',22/2).should eq [23,11]
|
24
48
|
end
|
25
49
|
end
|
data/spec/workbook_spec.rb
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
|
-
describe Rspreadsheet::Workbook
|
3
|
+
describe Rspreadsheet::Workbook do
|
4
4
|
it 'has correct number of sheets' do
|
5
5
|
book = Rspreadsheet::Workbook.new($test_filename)
|
6
6
|
book.worksheets_count.should == 1
|
7
|
-
book.worksheets
|
8
|
-
book.worksheets
|
9
|
-
book.worksheets
|
10
|
-
book.worksheets
|
7
|
+
book.worksheets(0).should be_nil
|
8
|
+
book.worksheets(1).should be_kind_of(Rspreadsheet::Worksheet)
|
9
|
+
book.worksheets(2).should be_nil
|
10
|
+
book.worksheets(nil).should be_nil
|
11
11
|
end
|
12
12
|
it 'freshly created has correctly namespaced xmlnode' do
|
13
13
|
@xmlnode = Rspreadsheet::Workbook.new.xmlnode
|
@@ -28,10 +28,23 @@ describe Rspreadsheet::Workbook, :focus=>true do
|
|
28
28
|
it 'nonemptycells behave correctly' do
|
29
29
|
book = Rspreadsheet::Workbook.new()
|
30
30
|
book.create_worksheet
|
31
|
-
@sheet = book.worksheets
|
31
|
+
@sheet = book.worksheets(1)
|
32
32
|
@sheet.cells(3,3).value = 'data'
|
33
33
|
@sheet.cells(5,7).value = 'data'
|
34
34
|
@sheet.nonemptycells.collect{|c| c.coordinates}.should =~ [[3,3],[5,7]]
|
35
35
|
end
|
36
|
-
|
36
|
+
it 'can create named sheets and they are the same as "numbered" ones' do
|
37
|
+
book = Rspreadsheet::Workbook.new
|
38
|
+
book.create_worksheet('test')
|
39
|
+
book.worksheets('test').should eq book.worksheets(1)
|
40
|
+
book.create_worksheet('another')
|
41
|
+
book.worksheets('another').should eq book.worksheets(2)
|
42
|
+
end
|
43
|
+
it 'can access sheets with brief syntax' do
|
44
|
+
book = Rspreadsheet::Workbook.new
|
45
|
+
book.create_worksheet('test')
|
46
|
+
book.worksheets('test').should be book.worksheets(1)
|
47
|
+
book['test'].should be book.worksheets(1)
|
48
|
+
book[1].should be book.worksheets(1)
|
49
|
+
end
|
37
50
|
end
|
data/spec/worksheet_spec.rb
CHANGED
@@ -1,19 +1,21 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
3
|
describe Rspreadsheet::Worksheet do
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
4
|
+
describe "from test workbook file" do
|
5
|
+
before do
|
6
|
+
@sheet = Rspreadsheet.new($test_filename).worksheets(1)
|
7
|
+
end
|
8
|
+
it 'contains nonempty xml in rows for testfile' do
|
9
|
+
@sheet.rows(1).xmlnode.elements.size.should be >1
|
10
|
+
end
|
11
|
+
it 'uses detach_my_subnode_respect_repeated well' do
|
12
|
+
@sheet.detach_my_subnode_respect_repeated(50, {:xml_items_node_name => 'table-row', :xml_repeated_attribute => 'number-rows-repeated'})
|
13
|
+
@sheet.rows(50).detach_my_subnode_respect_repeated(12, {:xml_items_node_name => 'table-cell', :xml_repeated_attribute => 'number-columns-repeated'})
|
14
|
+
end
|
13
15
|
end
|
14
16
|
end
|
15
17
|
|
16
|
-
describe Rspreadsheet::Worksheet do
|
18
|
+
describe Rspreadsheet::Worksheet, :focus do
|
17
19
|
before do
|
18
20
|
book = Rspreadsheet.new
|
19
21
|
@sheet = book.create_worksheet
|
@@ -26,6 +28,10 @@ describe Rspreadsheet::Worksheet do
|
|
26
28
|
@xmlnode.namespaces.namespace.should_not be_nil
|
27
29
|
@xmlnode.namespaces.namespace.prefix.should == 'table'
|
28
30
|
end
|
31
|
+
it 'freshly created has correct name' do
|
32
|
+
@sheet2 = Rspreadsheet.new.create_worksheet('test')
|
33
|
+
@sheet2.name.should eq 'test'
|
34
|
+
end
|
29
35
|
it 'remembers the value stored to A1 cell' do
|
30
36
|
@sheet[1,1].should == nil
|
31
37
|
@sheet[1,1] = 'test text'
|
@@ -42,7 +48,7 @@ describe Rspreadsheet::Worksheet do
|
|
42
48
|
@sheet.cells(1,1).class.should == Rspreadsheet::Cell
|
43
49
|
end
|
44
50
|
it 'has name, which can be changed and is remembered' do
|
45
|
-
@sheet.name.
|
51
|
+
@sheet.name.should_not be(nil) # it should have some default name
|
46
52
|
@sheet.name = 'Icecream'
|
47
53
|
@sheet.name.should == 'Icecream'
|
48
54
|
@sheet.name = 'Cofee'
|
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.2.
|
4
|
+
version: 0.2.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-11-
|
11
|
+
date: 2014-11-23 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: libxml-ruby
|
@@ -165,6 +165,8 @@ files:
|
|
165
165
|
- ".kateproject"
|
166
166
|
- ".kateproject.d/notes.txt"
|
167
167
|
- ".travis.yml"
|
168
|
+
- ".yardopts"
|
169
|
+
- CHANGELOG.md
|
168
170
|
- COPYING.txt
|
169
171
|
- DEVEL_BLOG.md
|
170
172
|
- GUIDE.md
|