spreadsheet 0.6.2.1 → 0.6.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,3 +1,21 @@
1
+ === 0.6.3 / 2009-01-14
2
+
3
+ * 1 Bugfix
4
+
5
+ * Fixes the issue reported by Corey Martella in
6
+ http://rubyforge.org/forum/message.php?msg_id=63651
7
+ as well as other issues engendered by the decision to always shorten
8
+ Rows to the last non-nil value.
9
+
10
+ * 2 minor enhancements
11
+
12
+ * Added bin/xlsopcodes, a tool for examining Excel files
13
+
14
+ * Documents created by Spreadsheet can now be Printed in Excel and
15
+ Excel-Viewer.
16
+ This issue was reported by Spencer Turner in
17
+ http://rubyforge.org/tracker/index.php?func=detail&aid=23287&group_id=678&atid=2677
18
+
1
19
  === 0.6.2.1 / 2008-12-18
2
20
 
3
21
  * 1 Bugfix
@@ -4,6 +4,7 @@ LICENSE.txt
4
4
  Manifest.txt
5
5
  README.txt
6
6
  Rakefile
7
+ bin/xlsopcodes
7
8
  lib/parseexcel.rb
8
9
  lib/parseexcel/parseexcel.rb
9
10
  lib/parseexcel/parser.rb
@@ -32,18 +33,23 @@ lib/spreadsheet/excel/writer/worksheet.rb
32
33
  lib/spreadsheet/font.rb
33
34
  lib/spreadsheet/format.rb
34
35
  lib/spreadsheet/formula.rb
36
+ lib/spreadsheet/helpers.rb
35
37
  lib/spreadsheet/link.rb
36
38
  lib/spreadsheet/row.rb
37
39
  lib/spreadsheet/workbook.rb
38
40
  lib/spreadsheet/worksheet.rb
39
41
  lib/spreadsheet/writer.rb
40
42
  test/data/test_copy.xls
43
+ test/data/test_datetime.xls
41
44
  test/data/test_empty.xls
42
45
  test/data/test_version_excel5.xls
43
46
  test/data/test_version_excel95.xls
44
47
  test/data/test_version_excel97.xls
45
48
  test/excel/row.rb
49
+ test/excel/writer/worksheet.rb
46
50
  test/font.rb
47
51
  test/integration.rb
52
+ test/row.rb
53
+ test/suite.rb
48
54
  test/workbook.rb
49
55
  test/worksheet.rb
@@ -0,0 +1,18 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'spreadsheet'
4
+
5
+ source, target = ARGV
6
+
7
+ if source.nil?
8
+ puts "Usage: #{$0} <source> [<target>]"
9
+ exit -1
10
+ end
11
+
12
+ target = target ? File.open(target, 'w') : STDOUT
13
+
14
+ reader = Spreadsheet::Excel::Reader.new :print_opcodes => target
15
+ reader.setup source
16
+
17
+ while tuple = reader.get_next_chunk
18
+ end
@@ -42,7 +42,7 @@ module Spreadsheet
42
42
 
43
43
  ##
44
44
  # The version of Spreadsheet you are using.
45
- VERSION = '0.6.2.1'
45
+ VERSION = '0.6.3'
46
46
 
47
47
  ##
48
48
  # Default client Encoding. Change this value if your application uses a
@@ -98,8 +98,10 @@ module Internals
98
98
  :font => 'v5C3x',
99
99
  :labelsst => 'v3V',
100
100
  :number => "v3#{EIGHT_BYTE_DOUBLE}",
101
+ :pagesetup => "v8#{EIGHT_BYTE_DOUBLE}2v",
101
102
  :rk => 'v3V',
102
103
  :row => 'v4x4V',
104
+ :window2 => 'v4x2v2x4',
103
105
  :xf => 'v3C4V2v',
104
106
  }
105
107
  # From BIFF5 on, the built-in number formats will be omitted. The built-in
@@ -278,6 +280,11 @@ module Internals
278
280
  :useselfs => 0x0160, # ○ USESELFS (Natural Language Formulas) ➜ 6.105
279
281
  :dsf => 0x0161, # ○ DSF (Double Stream File) ➜ 6.32
280
282
  :refreshall => 0x01b7, # ○ REFRESHALL
283
+ ########################## ● Worksheet View Settings Block ➜ 5.5
284
+ :window2 => 0x023e, # ● WINDOW2 ➜ 5.110
285
+ :scl => 0x00a0, # ○ SCL ➜ 5.92 (BIFF4-BIFF8 only)
286
+ :pane => 0x0041, # ○ PANE ➜ 5.75
287
+ :selection => 0x001d, # ○○ SELECTION ➜ 5.93
281
288
  ########################## ○ Page Settings Block ➜ 5.4
282
289
  :hpagebreaks => 0x001b, # ○ HORIZONTALPAGEBREAKS ➜ 6.54
283
290
  :vpagebreaks => 0x001a, # ○ VERTICALPAGEBREAKS ➜ 6.107
@@ -290,7 +297,7 @@ module Internals
290
297
  :topmargin => 0x0028, # ○ TOPMARGIN ➜ 6.103
291
298
  :bottommargin => 0x0029, # ○ BOTTOMMARGIN ➜ 6.11
292
299
  # ○ PLS (opcode unknown)
293
- :setup => 0x00a1, # ○ SETUP ➜ 6.89 (BIFF4-BIFF8 only)
300
+ :pagesetup => 0x00a1, # ○ PAGESETUP ➜ 6.89 (BIFF4-BIFF8 only)
294
301
  :bitmap => 0x00e9, # ○ BITMAP ➜ 6.6 (Background-Bitmap, BIFF8 only)
295
302
  ##########################
296
303
  :printheaders => 0x002a, # ○ PRINTHEADERS ➜ 6.76
@@ -340,6 +347,8 @@ module Internals
340
347
  }
341
348
  NGILA_V_FX = XF_V_ALIGN.invert
342
349
  OPCODE_SIZE = 4
350
+ ROW_HEIGHT = 12.1
351
+ SST_CHUNKSIZE = 20
343
352
  def binfmt key
344
353
  BINARY_FORMATS[key]
345
354
  end
@@ -74,6 +74,23 @@ class Reader
74
74
  name
75
75
  end
76
76
  end
77
+ def get_next_chunk
78
+ pos = @pos
79
+ if pos < @data.size
80
+ op, len = @data[@pos,OPCODE_SIZE].unpack('v2')
81
+ @pos += OPCODE_SIZE
82
+ if len
83
+ work = @data[@pos,len]
84
+ @pos += len
85
+ code = SEDOCPO.fetch(op, op)
86
+ if io = @opts[:print_opcodes]
87
+ io.puts sprintf("0x%04x/%-16s %5i: %s",
88
+ op, code.inspect, len, work.inspect)
89
+ end
90
+ [ pos, code, len + OPCODE_SIZE, work]
91
+ end
92
+ end
93
+ end
77
94
  def in_row_block? op, previous
78
95
  if op == :row
79
96
  previous == op
@@ -98,17 +115,7 @@ class Reader
98
115
  # The entry-point for reading Excel-documents. Reads the Biff-Version and
99
116
  # loads additional reader-methods before proceeding with parsing the document.
100
117
  def read io
101
- @ole = Ole::Storage.open io
102
- @workbook = Workbook.new io, {}
103
- @book = @ole.file.open("Book") rescue @ole.file.open("Workbook")
104
- @data = @book.read
105
- read_bof
106
- @workbook.ole = @book
107
- @workbook.bof = @bof
108
- @workbook.version = @version
109
- biff = @workbook.biff_version
110
- extend_reader biff
111
- extend_internals biff
118
+ setup io
112
119
  read_workbook
113
120
  @workbook.default_format = @workbook.format 0
114
121
  @workbook.changes.clear
@@ -1047,6 +1054,19 @@ class Reader
1047
1054
  # TODO: Row spacing
1048
1055
  worksheet.set_row_address index, attrs
1049
1056
  end
1057
+ def setup io
1058
+ @ole = Ole::Storage.open io
1059
+ @workbook = Workbook.new io, {}
1060
+ @book = @ole.file.open("Book") rescue @ole.file.open("Workbook")
1061
+ @data = @book.read
1062
+ read_bof
1063
+ @workbook.ole = @book
1064
+ @workbook.bof = @bof
1065
+ @workbook.version = @version
1066
+ biff = @workbook.biff_version
1067
+ extend_reader biff
1068
+ extend_internals biff
1069
+ end
1050
1070
  private
1051
1071
  def extend_internals version
1052
1072
  require 'spreadsheet/excel/internals/biff%i' % version
@@ -1060,19 +1080,6 @@ class Reader
1060
1080
  extend Reader.const_get('Biff%i' % version)
1061
1081
  rescue LoadError
1062
1082
  end
1063
- def get_next_chunk
1064
- pos = @pos
1065
- op, len = @data[@pos,OPCODE_SIZE].unpack('v2')
1066
- @pos += OPCODE_SIZE
1067
- if len
1068
- work = @data[@pos,len]
1069
- @pos += len
1070
- code = SEDOCPO.fetch(op, op)
1071
- #puts "0x%04x/%-16s (0x%08x) %5i: %s" % [op, code.inspect, pos, len, work[0,16].inspect]
1072
- #puts "0x%04x/%-16s %5i: %s" % [op, code.inspect, len, work[0,32].inspect]
1073
- [ pos, code, len + OPCODE_SIZE, work]
1074
- end
1075
- end
1076
1083
  end
1077
1084
  end
1078
1085
  end
@@ -36,11 +36,11 @@ class Worksheet < Spreadsheet::Worksheet
36
36
  return if @row_addresses
37
37
  @dimensions = nil
38
38
  @row_addresses = []
39
- @reader.read_worksheet self, @offset
39
+ @reader.read_worksheet self, @offset if @reader
40
40
  end
41
41
  def row idx
42
- ensure_rows_read
43
42
  @rows[idx] or begin
43
+ ensure_rows_read
44
44
  if addr = @row_addresses[idx]
45
45
  row = @reader.read_row self, addr
46
46
  [:default_format, :height, :outline_level, :hidden, ].each do |key|
@@ -53,7 +53,7 @@ class Worksheet < Spreadsheet::Worksheet
53
53
  end
54
54
  end
55
55
  end
56
- def row_updated idx, row, args={}
56
+ def row_updated idx, row
57
57
  res = super
58
58
  @workbook.changes.store self, true
59
59
  @workbook.changes.store :boundsheets, true
@@ -83,11 +83,11 @@ class Worksheet < Spreadsheet::Worksheet
83
83
  index_of_first(@row_addresses) ].compact.min || 0
84
84
  @dimensions[1] = [ @rows.size, @row_addresses.size ].compact.max || 0
85
85
  compact = @rows.compact
86
- first_rows = compact.collect do |row| index_of_first row end.compact.min
86
+ first_rows = compact.collect do |row| row.first_used end.compact.min
87
87
  first_addrs = @row_addresses.compact.collect do |addr|
88
88
  addr[:first_used] end.min
89
89
  @dimensions[2] = [ first_rows, first_addrs ].compact.min || 0
90
- last_rows = compact.collect do |row| row.size end.max
90
+ last_rows = compact.collect do |row| row.first_unused end.max
91
91
  last_addrs = @row_addresses.compact.collect do |addr|
92
92
  addr[:first_unused] end.max
93
93
  @dimensions[3] = [last_rows, last_addrs].compact.max || 0
@@ -32,8 +32,8 @@ class Format < DelegateClass Spreadsheet::Format
32
32
  color :pattern_bg_color, :pattern_bg
33
33
  attr_accessor :xf_index
34
34
  attr_reader :format
35
- def initialize writer, workbook, format=workbook.default_format, type=:format
36
- @type = type.to_s.downcase
35
+ def initialize writer, workbook, format=workbook.default_format, opts={}
36
+ @opts = { :type => :format }.merge opts
37
37
  @format = format
38
38
  @writer = writer
39
39
  @workbook = workbook
@@ -62,7 +62,7 @@ class Format < DelegateClass Spreadsheet::Format
62
62
  writer.write [op,data.size].pack("v2")
63
63
  writer.write data
64
64
  end
65
- def write_xf writer, type=@type
65
+ def write_xf writer, type=@opts[:type]
66
66
  xf_type = xf_type_prot type
67
67
  data = [
68
68
  font_index, # Index to FONT record (➜ 6.43)
@@ -56,7 +56,8 @@ class Workbook < Spreadsheet::Writer
56
56
  formats = []
57
57
  unless opts[:existing_document]
58
58
  15.times do
59
- formats.push Format.new(self, workbook, workbook.default_format, :style)
59
+ formats.push Format.new(self, workbook, workbook.default_format,
60
+ :type => :style)
60
61
  end
61
62
  formats.push Format.new(self, workbook)
62
63
  end
@@ -258,7 +259,7 @@ class Workbook < Spreadsheet::Writer
258
259
  write_op writer, 0x000a
259
260
  end
260
261
  def write_extsst workbook, offsets, writer
261
- header = [8].pack('v')
262
+ header = [SST_CHUNKSIZE].pack('v')
262
263
  data = offsets.collect do |pair| pair.push(0).pack('Vv2') end
263
264
  write_op writer, 0x00ff, header, data
264
265
  end
@@ -374,7 +375,9 @@ class Workbook < Spreadsheet::Writer
374
375
  buffer1 = StringIO.new ''
375
376
  # ● BOF Type = workbook globals (➜ 6.8)
376
377
  write_bof workbook, buffer1, :globals
377
- # ○ File Protection Block ➜ 5.19
378
+ # ○ File Protection Block ➜ 4.19
379
+ # ○ WRITEACCESS User name (BIFF3-BIFF8, ➜ 5.112)
380
+ # ○ FILESHARING File sharing options (BIFF3-BIFF8, ➜ 5.44)
378
381
  # ○ CODEPAGE ➜ 6.17
379
382
  write_encoding workbook, buffer1
380
383
  # ○ DSF ➜ 6.32
@@ -382,42 +385,46 @@ class Workbook < Spreadsheet::Writer
382
385
  # ○ TABID
383
386
  write_tabid workbook, buffer1
384
387
  # ○ FNGROUPCOUNT
385
- # ○ Workbook Protection Block ➜ 5.18
388
+ # ○ Workbook Protection Block ➜ 4.18
389
+ # ○ WINDOWPROTECT Window settings: 1 = protected (➜ 5.111)
390
+ # ○ PROTECT Cell contents: 1 = protected (➜ 5.82)
386
391
  write_protect workbook, buffer1
392
+ # ○ OBJECTPROTECT Embedded objects: 1 = protected (➜ 5.72)
393
+ # ○ PASSWORD Hash value of the password; 0 = No password (➜ 5.76)
387
394
  write_password workbook, buffer1
388
- # WINDOW16.108
395
+ # BACKUP5.5
396
+ # ○ HIDEOBJ ➜ 5.56
397
+ # ● WINDOW1 ➜ 5.109
389
398
  write_window1 workbook, buffer1
390
- # ○ BACKUP6.5
391
- # ○ HIDEOBJ ➜ 6.52
392
- # ○ DATEMODE ➜ 6.25
399
+ # ○ DATEMODE5.28
393
400
  write_datemode workbook, buffer1
394
- # ○ PRECISION ➜ 6.74
401
+ # ○ PRECISION ➜ 5.79
395
402
  write_precision workbook, buffer1
396
403
  # ○ REFRESHALL
397
404
  write_refreshall workbook, buffer1
398
- # ○ BOOKBOOL ➜ 6.9
405
+ # ○ BOOKBOOL ➜ 5.9
399
406
  write_bookbool workbook, buffer1
400
- # ●● FONT ➜ 6.43
407
+ # ●● FONT ➜ 5.45
401
408
  write_fonts workbook, buffer1
402
- # ○○ FORMAT ➜ 6.45
409
+ # ○○ FORMAT ➜ 5.49
403
410
  write_formats workbook, buffer1
404
- # ●● XF ➜ 6.115
411
+ # ●● XF ➜ 5.115
405
412
  write_xfs workbook, buffer1
406
- # ●● STYLE ➜ 6.99
413
+ # ●● STYLE ➜ 5.103
407
414
  write_styles workbook, buffer1
408
- # ○ PALETTE ➜ 6.70
409
- # ○ USESELFS ➜ 6.105
415
+ # ○ PALETTE ➜ 5.74
416
+ # ○ USESELFS ➜ 5.106
410
417
  buffer1.rewind
411
- # ●● BOUNDSHEET ➜ 6.12
418
+ # ●● BOUNDSHEET ➜ 5.95
412
419
  buffer2 = StringIO.new ''
413
- # ○ COUNTRY ➜ 6.23
414
- # ○ Link Table ➜ 5.10.3
420
+ # ○ COUNTRY ➜ 5.22
421
+ # ○ Link Table ➜ 4.10.3
415
422
  # ○○ NAME ➜ 6.66
416
- # ○ Shared String Table ➜ 5.11
417
- # ● SST ➜ 6.96
418
- # ● EXTSST ➜ 6.40
423
+ # ○ Shared String Table ➜ 4.11
424
+ # ● SST ➜ 5.100
425
+ # ● EXTSST ➜ 5.42
419
426
  write_sst workbook, buffer2, buffer1.size
420
- # ● EOF ➜ 6.36
427
+ # ● EOF ➜ 5.37
421
428
  write_eof workbook, buffer2
422
429
  buffer2.rewind
423
430
  # worksheet data can only be assembled after write_sst
@@ -482,7 +489,9 @@ class Workbook < Spreadsheet::Writer
482
489
  strings.each_with_index do |string, idx|
483
490
  sst.store string, idx
484
491
  op_offset = data.size + 4
485
- offsets.push [offset + writer.pos + op_offset, op_offset] if idx % 8 == 0
492
+ if idx % SST_CHUNKSIZE == 0
493
+ offsets.push [offset + writer.pos + op_offset, op_offset]
494
+ end
486
495
  header, packed, next_wide = _unicode_string string, 2
487
496
  # the first few bytes (header + first character) must not be split
488
497
  must_fit = header.size + wide + 1
@@ -546,9 +555,9 @@ class Workbook < Spreadsheet::Writer
546
555
  # 0x07 = Currency [0] (BIFF4-BIFF8)
547
556
  # 0x08 = Hyperlink (BIFF8)
548
557
  # 0x09 = Followed Hyperlink (BIFF8)
549
- 0xFF, # Level for RowLevel or ColLevel style (zero-based, lv),
550
- # 0xFF otherwise
551
- 0x00, # The RowLevel and ColLevel styles specify the formatting of
558
+ 0xff, # Level for RowLevel or ColLevel style (zero-based, lv),
559
+ # 0xff otherwise
560
+ # The RowLevel and ColLevel styles specify the formatting of
552
561
  # subtotal cells in a specific outline level. The level is
553
562
  # specified by the last field in the STYLE record. Valid values
554
563
  # are 0…6 for the outline levels 1…7.
@@ -158,7 +158,7 @@ class Worksheet
158
158
  write_op 0x000c, [count].pack('v')
159
159
  end
160
160
  def write_cell type, row, idx, *args
161
- xf_idx = @workbook.xf_index @worksheet.workbook, row.format(idx)
161
+ xf_idx = @workbook.xf_index @worksheet.workbook, row.formats[idx]
162
162
  data = [
163
163
  row.idx, # Index to row
164
164
  idx, # Index to column
@@ -178,6 +178,7 @@ class Worksheet
178
178
  # RK ➜ 6.82 (BIFF3-BIFF8)
179
179
  # RSTRING ➜ 6.84 (BIFF5/BIFF7)
180
180
  multiples, first_idx = nil
181
+ row = row.formatted
181
182
  row.each_with_index do |cell, idx|
182
183
  cell = nil if cell == ''
183
184
  ## it appears that there are limitations to RK precision, both for
@@ -357,7 +358,7 @@ class Worksheet
357
358
  # Write a cell with a Formula. May write an additional String record depending
358
359
  # on the stored result of the Formula.
359
360
  def write_formula row, idx
360
- xf_idx = @workbook.xf_index @worksheet.workbook, row.format(idx)
361
+ xf_idx = @workbook.xf_index @worksheet.workbook, row.formats[idx]
361
362
  cell = row[idx]
362
363
  data1 = [
363
364
  row.idx, # Index to row
@@ -415,47 +416,52 @@ class Worksheet
415
416
  ##
416
417
  # Write a new Worksheet.
417
418
  def write_from_scratch
418
- # ● BOF Type = worksheet (➜ 6.8)
419
+ # ● BOF Type = worksheet (➜ 5.8)
419
420
  write_bof
420
- # ○ UNCALCED ➜ 6.104
421
- # ○ INDEX ➜ 5.7 (Row Blocks), ➜ 6.55
422
- # ○ Calculation Settings Block ➜ 5.3
421
+ # ○ UNCALCED ➜ 5.105
422
+ # ○ INDEX ➜ 4.7 (Row Blocks), ➜ 5.59
423
+ # ○ Calculation Settings Block ➜ 4.3
423
424
  write_calccount
424
425
  write_refmode
425
426
  write_iteration
426
427
  write_saverecalc
427
- # ○ PRINTHEADERS ➜ 6.76
428
- # ○ PRINTGRIDLINES ➜ 6.75
429
- # ○ GRIDSET ➜ 6.48
430
- # ○ GUTS ➜ 6.49
431
- # ○ DEFAULTROWHEIGHT ➜ 6.28
428
+ # ○ PRINTHEADERS ➜ 5.81
429
+ # ○ PRINTGRIDLINES ➜ 5.80
430
+ # ○ GRIDSET ➜ 5.52
431
+ # ○ GUTS ➜ 5.53
432
+ # ○ DEFAULTROWHEIGHT ➜ 5.31
432
433
  write_defaultrowheight
433
- # ○ WSBOOL ➜ 6.113
434
+ # ○ WSBOOL ➜ 5.113
434
435
  write_wsbool
435
- # ○ Page Settings Block ➜ 5.4
436
- # ○ Worksheet Protection Block ➜ 5.18
437
- # ○ DEFCOLWIDTH ➜ 6.29
436
+ # ○ Page Settings Block ➜ 4.4
437
+ # ○ Worksheet Protection Block ➜ 4.18
438
+ # ○ DEFCOLWIDTH ➜ 5.32
438
439
  write_defcolwidth
439
- # ○○ COLINFO ➜ 6.18
440
+ # ○○ COLINFO ➜ 5.18
440
441
  write_colinfos
441
- # ○ SORT ➜ 6.95
442
- # ● DIMENSIONS ➜ 6.31
442
+ # ○ SORT ➜ 5.99
443
+ # ● DIMENSIONS ➜ 5.35
443
444
  write_dimensions
444
- # ○○ Row Blocks ➜ 5.7
445
+ # ○○ Row Blocks ➜ 4.7
445
446
  write_rows
446
- # ● Worksheet View Settings Block ➜ 5.5
447
- # STANDARDWIDTH6.97
448
- # ○○ MERGEDCELLS ➜ 6.63
449
- # ○ LABELRANGES6.60
450
- # ○ PHONETIC6.73
451
- # ○ Conditional Formatting Table ➜ 5.12
452
- # ○ Hyperlink Table ➜ 5.13
447
+ # ● Worksheet View Settings Block ➜ 4.5
448
+ # WINDOW25.110
449
+ write_window2
450
+ # ○ SCL5.92 (BIFF4-BIFF8 only)
451
+ # ○ PANE5.75
452
+ # ○○ SELECTION ➜ 5.93
453
+ # ○ STANDARDWIDTH ➜ 5.101
454
+ # ○○ MERGEDCELLS ➜ 5.67
455
+ # ○ LABELRANGES ➜ 5.64
456
+ # ○ PHONETIC ➜ 5.77
457
+ # ○ Conditional Formatting Table ➜ 4.12
458
+ # ○ Hyperlink Table ➜ 4.13
453
459
  write_hyperlink_table
454
- # ○ Data Validity Table ➜ 5.14
455
- # ○ SHEETLAYOUT ➜ 6.91 (BIFF8X only)
456
- # ○ SHEETPROTECTION Additional protection, ➜ 6.92 (BIFF8X only)
457
- # ○ RANGEPROTECTION Additional protection, ➜ 6.79 (BIFF8X only)
458
- # ● EOF ➜ 6.36
460
+ # ○ Data Validity Table ➜ 4.14
461
+ # ○ SHEETLAYOUT ➜ 5.96 (BIFF8X only)
462
+ # ○ SHEETPROTECTION Additional protection, ➜ 5.98 (BIFF8X only)
463
+ # ○ RANGEPROTECTION Additional protection, ➜ 5.84 (BIFF8X only)
464
+ # ● EOF ➜ 5.36
459
465
  write_eof
460
466
  end
461
467
  def write_hlink row, col, link
@@ -556,11 +562,11 @@ class Worksheet
556
562
  ]
557
563
  # List of nc=lc-fc+1 16-bit indexes to XF records (➜ 6.115)
558
564
  multiples.each_with_index do |blank, cell_idx|
559
- xf_idx = @workbook.xf_index @worksheet.workbook, row.format(idx + cell_idx)
565
+ xf_idx = @workbook.xf_index @worksheet.workbook, row.formats[idx + cell_idx]
560
566
  data.push xf_idx
561
567
  end
562
568
  # Index to last column (lc)
563
- data.push idx + multiples.size
569
+ data.push idx + multiples.size - 1
564
570
  write_op opcode(:mulblank), data.pack('v*')
565
571
  end
566
572
  ##
@@ -673,23 +679,28 @@ class Worksheet
673
679
  # formatted with a medium or thick
674
680
  # line style. Thin line styles are
675
681
  # not taken into account.
676
- height = row.height || 12 # FIXME: where is the default font height?
682
+ height = row.height || ROW_HEIGHT
677
683
  opts = row.outline_level & 0x00000007
678
684
  opts |= 0x00000010 if row.collapsed?
679
685
  opts |= 0x00000020 if row.hidden?
680
- opts |= 0x00000040 if height != 12 # FIXME: where is the default font height?
686
+ opts |= 0x00000040 if height != ROW_HEIGHT
681
687
  if fmt = row.default_format
682
688
  xf_idx = @workbook.xf_index @worksheet.workbook, fmt
683
689
  opts |= 0x00000080
684
690
  opts |= xf_idx << 16
685
691
  end
686
692
  opts |= 0x00000100
693
+ height = if height == ROW_HEIGHT
694
+ height * TWIPS
695
+ else
696
+ ( height * TWIPS ) | 0x8000
697
+ end
687
698
  # TODO: Row spacing
688
699
  data = [
689
700
  row.idx,
690
701
  row.first_used,
691
702
  row.first_unused,
692
- height * TWIPS,
703
+ height,
693
704
  opts,
694
705
  ].pack binfmt(:row)
695
706
  write_op opcode(:row), data
@@ -714,6 +725,54 @@ class Worksheet
714
725
  # 0 = Do not recalculate; 1 = Recalculate before saving the document
715
726
  write_op 0x005f, [1].pack('v')
716
727
  end
728
+ def write_window2
729
+ # This record contains additional settings for the document window
730
+ # (BIFF2-BIFF4) or for the window of a specific worksheet (BIFF5-BIFF8).
731
+ # It is part of the Sheet View Settings Block (➜ 4.5).
732
+ # Offset Size Contents
733
+ # 0 2 Option flags:
734
+ # Bits Mask Contents
735
+ # 0 0x0001 0 = Show formula results
736
+ # 1 = Show formulas
737
+ # 1 0x0002 0 = Do not show grid lines
738
+ # 1 = Show grid lines
739
+ # 2 0x0004 0 = Do not show sheet headers
740
+ # 1 = Show sheet headers
741
+ # 3 0x0008 0 = Panes are not frozen
742
+ # 1 = Panes are frozen (freeze)
743
+ # 4 0x0010 0 = Show zero values as empty cells
744
+ # 1 = Show zero values
745
+ # 5 0x0020 0 = Manual grid line colour
746
+ # 1 = Automatic grid line colour
747
+ # 6 0x0040 0 = Columns from left to right
748
+ # 1 = Columns from right to left
749
+ # 7 0x0080 0 = Do not show outline symbols
750
+ # 1 = Show outline symbols
751
+ # 8 0x0100 0 = Keep splits if pane freeze is removed
752
+ # 1 = Remove splits if pane freeze is removed
753
+ # 9 0x0200 0 = Sheet not selected
754
+ # 1 = Sheet selected (BIFF5-BIFF8)
755
+ # 10 0x0400 0 = Sheet not active
756
+ # 1 = Sheet active (BIFF5-BIFF8)
757
+ # 11 0x0800 0 = Show in normal view
758
+ # 1 = Show in page break preview (BIFF8)
759
+ # 2 2 Index to first visible row
760
+ # 4 2 Index to first visible column
761
+ # 6 2 Colour index of grid line colour (➜ 5.74).
762
+ # Note that in BIFF2-BIFF5 an RGB colour is written instead.
763
+ # 8 2 Not used
764
+ # 10 2 Cached magnification factor in page break preview (in percent)
765
+ # 0 = Default (60%)
766
+ # 12 2 Cached magnification factor in normal view (in percent)
767
+ # 0 = Default (100%)
768
+ # 14 4 Not used
769
+ flags = 310
770
+ if @worksheet.active
771
+ flags |= 0x0200
772
+ end
773
+ data = [ flags, 0, 0, 0, 0, 0 ].pack binfmt(:window2)
774
+ write_op opcode(:window2), data
775
+ end
717
776
  def write_wsbool
718
777
  bits = [
719
778
  # Bit Mask Contents
@@ -0,0 +1,11 @@
1
+ class Array
2
+ def rcompact
3
+ dup.rcompact!
4
+ end
5
+ def rcompact!
6
+ while !empty? && last.nil?
7
+ pop
8
+ end
9
+ self
10
+ end
11
+ end
@@ -1,3 +1,5 @@
1
+ require 'spreadsheet/helpers'
2
+
1
3
  module Spreadsheet
2
4
  ##
3
5
  # The Row class. Encapsulates Cell data and formatting.
@@ -23,10 +25,7 @@ module Spreadsheet
23
25
  alias_method :"unupdated_#{key}=", :"#{key}="
24
26
  define_method "#{key}=" do |value|
25
27
  send "unupdated_#{key}=", value
26
- if @worksheet
27
- @formatted = true
28
- @worksheet.row_updated @idx, self, :formatted => true
29
- end
28
+ @worksheet.row_updated @idx, self if @worksheet
30
29
  value
31
30
  end
32
31
  end
@@ -36,9 +35,7 @@ module Spreadsheet
36
35
  keys.each do |key|
37
36
  define_method key do |*args|
38
37
  res = super
39
- if @worksheet
40
- @worksheet.row_updated @idx, self, :formatted => @formatted
41
- end
38
+ @worksheet.row_updated @idx, self if @worksheet
42
39
  res
43
40
  end
44
41
  end
@@ -55,31 +52,22 @@ module Spreadsheet
55
52
  def initialize worksheet, idx, cells=[]
56
53
  @worksheet = worksheet
57
54
  @idx = idx
58
- while !cells.empty? && !cells.last
59
- cells.pop
60
- end
61
55
  super cells
62
- @first_used ||= index_of_first self
63
- @first_unused ||= size
64
56
  @formats = []
65
- @height = 12
57
+ @height = 12.1
66
58
  end
67
59
  ##
68
60
  # Set the default Format used when writing a Cell if no explicit Format is
69
61
  # stored for the cell.
70
62
  def default_format= format
71
63
  @worksheet.add_format format if @worksheet
72
- @worksheet.row_updated @idx, self, :formatted => true if @worksheet
64
+ @worksheet.row_updated @idx, self if @worksheet
73
65
  @default_format = format
74
66
  end
75
67
  ##
76
- # #first_unused (really last used + 1) - the 0-based index of the first of
77
- # all remaining contiguous blank Cells.
78
- alias :first_unused :size
79
- ##
80
68
  # #first_used the 0-based index of the first non-blank Cell.
81
69
  def first_used
82
- index_of_first self
70
+ [ index_of_first(self), index_of_first(@formats) ].compact.min
83
71
  end
84
72
  ##
85
73
  # The Format for the Cell at _idx_ (0-based), or the first valid Format in
@@ -89,22 +77,48 @@ module Spreadsheet
89
77
  || @worksheet.column(idx).default_format if @worksheet
90
78
  end
91
79
  ##
92
- # Set the Format for the Cell at _idx_ (0-based).
93
- def set_format idx, fmt
94
- @formats[idx] = fmt
95
- @worksheet.add_format fmt
96
- @worksheet.row_updated @idx, self, :formatted => true if @worksheet
97
- fmt
80
+ # Returns a copy of self with nil-values appended for empty cells that have
81
+ # an associated Format.
82
+ # This is primarily a helper-function for the writer classes.
83
+ def formatted
84
+ copy = dup
85
+ @formats.rcompact!
86
+ if copy.length < @formats.size
87
+ copy.concat Array.new(@formats.size - copy.length)
88
+ end
89
+ copy
90
+ end
91
+ ##
92
+ # Same as Row#size, but takes into account formatted empty cells
93
+ def formatted_size
94
+ @formats.rcompact!
95
+ sz = size
96
+ fs = @formats.size
97
+ fs > sz ? fs : sz
98
98
  end
99
+ ##
100
+ # #first_unused (really last used + 1) - the 0-based index of the first of
101
+ # all remaining contiguous blank Cells.
102
+ alias :first_unused :formatted_size
99
103
  def inspect
100
104
  variables = instance_variables.collect do |name|
101
105
  "%s=%s" % [name, instance_variable_get(name)]
102
106
  end.join(' ')
103
107
  sprintf "#<%s:0x%014x %s %s>", self.class, object_id, variables, super
104
108
  end
109
+ ##
110
+ # Set the Format for the Cell at _idx_ (0-based).
111
+ def set_format idx, fmt
112
+ @formats[idx] = fmt
113
+ @worksheet.add_format fmt
114
+ @worksheet.row_updated @idx, self if @worksheet
115
+ fmt
116
+ end
105
117
  private
106
118
  def index_of_first ary # :nodoc:
107
- ary.index(ary.find do |elm| elm end)
119
+ if first = ary.find do |elm| !elm.nil? end
120
+ ary.index first
121
+ end
108
122
  end
109
123
  end
110
124
  end
@@ -13,7 +13,7 @@ module Spreadsheet
13
13
  class Workbook
14
14
  include Spreadsheet::Encodings
15
15
  attr_reader :io, :worksheets, :formats, :fonts
16
- attr_accessor :encoding, :version, :default_format
16
+ attr_accessor :active_worksheet, :encoding, :default_format, :version
17
17
  def initialize io = nil, opts={:default_format => Format.new}
18
18
  @worksheets = []
19
19
  @io = io
@@ -19,12 +19,19 @@ module Spreadsheet
19
19
  # instances may appear at more than one position in #columns.
20
20
  # If you modify a Column directly, your changes will be
21
21
  # reflected in all those positions.
22
+ # #active :: When a user chooses to print a Workbook, Excel will include
23
+ # all active Worksheets. Defaults to true. If you want to
24
+ # exclude a Worksheet from the default print-list, set this
25
+ # to false. Warning: Excel will display a cryptic Error
26
+ # Message if a user tries to print a Workbook that has no
27
+ # active Worksheets.
22
28
  class Worksheet
23
29
  include Spreadsheet::Encodings
24
30
  include Enumerable
25
- attr_accessor :name, :workbook
31
+ attr_accessor :active, :name, :workbook
26
32
  attr_reader :rows, :columns
27
33
  def initialize opts={}
34
+ @active = true
28
35
  @dimensions = [0,0,0,0]
29
36
  @name = opts[:name] || 'Worksheet'
30
37
  @workbook = opts[:workbook]
@@ -173,11 +180,8 @@ module Spreadsheet
173
180
  ##
174
181
  # Tell Worksheet that the Row at _idx_ has been updated and the #dimensions
175
182
  # need to be recalculated. You should not need to call this directly.
176
- def row_updated idx, row, opts={}
183
+ def row_updated idx, row
177
184
  @dimensions = nil
178
- unless opts[:formatted]
179
- row = shorten(row)
180
- end
181
185
  @rows[idx] = row
182
186
  format_dates row
183
187
  row
@@ -188,7 +192,7 @@ module Spreadsheet
188
192
  res = if row = @rows[idx]
189
193
  row[0, cells.size] = cells
190
194
  row
191
- elsif cells = shorten(cells)
195
+ else
192
196
  Row.new self, idx, cells
193
197
  end
194
198
  row_updated idx, res
@@ -251,8 +255,9 @@ module Spreadsheet
251
255
  @dimensions[0] = index_of_first(@rows) || 0
252
256
  @dimensions[1] = @rows.size
253
257
  compact = @rows.compact
254
- @dimensions[2] = compact.collect do |row| index_of_first row end.compact.min || 0
255
- @dimensions[3] = compact.collect do |row| row.size end.max || 0
258
+ @dimensions[2] = compact.collect do |row| row.first_used end.compact.min || 0
259
+ @dimensions[3] = compact.collect do |row| row.first_unused end.max || 0
260
+ puts caller if @dimensions.nil?
256
261
  @dimensions
257
262
  end
258
263
  def shorten ary # :nodoc:
@@ -19,8 +19,8 @@ class TestRow < Test::Unit::TestCase
19
19
  assert_equal Date.new(1975,8,21), row.date(1)
20
20
  end
21
21
  def test_datetime
22
- row = Row.new @worksheet, 0, [nil, 27627.6789]
23
- d1 = DateTime.new(1975,8,21) + 0.6789
22
+ row = Row.new @worksheet, 0, [nil, 27627.765]
23
+ d1 = DateTime.new(1975,8,21) + 0.765
24
24
  d2 = row.datetime 1
25
25
  assert_equal d1, d2
26
26
  end
@@ -0,0 +1,22 @@
1
+ #!/usr/bin/env ruby
2
+ # Excel::Writer::TestWorksheet -- Spreadheet -- 21.11.2007 -- hwyss@ywesee.com
3
+
4
+ require 'test/unit'
5
+ require 'spreadsheet/excel/writer/worksheet'
6
+
7
+ module Spreadsheet
8
+ module Excel
9
+ module Writer
10
+ class TestWorksheet < Test::Unit::TestCase
11
+ def test_need_number
12
+ sheet = Worksheet.new nil, nil
13
+ assert_equal false, sheet.need_number?(10)
14
+ assert_equal false, sheet.need_number?(114.55)
15
+ assert_equal false, sheet.need_number?(0.1)
16
+ assert_equal false, sheet.need_number?(0.01)
17
+ assert_equal true, sheet.need_number?(0.001)
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -63,7 +63,7 @@ module Spreadsheet
63
63
  enc = Encoding.find enc
64
64
  end
65
65
  assert_equal enc, book.encoding
66
- assert_equal 24, book.formats.size
66
+ assert_equal 25, book.formats.size
67
67
  assert_equal 5, book.fonts.size
68
68
  str1 = book.shared_string 0
69
69
  assert_equal @@iconv.iconv('Shared String'), str1
@@ -92,10 +92,10 @@ module Spreadsheet
92
92
  end
93
93
  assert_equal long, str4
94
94
  sheet = book.worksheet 0
95
- assert_equal 10, sheet.row_count
96
- assert_equal 11, sheet.column_count
97
- useds = [0,0,0,0,0,0,0,1,0,0]
98
- unuseds = [2,2,1,1,1,2,1,11,1,2]
95
+ assert_equal 11, sheet.row_count
96
+ assert_equal 12, sheet.column_count
97
+ useds = [0,0,0,0,0,0,0,1,0,0,11]
98
+ unuseds = [2,2,1,1,1,2,1,11,1,2,12]
99
99
  sheet.each do |row|
100
100
  assert_equal useds.shift, row.first_used
101
101
  assert_equal unuseds.shift, row.first_unused
@@ -154,6 +154,7 @@ module Spreadsheet
154
154
  assert_equal 0.0001, row[0]
155
155
  row = sheet.row 9
156
156
  assert_equal 0.00009, row[0]
157
+ assert_equal :green, sheet.row(10).format(11).pattern_fg_color
157
158
  end
158
159
  def test_version_excel97__ooffice
159
160
  path = File.join @data, 'test_version_excel97.xls'
@@ -166,7 +167,7 @@ module Spreadsheet
166
167
  enc = Encoding.find enc
167
168
  end
168
169
  assert_equal enc, book.encoding
169
- assert_equal 24, book.formats.size
170
+ assert_equal 25, book.formats.size
170
171
  assert_equal 5, book.fonts.size
171
172
  str1 = book.shared_string 0
172
173
  assert_equal 'Shared String', str1
@@ -195,10 +196,10 @@ module Spreadsheet
195
196
  end
196
197
  assert_equal long, str4
197
198
  sheet = book.worksheet 0
198
- assert_equal 10, sheet.row_count
199
- assert_equal 11, sheet.column_count
200
- useds = [0,0,0,0,0,0,0,1,0,0]
201
- unuseds = [2,2,1,1,1,2,1,11,1,2]
199
+ assert_equal 11, sheet.row_count
200
+ assert_equal 12, sheet.column_count
201
+ useds = [0,0,0,0,0,0,0,1,0,0,11]
202
+ unuseds = [2,2,1,1,1,2,1,11,1,2,12]
202
203
  sheet.each do |row|
203
204
  assert_equal useds.shift, row.first_used
204
205
  assert_equal unuseds.shift, row.first_unused
@@ -577,10 +578,10 @@ module Spreadsheet
577
578
  book.write path
578
579
  assert_nothing_raised do book = Spreadsheet.open path end
579
580
  sheet = book.worksheet 0
580
- assert_equal 10, sheet.row_count
581
- assert_equal 11, sheet.column_count
582
- useds = [0,0,0,0,0,0,0,1,0,0]
583
- unuseds = [2,2,1,1,1,2,1,11,1,2]
581
+ assert_equal 11, sheet.row_count
582
+ assert_equal 12, sheet.column_count
583
+ useds = [0,0,0,0,0,0,0,1,0,0,11]
584
+ unuseds = [2,2,1,1,1,2,1,11,1,2,12]
584
585
  sheet.each do |row|
585
586
  assert_equal useds.shift, row.first_used
586
587
  assert_equal unuseds.shift, row.first_unused
@@ -686,10 +687,10 @@ module Spreadsheet
686
687
  assert_equal str3, book.shared_string(2)
687
688
  assert_equal str4, book.shared_string(3)
688
689
  sheet = book.worksheet 0
689
- assert_equal 10, sheet.row_count
690
- assert_equal 11, sheet.column_count
691
- useds = [0,0,0,0,0,0,0,1,0,0]
692
- unuseds = [2,2,1,1,1,2,1,11,1,2]
690
+ assert_equal 11, sheet.row_count
691
+ assert_equal 12, sheet.column_count
692
+ useds = [0,0,0,0,0,0,0,1,0,0,11]
693
+ unuseds = [2,2,1,1,1,2,1,11,1,2,12]
693
694
  sheet.each do |row|
694
695
  assert_equal useds.shift, row.first_used
695
696
  assert_equal unuseds.shift, row.first_unused
@@ -0,0 +1,33 @@
1
+ #!/usr/bin/env ruby
2
+ # TestRow -- Spreadsheet -- 08.01.2009 -- hwyss@ywesee.com
3
+
4
+ $: << File.expand_path('../../lib', File.dirname(__FILE__))
5
+
6
+ require 'test/unit'
7
+ require 'spreadsheet'
8
+
9
+ module Spreadsheet
10
+ class TestRow < Test::Unit::TestCase
11
+ def setup
12
+ @workbook = Excel::Workbook.new
13
+ @worksheet = Excel::Worksheet.new
14
+ @workbook.add_worksheet @worksheet
15
+ end
16
+ def test_formatted
17
+ row = Row.new @worksheet, 0, [nil, 1]
18
+ assert_equal 2, row.formatted.size
19
+ row.set_format 3, Format.new
20
+ assert_equal 4, row.formatted.size
21
+ end
22
+ def test_concat
23
+ row = Row.new @worksheet, 0, [nil, 1, nil]
24
+ assert_equal [nil, 1, nil], row
25
+ row.concat [2, nil]
26
+ assert_equal [nil, 1, nil, 2, nil], row
27
+ row.concat [3]
28
+ assert_equal [nil, 1, nil, 2, nil, 3], row
29
+ row.concat [nil, 4]
30
+ assert_equal [nil, 1, nil, 2, nil, 3, nil, 4], row
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+ # suite.rb -- oddb -- 08.01.2009 -- hwyss@ywesee.com
3
+
4
+ require 'find'
5
+
6
+ here = File.dirname(__FILE__)
7
+
8
+ $: << here
9
+
10
+ Find.find(here) do |file|
11
+ if /(?<!suite)\.rb$/o.match(file)
12
+ require file
13
+ end
14
+ end
@@ -28,10 +28,10 @@ module Spreadsheet
28
28
  assert_equal 2, @sheet.row_count
29
29
  @sheet[1,0] = nil
30
30
  assert_equal 2, @sheet.column_count
31
- assert_equal 1, @sheet.row_count
31
+ assert_equal 2, @sheet.row_count
32
32
  @sheet[0,1] = nil
33
- assert_equal 1, @sheet.column_count
34
- assert_equal 1, @sheet.row_count
33
+ assert_equal 2, @sheet.column_count
34
+ assert_equal 2, @sheet.row_count
35
35
  end
36
36
  def test_column_count
37
37
  assert_equal 0, @sheet.column_count
@@ -44,7 +44,7 @@ module Spreadsheet
44
44
  @sheet.replace_row 5, nil, 'something', 4, 7, nil
45
45
  assert_equal 4, @sheet.column_count
46
46
  @sheet.replace_row 3
47
- assert_equal 3, @sheet.column_count
47
+ assert_equal 4, @sheet.column_count
48
48
  end
49
49
  def test_row_count
50
50
  assert_equal 0, @sheet.row_count
@@ -57,7 +57,15 @@ module Spreadsheet
57
57
  @sheet.replace_row 5, nil, 'something', 4, 7, nil
58
58
  assert_equal 6, @sheet.row_count
59
59
  @sheet.replace_row 3
60
+ assert_equal 6, @sheet.row_count
61
+ @sheet.delete_row 3
62
+ assert_equal 5, @sheet.row_count
63
+ @sheet.delete_row 3
64
+ assert_equal 4, @sheet.row_count
65
+ @sheet.delete_row 2
60
66
  assert_equal 4, @sheet.row_count
67
+ @sheet.delete_row 2
68
+ assert_equal 3, @sheet.row_count
61
69
  end
62
70
  def test_modify_column
63
71
  assert_equal 10, @sheet.column(0).width
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.2.1
4
+ version: 0.6.3
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-12-19 00:00:00 +01:00
12
+ date: 2009-01-14 00:00:00 +01:00
13
13
  default_executable:
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
@@ -35,8 +35,8 @@ dependencies:
35
35
  description: The Spreadsheet Library is designed to read and write Spreadsheet Documents. As of version 0.6.0, only Microsoft Excel compatible spreadsheets are supported. Spreadsheet is a combination/complete rewrite of the Spreadsheet::Excel Library by Daniel J. Berger and the ParseExcel Library by Hannes Wyss. Spreadsheet can read, write and modify Spreadsheet Documents.
36
36
  email:
37
37
  - hannes.wyss@gmail.com
38
- executables: []
39
-
38
+ executables:
39
+ - xlsopcodes
40
40
  extensions: []
41
41
 
42
42
  extra_rdoc_files:
@@ -52,6 +52,7 @@ files:
52
52
  - Manifest.txt
53
53
  - README.txt
54
54
  - Rakefile
55
+ - bin/xlsopcodes
55
56
  - lib/parseexcel.rb
56
57
  - lib/parseexcel/parseexcel.rb
57
58
  - lib/parseexcel/parser.rb
@@ -80,19 +81,24 @@ files:
80
81
  - lib/spreadsheet/font.rb
81
82
  - lib/spreadsheet/format.rb
82
83
  - lib/spreadsheet/formula.rb
84
+ - lib/spreadsheet/helpers.rb
83
85
  - lib/spreadsheet/link.rb
84
86
  - lib/spreadsheet/row.rb
85
87
  - lib/spreadsheet/workbook.rb
86
88
  - lib/spreadsheet/worksheet.rb
87
89
  - lib/spreadsheet/writer.rb
88
90
  - test/data/test_copy.xls
91
+ - test/data/test_datetime.xls
89
92
  - test/data/test_empty.xls
90
93
  - test/data/test_version_excel5.xls
91
94
  - test/data/test_version_excel95.xls
92
95
  - test/data/test_version_excel97.xls
93
96
  - test/excel/row.rb
97
+ - test/excel/writer/worksheet.rb
94
98
  - test/font.rb
95
99
  - test/integration.rb
100
+ - test/row.rb
101
+ - test/suite.rb
96
102
  - test/workbook.rb
97
103
  - test/worksheet.rb
98
104
  has_rdoc: true