spreadsheet 0.6.0 → 0.6.1

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 CHANGED
@@ -1,3 +1,11 @@
1
+ === 0.6.1 / 2008-10-17
2
+
3
+ * 3 minor enhancements
4
+
5
+ * Adds Column formatting and Worksheet#format_column
6
+ * Reads and writes correct Fonts (Font-indices > 3 appear to be 1-based)
7
+ * Reads xf data
8
+
1
9
  === 0.6.0 / 2008-10-13
2
10
 
3
11
  * 1 major enhancement
data/README.txt CHANGED
@@ -1,6 +1,13 @@
1
+ Last Update: 13.10.2008, 19.45 - zdavatz
2
+
1
3
  = Spreadsheet
2
4
 
3
5
  http://spreadsheet.rubyforge.org
6
+ http://scm.ywesee.com/spreadsheet
7
+
8
+ For a viewable directory of all recent changes, please see:
9
+
10
+ http://scm.ywesee.com/?p=spreadsheet;a=summary
4
11
 
5
12
  == Description
6
13
 
@@ -34,6 +41,16 @@ Hannes Wyss. Spreadsheet can read, write and modify Spreadsheet Documents.
34
41
 
35
42
  Have a look at the GUIDE[link://files/GUIDE_txt.html].
36
43
 
44
+ == Installation
45
+
46
+ Using RubyGems[http://www.rubygems.org]:
47
+
48
+ * sudo gem install spreadsheet
49
+
50
+ If you don't like RubyGems[http://www.rubygems.org], let me know which
51
+ installation solution you prefer and I'll include it in the future.
52
+
53
+
37
54
  == Authors
38
55
 
39
56
  Original Code:
@@ -49,6 +66,6 @@ Copyright (c) 2008 by Hannes Wyss (hannes.wyss@gmail.com)
49
66
 
50
67
  == License
51
68
 
52
- This library is distributed under the GPL.
69
+ This library is distributed under the GPL.
53
70
  Please see the LICENSE[link://files/LICENSE_txt.html] file.
54
71
 
data/Rakefile CHANGED
@@ -10,6 +10,7 @@ Hoe.new('spreadsheet', Spreadsheet::VERSION) do |p|
10
10
  # p.rubyforge_name = 'spreadsheetx' # if different than lowercase project name
11
11
  p.developer('Hannes Wyss', 'hannes.wyss@gmail.com')
12
12
  p.remote_rdoc_dir = ''
13
+ p.extra_deps << 'ruby-ole'
13
14
  end
14
15
 
15
16
  # vim: syntax=Ruby
data/lib/spreadsheet.rb CHANGED
@@ -42,7 +42,7 @@ module Spreadsheet
42
42
 
43
43
  ##
44
44
  # The version of Spreadsheet you are using.
45
- VERSION = '0.6.0'
45
+ VERSION = '0.6.1'
46
46
 
47
47
  ##
48
48
  # Default client Encoding. Change this value if your application uses a
@@ -34,6 +34,16 @@ module Spreadsheet
34
34
  end
35
35
  end
36
36
  class Worksheet
37
+ unless instance_methods.include? "new_format_column"
38
+ alias :new_format_column :format_column
39
+ def format_column column, width=nil, format=nil
40
+ if width.is_a? Format
41
+ new_format_column column, width, format
42
+ else
43
+ new_format_column column, format, :width => width
44
+ end
45
+ end
46
+ end
37
47
  def write row, col, data=nil, format=nil
38
48
  if data.is_a? Array
39
49
  write_row row, col, data, format
@@ -94,6 +94,7 @@ module Internals
94
94
  BINARY_FORMATS = {
95
95
  :blank => 'v3',
96
96
  :boolerr => 'v3C2',
97
+ :colinfo => 'v5x2',
97
98
  :font => 'v5C3x',
98
99
  :labelsst => 'v3V',
99
100
  :number => "v3#{EIGHT_BYTE_DOUBLE}",
@@ -107,7 +108,7 @@ module Internals
107
108
  # default in a US-English environment. All indexes from 0 to 163 are
108
109
  # reserved for built-in formats.
109
110
  BUILTIN_FORMATS = { # TODO: locale support
110
- 0 => 'General',
111
+ 0 => 'GENERAL',
111
112
  1 => '0',
112
113
  2 => '0.00',
113
114
  3 => '#,##0',
@@ -196,34 +197,35 @@ module Internals
196
197
  }
197
198
  LEAP_ERROR = Date.new 1900, 2, 28
198
199
  OPCODES = {
199
- :blank => 0x0201,
200
- :boolerr => 0x0205,
201
- :boundsheet => 0x0085,
202
- :codepage => 0x0042,
203
- :continue => 0x003c,
204
- :datemode => 0x0022,
205
- :dbcell => 0x0a0b,
206
- :dimensions => 0x0200,
207
- :eof => 0x000a,
208
- :extsst => 0x00ff,
209
- :font => 0x0031,
210
- :format => 0x041e,
211
- :formula => 0x0006,
212
- :index => 0x020b,
213
- :label => 0x0204,
214
- :labelsst => 0x00fd,
215
- :mulblank => 0x00be,
216
- :mulrk => 0x00bd,
217
- :number => 0x0203,
218
- :rk => 0x027e,
219
- :row => 0x0208,
220
- :rstring => 0x00d6,
221
- :sst => 0x00fc,
222
- :string => 0x0207,
223
- :style => 0x0293,
224
- :uncalced => 0x005e,
225
- :xf => 0x00e0,
200
+ :blank => 0x0201, # BLANK ➜ 6.7
201
+ :boolerr => 0x0205, # BOOLERR ➜ 6.10
202
+ :boundsheet => 0x0085, # ●● BOUNDSHEET ➜ 6.12
203
+ :codepage => 0x0042, # ○ CODEPAGE ➜ 6.17
204
+ :colinfo => 0x007d, # ○○ COLINFO ➜ 6.18
205
+ :continue => 0x003c, # ○ CONTINUE ➜ 6.22
206
+ :datemode => 0x0022, # ○ DATEMODE ➜ 6.25
207
+ :dbcell => 0x0a0b, # ○ DBCELL
208
+ :dimensions => 0x0200, # ● DIMENSIONS ➜ 6.31
209
+ :eof => 0x000a, # ● EOF ➜ 6.36
210
+ :font => 0x0031, # ●● FONT ➜ 6.43
211
+ :format => 0x041e, # ○○ FORMAT (Number Format) ➜ 6.45
212
+ :formula => 0x0006, # FORMULA ➜ 6.46
213
+ :label => 0x0204, # LABEL ➜ 6.59 (BIFF2-BIFF7)
214
+ :labelsst => 0x00fd, # LABELSST ➜ 6.61 (BIFF8 only)
215
+ :mulblank => 0x00be, # MULBLANK ➜ 6.64 (BIFF5-BIFF8)
216
+ :mulrk => 0x00bd, # MULRK ➜ 6.65 (BIFF5-BIFF8)
217
+ :number => 0x0203, # NUMBER ➜ 6.68
218
+ :rk => 0x027e, # RK ➜ 6.82 (BIFF3-BIFF8)
219
+ :row => 0x0208, # ● ROW ➜ 6.83
220
+ :rstring => 0x00d6, # RSTRING ➜ 6.84 (BIFF5/BIFF7)
221
+ :sst => 0x00fc, # ● SST ➜ 6.96
222
+ :string => 0x0207, # STRING ➜ 6.98
223
+ :style => 0x0293, # ●● STYLE ➜ 6.99
224
+ :xf => 0x00e0, # ●● XF ➜ 6.115
226
225
  ########################## Unhandled Opcodes ################################
226
+ :extsst => 0x00ff, # ● EXTSST ➜ 6.40
227
+ :index => 0x020b, # ○ INDEX ➜ 5.7 (Row Blocks), ➜ 6.55
228
+ :uncalced => 0x005e, # ○ UNCALCED ➜ 6.104
227
229
  ########################## ○ Calculation Settings Block ➜ 5.3
228
230
  :calccount => 0x000c, # ○ CALCCOUNT ➜ 6.14
229
231
  :calcmode => 0x000d, # ○ CALCMODE ➜ 6.15
@@ -296,7 +298,6 @@ module Internals
296
298
  :defrowheight => 0x0225, # ○ DEFAULTROWHEIGHT ➜ 6.28
297
299
  :wsbool => 0x0081, # ○ WSBOOL ➜ 6.113
298
300
  :defcolwidth => 0x0055, # ○ DEFCOLWIDTH ➜ 6.29
299
- :colinfo => 0x007d, # ○○ COLINFO ➜ 6.18
300
301
  :sort => 0x0090, # ○ SORT ➜ 6.95
301
302
  }
302
303
  =begin ## unknown opcodes
@@ -311,6 +312,31 @@ module Internals
311
312
  0x0022 => :double_accounting,
312
313
  }
313
314
  SEPYT_ENILREDNU = UNDERLINE_TYPES.invert
315
+ XF_H_ALIGN = {
316
+ :default => 0,
317
+ :left => 1,
318
+ :center => 2,
319
+ :right => 3,
320
+ :fill => 4,
321
+ :justify => 5,
322
+ :merge => 6,
323
+ :distributed => 7,
324
+ }
325
+ NGILA_H_FX = XF_H_ALIGN.invert
326
+ XF_TEXT_DIRECTION = {
327
+ :context => 0,
328
+ :left_to_right => 1,
329
+ :right_to_left => 2,
330
+ }
331
+ NOITCERID_TXET_FX = XF_TEXT_DIRECTION.invert
332
+ XF_V_ALIGN = {
333
+ :top => 0,
334
+ :middle => 1,
335
+ :bottom => 2,
336
+ :justify => 3,
337
+ :distributed => 4,
338
+ }
339
+ NGILA_V_FX = XF_V_ALIGN.invert
314
340
  def binfmt key
315
341
  BINARY_FORMATS[key]
316
342
  end
@@ -107,6 +107,7 @@ class Reader
107
107
  extend_reader biff
108
108
  extend_internals biff
109
109
  read_workbook
110
+ @workbook.default_format = @workbook.format 0
110
111
  @workbook
111
112
  end
112
113
  def read_blank worksheet, addr, work
@@ -181,6 +182,31 @@ class Reader
181
182
  codepage, _ = work.unpack 'v'
182
183
  @workbook.set_encoding encoding(codepage), pos, len
183
184
  end
185
+ def read_colinfo worksheet, work, pos, len
186
+ # Offset Size Contents
187
+ # 0 2 Index to first column in the range
188
+ # 2 2 Index to last column in the range
189
+ # 4 2 Width of the columns in 1/256 of the width of the zero
190
+ # character, using default font (first FONT record in the
191
+ # file)
192
+ # 6 2 Index to XF record (➜ 6.115) for default column formatting
193
+ # 8 2 Option flags:
194
+ # Bits Mask Contents
195
+ # 0 0x0001 1 = Columns are hidden
196
+ # 10-8 0x0700 Outline level of the columns (0 = no outline)
197
+ # 12 0x1000 1 = Columns are collapsed
198
+ # 10 2 Not used
199
+ first, last, width, xf, opts = work.unpack binfmt(:colinfo)[0..-2]
200
+ first.upto last do |col|
201
+ column = Column.new col, @workbook.format(xf),
202
+ :width => width.to_f / 256,
203
+ :hidden => (opts & 0x0001) > 0,
204
+ :collapsed => (opts & 0x1000) > 0,
205
+ :outline_level => (opts & 0x0700)
206
+ column.worksheet = worksheet
207
+ worksheet.columns[col] = column
208
+ end
209
+ end
184
210
  def read_dimensions worksheet, work, pos, len
185
211
  # Offset Size Contents
186
212
  # 0 4 Index to first used row
@@ -550,7 +576,7 @@ class Reader
550
576
  read_style work, pos, len
551
577
  when :format # ○○ FORMAT (Number Format) ➜ 6.45
552
578
  read_format work, pos, len
553
- when :font
579
+ when :font # ●● FONT ➜ 6.43
554
580
  read_font work, pos, len
555
581
  end
556
582
  previous_op = op unless op == :continue
@@ -577,6 +603,8 @@ class Reader
577
603
  #when :index # ○ INDEX ➜ 5.7 (Row Blocks), ➜ 6.55
578
604
  # TODO: if there are changes in rows, omit index when writing
579
605
  #read_index worksheet, work, pos, len
606
+ when :colinfo # ○○ COLINFO ➜ 6.18
607
+ read_colinfo worksheet, work, pos, len
580
608
  when :dimensions # ● DIMENSIONS ➜ 6.31
581
609
  read_dimensions worksheet, work, pos, len
582
610
  when :row # ○○ Row Blocks ➜ 5.7
@@ -729,12 +757,42 @@ class Reader
729
757
  font_idx, numfmt, xf_type, xf_align, xf_rotation, xf_indent, xf_used_attr,
730
758
  xf_borders, xf_brdcolors, xf_pattern = work.unpack binfmt(:xf)
731
759
  fmt.number_format = @formats[numfmt]
732
- fmt.font = @workbook.font font_idx
760
+ ## this appears to be undocumented: the first 4 fonts seem to be accessed
761
+ # with a 0-based index, but all subsequent font indices are 1-based.
762
+ fmt.font = @workbook.font(font_idx > 3 ? font_idx - 1 : font_idx)
763
+ fmt.horizontal_align = NGILA_H_FX[xf_align & 0x07]
764
+ fmt.text_wrap = xf_align & 0x08 > 0
765
+ fmt.vertical_align = NGILA_V_FX[xf_align & 0x70]
766
+ fmt.rotation = if xf_rotation == 255
767
+ :stacked
768
+ elsif xf_rotation > 90
769
+ 90 - xf_rotation
770
+ else
771
+ xf_rotation
772
+ end
773
+ fmt.indent_level = xf_indent & 0x0f
774
+ fmt.shrink = xf_indent & 0x10 > 0
775
+ fmt.text_direction = NOITCERID_TXET_FX[xf_indent & 0xc0]
776
+ fmt.left = xf_borders & 0x0000000f > 0
777
+ fmt.right = xf_borders & 0x000000f0 > 0
778
+ fmt.top = xf_borders & 0x00000f00 > 0
779
+ fmt.bottom = xf_borders & 0x0000f000 > 0
780
+ fmt.left_color = COLOR_CODES[xf_borders & 0x007f0000] || :border
781
+ fmt.right_color = COLOR_CODES[xf_borders & 0x3f800000] || :border
782
+ fmt.cross_down = xf_borders & 0x40000000 > 0
783
+ fmt.cross_up = xf_borders & 0x80000000 > 0
784
+ fmt.top_color = COLOR_CODES[xf_brdcolors & 0x0000007f] || :border
785
+ fmt.bottom_color = COLOR_CODES[xf_brdcolors & 0x00003f80] || :border
786
+ fmt.diagonal_color = COLOR_CODES[xf_brdcolors & 0x001fc000] || :border
787
+ #fmt.diagonal_style = COLOR_CODES[xf_brdcolors & 0x01e00000]
788
+ fmt.pattern = xf_brdcolors & 0xfc000000
789
+ fmt.pattern_fg_color = COLOR_CODES[xf_pattern & 0x007f] || :border
790
+ fmt.pattern_bg_color = COLOR_CODES[xf_pattern & 0x3f80] || :pattern_bg
733
791
  @workbook.add_format fmt
734
792
  end
735
793
  def set_cell worksheet, row, column, xf, value=nil
736
794
  cells = @current_row_block[row] ||= Row.new(nil, row)
737
- cells.formats[column] = @workbook.format(xf)
795
+ cells.formats[column] = @workbook.format(xf) unless xf == 0
738
796
  cells[column] = value
739
797
  end
740
798
  def set_row_address worksheet, work, pos, len
@@ -758,21 +816,27 @@ class Reader
758
816
  @current_row_block_offset ||= [pos]
759
817
  index, first_used, first_unused, flags,
760
818
  hasdefaults, offset = work.unpack binfmt(:row)
819
+ format = nil
761
820
  # TODO: read attributes from work[13,3], read flags
762
- if hasdefaults > 0
763
- # TODO: read row default XF
821
+ if hasdefaults > 0 && work.size > 13
822
+ xf, = work[-2..-1].unpack 'v'
823
+ format = @workbook.format(xf)
764
824
  end
765
- worksheet.set_row_address index, :first_used => first_used,
766
- :first_unused => first_unused,
767
- :index => index,
768
- :row_block => @current_row_block_offset,
769
- :offset => @current_row_block_offset[0]
770
- #:first_cell => offset
825
+ worksheet.set_row_address index,
826
+ :default_format => format,
827
+ :first_used => first_used,
828
+ :first_unused => first_unused,
829
+ :index => index,
830
+ :row_block => @current_row_block_offset,
831
+ :offset => @current_row_block_offset[0]
832
+ #:first_cell => offset
771
833
  end
772
834
  private
773
835
  def extend_internals version
774
836
  require 'spreadsheet/excel/internals/biff%i' % version
775
837
  extend Internals.const_get('Biff%i' % version)
838
+ ## spreadsheets may not include a codepage record.
839
+ @workbook.encoding = encoding 850 if version < 8
776
840
  rescue LoadError
777
841
  end
778
842
  def extend_reader version
@@ -17,6 +17,10 @@ class Worksheet < Spreadsheet::Worksheet
17
17
  @offset, @ole, @reader = opts[:offset], opts[:ole], opts[:reader]
18
18
  @dimensions = nil
19
19
  end
20
+ def column idx
21
+ ensure_rows_read
22
+ super
23
+ end
20
24
  def date_base
21
25
  @workbook.date_base
22
26
  end
@@ -35,6 +39,7 @@ class Worksheet < Spreadsheet::Worksheet
35
39
  @rows.fetch idx do
36
40
  if addr = @row_addresses[idx]
37
41
  row = @reader.read_row self, addr
42
+ row.default_format = addr[:default_format]
38
43
  row.worksheet = self
39
44
  row
40
45
  else
@@ -73,10 +78,12 @@ class Worksheet < Spreadsheet::Worksheet
73
78
  @dimensions[1] = [ @rows.size, @row_addresses.size ].compact.max
74
79
  compact = @rows.compact
75
80
  first_rows = compact.collect do |row| index_of_first row end.compact.min
76
- first_addrs = @row_addresses.collect do |addr| addr[:first_used] end.min
81
+ first_addrs = @row_addresses.compact.collect do |addr|
82
+ addr[:first_used] end.min
77
83
  @dimensions[2] = [ first_rows, first_addrs ].compact.min
78
84
  last_rows = compact.collect do |row| row.size end.max
79
- last_addrs = @row_addresses.collect do |addr| addr[:first_unused] end.max
85
+ last_addrs = @row_addresses.compact.collect do |addr|
86
+ addr[:first_unused] end.max
80
87
  @dimensions[3] = [last_rows, last_addrs].compact.max
81
88
  @dimensions
82
89
  end
@@ -21,28 +21,6 @@ class Format < DelegateClass(Format)
21
21
  color_code(@format.send(key) || default)
22
22
  end
23
23
  end
24
- XF_H_ALIGN = {
25
- :default => 0,
26
- :left => 1,
27
- :center => 2,
28
- :right => 3,
29
- :fill => 4,
30
- :justify => 5,
31
- :merge => 6,
32
- :distributed => 7,
33
- }
34
- XF_TEXT_DIRECTION = {
35
- :context => 0,
36
- :left_to_right => 1,
37
- :right_to_left => 2,
38
- }
39
- XF_V_ALIGN = {
40
- :top => 0,
41
- :middle => 1,
42
- :bottom => 2,
43
- :justify => 3,
44
- :distributed => 4,
45
- }
46
24
  boolean :hidden, :locked, :merge_range, :shrink, :text_justlast, :text_wrap,
47
25
  :cross_down, :cross_up, :left, :right, :top, :bottom
48
26
  color :left_color, :border
@@ -54,7 +32,7 @@ class Format < DelegateClass(Format)
54
32
  color :pattern_bg_color, :pattern_bg
55
33
  attr_accessor :xf_index
56
34
  attr_reader :format
57
- def initialize writer, workbook, format, type=:style
35
+ def initialize writer, workbook, format=workbook.default_format, type=:format
58
36
  @type = type.to_s.downcase
59
37
  @format = format
60
38
  @writer = writer
@@ -219,7 +197,7 @@ class Format < DelegateClass(Format)
219
197
  end
220
198
  def xf_pattern
221
199
  ptrn = pattern_fg_color
222
- ptrn |= pattern_bg_color
200
+ ptrn |= pattern_bg_color << 7
223
201
  ptrn
224
202
  end
225
203
  def xf_rotation
@@ -235,7 +213,7 @@ class Format < DelegateClass(Format)
235
213
  rot
236
214
  end
237
215
  def xf_type_prot type
238
- type = type.to_s.downcase == 'style' ? 0xfff4 : 0x0000
216
+ type = type.to_s.downcase == 'style' ? 0xfff5 : 0x0000
239
217
  type |= locked
240
218
  type |= hidden << 1
241
219
  type
@@ -243,6 +221,7 @@ class Format < DelegateClass(Format)
243
221
  def xf_used_attr
244
222
  atr_num = num_format & 1
245
223
  atr_fnt = font_index & 1
224
+ atr_fnt = 1 unless @format.font.color == :text
246
225
  atr_alc = 0
247
226
  if horizontal_align != 0 \
248
227
  || vertical_align != 2 \
@@ -252,9 +231,13 @@ class Format < DelegateClass(Format)
252
231
  atr_alc = 1
253
232
  end
254
233
  atr_bdr = [top, bottom, left, right, cross_up, cross_down].max
255
- atr_pat = @format.font.color != :text \
256
- || @format.bg_color != :pattern_bg \
257
- || pattern != 0x00 ? 1 : 0
234
+ atr_pat = 0
235
+ if @format.pattern_fg_color != :border \
236
+ || @format.pattern_bg_color != :pattern_bg \
237
+ || pattern != 0x00
238
+ then
239
+ atr_pat = 1
240
+ end
258
241
  atr_prot = hidden? || locked? ? 1 : 0
259
242
  attrs = atr_num
260
243
  attrs |= atr_fnt << 1
@@ -262,7 +245,7 @@ class Format < DelegateClass(Format)
262
245
  attrs |= atr_bdr << 3
263
246
  attrs |= atr_pat << 4
264
247
  attrs |= atr_prot << 5
265
- attrs
248
+ attrs << 2
266
249
  end
267
250
  end
268
251
  end
@@ -48,16 +48,23 @@ class Workbook < Spreadsheet::Writer
48
48
  @number_formats.delete workbook
49
49
  @worksheets.delete workbook
50
50
  end
51
- def collect_formats workbook
51
+ def collect_formats workbook, opts={}
52
52
  # The default cell format is always present in an Excel file, described by
53
53
  # the XF record with the fixed index 15 (0-based). By default, it uses the
54
54
  # worksheet/workbook default cell style, described by the very first XF
55
55
  # record (index 0).
56
56
  formats = []
57
+ unless opts[:existing_document]
58
+ 15.times do
59
+ formats.push Format.new(self, workbook, workbook.default_format, :style)
60
+ end
61
+ formats.push Format.new(self, workbook)
62
+ end
57
63
  workbook.formats.each do |fmt|
58
- format = Format.new self, workbook, fmt
59
- format.xf_index = formats.size
60
- formats.push format
64
+ formats.push Format.new(self, workbook, fmt)
65
+ end
66
+ formats.each_with_index do |fmt, idx|
67
+ fmt.xf_index = idx
61
68
  end
62
69
  @formats[workbook] = formats
63
70
  end
@@ -68,6 +75,7 @@ class Workbook < Spreadsheet::Writer
68
75
  end
69
76
  total = current.size
70
77
  current.uniq!
78
+ current.delete ''
71
79
  if (stored - current).empty?
72
80
  ## if all previously stored strings are still needed, we don't have to
73
81
  # rewrite all cells because the sst-index of such string does not change.
@@ -78,7 +86,10 @@ class Workbook < Spreadsheet::Writer
78
86
  end
79
87
  end
80
88
  def font_index workbook, font_key
81
- @fonts[workbook][font_key] || 0
89
+ idx = @fonts[workbook][font_key] || 0
90
+ ## this appears to be undocumented: the first 4 fonts seem to be accessed
91
+ # with a 0-based index, but all subsequent font indices are 1-based.
92
+ idx > 3 ? idx.next : idx
82
93
  end
83
94
  def number_format_index workbook, format
84
95
  @number_formats[workbook][format] || 0
@@ -135,7 +146,7 @@ class Workbook < Spreadsheet::Writer
135
146
  # Copy unchanged data verbatim, adjust offsets and write new records for
136
147
  # changed data.
137
148
  def write_changes workbook, io
138
- collect_formats workbook
149
+ collect_formats workbook, :existing_document => true
139
150
  reader = workbook.ole
140
151
  sheet_data = {}
141
152
  sst_status, sst_total, sst_strings = complete_sst_update? workbook
@@ -299,7 +310,7 @@ class Workbook < Spreadsheet::Writer
299
310
  end
300
311
  def write_fonts workbook, writer
301
312
  fonts = @fonts[workbook] = {}
302
- workbook.formats.each do |format|
313
+ @formats[workbook].each do |format|
303
314
  if(font = format.font) && !fonts.include?(font.key)
304
315
  fonts.store font.key, fonts.size
305
316
  write_font workbook, writer, font
@@ -317,6 +328,8 @@ class Workbook < Spreadsheet::Writer
317
328
  BUILTIN_FORMATS.each do |idx, str|
318
329
  formats.store client(str, 'UTF8'), idx
319
330
  end
331
+ ## Ensure at least a 'GENERAL' format is written
332
+ formats.delete client('GENERAL', 'UTF8')
320
333
  idx = 0xa4
321
334
  workbook.formats.each do |fmt|
322
335
  str = fmt.number_format
@@ -342,6 +355,7 @@ class Workbook < Spreadsheet::Writer
342
355
  # ○ DSF ➜ 6.32
343
356
  write_dsf workbook, buffer1
344
357
  # ○ TABID
358
+ write_tabid workbook, buffer1
345
359
  # ○ FNGROUPCOUNT
346
360
  # ○ Workbook Protection Block ➜ 5.18
347
361
  write_protect workbook, buffer1
@@ -505,6 +519,9 @@ class Workbook < Spreadsheet::Writer
505
519
  ]
506
520
  write_op writer, 0x0293, data.pack('vC2')
507
521
  end
522
+ def write_tabid workbook, writer
523
+ write_op writer, 0x013d, [1].pack('v')
524
+ end
508
525
  def write_window1 workbook, writer
509
526
  data = [
510
527
  0x0000, # Horizontal position of the document window
@@ -555,20 +572,7 @@ class Workbook < Spreadsheet::Writer
555
572
  # the XF record with the fixed index 15 (0-based). By default, it uses the
556
573
  # worksheet/workbook default cell style, described by the very first XF
557
574
  # record (index 0).
558
- formats = @formats[workbook].dup
559
- default = formats.first
560
- ## First 15 formats, or dummy/default styles if there are fewer formats
561
- fmts1 = formats.slice!(0,15)
562
- while fmts1.size < 15 do
563
- fmt = Format.new self, workbook, workbook.default_format, writer
564
- fmt.xf_index = fmts1.size
565
- fmts1.push fmt
566
- end
567
- fmts1.each do |fmt| fmt.write_xf writer end
568
- ## Default cell format
569
- default.write_xf writer, :format
570
- ## remaining formats
571
- formats.each do |fmt| fmt.write_xf writer end
575
+ @formats[workbook].each do |fmt| fmt.write_xf writer end
572
576
  end
573
577
  def sst_index worksheet, str
574
578
  @sst[worksheet][str]
@@ -100,7 +100,7 @@ class Worksheet
100
100
  end
101
101
  def strings
102
102
  @worksheet.inject [] do |memo, row|
103
- strings = row.select do |cell| cell.is_a? String end
103
+ strings = row.select do |cell| cell.is_a?(String) && !cell.empty? end
104
104
  memo.concat strings
105
105
  end
106
106
  end
@@ -171,8 +171,9 @@ class Worksheet
171
171
  # RSTRING ➜ 6.84 (BIFF5/BIFF7)
172
172
  multiples, first_idx = nil
173
173
  row.each_with_index do |cell, idx|
174
- if multiples && (!multiples.last.is_a?(cell.class) \
175
- || (cell.is_a?(Numeric) && cell.abs < 0.1))
174
+ cell = nil if cell == ''
175
+ number = cell.is_a?(Float) && cell.to_s.length > 5
176
+ if multiples && (!multiples.last.is_a?(cell.class) || number)
176
177
  write_multiples row, first_idx, multiples
177
178
  multiples, first_idx = nil
178
179
  end
@@ -196,7 +197,7 @@ class Worksheet
196
197
  # 10^9. Not sure what is a good rule of thumb here, but it seems that
197
198
  # Decimal Numbers with more than 4 significant digits are not represented
198
199
  # with sufficient precision by RK
199
- if cell.is_a?(Float) && cell.to_s.length > 5
200
+ if number
200
201
  write_number row, idx
201
202
  elsif multiples
202
203
  multiples.push cell
@@ -240,6 +241,43 @@ class Worksheet
240
241
  end
241
242
  @io.write reader.read(endpos - lastpos)
242
243
  end
244
+ def write_colinfo bunch
245
+ col = bunch.first
246
+ width = col.width.to_f * 256
247
+ xf_idx = @workbook.xf_index @worksheet.workbook, col.default_format
248
+ opts = 0
249
+ opts |= 0x0001 if col.hidden?
250
+ opts |= col.outline_level.to_i << 8
251
+ opts |= 0x1000 if col.collapsed?
252
+ data = [
253
+ col.idx, # Index to first column in the range
254
+ bunch.last.idx, # Index to last column in the range
255
+ width.to_i, # Width of the columns in 1/256 of the width of the zero
256
+ # character, using default font (first FONT record in the
257
+ # file)
258
+ xf_idx.to_i, # Index to XF record (➜ 6.115) for default column formatting
259
+ opts, # Option flags:
260
+ # Bits Mask Contents
261
+ # 0 0x0001 1 = Columns are hidden
262
+ # 10-8 0x0700 Outline level of the columns
263
+ # (0 = no outline)
264
+ # 12 0x1000 1 = Columns are collapsed
265
+ ]
266
+ write_op opcode(:colinfo), data.pack(binfmt(:colinfo))
267
+ end
268
+ def write_colinfos
269
+ cols = @worksheet.columns
270
+ bunch = []
271
+ cols.each_with_index do |column, idx|
272
+ if column
273
+ bunch << column
274
+ if cols[idx.next] != column
275
+ write_colinfo bunch
276
+ bunch.clear
277
+ end
278
+ end
279
+ end
280
+ end
243
281
  def write_defaultrowheight
244
282
  data = [
245
283
  0x00, # Option flags:
@@ -252,6 +290,20 @@ class Worksheet
252
290
  ]
253
291
  write_op 0x0225, data.pack('v2')
254
292
  end
293
+ def write_defcolwidth
294
+ # Offset Size Contents
295
+ # 0 2 Column width in characters, using the width of the zero
296
+ # character from default font (first FONT record in the
297
+ # file). Excel adds some extra space to the default width,
298
+ # depending on the default font and default font size. The
299
+ # algorithm how to exactly calculate the resulting column
300
+ # width is not known.
301
+ #
302
+ # Example: The default width of 8 set in this record results
303
+ # in a column width of 8.43 using Arial font with a size of
304
+ # 10 points.
305
+ write_op 0x0055, [8].pack('v')
306
+ end
255
307
  def write_dimensions
256
308
  # Offset Size Contents
257
309
  # 0 4 Index to first used row
@@ -268,11 +320,12 @@ class Worksheet
268
320
  # Write a cell with a Formula. May write an additional String record depending
269
321
  # on the stored result of the Formula.
270
322
  def write_formula row, idx
323
+ xf_idx = @workbook.xf_index @worksheet.workbook, row.format(idx)
271
324
  cell = row[idx]
272
325
  data1 = [
273
326
  row.idx, # Index to row
274
327
  idx, # Index to column
275
- 0, # Index to XF record (➜ 6.115)
328
+ xf_idx, # Index to XF record (➜ 6.115)
276
329
  ].pack 'v3'
277
330
  data2 = nil
278
331
  case value = cell.value
@@ -345,7 +398,9 @@ class Worksheet
345
398
  # ○ Page Settings Block ➜ 5.4
346
399
  # ○ Worksheet Protection Block ➜ 5.18
347
400
  # ○ DEFCOLWIDTH ➜ 6.29
401
+ write_defcolwidth
348
402
  # ○○ COLINFO ➜ 6.18
403
+ write_colinfos
349
404
  # ○ SORT ➜ 6.95
350
405
  # ● DIMENSIONS ➜ 6.31
351
406
  write_dimensions
@@ -400,9 +455,9 @@ class Worksheet
400
455
  idx, # Index to first column (fc)
401
456
  ]
402
457
  # List of nc=lc-fc+1 16-bit indexes to XF records (➜ 6.115)
403
- multiples.each do |cell|
404
- # TODO: XF indices
405
- data.push 0, encode_rk(cell)
458
+ multiples.each_with_index do |cell, cell_idx|
459
+ xf_idx = @workbook.xf_index @worksheet.workbook, row.format(idx + cell_idx)
460
+ data.push xf_idx, encode_rk(cell)
406
461
  fmt << 'vV'
407
462
  end
408
463
  # Index to last column (lc)
@@ -477,9 +532,12 @@ class Worksheet
477
532
  0, # Not used
478
533
  has_defaults,
479
534
  0, # OOffice does not set this - ignore until someone complains
535
+ 1,
536
+ 15,
537
+ 0,
480
538
  ]
481
539
  # OpenOffice apparently can't read Rows with a length other than 16 Bytes
482
- fmt = binfmt(:row) + 'x3'
540
+ fmt = binfmt(:row) + 'C3'
483
541
  =begin
484
542
  if format = row.default_format
485
543
  fmt = fmt + 'xv'
@@ -59,9 +59,12 @@ module Spreadsheet
59
59
  :korean_hangul, :korean_johab, :chinese_simplified,
60
60
  :chinese_traditional, :greek, :turkish, :vietnamese,
61
61
  :hebrew, :arabic, :cyrillic, :thai, :iso_latin2, :oem_latin1
62
- def initialize name
62
+ def initialize name, opts={}
63
63
  self.name = name
64
64
  @color = :text
65
+ opts.each do |key, val|
66
+ self.send "#{key}=", val
67
+ end
65
68
  end
66
69
  ##
67
70
  # Sets #weight to :bold if(_bool_), :normal otherwise.
@@ -33,7 +33,7 @@ module Spreadsheet
33
33
  ##
34
34
  # Color attributes
35
35
  colors :bottom_color, :top_color, :left_color, :right_color,
36
- :bg_color, :pattern_fg_color, :pattern_bg_color,
36
+ :pattern_fg_color, :pattern_bg_color,
37
37
  :diagonal_color
38
38
  ##
39
39
  # Text direction
@@ -72,16 +72,15 @@ module Spreadsheet
72
72
  # Text rotation
73
73
  attr_reader :rotation
74
74
  def initialize opts={}
75
- @font = Font.new client("Arial", 'UTF8')
76
- @number_format = client 'General', 'UTF8'
75
+ @font = Font.new client("Arial", 'UTF8'), :family => :swiss
76
+ @number_format = client 'GENERAL', 'UTF8'
77
77
  @rotation = 0
78
- @bg_color = :pattern_bg
79
78
  @pattern = 0
80
- @bottom_color = :border
81
- @top_color = :border
82
- @left_color = :border
83
- @right_color = :border
84
- @diagonal_color = :border
79
+ @bottom_color = :builtin_black
80
+ @top_color = :builtin_black
81
+ @left_color = :builtin_black
82
+ @right_color = :builtin_black
83
+ @diagonal_color = :builtin_black
85
84
  @pattern_fg_color = :border
86
85
  @pattern_bg_color = :pattern_bg
87
86
  # Temp code to prevent merged formats in non-merged cells.
@@ -47,7 +47,7 @@ module Spreadsheet
47
47
  # Set the default Format used when writing a Cell if no explicit Format is
48
48
  # stored for the cell.
49
49
  def default_format= format
50
- @worksheet.add_format format
50
+ @worksheet.add_format format if @worksheet
51
51
  @default_format = format
52
52
  end
53
53
  ##
@@ -60,10 +60,11 @@ module Spreadsheet
60
60
  index_of_first self
61
61
  end
62
62
  ##
63
- # The Format for the Cell at _idx_ (0-based), or default_format
64
- # if no Format is set.
63
+ # The Format for the Cell at _idx_ (0-based), or the first valid Format in
64
+ # Row#default_format, Column#default_format and Worksheet#default_format.
65
65
  def format idx
66
- @formats[idx] || @default_format
66
+ @formats[idx] || @default_format \
67
+ || @worksheet.column(idx).default_format if @worksheet
67
68
  end
68
69
  ##
69
70
  # Set the Format for the Cell at _idx_ (0-based).
@@ -27,13 +27,14 @@ module Spreadsheet
27
27
  # Add a Font to the Workbook. Used by the parser. You should not need to
28
28
  # use this Method.
29
29
  def add_font font
30
- @fonts.push font
30
+ @fonts.push(font).uniq! if font
31
+ font
31
32
  end
32
33
  ##
33
34
  # Add a Format to the Workbook. If you use Row#set_format, you should not
34
35
  # need to use this Method.
35
36
  def add_format format
36
- @formats.push(format).uniq!
37
+ @formats.push(format) if format && !@formats.include?(format)
37
38
  format
38
39
  end
39
40
  ##
@@ -1,4 +1,5 @@
1
1
  require 'date'
2
+ require 'spreadsheet/column'
2
3
  require 'spreadsheet/encodings'
3
4
  require 'spreadsheet/row'
4
5
 
@@ -14,22 +15,27 @@ module Spreadsheet
14
15
  # #rows :: The Rows in this Worksheet. It is not recommended to
15
16
  # Manipulate this Array directly. If you do, call
16
17
  # #updated_from with the smallest modified index.
18
+ # #columns :: The Column formatting in this Worksheet. Column
19
+ # instances may appear at more than one position in #columns.
20
+ # If you modify a Column directly, your changes will be
21
+ # reflected in all those positions.
17
22
  class Worksheet
18
23
  include Encodings
19
- attr_accessor :name, :workbook
20
- attr_reader :rows
21
24
  include Enumerable
25
+ attr_accessor :name, :workbook
26
+ attr_reader :rows, :columns
22
27
  def initialize opts={}
23
28
  @dimensions = [0,0,0,0]
24
29
  @name = opts[:name] || 'Worksheet'
25
30
  @workbook = opts[:workbook]
26
31
  @rows = []
32
+ @columns = []
27
33
  end
28
34
  ##
29
35
  # Add a Format to the Workbook. If you use Row#set_format, you should not
30
36
  # need to use this Method.
31
37
  def add_format fmt
32
- @workbook.add_format fmt
38
+ @workbook.add_format fmt if fmt
33
39
  end
34
40
  ##
35
41
  # Get the enriched value of the Cell at _row_, _column_.
@@ -38,6 +44,11 @@ module Spreadsheet
38
44
  row(row)[column]
39
45
  end
40
46
  ##
47
+ # Returns the Column at _idx_.
48
+ def column idx
49
+ @columns[idx] || Column.new(idx, default_format, :worksheet => self)
50
+ end
51
+ ##
41
52
  # The number of columns in this Worksheet which contain data.
42
53
  def column_count
43
54
  dimensions[3] - dimensions[2]
@@ -59,7 +70,7 @@ module Spreadsheet
59
70
  # Set the default Format of this Worksheet.
60
71
  def default_format= format
61
72
  @default_format = format
62
- add_format format if format
73
+ add_format format
63
74
  format
64
75
  end
65
76
  ##
@@ -87,6 +98,31 @@ module Spreadsheet
87
98
  @workbook.encoding
88
99
  end
89
100
  ##
101
+ # Sets the default Format of the column at _idx_.
102
+ #
103
+ # _idx_ may be an Integer, or an Enumerable that iterates over a number of
104
+ # Integers.
105
+ #
106
+ # _format_ is a Format, or nil if you want to remove the Formatting at _idx_
107
+ #
108
+ # Returns an instance of Column if _idx_ is an Integer, an Array of Columns
109
+ # otherwise.
110
+ def format_column idx, format=nil, opts={}
111
+ opts[:worksheet] = self
112
+ res = case idx
113
+ when Integer
114
+ column = nil
115
+ if format
116
+ column = Column.new(idx, format, opts)
117
+ end
118
+ @columns[idx] = column
119
+ else
120
+ idx.collect do |col| format_column col, format, opts end
121
+ end
122
+ shorten @columns
123
+ res
124
+ end
125
+ ##
90
126
  # Insert a Row at _idx_ (0-based) containing _cells_
91
127
  def insert_row idx, cells=[]
92
128
  res = @rows.insert idx, Row.new(self, idx, cells)
data/test/font.rb CHANGED
@@ -77,11 +77,11 @@ module Spreadsheet
77
77
  assert_equal true, @font.shadow
78
78
  end
79
79
  def test_size
80
- assert_equal 10, @font.size
80
+ assert_equal 10, @font.size
81
81
  @font.size = 12
82
- assert_equal 12, @font.size
82
+ assert_equal 12, @font.size
83
83
  @font.size = 11.2
84
- assert_equal 11.2, @font.size
84
+ assert_equal 11.2, @font.size
85
85
  assert_raises ArgumentError do @font.size = "123" end
86
86
  end
87
87
  def test_strikeout
data/test/integration.rb CHANGED
@@ -37,7 +37,7 @@ module Spreadsheet
37
37
  book = Spreadsheet.open path
38
38
  assert_instance_of Excel::Workbook, book
39
39
  assert_equal 8, book.biff_version
40
- assert_equal @@iconv.iconv('Microsoft Excel 97/2000/XP'),
40
+ assert_equal @@iconv.iconv('Microsoft Excel 97/2000/XP'),
41
41
  book.version_string
42
42
  enc = 'UTF-16LE'
43
43
  if defined? Encoding
@@ -532,13 +532,13 @@ module Spreadsheet
532
532
  end
533
533
  end
534
534
  assert_equal long, str4
535
- sheet = book.worksheet 0
535
+ sheet = book.worksheet 0
536
536
  sheet[0,0] = 4
537
537
  row = sheet.row 1
538
538
  row[0] = 3
539
539
  book.write path
540
540
  assert_nothing_raised do book = Spreadsheet.open path end
541
- sheet = book.worksheet 0
541
+ sheet = book.worksheet 0
542
542
  assert_equal 10, sheet.row_count
543
543
  assert_equal 11, sheet.column_count
544
544
  useds = [0,0,0,0,0,0,0,1,0,0]
@@ -635,7 +635,7 @@ module Spreadsheet
635
635
  end
636
636
  end
637
637
  assert_equal long, str4
638
- sheet = book.worksheet 0
638
+ sheet = book.worksheet 0
639
639
  sheet[0,0] = 4
640
640
  str5 = 'A completely different String'
641
641
  sheet[0,1] = str5
@@ -647,7 +647,7 @@ module Spreadsheet
647
647
  assert_equal str2, book.shared_string(1)
648
648
  assert_equal str3, book.shared_string(2)
649
649
  assert_equal str4, book.shared_string(3)
650
- sheet = book.worksheet 0
650
+ sheet = book.worksheet 0
651
651
  assert_equal 10, sheet.row_count
652
652
  assert_equal 11, sheet.column_count
653
653
  useds = [0,0,0,0,0,0,0,1,0,0]
@@ -719,6 +719,13 @@ module Spreadsheet
719
719
  str2 = 'Another Shared String'
720
720
  str3 = '1234567890 ' * 1000
721
721
  str4 = '9876543210 ' * 1000
722
+ fmt1 = Format.new :italic => true, :color => :blue
723
+ sheet1.format_column 1, fmt1, :width => 20
724
+ fmt2 = Format.new(:weight => :bold, :color => :yellow)
725
+ sheet1.format_column 2, fmt2
726
+ sheet1.format_column 3, Format.new(:weight => :bold, :color => :red)
727
+ sheet1.format_column 6..9, fmt1
728
+ sheet1.format_column [4,5,7], fmt2
722
729
  sheet1[0,0] = str1
723
730
  sheet1.row(0).push str1
724
731
  sheet1.row(1).concat [str2, str2]
@@ -797,10 +804,15 @@ module Spreadsheet
797
804
  assert_equal 2, book.worksheets.size
798
805
  sheet = book.worksheets.first
799
806
  assert_instance_of Spreadsheet::Excel::Worksheet, sheet
800
- assert_equal "W\000o\000r\000k\000s\000h\000e\000e\000t\0001\000",
807
+ assert_equal "W\000o\000r\000k\000s\000h\000e\000e\000t\0001\000",
801
808
  sheet.name
802
809
  assert_not_nil sheet.offset
810
+ assert_not_nil col = sheet.column(1)
811
+ assert_equal true, col.default_format.font.italic?
812
+ assert_equal :blue, col.default_format.font.color
813
+ assert_equal 20, col.width
803
814
  row = sheet.row 0
815
+ assert_equal col.default_format, row.format(1)
804
816
  assert_equal str1, row[0]
805
817
  assert_equal str1, sheet[0,0]
806
818
  assert_equal str1, sheet.cell(0,0)
@@ -880,7 +892,7 @@ module Spreadsheet
880
892
  assert_equal 1.00005, sheet1[10,4]
881
893
  assert_instance_of Spreadsheet::Excel::Worksheet, sheet
882
894
  sheet = book.worksheets.last
883
- assert_equal "m\000y\000 \000n\000a\000m\000e\000",
895
+ assert_equal "m\000y\000 \000n\000a\000m\000e\000",
884
896
  sheet.name
885
897
  assert_not_nil sheet.offset
886
898
  end
@@ -893,6 +905,8 @@ module Spreadsheet
893
905
  str2 = @@iconv.iconv 'Another Shared String'
894
906
  str3 = @@iconv.iconv('1234567890 ' * 1000)
895
907
  str4 = @@iconv.iconv('9876543210 ' * 1000)
908
+ fmt = Format.new :italic => true, :color => :blue
909
+ sheet1.format_column 1, fmt, :width => 20
896
910
  sheet1[0,0] = str1
897
911
  sheet1.row(0).push str1
898
912
  sheet1.row(1).concat [str2, str2]
@@ -948,7 +962,11 @@ module Spreadsheet
948
962
  assert_instance_of Spreadsheet::Excel::Worksheet, sheet
949
963
  assert_equal "Worksheet1", sheet.name
950
964
  assert_not_nil sheet.offset
965
+ assert_not_nil col = sheet.column(1)
966
+ assert_equal true, col.default_format.font.italic?
967
+ assert_equal :blue, col.default_format.font.color
951
968
  row = sheet.row 0
969
+ assert_equal col.default_format, row.format(1)
952
970
  assert_equal str1, row[0]
953
971
  assert_equal str1, sheet[0,0]
954
972
  assert_equal str1, sheet.cell(0,0)
@@ -1008,14 +1026,9 @@ module Spreadsheet
1008
1026
  assert_equal [1,2,3,4,5,6,7,8,9,0], sheet.cell(7,1..10)
1009
1027
  assert_instance_of Spreadsheet::Excel::Worksheet, sheet
1010
1028
  sheet = book.worksheets.last
1011
- assert_equal "my name",
1029
+ assert_equal "my name",
1012
1030
  sheet.name
1013
1031
  assert_not_nil sheet.offset
1014
1032
  end
1015
- def test_read_bsv
1016
- book = Spreadsheet.open '/home/hwyss/cogito/oddb.org/data/xls/BSV_per_2008.10.01.xls'
1017
- sheet = book.worksheet 0
1018
- assert_equal Date.new(2000), sheet[1,6]
1019
- end
1020
1033
  end
1021
1034
  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.0
4
+ version: 0.6.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Hannes Wyss
@@ -9,9 +9,19 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2008-10-13 00:00:00 +02:00
12
+ date: 2008-10-17 00:00:00 +02:00
13
13
  default_executable:
14
14
  dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: ruby-ole
17
+ type: :runtime
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: "0"
24
+ version:
15
25
  - !ruby/object:Gem::Dependency
16
26
  name: hoe
17
27
  type: :development