spreadsheet 0.6.1.9 → 0.6.2
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/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
|