surpass 0.0.3

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.
Files changed (78) hide show
  1. data/History.txt +0 -0
  2. data/README.txt +133 -0
  3. data/Rakefile +35 -0
  4. data/examples/big-16mb.rb +25 -0
  5. data/examples/big-random-strings.rb +28 -0
  6. data/examples/blanks.rb +34 -0
  7. data/examples/col_width.rb +16 -0
  8. data/examples/dates.rb +31 -0
  9. data/examples/format.rb +23 -0
  10. data/examples/hello-world.rb +9 -0
  11. data/examples/image.rb +10 -0
  12. data/examples/merged.rb +36 -0
  13. data/examples/merged0.rb +27 -0
  14. data/examples/merged1.rb +99 -0
  15. data/examples/num_formats.rb +55 -0
  16. data/examples/numbers.rb +24 -0
  17. data/examples/outline.rb +110 -0
  18. data/examples/panes.rb +48 -0
  19. data/examples/protection.rb +132 -0
  20. data/examples/python.bmp +0 -0
  21. data/examples/row_styles.rb +16 -0
  22. data/examples/row_styles_empty.rb +15 -0
  23. data/examples/set_cell_and_range_style.rb +12 -0
  24. data/examples/wrapped-text.rb +13 -0
  25. data/examples/write_arrays.rb +16 -0
  26. data/examples/ws_props.rb +80 -0
  27. data/lib/biff_record.rb +2168 -0
  28. data/lib/bitmap.rb +218 -0
  29. data/lib/cell.rb +214 -0
  30. data/lib/chart.rb +16 -0
  31. data/lib/column.rb +40 -0
  32. data/lib/document.rb +406 -0
  33. data/lib/excel_formula.rb +6 -0
  34. data/lib/excel_magic.rb +1013 -0
  35. data/lib/formatting.rb +554 -0
  36. data/lib/row.rb +137 -0
  37. data/lib/style.rb +179 -0
  38. data/lib/surpass.rb +51 -0
  39. data/lib/utilities.rb +86 -0
  40. data/lib/workbook.rb +206 -0
  41. data/lib/worksheet.rb +561 -0
  42. data/spec/biff_record_spec.rb +268 -0
  43. data/spec/cell_spec.rb +56 -0
  44. data/spec/data/random-strings.txt +10000 -0
  45. data/spec/document_spec.rb +168 -0
  46. data/spec/excel_formula_spec.rb +0 -0
  47. data/spec/formatting_spec.rb +53 -0
  48. data/spec/reference/P-0508-0000507647-3280-5298.xls +0 -0
  49. data/spec/reference/all-cell-styles.bin +0 -0
  50. data/spec/reference/all-number-formats.bin +0 -0
  51. data/spec/reference/all-styles.bin +0 -0
  52. data/spec/reference/mini.xls +0 -0
  53. data/spec/row_spec.rb +19 -0
  54. data/spec/spec_helper.rb +10 -0
  55. data/spec/style_spec.rb +89 -0
  56. data/spec/utilities_spec.rb +57 -0
  57. data/spec/workbook_spec.rb +48 -0
  58. data/spec/worksheet_spec.rb +0 -0
  59. data/stats/cloc.txt +8 -0
  60. data/stats/rcov.txt +0 -0
  61. data/stats/specdoc.txt +158 -0
  62. data/surpass-manual-0-0-3.pdf +0 -0
  63. data/surpass.gemspec +34 -0
  64. data/tasks/ann.rake +80 -0
  65. data/tasks/bones.rake +20 -0
  66. data/tasks/excel.rake +6 -0
  67. data/tasks/gem.rake +201 -0
  68. data/tasks/git.rake +40 -0
  69. data/tasks/metrics.rake +42 -0
  70. data/tasks/notes.rake +27 -0
  71. data/tasks/post_load.rake +34 -0
  72. data/tasks/rdoc.rake +51 -0
  73. data/tasks/rubyforge.rake +55 -0
  74. data/tasks/setup.rb +292 -0
  75. data/tasks/spec.rake +54 -0
  76. data/tasks/svn.rake +47 -0
  77. data/tasks/test.rake +40 -0
  78. metadata +144 -0
data/lib/document.rb ADDED
@@ -0,0 +1,406 @@
1
+ class Reader
2
+ DIR_ENTRY_SIZE = 128
3
+
4
+ attr_accessor :header
5
+ attr_accessor :data
6
+
7
+ attr_accessor :doc_magic
8
+ attr_accessor :file_uid
9
+ attr_accessor :rev_num
10
+ attr_accessor :ver_num
11
+ attr_accessor :byte_order
12
+ attr_accessor :sect_size
13
+ attr_accessor :short_sect_size
14
+
15
+ attr_accessor :total_sat_sectors
16
+ attr_accessor :dir_start_sid
17
+ attr_accessor :min_stream_size
18
+ attr_accessor :ssat_start_sid
19
+ attr_accessor :total_ssat_sectors
20
+ attr_accessor :msat_start_sid
21
+ attr_accessor :total_msat_sectors
22
+
23
+ attr_accessor :msat
24
+ attr_accessor :sat
25
+ attr_accessor :ssat
26
+ attr_accessor :dir_entry_list
27
+
28
+ def initialize(file)
29
+ @streams = {}
30
+
31
+ file = File.open(file, 'rb') unless file.respond_to?(:read)
32
+ doc = file.read
33
+ @header, @data = doc[0...512], doc[512..-1]
34
+
35
+ build_header
36
+ build_msat
37
+ build_sat
38
+ build_directory
39
+ build_short_sectors_data
40
+
41
+ if @short_sectors_data.length > 0
42
+ build_ssat
43
+ else
44
+ @ssat_start_sid = -2
45
+ @total_ssat_sectors = 0
46
+ @ssat = [-2]
47
+ end
48
+
49
+ @dir_entry_list[1..-1].each do |d|
50
+ did, sz, name, t, c, did_left, did_right, did_root, dentry_start_sid, stream_size = d
51
+ stream_data = ''
52
+ if stream_size > 0
53
+ if stream_size > @min_stream_size
54
+ args = [@data, @sat, dentry_start_sid, @sect_size]
55
+ else
56
+ args = [@short_sectors_data, @ssat, dentry_start_sid, @short_sect_size]
57
+ end
58
+ stream_data = stream_data(*args)
59
+ end
60
+ # BAD IDEA: names may be equal. NEED use full paths...
61
+ @streams[name] = stream_data if !name.length == 0
62
+ end
63
+ end
64
+
65
+ def build_header
66
+ @doc_magic = @header[0...8]
67
+ raise 'Not an OLE file.' unless @doc_magic === "\320\317\021\340\241\261\032\341"
68
+
69
+ @file_uid = @header[8...24]
70
+ @rev_num = @header[24...26]
71
+ @ver_num = @header[26...28]
72
+ @byte_order = @header[28...30]
73
+ @log2_sect_size = @header[30...32].unpack('v')[0]
74
+ @log2_short_sect_size = @header[32...34].unpack('v')[0]
75
+ @total_sat_sectors = @header[44...48].unpack('V')[0]
76
+ @dir_start_sid = @header[48...52].unpack('V')[0]
77
+ @min_stream_size = @header[56...60].unpack('V')[0]
78
+ @ssat_start_sid = @header[60...64].unpack('V')[0]
79
+ @total_ssat_sectors = @header[64...68].unpack('V')[0]
80
+ @msat_start_sid = @header[68...72].unpack('V')[0]
81
+ @total_msat_sectors = @header[72...76].unpack('V')[0]
82
+
83
+ @sect_size = 1 << @log2_sect_size
84
+ @short_sect_size = 1 << @log2_short_sect_size
85
+ end
86
+
87
+ def build_msat
88
+ @msat = @header[76..-1].unpack('V109')
89
+ next_sector = @msat_start_sid
90
+ while next_sector > 0 do
91
+ raise "not implemented"
92
+ start = next_sector * @sect_size
93
+ finish = (next_sector + 1) * @sect_size
94
+ msat_sector = @data[start...finish]
95
+ @msat << msat_sector
96
+ next_sector = msat_sector[-1]
97
+ end
98
+ end
99
+
100
+ def build_sat
101
+ sat_stream = @msat.collect {|i| i >= 0 ? @data[(i*@sect_size)...((i+1)*@sect_size)] : '' }.join
102
+ @sat = sat_stream.unpack('V*')
103
+ end
104
+
105
+ def build_ssat
106
+ ssat_stream = stream_data(@data, @sat, @ssat_start_sid, @sect_size)
107
+ @ssat = ssat_stream.unpack('V*')
108
+ end
109
+
110
+ def build_directory
111
+ dir_stream = stream_data(@data, @sat, @dir_start_sid, @sect_size)
112
+ @dir_entry_list = []
113
+ i = 0
114
+ while i < dir_stream.length do
115
+ dentry = dir_stream[i...(i+DIR_ENTRY_SIZE)]
116
+ i += DIR_ENTRY_SIZE
117
+
118
+ did = @dir_entry_list.length
119
+ sz = dentry[64...66].unpack('C')[0]
120
+
121
+ if sz > 0
122
+ name = dentry[0...(sz-2)] # TODO unicode
123
+ else
124
+ name = ''
125
+ end
126
+
127
+ t = dentry[66...67].unpack('C')[0]
128
+ c = dentry[67...68].unpack('C')[0]
129
+ did_left = dentry[68...72].unpack('V')[0]
130
+ did_right = dentry[72...76].unpack('V')[0]
131
+ did_root = dentry[76...80].unpack('V')[0]
132
+ dentry_start_sid = dentry[116...120].unpack('V')[0]
133
+ stream_size = dentry[120...124].unpack('V')[0]
134
+
135
+ @dir_entry_list << [did, sz, name, t, c, did_left, did_right, did_root, dentry_start_sid, stream_size]
136
+ end
137
+ end
138
+
139
+ def build_short_sectors_data
140
+ did, sz, name, t, c, did_left, did_right, did_root, dentry_start_sid, stream_size = @dir_entry_list[0]
141
+ raise unless t == 0x05 # Short-Stream Container Stream (SSCS) resides in Root Storage
142
+
143
+ if stream_size == 0
144
+ @short_sectors_data = ''
145
+ else
146
+ @short_sectors_data = stream_data(@data, @sat, dentry_start_sid, @sect_size)
147
+ end
148
+ end
149
+
150
+ def stream_data(data, sat, start_sid, sect_size)
151
+ sid = start_sid
152
+ chunks = [[sid, sid]]
153
+ stream_data = ''
154
+ while sat[sid] >= 0 do
155
+ next_in_chain = sat[sid]
156
+ last_chunk_start, last_chunk_finish = chunks[-1]
157
+ if next_in_chain == last_chunk_finish + 1
158
+ chunks[-1] = last_chunk_start, next_in_chain
159
+ else
160
+ chunks << [next_in_chain, next_in_chain]
161
+ end
162
+ sid = next_in_chain
163
+ end
164
+
165
+ chunks.each do |s, f|
166
+ stream_data += data[s*sect_size...(f+1)*sect_size]
167
+ end
168
+ stream_data
169
+ end
170
+ end
171
+
172
+
173
+ # This implementation writes only 'Root Entry', 'Workbook' streams
174
+ # and 2 empty streams for aligning directory stream on sector boundary
175
+ #
176
+ # LAYOUT:
177
+ # 0 header
178
+ # 76 MSAT (1st part: 109 SID)
179
+ # 512 workbook stream
180
+ # ... additional MSAT sectors if streams' size > about 7 Mb == (109*512 * 128)
181
+ # ... SAT
182
+ # ... directory stream
183
+ #
184
+ # NOTE: this layout is "ad hoc". It can be more general. RTFM
185
+ class ExcelDocument
186
+ SECTOR_SIZE = 0x0200
187
+ MIN_LIMIT = 0x1000
188
+
189
+ SID_FREE_SECTOR = -1
190
+ SID_END_OF_CHAIN = -2
191
+ SID_USED_BY_SAT = -3
192
+ SID_USED_BY_MSAT = -4
193
+
194
+ def initialize
195
+ @book_stream_sect = []
196
+ @dir_stream_sect = []
197
+
198
+ @packed_sat = ''
199
+ @sat_sect = []
200
+
201
+ @packed_msat_1st = ''
202
+ @packed_msat_2nd = ''
203
+ @msat_sect_2nd = []
204
+ @header = ''
205
+ end
206
+
207
+ def build_directory
208
+ @dir_stream = ''
209
+
210
+ name = 'Root Entry'
211
+ type = 0x05 # root storage
212
+ colour = 0x01 # black
213
+ did_left = -1
214
+ did_right = -1
215
+ did_root = 1
216
+ start_sid = -2
217
+ stream_sz = 0
218
+ @dir_stream += pack_directory(name, type, colour, did_left, did_right, did_root, start_sid, stream_sz)
219
+
220
+ name = 'Workbook'
221
+ type = 0x02 # user stream
222
+ colour = 0x01 # black
223
+ did_left = -1
224
+ did_right = -1
225
+ did_root = -1
226
+ start_sid = 0
227
+ stream_sz = @book_stream_len
228
+ @dir_stream += pack_directory(name, type, colour, did_left, did_right, did_root, start_sid, stream_sz)
229
+ # padding
230
+ name = ''
231
+ type = 0x00 # empty
232
+ colour = 0x01 # black
233
+ did_left = -1
234
+ did_right = -1
235
+ did_root = -1
236
+ start_sid = -2
237
+ stream_sz = 0
238
+ @dir_stream += pack_directory(name, type, colour, did_left, did_right, did_root, start_sid, stream_sz) * 2
239
+ end
240
+
241
+ def pack_directory(name, type, colour, did_left, did_right, did_root, start_sid, stream_sz)
242
+ encoded_name = ''
243
+ 0.upto(name.length) do |i|
244
+ encoded_name << name[i, 1] + "\000"
245
+ end
246
+ encoded_name << "\000"
247
+ name_sz = encoded_name.length
248
+
249
+ args = [encoded_name, name_sz, type, colour, did_left, did_right, did_root, 0, 0, 0, 0, 0, 0, 0, 0, 0, start_sid, stream_sz, 0]
250
+ args.pack('a64 v C2 V3 V9 V V2')
251
+ end
252
+
253
+ def build_sat
254
+ book_sect_count = @book_stream_len >> 9
255
+ dir_sect_count = @dir_stream.length >> 9
256
+ total_sect_count = book_sect_count + dir_sect_count
257
+ sat_sect_count = 0
258
+ msat_sect_count = 0
259
+ sat_sect_count_limit = 109
260
+
261
+ while (total_sect_count > 128*sat_sect_count) || (sat_sect_count > sat_sect_count_limit) do
262
+ sat_sect_count += 1
263
+ total_sect_count += 1
264
+ if sat_sect_count > sat_sect_count_limit
265
+ msat_sect_count += 1
266
+ total_sect_count += 1
267
+ sat_sect_count_limit += 127
268
+ end
269
+ end
270
+
271
+ # initialize the sat array to be filled with the "empty" character specified by SID_FREE_SECTOR
272
+ sat = [SID_FREE_SECTOR]*128*sat_sect_count
273
+
274
+ sect = 0
275
+ while sect < book_sect_count - 1 do
276
+ @book_stream_sect << sect
277
+ sat[sect] = sect + 1
278
+ sect += 1
279
+ end
280
+ @book_stream_sect << sect
281
+ sat[sect] = SID_END_OF_CHAIN
282
+ sect += 1
283
+
284
+ while sect < book_sect_count + msat_sect_count do
285
+ @msat_sect_2nd << sect
286
+ sat[sect] = SID_USED_BY_MSAT
287
+ sect += 1
288
+ end
289
+
290
+ while sect < book_sect_count + msat_sect_count + sat_sect_count do
291
+ @sat_sect << sect
292
+ sat[sect] = SID_USED_BY_SAT
293
+ sect += 1
294
+ end
295
+
296
+ while sect < book_sect_count + msat_sect_count + sat_sect_count + dir_sect_count - 1 do
297
+ @dir_stream_sect << sect
298
+ sat[sect] = sect + 1
299
+ sect += 1
300
+ end
301
+
302
+ @dir_stream_sect << sect
303
+ sat[sect] = SID_END_OF_CHAIN
304
+ sect += 1
305
+
306
+ @packed_sat = sat.pack('V*')
307
+
308
+ msat_1st = []
309
+ 109.times do |i|
310
+ msat_1st[i] = @sat_sect[i] || SID_FREE_SECTOR
311
+ end
312
+ @packed_msat_1st = msat_1st.pack('V*')
313
+
314
+ msat_2nd = [SID_FREE_SECTOR] * 128 * msat_sect_count
315
+ msat_2nd[-1] = SID_END_OF_CHAIN if msat_sect_count > 0
316
+
317
+ i = 109
318
+ msat_sect = 0
319
+ sid_num = 0
320
+
321
+ while i < sat_sect_count do
322
+ if (sid_num + 1) % 128 == 0
323
+ msat_sect += 1
324
+ msat_2nd[sid_num] = @msat_sect_2nd[msat_sect] if msat_sect < @msat_sect_2nd.length
325
+ else
326
+ msat_2nd[sid_num] = @sat_sect[i]
327
+ i += 1
328
+ end
329
+ sid_num += 1
330
+ end
331
+
332
+ @packed_msat_2nd = msat_2nd.pack('V*')
333
+ end
334
+
335
+ def build_header
336
+ doc_magic = "\320\317\021\340\241\261\032\341"
337
+ file_uid = "\000" * 16
338
+ rev_num = ">\000"
339
+ ver_num = "\003\000"
340
+ byte_order = "\376\377"
341
+ log_sect_size = [9].pack('v')
342
+ log_short_sect_size = [6].pack('v')
343
+ not_used0 = "\000"*10
344
+ total_sat_sectors = [@sat_sect.length].pack('V')
345
+ dir_start_sid = [@dir_stream_sect[0]].pack('V')
346
+ not_used1 = "\000"*4
347
+ min_stream_size = [0x1000].pack('V')
348
+ ssat_start_sid = [-2].pack('V')
349
+ total_ssat_sectors = [0].pack('V')
350
+
351
+ if @msat_sect_2nd.length == 0
352
+ msat_start_sid = [-2].pack('V')
353
+ else
354
+ msat_start_sid = [@msat_sect_2nd[0]].pack('V')
355
+ end
356
+
357
+ total_msat_sectors = [@msat_sect_2nd.length].pack('V')
358
+
359
+ @header = [
360
+ doc_magic,
361
+ file_uid,
362
+ rev_num,
363
+ ver_num,
364
+ byte_order,
365
+ log_sect_size,
366
+ log_short_sect_size,
367
+ not_used0,
368
+ total_sat_sectors,
369
+ dir_start_sid,
370
+ not_used1,
371
+ min_stream_size,
372
+ ssat_start_sid,
373
+ total_ssat_sectors,
374
+ msat_start_sid,
375
+ total_msat_sectors
376
+ ].join
377
+ end
378
+
379
+ def data(stream)
380
+ distance_to_end_of_next_sector_boundary = 0x1000 - (stream.length % 0x1000)
381
+ @book_stream_len = stream.length + distance_to_end_of_next_sector_boundary
382
+ padding = "\000" * distance_to_end_of_next_sector_boundary
383
+
384
+ build_directory
385
+ build_sat
386
+ build_header
387
+
388
+ s = StringIO.new
389
+ s.write(@header)
390
+ s.write(@packed_msat_1st)
391
+ s.write(stream)
392
+ s.write(padding)
393
+ s.write(@packed_msat_2nd)
394
+ s.write(@packed_sat)
395
+ s.write(@dir_stream)
396
+ s.rewind
397
+ s
398
+ end
399
+
400
+ def save(file, stream)
401
+ we_own_it = !file.respond_to?(:write)
402
+ file = File.open(file, 'wb') if we_own_it
403
+ file.write data(stream).read
404
+ file.close if we_own_it
405
+ end
406
+ end
@@ -0,0 +1,6 @@
1
+ class ExcelFormula
2
+ NO_CALCS=0x00
3
+ RECALC_ALWAYS=0x01
4
+ CALC_ON_OPEN=0x02
5
+ PART_OF_SHARED_FORMULA=0x08
6
+ end