spreet 0.0.2 → 0.0.3
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +47 -1
- data/VERSION +1 -1
- data/lib/big_array.rb +100 -0
- data/lib/duration.rb +145 -0
- data/lib/spreet.rb +75 -103
- data/lib/spreet/coordinates.rb +62 -0
- data/lib/spreet/handlers/open_document.rb +310 -31
- data/lib/time.rb +13 -0
- data/test/helper.rb +15 -0
- data/test/samples/pascal.ods +0 -0
- data/test/test_big_array.rb +24 -0
- data/test/test_coordinates.rb +31 -0
- data/test/test_csv.rb +39 -0
- data/test/test_duration.rb +34 -0
- data/test/test_open_document.rb +17 -0
- data/test/test_spreet.rb +58 -36
- metadata +56 -9
data/README.rdoc
CHANGED
@@ -1,3 +1,49 @@
|
|
1
1
|
= Spreet
|
2
2
|
|
3
|
-
Universal handler for spr[eadsh]eets.
|
3
|
+
Universal handler for spr[eadsh]eets.
|
4
|
+
|
5
|
+
== Why ?
|
6
|
+
This gems is a handler for spreadsheets. With its independent API, it is possible to create, update files in some formats. Today the list is not very long:
|
7
|
+
|
8
|
+
* CSV: UTF-8 with commas (Read & Write)
|
9
|
+
* CSV for Excel: CP1252 with semicolons (Read & Write)
|
10
|
+
* ODS: Open Document Format (Read & Write with restrictions)
|
11
|
+
|
12
|
+
== Installation
|
13
|
+
|
14
|
+
gem install spreet
|
15
|
+
|
16
|
+
== How to use it
|
17
|
+
|
18
|
+
# Create a new document
|
19
|
+
doc = Spreet::Document.new
|
20
|
+
sheet = doc.sheets.add "My Sheet"
|
21
|
+
|
22
|
+
# Coordinates can be called with spreadsheet style...
|
23
|
+
sheet["A1"] = "Last name"
|
24
|
+
# ...or more classic style...
|
25
|
+
sheet[1,0] = "First name"
|
26
|
+
# ...or if necessary as a Hash
|
27
|
+
sheet[:x=>2, :y=>0] = "Born on"
|
28
|
+
|
29
|
+
sheet.next_row
|
30
|
+
for person in People.all
|
31
|
+
sheet.row person.last_name, person.first_name, person.born_on
|
32
|
+
end
|
33
|
+
|
34
|
+
# Write it as a classic CSV
|
35
|
+
sheet.write("people-1.csv")
|
36
|
+
# Write it as a CSV for Excel
|
37
|
+
sheet.write("people-2.csv", :format=>:xcsv) # CSV for Excel
|
38
|
+
# or write it as an Open Document Spreadsheet
|
39
|
+
sheet.write("people-3.ods")
|
40
|
+
|
41
|
+
== To do soon
|
42
|
+
|
43
|
+
* Add style management for cells
|
44
|
+
* Add Header/Footer
|
45
|
+
* HTML Writer
|
46
|
+
* PDF Writer like OpenOffice/LibreOffice would make it
|
47
|
+
|
48
|
+
== Travis
|
49
|
+
{<img src="https://secure.travis-ci.org/burisu/spreet.png"/>}[http://travis-ci.org/burisu/spreet]
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.0.
|
1
|
+
0.0.3
|
data/lib/big_array.rb
ADDED
@@ -0,0 +1,100 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
# Class to manage big hash with lot of pairs
|
4
|
+
class BigArray
|
5
|
+
|
6
|
+
def initialize(klass_name=nil, partition=8, levels=4)
|
7
|
+
@partition = partition.to_i
|
8
|
+
raise ArgumentError.new("Partition must be an integer > 0") unless @partition > 0
|
9
|
+
@levels = levels.to_i
|
10
|
+
raise ArgumentError.new("Levels must be an integer > 0") unless @levels > 0
|
11
|
+
klass_name ||= "#{self.class.name}#{@partition}_#{@levels}"
|
12
|
+
@base_class = "Hash"
|
13
|
+
code = ""
|
14
|
+
code << "class #{klass_name}\n"
|
15
|
+
code << " def initialize()\n"
|
16
|
+
code << " @root = #{@base_class}.new\n"
|
17
|
+
code << " end\n\n"
|
18
|
+
|
19
|
+
code << " def [](index)\n"
|
20
|
+
code << dive do |pointer|
|
21
|
+
"return nil"
|
22
|
+
end.strip.gsub(/^/, ' ')+"\n"
|
23
|
+
code << " return cursor[#{index_at_level(@levels)}]\n"
|
24
|
+
code << " end\n\n"
|
25
|
+
|
26
|
+
code << " def []=(index, value)\n"
|
27
|
+
# code << " index, value = args[0], args[1]\n"
|
28
|
+
code << dive do |pointer|
|
29
|
+
"#{pointer} = #{@base_class}.new"
|
30
|
+
end.strip.gsub(/^/, ' ')+"\n"
|
31
|
+
code << " return cursor[#{index_at_level(@levels)}] = value\n"
|
32
|
+
code << " end\n\n"
|
33
|
+
|
34
|
+
code << " def delete(index)\n"
|
35
|
+
# code << " index, value = args[0], args[1]\n"
|
36
|
+
code << dive do |pointer|
|
37
|
+
"return nil"
|
38
|
+
end.strip.gsub(/^/, ' ')+"\n"
|
39
|
+
code << " return cursor.delete(#{index_at_level(@levels)})\n"
|
40
|
+
code << " end\n\n"
|
41
|
+
|
42
|
+
code << " def each(&block)\n"
|
43
|
+
code << browse do
|
44
|
+
"yield(index, value)"
|
45
|
+
end.strip.gsub(/^/, ' ')+"\n"
|
46
|
+
code << " end\n\n"
|
47
|
+
|
48
|
+
code << " def to_hash\n"
|
49
|
+
code << " hash = {}\n"
|
50
|
+
code << browse do
|
51
|
+
"hash[index] = value"
|
52
|
+
end.strip.gsub(/^/, ' ')+"\n"
|
53
|
+
code << " return hash\n"
|
54
|
+
code << " end\n\n"
|
55
|
+
|
56
|
+
code << "end\n"
|
57
|
+
# raise code
|
58
|
+
eval(code)
|
59
|
+
return self.class.const_get(klass_name)
|
60
|
+
end
|
61
|
+
|
62
|
+
private
|
63
|
+
|
64
|
+
def dive(&block)
|
65
|
+
code = ""
|
66
|
+
for level in 1..(@levels-1)
|
67
|
+
pointer = "#{level == 1 ? '@root' : 'cursor'}[#{index_at_level(level)}]"
|
68
|
+
code << "unless #{pointer}.is_a?(#{@base_class})\n"
|
69
|
+
code << yield(pointer).to_s.strip.gsub(/^/, ' ')+"\n"
|
70
|
+
code << "end\n"
|
71
|
+
code << "cursor = #{pointer}\n"
|
72
|
+
end
|
73
|
+
return code
|
74
|
+
end
|
75
|
+
|
76
|
+
def browse(level = 1, &block)
|
77
|
+
code = ""
|
78
|
+
value = (level == @levels ? 'value' : "h#{level}")
|
79
|
+
code << "for l#{level}, #{value} in #{level == 1 ? '@root' : 'h'+(level-1).to_s}\n"
|
80
|
+
if level > 1
|
81
|
+
i = (level == @levels ? "index" : "i#{level}")
|
82
|
+
code << " #{i} = ("+(level>2 ? "i" : "l")+"#{level-1} << #{@partition})|l#{level}\n"
|
83
|
+
end
|
84
|
+
if level == @levels
|
85
|
+
code << yield.to_s.strip.gsub(/^/, ' ')+"\n"
|
86
|
+
else
|
87
|
+
code << browse(level + 1, &block).to_s.strip.gsub(/^/, ' ')+"\n"
|
88
|
+
end
|
89
|
+
code << "end\n"
|
90
|
+
return code
|
91
|
+
end
|
92
|
+
|
93
|
+
def index_at_level(level, variable="index")
|
94
|
+
v = variable
|
95
|
+
v = "(#{v} >> #{(@levels-level)*@partition})" if level < @levels
|
96
|
+
return "#{v}&#{2**@partition-1}"
|
97
|
+
end
|
98
|
+
|
99
|
+
|
100
|
+
end
|
data/lib/duration.rb
ADDED
@@ -0,0 +1,145 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
# Class to manage durations (or intervals in SQL vocabulary)
|
4
|
+
class Duration
|
5
|
+
# 365.25 * 86_400 == 31_557_600
|
6
|
+
SECONDS_IN_YEAR = 31_557_600 # 31_556_926 => 31_556_928
|
7
|
+
SECONDS_IN_MONTH = SECONDS_IN_YEAR / 12
|
8
|
+
|
9
|
+
FIELDS = [:years, :months, :days, :hours, :minutes, :seconds]
|
10
|
+
attr_accessor *FIELDS
|
11
|
+
attr_reader :sign
|
12
|
+
|
13
|
+
def initialize(*args)
|
14
|
+
@years, @months, @days, @hours, @minutes, @seconds = 0, 0, 0, 0, 0, 0
|
15
|
+
@sign = 1
|
16
|
+
if args.size == 1 and args[0].is_a? String
|
17
|
+
self.parse(args[0])
|
18
|
+
else
|
19
|
+
@years = (args.shift || 0).to_i
|
20
|
+
@months = (args.shift || 0).to_i
|
21
|
+
@days = (args.shift || 0).to_i
|
22
|
+
@hours = (args.shift || 0).to_i
|
23
|
+
@minutes = (args.shift || 0).to_i
|
24
|
+
@seconds = (args.shift || 0).to_i
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def parse(string)
|
29
|
+
unless string.match(/^\-?P(\d+Y)?(\d+M)?(\d+D)?(T(\d+H)?(\d+M)?(\d+(\.\d+)?S)?)?$/)
|
30
|
+
raise ArgumentError.new("Malformed string")
|
31
|
+
end
|
32
|
+
strings = string.split('T')
|
33
|
+
strings[0].gsub(/\d+[YMD]/) do |token|
|
34
|
+
code, count = token.to_s[-1..-1], token.to_s[0..-2].to_i
|
35
|
+
if code == "Y"
|
36
|
+
@years = count
|
37
|
+
elsif code == "M"
|
38
|
+
@months = count
|
39
|
+
elsif code == "D"
|
40
|
+
@days = count
|
41
|
+
end
|
42
|
+
token
|
43
|
+
end
|
44
|
+
strings[1].to_s.gsub(/(\d+[HM]|\d+(\.\d+)?S)/) do |token|
|
45
|
+
code, count = token.to_s[-1..-1], token.to_s[0..-2]
|
46
|
+
if code == "H"
|
47
|
+
@hours = count.to_i
|
48
|
+
elsif code == "M"
|
49
|
+
@minutes = count.to_i
|
50
|
+
elsif code == "S"
|
51
|
+
@seconds = count.to_f
|
52
|
+
end
|
53
|
+
token
|
54
|
+
end
|
55
|
+
self.sign = (string.match(/^\-/) ? -1 : 1)
|
56
|
+
return self
|
57
|
+
end
|
58
|
+
|
59
|
+
def sign=(val)
|
60
|
+
@sign = (val >= 0 ? 1 : -1)
|
61
|
+
end
|
62
|
+
|
63
|
+
def to_s(compression = :normal)
|
64
|
+
if compression == :maximum
|
65
|
+
return (@sign > 0 ? "" : "-")+"P#{@years.to_s+'Y' if @years > 0}#{@months.to_s+'M' if @months > 0}#{@days.to_s+'D' if @days > 0}"+((@hours.zero? and @minutes.zero? and @seconds.zero?) ? '' : "T#{(@hours.to_s+'H') if @hours > 0}#{(@minutes.to_s+'M') if @minutes > 0}#{((@seconds.floor != @seconds ? @seconds.to_s : @seconds.to_i.to_s)+'S') if @seconds > 0}")
|
66
|
+
elsif compression == :minimum
|
67
|
+
return (@sign > 0 ? "" : "-")+"P"+@years.to_s+"Y"+@months.to_s+"M"+@days.to_s+"DT"+@hours.to_s+"H"+@minutes.to_s+"M"+@seconds.to_s+"S"
|
68
|
+
else
|
69
|
+
return (@sign > 0 ? "" : "-")+"P"+((@years.zero? and @months.zero? and @days.zero?) ? '' : @years.to_s+"Y"+@months.to_s+"M"+@days.to_s+"D")+((@hours.zero? and @minutes.zero? and @seconds.zero?) ? '' : "T"+@hours.to_s+"H"+@minutes.to_s+"M"+(@seconds.round != @seconds ? @seconds.to_s : @seconds.to_i.to_s)+"S")
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
# Export all values to hash
|
74
|
+
def to_hash
|
75
|
+
{:years=>@years, :months=>@months, :days=>@days, :hours=>@hours, :minutes=>@minutes, :seconds=>@seconds, :sign=>@sign}
|
76
|
+
end
|
77
|
+
|
78
|
+
# Computes a duration in seconds based on theoric and statistic values
|
79
|
+
# Because the relation between some of date parts isn't fixed (such as the number of days in a month),
|
80
|
+
# the order relationship between durations is only partial, and the result of a comparison
|
81
|
+
# between two durations may be undetermined.
|
82
|
+
def to_f
|
83
|
+
count = @seconds
|
84
|
+
count += 60 * @minutes
|
85
|
+
# 60 * 60 = 3_600
|
86
|
+
count += 3_600 * @hours
|
87
|
+
# 60 * 60 * 24 = 86_400
|
88
|
+
count += 86_400 * @days
|
89
|
+
# 365.25/12 * 86_400 == 31_557_600/12 == 2_629_800
|
90
|
+
count += SECONDS_IN_MONTH * @months
|
91
|
+
# 365.25 * 86_400 == 31_557_600
|
92
|
+
count += SECONDS_IN_YEAR * @years
|
93
|
+
return @sign * count
|
94
|
+
end
|
95
|
+
|
96
|
+
def to_i
|
97
|
+
self.to_f.to_i
|
98
|
+
end
|
99
|
+
|
100
|
+
# Normalize seconds, minutes, hours and month with their fixed relations
|
101
|
+
def normalize!(normalize_method = :right)
|
102
|
+
if normalize_method == :seconds
|
103
|
+
count = self.to_f
|
104
|
+
@years = (count / SECONDS_IN_YEAR).floor
|
105
|
+
count -= @years * SECONDS_IN_YEAR
|
106
|
+
@months = (count / SECONDS_IN_MONTH).floor
|
107
|
+
count -= @months * SECONDS_IN_MONTH
|
108
|
+
@days = (count / 86_400).floor
|
109
|
+
count -= @days * 86_400
|
110
|
+
@hours = (count / 3_600).floor
|
111
|
+
count -= @hours * 3_600
|
112
|
+
@minutes = (count / 60).floor
|
113
|
+
count -= @minutes * 60
|
114
|
+
@seconds = count
|
115
|
+
else
|
116
|
+
if @seconds >= 60
|
117
|
+
minutes = (@seconds / 60).floor
|
118
|
+
@seconds -= minutes * 60
|
119
|
+
@minutes += minutes
|
120
|
+
end
|
121
|
+
if @minutes >= 60
|
122
|
+
hours = (@minutes / 60).floor
|
123
|
+
@minutes -= hours * 60
|
124
|
+
@hours += hours
|
125
|
+
end
|
126
|
+
if @hours >= 24
|
127
|
+
days = (@hours / 24).floor
|
128
|
+
@hours -= days * 24
|
129
|
+
@days += days
|
130
|
+
end
|
131
|
+
# No way to convert correctly days in month
|
132
|
+
if @months >= 12
|
133
|
+
years = (@months / 12).floor
|
134
|
+
@months -= years * 12
|
135
|
+
@years += years
|
136
|
+
end
|
137
|
+
end
|
138
|
+
return self
|
139
|
+
end
|
140
|
+
|
141
|
+
def normalize(normalize_method = :right)
|
142
|
+
self.dup.normalize!(normalize_method)
|
143
|
+
end
|
144
|
+
|
145
|
+
end
|
data/lib/spreet.rb
CHANGED
@@ -1,11 +1,19 @@
|
|
1
1
|
# encoding: utf-8
|
2
2
|
require 'pathname'
|
3
|
+
require 'duration'
|
4
|
+
require 'money'
|
5
|
+
require 'time'
|
6
|
+
require 'big_array'
|
7
|
+
require 'spreet/coordinates'
|
8
|
+
|
9
|
+
# Create class for arrays
|
10
|
+
BigArray.new("Cells", 10, 3)
|
3
11
|
|
4
12
|
module Spreet
|
5
13
|
|
6
14
|
module VERSION
|
7
15
|
version = nil
|
8
|
-
File.open("VERSION") {|f| version = f.read.split('.')}
|
16
|
+
File.open(File.join(File.dirname(__FILE__), "..", "VERSION")) {|f| version = f.read.split('.')}
|
9
17
|
MAJOR = version[0].to_i.freeze
|
10
18
|
MINOR = version[1].to_i.freeze
|
11
19
|
TINY = version[2].to_i.freeze
|
@@ -15,83 +23,42 @@ module Spreet
|
|
15
23
|
end
|
16
24
|
|
17
25
|
|
18
|
-
class Coordinates
|
19
|
-
# Limit coordinates x and y in 0..65535 but coordinates are in one integer of 32 bits
|
20
|
-
CPU_SEMI_WIDTH = 16 # ((RUBY_PLATFORM.match(/^[^\-]*[^\-0-9]64/) ? 64 : 32) / 2).freeze
|
21
|
-
Y_FILTER = ((1 << CPU_SEMI_WIDTH) - 1).freeze
|
22
|
-
|
23
|
-
BASE_26_BEF = "0123456789abcdefghijklmnop"
|
24
|
-
BASE_26_AFT = "abcdefghijklmnopqrstuvwxyz"
|
25
|
-
|
26
|
-
attr_accessor :x, :y
|
27
|
-
def initialize(*args)
|
28
|
-
value = (args.size == 1 ? args[0] : args)
|
29
|
-
@x, @y = 0, 0
|
30
|
-
if value.is_a? String
|
31
|
-
if value.downcase.match(/^[a-z]+[0-9]+$/)
|
32
|
-
value = value.downcase.split(/([A-Z]+|[0-9]+)/).delete_if{|x| x.size.zero?}
|
33
|
-
@x, @y = value[0].tr(BASE_26_AFT, BASE_26_BEF).to_i(26), value[1].to_i(10)-1
|
34
|
-
elsif value.downcase.match(/^[0-9]+[^0-9]+[0-9]+$/)
|
35
|
-
value = value.downcase.split(/[^0-9]+/)
|
36
|
-
@x, @y = value[0].to_i(10), value[1].to_i(10)
|
37
|
-
end
|
38
|
-
elsif value.is_a? Integer
|
39
|
-
@x, @y = (value >> CPU_SEMI_WIDTH), value & Y_FILTER
|
40
|
-
elsif value.is_a? Coordinates
|
41
|
-
@x, @y = value.x, value.y
|
42
|
-
elsif value.is_a? Array
|
43
|
-
@x, @y = value[0].to_i, value[1].to_i
|
44
|
-
elsif value.is_a? Hash
|
45
|
-
@x, @y = value[:x] || value[:column] || 0, value[:y] || value[:row] || 0
|
46
|
-
end
|
47
|
-
end
|
48
|
-
|
49
|
-
def to_s
|
50
|
-
@x.to_s(26).tr(BASE_26_BEF, BASE_26_AFT).upcase+(@y+1).to_s(10)
|
51
|
-
end
|
52
|
-
|
53
|
-
def to_a
|
54
|
-
[@x, @y]
|
55
|
-
end
|
56
|
-
|
57
|
-
def to_hash
|
58
|
-
{:x=>@x, :y=>@y}
|
59
|
-
end
|
60
|
-
|
61
|
-
def to_i
|
62
|
-
(@x << CPU_SEMI_WIDTH) + @y
|
63
|
-
end
|
64
|
-
|
65
|
-
def ==(other_coordinate)
|
66
|
-
other_coordinate.x == self.x and other_coordinate.y == self.y
|
67
|
-
end
|
68
|
-
|
69
|
-
def <=>(other_coordinate)
|
70
|
-
self.to_i <=> other_coordinate.to_i
|
71
|
-
end
|
72
|
-
end
|
73
|
-
|
74
26
|
# Represents a cell in a sheet
|
75
27
|
class Cell
|
76
28
|
attr_reader :text, :value, :type, :sheet, :coordinates
|
29
|
+
attr_accessor :annotation
|
77
30
|
|
78
31
|
def initialize(sheet, *args)
|
79
32
|
@sheet = sheet
|
80
33
|
@coordinates = Coordinates.new(*args)
|
81
34
|
self.value = nil
|
82
35
|
@empty = true
|
36
|
+
@covered = false # determine_covered
|
37
|
+
@annotation = nil
|
83
38
|
end
|
84
39
|
|
85
40
|
def value=(val)
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
41
|
+
if val.is_a?(Cell)
|
42
|
+
@value = val.value
|
43
|
+
@type = val.type
|
44
|
+
self.text = val.text
|
45
|
+
@empty = val.empty?
|
46
|
+
@annotation = val.annotation
|
47
|
+
else
|
48
|
+
@value = val
|
49
|
+
@type = determine_type
|
50
|
+
self.text = val
|
51
|
+
@empty = false
|
52
|
+
end
|
90
53
|
end
|
91
54
|
|
92
55
|
def empty?
|
93
56
|
@empty
|
94
57
|
end
|
58
|
+
|
59
|
+
def covered?
|
60
|
+
@covered
|
61
|
+
end
|
95
62
|
|
96
63
|
def clear!
|
97
64
|
self.value = nil
|
@@ -106,29 +73,35 @@ module Spreet
|
|
106
73
|
self.coordinates <=> other_cell.coordinates
|
107
74
|
end
|
108
75
|
|
76
|
+
def text=(val)
|
77
|
+
@text = val.to_s
|
78
|
+
end
|
79
|
+
|
80
|
+
def inspect
|
81
|
+
"<#{self.coordinates}: #{self.text.inspect}#{'('+self.value.inspect+')' if self.text != self.value}>"
|
82
|
+
end
|
83
|
+
|
109
84
|
private
|
85
|
+
|
110
86
|
|
111
87
|
def determine_type
|
112
|
-
if value.is_a? Date
|
88
|
+
if value.is_a? Date or value.is_a? DateTime
|
113
89
|
:date
|
114
|
-
elsif value.is_a?
|
115
|
-
:
|
116
|
-
elsif value.is_a?
|
117
|
-
:
|
118
|
-
elsif value.is_a?
|
119
|
-
:
|
90
|
+
elsif value.is_a? Numeric # or percentage
|
91
|
+
:float
|
92
|
+
elsif value.is_a? Money
|
93
|
+
:currency
|
94
|
+
elsif value.is_a? Duration
|
95
|
+
:time
|
120
96
|
elsif value.is_a?(TrueClass) or value.is_a?(FalseClass)
|
121
97
|
:boolean
|
122
|
-
|
123
|
-
:null
|
124
|
-
else
|
98
|
+
else # if value.is_a?(String)
|
125
99
|
:string
|
126
100
|
end
|
127
101
|
end
|
128
102
|
|
129
103
|
end
|
130
104
|
|
131
|
-
|
132
105
|
class Sheet
|
133
106
|
attr_reader :document, :name, :columns
|
134
107
|
attr_accessor :current_row
|
@@ -138,7 +111,8 @@ module Spreet
|
|
138
111
|
self.name = name
|
139
112
|
raise ArgumentError.new("Must be a Document") unless document.is_a? Document
|
140
113
|
@current_row = 0
|
141
|
-
@cells = {}
|
114
|
+
@cells = {} # BigArray::Cells.new
|
115
|
+
@bound = compute_bound
|
142
116
|
end
|
143
117
|
|
144
118
|
def name=(value)
|
@@ -152,11 +126,6 @@ module Spreet
|
|
152
126
|
@name = value
|
153
127
|
end
|
154
128
|
|
155
|
-
def cells
|
156
|
-
@cells.delete_if{|k,v| v.empty?}
|
157
|
-
@cells.values
|
158
|
-
end
|
159
|
-
|
160
129
|
def next_row(increment = 1)
|
161
130
|
@current_row += increment
|
162
131
|
end
|
@@ -175,7 +144,7 @@ module Spreet
|
|
175
144
|
value = args.delete_at(-1)
|
176
145
|
cell = self[*args]
|
177
146
|
cell.value = value
|
178
|
-
@
|
147
|
+
@updated = true
|
179
148
|
end
|
180
149
|
|
181
150
|
def row(*args)
|
@@ -188,13 +157,17 @@ module Spreet
|
|
188
157
|
next_row
|
189
158
|
end
|
190
159
|
|
160
|
+
def rows(index)
|
161
|
+
row = []
|
162
|
+
for i in 0..bound.x
|
163
|
+
row[i] = self[i, index]
|
164
|
+
end
|
165
|
+
return row
|
166
|
+
end
|
167
|
+
|
191
168
|
def each_row(&block)
|
192
169
|
for j in 0..bound.y
|
193
|
-
|
194
|
-
for i in 0..bound.x
|
195
|
-
row[i] = self[i, j]
|
196
|
-
end
|
197
|
-
yield row
|
170
|
+
yield rows(j)
|
198
171
|
end
|
199
172
|
end
|
200
173
|
|
@@ -204,13 +177,17 @@ module Spreet
|
|
204
177
|
end
|
205
178
|
|
206
179
|
def bound
|
207
|
-
@
|
180
|
+
if @updated
|
181
|
+
compute_bound
|
182
|
+
else
|
183
|
+
@bound
|
184
|
+
end
|
208
185
|
end
|
209
186
|
|
210
187
|
def remove!(coordinates)
|
211
188
|
raise ArgumentError.new("Must be a Coordinates") unless document.is_a?(Coordinates)
|
212
189
|
@cells.delete(coordinates.to_i)
|
213
|
-
@
|
190
|
+
@updated = true
|
214
191
|
end
|
215
192
|
|
216
193
|
# Moves the sheet to an other position in the list of sheets
|
@@ -231,14 +208,17 @@ module Spreet
|
|
231
208
|
private
|
232
209
|
|
233
210
|
def compute_bound
|
234
|
-
bound = Coordinates.new
|
235
|
-
for
|
211
|
+
bound = Coordinates.new(0,0)
|
212
|
+
for index, cell in @cells
|
213
|
+
# for cell in @cells.compact
|
236
214
|
unless cell.empty?
|
237
215
|
bound.x = cell.coordinates.x if cell.coordinates.x > bound.x
|
238
|
-
bound.y = cell.coordinates.y if cell.coordinates.
|
216
|
+
bound.y = cell.coordinates.y if cell.coordinates.y > bound.y
|
239
217
|
end
|
240
218
|
end
|
241
|
-
|
219
|
+
@updated = false
|
220
|
+
@bound = bound
|
221
|
+
return @bound
|
242
222
|
end
|
243
223
|
|
244
224
|
end
|
@@ -280,11 +260,14 @@ module Spreet
|
|
280
260
|
end
|
281
261
|
|
282
262
|
def remove(sheet)
|
283
|
-
@array.
|
263
|
+
@array.delete_at(index(sheet))
|
284
264
|
end
|
285
265
|
|
286
266
|
def move(sheet, shift=0)
|
287
|
-
|
267
|
+
position = index(sheet) + shift
|
268
|
+
position = 0 if position < 0
|
269
|
+
position = self.count-1 if position >= self.count
|
270
|
+
move_at(sheet, position)
|
288
271
|
end
|
289
272
|
|
290
273
|
def move_at(sheet, position=-1)
|
@@ -310,17 +293,6 @@ module Spreet
|
|
310
293
|
def initialize(option={})
|
311
294
|
@sheets = Sheets.new(self)
|
312
295
|
end
|
313
|
-
|
314
|
-
def to_term
|
315
|
-
text = "Spreet (#{@sheets.count}):\n"
|
316
|
-
for sheet in @sheets
|
317
|
-
text << " - #{sheet.name}:\n"
|
318
|
-
for cell in sheet.cells.sort
|
319
|
-
text << " - #{cell.coordinates.to_s}: #{cell.text.inspect}\n"
|
320
|
-
end
|
321
|
-
end
|
322
|
-
return text
|
323
|
-
end
|
324
296
|
|
325
297
|
def write(file, options={})
|
326
298
|
handler = self.class.extract_handler(file, options.delete(:format))
|