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.
@@ -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