writeexcel 0.1.0 → 0.3.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 (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