lib/spreadsheet/excel/writer/worksheet.rb in spreadsheet-0.6.1.9 vs lib/spreadsheet/excel/writer/worksheet.rb in spreadsheet-0.6.2

- old
+ new

@@ -1,7 +1,8 @@ require 'stringio' require 'spreadsheet/excel/writer/biff8' +require 'spreadsheet/excel/internals' require 'spreadsheet/excel/internals/biff8' module Spreadsheet module Excel module Writer @@ -45,11 +46,11 @@ return date if date.is_a? Numeric if date.is_a? Time date = DateTime.new date.year, date.month, date.day, date.hour, date.min, date.sec end - value = date - @worksheet.workbook.date_base + value = date - @workbook.date_base if date > LEAP_ERROR value += 1 end value end @@ -59,13 +60,17 @@ # 1 0x00000002 0 = Floating-point value 1 = Signed integer value # 31-2 0xFFFFFFFC Encoded value cent = 0 int = 2 higher = value * 100 - if higher == higher.to_i - value = higher.to_i + if higher.is_a?(Float) && higher < 0xfffffffc cent = 1 + if higher == higher.to_i + value = higher.to_i + else + value = higher + end end if value.is_a?(Integer) ## although not documented as signed, 'V' appears to correctly pack # negative numbers. value <<= 2 @@ -78,10 +83,15 @@ value | cent | int end def name unicode_string @worksheet.name end + def need_number? cell + (cell.is_a?(Numeric) && cell.abs > 0x1fffffff) \ + || (cell.is_a?(Float) \ + && !/^[\000\001]\000{3}/.match([cell * 100].pack(EIGHT_BYTE_DOUBLE))) + end def row_blocks # All cells in an Excel document are divided into blocks of 32 consecutive # rows, called Row Blocks. The first Row Block starts with the first used # row in that sheet. Inside each Row Block there will occur ROW records # describing the properties of the rows, and cell records with all the cell @@ -172,12 +182,11 @@ cell = nil if cell == '' ## it appears that there are limitations to RK precision, both for # Integers and Floats, that lie well below 2^30 significant bits, or # Ruby's Bignum threshold. In that case we'll just write a Number # record - need_number = (cell.is_a?(Float) && cell.to_s.length > 5) \ - || (cell.is_a?(Numeric) && cell.abs > 0x500000) + need_number = need_number? cell if multiples && (!multiples.last.is_a?(cell.class) || need_number) write_multiples row, first_idx, multiples multiples, first_idx = nil end nxt = idx + 1 @@ -221,23 +230,48 @@ def write_changes reader, endpos, sst_status reader.seek @worksheet.offset blocks = row_blocks lastpos = reader.pos offsets = {} + row_offsets = [] + changes = @worksheet.changes @worksheet.offsets.each do |key, pair| - if @worksheet.changes.include?(key) \ + if changes.include?(key) \ || (sst_status == :complete_update && key.is_a?(Integer)) offsets.store pair, key end end - offsets.invert.sort_by do |key, (pos, len)| - pos - end.each do |key, (pos, len)| - @io.write reader.read(pos - lastpos) + ## FIXME it may be smarter to simply write all rowblocks, instead of doing a + # song-and-dance routine for every row... + work = offsets.invert + work.each do |key, (pos, len)| + case key + when Integer + row_offsets.push [key, [pos, len]] + when :dimensions + row_offsets.push [-1, [pos, len]] + end + end + row_offsets.sort! + row_offsets.reverse! + @worksheet.each do |row| + key = row.idx + if changes.include?(key) && !work.include?(key) + row, pair = row_offsets.find do |idx, _| idx <= key end + work.store key, pair + end + end + work = work.sort_by do |key, (pos, len)| + [pos, key.is_a?(Integer) ? key : -1] + end + work.each do |key, (pos, len)| + @io.write reader.read(pos - lastpos) if pos > lastpos if key.is_a?(Integer) - block = blocks.find do |rows| rows.any? do |row| row.idx == key end end - write_rowblock block + if block = blocks.find do |rows| rows.any? do |row| row.idx == key end end + write_rowblock block + blocks.delete block + end else send "write_#{key}" end lastpos = pos + len reader.seek lastpos @@ -542,19 +576,23 @@ xf_idx = @workbook.xf_index @worksheet.workbook, row.format(idx + cell_idx) data.push xf_idx, encode_rk(cell) fmt << 'vV' end # Index to last column (lc) - data.push idx + multiples.size + data.push idx + multiples.size - 1 write_op opcode(:mulrk), data.pack(fmt << 'v') end def write_multiples row, idx, multiples case multiples.last when NilClass write_mulblank row, idx, multiples when Numeric - write_mulrk row, idx, multiples + if multiples.size > 1 + write_mulrk row, idx, multiples + else + write_rk row, idx + end end end ## # Write a cell with a 64-bit double precision Float value def write_number row, idx @@ -598,40 +636,64 @@ # 6 2 Bit Mask Contents # 14-0 0x7fff Height of the row, in twips = 1/20 of a point # 15 0x8000 0 = Row has custom height; # 1 = Row has default height # 8 2 Not used - # 10 1 0 = No defaults written; - # 1 = Default row attribute field and XF index occur below (fl) - # 11 2 Relative offset to calculate stream position of the first - # cell record for this row (➜ 5.7.1) - # [13] 3 (written only if fl = 1) Default row attributes (➜ 3.12) - # [16] 2 (written only if fl = 1) Index to XF record (➜ 6.115) - has_defaults = row.default_format ? 1 : 0 + # 10 2 In BIFF3-BIFF4 this field contains a relative offset to + # calculate stream position of the first cell record for this + # row (➜ 5.7.1). In BIFF5-BIFF8 this field is not used + # anymore, but the DBCELL record (➜ 6.26) instead. + # 12 4 Option flags and default row formatting: + # Bit Mask Contents + # 2-0 0x00000007 Outline level of the row + # 4 0x00000010 1 = Outline group starts or ends here + # (depending on where the outline + # buttons are located, see WSBOOL + # record, ➜ 6.113), and is collapsed + # 5 0x00000020 1 = Row is hidden (manually, or by a + # filter or outline group) + # 6 0x00000040 1 = Row height and default font height + # do not match + # 7 0x00000080 1 = Row has explicit default format (fl) + # 8 0x00000100 Always 1 + # 27-16 0x0fff0000 If fl = 1: Index to default XF record + # (➜ 6.115) + # 28 0x10000000 1 = Additional space above the row. + # This flag is set, if the upper + # border of at least one cell in this + # row or if the lower border of at + # least one cell in the row above is + # formatted with a thick line style. + # Thin and medium line styles are not + # taken into account. + # 29 0x20000000 1 = Additional space below the row. + # This flag is set, if the lower + # border of at least one cell in this + # row or if the upper border of at + # least one cell in the row below is + # formatted with a medium or thick + # line style. Thin line styles are + # not taken into account. + height = row.height || 12 # FIXME: where is the default font height? + opts = row.outline_level & 0x00000007 + opts |= 0x00000010 if row.collapsed? + opts |= 0x00000020 if row.hidden? + opts |= 0x00000040 if height != 12 # FIXME: where is the default font height? + if fmt = row.default_format + xf_idx = @workbook.xf_index @worksheet.workbook, fmt + opts |= 0x00000080 + opts |= xf_idx << 16 + end + opts |= 0x00000100 + # TODO: Row spacing data = [ row.idx, row.first_used, row.first_unused, - row.height * TWIPS, - 0, # Not used - has_defaults, - 0, # OOffice does not set this - ignore until someone complains - 1, - 15, - 0, - ] - # OpenOffice apparently can't read Rows with a length other than 16 Bytes - fmt = binfmt(:row) + 'C3' -=begin - if format = row.default_format - fmt = fmt + 'xv' - data.concat [ - #0, # Row attributes should only matter in BIFF2 - workbook.xf_index(@worksheet.workbook, format), - ] - end -=end - write_op opcode(:row), data.pack(fmt) + height * TWIPS, + opts, + ].pack binfmt(:row) + write_op opcode(:row), data end def write_rowblock block # ●● ROW Properties of the used rows # ○○ Cell Block(s) Cell records for all used cells # ○ DBCELL Stream offsets to the cell records of each row