spreet 0.0.2 → 0.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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))
|