spreadsheet 0.6.0 → 0.6.1

Sign up to get free protection for your applications and to get access to all the features.
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