Class: ExcelDocument

Inherits:
Object
  • Object
show all
Defined in:
lib/surpass/document.rb

Overview

This implementation writes only ‘Root Entry’, ‘Workbook’ streams and 2 empty streams for aligning directory stream on sector boundary

LAYOUT: 0 header 76 MSAT (1st part: 109 SID) 512 workbook stream … additional MSAT sectors if streams’ size > about 7 Mb == (109*512 * 128) … SAT … directory stream

NOTE: this layout is “ad hoc”. It can be more general. RTFM

Constant Summary collapse

SECTOR_SIZE =
0x0200
MIN_LIMIT =
0x1000
SID_FREE_SECTOR =
-1
SID_END_OF_CHAIN =
-2
SID_USED_BY_SAT =
-3
SID_USED_BY_MSAT =
-4

Instance Method Summary collapse

Constructor Details

#initializeExcelDocument

Returns a new instance of ExcelDocument.



194
195
196
197
198
199
200
201
202
203
204
205
# File 'lib/surpass/document.rb', line 194

def initialize
  @book_stream_sect = []
  @dir_stream_sect = []
  
  @packed_sat = ''
  @sat_sect = []
  
  @packed_msat_1st = ''
  @packed_msat_2nd = ''
  @msat_sect_2nd = []
  @header = ''
end

Instance Method Details

#build_directoryObject



207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
# File 'lib/surpass/document.rb', line 207

def build_directory
  @dir_stream = ''
  
  name = 'Root Entry'
  type = 0x05 # root storage
  colour = 0x01 # black
  did_left  = -1
  did_right = -1
  did_root  = 1
  start_sid = -2
  stream_sz = 0
  @dir_stream += pack_directory(name, type, colour, did_left, did_right, did_root, start_sid, stream_sz)

  name = 'Workbook'
  type = 0x02 # user stream
  colour = 0x01 # black
  did_left  = -1
  did_right = -1
  did_root  = -1
  start_sid = 0
  stream_sz = @book_stream_len
  @dir_stream += pack_directory(name, type, colour, did_left, did_right, did_root, start_sid, stream_sz)
  # padding
  name      = ''
  type      = 0x00 # empty
  colour    = 0x01 # black
  did_left  = -1
  did_right = -1
  did_root  = -1
  start_sid = -2
  stream_sz = 0
  @dir_stream += pack_directory(name, type, colour, did_left, did_right, did_root, start_sid, stream_sz) * 2
end

#build_headerObject



335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
# File 'lib/surpass/document.rb', line 335

def build_header
   doc_magic                    = "\320\317\021\340\241\261\032\341"
   file_uid                     =  "\000" * 16
   rev_num                      = ">\000"
   ver_num                      = "\003\000"
   byte_order                   = "\376\377"
   log_sect_size                = [9].pack('v')
   log_short_sect_size          = [6].pack('v')
   not_used0                    = "\000"*10
   total_sat_sectors            = [@sat_sect.length].pack('V')
   dir_start_sid                = [@dir_stream_sect[0]].pack('V')
   not_used1                    = "\000"*4        
   min_stream_size              = [0x1000].pack('V')
   ssat_start_sid               = [-2].pack('V')
   total_ssat_sectors           = [0].pack('V')
   
   if @msat_sect_2nd.length == 0
     msat_start_sid = [-2].pack('V')
   else
     msat_start_sid = [@msat_sect_2nd[0]].pack('V')
   end
   
   total_msat_sectors = [@msat_sect_2nd.length].pack('V')
   
   @header = [
       doc_magic,
       file_uid,
       rev_num,
       ver_num,
       byte_order,
       log_sect_size,
       log_short_sect_size,
       not_used0,
       total_sat_sectors,
       dir_start_sid,
       not_used1,
       min_stream_size,
       ssat_start_sid,
       total_ssat_sectors,
       msat_start_sid,
       total_msat_sectors
   ].join
end

#build_satObject



253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
# File 'lib/surpass/document.rb', line 253

def build_sat
  book_sect_count       = @book_stream_len >> 9
  dir_sect_count        = @dir_stream.length >> 9
  total_sect_count      = book_sect_count + dir_sect_count
  sat_sect_count        = 0
  msat_sect_count       = 0
  sat_sect_count_limit  = 109
  
  while (total_sect_count > 128*sat_sect_count) || (sat_sect_count > sat_sect_count_limit) do
    sat_sect_count += 1
    total_sect_count += 1
    if sat_sect_count > sat_sect_count_limit
      msat_sect_count += 1
      total_sect_count += 1
      sat_sect_count_limit += 127
    end
  end
  
  # initialize the sat array to be filled with the "empty" character specified by SID_FREE_SECTOR
  sat = [SID_FREE_SECTOR]*128*sat_sect_count
  
  sect = 0
  while sect < book_sect_count - 1 do
    @book_stream_sect << sect
    sat[sect] = sect + 1
    sect += 1
  end
  @book_stream_sect << sect
  sat[sect] = SID_END_OF_CHAIN
  sect += 1
  
  while sect < book_sect_count + msat_sect_count do
    @msat_sect_2nd << sect
    sat[sect] = SID_USED_BY_MSAT
    sect += 1
  end
  
  while sect < book_sect_count + msat_sect_count + sat_sect_count do
    @sat_sect << sect
    sat[sect] = SID_USED_BY_SAT
    sect += 1
  end
  
  while sect < book_sect_count + msat_sect_count + sat_sect_count + dir_sect_count - 1 do
    @dir_stream_sect << sect
    sat[sect] = sect + 1
    sect += 1
  end
  
  @dir_stream_sect << sect
  sat[sect] = SID_END_OF_CHAIN
  sect += 1
  
  @packed_sat = sat.pack('V*')
  
  msat_1st = []
  109.times do |i|
    msat_1st[i] = @sat_sect[i] || SID_FREE_SECTOR
  end
  @packed_msat_1st = msat_1st.pack('V*')
  
  msat_2nd = [SID_FREE_SECTOR] * 128 * msat_sect_count
  msat_2nd[-1] = SID_END_OF_CHAIN if msat_sect_count > 0
  
  i = 109
  msat_sect = 0
  sid_num = 0
  
  while i < sat_sect_count do
    if (sid_num + 1) % 128 == 0
      msat_sect += 1
      msat_2nd[sid_num] = @msat_sect_2nd[msat_sect] if msat_sect < @msat_sect_2nd.length
    else
      msat_2nd[sid_num] = @sat_sect[i]
      i += 1
    end
    sid_num += 1
  end
  
  @packed_msat_2nd = msat_2nd.pack('V*')
end

#data(stream) ⇒ Object



379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
# File 'lib/surpass/document.rb', line 379

def data(stream)
  distance_to_end_of_next_sector_boundary = 0x1000 - (stream.length % 0x1000)
  @book_stream_len = stream.length + distance_to_end_of_next_sector_boundary
  padding = "\000" * distance_to_end_of_next_sector_boundary

  build_directory
  build_sat
  build_header
  
  s = StringIO.new
  s.write(@header)
  s.write(@packed_msat_1st)
  s.write(stream)
  s.write(padding)
  s.write(@packed_msat_2nd)
  s.write(@packed_sat)
  s.write(@dir_stream)
  s.rewind
  s
end

#pack_directory(name, type, colour, did_left, did_right, did_root, start_sid, stream_sz) ⇒ Object



241
242
243
244
245
246
247
248
249
250
251
# File 'lib/surpass/document.rb', line 241

def pack_directory(name, type, colour, did_left, did_right, did_root, start_sid, stream_sz)
  encoded_name = ''
  0.upto(name.length) do |i|
    encoded_name << name[i, 1] + "\000" 
  end
  encoded_name << "\000"
  name_sz = encoded_name.length

  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]
  args.pack('a64 v C2 V3 V9 V V2')
end

#save(file, stream) ⇒ Object



400
401
402
403
404
405
# File 'lib/surpass/document.rb', line 400

def save(file, stream)
  we_own_it = !file.respond_to?(:write)
  file = File.open(file, 'wb') if we_own_it
  file.write data(stream).read
  file.close if we_own_it
end