surpass 0.0.3

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