writeexcel 0.3.5 → 0.4.0

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 (122) hide show
  1. data/.gitattributes +1 -0
  2. data/README.rdoc +12 -6
  3. data/VERSION +1 -1
  4. data/charts/chartex.rb +316 -315
  5. data/charts/demo1.rb +1 -0
  6. data/charts/demo2.rb +1 -0
  7. data/charts/demo3.rb +117 -116
  8. data/charts/demo4.rb +119 -118
  9. data/charts/demo5.rb +48 -47
  10. data/examples/a_simple.rb +1 -0
  11. data/examples/autofilter.rb +1 -0
  12. data/examples/bigfile.rb +30 -29
  13. data/examples/chart_area.rb +121 -120
  14. data/examples/chart_bar.rb +120 -119
  15. data/examples/chart_column.rb +120 -119
  16. data/examples/chart_line.rb +120 -119
  17. data/examples/chart_pie.rb +108 -107
  18. data/examples/chart_scatter.rb +121 -120
  19. data/examples/chart_stock.rb +148 -147
  20. data/examples/chess.rb +1 -0
  21. data/examples/colors.rb +1 -0
  22. data/examples/comments1.rb +1 -0
  23. data/examples/comments2.rb +3 -2
  24. data/examples/copyformat.rb +1 -0
  25. data/examples/data_validate.rb +1 -0
  26. data/examples/date_time.rb +1 -0
  27. data/examples/defined_name.rb +1 -0
  28. data/examples/demo.rb +1 -0
  29. data/examples/diag_border.rb +1 -0
  30. data/examples/formats.rb +1 -0
  31. data/examples/formula_result.rb +1 -0
  32. data/examples/header.rb +1 -0
  33. data/examples/hide_sheet.rb +1 -0
  34. data/examples/hyperlink.rb +1 -0
  35. data/examples/images.rb +1 -0
  36. data/examples/indent.rb +1 -0
  37. data/examples/merge1.rb +1 -0
  38. data/examples/merge2.rb +1 -0
  39. data/examples/merge3.rb +1 -0
  40. data/examples/merge4.rb +1 -0
  41. data/examples/merge5.rb +1 -0
  42. data/examples/merge6.rb +67 -66
  43. data/examples/outline.rb +1 -0
  44. data/examples/outline_collapsed.rb +1 -0
  45. data/examples/panes.rb +1 -0
  46. data/examples/properties.rb +1 -0
  47. data/examples/properties_jp.rb +1 -0
  48. data/examples/protection.rb +1 -0
  49. data/examples/regions.rb +1 -0
  50. data/examples/repeat.rb +1 -0
  51. data/examples/right_to_left.rb +1 -0
  52. data/examples/row_wrap.rb +1 -0
  53. data/examples/stats.rb +1 -0
  54. data/examples/stocks.rb +1 -0
  55. data/examples/tab_colors.rb +1 -0
  56. data/examples/write_arrays.rb +1 -0
  57. data/lib/writeexcel.rb +6 -1
  58. data/lib/writeexcel/biffwriter.rb +21 -20
  59. data/lib/writeexcel/chart.rb +25 -12
  60. data/lib/writeexcel/charts/area.rb +153 -152
  61. data/lib/writeexcel/charts/bar.rb +178 -177
  62. data/lib/writeexcel/charts/column.rb +157 -156
  63. data/lib/writeexcel/charts/external.rb +62 -61
  64. data/lib/writeexcel/charts/line.rb +153 -152
  65. data/lib/writeexcel/charts/pie.rb +170 -169
  66. data/lib/writeexcel/charts/scatter.rb +4 -3
  67. data/lib/writeexcel/charts/stock.rb +212 -211
  68. data/lib/writeexcel/compatibility.rb +320 -0
  69. data/lib/writeexcel/excelformulaparser.rb +587 -586
  70. data/lib/writeexcel/format.rb +12 -13
  71. data/lib/writeexcel/formula.rb +30 -28
  72. data/lib/writeexcel/helper.rb +23 -0
  73. data/lib/writeexcel/olewriter.rb +5 -16
  74. data/lib/writeexcel/properties.rb +43 -54
  75. data/lib/writeexcel/storage_lite.rb +981 -968
  76. data/lib/writeexcel/workbook.rb +94 -73
  77. data/lib/writeexcel/worksheet.rb +230 -210
  78. data/test/helper.rb +19 -0
  79. data/test/test_00_IEEE_double.rb +1 -0
  80. data/test/test_01_add_worksheet.rb +1 -0
  81. data/test/test_02_merge_formats.rb +3 -5
  82. data/test/test_04_dimensions.rb +3 -5
  83. data/test/test_05_rows.rb +6 -6
  84. data/test/test_06_extsst.rb +8 -8
  85. data/test/test_11_date_time.rb +3 -5
  86. data/test/test_12_date_only.rb +3 -5
  87. data/test/test_13_date_seconds.rb +4 -6
  88. data/test/test_21_escher.rb +3 -5
  89. data/test/test_22_mso_drawing_group.rb +20 -22
  90. data/test/test_23_note.rb +5 -7
  91. data/test/test_24_txo.rb +3 -5
  92. data/test/test_25_position_object.rb +84 -79
  93. data/test/test_26_autofilter.rb +3 -13
  94. data/test/test_27_autofilter.rb +3 -13
  95. data/test/test_28_autofilter.rb +3 -13
  96. data/test/test_29_process_jpg.rb +5 -0
  97. data/test/test_30_validation_dval.rb +3 -5
  98. data/test/test_31_validation_dv_strings.rb +3 -5
  99. data/test/test_32_validation_dv_formula.rb +3 -5
  100. data/test/test_40_property_types.rb +10 -9
  101. data/test/test_41_properties.rb +1 -0
  102. data/test/test_42_set_properties.rb +14 -15
  103. data/test/test_50_name_stored.rb +299 -302
  104. data/test/test_51_name_print_area.rb +357 -360
  105. data/test/test_52_name_print_titles.rb +454 -457
  106. data/test/test_53_autofilter.rb +203 -206
  107. data/test/test_60_chart_generic.rb +5 -0
  108. data/test/test_61_chart_subclasses.rb +95 -94
  109. data/test/test_62_chart_formats.rb +272 -267
  110. data/test/test_63_chart_area_formats.rb +649 -644
  111. data/test/test_biff.rb +12 -38
  112. data/test/test_compatibility.rb +627 -0
  113. data/test/test_example_match.rb +3 -18
  114. data/test/test_format.rb +46 -105
  115. data/test/test_formula.rb +1 -0
  116. data/test/test_ole.rb +3 -4
  117. data/test/test_storage_lite.rb +125 -146
  118. data/test/test_workbook.rb +2 -23
  119. data/test/test_worksheet.rb +4 -5
  120. data/utils/add_magic_comment.rb +80 -0
  121. data/writeexcel.gemspec +8 -2
  122. metadata +10 -4
@@ -1,968 +1,981 @@
1
- require 'tempfile'
2
- require 'stringio'
3
-
4
- class OLEStorageLite #:nodoc:
5
- PPS_TYPE_ROOT = 5
6
- PPS_TYPE_DIR = 1
7
- PPS_TYPE_FILE = 2
8
- DATA_SIZE_SMALL = 0x1000
9
- LONG_INT_SIZE = 4
10
- PPS_SIZE = 0x80
11
-
12
- attr_reader :file
13
-
14
- def initialize(file = nil)
15
- @file = file
16
- end
17
-
18
- def getPpsTree(data)
19
- info = _initParse(file)
20
- info ? _getPpsTree(0, info, data) : nil
21
- end
22
-
23
- def getPpsSearch(name, data, icase)
24
- info = _initParse(file)
25
- info ? _getPpsSearch(0, info, name, data, icase) : nil
26
- end
27
-
28
- def getNthPps(no, data)
29
- info = _initParse(file)
30
- info ? _getNthPps(no, info, data) : nil
31
- end
32
-
33
- def _initParse(file)
34
- io = file.kind_of?(String) ? open(file, 'rb') : file
35
- _getHeaderInfo(io)
36
- end
37
- private :_initParse
38
-
39
- def _getPpsTree(no, info, data, done)
40
- if done
41
- return [] if done.include?(no)
42
- else
43
- done = []
44
- end
45
- done << no
46
-
47
- rootblock = info[:root_start]
48
-
49
- #1. Get Information about itself
50
- pps = _getNthPps(no, info, data)
51
-
52
- #2. Child
53
- if pps.dir_pps != 0xFFFFFFFF
54
- pps.child = _getPpsTree(pps.dir_pps, info, data, done)
55
- else
56
- pps.child = nil
57
- end
58
-
59
- #3. Previous,Next PPSs
60
- list = []
61
- list << _getPpsTree(pps.prev_pps, info, data, done) if pps.prev_pps != 0xFFFFFFFF
62
- list << pps
63
- list << _getPpsTree(pps.next_pps, info, data, done) if pps.next_pps != 0xFFFFFFFF
64
- end
65
- private :_getPpsTree
66
-
67
- def _getPpsSearch(no, info, name, data, icase, done = nil)
68
- rootblock = info[:root_start]
69
- #1. Check it self
70
- if done
71
- return [] if done.include?(no)
72
- else
73
- done = []
74
- end
75
- done << no
76
- pps = _getNthPps(no, info, nil)
77
-
78
- re = Regexp.new("^\Q#{pps.name}\E$", Regexp::IGNORECASE)
79
- if (icase && !name.select { |v| v =~ re }.empty?) || name.include?(pps.name)
80
- pps = _getNthPps(no, info, data) if data
81
- res = [pps]
82
- else
83
- res = []
84
- end
85
-
86
- #2. Check Child, Previous, Next PPSs
87
- res +=
88
- _getPpsSearch(pps.dir_pps, info, name, data, icase, done) if pps.dir_pps != 0xFFFFFFFF
89
- res +=
90
- _getPpsSearch(pps.prev_pps, info, name, data, icase, done) if pps.prev_pps != 0xFFFFFFFF
91
- res +=
92
- _getPpsSearch(pps.next_pps, info, name, data, icase, done) if pps.next_pps != 0xFFFFFFFF
93
- res
94
- end
95
- private :_getPpsSearch
96
-
97
- def _getHeaderInfo(io)
98
- info = { :fileh => io }
99
-
100
- #0. Check ID
101
- info[:fileh].seek(0, 0)
102
- return nil unless info[:fileh].read(8) == "\xD0\xCF\x11\xE0\xA1\xB1\x1A\xE1"
103
-
104
- # BIG BLOCK SIZE
105
- val = _getInfoFromFile(info[:fileh], 0x1E, 2, "v")
106
- return nil if val.nil?
107
- info[:big_block_size] = 2 ** val
108
-
109
- # SMALL BLOCK SIZE
110
- val = _getInfoFromFile(info[:fileh], 0x20, 2, "v")
111
- return nil if val.nil?
112
- info[:small_block_size] = 2 ** val
113
-
114
- # BDB Count
115
- val = _getInfoFromFile(info[:fileh], 0x2C, 4, "V")
116
- return nil if val.nil?
117
- info[:bdb_count] = val
118
-
119
- # START BLOCK
120
- val = _getInfoFromFile(info[:fileh], 0x30, 4, "V")
121
- return nil if val.nil?
122
- info[:root_start] = val
123
-
124
- # SMALL BD START
125
- val = _getInfoFromFile(info[:fileh], 0x3C, 4, "V")
126
- return nil if val.nil?
127
- info[:sbd_start] = val
128
-
129
- # SMALL BD COUNT
130
- val = _getInfoFromFile(info[:fileh], 0x40, 4, "V")
131
- return nil if val.nil?
132
- info[:sbd_count] = val
133
-
134
- # EXTRA BBD START
135
- val = _getInfoFromFile(info[:fileh], 0x44, 4, "V")
136
- return nil if val.nil?
137
- info[:extra_bbd_start] = val
138
-
139
- # EXTRA BBD COUNT
140
- val = _getInfoFromFile(info[:fileh], 0x48, 4, "V")
141
- return nil if val.nil?
142
- info[:extra_bbd_count] = val
143
-
144
- #GET BBD INFO
145
- info[:bbd_info] = _getBbdInfo(info)
146
-
147
- # GET ROOT PPS
148
- root = _getNthPps(0, info, nil)
149
- info[:sb_start] = root.start_block
150
- info[:sb_size] = root.size
151
- info
152
- end
153
- private :_getHeaderInfo
154
-
155
- def _getInfoFromFile(io, pos, len, fmt)
156
- io.seek(pos, 0)
157
- str = io.read(len)
158
- if str.length != len
159
- nil
160
- else
161
- str.unpack(fmt)[0]
162
- end
163
- end
164
- private :_getInfoFromFile
165
-
166
- def _getBbdInfo(info)
167
- bdlist = []
168
- iBdbCnt = info[:bdb_count]
169
- i1stCnt = (info[:big_block_size] - 0x4C) / LONG_INT_SIZE
170
- iBdlCnt = info[:big_block_size] / LONG_INT_SIZE - 1
171
-
172
- #1. 1st BDlist
173
- info[:fileh].seek(0x4C, 0)
174
- iGetCnt = iBdbCnt < i1stCnt ? iBdbCnt : i1stCnt
175
- str = info[:fileh].read(LONG_INT_SIZE * iGetCnt)
176
- bdlist += str.unpack("V#{iGetCnt}")
177
- iBdbCnt -= iGetCnt
178
-
179
- #2. Extra BDList
180
- iBlock = info[:extra_bbd_start]
181
- while iBdbCnt> 0 && _isNormalBlock(iBlock)
182
- _setFilePos(iBlock, 0, info)
183
- iGetCnt = iBdbCnt < iBdlCnt ? iBdbCnt : iBdlCnt
184
- str = info[:fileh].read(LONG_INT_SIZE * iGetCnt)
185
- bdlist += str.unpack("V#{iGetCnt}")
186
- iBdbCnt -= iGetCnt
187
- str = info[:fileh].read(LONG_INT_SIZE)
188
- iBlock = str.unpack("V")
189
- end
190
-
191
- #3.Get BDs
192
- hBd = Hash.new
193
- iBlkNo = 0
194
- iBdCnt = info[:big_block_size] / LONG_INT_SIZE
195
- bdlist.each do |iBdL|
196
- _setFilePos(iBdL, 0, info)
197
- str = info[:fileh].read(info[:big_block_size])
198
- arr = str.unpack("V#{iBdCnt}")
199
- (0...iBdCnt).each do |i|
200
- hBd[iBlkNo] = arr[i] if arr[i] != iBlkNo + 1
201
- iBlkNo += 1
202
- end
203
- end
204
- hBd
205
- end
206
- private :_getBbdInfo
207
-
208
- def _getNthPps(pos, info, data)
209
- ppsstart = info[:root_start]
210
-
211
- basecnt = info[:big_block_size] / PPS_SIZE
212
- ppsblock = pos / basecnt
213
- ppspos = pos % basecnt
214
-
215
- block = _getNthBlockNo(ppsstart, ppsblock, info)
216
- return nil if block.nil?
217
-
218
- _setFilePos(block, PPS_SIZE * ppspos, info)
219
- str = info[:fileh].read(PPS_SIZE)
220
- return nil if str.nil? || str == ''
221
- nmsize = str[0x40, 2].unpack('v')[0]
222
- nmsize -= 2 if nmsize > 2
223
- nm = str[0, nmsize]
224
- type = str[0x42, 2].unpack('C')[0]
225
- ppsprev = str[0x44, LONG_INT_SIZE].unpack('V')[0]
226
- ppsnext = str[0x48, LONG_INT_SIZE].unpack('V')[0]
227
- dirpps = str[0x4C, LONG_INT_SIZE].unpack('V')[0]
228
- time1st =
229
- (type == PPS_TYPE_ROOT || type == PPS_TYPE_DIR) ? oleData2Local(str[0x64, 8]) : nil
230
- time2nd =
231
- (type == PPS_TYPE_ROOT || type == PPS_TYPE_DIR) ? oleData2Local(str[0x6C, 8]) : nil
232
- start, size = str[0x74, 8].unpack('VV')
233
- if data
234
- sdata = _getData(type, start, size, info)
235
- OLEStorageLitePPS.new(pos, nm, type, ppsprev, ppsnext, dirpps,
236
- time1st, time2nd, start, size, sdata, nil)
237
- else
238
- OLEStorageLitePPS.new(pos, nm, type, ppsprev, ppsnext, dirpps,
239
- time1st, time2nd, start, size, nil, nil)
240
- end
241
- end
242
- private :_getNthPps
243
-
244
- def _setFilePos(iBlock, iPos, info)
245
- info[:fileh].seek((iBlock + 1) * info[:big_block_size] + iPos, 0)
246
- end
247
- private :_setFilePos
248
-
249
- def _getNthBlockNo(stblock, nth, info)
250
- inext = stblock
251
- (0...nth).each do |i|
252
- sv = inext
253
- inext = _getNextBlockNo(sv, info)
254
- return nil unless _isNormalBlock(inext)
255
- end
256
- inext
257
- end
258
- private :_getNthBlockNo
259
-
260
- def _getData(iType, iBlock, iSize, info)
261
- if iType == PPS_TYPE_FILE
262
- if iSize < DATA_SIZE_SMALL
263
- return _getSmallData(iBlock, iSize, info)
264
- else
265
- return _getBigData(iBlock, iSize, info)
266
- end
267
- elsif iType == PPS_TYPE_ROOT # Root
268
- return _getBigData(iBlock, iSize, info)
269
- elsif iType == PPS_TYPE_DIR # Directory
270
- return nil
271
- end
272
- end
273
- private :_getData
274
-
275
- def _getBigData(iBlock, iSize, info)
276
- return '' unless _isNormalBlock(iBlock)
277
- iRest = iSize
278
- sRes = ''
279
- aKeys = info[:bbd_info].keys.sort
280
-
281
- while iRest > 0
282
- aRes = aKeys.select { |key| key >= iBlock }
283
- iNKey = aRes[0]
284
- i = iNKey - iBlock
285
- iNext = info[:bbd_info][iNKey]
286
- _setFilePos(iBlock, 0, info)
287
- iGetSize = info[:big_block_size] * (i + 1)
288
- iGetSize = iRest if iRest < iGetSize
289
- sRes += info[:fileh].read(iGetSize)
290
- iRest -= iGetSize
291
- iBlock = iNext
292
- end
293
- return sRes
294
- end
295
- private :_getBigData
296
-
297
- def _getNextBlockNo(iBlockNo, info)
298
- iRes = info[:bbd_info][iBlockNo]
299
- iRes ? iRes : iBlockNo + 1
300
- end
301
- private :_getNextBlockNo
302
-
303
- def _isNormalBlock(iBlock)
304
- iBlock < 0xFFFFFFFC ? 1 : nil
305
- end
306
- private :_isNormalBlock
307
-
308
- def _getSmallData(iSmBlock, iSize, info)
309
- iRest = iSize
310
- sRes = ''
311
- while iRest > 0
312
- _setFilePosSmall(iSmBlock, info)
313
- sRes += info[:fileh].read(
314
- iRest >= info[:small_block_size] ? info[:small_block_size] : iRest)
315
- iRest -= info[:small_block_size]
316
- iSmBlock = _getNextSmallBlockNo(iSmBlock, info)
317
- end
318
- sRes
319
- end
320
- private :_getSmallData
321
-
322
- def _setFilePosSmall(iSmBlock, info)
323
- iSmStart = info[:sb_start]
324
- iBaseCnt = info[:big_block_size] / info[:small_block_size]
325
- iNth = iSmBlock / iBaseCnt
326
- iPos = iSmBlock % iBaseCnt
327
-
328
- iBlk = _getNthBlockNo(iSmStart, iNth, info)
329
- _setFilePos(iBlk, iPos * info[:small_block_size], info)
330
- end
331
- private :_setFilePosSmall
332
-
333
- def _getNextSmallBlockNo(iSmBlock, info)
334
- iBaseCnt = info[:big_block_size] / LONG_INT_SIZE
335
- iNth = iSmBlock / iBaseCnt
336
- iPos = iSmBlock % iBaseCnt
337
- iBlk = _getNthBlockNo(info[:sbd_start], iNth, info)
338
- _setFilePos(iBlk, iPos * LONG_INT_SIZE, info)
339
- info[:fileh].read(LONG_INT_SIZE).unpack('V')
340
- end
341
- private :_getNextSmallBlockNo
342
-
343
- def asc2ucs(str)
344
- str.split(//).join("\0") + "\0"
345
- end
346
-
347
- def ucs2asc(str)
348
- ary = str.unpack('v*').map { |s| [s].pack('c')}
349
- ary.join('')
350
- end
351
-
352
- #------------------------------------------------------------------------------
353
- # OLEDate2Local()
354
- #
355
- # Convert from a Window FILETIME structure to a localtime array. FILETIME is
356
- # a 64-bit value representing the number of 100-nanosecond intervals since
357
- # January 1 1601.
358
- #
359
- # We first convert the FILETIME to seconds and then subtract the difference
360
- # between the 1601 epoch and the 1970 Unix epoch.
361
- #
362
- def oleData2Local(oletime)
363
- # Unpack the FILETIME into high and low longs.
364
- lo, hi = oletime.unpack('V2')
365
-
366
- # Convert the longs to a double.
367
- nanoseconds = hi * 2 ** 32 + lo
368
-
369
- # Convert the 100 nanosecond units into seconds.
370
- time = nanoseconds / 1e7
371
-
372
- # Subtract the number of seconds between the 1601 and 1970 epochs.
373
- time -= 11644473600
374
-
375
- # Convert to a localtime (actually gmtime) structure.
376
- if time >= 1
377
- ltime = Time.at(time).getgm.to_a[0, 9]
378
- ltime[4] -= 1 # month
379
- ltime[5] -= 1900 # year
380
- ltime[7] -= 1 # past from 1, Jan
381
- ltime[8] = ltime[8] ? 1 : 0
382
- ltime
383
- else
384
- []
385
- end
386
- end
387
-
388
- #------------------------------------------------------------------------------
389
- # LocalDate2OLE()
390
- #
391
- # Convert from a a localtime array to a Window FILETIME structure. FILETIME is
392
- # a 64-bit value representing the number of 100-nanosecond intervals since
393
- # January 1 1601.
394
- #
395
- # We first convert the localtime (actually gmtime) to seconds and then add the
396
- # difference between the 1601 epoch and the 1970 Unix epoch. We convert that to
397
- # 100 nanosecond units, divide it into high and low longs and return it as a
398
- # packed 64bit structure.
399
- #
400
- def localDate2OLE(localtime)
401
- return "\x00" * 8 unless localtime
402
-
403
- # Convert from localtime (actually gmtime) to seconds.
404
- args = localtime.reverse
405
- args[0] += 1900 # year
406
- args[1] += 1 # month
407
- time = Time.gm(*args)
408
-
409
- # Add the number of seconds between the 1601 and 1970 epochs.
410
- time = time.to_i + 11644473600
411
-
412
- # The FILETIME seconds are in units of 100 nanoseconds.
413
- nanoseconds = time * 10000000
414
-
415
- # Pack the total nanoseconds into 64 bits...
416
- hi, lo = nanoseconds.divmod 1 << 32
417
-
418
- [lo, hi].pack("VV") # oletime
419
- end
420
- end
421
-
422
- class OLEStorageLitePPS < OLEStorageLite #:nodoc:
423
- attr_accessor :no, :name, :type, :prev_pps, :next_pps, :dir_pps
424
- attr_accessor :time_1st, :time_2nd, :start_block, :size, :data, :child
425
- attr_reader :pps_file
426
-
427
- def initialize(iNo, sNm, iType, iPrev, iNext, iDir,
428
- raTime1st, raTime2nd, iStart, iSize, sData, raChild)
429
- @no = iNo
430
- @name = sNm
431
- @type = iType
432
- @prev_pps = iPrev
433
- @next_pps = iNext
434
- @dir_pps = iDir
435
- @time_1st = raTime1st
436
- @time_2nd = raTime2nd
437
- @start_block = iStart
438
- @size = iSize
439
- @data = sData
440
- @child = raChild
441
- @pps_file = nil
442
- end
443
-
444
- def _datalen
445
- return 0 if @data.nil?
446
- if @pps_file
447
- return @pps_file.lstat.size
448
- else
449
- return @data.size
450
- end
451
- end
452
- protected :_datalen
453
-
454
- def _makeSmallData(aList, rh_info)
455
- file = rh_info[:fileh]
456
- iSmBlk = 0
457
- sRes = ''
458
-
459
- aList.each do |pps|
460
- #1. Make SBD, small data string
461
- if pps.type == PPS_TYPE_FILE
462
- next if pps.size <= 0
463
- if pps.size < rh_info[:small_size]
464
- iSmbCnt = pps.size / rh_info[:small_block_size]
465
- iSmbCnt += 1 if pps.size % rh_info[:small_block_size] > 0
466
- #1.1 Add to SBD
467
- 0.upto(iSmbCnt-1-1) do |i|
468
- file.write([i + iSmBlk+1].pack("V"))
469
- end
470
- file.write([-2].pack("V"))
471
-
472
- #1.2 Add to Data String(this will be written for RootEntry)
473
- #Check for update
474
- if pps.pps_file
475
- pps.pps_file.seek(0) #To The Top
476
- while sBuff = pps.pps_file.read(4096)
477
- sRes << sBuff
478
- end
479
- else
480
- sRes << pps.data
481
- end
482
- if pps.size % rh_info[:small_block_size] > 0
483
- cnt = rh_info[:small_block_size] - (pps.size % rh_info[:small_block_size])
484
- sRes << "\0" * cnt
485
- end
486
- #1.3 Set for PPS
487
- pps.start_block = iSmBlk
488
- iSmBlk += iSmbCnt
489
- end
490
- end
491
- end
492
- iSbCnt = rh_info[:big_block_size] / LONG_INT_SIZE
493
- file.write([-1].pack("V") * (iSbCnt - (iSmBlk % iSbCnt))) if iSmBlk % iSbCnt > 0
494
- #2. Write SBD with adjusting length for block
495
- return sRes
496
- end
497
- private :_makeSmallData
498
-
499
- def _savePpsWk(rh_info)
500
- #1. Write PPS
501
- file = rh_info[:fileh]
502
- file.write(
503
- @name +
504
- ("\x00" * (64 - @name.length)) + #64
505
- [@name.length + 2].pack("v") + #66
506
- [@type].pack("c") + #67
507
- [0x00].pack("c") + #UK #68
508
- [@prev_pps].pack("V") + #Prev #72
509
- [@next_pps].pack("V") + #Next #76
510
- [@dir_pps].pack("V") + #Dir #80
511
- "\x00\x09\x02\x00" + #84
512
- "\x00\x00\x00\x00" + #88
513
- "\xc0\x00\x00\x00" + #92
514
- "\x00\x00\x00\x46" + #96
515
- "\x00\x00\x00\x00" + #100
516
- localDate2OLE(@time_1st) + #108
517
- localDate2OLE(@time_2nd) #116
518
- )
519
- if @start_block != 0
520
- file.write([@start_block].pack('V'))
521
- else
522
- file.write([0].pack('V'))
523
- end
524
- if @size != 0 #124
525
- file.write([@size].pack('V'))
526
- else
527
- file.write([0].pack('V'))
528
- end
529
- file.write([0].pack('V')) #128
530
- end
531
- protected :_savePpsWk
532
- end
533
-
534
- class OLEStorageLitePPSRoot < OLEStorageLitePPS #:nodoc:
535
- def initialize(raTime1st, raTime2nd, raChild)
536
- super(
537
- nil,
538
- asc2ucs('Root Entry'),
539
- PPS_TYPE_ROOT,
540
- nil,
541
- nil,
542
- nil,
543
- raTime1st,
544
- raTime2nd,
545
- nil,
546
- nil,
547
- nil,
548
- raChild)
549
- end
550
-
551
- def save(sFile, bNoAs = nil, rh_info = nil)
552
- #0.Initial Setting for saving
553
- rh_info = Hash.new unless rh_info
554
- if rh_info[:big_block_size]
555
- rh_info[:big_block_size] = 2 ** adjust2(rh_info[:big_block_size])
556
- else
557
- rh_info[:big_block_size] = 2 ** 9
558
- end
559
- if rh_info[:small_block_size]
560
- rh_info[:small_block_size] = 2 ** adjust2(rh_info[:small_block_size])
561
- else
562
- rh_info[:small_block_size] = 2 ** 6
563
- end
564
- rh_info[:small_size] = 0x1000
565
- rh_info[:pps_size] = 0x80
566
-
567
- close_file = true
568
-
569
- #1.Open File
570
- #1.1 sFile is Ref of scalar
571
- if sFile.kind_of?(String)
572
- rh_info[:fileh] = open(sFile, "wb")
573
- else
574
- rh_info[:fileh] = sFile.binmode
575
- end
576
-
577
- iBlk = 0
578
- #1. Make an array of PPS (for Save)
579
- aList=[]
580
- if bNoAs
581
- _savePpsSetPnt2([self], aList, rh_info)
582
- else
583
- _savePpsSetPnt([self], aList, rh_info)
584
- end
585
- iSBDcnt, iBBcnt, iPPScnt = _calcSize(aList, rh_info)
586
-
587
- #2.Save Header
588
- _saveHeader(rh_info, iSBDcnt, iBBcnt, iPPScnt)
589
-
590
- #3.Make Small Data string (write SBD)
591
- # Small Datas become RootEntry Data
592
- @data = _makeSmallData(aList, rh_info)
593
-
594
- #4. Write BB
595
- iBBlk = iSBDcnt
596
- _saveBigData(iBBlk, aList, rh_info)
597
-
598
- #5. Write PPS
599
- _savePps(aList, rh_info)
600
-
601
- #6. Write BD and BDList and Adding Header informations
602
- _saveBbd(iSBDcnt, iBBcnt, iPPScnt, rh_info)
603
-
604
- #7.Close File
605
- rh_info[:fileh].close if close_file
606
- end
607
-
608
- def _calcSize(aList, rh_info)
609
- #0. Calculate Basic Setting
610
- iSBDcnt, iBBcnt, iPPScnt = [0,0,0]
611
- iSmallLen = 0
612
- iSBcnt = 0
613
- aList.each do |pps|
614
- if pps.type == PPS_TYPE_FILE
615
- pps.size = pps._datalen #Mod
616
- if pps.size < rh_info[:small_size]
617
- iSBcnt += pps.size / rh_info[:small_block_size]
618
- iSBcnt += 1 if pps.size % rh_info[:small_block_size] > 0
619
- else
620
- iBBcnt += pps.size / rh_info[:big_block_size]
621
- iBBcnt += 1 if pps.size % rh_info[:big_block_size] > 0
622
- end
623
- end
624
- end
625
- iSmallLen = iSBcnt * rh_info[:small_block_size]
626
- iSlCnt = rh_info[:big_block_size] / LONG_INT_SIZE
627
- iSBDcnt = iSBcnt / iSlCnt
628
- iSBDcnt += 1 if iSBcnt % iSlCnt > 0
629
- iBBcnt += iSmallLen / rh_info[:big_block_size]
630
- iBBcnt += 1 if iSmallLen % rh_info[:big_block_size] > 0
631
- iCnt = aList.size
632
- iBdCnt = rh_info[:big_block_size] / PPS_SIZE
633
- iPPScnt = iCnt / iBdCnt
634
- iPPScnt += 1 if iCnt % iBdCnt > 0
635
- return [iSBDcnt, iBBcnt, iPPScnt]
636
- end
637
- private :_calcSize
638
-
639
- def _adjust2(i2)
640
- iWk = Math.log(i2)/Math.log(2)
641
- return iWk > Integer(iWk) ? Integer(iWk) + 1 : iWk
642
- end
643
- private :_adjust2
644
-
645
- def _saveHeader(rh_info, iSBDcnt, iBBcnt, iPPScnt)
646
- file = rh_info[:fileh]
647
-
648
- #0. Calculate Basic Setting
649
- iBlCnt = rh_info[:big_block_size] / LONG_INT_SIZE
650
- i1stBdL = (rh_info[:big_block_size] - 0x4C) / LONG_INT_SIZE
651
- i1stBdMax = i1stBdL * iBlCnt - i1stBdL
652
- iBdExL = 0
653
- iAll = iBBcnt + iPPScnt + iSBDcnt
654
- iAllW = iAll
655
- iBdCntW = iAllW / iBlCnt
656
- iBdCntW += 1 if iAllW % iBlCnt > 0
657
- iBdCnt = 0
658
- #0.1 Calculate BD count
659
- iBlCnt -= 1 #the BlCnt is reduced in the count of the last sect is used for a pointer the next Bl
660
- iBBleftover = iAll - i1stBdMax
661
- if iAll >i1stBdMax
662
- while true
663
- iBdCnt = iBBleftover / iBlCnt
664
- iBdCnt += 1 if iBBleftover % iBlCnt > 0
665
- iBdExL = iBdCnt / iBlCnt
666
- iBdExL += 1 if iBdCnt % iBlCnt > 0
667
- iBBleftover += iBdExL
668
- break if iBdCnt == iBBleftover / iBlCnt + (iBBleftover % iBlCnt > 0 ? 1 : 0)
669
- end
670
- end
671
- iBdCnt += i1stBdL
672
- #print "iBdCnt = iBdCnt \n"
673
-
674
- #1.Save Header
675
- file.write(
676
- "\xD0\xCF\x11\xE0\xA1\xB1\x1A\xE1" +
677
- "\x00\x00\x00\x00" * 4 +
678
- [0x3b].pack("v") +
679
- [0x03].pack("v") +
680
- [-2].pack("v") +
681
- [9].pack("v") +
682
- [6].pack("v") +
683
- [0].pack("v") +
684
- "\x00\x00\x00\x00" * 2 +
685
- [iBdCnt].pack("V") +
686
- [iBBcnt+iSBDcnt].pack("V") + #ROOT START
687
- [0].pack("V") +
688
- [0x1000].pack("V") +
689
- [0].pack("V") + #Small Block Depot
690
- [1].pack("V")
691
- )
692
- #2. Extra BDList Start, Count
693
- if iAll <= i1stBdMax
694
- file.write(
695
- [-2].pack("V") + #Extra BDList Start
696
- [0].pack("V") #Extra BDList Count
697
- )
698
- else
699
- file.write(
700
- [iAll + iBdCnt].pack("V") +
701
- [iBdExL].pack("V")
702
- )
703
- end
704
-
705
- #3. BDList
706
- cnt = i1stBdL
707
- cnt = iBdCnt if iBdCnt < i1stBdL
708
- 0.upto(cnt-1) do |i|
709
- file.write([iAll + i].pack("V"))
710
- end
711
- file.write([-1].pack("V") * (i1stBdL - cnt)) if cnt < i1stBdL
712
- end
713
- private :_saveHeader
714
-
715
- def _saveBigData(iStBlk, aList, rh_info)
716
- iRes = 0
717
- file = rh_info[:fileh]
718
-
719
- #1.Write Big (ge 0x1000) Data into Block
720
- aList.each do |pps|
721
- if pps.type != PPS_TYPE_DIR
722
- #print "PPS: pps DEF:", defined(pps->{Data}), "\n"
723
- pps.size = pps._datalen #Mod
724
- if (pps.size >= rh_info[:small_size]) ||
725
- ((pps.type == PPS_TYPE_ROOT) && !pps.data.nil?)
726
- #1.1 Write Data
727
- #Check for update
728
- if pps.pps_file
729
- iLen = 0
730
- pps.pps_file.seek(0, 0) #To The Top
731
- while sBuff = pps.pps_file.read(4096)
732
- iLen += sBuff.length
733
- file.write(sBuff) #Check for update
734
- end
735
- else
736
- file.write(pps.data)
737
- end
738
- if pps.size % rh_info[:big_block_size] > 0
739
- file.write(
740
- "\x00" *
741
- (rh_info[:big_block_size] -
742
- (pps.size % rh_info[:big_block_size]))
743
- )
744
- end
745
- #1.2 Set For PPS
746
- pps.start_block = iStBlk
747
- iStBlk += pps.size / rh_info[:big_block_size]
748
- iStBlk += 1 if pps.size % rh_info[:big_block_size] > 0
749
- end
750
- end
751
- end
752
- end
753
-
754
- def _savePps(aList, rh_info)
755
- #0. Initial
756
- file = rh_info[:fileh]
757
- #2. Save PPS
758
- aList.each do |oItem|
759
- oItem._savePpsWk(rh_info)
760
- end
761
- #3. Adjust for Block
762
- iCnt = aList.size
763
- iBCnt = rh_info[:big_block_size] / rh_info[:pps_size]
764
- if iCnt % iBCnt > 0
765
- file.write("\x00" * ((iBCnt - (iCnt % iBCnt)) * rh_info[:pps_size]))
766
- end
767
- return (iCnt / iBCnt) + ((iCnt % iBCnt) > 0 ? 1: 0)
768
- end
769
- private :_savePps
770
-
771
- def _savePpsSetPnt(pps_array, aList, rh_info)
772
- #1. make Array as Children-Relations
773
- #1.1 if No Children
774
- bpp=1
775
- if pps_array.nil? || pps_array.size == 0
776
- return 0xFFFFFFFF
777
- #1.2 Just Only one
778
- elsif pps_array.size == 1
779
- aList << pps_array[0]
780
- pps_array[0].no = aList.size - 1
781
- pps_array[0].prev_pps = 0xFFFFFFFF
782
- pps_array[0].next_pps = 0xFFFFFFFF
783
- pps_array[0].dir_pps = _savePpsSetPnt(pps_array[0].child, aList, rh_info)
784
- return pps_array[0].no
785
- #1.3 Array
786
- else
787
- iCnt = pps_array.size
788
- #1.3.1 Define Center
789
- iPos = Integer(iCnt / 2.0) #$iCnt
790
-
791
- aList.push(pps_array[iPos])
792
- pps_array[iPos].no = aList.size - 1
793
-
794
- aWk = pps_array.dup
795
- #1.3.2 Devide a array into Previous,Next
796
- aPrev = aWk[0, iPos]
797
- aWk[0..iPos-1] = nil
798
- aNext = aWk[1, iCnt - iPos - 1]
799
- aWk[1..(1 + iCnt - iPos -1 -1)] = nil
800
- pps_array[iPos].prev_pps = _savePpsSetPnt(aPrev, aList, rh_info)
801
- pps_array[iPos].next_pps = _savePpsSetPnt(aNext, aList, rh_info)
802
- pps_array[iPos].dir_pps = _savePpsSetPnt(pps_array[iPos].child, aList, rh_info)
803
- return pps_array[iPos].no
804
- end
805
- end
806
- private :_savePpsSetPnt
807
-
808
- def _savePpsSetPnt2(pps_array, aList, rh_info)
809
- #1. make Array as Children-Relations
810
- #1.1 if No Children
811
- if pps_array.nil? || pps_array.size == 0
812
- return 0xFFFFFFFF
813
- #1.2 Just Only one
814
- elsif pps_array.size == 1
815
- aList << pps_array[0]
816
- pps_array[0].no = aList.size - 1
817
- pps_array[0].prev_pps = 0xFFFFFFFF
818
- pps_array[0].next_pps = 0xFFFFFFFF
819
- pps_array[0].dir_pps = _savePpsSetPnt2(pps_array[0].child, aList, rh_info)
820
- return pps_array[0].no
821
- #1.3 Array
822
- else
823
- iCnt = pps_array.size
824
- #1.3.1 Define Center
825
- iPos = 0 #int($iCnt/ 2); #$iCnt
826
-
827
- aWk = pps_array.dup
828
- aPrev = aWk[1, 1]
829
- aWk[1..1] = nil
830
- aNext = aWk[1..aWk.size] #, $iCnt - $iPos -1);
831
- pps_array[iPos].prev_pps = _savePpsSetPnt2(pps_array, aList, rh_info)
832
- aList.push(pps_array[iPos])
833
- pps_array[iPos].no = aList.size
834
-
835
- #1.3.2 Devide a array into Previous,Next
836
- pps_array[iPos].next_pps = _savePpsSetPnt2(aNext, aList, rh_info)
837
- pps_array[iPos].dir_pps = _savePpsSetPnt2(pps_array[iPos].child, aList, rh_info)
838
- return pps_array[iPos].no
839
- end
840
- end
841
- private :_savePpsSetPnt2
842
-
843
- def _saveBbd(iSbdSize, iBsize, iPpsCnt, rh_info)
844
- file = rh_info[:fileh]
845
- #0. Calculate Basic Setting
846
- iBbCnt = rh_info[:big_block_size] / LONG_INT_SIZE
847
- iBlCnt = iBbCnt - 1
848
- i1stBdL = (rh_info[:big_block_size] - 0x4C) / LONG_INT_SIZE
849
- i1stBdMax = i1stBdL * iBbCnt - i1stBdL
850
- iBdExL = 0
851
- iAll = iBsize + iPpsCnt + iSbdSize
852
- iAllW = iAll
853
- iBdCntW = iAllW / iBbCnt
854
- iBdCntW += 1 if iAllW % iBbCnt > 0
855
- iBdCnt = 0
856
- #0.1 Calculate BD count
857
- iBBleftover = iAll - i1stBdMax
858
- if iAll >i1stBdMax
859
- while true
860
- iBdCnt = iBBleftover / iBlCnt
861
- iBdCnt += 1 if iBBleftover % iBlCnt > 0
862
-
863
- iBdExL = iBdCnt / iBlCnt
864
- iBdExL += 1 if iBdCnt % iBlCnt > 0
865
- iBBleftover = iBBleftover + iBdExL
866
- break if iBdCnt == (iBBleftover / iBlCnt + ((iBBleftover % iBlCnt) > 0 ? 1: 0))
867
- end
868
- end
869
- iAllW += iBdExL
870
- iBdCnt += i1stBdL
871
- #print "iBdCnt = iBdCnt \n"
872
-
873
- #1. Making BD
874
- #1.1 Set for SBD
875
- if iSbdSize > 0
876
- 0.upto(iSbdSize-1-1) do |i|
877
- file.write([i + 1].pack('V'))
878
- end
879
- file.write([-2].pack('V'))
880
- end
881
- #1.2 Set for B
882
- 0.upto(iBsize-1-1) do |i|
883
- file.write([i + iSbdSize + 1].pack('V'))
884
- end
885
- file.write([-2].pack('V'))
886
-
887
- #1.3 Set for PPS
888
- 0.upto(iPpsCnt-1-1) do |i|
889
- file.write([i+iSbdSize+iBsize+1].pack("V"))
890
- end
891
- file.write([-2].pack('V'))
892
- #1.4 Set for BBD itself ( 0xFFFFFFFD : BBD)
893
- 0.upto(iBdCnt-1) do |i|
894
- file.write([0xFFFFFFFD].pack("V"))
895
- end
896
- #1.5 Set for ExtraBDList
897
- 0.upto(iBdExL-1) do |i|
898
- file.write([0xFFFFFFFC].pack("V"))
899
- end
900
- #1.6 Adjust for Block
901
- if (iAllW + iBdCnt) % iBbCnt > 0
902
- file.write([-1].pack('V') * (iBbCnt - ((iAllW + iBdCnt) % iBbCnt)))
903
- end
904
- #2.Extra BDList
905
- if iBdCnt > i1stBdL
906
- iN = 0
907
- iNb = 0
908
- i1stBdL.upto(iBdCnt-1) do |i|
909
- if iN >= iBbCnt-1
910
- iN = 0
911
- iNb += 1
912
- file.write([iAll+iBdCnt+iNb].pack("V"))
913
- end
914
- file.write([iBsize+iSbdSize+iPpsCnt+i].pack("V"))
915
- iN += 1
916
- end
917
- if (iBdCnt-i1stBdL) % (iBbCnt-1) > 0
918
- file.write([-1].pack("V") * ((iBbCnt-1) - ((iBdCnt-i1stBdL) % (iBbCnt-1))))
919
- end
920
- file.write([-2].pack('V'))
921
- end
922
- end
923
- end
924
-
925
- class OLEStorageLitePPSFile < OLEStorageLitePPS #:nodoc:
926
- def initialize(sNm, data = '')
927
- super(
928
- nil,
929
- sNm || '',
930
- PPS_TYPE_FILE,
931
- nil,
932
- nil,
933
- nil,
934
- nil,
935
- nil,
936
- nil,
937
- nil,
938
- data || '',
939
- nil
940
- )
941
- end
942
-
943
- def set_file(sFile = '')
944
- if sFile.nil? or sFile == ''
945
- @pps_file = Tempfile.new('OLEStorageLitePPSFile')
946
- elsif sFile.kind_of?(IO) || sFile.kind_of?(StringIO)
947
- @pps_file = sFile
948
- elsif sFile.kind_of?(String)
949
- #File Name
950
- @pps_file = open(sFile, "r+")
951
- return nil unless @pps_file
952
- else
953
- return nil
954
- end
955
- @pps_file.seek(0, IO::SEEK_END)
956
- @pps_file.binmode
957
- end
958
-
959
- def append (data)
960
- return if data.nil?
961
- if @pps_file
962
- @pps_file << data
963
- @pps_file.flush
964
- else
965
- @data << data
966
- end
967
- end
968
- end
1
+ # -*- coding: utf-8 -*-
2
+ #
3
+ # OLE::Storage_Lite
4
+ # by Kawai, Takanori (Hippo2000) 2000.11.4, 8, 14
5
+ # This Program is Still ALPHA version.
6
+ #//////////////////////////////////////////////////////////////////////////////
7
+ #
8
+ # converted from CPAN's OLE::Storage_Lite.
9
+ # converted to Ruby by Hideo Nakamura, cxn03651@msj.biglobe.ne.jp
10
+ #
11
+
12
+ require 'tempfile'
13
+ require 'stringio'
14
+
15
+ class OLEStorageLite #:nodoc:
16
+ PPS_TYPE_ROOT = 5
17
+ PPS_TYPE_DIR = 1
18
+ PPS_TYPE_FILE = 2
19
+ DATA_SIZE_SMALL = 0x1000
20
+ LONG_INT_SIZE = 4
21
+ PPS_SIZE = 0x80
22
+
23
+ attr_reader :file
24
+
25
+ def initialize(file = nil)
26
+ @file = file
27
+ end
28
+
29
+ def getPpsTree(data)
30
+ info = _initParse(file)
31
+ info ? _getPpsTree(0, info, data) : nil
32
+ end
33
+
34
+ def getPpsSearch(name, data, icase)
35
+ info = _initParse(file)
36
+ info ? _getPpsSearch(0, info, name, data, icase) : nil
37
+ end
38
+
39
+ def getNthPps(no, data)
40
+ info = _initParse(file)
41
+ info ? _getNthPps(no, info, data) : nil
42
+ end
43
+
44
+ def _initParse(file)
45
+ io = file.kind_of?(String) ? open(file, 'rb') : file
46
+ _getHeaderInfo(io)
47
+ end
48
+ private :_initParse
49
+
50
+ def _getPpsTree(no, info, data, done)
51
+ if done
52
+ return [] if done.include?(no)
53
+ else
54
+ done = []
55
+ end
56
+ done << no
57
+
58
+ rootblock = info[:root_start]
59
+
60
+ #1. Get Information about itself
61
+ pps = _getNthPps(no, info, data)
62
+
63
+ #2. Child
64
+ if pps.dir_pps != 0xFFFFFFFF
65
+ pps.child = _getPpsTree(pps.dir_pps, info, data, done)
66
+ else
67
+ pps.child = nil
68
+ end
69
+
70
+ #3. Previous,Next PPSs
71
+ list = []
72
+ list << _getPpsTree(pps.prev_pps, info, data, done) if pps.prev_pps != 0xFFFFFFFF
73
+ list << pps
74
+ list << _getPpsTree(pps.next_pps, info, data, done) if pps.next_pps != 0xFFFFFFFF
75
+ end
76
+ private :_getPpsTree
77
+
78
+ def _getPpsSearch(no, info, name, data, icase, done = nil)
79
+ rootblock = info[:root_start]
80
+ #1. Check it self
81
+ if done
82
+ return [] if done.include?(no)
83
+ else
84
+ done = []
85
+ end
86
+ done << no
87
+ pps = _getNthPps(no, info, nil)
88
+
89
+ re = Regexp.new("^\Q#{pps.name}\E$", Regexp::IGNORECASE)
90
+ if (icase && !name.select { |v| v =~ re }.empty?) || name.include?(pps.name)
91
+ pps = _getNthPps(no, info, data) if data
92
+ res = [pps]
93
+ else
94
+ res = []
95
+ end
96
+
97
+ #2. Check Child, Previous, Next PPSs
98
+ res +=
99
+ _getPpsSearch(pps.dir_pps, info, name, data, icase, done) if pps.dir_pps != 0xFFFFFFFF
100
+ res +=
101
+ _getPpsSearch(pps.prev_pps, info, name, data, icase, done) if pps.prev_pps != 0xFFFFFFFF
102
+ res +=
103
+ _getPpsSearch(pps.next_pps, info, name, data, icase, done) if pps.next_pps != 0xFFFFFFFF
104
+ res
105
+ end
106
+ private :_getPpsSearch
107
+
108
+ def _getHeaderInfo(io)
109
+ info = { :fileh => io }
110
+
111
+ #0. Check ID
112
+ info[:fileh].seek(0, 0)
113
+ return nil unless info[:fileh].read(8) == "\xD0\xCF\x11\xE0\xA1\xB1\x1A\xE1"
114
+
115
+ # BIG BLOCK SIZE
116
+ val = _getInfoFromFile(info[:fileh], 0x1E, 2, "v")
117
+ return nil if val.nil?
118
+ info[:big_block_size] = 2 ** val
119
+
120
+ # SMALL BLOCK SIZE
121
+ val = _getInfoFromFile(info[:fileh], 0x20, 2, "v")
122
+ return nil if val.nil?
123
+ info[:small_block_size] = 2 ** val
124
+
125
+ # BDB Count
126
+ val = _getInfoFromFile(info[:fileh], 0x2C, 4, "V")
127
+ return nil if val.nil?
128
+ info[:bdb_count] = val
129
+
130
+ # START BLOCK
131
+ val = _getInfoFromFile(info[:fileh], 0x30, 4, "V")
132
+ return nil if val.nil?
133
+ info[:root_start] = val
134
+
135
+ # SMALL BD START
136
+ val = _getInfoFromFile(info[:fileh], 0x3C, 4, "V")
137
+ return nil if val.nil?
138
+ info[:sbd_start] = val
139
+
140
+ # SMALL BD COUNT
141
+ val = _getInfoFromFile(info[:fileh], 0x40, 4, "V")
142
+ return nil if val.nil?
143
+ info[:sbd_count] = val
144
+
145
+ # EXTRA BBD START
146
+ val = _getInfoFromFile(info[:fileh], 0x44, 4, "V")
147
+ return nil if val.nil?
148
+ info[:extra_bbd_start] = val
149
+
150
+ # EXTRA BBD COUNT
151
+ val = _getInfoFromFile(info[:fileh], 0x48, 4, "V")
152
+ return nil if val.nil?
153
+ info[:extra_bbd_count] = val
154
+
155
+ #GET BBD INFO
156
+ info[:bbd_info] = _getBbdInfo(info)
157
+
158
+ # GET ROOT PPS
159
+ root = _getNthPps(0, info, nil)
160
+ info[:sb_start] = root.start_block
161
+ info[:sb_size] = root.size
162
+ info
163
+ end
164
+ private :_getHeaderInfo
165
+
166
+ def _getInfoFromFile(io, pos, len, fmt)
167
+ io.seek(pos, 0)
168
+ str = io.read(len)
169
+ if str.bytesize != len
170
+ nil
171
+ else
172
+ str.unpack(fmt)[0]
173
+ end
174
+ end
175
+ private :_getInfoFromFile
176
+
177
+ def _getBbdInfo(info)
178
+ bdlist = []
179
+ iBdbCnt = info[:bdb_count]
180
+ i1stCnt = (info[:big_block_size] - 0x4C) / LONG_INT_SIZE
181
+ iBdlCnt = info[:big_block_size] / LONG_INT_SIZE - 1
182
+
183
+ #1. 1st BDlist
184
+ info[:fileh].seek(0x4C, 0)
185
+ iGetCnt = iBdbCnt < i1stCnt ? iBdbCnt : i1stCnt
186
+ str = info[:fileh].read(LONG_INT_SIZE * iGetCnt)
187
+ bdlist += str.unpack("V#{iGetCnt}")
188
+ iBdbCnt -= iGetCnt
189
+
190
+ #2. Extra BDList
191
+ iBlock = info[:extra_bbd_start]
192
+ while iBdbCnt> 0 && _isNormalBlock(iBlock)
193
+ _setFilePos(iBlock, 0, info)
194
+ iGetCnt = iBdbCnt < iBdlCnt ? iBdbCnt : iBdlCnt
195
+ str = info[:fileh].read(LONG_INT_SIZE * iGetCnt)
196
+ bdlist += str.unpack("V#{iGetCnt}")
197
+ iBdbCnt -= iGetCnt
198
+ str = info[:fileh].read(LONG_INT_SIZE)
199
+ iBlock = str.unpack("V")
200
+ end
201
+
202
+ #3.Get BDs
203
+ hBd = Hash.new
204
+ iBlkNo = 0
205
+ iBdCnt = info[:big_block_size] / LONG_INT_SIZE
206
+ bdlist.each do |iBdL|
207
+ _setFilePos(iBdL, 0, info)
208
+ str = info[:fileh].read(info[:big_block_size])
209
+ arr = str.unpack("V#{iBdCnt}")
210
+ (0...iBdCnt).each do |i|
211
+ hBd[iBlkNo] = arr[i] if arr[i] != iBlkNo + 1
212
+ iBlkNo += 1
213
+ end
214
+ end
215
+ hBd
216
+ end
217
+ private :_getBbdInfo
218
+
219
+ def _getNthPps(pos, info, data)
220
+ ppsstart = info[:root_start]
221
+
222
+ basecnt = info[:big_block_size] / PPS_SIZE
223
+ ppsblock = pos / basecnt
224
+ ppspos = pos % basecnt
225
+
226
+ block = _getNthBlockNo(ppsstart, ppsblock, info)
227
+ return nil if block.nil?
228
+
229
+ _setFilePos(block, PPS_SIZE * ppspos, info)
230
+ str = info[:fileh].read(PPS_SIZE)
231
+ return nil if str.nil? || str == ''
232
+ nmsize = str[0x40, 2].unpack('v')[0]
233
+ nmsize -= 2 if nmsize > 2
234
+ nm = str[0, nmsize]
235
+ type = str[0x42, 2].unpack('C')[0]
236
+ ppsprev = str[0x44, LONG_INT_SIZE].unpack('V')[0]
237
+ ppsnext = str[0x48, LONG_INT_SIZE].unpack('V')[0]
238
+ dirpps = str[0x4C, LONG_INT_SIZE].unpack('V')[0]
239
+ time1st =
240
+ (type == PPS_TYPE_ROOT || type == PPS_TYPE_DIR) ? oleData2Local(str[0x64, 8]) : nil
241
+ time2nd =
242
+ (type == PPS_TYPE_ROOT || type == PPS_TYPE_DIR) ? oleData2Local(str[0x6C, 8]) : nil
243
+ start, size = str[0x74, 8].unpack('VV')
244
+ if data
245
+ sdata = _getData(type, start, size, info)
246
+ OLEStorageLitePPS.new(pos, nm, type, ppsprev, ppsnext, dirpps,
247
+ time1st, time2nd, start, size, sdata, nil)
248
+ else
249
+ OLEStorageLitePPS.new(pos, nm, type, ppsprev, ppsnext, dirpps,
250
+ time1st, time2nd, start, size, nil, nil)
251
+ end
252
+ end
253
+ private :_getNthPps
254
+
255
+ def _setFilePos(iBlock, iPos, info)
256
+ info[:fileh].seek((iBlock + 1) * info[:big_block_size] + iPos, 0)
257
+ end
258
+ private :_setFilePos
259
+
260
+ def _getNthBlockNo(stblock, nth, info)
261
+ inext = stblock
262
+ (0...nth).each do |i|
263
+ sv = inext
264
+ inext = _getNextBlockNo(sv, info)
265
+ return nil unless _isNormalBlock(inext)
266
+ end
267
+ inext
268
+ end
269
+ private :_getNthBlockNo
270
+
271
+ def _getData(iType, iBlock, iSize, info)
272
+ if iType == PPS_TYPE_FILE
273
+ if iSize < DATA_SIZE_SMALL
274
+ return _getSmallData(iBlock, iSize, info)
275
+ else
276
+ return _getBigData(iBlock, iSize, info)
277
+ end
278
+ elsif iType == PPS_TYPE_ROOT # Root
279
+ return _getBigData(iBlock, iSize, info)
280
+ elsif iType == PPS_TYPE_DIR # Directory
281
+ return nil
282
+ end
283
+ end
284
+ private :_getData
285
+
286
+ def _getBigData(iBlock, iSize, info)
287
+ return '' unless _isNormalBlock(iBlock)
288
+ iRest = iSize
289
+ sRes = ''
290
+ aKeys = info[:bbd_info].keys.sort
291
+
292
+ while iRest > 0
293
+ aRes = aKeys.select { |key| key >= iBlock }
294
+ iNKey = aRes[0]
295
+ i = iNKey - iBlock
296
+ iNext = info[:bbd_info][iNKey]
297
+ _setFilePos(iBlock, 0, info)
298
+ iGetSize = info[:big_block_size] * (i + 1)
299
+ iGetSize = iRest if iRest < iGetSize
300
+ sRes += info[:fileh].read(iGetSize)
301
+ iRest -= iGetSize
302
+ iBlock = iNext
303
+ end
304
+ sRes
305
+ end
306
+ private :_getBigData
307
+
308
+ def _getNextBlockNo(iBlockNo, info)
309
+ iRes = info[:bbd_info][iBlockNo]
310
+ iRes ? iRes : iBlockNo + 1
311
+ end
312
+ private :_getNextBlockNo
313
+
314
+ def _isNormalBlock(iBlock)
315
+ iBlock < 0xFFFFFFFC ? 1 : nil
316
+ end
317
+ private :_isNormalBlock
318
+
319
+ def _getSmallData(iSmBlock, iSize, info)
320
+ iRest = iSize
321
+ sRes = ''
322
+ while iRest > 0
323
+ _setFilePosSmall(iSmBlock, info)
324
+ sRes += info[:fileh].read(
325
+ iRest >= info[:small_block_size] ? info[:small_block_size] : iRest)
326
+ iRest -= info[:small_block_size]
327
+ iSmBlock = _getNextSmallBlockNo(iSmBlock, info)
328
+ end
329
+ sRes
330
+ end
331
+ private :_getSmallData
332
+
333
+ def _setFilePosSmall(iSmBlock, info)
334
+ iSmStart = info[:sb_start]
335
+ iBaseCnt = info[:big_block_size] / info[:small_block_size]
336
+ iNth = iSmBlock / iBaseCnt
337
+ iPos = iSmBlock % iBaseCnt
338
+
339
+ iBlk = _getNthBlockNo(iSmStart, iNth, info)
340
+ _setFilePos(iBlk, iPos * info[:small_block_size], info)
341
+ end
342
+ private :_setFilePosSmall
343
+
344
+ def _getNextSmallBlockNo(iSmBlock, info)
345
+ iBaseCnt = info[:big_block_size] / LONG_INT_SIZE
346
+ iNth = iSmBlock / iBaseCnt
347
+ iPos = iSmBlock % iBaseCnt
348
+ iBlk = _getNthBlockNo(info[:sbd_start], iNth, info)
349
+ _setFilePos(iBlk, iPos * LONG_INT_SIZE, info)
350
+ info[:fileh].read(LONG_INT_SIZE).unpack('V')
351
+ end
352
+ private :_getNextSmallBlockNo
353
+
354
+ def asc2ucs(str)
355
+ str.split(//).join("\0") + "\0"
356
+ end
357
+
358
+ def ucs2asc(str)
359
+ ary = str.unpack('v*').map { |s| [s].pack('c')}
360
+ ary.join('')
361
+ end
362
+
363
+ #------------------------------------------------------------------------------
364
+ # OLEDate2Local()
365
+ #
366
+ # Convert from a Window FILETIME structure to a localtime array. FILETIME is
367
+ # a 64-bit value representing the number of 100-nanosecond intervals since
368
+ # January 1 1601.
369
+ #
370
+ # We first convert the FILETIME to seconds and then subtract the difference
371
+ # between the 1601 epoch and the 1970 Unix epoch.
372
+ #
373
+ def oleData2Local(oletime)
374
+ # Unpack the FILETIME into high and low longs.
375
+ lo, hi = oletime.unpack('V2')
376
+
377
+ # Convert the longs to a double.
378
+ nanoseconds = hi * 2 ** 32 + lo
379
+
380
+ # Convert the 100 nanosecond units into seconds.
381
+ time = nanoseconds / 1e7
382
+
383
+ # Subtract the number of seconds between the 1601 and 1970 epochs.
384
+ time -= 11644473600
385
+
386
+ # Convert to a localtime (actually gmtime) structure.
387
+ if time >= 1
388
+ ltime = Time.at(time).getgm.to_a[0, 9]
389
+ ltime[4] -= 1 # month
390
+ ltime[5] -= 1900 # year
391
+ ltime[7] -= 1 # past from 1, Jan
392
+ ltime[8] = ltime[8] ? 1 : 0
393
+ ltime
394
+ else
395
+ []
396
+ end
397
+ end
398
+
399
+ #------------------------------------------------------------------------------
400
+ # LocalDate2OLE()
401
+ #
402
+ # Convert from a a localtime array to a Window FILETIME structure. FILETIME is
403
+ # a 64-bit value representing the number of 100-nanosecond intervals since
404
+ # January 1 1601.
405
+ #
406
+ # We first convert the localtime (actually gmtime) to seconds and then add the
407
+ # difference between the 1601 epoch and the 1970 Unix epoch. We convert that to
408
+ # 100 nanosecond units, divide it into high and low longs and return it as a
409
+ # packed 64bit structure.
410
+ #
411
+ def localDate2OLE(localtime)
412
+ return "\x00" * 8 unless localtime
413
+
414
+ # Convert from localtime (actually gmtime) to seconds.
415
+ args = localtime.reverse
416
+ args[0] += 1900 # year
417
+ args[1] += 1 # month
418
+ time = Time.gm(*args)
419
+
420
+ # Add the number of seconds between the 1601 and 1970 epochs.
421
+ time = time.to_i + 11644473600
422
+
423
+ # The FILETIME seconds are in units of 100 nanoseconds.
424
+ nanoseconds = time * 10000000
425
+
426
+ # Pack the total nanoseconds into 64 bits...
427
+ hi, lo = nanoseconds.divmod 1 << 32
428
+
429
+ [lo, hi].pack("VV") # oletime
430
+ end
431
+ end
432
+
433
+ class OLEStorageLitePPS < OLEStorageLite #:nodoc:
434
+ attr_accessor :no, :name, :type, :prev_pps, :next_pps, :dir_pps
435
+ attr_accessor :time_1st, :time_2nd, :start_block, :size, :data, :child
436
+ attr_reader :pps_file
437
+
438
+ def initialize(iNo, sNm, iType, iPrev, iNext, iDir,
439
+ raTime1st, raTime2nd, iStart, iSize, sData, raChild)
440
+ @no = iNo
441
+ @name = sNm
442
+ @type = iType
443
+ @prev_pps = iPrev
444
+ @next_pps = iNext
445
+ @dir_pps = iDir
446
+ @time_1st = raTime1st
447
+ @time_2nd = raTime2nd
448
+ @start_block = iStart
449
+ @size = iSize
450
+ @data = sData
451
+ @child = raChild
452
+ @pps_file = nil
453
+ end
454
+
455
+ def _datalen
456
+ return 0 if @data.nil?
457
+ if @pps_file
458
+ return @pps_file.lstat.size
459
+ else
460
+ return @data.bytesize
461
+ end
462
+ end
463
+ protected :_datalen
464
+
465
+ def _makeSmallData(aList, rh_info)
466
+ file = rh_info[:fileh]
467
+ iSmBlk = 0
468
+ sRes = ''
469
+
470
+ aList.each do |pps|
471
+ #1. Make SBD, small data string
472
+ if pps.type == PPS_TYPE_FILE
473
+ next if pps.size <= 0
474
+ if pps.size < rh_info[:small_size]
475
+ iSmbCnt = pps.size / rh_info[:small_block_size]
476
+ iSmbCnt += 1 if pps.size % rh_info[:small_block_size] > 0
477
+ #1.1 Add to SBD
478
+ 0.upto(iSmbCnt-1-1) do |i|
479
+ file.write([i + iSmBlk+1].pack("V"))
480
+ end
481
+ file.write([-2].pack("V"))
482
+
483
+ #1.2 Add to Data String(this will be written for RootEntry)
484
+ #Check for update
485
+ if pps.pps_file
486
+ pps.pps_file.seek(0) #To The Top
487
+ while sBuff = pps.pps_file.read(4096)
488
+ sRes << sBuff
489
+ end
490
+ else
491
+ sRes << pps.data
492
+ end
493
+ if pps.size % rh_info[:small_block_size] > 0
494
+ cnt = rh_info[:small_block_size] - (pps.size % rh_info[:small_block_size])
495
+ sRes << "\0" * cnt
496
+ end
497
+ #1.3 Set for PPS
498
+ pps.start_block = iSmBlk
499
+ iSmBlk += iSmbCnt
500
+ end
501
+ end
502
+ end
503
+ iSbCnt = rh_info[:big_block_size] / LONG_INT_SIZE
504
+ file.write([-1].pack("V") * (iSbCnt - (iSmBlk % iSbCnt))) if iSmBlk % iSbCnt > 0
505
+ #2. Write SBD with adjusting length for block
506
+ sRes
507
+ end
508
+ private :_makeSmallData
509
+
510
+ def _savePpsWk(rh_info)
511
+ #1. Write PPS
512
+ file = rh_info[:fileh]
513
+ file.write( [
514
+ @name,
515
+ ("\x00" * (64 - @name.bytesize)), #64
516
+ [@name.bytesize + 2].pack("v"), #66
517
+ [@type].pack("c"), #67
518
+ [0x00].pack("c"), #UK #68
519
+ [@prev_pps].pack("V"), #Prev #72
520
+ [@next_pps].pack("V"), #Next #76
521
+ [@dir_pps].pack("V"), #Dir #80
522
+ "\x00\x09\x02\x00", #84
523
+ "\x00\x00\x00\x00", #88
524
+ "\xc0\x00\x00\x00", #92
525
+ "\x00\x00\x00\x46", #96
526
+ "\x00\x00\x00\x00", #100
527
+ localDate2OLE(@time_1st), #108
528
+ localDate2OLE(@time_2nd) #116
529
+ ].collect { |d| d.force_encoding(Encoding::BINARY) }.join('')
530
+ )
531
+ if @start_block != 0
532
+ file.write([@start_block].pack('V'))
533
+ else
534
+ file.write([0].pack('V'))
535
+ end
536
+ if @size != 0 #124
537
+ file.write([@size].pack('V'))
538
+ else
539
+ file.write([0].pack('V'))
540
+ end
541
+ file.write([0].pack('V')) #128
542
+ end
543
+ protected :_savePpsWk
544
+ end
545
+
546
+ class OLEStorageLitePPSRoot < OLEStorageLitePPS #:nodoc:
547
+ def initialize(raTime1st, raTime2nd, raChild)
548
+ super(
549
+ nil,
550
+ asc2ucs('Root Entry'),
551
+ PPS_TYPE_ROOT,
552
+ nil,
553
+ nil,
554
+ nil,
555
+ raTime1st,
556
+ raTime2nd,
557
+ nil,
558
+ nil,
559
+ nil,
560
+ raChild)
561
+ end
562
+
563
+ def save(sFile, bNoAs = nil, rh_info = nil)
564
+ #0.Initial Setting for saving
565
+ rh_info = Hash.new unless rh_info
566
+ if rh_info[:big_block_size]
567
+ rh_info[:big_block_size] = 2 ** adjust2(rh_info[:big_block_size])
568
+ else
569
+ rh_info[:big_block_size] = 2 ** 9
570
+ end
571
+ if rh_info[:small_block_size]
572
+ rh_info[:small_block_size] = 2 ** adjust2(rh_info[:small_block_size])
573
+ else
574
+ rh_info[:small_block_size] = 2 ** 6
575
+ end
576
+ rh_info[:small_size] = 0x1000
577
+ rh_info[:pps_size] = 0x80
578
+
579
+ close_file = true
580
+
581
+ #1.Open File
582
+ #1.1 sFile is Ref of scalar
583
+ if sFile.kind_of?(String)
584
+ rh_info[:fileh] = open(sFile, "wb")
585
+ else
586
+ rh_info[:fileh] = sFile.binmode
587
+ end
588
+
589
+ iBlk = 0
590
+ #1. Make an array of PPS (for Save)
591
+ aList=[]
592
+ if bNoAs
593
+ _savePpsSetPnt2([self], aList, rh_info)
594
+ else
595
+ _savePpsSetPnt([self], aList, rh_info)
596
+ end
597
+ iSBDcnt, iBBcnt, iPPScnt = _calcSize(aList, rh_info)
598
+
599
+ #2.Save Header
600
+ _saveHeader(rh_info, iSBDcnt, iBBcnt, iPPScnt)
601
+
602
+ #3.Make Small Data string (write SBD)
603
+ # Small Datas become RootEntry Data
604
+ @data = _makeSmallData(aList, rh_info)
605
+
606
+ #4. Write BB
607
+ iBBlk = iSBDcnt
608
+ _saveBigData(iBBlk, aList, rh_info)
609
+
610
+ #5. Write PPS
611
+ _savePps(aList, rh_info)
612
+
613
+ #6. Write BD and BDList and Adding Header informations
614
+ _saveBbd(iSBDcnt, iBBcnt, iPPScnt, rh_info)
615
+
616
+ #7.Close File
617
+ rh_info[:fileh].close if close_file
618
+ end
619
+
620
+ def _calcSize(aList, rh_info)
621
+ #0. Calculate Basic Setting
622
+ iSBDcnt, iBBcnt, iPPScnt = [0,0,0]
623
+ iSmallLen = 0
624
+ iSBcnt = 0
625
+ aList.each do |pps|
626
+ if pps.type == PPS_TYPE_FILE
627
+ pps.size = pps._datalen #Mod
628
+ if pps.size < rh_info[:small_size]
629
+ iSBcnt += pps.size / rh_info[:small_block_size]
630
+ iSBcnt += 1 if pps.size % rh_info[:small_block_size] > 0
631
+ else
632
+ iBBcnt += pps.size / rh_info[:big_block_size]
633
+ iBBcnt += 1 if pps.size % rh_info[:big_block_size] > 0
634
+ end
635
+ end
636
+ end
637
+ iSmallLen = iSBcnt * rh_info[:small_block_size]
638
+ iSlCnt = rh_info[:big_block_size] / LONG_INT_SIZE
639
+ iSBDcnt = iSBcnt / iSlCnt
640
+ iSBDcnt += 1 if iSBcnt % iSlCnt > 0
641
+ iBBcnt += iSmallLen / rh_info[:big_block_size]
642
+ iBBcnt += 1 if iSmallLen % rh_info[:big_block_size] > 0
643
+ iCnt = aList.size
644
+ iBdCnt = rh_info[:big_block_size] / PPS_SIZE
645
+ iPPScnt = iCnt / iBdCnt
646
+ iPPScnt += 1 if iCnt % iBdCnt > 0
647
+ [iSBDcnt, iBBcnt, iPPScnt]
648
+ end
649
+ private :_calcSize
650
+
651
+ def _adjust2(i2)
652
+ iWk = Math.log(i2)/Math.log(2)
653
+ iWk > Integer(iWk) ? Integer(iWk) + 1 : iWk
654
+ end
655
+ private :_adjust2
656
+
657
+ def _saveHeader(rh_info, iSBDcnt, iBBcnt, iPPScnt)
658
+ file = rh_info[:fileh]
659
+
660
+ #0. Calculate Basic Setting
661
+ iBlCnt = rh_info[:big_block_size] / LONG_INT_SIZE
662
+ i1stBdL = (rh_info[:big_block_size] - 0x4C) / LONG_INT_SIZE
663
+ i1stBdMax = i1stBdL * iBlCnt - i1stBdL
664
+ iBdExL = 0
665
+ iAll = iBBcnt + iPPScnt + iSBDcnt
666
+ iAllW = iAll
667
+ iBdCntW = iAllW / iBlCnt
668
+ iBdCntW += 1 if iAllW % iBlCnt > 0
669
+ iBdCnt = 0
670
+ #0.1 Calculate BD count
671
+ iBlCnt -= 1 #the BlCnt is reduced in the count of the last sect is used for a pointer the next Bl
672
+ iBBleftover = iAll - i1stBdMax
673
+ if iAll >i1stBdMax
674
+ while true
675
+ iBdCnt = iBBleftover / iBlCnt
676
+ iBdCnt += 1 if iBBleftover % iBlCnt > 0
677
+ iBdExL = iBdCnt / iBlCnt
678
+ iBdExL += 1 if iBdCnt % iBlCnt > 0
679
+ iBBleftover += iBdExL
680
+ break if iBdCnt == iBBleftover / iBlCnt + (iBBleftover % iBlCnt > 0 ? 1 : 0)
681
+ end
682
+ end
683
+ iBdCnt += i1stBdL
684
+ #print "iBdCnt = iBdCnt \n"
685
+
686
+ #1.Save Header
687
+ file.write( [
688
+ "\xD0\xCF\x11\xE0\xA1\xB1\x1A\xE1",
689
+ "\x00\x00\x00\x00" * 4,
690
+ [0x3b].pack("v"),
691
+ [0x03].pack("v"),
692
+ [-2].pack("v"),
693
+ [9].pack("v"),
694
+ [6].pack("v"),
695
+ [0].pack("v"),
696
+ "\x00\x00\x00\x00" * 2,
697
+ [iBdCnt].pack("V"),
698
+ [iBBcnt+iSBDcnt].pack("V"), #ROOT START
699
+ [0].pack("V"),
700
+ [0x1000].pack("V"),
701
+ [0].pack("V"), #Small Block Depot
702
+ [1].pack("V")
703
+ ].collect { |d| d.force_encoding(Encoding::BINARY) }.join('')
704
+ )
705
+ #2. Extra BDList Start, Count
706
+ if iAll <= i1stBdMax
707
+ file.write(
708
+ [-2].pack("V") + #Extra BDList Start
709
+ [0].pack("V") #Extra BDList Count
710
+ )
711
+ else
712
+ file.write(
713
+ [iAll + iBdCnt].pack("V") +
714
+ [iBdExL].pack("V")
715
+ )
716
+ end
717
+
718
+ #3. BDList
719
+ cnt = i1stBdL
720
+ cnt = iBdCnt if iBdCnt < i1stBdL
721
+ 0.upto(cnt-1) do |i|
722
+ file.write([iAll + i].pack("V"))
723
+ end
724
+ file.write([-1].pack("V") * (i1stBdL - cnt)) if cnt < i1stBdL
725
+ end
726
+ private :_saveHeader
727
+
728
+ def _saveBigData(iStBlk, aList, rh_info)
729
+ iRes = 0
730
+ file = rh_info[:fileh]
731
+
732
+ #1.Write Big (ge 0x1000) Data into Block
733
+ aList.each do |pps|
734
+ if pps.type != PPS_TYPE_DIR
735
+ #print "PPS: pps DEF:", defined(pps->{Data}), "\n"
736
+ pps.size = pps._datalen #Mod
737
+ if (pps.size >= rh_info[:small_size]) ||
738
+ ((pps.type == PPS_TYPE_ROOT) && !pps.data.nil?)
739
+ #1.1 Write Data
740
+ #Check for update
741
+ if pps.pps_file
742
+ iLen = 0
743
+ pps.pps_file.seek(0, 0) #To The Top
744
+ while sBuff = pps.pps_file.read(4096)
745
+ iLen += sBuff.bytesize
746
+ file.write(sBuff) #Check for update
747
+ end
748
+ else
749
+ file.write(pps.data)
750
+ end
751
+ if pps.size % rh_info[:big_block_size] > 0
752
+ file.write(
753
+ "\x00" *
754
+ (rh_info[:big_block_size] -
755
+ (pps.size % rh_info[:big_block_size]))
756
+ )
757
+ end
758
+ #1.2 Set For PPS
759
+ pps.start_block = iStBlk
760
+ iStBlk += pps.size / rh_info[:big_block_size]
761
+ iStBlk += 1 if pps.size % rh_info[:big_block_size] > 0
762
+ end
763
+ end
764
+ end
765
+ end
766
+
767
+ def _savePps(aList, rh_info)
768
+ #0. Initial
769
+ file = rh_info[:fileh]
770
+ #2. Save PPS
771
+ aList.each do |oItem|
772
+ oItem._savePpsWk(rh_info)
773
+ end
774
+ #3. Adjust for Block
775
+ iCnt = aList.size
776
+ iBCnt = rh_info[:big_block_size] / rh_info[:pps_size]
777
+ if iCnt % iBCnt > 0
778
+ file.write("\x00" * ((iBCnt - (iCnt % iBCnt)) * rh_info[:pps_size]))
779
+ end
780
+ (iCnt / iBCnt) + ((iCnt % iBCnt) > 0 ? 1: 0)
781
+ end
782
+ private :_savePps
783
+
784
+ def _savePpsSetPnt(pps_array, aList, rh_info)
785
+ #1. make Array as Children-Relations
786
+ #1.1 if No Children
787
+ bpp=1
788
+ if pps_array.nil? || pps_array.size == 0
789
+ return 0xFFFFFFFF
790
+ #1.2 Just Only one
791
+ elsif pps_array.size == 1
792
+ aList << pps_array[0]
793
+ pps_array[0].no = aList.size - 1
794
+ pps_array[0].prev_pps = 0xFFFFFFFF
795
+ pps_array[0].next_pps = 0xFFFFFFFF
796
+ pps_array[0].dir_pps = _savePpsSetPnt(pps_array[0].child, aList, rh_info)
797
+ return pps_array[0].no
798
+ #1.3 Array
799
+ else
800
+ iCnt = pps_array.size
801
+ #1.3.1 Define Center
802
+ iPos = Integer(iCnt / 2.0) #$iCnt
803
+
804
+ aList.push(pps_array[iPos])
805
+ pps_array[iPos].no = aList.size - 1
806
+
807
+ aWk = pps_array.dup
808
+ #1.3.2 Devide a array into Previous,Next
809
+ aPrev = aWk[0, iPos]
810
+ aWk[0..iPos-1] = nil
811
+ aNext = aWk[1, iCnt - iPos - 1]
812
+ aWk[1..(1 + iCnt - iPos -1 -1)] = nil
813
+ pps_array[iPos].prev_pps = _savePpsSetPnt(aPrev, aList, rh_info)
814
+ pps_array[iPos].next_pps = _savePpsSetPnt(aNext, aList, rh_info)
815
+ pps_array[iPos].dir_pps = _savePpsSetPnt(pps_array[iPos].child, aList, rh_info)
816
+ return pps_array[iPos].no
817
+ end
818
+ end
819
+ private :_savePpsSetPnt
820
+
821
+ def _savePpsSetPnt2(pps_array, aList, rh_info)
822
+ #1. make Array as Children-Relations
823
+ #1.1 if No Children
824
+ if pps_array.nil? || pps_array.size == 0
825
+ return 0xFFFFFFFF
826
+ #1.2 Just Only one
827
+ elsif pps_array.size == 1
828
+ aList << pps_array[0]
829
+ pps_array[0].no = aList.size - 1
830
+ pps_array[0].prev_pps = 0xFFFFFFFF
831
+ pps_array[0].next_pps = 0xFFFFFFFF
832
+ pps_array[0].dir_pps = _savePpsSetPnt2(pps_array[0].child, aList, rh_info)
833
+ return pps_array[0].no
834
+ #1.3 Array
835
+ else
836
+ iCnt = pps_array.size
837
+ #1.3.1 Define Center
838
+ iPos = 0 #int($iCnt/ 2); #$iCnt
839
+
840
+ aWk = pps_array.dup
841
+ aPrev = aWk[1, 1]
842
+ aWk[1..1] = nil
843
+ aNext = aWk[1..aWk.size] #, $iCnt - $iPos -1);
844
+ pps_array[iPos].prev_pps = _savePpsSetPnt2(pps_array, aList, rh_info)
845
+ aList.push(pps_array[iPos])
846
+ pps_array[iPos].no = aList.size
847
+
848
+ #1.3.2 Devide a array into Previous,Next
849
+ pps_array[iPos].next_pps = _savePpsSetPnt2(aNext, aList, rh_info)
850
+ pps_array[iPos].dir_pps = _savePpsSetPnt2(pps_array[iPos].child, aList, rh_info)
851
+ return pps_array[iPos].no
852
+ end
853
+ end
854
+ private :_savePpsSetPnt2
855
+
856
+ def _saveBbd(iSbdSize, iBsize, iPpsCnt, rh_info)
857
+ file = rh_info[:fileh]
858
+ #0. Calculate Basic Setting
859
+ iBbCnt = rh_info[:big_block_size] / LONG_INT_SIZE
860
+ iBlCnt = iBbCnt - 1
861
+ i1stBdL = (rh_info[:big_block_size] - 0x4C) / LONG_INT_SIZE
862
+ i1stBdMax = i1stBdL * iBbCnt - i1stBdL
863
+ iBdExL = 0
864
+ iAll = iBsize + iPpsCnt + iSbdSize
865
+ iAllW = iAll
866
+ iBdCntW = iAllW / iBbCnt
867
+ iBdCntW += 1 if iAllW % iBbCnt > 0
868
+ iBdCnt = 0
869
+ #0.1 Calculate BD count
870
+ iBBleftover = iAll - i1stBdMax
871
+ if iAll >i1stBdMax
872
+ while true
873
+ iBdCnt = iBBleftover / iBlCnt
874
+ iBdCnt += 1 if iBBleftover % iBlCnt > 0
875
+
876
+ iBdExL = iBdCnt / iBlCnt
877
+ iBdExL += 1 if iBdCnt % iBlCnt > 0
878
+ iBBleftover += iBdExL
879
+ break if iBdCnt == (iBBleftover / iBlCnt + ((iBBleftover % iBlCnt) > 0 ? 1: 0))
880
+ end
881
+ end
882
+ iAllW += iBdExL
883
+ iBdCnt += i1stBdL
884
+ #print "iBdCnt = iBdCnt \n"
885
+
886
+ #1. Making BD
887
+ #1.1 Set for SBD
888
+ if iSbdSize > 0
889
+ 0.upto(iSbdSize-1-1) do |i|
890
+ file.write([i + 1].pack('V'))
891
+ end
892
+ file.write([-2].pack('V'))
893
+ end
894
+ #1.2 Set for B
895
+ 0.upto(iBsize-1-1) do |i|
896
+ file.write([i + iSbdSize + 1].pack('V'))
897
+ end
898
+ file.write([-2].pack('V'))
899
+
900
+ #1.3 Set for PPS
901
+ 0.upto(iPpsCnt-1-1) do |i|
902
+ file.write([i+iSbdSize+iBsize+1].pack("V"))
903
+ end
904
+ file.write([-2].pack('V'))
905
+ #1.4 Set for BBD itself ( 0xFFFFFFFD : BBD)
906
+ 0.upto(iBdCnt-1) do |i|
907
+ file.write([0xFFFFFFFD].pack("V"))
908
+ end
909
+ #1.5 Set for ExtraBDList
910
+ 0.upto(iBdExL-1) do |i|
911
+ file.write([0xFFFFFFFC].pack("V"))
912
+ end
913
+ #1.6 Adjust for Block
914
+ if (iAllW + iBdCnt) % iBbCnt > 0
915
+ file.write([-1].pack('V') * (iBbCnt - ((iAllW + iBdCnt) % iBbCnt)))
916
+ end
917
+ #2.Extra BDList
918
+ if iBdCnt > i1stBdL
919
+ iN = 0
920
+ iNb = 0
921
+ i1stBdL.upto(iBdCnt-1) do |i|
922
+ if iN >= iBbCnt-1
923
+ iN = 0
924
+ iNb += 1
925
+ file.write([iAll+iBdCnt+iNb].pack("V"))
926
+ end
927
+ file.write([iBsize+iSbdSize+iPpsCnt+i].pack("V"))
928
+ iN += 1
929
+ end
930
+ if (iBdCnt-i1stBdL) % (iBbCnt-1) > 0
931
+ file.write([-1].pack("V") * ((iBbCnt-1) - ((iBdCnt-i1stBdL) % (iBbCnt-1))))
932
+ end
933
+ file.write([-2].pack('V'))
934
+ end
935
+ end
936
+ end
937
+
938
+ class OLEStorageLitePPSFile < OLEStorageLitePPS #:nodoc:
939
+ def initialize(sNm, data = '')
940
+ super(
941
+ nil,
942
+ sNm || '',
943
+ PPS_TYPE_FILE,
944
+ nil,
945
+ nil,
946
+ nil,
947
+ nil,
948
+ nil,
949
+ nil,
950
+ nil,
951
+ data || '',
952
+ nil
953
+ )
954
+ end
955
+
956
+ def set_file(sFile = '')
957
+ if sFile.nil? or sFile == ''
958
+ @pps_file = Tempfile.new('OLEStorageLitePPSFile')
959
+ elsif sFile.kind_of?(IO) || sFile.kind_of?(StringIO)
960
+ @pps_file = sFile
961
+ elsif sFile.kind_of?(String)
962
+ #File Name
963
+ @pps_file = open(sFile, "r+")
964
+ return nil unless @pps_file
965
+ else
966
+ return nil
967
+ end
968
+ @pps_file.seek(0, IO::SEEK_END)
969
+ @pps_file.binmode
970
+ end
971
+
972
+ def append (data)
973
+ return if data.nil?
974
+ if @pps_file
975
+ @pps_file << data
976
+ @pps_file.flush
977
+ else
978
+ @data << data
979
+ end
980
+ end
981
+ end