spreadsheet-excel 0.3.4
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/CHANGES +124 -0
- data/CVS/Entries +10 -0
- data/CVS/Repository +1 -0
- data/CVS/Root +1 -0
- data/README +69 -0
- data/doc/CVS/Entries +3 -0
- data/doc/CVS/Repository +1 -0
- data/doc/CVS/Root +1 -0
- data/doc/format.txt +330 -0
- data/doc/spreadsheet.txt +178 -0
- data/examples/CVS/Entries +3 -0
- data/examples/CVS/Repository +1 -0
- data/examples/CVS/Root +1 -0
- data/examples/example_basic.rb +27 -0
- data/examples/example_format.rb +69 -0
- data/lib/CVS/Entries +1 -0
- data/lib/CVS/Repository +1 -0
- data/lib/CVS/Root +1 -0
- data/lib/spreadsheet/CVS/Entries +7 -0
- data/lib/spreadsheet/CVS/Repository +1 -0
- data/lib/spreadsheet/CVS/Root +1 -0
- data/lib/spreadsheet/biffwriter.rb +46 -0
- data/lib/spreadsheet/excel.rb +13 -0
- data/lib/spreadsheet/format.rb +359 -0
- data/lib/spreadsheet/olewriter.rb +178 -0
- data/lib/spreadsheet/workbook.rb +279 -0
- data/lib/spreadsheet/worksheet.rb +449 -0
- data/test/CVS/Entries +9 -0
- data/test/CVS/Repository +1 -0
- data/test/CVS/Root +1 -0
- data/test/perl_output/CVS/Entries +10 -0
- data/test/perl_output/CVS/Repository +1 -0
- data/test/perl_output/CVS/Root +1 -0
- data/test/perl_output/README +31 -0
- data/test/perl_output/f_font_biff +0 -0
- data/test/perl_output/f_font_key +1 -0
- data/test/perl_output/f_xf_biff +0 -0
- data/test/perl_output/ole_write_header +0 -0
- data/test/perl_output/ws_colinfo +1 -0
- data/test/perl_output/ws_store_dimensions +1 -0
- data/test/perl_output/ws_store_selection +1 -0
- data/test/perl_output/ws_store_window2 +1 -0
- data/test/tc_all.rb +6 -0
- data/test/tc_biff.rb +62 -0
- data/test/tc_excel.rb +26 -0
- data/test/tc_format.rb +133 -0
- data/test/tc_ole.rb +125 -0
- data/test/tc_workbook.rb +71 -0
- data/test/tc_worksheet.rb +142 -0
- data/test/ts_all.rb +9 -0
- metadata +103 -0
@@ -0,0 +1,178 @@
|
|
1
|
+
# olewriter.rb
|
2
|
+
#
|
3
|
+
# This class should never be instantiated directly. The entire class, and all
|
4
|
+
# its methods should be considered private.
|
5
|
+
|
6
|
+
class MaxSizeError < StandardError; end
|
7
|
+
|
8
|
+
class OLEWriter < IO
|
9
|
+
|
10
|
+
# Not meant for public consumption
|
11
|
+
MaxSize = 7087104
|
12
|
+
BlockSize = 4096
|
13
|
+
BlockDiv = 512
|
14
|
+
ListBlocks = 127
|
15
|
+
|
16
|
+
attr_reader :biff_size, :book_size, :big_blocks, :list_blocks
|
17
|
+
attr_reader :root_start, :size_allowed
|
18
|
+
|
19
|
+
# Accept an IO object, a fileno, or a String
|
20
|
+
def initialize(arg, &block)
|
21
|
+
if arg.kind_of?(String)
|
22
|
+
super(File.open(arg, "w+").fileno, "w+", &block)
|
23
|
+
elsif arg.respond_to?(:fileno)
|
24
|
+
super(arg.fileno, "w+", &block)
|
25
|
+
else
|
26
|
+
super(arg, "w+", &block)
|
27
|
+
end
|
28
|
+
binmode
|
29
|
+
|
30
|
+
@biff_only = false
|
31
|
+
@size_allowed = true
|
32
|
+
@biff_size = 0
|
33
|
+
@book_size = 0
|
34
|
+
@big_blocks = 0
|
35
|
+
@list_blocks = 0
|
36
|
+
@root_start = 0
|
37
|
+
@block_count = 4
|
38
|
+
end
|
39
|
+
|
40
|
+
# Set the size of the data to be written to the OLE stream
|
41
|
+
#
|
42
|
+
# @big_blocks = (109 depot block x (128 -1 marker word)
|
43
|
+
# - (1 x end words)) = 13842
|
44
|
+
#
|
45
|
+
# MaxSize = @big_blocks * 512 bytes = 7087104
|
46
|
+
def set_size(size = BlockSize)
|
47
|
+
raise MaxSizeError if size > MaxSize
|
48
|
+
|
49
|
+
@biff_size = size
|
50
|
+
|
51
|
+
if biff_size > BlockSize
|
52
|
+
@book_size = size
|
53
|
+
else
|
54
|
+
@book_size = BlockSize
|
55
|
+
end
|
56
|
+
|
57
|
+
@size_allowed = true
|
58
|
+
end
|
59
|
+
|
60
|
+
# Calculate various sizes needed for the OLE stream
|
61
|
+
def calculate_sizes
|
62
|
+
@big_blocks = (@book_size.to_f/BlockDiv.to_f).ceil
|
63
|
+
@list_blocks = (@big_blocks / ListBlocks) + 1
|
64
|
+
@root_start = @big_blocks
|
65
|
+
end
|
66
|
+
|
67
|
+
# Write root entry, big block list and close the filehandle.
|
68
|
+
def close
|
69
|
+
if @size_allowed == true
|
70
|
+
write_padding
|
71
|
+
write_property_storage
|
72
|
+
write_big_block_depot
|
73
|
+
end
|
74
|
+
super
|
75
|
+
end
|
76
|
+
|
77
|
+
# Write the OLE header block
|
78
|
+
def write_header
|
79
|
+
return if @biff_only == true
|
80
|
+
calculate_sizes
|
81
|
+
root_start = @root_start
|
82
|
+
|
83
|
+
write([0xD0CF11E0, 0xA1B11AE1].pack("NN"))
|
84
|
+
write([0x00, 0x00, 0x00, 0x00].pack("VVVV"))
|
85
|
+
write([0x3E, 0x03, -2, 0x09].pack("vvvv"))
|
86
|
+
write([0x06, 0x00, 0x00].pack("VVV"))
|
87
|
+
write([@list_blocks, root_start].pack("VV"))
|
88
|
+
write([0x00, 0x1000,-2].pack("VVV"))
|
89
|
+
write([0x00, -2 ,0x00].pack("VVV"))
|
90
|
+
|
91
|
+
unused = [-1].pack("V")
|
92
|
+
|
93
|
+
1.upto(@list_blocks){
|
94
|
+
root_start += 1
|
95
|
+
write([root_start].pack("V"))
|
96
|
+
}
|
97
|
+
|
98
|
+
@list_blocks.upto(108){
|
99
|
+
write(unused)
|
100
|
+
}
|
101
|
+
end
|
102
|
+
|
103
|
+
# Write a big block depot
|
104
|
+
def write_big_block_depot
|
105
|
+
total_blocks = @list_blocks * 128
|
106
|
+
used_blocks = @big_blocks + @list_blocks + 2
|
107
|
+
|
108
|
+
marker = [-3].pack("V")
|
109
|
+
eoc = [-2].pack("V")
|
110
|
+
unused = [-1].pack("V")
|
111
|
+
|
112
|
+
num_blocks = @big_blocks - 1
|
113
|
+
|
114
|
+
1.upto(num_blocks){|n|
|
115
|
+
write([n].pack("V"))
|
116
|
+
}
|
117
|
+
|
118
|
+
write eoc
|
119
|
+
write eoc
|
120
|
+
|
121
|
+
1.upto(@list_blocks){ write(marker) }
|
122
|
+
|
123
|
+
used_blocks.upto(total_blocks){ write(unused) }
|
124
|
+
|
125
|
+
end
|
126
|
+
|
127
|
+
# Write property storage
|
128
|
+
def write_property_storage
|
129
|
+
write_pps('Root Entry', 0x05, 1, -2, 0x00)
|
130
|
+
write_pps('Book', 0x02, -1, 0x00, @book_size)
|
131
|
+
write_pps("", 0x00, -1, 0x00, 0x0000)
|
132
|
+
write_pps("", 0x00, -1, 0x00, 0x0000)
|
133
|
+
end
|
134
|
+
|
135
|
+
# Write property sheet in property storage
|
136
|
+
def write_pps(name, type, dir, start, size)
|
137
|
+
length = 0
|
138
|
+
ord_name = []
|
139
|
+
unless name.empty?
|
140
|
+
name += "\0"
|
141
|
+
ord_name = name.unpack("c*")
|
142
|
+
length = name.length * 2
|
143
|
+
end
|
144
|
+
|
145
|
+
zero = [0].pack("C")
|
146
|
+
unknown = [0].pack("V")
|
147
|
+
|
148
|
+
write(ord_name.pack("v*"))
|
149
|
+
|
150
|
+
for n in 1..64-length
|
151
|
+
write(zero)
|
152
|
+
end
|
153
|
+
|
154
|
+
write([length,type,-1,-1,dir].pack("vvVVV"))
|
155
|
+
|
156
|
+
for n in 1..5
|
157
|
+
write(unknown)
|
158
|
+
end
|
159
|
+
|
160
|
+
for n in 1..4
|
161
|
+
write([0].pack("V"))
|
162
|
+
end
|
163
|
+
|
164
|
+
write([start,size].pack("VV"))
|
165
|
+
write(unknown)
|
166
|
+
end
|
167
|
+
|
168
|
+
# Pad the end of the file
|
169
|
+
def write_padding
|
170
|
+
min_size = 512
|
171
|
+
min_size = BlockSize if @biff_size < BlockSize
|
172
|
+
|
173
|
+
if @biff_size % min_size != 0
|
174
|
+
padding = min_size - (@biff_size % min_size)
|
175
|
+
write("\0" * padding)
|
176
|
+
end
|
177
|
+
end
|
178
|
+
end
|
@@ -0,0 +1,279 @@
|
|
1
|
+
class Workbook < BIFFWriter
|
2
|
+
BOF = 11
|
3
|
+
EOF = 4
|
4
|
+
SheetName = "Sheet"
|
5
|
+
|
6
|
+
attr_accessor :date_system
|
7
|
+
attr_reader :formats, :xf_index, :worksheets
|
8
|
+
|
9
|
+
def initialize(file)
|
10
|
+
super
|
11
|
+
|
12
|
+
@file = file
|
13
|
+
@format = Format.new
|
14
|
+
|
15
|
+
@active_sheet = 0
|
16
|
+
@first_sheet = 0
|
17
|
+
@biffsize = 0
|
18
|
+
@date_system = 1900
|
19
|
+
@xf_index = 16
|
20
|
+
|
21
|
+
@worksheets = []
|
22
|
+
@formats = []
|
23
|
+
|
24
|
+
@url_format = add_format(:color=>"blue", :underline=>1)
|
25
|
+
end
|
26
|
+
|
27
|
+
def close
|
28
|
+
store_workbook
|
29
|
+
end
|
30
|
+
|
31
|
+
def add_format(*args)
|
32
|
+
if args[0].kind_of?(Hash)
|
33
|
+
f = Format.new(args[0], @xf_index)
|
34
|
+
elsif args[0].nil?
|
35
|
+
f = Format.new
|
36
|
+
else
|
37
|
+
raise TypeError unless args[0].kind_of?(Format)
|
38
|
+
f = args[0]
|
39
|
+
f.xf_index = @xf_index
|
40
|
+
end
|
41
|
+
@xf_index += 1
|
42
|
+
@formats.push(f)
|
43
|
+
return f
|
44
|
+
end
|
45
|
+
|
46
|
+
def add_worksheet(name=nil)
|
47
|
+
index = @worksheets.length
|
48
|
+
|
49
|
+
if name.nil?
|
50
|
+
name = SheetName + (index + 1).to_s
|
51
|
+
end
|
52
|
+
|
53
|
+
args = [name,index, @active_sheet, @first_sheet, @url_format]
|
54
|
+
ws = Worksheet.new(*args)
|
55
|
+
@worksheets[index] = ws
|
56
|
+
return ws
|
57
|
+
end
|
58
|
+
|
59
|
+
def calc_sheet_offsets
|
60
|
+
offset = @datasize
|
61
|
+
@worksheets.each{ |sheet|
|
62
|
+
offset += BOF + sheet.name.length
|
63
|
+
}
|
64
|
+
|
65
|
+
offset += EOF
|
66
|
+
|
67
|
+
@worksheets.each{ |sheet|
|
68
|
+
sheet.offset = offset
|
69
|
+
offset += sheet.datasize
|
70
|
+
}
|
71
|
+
|
72
|
+
@biffsize = offset
|
73
|
+
end
|
74
|
+
|
75
|
+
def store_workbook
|
76
|
+
@worksheets.each{ |sheet|
|
77
|
+
sheet.close
|
78
|
+
}
|
79
|
+
|
80
|
+
store_bof(0x0005)
|
81
|
+
store_window1
|
82
|
+
store_date_system
|
83
|
+
store_all_fonts
|
84
|
+
store_all_num_formats
|
85
|
+
store_all_xfs
|
86
|
+
store_all_styles
|
87
|
+
calc_sheet_offsets
|
88
|
+
|
89
|
+
@worksheets.each{ |sheet|
|
90
|
+
store_boundsheet(sheet.name, sheet.offset)
|
91
|
+
}
|
92
|
+
|
93
|
+
store_eof
|
94
|
+
store_ole_file
|
95
|
+
end
|
96
|
+
|
97
|
+
def store_ole_file
|
98
|
+
OLEWriter.open(@file){ |ole|
|
99
|
+
ole.set_size(@biffsize)
|
100
|
+
ole.write_header
|
101
|
+
ole.print(@data)
|
102
|
+
@worksheets.each{ |sheet|
|
103
|
+
ole.print(sheet.data)
|
104
|
+
}
|
105
|
+
ole.close # Added because of bug introduced in Ruby 1.8.3
|
106
|
+
}
|
107
|
+
end
|
108
|
+
|
109
|
+
def store_window1
|
110
|
+
record = 0x003D
|
111
|
+
length = 0x0012
|
112
|
+
|
113
|
+
xWn = 0x0000
|
114
|
+
yWn = 0x0000
|
115
|
+
dxWn = 0x25BC
|
116
|
+
dyWn = 0x1572
|
117
|
+
|
118
|
+
grbit = 0x0038
|
119
|
+
ctabsel = 0x0001
|
120
|
+
wTabRatio = 0x0258
|
121
|
+
|
122
|
+
itabFirst = @first_sheet
|
123
|
+
itabCur = @active_sheet
|
124
|
+
|
125
|
+
header = [record,length].pack("vv")
|
126
|
+
fields = [xWn,yWn,dxWn,dyWn,grbit,itabCur,itabFirst,ctabsel,wTabRatio]
|
127
|
+
data = fields.pack("vvvvvvvvv")
|
128
|
+
|
129
|
+
append(header,data)
|
130
|
+
end
|
131
|
+
|
132
|
+
def store_all_fonts
|
133
|
+
font = @format.font_biff
|
134
|
+
for n in 1..5
|
135
|
+
append(font)
|
136
|
+
end
|
137
|
+
|
138
|
+
fonts = Hash.new(0)
|
139
|
+
index = 6
|
140
|
+
key = @format.font_key
|
141
|
+
fonts[key] = 0
|
142
|
+
|
143
|
+
@formats.each{ |format|
|
144
|
+
key = format.font_key
|
145
|
+
if fonts.has_key?(key)
|
146
|
+
format.font_index = fonts[key]
|
147
|
+
else
|
148
|
+
fonts[key] = index
|
149
|
+
format.font_index = index
|
150
|
+
index += 1
|
151
|
+
append(format.font_biff)
|
152
|
+
end
|
153
|
+
}
|
154
|
+
end
|
155
|
+
|
156
|
+
def store_xf(style)
|
157
|
+
name = 0x00E0
|
158
|
+
length = 0x0010
|
159
|
+
|
160
|
+
ifnt = 0x0000
|
161
|
+
ifmt = 0x0000
|
162
|
+
align = 0x0020
|
163
|
+
icv = 0x20C0
|
164
|
+
fill = 0x0000
|
165
|
+
brd_line = 0x0000
|
166
|
+
brd_color = 0x0000
|
167
|
+
|
168
|
+
header = [name, length].pack("vv")
|
169
|
+
fields = [ifnt,ifmt,style,align,icv,fill,brd_line,brd_color]
|
170
|
+
data = fields.pack("vvvvvvvv")
|
171
|
+
|
172
|
+
append(header, data);
|
173
|
+
end
|
174
|
+
|
175
|
+
def store_all_num_formats
|
176
|
+
index = 164
|
177
|
+
|
178
|
+
num_formats_hash = {}
|
179
|
+
num_formats_array = []
|
180
|
+
|
181
|
+
@formats.each{ |format|
|
182
|
+
num_format = format.num_format
|
183
|
+
next if num_format.kind_of?(Numeric)
|
184
|
+
if num_formats_hash.has_key?(num_format)
|
185
|
+
format.num_format = num_formats_hash[num_format]
|
186
|
+
else
|
187
|
+
num_formats_hash[num_format] = index
|
188
|
+
format.num_format = index
|
189
|
+
num_formats_array.push(num_format)
|
190
|
+
index += 1
|
191
|
+
end
|
192
|
+
}
|
193
|
+
|
194
|
+
index = 164
|
195
|
+
num_formats_array.each{ |num_format|
|
196
|
+
store_num_format(num_format,index)
|
197
|
+
index += 1
|
198
|
+
}
|
199
|
+
end
|
200
|
+
|
201
|
+
def store_all_xfs
|
202
|
+
xf = @format.xf_biff(0xFFF5)
|
203
|
+
for n in 1..15
|
204
|
+
append(xf)
|
205
|
+
end
|
206
|
+
|
207
|
+
xf = @format.xf_biff(0x0001)
|
208
|
+
append(xf)
|
209
|
+
|
210
|
+
@formats.each{ |format|
|
211
|
+
xf = format.xf_biff(0x0001)
|
212
|
+
append(xf)
|
213
|
+
}
|
214
|
+
end
|
215
|
+
|
216
|
+
def store_style
|
217
|
+
record = 0x0293
|
218
|
+
length = 0x0004
|
219
|
+
|
220
|
+
ixfe = 0x8000
|
221
|
+
builtin = 0x00
|
222
|
+
iLevel = 0xff
|
223
|
+
|
224
|
+
header = [record, length].pack("vv")
|
225
|
+
data = [ixfe, builtin, iLevel].pack("vCC")
|
226
|
+
|
227
|
+
append(header, data)
|
228
|
+
end
|
229
|
+
|
230
|
+
alias store_all_styles store_style
|
231
|
+
|
232
|
+
def store_boundsheet(sheet_name, offset)
|
233
|
+
name = 0x0085
|
234
|
+
length = 0x07 + sheet_name.length
|
235
|
+
|
236
|
+
grbit = 0x0000
|
237
|
+
cch = sheet_name.length
|
238
|
+
|
239
|
+
header = [name, length].pack("vv")
|
240
|
+
data = [offset, grbit, cch].pack("VvC")
|
241
|
+
|
242
|
+
append(header, data, sheet_name)
|
243
|
+
end
|
244
|
+
|
245
|
+
def store_num_format(format, ifmt)
|
246
|
+
record = 0x041E
|
247
|
+
cch = format.length
|
248
|
+
length = 0x03 + cch
|
249
|
+
|
250
|
+
header = [record, length].pack("vv")
|
251
|
+
data = [ifmt, cch].pack("vC")
|
252
|
+
|
253
|
+
append(header, data, format)
|
254
|
+
end
|
255
|
+
|
256
|
+
def store_date_system
|
257
|
+
record = 0x0022
|
258
|
+
length = 0x0002
|
259
|
+
|
260
|
+
f1904 = 0
|
261
|
+
f1904 = 1 if @date_system == 1904
|
262
|
+
|
263
|
+
header = [record, length].pack("vv")
|
264
|
+
data = [f1904].pack("v")
|
265
|
+
|
266
|
+
append(header, data)
|
267
|
+
end
|
268
|
+
end
|
269
|
+
|
270
|
+
=begin
|
271
|
+
= Notes on the difference between Workbook.pm and workbook.rb
|
272
|
+
---deprecated methods
|
273
|
+
I generally elminated any deprecated methods. That means no 'write'
|
274
|
+
methods.
|
275
|
+
---date_system
|
276
|
+
This is the 1904 attribute. However, since a number can't be a method,
|
277
|
+
this doesn't work very well for attribute_accessor. Besides, date_system
|
278
|
+
is more descriptive.
|
279
|
+
=end
|