ruby-spreadsheet 0.6.5
Sign up to get free protection for your applications and to get access to all the features.
- data/.document +5 -0
- data/GUIDE.txt +267 -0
- data/Gemfile +12 -0
- data/Gemfile.lock +20 -0
- data/History.txt +307 -0
- data/LICENSE.txt +619 -0
- data/README.txt +91 -0
- data/Rakefile +53 -0
- data/VERSION +1 -0
- data/bin/xlsopcodes +18 -0
- data/lib/parseexcel.rb +27 -0
- data/lib/parseexcel/parseexcel.rb +75 -0
- data/lib/parseexcel/parser.rb +11 -0
- data/lib/spreadsheet.rb +79 -0
- data/lib/spreadsheet/column.rb +71 -0
- data/lib/spreadsheet/compatibility.rb +23 -0
- data/lib/spreadsheet/datatypes.rb +110 -0
- data/lib/spreadsheet/encodings.rb +46 -0
- data/lib/spreadsheet/excel.rb +88 -0
- data/lib/spreadsheet/excel/error.rb +26 -0
- data/lib/spreadsheet/excel/internals.rb +386 -0
- data/lib/spreadsheet/excel/internals/biff5.rb +17 -0
- data/lib/spreadsheet/excel/internals/biff8.rb +19 -0
- data/lib/spreadsheet/excel/offset.rb +41 -0
- data/lib/spreadsheet/excel/reader.rb +1173 -0
- data/lib/spreadsheet/excel/reader/biff5.rb +22 -0
- data/lib/spreadsheet/excel/reader/biff8.rb +193 -0
- data/lib/spreadsheet/excel/row.rb +92 -0
- data/lib/spreadsheet/excel/sst_entry.rb +46 -0
- data/lib/spreadsheet/excel/workbook.rb +80 -0
- data/lib/spreadsheet/excel/worksheet.rb +100 -0
- data/lib/spreadsheet/excel/writer.rb +1 -0
- data/lib/spreadsheet/excel/writer/biff8.rb +75 -0
- data/lib/spreadsheet/excel/writer/format.rb +253 -0
- data/lib/spreadsheet/excel/writer/workbook.rb +652 -0
- data/lib/spreadsheet/excel/writer/worksheet.rb +948 -0
- data/lib/spreadsheet/font.rb +92 -0
- data/lib/spreadsheet/format.rb +177 -0
- data/lib/spreadsheet/formula.rb +9 -0
- data/lib/spreadsheet/helpers.rb +11 -0
- data/lib/spreadsheet/link.rb +43 -0
- data/lib/spreadsheet/row.rb +132 -0
- data/lib/spreadsheet/workbook.rb +120 -0
- data/lib/spreadsheet/worksheet.rb +279 -0
- data/lib/spreadsheet/writer.rb +30 -0
- data/ruby-spreadsheet.gemspec +126 -0
- data/test/data/test_changes.xls +0 -0
- data/test/data/test_copy.xls +0 -0
- data/test/data/test_datetime.xls +0 -0
- data/test/data/test_empty.xls +0 -0
- data/test/data/test_formula.xls +0 -0
- data/test/data/test_missing_row.xls +0 -0
- data/test/data/test_version_excel5.xls +0 -0
- data/test/data/test_version_excel95.xls +0 -0
- data/test/data/test_version_excel97.xls +0 -0
- data/test/excel/row.rb +35 -0
- data/test/excel/writer/worksheet.rb +23 -0
- data/test/font.rb +163 -0
- data/test/integration.rb +1281 -0
- data/test/row.rb +33 -0
- data/test/suite.rb +14 -0
- data/test/workbook.rb +21 -0
- data/test/worksheet.rb +80 -0
- metadata +203 -0
@@ -0,0 +1,22 @@
|
|
1
|
+
module Spreadsheet
|
2
|
+
module Excel
|
3
|
+
class Reader
|
4
|
+
##
|
5
|
+
# This Module collects reader methods such as read_string that are specific to
|
6
|
+
# Biff5. This Module is likely to be expanded as Support for older Versions
|
7
|
+
# of Excel grows.
|
8
|
+
module Biff5
|
9
|
+
##
|
10
|
+
# Read a String of 8-bit Characters
|
11
|
+
def read_string work, count_length=1
|
12
|
+
# Offset Size Contents
|
13
|
+
# 0 1 or 2 Length of the string (character count, ln)
|
14
|
+
# 1 or 2 ln Character array (8-bit characters)
|
15
|
+
fmt = count_length == 1 ? 'C' : 'v'
|
16
|
+
length, = work.unpack fmt
|
17
|
+
work[count_length, length]
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,193 @@
|
|
1
|
+
module Spreadsheet
|
2
|
+
module Excel
|
3
|
+
class Reader
|
4
|
+
##
|
5
|
+
# This Module collects reader methods such as read_string that are specific to
|
6
|
+
# Biff8. This Module is likely to be expanded as Support for older Versions
|
7
|
+
# of Excel grows and methods get moved here for disambiguation.
|
8
|
+
module Biff8
|
9
|
+
include Spreadsheet::Excel::Internals
|
10
|
+
##
|
11
|
+
# When a String is too long for one Opcode, it is continued in a Continue
|
12
|
+
# Opcode. Excel may reconsider compressing the remainder of the string.
|
13
|
+
# This method appends the available remainder (decompressed if necessary) to
|
14
|
+
# the incomplete string.
|
15
|
+
def continue_string work, incomplete_string=@incomplete_string
|
16
|
+
opts, _ = work.unpack 'C'
|
17
|
+
wide = opts & 1
|
18
|
+
head, chars = incomplete_string
|
19
|
+
owing = chars - head.size / 2
|
20
|
+
size = owing * (wide + 1)
|
21
|
+
string = work[1, size]
|
22
|
+
if wide == 0
|
23
|
+
string = wide string
|
24
|
+
end
|
25
|
+
head << string
|
26
|
+
if head.size >= chars * 2
|
27
|
+
@incomplete_string = nil
|
28
|
+
end
|
29
|
+
size + 1
|
30
|
+
end
|
31
|
+
##
|
32
|
+
# When a String is too long for one Opcode, it is continued in a Continue
|
33
|
+
# Opcode. Excel may reconsider compressing the remainder of the string.
|
34
|
+
# This method only evaluates the header and registers the address of the
|
35
|
+
# continuation with the previous SstEntry.
|
36
|
+
def continue_string_header work, oppos
|
37
|
+
opts, _ = work.unpack 'C'
|
38
|
+
wide = opts & 1
|
39
|
+
owing = @incomplete_sst.continued_chars
|
40
|
+
size = [work.size, owing * (1 + wide) + 1].min
|
41
|
+
chars = (size - 1) / (1 + wide)
|
42
|
+
skip = size
|
43
|
+
@incomplete_sst.continue oppos + OPCODE_SIZE, size, chars
|
44
|
+
unless @incomplete_sst.continued?
|
45
|
+
@workbook.add_shared_string @incomplete_sst
|
46
|
+
skip += @incomplete_skip
|
47
|
+
@incomplete_sst = nil
|
48
|
+
@incomplete_skip = nil
|
49
|
+
end
|
50
|
+
skip
|
51
|
+
end
|
52
|
+
##
|
53
|
+
# Read more data into the Shared String Table. (see also: #read_sst)
|
54
|
+
# This method only evaluates the header, the actual work is done in #_read_sst
|
55
|
+
def continue_sst work, oppos, len
|
56
|
+
pos = 0
|
57
|
+
if @incomplete_sst
|
58
|
+
pos = continue_string_header work, oppos
|
59
|
+
end
|
60
|
+
@sst_offset[1] += len
|
61
|
+
_read_sst work, oppos, pos
|
62
|
+
end
|
63
|
+
def postread_workbook # :nodoc:
|
64
|
+
super
|
65
|
+
@incomplete_string, @sst_size, @sst_offset, @incomplete_sst = nil, @incomplete_skip = nil
|
66
|
+
end
|
67
|
+
##
|
68
|
+
# Store the offset of extsst, so we can write a new extsst when the
|
69
|
+
# sst has changed
|
70
|
+
def read_extsst work, pos, len
|
71
|
+
@workbook.offsets.store :extsst, [pos, len]
|
72
|
+
end
|
73
|
+
##
|
74
|
+
# Read the Shared String Table present in all Biff8 Files.
|
75
|
+
# This method only evaluates the header, the actual work is done in #_read_sst
|
76
|
+
def read_sst work, pos, len
|
77
|
+
# Offset Size Contents
|
78
|
+
# 0 4 Total number of strings in the workbook (see below)
|
79
|
+
# 4 4 Number of following strings (nm)
|
80
|
+
# 8 var. List of nm Unicode strings, 16-bit string length (➜ 3.4)
|
81
|
+
total, @sst_size = work.unpack 'V2'
|
82
|
+
@sst_offset = [pos, len]
|
83
|
+
@workbook.offsets.store :sst, @sst_offset
|
84
|
+
_read_sst work, pos, 8
|
85
|
+
end
|
86
|
+
##
|
87
|
+
# Read a string from the Spreadsheet, such as a Worksheet- or Font-Name, or a
|
88
|
+
# Number-Format. See also #read_string_header and #read_string_body
|
89
|
+
def read_string work, count_length=1
|
90
|
+
# Offset Size Contents
|
91
|
+
# 0 1 or 2 Length of the string (character count, ln)
|
92
|
+
# 1 or 2 1 Option flags:
|
93
|
+
# Bit Mask Contents
|
94
|
+
# 0 0x01 Character compression (ccompr):
|
95
|
+
# 0 = Compressed (8-bit characters)
|
96
|
+
# 1 = Uncompressed (16-bit characters)
|
97
|
+
# 2 0x04 Asian phonetic settings (phonetic):
|
98
|
+
# 0 = Does not contain Asian phonetic settings
|
99
|
+
# 1 = Contains Asian phonetic settings
|
100
|
+
# 3 0x08 Rich-Text settings (richtext):
|
101
|
+
# 0 = Does not contain Rich-Text settings
|
102
|
+
# 1 = Contains Rich-Text settings
|
103
|
+
# [2 or 3] 2 (optional, only if richtext=1)
|
104
|
+
# Number of Rich-Text formatting runs (rt)
|
105
|
+
# [var.] 4 (optional, only if phonetic=1)
|
106
|
+
# Size of Asian phonetic settings block (in bytes, sz)
|
107
|
+
# var. ln Character array (8-bit characters
|
108
|
+
# or 2∙ln or 16-bit characters, dependent on ccompr)
|
109
|
+
# [var.] 4∙rt (optional, only if richtext=1)
|
110
|
+
# List of rt formatting runs (➜ 3.2)
|
111
|
+
# [var.] sz (optional, only if phonetic=1)
|
112
|
+
# Asian Phonetic Settings Block (➜ 3.4.2)
|
113
|
+
chars, offset, wide, phonetic, richtext, available, owing, skip \
|
114
|
+
= read_string_header work, count_length
|
115
|
+
string, data = read_string_body work, offset, available, wide > 0
|
116
|
+
if owing > 0
|
117
|
+
@incomplete_string = [string, chars]
|
118
|
+
end
|
119
|
+
string
|
120
|
+
end
|
121
|
+
##
|
122
|
+
# Read the body of a string. Returns the String (decompressed if necessary) and
|
123
|
+
# the available data (unchanged).
|
124
|
+
def read_string_body work, offset, available, wide
|
125
|
+
data = work[offset, available]
|
126
|
+
string = wide ? data : wide(data)
|
127
|
+
[string, data]
|
128
|
+
end
|
129
|
+
##
|
130
|
+
# Read the header of a string. Returns the following information in an Array:
|
131
|
+
# * The total number of characters in the string
|
132
|
+
# * The offset of the actual string data (= the length of this header in bytes)
|
133
|
+
# * Whether or not the string was compressed (0/1)
|
134
|
+
# * Whether or not the string contains asian phonetic settings (0/1)
|
135
|
+
# * Whether or not the string contains richtext formatting (0/1)
|
136
|
+
# * The number of bytes containing characters in this chunk of data
|
137
|
+
# * The number of characters missing from this chunk of data and expected to
|
138
|
+
# follow in a Continue Opcode
|
139
|
+
def read_string_header work, count_length=1, offset=0
|
140
|
+
fmt = count_length == 1 ? 'C2' : 'vC'
|
141
|
+
chars, opts = work[offset, 1 + count_length].unpack fmt
|
142
|
+
wide = opts & 1
|
143
|
+
phonetic = (opts >> 2) & 1
|
144
|
+
richtext = (opts >> 3) & 1
|
145
|
+
size = chars * (wide + 1)
|
146
|
+
skip = 0
|
147
|
+
if richtext > 0
|
148
|
+
runs, = work[offset + 1 + count_length, 2].unpack 'v'
|
149
|
+
skip = 4 * runs
|
150
|
+
end
|
151
|
+
if phonetic > 0
|
152
|
+
psize, = work[offset + 1 + count_length + richtext * 2, 4].unpack 'V'
|
153
|
+
skip += psize
|
154
|
+
end
|
155
|
+
flagsize = 1 + count_length + richtext * 2 + phonetic * 4
|
156
|
+
avbl = [work.size - offset, flagsize + size].min
|
157
|
+
have_chrs = (avbl - flagsize) / (1 + wide)
|
158
|
+
owing = chars - have_chrs
|
159
|
+
[chars, flagsize, wide, phonetic, richtext, avbl, owing, skip]
|
160
|
+
end
|
161
|
+
##
|
162
|
+
# Insert null-characters into a compressed UTF-16 string
|
163
|
+
def wide string
|
164
|
+
data = ''
|
165
|
+
string.each_byte do |byte| data << byte.chr << 0.chr end
|
166
|
+
data
|
167
|
+
end
|
168
|
+
private
|
169
|
+
##
|
170
|
+
# Read the Shared String Table present in all Biff8 Files.
|
171
|
+
def _read_sst work, oppos, pos
|
172
|
+
worksize = work.size
|
173
|
+
while @workbook.sst_size < @sst_size && pos < worksize do
|
174
|
+
sst = SstEntry.new :offset => oppos + OPCODE_SIZE + pos,
|
175
|
+
:ole => @data,
|
176
|
+
:reader => self
|
177
|
+
sst.chars, sst.flags, wide, sst.phonetic, sst.richtext, sst.available,
|
178
|
+
sst.continued_chars, skip = read_string_header work, 2, pos
|
179
|
+
sst.wide = wide > 0
|
180
|
+
if sst.continued?
|
181
|
+
@incomplete_sst = sst
|
182
|
+
@incomplete_skip = skip
|
183
|
+
pos += sst.available
|
184
|
+
else
|
185
|
+
@workbook.add_shared_string sst
|
186
|
+
pos += sst.available + skip
|
187
|
+
end
|
188
|
+
end
|
189
|
+
end
|
190
|
+
end
|
191
|
+
end
|
192
|
+
end
|
193
|
+
end
|
@@ -0,0 +1,92 @@
|
|
1
|
+
require 'date'
|
2
|
+
require 'spreadsheet/row'
|
3
|
+
|
4
|
+
module Spreadsheet
|
5
|
+
module Excel
|
6
|
+
##
|
7
|
+
# Excel-specific Row methods
|
8
|
+
class Row < Spreadsheet::Row
|
9
|
+
##
|
10
|
+
# The Excel date calculation erroneously assumes that 1900 is a leap-year. All
|
11
|
+
# Dates after 28.2.1900 are off by one.
|
12
|
+
LEAP_ERROR = Date.new 1900, 2, 28
|
13
|
+
##
|
14
|
+
# Force convert the cell at _idx_ to a Date
|
15
|
+
def date idx
|
16
|
+
_date at(idx)
|
17
|
+
end
|
18
|
+
##
|
19
|
+
# Force convert the cell at _idx_ to a DateTime
|
20
|
+
def datetime idx
|
21
|
+
_datetime at(idx)
|
22
|
+
end
|
23
|
+
def each &block
|
24
|
+
size.times do |idx|
|
25
|
+
block.call self[idx]
|
26
|
+
end
|
27
|
+
end
|
28
|
+
##
|
29
|
+
# Access data in this Row like you would in an Array. If a cell is formatted
|
30
|
+
# as a Date or DateTime, the decoded Date or DateTime value is returned.
|
31
|
+
def [] idx, len=nil
|
32
|
+
if len
|
33
|
+
idx = idx...(idx+len)
|
34
|
+
end
|
35
|
+
if idx.is_a? Range
|
36
|
+
data = []
|
37
|
+
idx.each do |i|
|
38
|
+
data.push enriched_data(i, at(i))
|
39
|
+
end
|
40
|
+
data
|
41
|
+
else
|
42
|
+
enriched_data idx, at(idx)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
private
|
46
|
+
def _date data # :nodoc:
|
47
|
+
return data if data.is_a?(Date)
|
48
|
+
datetime = _datetime data
|
49
|
+
Date.new datetime.year, datetime.month, datetime.day
|
50
|
+
end
|
51
|
+
def _datetime data # :nodoc:
|
52
|
+
return data if data.is_a?(DateTime)
|
53
|
+
base = @worksheet.date_base
|
54
|
+
date = base + data.to_f
|
55
|
+
hour = (data % 1) * 24
|
56
|
+
min = (hour % 1) * 60
|
57
|
+
sec = ((min % 1) * 60).round
|
58
|
+
min = min.floor
|
59
|
+
hour = hour.floor
|
60
|
+
if sec > 59
|
61
|
+
sec = 0
|
62
|
+
min += 1
|
63
|
+
end
|
64
|
+
if min > 59
|
65
|
+
min = 0
|
66
|
+
hour += 1
|
67
|
+
end
|
68
|
+
if hour > 23
|
69
|
+
hour = 0
|
70
|
+
date += 1
|
71
|
+
end
|
72
|
+
if LEAP_ERROR > base
|
73
|
+
date -= 1
|
74
|
+
end
|
75
|
+
DateTime.new(date.year, date.month, date.day, hour, min, sec)
|
76
|
+
end
|
77
|
+
def enriched_data idx, data # :nodoc:
|
78
|
+
res = nil
|
79
|
+
if link = @worksheet.links[[@idx, idx]]
|
80
|
+
res = link
|
81
|
+
elsif data.is_a?(Numeric) && fmt = format(idx)
|
82
|
+
res = if fmt.datetime? || fmt.time?
|
83
|
+
_datetime data
|
84
|
+
elsif fmt.date?
|
85
|
+
_date data
|
86
|
+
end
|
87
|
+
end
|
88
|
+
res || data
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
require 'spreadsheet/encodings'
|
2
|
+
|
3
|
+
module Spreadsheet
|
4
|
+
module Excel
|
5
|
+
##
|
6
|
+
# Shared String Table Entry
|
7
|
+
class SstEntry
|
8
|
+
include Spreadsheet::Encodings
|
9
|
+
attr_accessor :chars, :phonetic, :richtext, :flags, :available,
|
10
|
+
:continued_chars, :wide
|
11
|
+
def initialize opts = {}
|
12
|
+
@content = nil
|
13
|
+
@offset = opts[:offset]
|
14
|
+
@ole = opts[:ole]
|
15
|
+
@reader = opts[:reader]
|
16
|
+
@continuations = []
|
17
|
+
end
|
18
|
+
##
|
19
|
+
# Access the contents of this Shared String
|
20
|
+
def content
|
21
|
+
@content or begin
|
22
|
+
data = nil
|
23
|
+
data = @ole[@offset, @available]
|
24
|
+
content, _ = @reader.read_string_body data, @flags, @available, @wide
|
25
|
+
@continuations.each do |offset, len|
|
26
|
+
@reader.continue_string(@ole[offset,len], [content, @chars])
|
27
|
+
end
|
28
|
+
content = client content, 'UTF-16LE'
|
29
|
+
if @reader.memoize?
|
30
|
+
@content = content
|
31
|
+
end
|
32
|
+
content
|
33
|
+
end
|
34
|
+
end
|
35
|
+
##
|
36
|
+
# Register the offset of a String continuation
|
37
|
+
def continue offset, size, chars
|
38
|
+
@continued_chars -= chars
|
39
|
+
@continuations.push [offset, size]
|
40
|
+
end
|
41
|
+
def continued? # :nodoc:
|
42
|
+
@continued_chars > 0
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
require 'spreadsheet/workbook'
|
2
|
+
require 'spreadsheet/excel/offset'
|
3
|
+
require 'spreadsheet/excel/writer'
|
4
|
+
require 'ole/storage'
|
5
|
+
|
6
|
+
module Spreadsheet
|
7
|
+
module Excel
|
8
|
+
##
|
9
|
+
# Excel-specific Workbook methods. These are mostly pertinent to the Excel
|
10
|
+
# reader. You should have no reason to use any of these.
|
11
|
+
class Workbook < Spreadsheet::Workbook
|
12
|
+
include Spreadsheet::Encodings
|
13
|
+
include Spreadsheet::Excel::Offset
|
14
|
+
BIFF_VERSIONS = {
|
15
|
+
0x000 => 2,
|
16
|
+
0x007 => 2,
|
17
|
+
0x200 => 2,
|
18
|
+
0x300 => 3,
|
19
|
+
0x400 => 4,
|
20
|
+
0x500 => 5,
|
21
|
+
0x600 => 8,
|
22
|
+
}
|
23
|
+
VERSION_STRINGS = {
|
24
|
+
0x600 => 'Microsoft Excel 97/2000/XP',
|
25
|
+
0x500 => 'Microsoft Excel 95',
|
26
|
+
}
|
27
|
+
offset :encoding, :boundsheets, :sst
|
28
|
+
attr_accessor :bof, :ole
|
29
|
+
attr_writer :date_base
|
30
|
+
def Workbook.open io, opts = {}
|
31
|
+
@reader = Reader.new opts
|
32
|
+
@reader.read io
|
33
|
+
end
|
34
|
+
def initialize *args
|
35
|
+
super
|
36
|
+
enc = 'UTF-16LE'
|
37
|
+
if RUBY_VERSION >= '1.9'
|
38
|
+
enc = Encoding.find enc
|
39
|
+
end
|
40
|
+
@encoding = enc
|
41
|
+
@version = 0x600
|
42
|
+
@sst = []
|
43
|
+
end
|
44
|
+
def add_shared_string str
|
45
|
+
@sst.push str
|
46
|
+
end
|
47
|
+
def add_worksheet worksheet
|
48
|
+
@changes.store :boundsheets, true
|
49
|
+
super
|
50
|
+
end
|
51
|
+
def biff_version
|
52
|
+
case @bof
|
53
|
+
when 0x009
|
54
|
+
2
|
55
|
+
when 0x209
|
56
|
+
3
|
57
|
+
when 0x409
|
58
|
+
4
|
59
|
+
else
|
60
|
+
BIFF_VERSIONS.fetch(@version) { raise "Unkown BIFF_VERSION '#@version'" }
|
61
|
+
end
|
62
|
+
end
|
63
|
+
def date_base
|
64
|
+
@date_base ||= DateTime.new 1899, 12, 31
|
65
|
+
end
|
66
|
+
def shared_string idx
|
67
|
+
@sst[idx.to_i].content
|
68
|
+
end
|
69
|
+
def sst_size
|
70
|
+
@sst.size
|
71
|
+
end
|
72
|
+
def uninspect_variables
|
73
|
+
super.push '@sst', '@offsets', '@changes'
|
74
|
+
end
|
75
|
+
def version_string
|
76
|
+
client VERSION_STRINGS.fetch(@version, "Unknown"), 'UTF-8'
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
@@ -0,0 +1,100 @@
|
|
1
|
+
require 'spreadsheet/excel/offset'
|
2
|
+
require 'spreadsheet/excel/row'
|
3
|
+
require 'spreadsheet/worksheet'
|
4
|
+
|
5
|
+
module Spreadsheet
|
6
|
+
module Excel
|
7
|
+
##
|
8
|
+
# Excel-specific Worksheet methods. These are mostly pertinent to the Excel
|
9
|
+
# reader, and to recording changes to the Worksheet. You should have no reason
|
10
|
+
# to use any of these.
|
11
|
+
class Worksheet < Spreadsheet::Worksheet
|
12
|
+
include Spreadsheet::Excel::Offset
|
13
|
+
offset :dimensions
|
14
|
+
attr_reader :offset, :ole, :links, :guts
|
15
|
+
def initialize opts = {}
|
16
|
+
@row_addresses = nil
|
17
|
+
super
|
18
|
+
@offset, @ole, @reader = opts[:offset], opts[:ole], opts[:reader]
|
19
|
+
@dimensions = nil
|
20
|
+
@links = {}
|
21
|
+
@guts = {}
|
22
|
+
end
|
23
|
+
def add_link row, column, link
|
24
|
+
@links.store [row, column], link
|
25
|
+
end
|
26
|
+
def column idx
|
27
|
+
ensure_rows_read
|
28
|
+
super
|
29
|
+
end
|
30
|
+
def date_base
|
31
|
+
@workbook.date_base
|
32
|
+
end
|
33
|
+
def each *args
|
34
|
+
ensure_rows_read
|
35
|
+
super
|
36
|
+
end
|
37
|
+
def ensure_rows_read
|
38
|
+
return if @row_addresses
|
39
|
+
@dimensions = nil
|
40
|
+
@row_addresses = []
|
41
|
+
@reader.read_worksheet self, @offset if @reader
|
42
|
+
end
|
43
|
+
def row idx
|
44
|
+
@rows[idx] or begin
|
45
|
+
ensure_rows_read
|
46
|
+
if addr = @row_addresses[idx]
|
47
|
+
row = @reader.read_row self, addr
|
48
|
+
[:default_format, :height, :outline_level, :hidden, ].each do |key|
|
49
|
+
row.send "unupdated_#{key}=", addr[key]
|
50
|
+
end
|
51
|
+
row.worksheet = self
|
52
|
+
row
|
53
|
+
else
|
54
|
+
Row.new self, idx
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
def row_updated idx, row
|
59
|
+
res = super
|
60
|
+
@workbook.changes.store self, true
|
61
|
+
@workbook.changes.store :boundsheets, true
|
62
|
+
@changes.store idx, true
|
63
|
+
@changes.store :dimensions, true
|
64
|
+
res
|
65
|
+
end
|
66
|
+
def set_row_address idx, opts
|
67
|
+
@offsets.store idx, opts[:row_block]
|
68
|
+
@row_addresses[idx] = opts
|
69
|
+
end
|
70
|
+
def shared_string idx
|
71
|
+
@workbook.shared_string idx
|
72
|
+
end
|
73
|
+
private
|
74
|
+
## premature optimization?
|
75
|
+
def have_set_dimensions value, pos, len
|
76
|
+
if @row_addresses.size < row_count
|
77
|
+
@row_addresses.concat Array.new(row_count - @row_addresses.size)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
def recalculate_dimensions
|
81
|
+
ensure_rows_read
|
82
|
+
shorten @rows
|
83
|
+
@dimensions = []
|
84
|
+
@dimensions[0] = [ index_of_first(@rows),
|
85
|
+
index_of_first(@row_addresses) ].compact.min || 0
|
86
|
+
@dimensions[1] = [ @rows.size, @row_addresses.size ].compact.max || 0
|
87
|
+
compact = @rows.compact
|
88
|
+
first_rows = compact.collect do |row| row.first_used end.compact.min
|
89
|
+
first_addrs = @row_addresses.compact.collect do |addr|
|
90
|
+
addr[:first_used] end.min
|
91
|
+
@dimensions[2] = [ first_rows, first_addrs ].compact.min || 0
|
92
|
+
last_rows = compact.collect do |row| row.first_unused end.max
|
93
|
+
last_addrs = @row_addresses.compact.collect do |addr|
|
94
|
+
addr[:first_unused] end.max
|
95
|
+
@dimensions[3] = [last_rows, last_addrs].compact.max || 0
|
96
|
+
@dimensions
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|