writeexcel 0.1.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (143) hide show
  1. data/README +26 -31
  2. data/examples/a_simple.rb +42 -42
  3. data/examples/{autofilters.rb → autofilter.rb} +264 -266
  4. data/examples/bigfile.rb +29 -0
  5. data/examples/chart_area.rb +120 -0
  6. data/examples/chart_bar.rb +119 -0
  7. data/examples/chart_column.rb +119 -0
  8. data/examples/chart_line.rb +119 -0
  9. data/examples/chart_pie.rb +107 -0
  10. data/examples/chart_scatter.rb +120 -0
  11. data/examples/chart_stock.rb +147 -0
  12. data/examples/copyformat.rb +51 -51
  13. data/examples/data_validate.rb +278 -278
  14. data/examples/date_time.rb +86 -86
  15. data/examples/defined_name.rb +31 -0
  16. data/examples/demo.rb +120 -118
  17. data/examples/diag_border.rb +35 -35
  18. data/examples/formats.rb +489 -489
  19. data/examples/header.rb +136 -136
  20. data/examples/hidden.rb +28 -28
  21. data/examples/hyperlink.rb +42 -42
  22. data/examples/images.rb +52 -52
  23. data/examples/merge1.rb +39 -39
  24. data/examples/merge2.rb +44 -44
  25. data/examples/merge3.rb +65 -65
  26. data/examples/merge4.rb +82 -82
  27. data/examples/merge5.rb +79 -79
  28. data/examples/properties.rb +33 -0
  29. data/examples/properties_jp.rb +32 -0
  30. data/examples/protection.rb +46 -46
  31. data/examples/regions.rb +52 -52
  32. data/examples/repeat.rb +42 -42
  33. data/examples/stats.rb +75 -75
  34. data/examples/stocks.rb +80 -80
  35. data/examples/tab_colors.rb +30 -30
  36. data/examples/write_arrays.rb +82 -0
  37. data/lib/writeexcel.rb +1134 -18
  38. data/lib/writeexcel/biffwriter.rb +273 -260
  39. data/lib/writeexcel/chart.rb +2306 -217
  40. data/lib/writeexcel/charts/area.rb +152 -0
  41. data/lib/writeexcel/charts/bar.rb +177 -0
  42. data/lib/writeexcel/charts/column.rb +156 -0
  43. data/lib/writeexcel/charts/external.rb +61 -0
  44. data/lib/writeexcel/charts/line.rb +152 -0
  45. data/lib/writeexcel/charts/pie.rb +169 -0
  46. data/lib/writeexcel/charts/scatter.rb +192 -0
  47. data/lib/writeexcel/charts/stock.rb +211 -0
  48. data/lib/writeexcel/excelformulaparser.rb +208 -195
  49. data/lib/writeexcel/format.rb +1697 -1108
  50. data/lib/writeexcel/formula.rb +1050 -986
  51. data/lib/writeexcel/olewriter.rb +322 -322
  52. data/lib/writeexcel/properties.rb +251 -250
  53. data/lib/writeexcel/storage_lite.rb +968 -0
  54. data/lib/writeexcel/workbook.rb +3294 -2630
  55. data/lib/writeexcel/worksheet.rb +9012 -6377
  56. data/test/excelfile/Chart1.xls +0 -0
  57. data/test/excelfile/Chart2.xls +0 -0
  58. data/test/excelfile/Chart3.xls +0 -0
  59. data/test/excelfile/Chart4.xls +0 -0
  60. data/test/excelfile/Chart5.xls +0 -0
  61. data/test/perl_output/Chart1.xls.data +0 -0
  62. data/test/perl_output/Chart2.xls.data +0 -0
  63. data/test/perl_output/Chart3.xls.data +0 -0
  64. data/test/perl_output/Chart4.xls.data +0 -0
  65. data/test/perl_output/Chart5.xls.data +0 -0
  66. data/test/perl_output/a_simple.xls +0 -0
  67. data/test/perl_output/autofilter.xls +0 -0
  68. data/test/perl_output/chart_area.xls +0 -0
  69. data/test/perl_output/chart_bar.xls +0 -0
  70. data/test/perl_output/chart_column.xls +0 -0
  71. data/test/perl_output/chart_line.xls +0 -0
  72. data/test/perl_output/data_validate.xls +0 -0
  73. data/test/perl_output/date_time.xls +0 -0
  74. data/test/perl_output/demo.xls +0 -0
  75. data/test/perl_output/demo101.bin +0 -0
  76. data/test/perl_output/demo201.bin +0 -0
  77. data/test/perl_output/demo301.bin +0 -0
  78. data/test/perl_output/demo401.bin +0 -0
  79. data/test/perl_output/demo501.bin +0 -0
  80. data/test/perl_output/diag_border.xls +0 -0
  81. data/test/perl_output/headers.xls +0 -0
  82. data/test/perl_output/hyperlink.xls +0 -0
  83. data/test/perl_output/images.xls +0 -0
  84. data/test/perl_output/merge1.xls +0 -0
  85. data/test/perl_output/merge2.xls +0 -0
  86. data/test/perl_output/merge3.xls +0 -0
  87. data/test/perl_output/merge4.xls +0 -0
  88. data/test/perl_output/merge5.xls +0 -0
  89. data/test/perl_output/protection.xls +0 -0
  90. data/test/perl_output/regions.xls +0 -0
  91. data/test/perl_output/stats.xls +0 -0
  92. data/test/perl_output/stocks.xls +0 -0
  93. data/test/perl_output/tab_colors.xls +0 -0
  94. data/test/perl_output/unicode_cyrillic.xls +0 -0
  95. data/test/perl_output/workbook1.xls +0 -0
  96. data/test/perl_output/workbook2.xls +0 -0
  97. data/test/tc_all.rb +32 -31
  98. data/test/tc_biff.rb +104 -104
  99. data/test/tc_chart.rb +22 -22
  100. data/test/tc_example_match.rb +1944 -1280
  101. data/test/tc_format.rb +1254 -1267
  102. data/test/tc_formula.rb +63 -63
  103. data/test/tc_ole.rb +110 -110
  104. data/test/tc_storage_lite.rb +149 -0
  105. data/test/tc_workbook.rb +140 -115
  106. data/test/tc_worksheet.rb +115 -115
  107. data/test/test_00_IEEE_double.rb +14 -14
  108. data/test/test_01_add_worksheet.rb +12 -12
  109. data/test/test_02_merge_formats.rb +58 -58
  110. data/test/test_04_dimensions.rb +397 -397
  111. data/test/test_05_rows.rb +182 -182
  112. data/test/test_06_extsst.rb +80 -80
  113. data/test/test_11_date_time.rb +484 -484
  114. data/test/test_12_date_only.rb +506 -506
  115. data/test/test_13_date_seconds.rb +486 -486
  116. data/test/test_21_escher.rb +642 -629
  117. data/test/test_22_mso_drawing_group.rb +750 -739
  118. data/test/test_23_note.rb +78 -78
  119. data/test/test_24_txo.rb +80 -80
  120. data/test/test_25_position_object.rb +82 -0
  121. data/test/test_26_autofilter.rb +327 -327
  122. data/test/test_27_autofilter.rb +144 -144
  123. data/test/test_28_autofilter.rb +174 -174
  124. data/test/test_29_process_jpg.rb +681 -131
  125. data/test/test_30_validation_dval.rb +82 -82
  126. data/test/test_31_validation_dv_strings.rb +131 -131
  127. data/test/test_32_validation_dv_formula.rb +211 -211
  128. data/test/test_40_property_types.rb +191 -191
  129. data/test/test_41_properties.rb +238 -238
  130. data/test/test_42_set_properties.rb +442 -419
  131. data/test/test_50_name_stored.rb +305 -0
  132. data/test/test_51_name_print_area.rb +363 -0
  133. data/test/test_52_name_print_titles.rb +460 -0
  134. data/test/test_53_autofilter.rb +209 -0
  135. data/test/test_60_chart_generic.rb +576 -0
  136. data/test/test_61_chart_subclasses.rb +97 -0
  137. data/test/test_62_chart_formats.rb +270 -0
  138. data/test/test_63_chart_area_formats.rb +647 -0
  139. data/test/test_chartex.rb +35 -0
  140. data/test/ts_all.rb +46 -34
  141. data/writeexcel.gemspec +18 -0
  142. data/writeexcel.rdoc +583 -0
  143. metadata +162 -108
@@ -0,0 +1,968 @@
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