WriteExcel 0.2.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.
- data/.document +5 -0
- data/.gitignore +21 -0
- data/LICENSE +20 -0
- data/README.rdoc +17 -0
- data/Rakefile +47 -0
- data/VERSION +1 -0
- data/examples/a_simple.rb +42 -0
- data/examples/autofilters.rb +266 -0
- data/examples/bigfile.rb +30 -0
- data/examples/copyformat.rb +51 -0
- data/examples/data_validate.rb +278 -0
- data/examples/date_time.rb +86 -0
- data/examples/demo.rb +118 -0
- data/examples/diag_border.rb +35 -0
- data/examples/formats.rb +489 -0
- data/examples/header.rb +136 -0
- data/examples/hidden.rb +28 -0
- data/examples/hyperlink.rb +42 -0
- data/examples/images.rb +52 -0
- data/examples/merge1.rb +39 -0
- data/examples/merge2.rb +44 -0
- data/examples/merge3.rb +65 -0
- data/examples/merge4.rb +82 -0
- data/examples/merge5.rb +79 -0
- data/examples/protection.rb +46 -0
- data/examples/regions.rb +52 -0
- data/examples/repeat.rb +42 -0
- data/examples/stats.rb +75 -0
- data/examples/stocks.rb +80 -0
- data/examples/tab_colors.rb +30 -0
- data/lib/WriteExcel.rb +30 -0
- data/lib/WriteExcel/biffwriter.rb +259 -0
- data/lib/WriteExcel/chart.rb +217 -0
- data/lib/WriteExcel/excelformula.y +138 -0
- data/lib/WriteExcel/excelformulaparser.rb +573 -0
- data/lib/WriteExcel/format.rb +1108 -0
- data/lib/WriteExcel/formula.rb +986 -0
- data/lib/WriteExcel/olewriter.rb +322 -0
- data/lib/WriteExcel/properties.rb +250 -0
- data/lib/WriteExcel/storage_lite.rb +590 -0
- data/lib/WriteExcel/workbook.rb +2602 -0
- data/lib/WriteExcel/worksheet.rb +6378 -0
- data/spec/WriteExcel_spec.rb +7 -0
- data/spec/spec.opts +1 -0
- data/spec/spec_helper.rb +9 -0
- data/test/tc_all.rb +31 -0
- data/test/tc_biff.rb +104 -0
- data/test/tc_chart.rb +22 -0
- data/test/tc_example_match.rb +1280 -0
- data/test/tc_format.rb +1264 -0
- data/test/tc_formula.rb +63 -0
- data/test/tc_ole.rb +110 -0
- data/test/tc_storage_lite.rb +102 -0
- data/test/tc_workbook.rb +115 -0
- data/test/tc_worksheet.rb +115 -0
- data/test/test_00_IEEE_double.rb +14 -0
- data/test/test_01_add_worksheet.rb +12 -0
- data/test/test_02_merge_formats.rb +58 -0
- data/test/test_04_dimensions.rb +397 -0
- data/test/test_05_rows.rb +182 -0
- data/test/test_06_extsst.rb +80 -0
- data/test/test_11_date_time.rb +484 -0
- data/test/test_12_date_only.rb +506 -0
- data/test/test_13_date_seconds.rb +486 -0
- data/test/test_21_escher.rb +629 -0
- data/test/test_22_mso_drawing_group.rb +739 -0
- data/test/test_23_note.rb +78 -0
- data/test/test_24_txo.rb +80 -0
- data/test/test_26_autofilter.rb +327 -0
- data/test/test_27_autofilter.rb +144 -0
- data/test/test_28_autofilter.rb +174 -0
- data/test/test_29_process_jpg.rb +131 -0
- data/test/test_30_validation_dval.rb +82 -0
- data/test/test_31_validation_dv_strings.rb +131 -0
- data/test/test_32_validation_dv_formula.rb +211 -0
- data/test/test_40_property_types.rb +191 -0
- data/test/test_41_properties.rb +238 -0
- data/test/test_42_set_properties.rb +430 -0
- data/test/ts_all.rb +34 -0
- metadata +154 -0
@@ -0,0 +1,322 @@
|
|
1
|
+
###############################################################################
|
2
|
+
#
|
3
|
+
# BIFFwriter - An abstract base class for Excel workbooks and worksheets.
|
4
|
+
#
|
5
|
+
#
|
6
|
+
# Used in conjunction with Spreadsheet::WriteExcel
|
7
|
+
#
|
8
|
+
# Copyright 2000-2008, John McNamara, jmcnamara@cpan.org
|
9
|
+
#
|
10
|
+
# original written in Perl by John McNamara
|
11
|
+
# converted to Ruby by Hideo Nakamura, cxn03651@msj.biglobe.ne.jp
|
12
|
+
#
|
13
|
+
class MaxSizeError < StandardError; end
|
14
|
+
|
15
|
+
class OLEWriter
|
16
|
+
|
17
|
+
# Not meant for public consumption
|
18
|
+
MaxSize = 7087104 # Use Spreadsheet::WriteExcel::Big to exceed this
|
19
|
+
BlockSize = 4096
|
20
|
+
BlockDiv = 512
|
21
|
+
ListBlocks = 127
|
22
|
+
|
23
|
+
attr_reader :biff_size, :book_size, :big_blocks, :list_blocks
|
24
|
+
attr_reader :root_start, :size_allowed
|
25
|
+
attr_accessor :biff_only, :internal_fh
|
26
|
+
|
27
|
+
# Accept an IO or IO-like object or a filename (as a String)
|
28
|
+
def initialize(arg)
|
29
|
+
if arg.kind_of?(String)
|
30
|
+
@io = File.open(arg, "w")
|
31
|
+
else
|
32
|
+
@io = arg
|
33
|
+
end
|
34
|
+
@io.binmode
|
35
|
+
|
36
|
+
@filehandle = ""
|
37
|
+
@fileclosed = false
|
38
|
+
@internal_fh = 0
|
39
|
+
@biff_only = 0
|
40
|
+
@size_allowed = true
|
41
|
+
@biff_size = 0
|
42
|
+
@book_size = 0
|
43
|
+
@big_blocks = 0
|
44
|
+
@list_blocks = 0
|
45
|
+
@root_start = 0
|
46
|
+
@block_count = 4
|
47
|
+
end
|
48
|
+
|
49
|
+
# Imitate IO.open behavior
|
50
|
+
|
51
|
+
###############################################################################
|
52
|
+
#
|
53
|
+
# _initialize()
|
54
|
+
#
|
55
|
+
# Create a new filehandle or use the provided filehandle.
|
56
|
+
#
|
57
|
+
def _initialize
|
58
|
+
olefile = @olefilename
|
59
|
+
|
60
|
+
# If the filename is a reference it is assumed that it is a valid
|
61
|
+
# filehandle, if not we create a filehandle.
|
62
|
+
#
|
63
|
+
|
64
|
+
# Create a new file, open for writing
|
65
|
+
fh = open(olefile, "wb")
|
66
|
+
|
67
|
+
# Workbook.pm also checks this but something may have happened since
|
68
|
+
# then.
|
69
|
+
raise "Can't open olefile. It may be in use or protected.\n" unless fh
|
70
|
+
|
71
|
+
@internal_fh = 1
|
72
|
+
|
73
|
+
# Store filehandle
|
74
|
+
@filehandle = fh
|
75
|
+
end
|
76
|
+
|
77
|
+
def self.open(arg)
|
78
|
+
if block_given?
|
79
|
+
ole = self.new(arg)
|
80
|
+
result = yield(ole)
|
81
|
+
ole.close
|
82
|
+
result
|
83
|
+
else
|
84
|
+
self.new(arg)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
###############################################################################
|
89
|
+
#
|
90
|
+
# write($data)
|
91
|
+
#
|
92
|
+
# Write BIFF data to OLE file.
|
93
|
+
#
|
94
|
+
def write(data)
|
95
|
+
#print "ole write\n"
|
96
|
+
#print data.unpack('C*').map! {|c| sprintf("%02X", c) }.join(' ') + "\n\n"
|
97
|
+
@io.write(data)
|
98
|
+
end
|
99
|
+
# def print(data)
|
100
|
+
# @io.print(data)
|
101
|
+
# end
|
102
|
+
|
103
|
+
###############################################################################
|
104
|
+
#
|
105
|
+
# set_size($biffsize)
|
106
|
+
#
|
107
|
+
# Set the size of the data to be written to the OLE stream
|
108
|
+
#
|
109
|
+
# $big_blocks = (109 depot block x (128 -1 marker word)
|
110
|
+
# - (1 x end words)) = 13842
|
111
|
+
# $maxsize = $big_blocks * 512 bytes = 7087104
|
112
|
+
#
|
113
|
+
def set_size(size = BlockSize)
|
114
|
+
if size > MaxSize
|
115
|
+
return @size_allowed = false
|
116
|
+
end
|
117
|
+
|
118
|
+
@biff_size = size
|
119
|
+
|
120
|
+
if biff_size > BlockSize
|
121
|
+
@book_size = size
|
122
|
+
else
|
123
|
+
@book_size = BlockSize
|
124
|
+
end
|
125
|
+
|
126
|
+
@size_allowed = true
|
127
|
+
end
|
128
|
+
|
129
|
+
###############################################################################
|
130
|
+
#
|
131
|
+
# _calculate_sizes()
|
132
|
+
#
|
133
|
+
# Calculate various sizes needed for the OLE stream
|
134
|
+
#
|
135
|
+
def calculate_sizes
|
136
|
+
@big_blocks = (@book_size.to_f/BlockDiv.to_f).ceil
|
137
|
+
@list_blocks = (@big_blocks / ListBlocks) + 1
|
138
|
+
@root_start = @big_blocks
|
139
|
+
end
|
140
|
+
|
141
|
+
###############################################################################
|
142
|
+
#
|
143
|
+
# close()
|
144
|
+
#
|
145
|
+
# Write root entry, big block list and close the filehandle.
|
146
|
+
# This routine is used to explicitly close the open filehandle without
|
147
|
+
# having to wait for DESTROY.
|
148
|
+
#
|
149
|
+
def close
|
150
|
+
if @size_allowed == true
|
151
|
+
#print "write_padding"
|
152
|
+
write_padding if @biff_only == 0
|
153
|
+
#print "write_property_storage"
|
154
|
+
write_property_storage if @biff_only == 0
|
155
|
+
#print "write_big_block_depot"
|
156
|
+
write_big_block_depot if @biff_only == 0
|
157
|
+
end
|
158
|
+
@io.close
|
159
|
+
end
|
160
|
+
|
161
|
+
###############################################################################
|
162
|
+
#
|
163
|
+
# write_header()
|
164
|
+
#
|
165
|
+
# Write OLE header block.
|
166
|
+
#
|
167
|
+
def write_header
|
168
|
+
return if @biff_only == 1
|
169
|
+
calculate_sizes
|
170
|
+
root_start = @root_start
|
171
|
+
num_lists = @list_blocks
|
172
|
+
|
173
|
+
id = [0xD0CF11E0, 0xA1B11AE1].pack("NN")
|
174
|
+
unknown1 = [0x00, 0x00, 0x00, 0x00].pack("VVVV")
|
175
|
+
unknown2 = [0x3E, 0x03].pack("vv")
|
176
|
+
unknown3 = [-2].pack("v")
|
177
|
+
unknown4 = [0x09].pack("v")
|
178
|
+
unknown5 = [0x06, 0x00, 0x00].pack("VVV")
|
179
|
+
num_bbd_blocks = [num_lists].pack("V")
|
180
|
+
root_startblock = [root_start].pack("V")
|
181
|
+
unknown6 = [0x00, 0x1000].pack("VV")
|
182
|
+
sbd_startblock = [-2].pack("V")
|
183
|
+
unknown7 = [0x00, -2 ,0x00].pack("VVV")
|
184
|
+
|
185
|
+
write(id)
|
186
|
+
write(unknown1)
|
187
|
+
write(unknown2)
|
188
|
+
write(unknown3)
|
189
|
+
write(unknown4)
|
190
|
+
write(unknown5)
|
191
|
+
write(num_bbd_blocks)
|
192
|
+
write(root_startblock)
|
193
|
+
write(unknown6)
|
194
|
+
write(sbd_startblock)
|
195
|
+
write(unknown7)
|
196
|
+
|
197
|
+
unused = [-1].pack("V")
|
198
|
+
|
199
|
+
1.upto(num_lists){
|
200
|
+
root_start += 1
|
201
|
+
write([root_start].pack("V"))
|
202
|
+
}
|
203
|
+
|
204
|
+
num_lists.upto(108){
|
205
|
+
write(unused)
|
206
|
+
}
|
207
|
+
end
|
208
|
+
|
209
|
+
###############################################################################
|
210
|
+
#
|
211
|
+
# _write_big_block_depot()
|
212
|
+
#
|
213
|
+
# Write big block depot.
|
214
|
+
#
|
215
|
+
def write_big_block_depot
|
216
|
+
num_blocks = @big_blocks
|
217
|
+
num_lists = @list_blocks
|
218
|
+
total_blocks = num_lists * 128
|
219
|
+
used_blocks = num_blocks + num_lists + 2
|
220
|
+
|
221
|
+
marker = [-3].pack("V")
|
222
|
+
end_of_chain = [-2].pack("V")
|
223
|
+
unused = [-1].pack("V")
|
224
|
+
|
225
|
+
1.upto(num_blocks-1){|n|
|
226
|
+
write([n].pack("V"))
|
227
|
+
}
|
228
|
+
|
229
|
+
write end_of_chain
|
230
|
+
write end_of_chain
|
231
|
+
|
232
|
+
1.upto(num_lists){ write(marker) }
|
233
|
+
|
234
|
+
used_blocks.upto(total_blocks){ write(unused) }
|
235
|
+
|
236
|
+
end
|
237
|
+
|
238
|
+
###############################################################################
|
239
|
+
#
|
240
|
+
# _write_property_storage()
|
241
|
+
#
|
242
|
+
# Write property storage. TODO: add summary sheets
|
243
|
+
#
|
244
|
+
def write_property_storage
|
245
|
+
|
246
|
+
######### name type dir start size
|
247
|
+
write_pps('Root Entry', 0x05, 1, -2, 0x00)
|
248
|
+
write_pps('Workbook', 0x02, -1, 0x00, @book_size)
|
249
|
+
write_pps("", 0x00, -1, 0x00, 0x0000)
|
250
|
+
write_pps("", 0x00, -1, 0x00, 0x0000)
|
251
|
+
end
|
252
|
+
|
253
|
+
###############################################################################
|
254
|
+
#
|
255
|
+
# _write_pps()
|
256
|
+
#
|
257
|
+
# Write property sheet in property storage
|
258
|
+
#
|
259
|
+
def write_pps(name, type, dir, start, size)
|
260
|
+
length = 0
|
261
|
+
ord_name = []
|
262
|
+
unless name.empty?
|
263
|
+
name = name + "\0"
|
264
|
+
ord_name = name.unpack("c*")
|
265
|
+
length = name.length * 2
|
266
|
+
end
|
267
|
+
|
268
|
+
rawname = ord_name.pack("v*")
|
269
|
+
zero = [0].pack("C")
|
270
|
+
|
271
|
+
pps_sizeofname = [length].pack("v") #0x40
|
272
|
+
pps_type = [type].pack("v") #0x42
|
273
|
+
pps_prev = [-1].pack("V") #0x44
|
274
|
+
pps_next = [-1].pack("V") #0x48
|
275
|
+
pps_dir = [dir].pack("V") #0x4c
|
276
|
+
|
277
|
+
unknown = [0].pack("V")
|
278
|
+
|
279
|
+
pps_ts1s = [0].pack("V") #0x64
|
280
|
+
pps_ts1d = [0].pack("V") #0x68
|
281
|
+
pps_ts2s = [0].pack("V") #0x6c
|
282
|
+
pps_ts2d = [0].pack("V") #0x70
|
283
|
+
pps_sb = [start].pack("V") #0x74
|
284
|
+
pps_size = [size].pack("V") #0x78
|
285
|
+
|
286
|
+
write(rawname)
|
287
|
+
for n in 1..64-length
|
288
|
+
write(zero)
|
289
|
+
end
|
290
|
+
write(pps_sizeofname)
|
291
|
+
write(pps_type)
|
292
|
+
write(pps_prev)
|
293
|
+
write(pps_next)
|
294
|
+
write(pps_dir)
|
295
|
+
for n in 1..5
|
296
|
+
write(unknown)
|
297
|
+
end
|
298
|
+
write(pps_ts1s)
|
299
|
+
write(pps_ts1d)
|
300
|
+
write(pps_ts2s)
|
301
|
+
write(pps_ts2d)
|
302
|
+
write(pps_sb)
|
303
|
+
write(pps_size)
|
304
|
+
write(unknown)
|
305
|
+
end
|
306
|
+
|
307
|
+
###############################################################################
|
308
|
+
#
|
309
|
+
# _write_padding()
|
310
|
+
#
|
311
|
+
# Pad the end of the file
|
312
|
+
#
|
313
|
+
def write_padding
|
314
|
+
min_size = 512
|
315
|
+
min_size = BlockSize if @biff_size < BlockSize
|
316
|
+
|
317
|
+
if @biff_size % min_size != 0
|
318
|
+
padding = min_size - (@biff_size % min_size)
|
319
|
+
write("\0" * padding)
|
320
|
+
end
|
321
|
+
end
|
322
|
+
end
|
@@ -0,0 +1,250 @@
|
|
1
|
+
###############################################################################
|
2
|
+
#
|
3
|
+
# Properties - A module for creating Excel property sets.
|
4
|
+
#
|
5
|
+
#
|
6
|
+
# Used in conjunction with Spreadsheet::WriteExcel
|
7
|
+
#
|
8
|
+
# Copyright 2000-2008, John McNamara.
|
9
|
+
#
|
10
|
+
# original written in Perl by John McNamara
|
11
|
+
# converted to Ruby by Hideo Nakamura, cxn03651@msj.biglobe.ne.jp
|
12
|
+
#
|
13
|
+
|
14
|
+
#require 'date'
|
15
|
+
|
16
|
+
###############################################################################
|
17
|
+
#
|
18
|
+
# create_summary_property_set().
|
19
|
+
#
|
20
|
+
# Create the SummaryInformation property set. This is mainly used for the
|
21
|
+
# Title, Subject, Author, Keywords, Comments, Last author keywords and the
|
22
|
+
# creation date.
|
23
|
+
#
|
24
|
+
def create_summary_property_set(properties)
|
25
|
+
byte_order = [0xFFFE].pack('v')
|
26
|
+
version = [0x0000].pack('v')
|
27
|
+
system_id = [0x00020105].pack('V')
|
28
|
+
class_id = ['00000000000000000000000000000000'].pack('H*')
|
29
|
+
num_property_sets = [0x0001].pack('V')
|
30
|
+
format_id = ['E0859FF2F94F6810AB9108002B27B3D9'].pack('H*')
|
31
|
+
offset = [0x0030].pack('V')
|
32
|
+
num_property = [properties.size].pack('V')
|
33
|
+
property_offsets = ''
|
34
|
+
|
35
|
+
# Create the property set data block and calculate the offsets into it.
|
36
|
+
property_data, offsets = pack_property_data(properties)
|
37
|
+
|
38
|
+
# Create the property type and offsets based on the previous calculation.
|
39
|
+
0.upto(properties.size-1) do |i|
|
40
|
+
property_offsets = property_offsets + [properties[i][0], offsets[i]].pack('VV')
|
41
|
+
end
|
42
|
+
|
43
|
+
# Size of size (4 bytes) + num_property (4 bytes) + the data structures.
|
44
|
+
size = 8 + (property_offsets).length + property_data.length
|
45
|
+
size = [size].pack('V')
|
46
|
+
|
47
|
+
return byte_order +
|
48
|
+
version +
|
49
|
+
system_id +
|
50
|
+
class_id +
|
51
|
+
num_property_sets +
|
52
|
+
format_id +
|
53
|
+
offset +
|
54
|
+
size +
|
55
|
+
num_property +
|
56
|
+
property_offsets +
|
57
|
+
property_data
|
58
|
+
end
|
59
|
+
|
60
|
+
|
61
|
+
###############################################################################
|
62
|
+
#
|
63
|
+
# Create the DocSummaryInformation property set. This is mainly used for the
|
64
|
+
# Manager, Company and Category keywords.
|
65
|
+
#
|
66
|
+
# The DocSummary also contains a stream for user defined properties. However
|
67
|
+
# this is a little arcane and probably not worth the implementation effort.
|
68
|
+
#
|
69
|
+
def create_doc_summary_property_set(properties)
|
70
|
+
byte_order = [0xFFFE].pack('v')
|
71
|
+
version = [0x0000].pack('v')
|
72
|
+
system_id = [0x00020105].pack('V')
|
73
|
+
class_id = ['00000000000000000000000000000000'].pack('H*')
|
74
|
+
num_property_sets = [0x0002].pack('V')
|
75
|
+
|
76
|
+
format_id_0 = ['02D5CDD59C2E1B10939708002B2CF9AE'].pack('H*')
|
77
|
+
format_id_1 = ['05D5CDD59C2E1B10939708002B2CF9AE'].pack('H*')
|
78
|
+
offset_0 = [0x0044].pack('V')
|
79
|
+
num_property_0 = [properties.size].pack('V')
|
80
|
+
property_offsets_0 = ''
|
81
|
+
|
82
|
+
# Create the property set data block and calculate the offsets into it.
|
83
|
+
property_data_0, offsets = pack_property_data(properties)
|
84
|
+
|
85
|
+
# Create the property type and offsets based on the previous calculation.
|
86
|
+
0.upto(properties.size-1) do |i|
|
87
|
+
property_offsets_0 = property_offsets_0 + [properties[i][0], offsets[i]].pack('VV')
|
88
|
+
end
|
89
|
+
|
90
|
+
# Size of size (4 bytes) + num_property (4 bytes) + the data structures.
|
91
|
+
data_len = 8 + (property_offsets_0).length + property_data_0.length
|
92
|
+
size_0 = [data_len].pack('V')
|
93
|
+
|
94
|
+
# The second property set offset is at the end of the first property set.
|
95
|
+
offset_1 = [0x0044 + data_len].pack('V')
|
96
|
+
|
97
|
+
# We will use a static property set stream rather than try to generate it.
|
98
|
+
property_data_1 = [%w(
|
99
|
+
98 00 00 00 03 00 00 00 00 00 00 00 20 00 00 00
|
100
|
+
01 00 00 00 36 00 00 00 02 00 00 00 3E 00 00 00
|
101
|
+
01 00 00 00 02 00 00 00 0A 00 00 00 5F 50 49 44
|
102
|
+
5F 47 55 49 44 00 02 00 00 00 E4 04 00 00 41 00
|
103
|
+
00 00 4E 00 00 00 7B 00 31 00 36 00 43 00 34 00
|
104
|
+
42 00 38 00 33 00 42 00 2D 00 39 00 36 00 35 00
|
105
|
+
46 00 2D 00 34 00 42 00 32 00 31 00 2D 00 39 00
|
106
|
+
30 00 33 00 44 00 2D 00 39 00 31 00 30 00 46 00
|
107
|
+
41 00 44 00 46 00 41 00 37 00 30 00 31 00 42 00
|
108
|
+
7D 00 00 00 00 00 00 00 2D 00 39 00 30 00 33 00
|
109
|
+
).join('')].pack('H*')
|
110
|
+
|
111
|
+
return byte_order +
|
112
|
+
version +
|
113
|
+
system_id +
|
114
|
+
class_id +
|
115
|
+
num_property_sets +
|
116
|
+
format_id_0 +
|
117
|
+
offset_0 +
|
118
|
+
format_id_1 +
|
119
|
+
offset_1 +
|
120
|
+
|
121
|
+
size_0 +
|
122
|
+
num_property_0 +
|
123
|
+
property_offsets_0 +
|
124
|
+
property_data_0 +
|
125
|
+
|
126
|
+
property_data_1
|
127
|
+
end
|
128
|
+
|
129
|
+
|
130
|
+
###############################################################################
|
131
|
+
#
|
132
|
+
# _pack_property_data().
|
133
|
+
#
|
134
|
+
# Create a packed property set structure. Strings are null terminated and
|
135
|
+
# padded to a 4 byte boundary. We also use this function to keep track of the
|
136
|
+
# property offsets within the data structure. These offsets are used by the
|
137
|
+
# calling functions. Currently we only need to handle 4 property types:
|
138
|
+
# VT_I2, VT_LPSTR, VT_FILETIME.
|
139
|
+
#
|
140
|
+
def pack_property_data(properties, offset = 0)
|
141
|
+
packed_property = ''
|
142
|
+
data = ''
|
143
|
+
offsets = []
|
144
|
+
|
145
|
+
# Get the strings codepage from the first property.
|
146
|
+
codepage = properties[0][2]
|
147
|
+
|
148
|
+
# The properties start after 8 bytes for size + num_properties + 8 bytes
|
149
|
+
# for each propety type/offset pair.
|
150
|
+
offset += 8 * (properties.size + 1)
|
151
|
+
|
152
|
+
properties.each do |property|
|
153
|
+
offsets.push(offset)
|
154
|
+
|
155
|
+
property_type = property[1]
|
156
|
+
|
157
|
+
if property_type == 'VT_I2'
|
158
|
+
packed_property = pack_VT_I2(property[2])
|
159
|
+
elsif property_type == 'VT_LPSTR'
|
160
|
+
packed_property = pack_VT_LPSTR(property[2], codepage)
|
161
|
+
elsif property_type == 'VT_FILETIME'
|
162
|
+
packed_property = pack_VT_FILETIME(property[2])
|
163
|
+
else
|
164
|
+
raise "Unknown property type: '#{property_type}'\n"
|
165
|
+
end
|
166
|
+
|
167
|
+
offset += packed_property.length
|
168
|
+
data = data + packed_property
|
169
|
+
end
|
170
|
+
|
171
|
+
return [data, offsets]
|
172
|
+
end
|
173
|
+
|
174
|
+
###############################################################################
|
175
|
+
#
|
176
|
+
# _pack_VT_I2().
|
177
|
+
#
|
178
|
+
# Pack an OLE property type: VT_I2, 16-bit signed integer.
|
179
|
+
#
|
180
|
+
def pack_VT_I2(value)
|
181
|
+
type = 0x0002
|
182
|
+
data = [type, value].pack('VV')
|
183
|
+
end
|
184
|
+
|
185
|
+
###############################################################################
|
186
|
+
#
|
187
|
+
# _pack_VT_LPSTR().
|
188
|
+
#
|
189
|
+
# Pack an OLE property type: VT_LPSTR, String in the Codepage encoding.
|
190
|
+
# The strings are null terminated and padded to a 4 byte boundary.
|
191
|
+
#
|
192
|
+
def pack_VT_LPSTR(str, codepage)
|
193
|
+
type = 0x001E
|
194
|
+
string = str + "\0"
|
195
|
+
|
196
|
+
if codepage == 0x04E4
|
197
|
+
# Latin1
|
198
|
+
byte_string = string
|
199
|
+
length = byte_string.length
|
200
|
+
elsif codepage == 0xFDE9
|
201
|
+
# UTF-8
|
202
|
+
nonAscii = /[^!"#\$%&'\(\)\*\+,\-\.\/\:\;<=>\?@0-9A-Za-z_\[\\\]^` ~\0\n]/
|
203
|
+
if string =~ nonAscii
|
204
|
+
require 'jcode'
|
205
|
+
byte_string = string
|
206
|
+
length = byte_string.jlength
|
207
|
+
else
|
208
|
+
byte_string = string
|
209
|
+
length = byte_string.length
|
210
|
+
end
|
211
|
+
else
|
212
|
+
raise "Unknown codepage: codepage\n"
|
213
|
+
end
|
214
|
+
|
215
|
+
# Pack the data.
|
216
|
+
data = [type, length].pack('VV')
|
217
|
+
data = data + byte_string
|
218
|
+
|
219
|
+
# The packed data has to null padded to a 4 byte boundary.
|
220
|
+
if (extra = length % 4) != 0
|
221
|
+
data = data + "\0" * (4 - extra)
|
222
|
+
end
|
223
|
+
return data
|
224
|
+
end
|
225
|
+
|
226
|
+
###############################################################################
|
227
|
+
#
|
228
|
+
# _pack_VT_FILETIME().
|
229
|
+
#
|
230
|
+
# Pack an OLE property type: VT_FILETIME.
|
231
|
+
#
|
232
|
+
def pack_VT_FILETIME(localtime)
|
233
|
+
type = 0x0040
|
234
|
+
|
235
|
+
epoch = DateTime.new(1601, 1, 1)
|
236
|
+
|
237
|
+
datetime = DateTime.new(
|
238
|
+
localtime.year,
|
239
|
+
localtime.mon,
|
240
|
+
localtime.mday,
|
241
|
+
localtime.hour,
|
242
|
+
localtime.min,
|
243
|
+
localtime.sec,
|
244
|
+
localtime.usec
|
245
|
+
)
|
246
|
+
bignum = (datetime - epoch) * 86400 * 1e7.to_i
|
247
|
+
high, low = bignum.divmod 1 << 32
|
248
|
+
|
249
|
+
[type].pack('V') + [low, high].pack('V2')
|
250
|
+
end
|