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.
@@ -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
@@ -42,7 +42,7 @@ module Spreadsheet
42
42
 
43
43
  ##
44
44
  # The version of Spreadsheet you are using.
45
- VERSION = '0.6.1.9'
45
+ VERSION = '0.6.2'
46
46
 
47
47
  ##
48
48
  # Default client Encoding. Change this value if your application uses a
@@ -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 :width, :hidden, :outline_level
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 => 'v5Cv',
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.each do |xf| set_cell worksheet, row, column, xf end
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
- # when :extsst # ● EXTSST ➜ 6.40
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 1 0 = No defaults written;
990
- # 1 = Default row attribute field and XF index occur below (fl)
991
- # 11 2 Relative offset to calculate stream position of the first
992
- # cell record for this row (➜ 5.7.1)
993
- # [13] 3 (written only if fl = 1) Default row attributes (➜ 3.12)
994
- # [16] 2 (written only if fl = 1) Index to XF record (➜ 6.115)
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
- hasdefaults, offset = work.unpack binfmt(:row)
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
- if hasdefaults > 0 && work.size > 13
1001
- xf, = work[-2..-1].unpack 'v'
1002
- format = @workbook.format(xf)
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
- worksheet.set_row_address index,
1005
- :default_format => format,
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
- DateTime.new(date.year, date.month, date.day,
61
- hour.to_i, min.to_i, sec.round)
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.fetch idx do
43
+ @rows[idx] or begin
44
44
  if addr = @row_addresses[idx]
45
45
  row = @reader.read_row self, addr
46
- row.default_format = addr[:default_format]
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
- else
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
- 0x00, # 0 = Base date is 1899-Dec-31
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, wide = _unicode_string string, 2
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
- data = write_op writer, op, data
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 - @worksheet.workbook.date_base
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 == higher.to_i
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 = (cell.is_a?(Float) && cell.to_s.length > 5) \
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 @worksheet.changes.include?(key) \
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
- offsets.invert.sort_by do |key, (pos, len)|
233
- pos
234
- end.each do |key, (pos, len)|
235
- @io.write reader.read(pos - lastpos)
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
- write_rowblock block
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
- write_mulrk row, idx, multiples
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 1 0 = No defaults written;
604
- # 1 = Default row attribute field and XF index occur below (fl)
605
- # 11 2 Relative offset to calculate stream position of the first
606
- # cell record for this row (➜ 5.7.1)
607
- # [13] 3 (written only if fl = 1) Default row attributes (➜ 3.12)
608
- # [16] 2 (written only if fl = 1) Index to XF record (➜ 6.115)
609
- has_defaults = row.default_format ? 1 : 0
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
- row.height * TWIPS,
615
- 0, # Not used
616
- has_defaults,
617
- 0, # OOffice does not set this - ignore until someone complains
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
@@ -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
- @worksheet.row_updated @idx, self if @worksheet
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
- row = @rows[idx] = shorten(row)
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
@@ -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 test_write_new_workbook
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
- str3 = '1234567890 ' * 1000
759
- str4 = '9876543210 ' * 1000
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 = book.shared_string 2
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::Excel::Workbook.new
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 = 0x500000
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.1.9
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-07 00:00:00 +01:00
12
+ date: 2008-12-11 00:00:00 +01:00
13
13
  default_executable:
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency