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.
Files changed (80) hide show
  1. data/.document +5 -0
  2. data/.gitignore +21 -0
  3. data/LICENSE +20 -0
  4. data/README.rdoc +17 -0
  5. data/Rakefile +47 -0
  6. data/VERSION +1 -0
  7. data/examples/a_simple.rb +42 -0
  8. data/examples/autofilters.rb +266 -0
  9. data/examples/bigfile.rb +30 -0
  10. data/examples/copyformat.rb +51 -0
  11. data/examples/data_validate.rb +278 -0
  12. data/examples/date_time.rb +86 -0
  13. data/examples/demo.rb +118 -0
  14. data/examples/diag_border.rb +35 -0
  15. data/examples/formats.rb +489 -0
  16. data/examples/header.rb +136 -0
  17. data/examples/hidden.rb +28 -0
  18. data/examples/hyperlink.rb +42 -0
  19. data/examples/images.rb +52 -0
  20. data/examples/merge1.rb +39 -0
  21. data/examples/merge2.rb +44 -0
  22. data/examples/merge3.rb +65 -0
  23. data/examples/merge4.rb +82 -0
  24. data/examples/merge5.rb +79 -0
  25. data/examples/protection.rb +46 -0
  26. data/examples/regions.rb +52 -0
  27. data/examples/repeat.rb +42 -0
  28. data/examples/stats.rb +75 -0
  29. data/examples/stocks.rb +80 -0
  30. data/examples/tab_colors.rb +30 -0
  31. data/lib/WriteExcel.rb +30 -0
  32. data/lib/WriteExcel/biffwriter.rb +259 -0
  33. data/lib/WriteExcel/chart.rb +217 -0
  34. data/lib/WriteExcel/excelformula.y +138 -0
  35. data/lib/WriteExcel/excelformulaparser.rb +573 -0
  36. data/lib/WriteExcel/format.rb +1108 -0
  37. data/lib/WriteExcel/formula.rb +986 -0
  38. data/lib/WriteExcel/olewriter.rb +322 -0
  39. data/lib/WriteExcel/properties.rb +250 -0
  40. data/lib/WriteExcel/storage_lite.rb +590 -0
  41. data/lib/WriteExcel/workbook.rb +2602 -0
  42. data/lib/WriteExcel/worksheet.rb +6378 -0
  43. data/spec/WriteExcel_spec.rb +7 -0
  44. data/spec/spec.opts +1 -0
  45. data/spec/spec_helper.rb +9 -0
  46. data/test/tc_all.rb +31 -0
  47. data/test/tc_biff.rb +104 -0
  48. data/test/tc_chart.rb +22 -0
  49. data/test/tc_example_match.rb +1280 -0
  50. data/test/tc_format.rb +1264 -0
  51. data/test/tc_formula.rb +63 -0
  52. data/test/tc_ole.rb +110 -0
  53. data/test/tc_storage_lite.rb +102 -0
  54. data/test/tc_workbook.rb +115 -0
  55. data/test/tc_worksheet.rb +115 -0
  56. data/test/test_00_IEEE_double.rb +14 -0
  57. data/test/test_01_add_worksheet.rb +12 -0
  58. data/test/test_02_merge_formats.rb +58 -0
  59. data/test/test_04_dimensions.rb +397 -0
  60. data/test/test_05_rows.rb +182 -0
  61. data/test/test_06_extsst.rb +80 -0
  62. data/test/test_11_date_time.rb +484 -0
  63. data/test/test_12_date_only.rb +506 -0
  64. data/test/test_13_date_seconds.rb +486 -0
  65. data/test/test_21_escher.rb +629 -0
  66. data/test/test_22_mso_drawing_group.rb +739 -0
  67. data/test/test_23_note.rb +78 -0
  68. data/test/test_24_txo.rb +80 -0
  69. data/test/test_26_autofilter.rb +327 -0
  70. data/test/test_27_autofilter.rb +144 -0
  71. data/test/test_28_autofilter.rb +174 -0
  72. data/test/test_29_process_jpg.rb +131 -0
  73. data/test/test_30_validation_dval.rb +82 -0
  74. data/test/test_31_validation_dv_strings.rb +131 -0
  75. data/test/test_32_validation_dv_formula.rb +211 -0
  76. data/test/test_40_property_types.rb +191 -0
  77. data/test/test_41_properties.rb +238 -0
  78. data/test/test_42_set_properties.rb +430 -0
  79. data/test/ts_all.rb +34 -0
  80. 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