spreadsheet 0.6.1.9 → 0.6.2
Sign up to get free protection for your applications and to get access to all the features.
- data/History.txt +67 -0
- data/lib/spreadsheet.rb +1 -1
- data/lib/spreadsheet/column.rb +1 -1
- data/lib/spreadsheet/datatypes.rb +3 -3
- data/lib/spreadsheet/excel/internals.rb +2 -1
- data/lib/spreadsheet/excel/reader.rb +59 -22
- data/lib/spreadsheet/excel/reader/biff8.rb +8 -1
- data/lib/spreadsheet/excel/row.rb +14 -3
- data/lib/spreadsheet/excel/worksheet.rb +5 -3
- data/lib/spreadsheet/excel/writer/workbook.rb +37 -8
- data/lib/spreadsheet/excel/writer/worksheet.rb +103 -41
- data/lib/spreadsheet/row.rb +24 -2
- data/lib/spreadsheet/worksheet.rb +5 -2
- data/test/integration.rb +64 -7
- metadata +2 -2
data/History.txt
CHANGED
@@ -1,3 +1,70 @@
|
|
1
|
+
=== 0.6.2 / 2008-12-11
|
2
|
+
|
3
|
+
* 14 Bugfixes
|
4
|
+
|
5
|
+
* Fixed a bug where #<boolean>! methods did not trigger a call to
|
6
|
+
#row_updated
|
7
|
+
|
8
|
+
* Corrected the Row-Format in both Reader and Writer (was Biff5 for some
|
9
|
+
reason)
|
10
|
+
|
11
|
+
* Populates Row-instances with @default_format, @height, @outline_level
|
12
|
+
and @hidden attributes
|
13
|
+
|
14
|
+
* Fixed a Bug where Workbooks deriving from a Template-Workbook without
|
15
|
+
SST could not be saved
|
16
|
+
Reported in
|
17
|
+
http://rubyforge.org/tracker/index.php?func=detail&aid=22863&group_id=678&atid=2678
|
18
|
+
|
19
|
+
* Improved handling of Numeric Values (writes a RK-Entry for a Float
|
20
|
+
only if it can be encoded with 4 leading zeroes, and a Number-Entry for an
|
21
|
+
Integer only if it cannot be encoded as an RK)
|
22
|
+
|
23
|
+
* Fixes a bug where changes to a Row were ignored if they were
|
24
|
+
outside of an existing Row-Block.
|
25
|
+
|
26
|
+
* Fixes a bug where MULRK-Entries sometimes only contained a single RK
|
27
|
+
|
28
|
+
* Fixes a bug where formatting was ignored if it was applied to empty Rows
|
29
|
+
Reported by Zomba Lumix in
|
30
|
+
http://rubyforge.org/forum/message.php?msg_id=61985
|
31
|
+
|
32
|
+
* Fixes a bug where modifying a Row in a loaded Workbook could lead to Rows
|
33
|
+
with smaller indices being set to nil.
|
34
|
+
Reported by Ivan Samsonov in
|
35
|
+
http://rubyforge.org/forum/message.php?msg_id=62816
|
36
|
+
|
37
|
+
* Deals with rounding-problems when calculating Time
|
38
|
+
Reported by Bughunter extraordinaire Bjørn Hjelle
|
39
|
+
|
40
|
+
* Correct splitting of wide characters in SST
|
41
|
+
Reported by Michel Ziegler and by Eugene Mikhailov in
|
42
|
+
http://rubyforge.org/tracker/index.php?func=detail&aid=23085&group_id=678&atid=2677
|
43
|
+
|
44
|
+
* Fix an off-by-one error in write_mulrk that caused Excel to complain that
|
45
|
+
'Data may be lost', reported by Emma in
|
46
|
+
http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-talk/321979
|
47
|
+
and by Chris Lowis in
|
48
|
+
http://rubyforge.org/tracker/index.php?func=detail&aid=22892&group_id=678&atid=2677
|
49
|
+
|
50
|
+
|
51
|
+
* Read formats correctly in read_mulrk
|
52
|
+
Reported by Ivan Samsonov
|
53
|
+
Fixes that part of http://rubyforge.org/forum/message.php?msg_id=62821
|
54
|
+
which is a bug. Does nothing for the disappearance of Rich-Text
|
55
|
+
formatting, which will not be addressed until 0.7.0
|
56
|
+
|
57
|
+
* Fixes a (benign?) bug, where adding text to a template-file resulted in
|
58
|
+
a duplicate extsst-record.
|
59
|
+
|
60
|
+
* 2 minor enhancements
|
61
|
+
|
62
|
+
* Improved recognition of Time-Formats
|
63
|
+
|
64
|
+
* Improvement to Robustness: allow Spreadsheet::Workbook.new
|
65
|
+
Takes care of http://rubyforge.org/forum/message.php?msg_id=62941
|
66
|
+
Reported by David Chamberlain
|
67
|
+
|
1
68
|
=== 0.6.1.9 / 2008-11-07
|
2
69
|
|
3
70
|
* 1 Bugfix
|
data/lib/spreadsheet.rb
CHANGED
data/lib/spreadsheet/column.rb
CHANGED
@@ -37,7 +37,7 @@ module Spreadsheet
|
|
37
37
|
attr_reader :default_format, :idx
|
38
38
|
boolean :hidden, :collapsed
|
39
39
|
enum :outline_level, 0, Integer
|
40
|
-
updater :
|
40
|
+
updater :collapsed, :hidden, :outline_level, :width
|
41
41
|
def initialize idx, format, opts={}
|
42
42
|
@idx = idx
|
43
43
|
opts[:width] ||= 10
|
@@ -26,13 +26,13 @@ class << self
|
|
26
26
|
define_method "#{key}?" do
|
27
27
|
!!instance_variable_get("@#{key}")
|
28
28
|
end
|
29
|
-
define_method "#{key}!" do
|
30
|
-
instance_variable_set("@#{key}", true)
|
31
|
-
end
|
32
29
|
define_method "#{key}=" do |arg|
|
33
30
|
arg = false if arg == 0
|
34
31
|
instance_variable_set("@#{key}", !!arg)
|
35
32
|
end
|
33
|
+
define_method "#{key}!" do
|
34
|
+
send "#{key}=", true
|
35
|
+
end
|
36
36
|
end
|
37
37
|
end
|
38
38
|
##
|
@@ -99,7 +99,7 @@ module Internals
|
|
99
99
|
:labelsst => 'v3V',
|
100
100
|
:number => "v3#{EIGHT_BYTE_DOUBLE}",
|
101
101
|
:rk => 'v3V',
|
102
|
-
:row => '
|
102
|
+
:row => 'v4x4V',
|
103
103
|
:xf => 'v3C4V2v',
|
104
104
|
}
|
105
105
|
# From BIFF5 on, the built-in number formats will be omitted. The built-in
|
@@ -339,6 +339,7 @@ module Internals
|
|
339
339
|
:distributed => 4,
|
340
340
|
}
|
341
341
|
NGILA_V_FX = XF_V_ALIGN.invert
|
342
|
+
OPCODE_SIZE = 4
|
342
343
|
def binfmt key
|
343
344
|
BINARY_FORMATS[key]
|
344
345
|
end
|
@@ -16,7 +16,6 @@ module Spreadsheet
|
|
16
16
|
class Reader
|
17
17
|
include Spreadsheet::Encodings
|
18
18
|
include Spreadsheet::Excel::Internals
|
19
|
-
OPCODE_SIZE = 4
|
20
19
|
ROW_BLOCK_OPS = [
|
21
20
|
:blank, :boolerr, :dbcell, :formula, :label, :labelsst, :mulblank, :mulrk,
|
22
21
|
:number, :rk, :rstring,
|
@@ -598,7 +597,7 @@ class Reader
|
|
598
597
|
# 4+2∙nc 2 Index to last column (lc)
|
599
598
|
row, column, *xfs = work.unpack 'v*'
|
600
599
|
last_column = xfs.pop # unused
|
601
|
-
xfs.
|
600
|
+
xfs.each_with_index do |xf, idx| set_cell worksheet, row, column + idx, xf end
|
602
601
|
end
|
603
602
|
def read_mulrk worksheet, addr, work
|
604
603
|
# Offset Size Contents
|
@@ -746,7 +745,8 @@ class Reader
|
|
746
745
|
read_sst work, pos, len
|
747
746
|
# TODO: implement memory-efficient sst handling, possibly in conjunction
|
748
747
|
# with EXTSST
|
749
|
-
|
748
|
+
when :extsst # ● EXTSST ➜ 6.40
|
749
|
+
read_extsst work, pos, len
|
750
750
|
when :style # ●● STYLE ➜ 6.99
|
751
751
|
read_style work, pos, len
|
752
752
|
when :format # ○○ FORMAT (Number Format) ➜ 6.45
|
@@ -986,29 +986,66 @@ class Reader
|
|
986
986
|
# 15 0x8000 0 = Row has custom height;
|
987
987
|
# 1 = Row has default height
|
988
988
|
# 8 2 Not used
|
989
|
-
# 10
|
990
|
-
#
|
991
|
-
#
|
992
|
-
#
|
993
|
-
#
|
994
|
-
#
|
989
|
+
# 10 2 In BIFF3-BIFF4 this field contains a relative offset to
|
990
|
+
# calculate stream position of the first cell record for this
|
991
|
+
# row (➜ 5.7.1). In BIFF5-BIFF8 this field is not used
|
992
|
+
# anymore, but the DBCELL record (➜ 6.26) instead.
|
993
|
+
# 12 4 Option flags and default row formatting:
|
994
|
+
# Bit Mask Contents
|
995
|
+
# 2-0 0x00000007 Outline level of the row
|
996
|
+
# 4 0x00000010 1 = Outline group starts or ends here
|
997
|
+
# (depending on where the outline
|
998
|
+
# buttons are located, see WSBOOL
|
999
|
+
# record, ➜ 6.113), and is collapsed
|
1000
|
+
# 5 0x00000020 1 = Row is hidden (manually, or by a
|
1001
|
+
# filter or outline group)
|
1002
|
+
# 6 0x00000040 1 = Row height and default font height
|
1003
|
+
# do not match
|
1004
|
+
# 7 0x00000080 1 = Row has explicit default format (fl)
|
1005
|
+
# 8 0x00000100 Always 1
|
1006
|
+
# 27-16 0x0fff0000 If fl = 1: Index to default XF record
|
1007
|
+
# (➜ 6.115)
|
1008
|
+
# 28 0x10000000 1 = Additional space above the row.
|
1009
|
+
# This flag is set, if the upper
|
1010
|
+
# border of at least one cell in this
|
1011
|
+
# row or if the lower border of at
|
1012
|
+
# least one cell in the row above is
|
1013
|
+
# formatted with a thick line style.
|
1014
|
+
# Thin and medium line styles are not
|
1015
|
+
# taken into account.
|
1016
|
+
# 29 0x20000000 1 = Additional space below the row.
|
1017
|
+
# This flag is set, if the lower
|
1018
|
+
# border of at least one cell in this
|
1019
|
+
# row or if the upper border of at
|
1020
|
+
# least one cell in the row below is
|
1021
|
+
# formatted with a medium or thick
|
1022
|
+
# line style. Thin line styles are
|
1023
|
+
# not taken into account.
|
995
1024
|
@current_row_block_offset ||= [pos]
|
996
|
-
index, first_used, first_unused, flags
|
997
|
-
|
1025
|
+
index, first_used, first_unused, height, flags = work.unpack binfmt(:row)
|
1026
|
+
height &= 0x7fff
|
998
1027
|
format = nil
|
999
1028
|
# TODO: read attributes from work[13,3], read flags
|
1000
|
-
|
1001
|
-
|
1002
|
-
|
1029
|
+
attrs = {
|
1030
|
+
:default_format => format,
|
1031
|
+
:first_used => first_used,
|
1032
|
+
:first_unused => first_unused,
|
1033
|
+
:index => index,
|
1034
|
+
:row_block => @current_row_block_offset,
|
1035
|
+
:offset => @current_row_block_offset[0],
|
1036
|
+
:outline_level => flags & 0x00000007,
|
1037
|
+
:collapsed => (flags & 0x0000010) > 0,
|
1038
|
+
:hidden => (flags & 0x0000020) > 0,
|
1039
|
+
}
|
1040
|
+
if (flags & 0x00000040) > 0
|
1041
|
+
attrs.store :height, height / TWIPS
|
1042
|
+
end
|
1043
|
+
if (flags & 0x00000080) > 0
|
1044
|
+
xf = (flags & 0x0fff0000) >> 16
|
1045
|
+
attrs.store :default_format, @workbook.format(xf)
|
1003
1046
|
end
|
1004
|
-
|
1005
|
-
|
1006
|
-
:first_used => first_used,
|
1007
|
-
:first_unused => first_unused,
|
1008
|
-
:index => index,
|
1009
|
-
:row_block => @current_row_block_offset,
|
1010
|
-
:offset => @current_row_block_offset[0]
|
1011
|
-
#:first_cell => offset
|
1047
|
+
# TODO: Row spacing
|
1048
|
+
worksheet.set_row_address index, attrs
|
1012
1049
|
end
|
1013
1050
|
private
|
1014
1051
|
def extend_internals version
|
@@ -6,6 +6,7 @@ module Spreadsheet
|
|
6
6
|
# Biff8. This Module is likely to be expanded as Support for older Versions
|
7
7
|
# of Excel grows and methods get moved here for disambiguation.
|
8
8
|
module Biff8
|
9
|
+
include Spreadsheet::Excel::Internals
|
9
10
|
##
|
10
11
|
# When a String is too long for one Opcode, it is continued in a Continue
|
11
12
|
# Opcode. Excel may reconsider compressing the remainder of the string.
|
@@ -37,7 +38,7 @@ module Biff8
|
|
37
38
|
wide = opts & 1
|
38
39
|
owing = @incomplete_sst.continued_chars
|
39
40
|
size = [work.size, owing * (1 + wide) + 1].min
|
40
|
-
chars = size - 1 / (1 + wide)
|
41
|
+
chars = (size - 1) / (1 + wide)
|
41
42
|
@incomplete_sst.continue oppos + OPCODE_SIZE, size, chars
|
42
43
|
unless @incomplete_sst.continued?
|
43
44
|
@incomplete_sst = nil
|
@@ -60,6 +61,12 @@ module Biff8
|
|
60
61
|
@incomplete_string, @sst_size, @sst_offset, @incomplete_sst = nil
|
61
62
|
end
|
62
63
|
##
|
64
|
+
# Store the offset of extsst, so we can write a new extsst when the
|
65
|
+
# sst has changed
|
66
|
+
def read_extsst work, pos, len
|
67
|
+
@workbook.offsets.store :extsst, [pos, len]
|
68
|
+
end
|
69
|
+
##
|
63
70
|
# Read the Shared String Table present in all Biff8 Files.
|
64
71
|
# This method only evaluates the header, the actual work is done in #_read_sst
|
65
72
|
def read_sst work, pos, len
|
@@ -56,9 +56,20 @@ class Row < Spreadsheet::Row
|
|
56
56
|
date = _date data
|
57
57
|
hour = (data % 1) * 24
|
58
58
|
min = (hour % 1) * 60
|
59
|
-
sec = (min % 1) * 60
|
60
|
-
|
61
|
-
|
59
|
+
sec = ((min % 1) * 60).round
|
60
|
+
min = min.floor
|
61
|
+
hour = hour.floor
|
62
|
+
if sec > 59
|
63
|
+
sec = 0
|
64
|
+
min += 1
|
65
|
+
end
|
66
|
+
if min > 59
|
67
|
+
hour += 1
|
68
|
+
end
|
69
|
+
if hour > 23
|
70
|
+
date += 1
|
71
|
+
end
|
72
|
+
DateTime.new(date.year, date.month, date.day, hour, min, sec)
|
62
73
|
end
|
63
74
|
def enriched_data idx, data # :nodoc:
|
64
75
|
res = nil
|
@@ -40,10 +40,12 @@ class Worksheet < Spreadsheet::Worksheet
|
|
40
40
|
end
|
41
41
|
def row idx
|
42
42
|
ensure_rows_read
|
43
|
-
@rows
|
43
|
+
@rows[idx] or begin
|
44
44
|
if addr = @row_addresses[idx]
|
45
45
|
row = @reader.read_row self, addr
|
46
|
-
|
46
|
+
[:default_format, :height, :outline_level, :hidden, ].each do |key|
|
47
|
+
row.send "#{key}=", addr[key]
|
48
|
+
end
|
47
49
|
row.worksheet = self
|
48
50
|
row
|
49
51
|
else
|
@@ -51,7 +53,7 @@ class Worksheet < Spreadsheet::Worksheet
|
|
51
53
|
end
|
52
54
|
end
|
53
55
|
end
|
54
|
-
def row_updated idx, row
|
56
|
+
def row_updated idx, row, args={}
|
55
57
|
res = super
|
56
58
|
@workbook.changes.store self, true
|
57
59
|
@workbook.changes.store :boundsheets, true
|
@@ -17,7 +17,7 @@ module Spreadsheet
|
|
17
17
|
class Workbook < Spreadsheet::Writer
|
18
18
|
include Spreadsheet::Excel::Writer::Biff8
|
19
19
|
include Spreadsheet::Excel::Internals
|
20
|
-
attr_reader :fonts
|
20
|
+
attr_reader :fonts, :date_base
|
21
21
|
def initialize *args
|
22
22
|
super
|
23
23
|
@biff_version = 0x0600
|
@@ -76,7 +76,7 @@ class Workbook < Spreadsheet::Writer
|
|
76
76
|
total = current.size
|
77
77
|
current.uniq!
|
78
78
|
current.delete ''
|
79
|
-
if (stored - current).empty?
|
79
|
+
if (stored - current).empty? && !stored.empty?
|
80
80
|
## if all previously stored strings are still needed, we don't have to
|
81
81
|
# rewrite all cells because the sst-index of such string does not change.
|
82
82
|
additions = current - stored
|
@@ -192,19 +192,34 @@ class Workbook < Spreadsheet::Writer
|
|
192
192
|
write_sst_changes workbook, buffer, writer.pos,
|
193
193
|
sst_total, sst_strings
|
194
194
|
pos, len = tuple
|
195
|
+
if offset = workbook.offsets[:extsst]
|
196
|
+
len += offset[1].to_i
|
197
|
+
end
|
195
198
|
bytechange = buffer.size - len
|
196
199
|
write_boundsheets workbook, writer, oldoffset + bytechange
|
197
200
|
reader.seek lastpos
|
198
201
|
writer.write reader.read(pos - lastpos)
|
199
202
|
buffer.rewind
|
200
203
|
writer.write buffer.read
|
201
|
-
|
204
|
+
elsif sst.empty? || workbook.biff_version < 8
|
202
205
|
write_boundsheets workbook, writer, oldoffset + bytechange
|
206
|
+
else
|
207
|
+
write_sst workbook, buffer, writer.pos
|
208
|
+
write_boundsheets workbook, writer, oldoffset + buffer.size
|
209
|
+
pos = lastpos
|
210
|
+
len = positions.min - lastpos
|
211
|
+
if len > OPCODE_SIZE
|
212
|
+
reader.seek pos
|
213
|
+
writer.write reader.read(len - OPCODE_SIZE)
|
214
|
+
end
|
215
|
+
buffer.rewind
|
216
|
+
writer.write buffer.read
|
217
|
+
write_eof workbook, writer
|
203
218
|
end
|
204
219
|
else
|
205
220
|
send "write_#{key}", workbook, writer
|
206
221
|
end
|
207
|
-
lastpos = pos + len
|
222
|
+
lastpos = [pos + len, reader.size - 1].min
|
208
223
|
reader.seek lastpos
|
209
224
|
end
|
210
225
|
writer.write reader.read
|
@@ -215,8 +230,9 @@ class Workbook < Spreadsheet::Writer
|
|
215
230
|
end
|
216
231
|
end
|
217
232
|
def write_datemode workbook, writer
|
233
|
+
mode = @date_base.year == 1899 ? 0x00 : 0x01
|
218
234
|
data = [
|
219
|
-
|
235
|
+
mode, # 0 = Base date is 1899-Dec-31
|
220
236
|
# (the cell value 1 represents 1900-Jan-01)
|
221
237
|
# 1 = Base date is 1904-Jan-01
|
222
238
|
# (the cell value 1 represents 1904-Jan-02)
|
@@ -462,17 +478,18 @@ class Workbook < Spreadsheet::Writer
|
|
462
478
|
data = [total, sst_size].pack 'V2'
|
463
479
|
op = 0x00fc
|
464
480
|
wide = 0
|
465
|
-
header =
|
466
481
|
offsets = []
|
467
482
|
strings.each_with_index do |string, idx|
|
468
483
|
sst.store string, idx
|
469
484
|
op_offset = data.size + 4
|
470
485
|
offsets.push [offset + writer.pos + op_offset, op_offset] if idx % 8 == 0
|
471
|
-
header, packed,
|
486
|
+
header, packed, next_wide = _unicode_string string, 2
|
487
|
+
# the first few bytes (header + first character) must not be split
|
472
488
|
must_fit = header.size + wide + 1
|
473
489
|
while data.size + must_fit > @recordsize_limit
|
474
490
|
op, data, wide = write_string_part writer, op, data, wide
|
475
491
|
end
|
492
|
+
wide = next_wide
|
476
493
|
data << header << packed
|
477
494
|
end
|
478
495
|
until data.empty?
|
@@ -485,7 +502,17 @@ class Workbook < Spreadsheet::Writer
|
|
485
502
|
end
|
486
503
|
def write_string_part writer, op, data, wide
|
487
504
|
bef = data.size
|
488
|
-
|
505
|
+
## if we're writing wide characters, we need to make sure we don't cut
|
506
|
+
# characters in half
|
507
|
+
if wide > 0 && data.size > @recordsize_limit
|
508
|
+
remove = @recordsize_limit - data.size
|
509
|
+
remove -= remove % 2
|
510
|
+
rest = data.slice!(remove..-1)
|
511
|
+
write_op writer, op, data
|
512
|
+
data = rest
|
513
|
+
else
|
514
|
+
data = write_op writer, op, data
|
515
|
+
end
|
489
516
|
op = 0x003c
|
490
517
|
# Unicode strings are split in a special way. At the beginning of each
|
491
518
|
# CONTINUE record the option flags byte is repeated. Only the
|
@@ -565,8 +592,10 @@ class Workbook < Spreadsheet::Writer
|
|
565
592
|
# depending on the class and state of _workbook_.
|
566
593
|
def write_workbook workbook, io
|
567
594
|
unless workbook.is_a?(Excel::Workbook) && workbook.io
|
595
|
+
@date_base = Date.new 1899, 12, 31
|
568
596
|
write_from_scratch workbook, io
|
569
597
|
else
|
598
|
+
@date_base = workbook.date_base
|
570
599
|
if workbook.changes.empty?
|
571
600
|
super
|
572
601
|
else
|
@@ -1,5 +1,6 @@
|
|
1
1
|
require 'stringio'
|
2
2
|
require 'spreadsheet/excel/writer/biff8'
|
3
|
+
require 'spreadsheet/excel/internals'
|
3
4
|
require 'spreadsheet/excel/internals/biff8'
|
4
5
|
|
5
6
|
module Spreadsheet
|
@@ -47,7 +48,7 @@ class Worksheet
|
|
47
48
|
date = DateTime.new date.year, date.month, date.day,
|
48
49
|
date.hour, date.min, date.sec
|
49
50
|
end
|
50
|
-
value = date - @
|
51
|
+
value = date - @workbook.date_base
|
51
52
|
if date > LEAP_ERROR
|
52
53
|
value += 1
|
53
54
|
end
|
@@ -61,9 +62,13 @@ class Worksheet
|
|
61
62
|
cent = 0
|
62
63
|
int = 2
|
63
64
|
higher = value * 100
|
64
|
-
if higher
|
65
|
-
value = higher.to_i
|
65
|
+
if higher.is_a?(Float) && higher < 0xfffffffc
|
66
66
|
cent = 1
|
67
|
+
if higher == higher.to_i
|
68
|
+
value = higher.to_i
|
69
|
+
else
|
70
|
+
value = higher
|
71
|
+
end
|
67
72
|
end
|
68
73
|
if value.is_a?(Integer)
|
69
74
|
## although not documented as signed, 'V' appears to correctly pack
|
@@ -80,6 +85,11 @@ class Worksheet
|
|
80
85
|
def name
|
81
86
|
unicode_string @worksheet.name
|
82
87
|
end
|
88
|
+
def need_number? cell
|
89
|
+
(cell.is_a?(Numeric) && cell.abs > 0x1fffffff) \
|
90
|
+
|| (cell.is_a?(Float) \
|
91
|
+
&& !/^[\000\001]\000{3}/.match([cell * 100].pack(EIGHT_BYTE_DOUBLE)))
|
92
|
+
end
|
83
93
|
def row_blocks
|
84
94
|
# All cells in an Excel document are divided into blocks of 32 consecutive
|
85
95
|
# rows, called Row Blocks. The first Row Block starts with the first used
|
@@ -174,8 +184,7 @@ class Worksheet
|
|
174
184
|
# Integers and Floats, that lie well below 2^30 significant bits, or
|
175
185
|
# Ruby's Bignum threshold. In that case we'll just write a Number
|
176
186
|
# record
|
177
|
-
need_number =
|
178
|
-
|| (cell.is_a?(Numeric) && cell.abs > 0x500000)
|
187
|
+
need_number = need_number? cell
|
179
188
|
if multiples && (!multiples.last.is_a?(cell.class) || need_number)
|
180
189
|
write_multiples row, first_idx, multiples
|
181
190
|
multiples, first_idx = nil
|
@@ -223,19 +232,44 @@ class Worksheet
|
|
223
232
|
blocks = row_blocks
|
224
233
|
lastpos = reader.pos
|
225
234
|
offsets = {}
|
235
|
+
row_offsets = []
|
236
|
+
changes = @worksheet.changes
|
226
237
|
@worksheet.offsets.each do |key, pair|
|
227
|
-
if
|
238
|
+
if changes.include?(key) \
|
228
239
|
|| (sst_status == :complete_update && key.is_a?(Integer))
|
229
240
|
offsets.store pair, key
|
230
241
|
end
|
231
242
|
end
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
243
|
+
## FIXME it may be smarter to simply write all rowblocks, instead of doing a
|
244
|
+
# song-and-dance routine for every row...
|
245
|
+
work = offsets.invert
|
246
|
+
work.each do |key, (pos, len)|
|
247
|
+
case key
|
248
|
+
when Integer
|
249
|
+
row_offsets.push [key, [pos, len]]
|
250
|
+
when :dimensions
|
251
|
+
row_offsets.push [-1, [pos, len]]
|
252
|
+
end
|
253
|
+
end
|
254
|
+
row_offsets.sort!
|
255
|
+
row_offsets.reverse!
|
256
|
+
@worksheet.each do |row|
|
257
|
+
key = row.idx
|
258
|
+
if changes.include?(key) && !work.include?(key)
|
259
|
+
row, pair = row_offsets.find do |idx, _| idx <= key end
|
260
|
+
work.store key, pair
|
261
|
+
end
|
262
|
+
end
|
263
|
+
work = work.sort_by do |key, (pos, len)|
|
264
|
+
[pos, key.is_a?(Integer) ? key : -1]
|
265
|
+
end
|
266
|
+
work.each do |key, (pos, len)|
|
267
|
+
@io.write reader.read(pos - lastpos) if pos > lastpos
|
236
268
|
if key.is_a?(Integer)
|
237
|
-
block = blocks.find do |rows| rows.any? do |row| row.idx == key end end
|
238
|
-
|
269
|
+
if block = blocks.find do |rows| rows.any? do |row| row.idx == key end end
|
270
|
+
write_rowblock block
|
271
|
+
blocks.delete block
|
272
|
+
end
|
239
273
|
else
|
240
274
|
send "write_#{key}"
|
241
275
|
end
|
@@ -544,7 +578,7 @@ class Worksheet
|
|
544
578
|
fmt << 'vV'
|
545
579
|
end
|
546
580
|
# Index to last column (lc)
|
547
|
-
data.push idx + multiples.size
|
581
|
+
data.push idx + multiples.size - 1
|
548
582
|
write_op opcode(:mulrk), data.pack(fmt << 'v')
|
549
583
|
end
|
550
584
|
def write_multiples row, idx, multiples
|
@@ -552,7 +586,11 @@ class Worksheet
|
|
552
586
|
when NilClass
|
553
587
|
write_mulblank row, idx, multiples
|
554
588
|
when Numeric
|
555
|
-
|
589
|
+
if multiples.size > 1
|
590
|
+
write_mulrk row, idx, multiples
|
591
|
+
else
|
592
|
+
write_rk row, idx
|
593
|
+
end
|
556
594
|
end
|
557
595
|
end
|
558
596
|
##
|
@@ -600,37 +638,61 @@ class Worksheet
|
|
600
638
|
# 15 0x8000 0 = Row has custom height;
|
601
639
|
# 1 = Row has default height
|
602
640
|
# 8 2 Not used
|
603
|
-
# 10
|
604
|
-
#
|
605
|
-
#
|
606
|
-
#
|
607
|
-
#
|
608
|
-
#
|
609
|
-
|
641
|
+
# 10 2 In BIFF3-BIFF4 this field contains a relative offset to
|
642
|
+
# calculate stream position of the first cell record for this
|
643
|
+
# row (➜ 5.7.1). In BIFF5-BIFF8 this field is not used
|
644
|
+
# anymore, but the DBCELL record (➜ 6.26) instead.
|
645
|
+
# 12 4 Option flags and default row formatting:
|
646
|
+
# Bit Mask Contents
|
647
|
+
# 2-0 0x00000007 Outline level of the row
|
648
|
+
# 4 0x00000010 1 = Outline group starts or ends here
|
649
|
+
# (depending on where the outline
|
650
|
+
# buttons are located, see WSBOOL
|
651
|
+
# record, ➜ 6.113), and is collapsed
|
652
|
+
# 5 0x00000020 1 = Row is hidden (manually, or by a
|
653
|
+
# filter or outline group)
|
654
|
+
# 6 0x00000040 1 = Row height and default font height
|
655
|
+
# do not match
|
656
|
+
# 7 0x00000080 1 = Row has explicit default format (fl)
|
657
|
+
# 8 0x00000100 Always 1
|
658
|
+
# 27-16 0x0fff0000 If fl = 1: Index to default XF record
|
659
|
+
# (➜ 6.115)
|
660
|
+
# 28 0x10000000 1 = Additional space above the row.
|
661
|
+
# This flag is set, if the upper
|
662
|
+
# border of at least one cell in this
|
663
|
+
# row or if the lower border of at
|
664
|
+
# least one cell in the row above is
|
665
|
+
# formatted with a thick line style.
|
666
|
+
# Thin and medium line styles are not
|
667
|
+
# taken into account.
|
668
|
+
# 29 0x20000000 1 = Additional space below the row.
|
669
|
+
# This flag is set, if the lower
|
670
|
+
# border of at least one cell in this
|
671
|
+
# row or if the upper border of at
|
672
|
+
# least one cell in the row below is
|
673
|
+
# formatted with a medium or thick
|
674
|
+
# line style. Thin line styles are
|
675
|
+
# not taken into account.
|
676
|
+
height = row.height || 12 # FIXME: where is the default font height?
|
677
|
+
opts = row.outline_level & 0x00000007
|
678
|
+
opts |= 0x00000010 if row.collapsed?
|
679
|
+
opts |= 0x00000020 if row.hidden?
|
680
|
+
opts |= 0x00000040 if height != 12 # FIXME: where is the default font height?
|
681
|
+
if fmt = row.default_format
|
682
|
+
xf_idx = @workbook.xf_index @worksheet.workbook, fmt
|
683
|
+
opts |= 0x00000080
|
684
|
+
opts |= xf_idx << 16
|
685
|
+
end
|
686
|
+
opts |= 0x00000100
|
687
|
+
# TODO: Row spacing
|
610
688
|
data = [
|
611
689
|
row.idx,
|
612
690
|
row.first_used,
|
613
691
|
row.first_unused,
|
614
|
-
|
615
|
-
|
616
|
-
|
617
|
-
|
618
|
-
1,
|
619
|
-
15,
|
620
|
-
0,
|
621
|
-
]
|
622
|
-
# OpenOffice apparently can't read Rows with a length other than 16 Bytes
|
623
|
-
fmt = binfmt(:row) + 'C3'
|
624
|
-
=begin
|
625
|
-
if format = row.default_format
|
626
|
-
fmt = fmt + 'xv'
|
627
|
-
data.concat [
|
628
|
-
#0, # Row attributes should only matter in BIFF2
|
629
|
-
workbook.xf_index(@worksheet.workbook, format),
|
630
|
-
]
|
631
|
-
end
|
632
|
-
=end
|
633
|
-
write_op opcode(:row), data.pack(fmt)
|
692
|
+
height * TWIPS,
|
693
|
+
opts,
|
694
|
+
].pack binfmt(:row)
|
695
|
+
write_op opcode(:row), data
|
634
696
|
end
|
635
697
|
def write_rowblock block
|
636
698
|
# ●● ROW Properties of the used rows
|
data/lib/spreadsheet/row.rb
CHANGED
@@ -15,12 +15,30 @@ module Spreadsheet
|
|
15
15
|
# Format is stored in #formats for the cell.
|
16
16
|
# #height:: The height of this Row in points (defaults to 12).
|
17
17
|
class Row < Array
|
18
|
+
include Datatypes
|
18
19
|
class << self
|
20
|
+
def format_updater *keys
|
21
|
+
keys.each do |key|
|
22
|
+
unless instance_methods.include? "unupdated_#{key}="
|
23
|
+
alias_method :"unupdated_#{key}=", :"#{key}="
|
24
|
+
define_method "#{key}=" do |value|
|
25
|
+
send "unupdated_#{key}=", value
|
26
|
+
if @worksheet
|
27
|
+
@formatted = true
|
28
|
+
@worksheet.row_updated @idx, self, :formatted => true
|
29
|
+
end
|
30
|
+
value
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
19
35
|
def updater *keys
|
20
36
|
keys.each do |key|
|
21
37
|
define_method key do |*args|
|
22
38
|
res = super
|
23
|
-
|
39
|
+
if @worksheet
|
40
|
+
@worksheet.row_updated @idx, self, :formatted => @formatted
|
41
|
+
end
|
24
42
|
res
|
25
43
|
end
|
26
44
|
end
|
@@ -28,9 +46,12 @@ module Spreadsheet
|
|
28
46
|
end
|
29
47
|
attr_reader :formats, :default_format
|
30
48
|
attr_accessor :idx, :height, :worksheet
|
49
|
+
boolean :hidden, :collapsed
|
50
|
+
enum :outline_level, 0, Integer
|
31
51
|
updater :[]=, :clear, :concat, :delete, :delete_if, :fill, :insert, :map!,
|
32
52
|
:pop, :push, :reject!, :replace, :reverse!, :shift, :slice!,
|
33
53
|
:sort!, :uniq!, :unshift
|
54
|
+
format_updater :collapsed, :height, :hidden, :outline_level
|
34
55
|
def initialize worksheet, idx, cells=[]
|
35
56
|
@worksheet = worksheet
|
36
57
|
@idx = idx
|
@@ -48,6 +69,7 @@ module Spreadsheet
|
|
48
69
|
# stored for the cell.
|
49
70
|
def default_format= format
|
50
71
|
@worksheet.add_format format if @worksheet
|
72
|
+
@worksheet.row_updated @idx, self, :formatted => true if @worksheet
|
51
73
|
@default_format = format
|
52
74
|
end
|
53
75
|
##
|
@@ -71,7 +93,7 @@ module Spreadsheet
|
|
71
93
|
def set_format idx, fmt
|
72
94
|
@formats[idx] = fmt
|
73
95
|
@worksheet.add_format fmt
|
74
|
-
@worksheet.row_updated @idx, self if @worksheet
|
96
|
+
@worksheet.row_updated @idx, self, :formatted => true if @worksheet
|
75
97
|
fmt
|
76
98
|
end
|
77
99
|
def inspect
|
@@ -173,9 +173,12 @@ module Spreadsheet
|
|
173
173
|
##
|
174
174
|
# Tell Worksheet that the Row at _idx_ has been updated and the #dimensions
|
175
175
|
# need to be recalculated. You should not need to call this directly.
|
176
|
-
def row_updated idx, row
|
176
|
+
def row_updated idx, row, opts={}
|
177
177
|
@dimensions = nil
|
178
|
-
|
178
|
+
unless opts[:formatted]
|
179
|
+
row = shorten(row)
|
180
|
+
end
|
181
|
+
@rows[idx] = row
|
179
182
|
format_dates row
|
180
183
|
row
|
181
184
|
end
|
data/test/integration.rb
CHANGED
@@ -749,14 +749,33 @@ module Spreadsheet
|
|
749
749
|
row = sheet.row 9
|
750
750
|
assert_equal 0.00009, row[0]
|
751
751
|
end
|
752
|
-
def
|
752
|
+
def test_write_to_stringio
|
753
753
|
book = Spreadsheet::Excel::Workbook.new
|
754
|
+
sheet = book.create_worksheet :name => 'My Worksheet'
|
755
|
+
sheet[0,0] = 'my cell'
|
756
|
+
data = StringIO.new ''
|
757
|
+
assert_nothing_raised do
|
758
|
+
book.write data
|
759
|
+
end
|
760
|
+
assert_nothing_raised do
|
761
|
+
book = Spreadsheet.open data
|
762
|
+
end
|
763
|
+
assert_instance_of Spreadsheet::Excel::Workbook, book
|
764
|
+
assert_equal 1, book.worksheets.size
|
765
|
+
sheet = book.worksheet 0
|
766
|
+
assert_equal 'My Worksheet', sheet.name
|
767
|
+
assert_equal 'my cell', sheet[0,0]
|
768
|
+
end
|
769
|
+
def test_write_new_workbook
|
770
|
+
book = Spreadsheet::Workbook.new
|
754
771
|
path = File.join @var, 'test_write_workbook.xls'
|
755
772
|
sheet1 = book.create_worksheet
|
756
|
-
str1 = 'Shared String'
|
773
|
+
str1 = 'My Shared String'
|
757
774
|
str2 = 'Another Shared String'
|
758
|
-
|
759
|
-
|
775
|
+
assert_equal 1, (str1.size + str2.size) % 2,
|
776
|
+
"str3 should start at an odd offset to test splitting of wide strings"
|
777
|
+
str3 = '–––––––––– ' * 1000
|
778
|
+
str4 = '1234567890 ' * 1000
|
760
779
|
fmt1 = Format.new :italic => true, :color => :blue
|
761
780
|
sheet1.format_column 1, fmt1, :width => 20
|
762
781
|
fmt2 = Format.new(:weight => :bold, :color => :yellow)
|
@@ -764,6 +783,7 @@ module Spreadsheet
|
|
764
783
|
sheet1.format_column 3, Format.new(:weight => :bold, :color => :red)
|
765
784
|
sheet1.format_column 6..9, fmt1
|
766
785
|
sheet1.format_column [4,5,7], fmt2
|
786
|
+
sheet1.row(0).height = 20
|
767
787
|
sheet1[0,0] = str1
|
768
788
|
sheet1.row(0).push str1
|
769
789
|
sheet1.row(1).concat [str2, str2]
|
@@ -811,6 +831,10 @@ module Spreadsheet
|
|
811
831
|
assert_equal 'b', sheet1[9,1]
|
812
832
|
assert_equal 'c', sheet1[9,2]
|
813
833
|
sheet1.delete_row 9
|
834
|
+
row = sheet1.row(11)
|
835
|
+
row.height = 40
|
836
|
+
row.push 'x'
|
837
|
+
row.pop
|
814
838
|
sheet2 = book.create_worksheet :name => 'my name'
|
815
839
|
book.write path
|
816
840
|
Spreadsheet.client_encoding = 'UTF-16LE'
|
@@ -822,7 +846,10 @@ module Spreadsheet
|
|
822
846
|
assert_equal 'UTF-16LE', book.encoding
|
823
847
|
assert_equal str1, book.shared_string(0)
|
824
848
|
assert_equal str2, book.shared_string(1)
|
825
|
-
test =
|
849
|
+
test = nil
|
850
|
+
assert_nothing_raised "I've probably split a two-byte-character" do
|
851
|
+
test = book.shared_string 2
|
852
|
+
end
|
826
853
|
if test != str3
|
827
854
|
str3.size.times do |idx|
|
828
855
|
len = idx.next
|
@@ -854,6 +881,7 @@ module Spreadsheet
|
|
854
881
|
assert_equal 20, col.width
|
855
882
|
row = sheet.row 0
|
856
883
|
assert_equal col.default_format, row.format(1)
|
884
|
+
assert_equal 20, row.height
|
857
885
|
assert_equal str1, row[0]
|
858
886
|
assert_equal str1, sheet[0,0]
|
859
887
|
assert_equal str1, sheet.cell(0,0)
|
@@ -936,6 +964,7 @@ module Spreadsheet
|
|
936
964
|
assert_equal 100.005, sheet1[10,2]
|
937
965
|
assert_equal 10.0005, sheet1[10,3]
|
938
966
|
assert_equal 1.00005, sheet1[10,4]
|
967
|
+
assert_equal 40, sheet1.row(11).height
|
939
968
|
assert_instance_of Spreadsheet::Excel::Worksheet, sheet
|
940
969
|
sheet = book.worksheets.last
|
941
970
|
assert_equal "m\000y\000 \000n\000a\000m\000e\000",
|
@@ -944,7 +973,7 @@ module Spreadsheet
|
|
944
973
|
end
|
945
974
|
def test_write_new_workbook__utf16
|
946
975
|
Spreadsheet.client_encoding = 'UTF-16LE'
|
947
|
-
book = Spreadsheet::
|
976
|
+
book = Spreadsheet::Workbook.new
|
948
977
|
path = File.join @var, 'test_write_workbook.xls'
|
949
978
|
sheet1 = book.create_worksheet
|
950
979
|
str1 = @@iconv.iconv 'Shared String'
|
@@ -972,6 +1001,8 @@ module Spreadsheet
|
|
972
1001
|
fmt = Format.new :number_format => "D\0D\0.\0M\0M\0.\0Y\0Y\0Y\0Y\0"
|
973
1002
|
sheet1.row(6).set_format 1, fmt
|
974
1003
|
sheet1.update_row 7, nil, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0
|
1004
|
+
sheet1.row(8).default_format = fmt
|
1005
|
+
sheet1[8,0] = @@iconv.iconv 'formatted when empty'
|
975
1006
|
sheet2 = book.create_worksheet :name => "m\0y\0 \0n\0a\0m\0e\0"
|
976
1007
|
book.write path
|
977
1008
|
Spreadsheet.client_encoding = 'UTF-8'
|
@@ -1070,14 +1101,32 @@ module Spreadsheet
|
|
1070
1101
|
assert_equal [1,2,3,4,5,6,7,8,9,0], row[1,10]
|
1071
1102
|
assert_equal [1,2,3,4,5,6,7,8,9,0], sheet[7,1..10]
|
1072
1103
|
assert_equal [1,2,3,4,5,6,7,8,9,0], sheet.cell(7,1..10)
|
1104
|
+
row = sheet.row 8
|
1105
|
+
assert_equal 'formatted when empty', row[0]
|
1106
|
+
assert_not_nil row.default_format
|
1073
1107
|
assert_instance_of Spreadsheet::Excel::Worksheet, sheet
|
1074
1108
|
sheet = book.worksheets.last
|
1075
1109
|
assert_equal "my name",
|
1076
1110
|
sheet.name
|
1077
1111
|
assert_not_nil sheet.offset
|
1078
1112
|
end
|
1113
|
+
def test_template
|
1114
|
+
template = File.join @data, 'test_copy.xls'
|
1115
|
+
output = File.join @var, 'test_template.xls'
|
1116
|
+
book = Spreadsheet.open template
|
1117
|
+
sheet1 = book.worksheet 0
|
1118
|
+
sheet1.row(4).replace [ 'Daniel J. Berger', 'U.S.A.',
|
1119
|
+
'Author of original code for Spreadsheet::Excel' ]
|
1120
|
+
book.write output
|
1121
|
+
assert_nothing_raised do
|
1122
|
+
book = Spreadsheet.open output
|
1123
|
+
end
|
1124
|
+
sheet = book.worksheet 0
|
1125
|
+
row = sheet.row(4)
|
1126
|
+
assert_equal 'Daniel J. Berger', row[0]
|
1127
|
+
end
|
1079
1128
|
def test_bignum
|
1080
|
-
smallnum =
|
1129
|
+
smallnum = 0x1fffffff
|
1081
1130
|
bignum = smallnum + 1
|
1082
1131
|
book = Spreadsheet::Workbook.new
|
1083
1132
|
sheet = book.create_worksheet
|
@@ -1085,6 +1134,10 @@ module Spreadsheet
|
|
1085
1134
|
sheet[1,0] = -bignum
|
1086
1135
|
sheet[0,1] = smallnum
|
1087
1136
|
sheet[1,1] = -smallnum
|
1137
|
+
sheet[0,2] = bignum - 0.1
|
1138
|
+
sheet[1,2] = -bignum - 0.1
|
1139
|
+
sheet[0,3] = smallnum - 0.1
|
1140
|
+
sheet[1,3] = -smallnum - 0.1
|
1088
1141
|
path = File.join @var, 'test_big-number.xls'
|
1089
1142
|
book.write path
|
1090
1143
|
assert_nothing_raised do
|
@@ -1094,6 +1147,10 @@ module Spreadsheet
|
|
1094
1147
|
assert_equal -bignum, book.worksheet(0)[1,0]
|
1095
1148
|
assert_equal smallnum, book.worksheet(0)[0,1]
|
1096
1149
|
assert_equal -smallnum, book.worksheet(0)[1,1]
|
1150
|
+
assert_equal bignum - 0.1, book.worksheet(0)[0,2]
|
1151
|
+
assert_equal -bignum - 0.1, book.worksheet(0)[1,2]
|
1152
|
+
assert_equal smallnum - 0.1, book.worksheet(0)[0,3]
|
1153
|
+
assert_equal -smallnum - 0.1, book.worksheet(0)[1,3]
|
1097
1154
|
end
|
1098
1155
|
end
|
1099
1156
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: spreadsheet
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.6.
|
4
|
+
version: 0.6.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Hannes Wyss
|
@@ -9,7 +9,7 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date: 2008-11
|
12
|
+
date: 2008-12-11 00:00:00 +01:00
|
13
13
|
default_executable:
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|