ru_excel 0.0.6

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.
@@ -0,0 +1,222 @@
1
+ require File.dirname(__FILE__)+'/biff_records'
2
+ module Excel
3
+ module BiffRecord
4
+ def _position_image(sheet, row_start, col_start, x1, y1, width, height)
5
+ =begin
6
+ Calculate the vertices that define the position of the image as required by
7
+ the OBJ record.
8
+
9
+ +------------+------------+
10
+ | A | B |
11
+ +-----+------------+------------+
12
+ | |(x1,y1) | |
13
+ | 1 |(A1)._______|______ |
14
+ | | | | |
15
+ | | | | |
16
+ +-----+----| BITMAP |-----+
17
+ | | | | |
18
+ | 2 | |______________. |
19
+ | | | (B2)|
20
+ | | | (x2,y2)|
21
+ +---- +------------+------------+
22
+
23
+ Example of a bitmap that covers some of the area from cell A1 to cell B2.
24
+
25
+ Based on the width and height of the bitmap we need to calculate 8 vars
26
+ col_start, row_start, col_end, row_end, x1, y1, x2, y2.
27
+ The width and height of the cells are also variable and have to be taken into
28
+ account.
29
+ The values of col_start and row_start are passed in from the calling
30
+ function. The values of col_end and row_end are calculated by subtracting
31
+ the width and height of the bitmap from the width and height of the
32
+ underlying cells.
33
+ The vertices are expressed as a percentage of the underlying cell width as
34
+ follows (rhs values are in pixels)
35
+
36
+ x1 = X / W *1024
37
+ y1 = Y / H *256
38
+ x2 = (X-1) / W *1024
39
+ y2 = (Y-1) / H *256
40
+
41
+ Where=> X is distance from the left side of the underlying cell
42
+ Y is distance from the top of the underlying cell
43
+ W is the width of the cell
44
+ H is the height of the cell
45
+
46
+ Note=> the SDK incorrectly states that the height should be expressed as a
47
+ percentage of 1024.
48
+
49
+ col_start - Col containing upper left corner of object
50
+ row_start - Row containing top left corner of object
51
+ x1 - Distance to left side of object
52
+ y1 - Distance to top of object
53
+ width - Width of image frame
54
+ height - Height of image frame
55
+
56
+ =end
57
+ # Adjust start column for offsets that are greater than the col width
58
+ while x1 >= sheet.col_width(col_start)
59
+ x1 -= sheet.col_width(col_start)
60
+ col_start += 1
61
+ end
62
+ # Adjust start row for offsets that are greater than the row height
63
+ while y1 >= sheet.row_height(row_start)
64
+ y1 -= sheet.row_height(row_start)
65
+ row_start += 1
66
+ end
67
+ # Initialise end cell to the same as the start cell
68
+ row_end = row_start # Row containing bottom right corner of object
69
+ col_end = col_start # Col containing lower right corner of object
70
+ width = width + x1 - 1
71
+ height = height + y1 - 1
72
+ # Subtract the underlying cell widths to find the end cell of the image
73
+ while (width >= sheet.col_width(col_end))
74
+ width -= sheet.col_width(col_end)
75
+ col_end += 1
76
+ end
77
+ # Subtract the underlying cell heights to find the end cell of the image
78
+ while (height >= sheet.row_height(row_end))
79
+ height -= sheet.row_height(row_end)
80
+ row_end += 1
81
+ end
82
+ # Bitmap isn't allowed to start or finish in a hidden cell, i.e. a cell
83
+ # with zero height or width.
84
+ if ((sheet.col_width( col_start) == 0) || (sheet.col_width( col_end) == 0) ||
85
+ (sheet.row_height(row_start) == 0) || (sheet.row_height(row_end) == 0))
86
+ return
87
+ end
88
+ # Convert the pixel values to the percentage value expected by Excel
89
+ x1 = x1.to_f / sheet.col_width( col_start) * 1024
90
+ y1 = y1.to_f / sheet.row_height(row_start) * 256
91
+ # Distance to right side of object
92
+ x2 = width.to_f / sheet.col_width( col_end) * 1024
93
+ # Distance to bottom of object
94
+ y2 = height.to_f / sheet.row_height(row_end) * 256
95
+ [col_start, x1, row_start, y1, col_end, x2, row_end, y2]
96
+ end
97
+
98
+ def objBmpRecord(row, col, sheet, im_data_bmp, x, y, scale_x, scale_y)
99
+
100
+ # Scale the frame of the image.
101
+ width = im_data_bmp.width * scale_x
102
+ height = im_data_bmp.height * scale_y
103
+
104
+ # Calculate the vertices of the image and write the OBJ record
105
+ col_start, x1, row_start, y1, col_end, x2, row_end, y2 = _position_image(sheet, row, col, x, y, width, height)
106
+
107
+ =begin
108
+ Store the OBJ record that precedes an IMDATA record. This could be generalise
109
+ to support other Excel objects.
110
+ =end
111
+ cObj = 0x0001 # Count of objects in file (set to 1)
112
+ _OT = 0x0008 # Object type. 8 = Picture
113
+ id = 0x0001 # Object ID
114
+ grbit = 0x0614 # Option flags
115
+ colL = col_start # Col containing upper left corner of object
116
+ dxL = x1 # Distance from left side of cell
117
+ rwT = row_start # Row containing top left corner of object
118
+ dyT = y1 # Distance from top of cell
119
+ colR = col_end # Col containing lower right corner of object
120
+ dxR = x2 # Distance from right of cell
121
+ rwB = row_end # Row containing bottom right corner of object
122
+ dyB = y2 # Distance from bottom of cell
123
+ cbMacro = 0x0000 # Length of FMLA structure
124
+ reserved1 = 0x0000 # Reserved
125
+ reserved2 = 0x0000 # Reserved
126
+ icvBack = 0x09 # Background colour
127
+ icvFore = 0x09 # Foreground colour
128
+ fls = 0x00 # Fill pattern
129
+ fAuto = 0x00 # Automatic fill
130
+ icv = 0x08 # Line colour
131
+ lns = 0xff # Line style
132
+ lnw = 0x01 # Line weight
133
+ fAutoB = 0x00 # Automatic border
134
+ frs = 0x0000 # Frame style
135
+ cf = 0x0009 # Image format, 9 = bitmap
136
+ reserved3 = 0x0000 # Reserved
137
+ cbPictFmla = 0x0000 # Length of FMLA structure
138
+ reserved4 = 0x0000 # Reserved
139
+ grbit2 = 0x0001 # Option flags
140
+ reserved5 = 0x0000 # Reserved
141
+
142
+ get_biff_data(0x005D, [cObj, _OT, id, grbit, colL, dxL, rwT, dyT, colR, dxR, rwB,
143
+ dyB, cbMacro, reserved1, reserved2, icvBack, icvFore, fls, fAuto,
144
+ icv, lns, lnw, fAutoB, frs, cf, reserved3, cbPictFmla, Reserved4,
145
+ grbit2, reserved5].pack('Vv12VvC8vVv4V'))
146
+ end
147
+
148
+ def _process_bitmap(bitmap)
149
+ =begin
150
+ Convert a 24 bit bitmap into the modified internal format used by Windows.
151
+ This is described in BITMAPCOREHEADER and BITMAPCOREINFO structures in the
152
+ MSDN library.
153
+ =end
154
+ # Open file and binmode the data in case the platform needs it.
155
+ data = nil
156
+ fh = File.open(bitmap, "rb") do |fh| data = fh.read() end
157
+ # Check that the file is big enough to be a bitmap.
158
+ if data.length <= 0x36
159
+ raise Exception("bitmap doesn't contain enough data.")
160
+ end
161
+ # The first 2 bytes are used to identify the bitmap.
162
+ if (data[0...(2)] != "BM")
163
+ raise Exception("bitmap doesn't appear to to be a valid bitmap image.")
164
+ end
165
+ # Remove bitmap data=> ID.
166
+ data = data[(2)..-1]
167
+ # Read and remove the bitmap size. This is more reliable than reading
168
+ # the data size at offset 0x22.
169
+ #
170
+ size = data[0...(4)].unpack("V")[0]
171
+ size -= 0x36 # Subtract size of bitmap header.
172
+ size += 0x0C # Add size of BIFF header.
173
+ data = data[(4)..-1]
174
+ # Remove bitmap data=> reserved, offset, header length.
175
+ data = data[(12)..-1]
176
+ # Read and remove the bitmap width and height. Verify the sizes.
177
+ width, height = data[0...(8)].unpack("VV")
178
+ data = data[(8)..-1]
179
+ if (width > 0xFFFF)
180
+ raise Exception("bitmap=> largest image width supported is 65k.")
181
+ end
182
+ if (height > 0xFFFF)
183
+ raise Exception("bitmap=> largest image height supported is 65k.")
184
+ end
185
+ # Read and remove the bitmap planes and bpp data. Verify them.
186
+ planes, bitcount = data[0...(4)].unpack("vv")
187
+ data = data[(4)..-1]
188
+ if (bitcount != 24)
189
+ raise Exception("bitmap isn't a 24bit true color bitmap.")
190
+ end
191
+ if (planes != 1)
192
+ raise Exception("bitmap=> only 1 plane supported in bitmap image.")
193
+ end
194
+ # Read and remove the bitmap compression. Verify compression.
195
+ compression = data[0...(4)].unpack("V")[0]
196
+ data = data[(4)..-1]
197
+ if (compression != 0)
198
+ raise Exception("bitmap=> compression not supported in bitmap image.")
199
+ end
200
+ # Remove bitmap data=> data size, hres, vres, colours, imp. colours.
201
+ data = data[(20)..-1]
202
+ # Add the BITMAPCOREHEADER data
203
+ header = [0x000c, width, height, 0x01, 0x18].pack("Vvvvv")
204
+ data = header + data
205
+ [width, height, size, data]
206
+ end
207
+
208
+ def imDataBmpRecord
209
+ rec_id 0x007F
210
+ =begin
211
+ Insert a 24bit bitmap image in a worksheet. The main record required is
212
+ IMDATA but it must be proceeded by a OBJ record to define its position.
213
+ =end
214
+ @width, @height, @size, data = _process_bitmap(filename)
215
+ # Write the IMDATA record to store the bitmap data
216
+ cf = 0x09
217
+ env = 0x01
218
+ lcb = @size
219
+ get_biff_data(0x007F, [cf, env, lcb, data].pack("vvVa*"))
220
+ end
221
+ end
222
+ end
@@ -0,0 +1,57 @@
1
+ require File.dirname(__FILE__)+'/biff_records'
2
+
3
+ module Excel
4
+ module Cell
5
+ extend self
6
+ def strCell(parent, idx, xf_idx, sst_idx)
7
+ BiffRecord.labelSSTRecord(parent.get_index, idx, xf_idx, sst_idx)
8
+ end
9
+ def blankCell(parent, idx, xf_idx)
10
+ BiffRecord.blankRecord(parent.get_index, idx, xf_idx)
11
+ end
12
+ def mulBlankCell(parent, col1, col2, xf_idx)
13
+ BiffRecord.mulBlankRecord(parent.get_index, col1, col2, xf_idx)
14
+ end
15
+ def numberCell(parent, idx, xf_idx, number)
16
+ rk_encoded = 0
17
+ packed = [number.to_f].pack('E')
18
+
19
+ #print @number
20
+ w0, w1, w2, w3 = packed.unpack('v4')
21
+ if w0 == 0 && w1 == 0 && w2 & 0xFFFC == w2
22
+ # 34 lsb are 0
23
+ #print "float RK"
24
+ rk_encoded = (w3 << 16) | w2
25
+ BiffRecord.rkRecord(parent.get_index, idx, xf_idx, rk_encoded)
26
+ elsif number.abs < 0x40000000 && number.to_i == number
27
+ #print "30-bit integer RK"
28
+ rk_encoded = 2 | (number.to_i << 2)
29
+ BiffRecord.rkRecord(parent.get_index, idx, xf_idx, rk_encoded)
30
+ else
31
+ temp = number*100
32
+ packed100 = [ temp].pack('E')
33
+ w0, w1, w2, w3 = packed100.unpack('v4')
34
+ if w0 == 0 && w1 == 0 && w2 & 0xFFFC == w2
35
+ # 34 lsb are 0
36
+ #print "float RK*100"
37
+ rk_encoded = 1 | (w3 << 16) | w2
38
+ BiffRecord.rkRecord(parent.get_index, idx, xf_idx, rk_encoded)
39
+ elsif temp.abs < 0x40000000 && temp.to_i == temp
40
+ #print "30-bit integer RK*100"
41
+ rk_encoded = 3 | (temp.to_i << 2)
42
+ BiffRecord.rkRecord(parent.get_index, idx, xf_idx, rk_encoded)
43
+ else
44
+ #print "Number"
45
+ #print
46
+ BiffRecord.numberRecord(parent.get_index, idx, xf_idx, number)
47
+ end
48
+ end
49
+ end
50
+ def mulNumberCell(parent, idx, xf_idx, sst_idx)
51
+ raise Exception
52
+ end
53
+ def fomulaCell(parent, idx, xf_idx, frmla)
54
+ BiffRecords.formulaRecord(parent.get_index, idx, xf_idx, frmla.rpn())
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,31 @@
1
+ dir = File.dirname(__FILE__)
2
+ require dir+'/biff_records'
3
+ require dir+'/deco'
4
+
5
+ module Excel
6
+ class Column
7
+ def initialize(indx, parent_sheet)
8
+ @index = indx
9
+ @parent = parent_sheet
10
+ @parent_wb = parent_sheet.parent
11
+ @xf_index = 0x0F
12
+
13
+ @width = 0x0B92
14
+ @hidden = 0
15
+ @level = 0
16
+ @collapse = 0
17
+ end
18
+ attr_accessor :level, :hidden, :width, :collapse
19
+
20
+
21
+ def get_biff_record
22
+ options = (@hidden & 0x01) << 0
23
+ options |= (@level & 0x07) << 8
24
+ options |= (@collapse & 0x01) << 12
25
+
26
+ BiffRecord.colInfoRecord(@index, @index, @width, @xf_index, options)
27
+ end
28
+ end
29
+ end
30
+
31
+
@@ -0,0 +1,268 @@
1
+ # This implementation writes only 'Root Entry', 'Workbook' streams
2
+ # and 2 empty streams for aligning directory stream on sector boundary
3
+ #
4
+ # LAYOUT
5
+ # 0 header
6
+ # 76 MSAT (1st part=> 109 SID)
7
+ # 512 workbook stream
8
+ # ... additional MSAT sectors if streams' size > about 7 Mb == (109*512 * 128)
9
+ # ... SAT
10
+ # ... directory stream
11
+ #
12
+ # NOTE=> this layout is "ad hoc". It can be more general. RTFM
13
+ module Excel
14
+ class XlsDoc
15
+ SECTOR_SIZE = 0x0200
16
+ MIN_LIMIT = 0x1000
17
+
18
+ SID_FREE_SECTOR = -1
19
+ SID_END_OF_CHAIN = -2
20
+ SID_USED_BY_SAT = -3
21
+ SID_USED_BY_MSAT = -4
22
+
23
+ def initialize
24
+ #@book_stream = '' # padded
25
+ @book_stream_sect = []
26
+
27
+ @dir_stream = ''
28
+ @dir_stream_sect = []
29
+
30
+ @packed_SAT = ''
31
+ @SAT_sect = []
32
+
33
+ @packed_MSAT_1st = ''
34
+ @packed_MSAT_2nd = ''
35
+ @MSAT_sect_2nd = []
36
+
37
+ @header = ''
38
+ end
39
+ def self.__block(dentry_name, dentry_type, dentry_colour, dentry_did_left,
40
+ dentry_did_right, dentry_did_root, dentry_start_sid, dentry_stream_sz)
41
+ [
42
+ dentry_name,
43
+ dentry_name.length,
44
+ dentry_type,
45
+ dentry_colour,
46
+ dentry_did_left,
47
+ dentry_did_right,
48
+ dentry_did_root,
49
+ 0, 0, 0, 0, 0, 0, 0, 0, 0,
50
+ dentry_start_sid,
51
+ dentry_stream_sz,
52
+ 0
53
+ ].pack('a64 v C2 l3 L9 l L L')
54
+ end
55
+ STREAM_START = __block(
56
+ "Root Entry\x00".gsub!(/(.)/,"\\1\x00"),
57
+ 0x05, # root storage
58
+ 0x01, # black
59
+ -1,
60
+ -1,
61
+ 1,
62
+ -2,
63
+ 0)
64
+ STREAM_WORKBOOK_DENTRY_NAME = "Workbook\x00".gsub!(/(.)/,"\\1\x00")
65
+ STREAM_PADDING = __block(
66
+ '',
67
+ 0x00, # empty
68
+ 0x01, # black
69
+ -1,
70
+ -1,
71
+ -1,
72
+ -2,
73
+ 0) * 2
74
+ def _build_directory # align on sector boundary
75
+ @dir_stream = ''
76
+ @dir_stream << STREAM_START
77
+ @dir_stream << XlsDoc.__block(
78
+ STREAM_WORKBOOK_DENTRY_NAME,
79
+ 0x02, # user stream
80
+ 0x01, # black
81
+ -1,
82
+ -1,
83
+ -1,
84
+ 0,
85
+ @book_stream_len)
86
+
87
+ @dir_stream << STREAM_PADDING
88
+ end
89
+
90
+ def _build_sat
91
+ # Build SAT
92
+ book_sect_count = @book_stream_len >> 9
93
+ dir_sect_count = @dir_stream.length >> 9
94
+
95
+ total_sect_count = book_sect_count + dir_sect_count
96
+ _SAT_sect_count = 0
97
+ _MSAT_sect_count = 0
98
+ _SAT_sect_count_limit = 109
99
+ while total_sect_count > 128*_SAT_sect_count or _SAT_sect_count > _SAT_sect_count_limit
100
+ _SAT_sect_count += 1
101
+ total_sect_count += 1
102
+ if _SAT_sect_count > _SAT_sect_count_limit
103
+ _MSAT_sect_count += 1
104
+ total_sect_count += 1
105
+ _SAT_sect_count_limit += 127
106
+ end
107
+ end
108
+
109
+
110
+ _SAT = [SID_FREE_SECTOR]*128*_SAT_sect_count
111
+
112
+ sect = 0
113
+ while sect < book_sect_count - 1
114
+ @book_stream_sect << sect
115
+ _SAT[sect] = sect + 1
116
+ sect += 1
117
+ end
118
+ @book_stream_sect << sect
119
+ _SAT[sect] = SID_END_OF_CHAIN
120
+ sect += 1
121
+
122
+ while sect < book_sect_count + _MSAT_sect_count
123
+ @MSAT_sect_2nd << sect
124
+ _SAT[sect] = SID_USED_BY_MSAT
125
+ sect += 1
126
+ end
127
+
128
+ while sect < book_sect_count + _MSAT_sect_count + _SAT_sect_count
129
+ @SAT_sect << sect
130
+ _SAT[sect] = SID_USED_BY_SAT
131
+ sect += 1
132
+ end
133
+
134
+ while sect < book_sect_count + _MSAT_sect_count + _SAT_sect_count + dir_sect_count - 1
135
+ @dir_stream_sect << sect
136
+ _SAT[sect] = sect + 1
137
+ sect += 1
138
+ end
139
+ @dir_stream_sect << sect
140
+ _SAT[sect] = SID_END_OF_CHAIN
141
+ sect += 1
142
+
143
+ @packed_SAT = _SAT.pack('l%d'%(_SAT_sect_count*128))
144
+
145
+
146
+ _MSAT_1st = @SAT_sect + [SID_FREE_SECTOR]*([109-@SAT_sect.length,0].max)
147
+
148
+ @packed_MSAT_1st = _MSAT_1st.pack('l109')
149
+
150
+ _MSAT_2nd = [SID_FREE_SECTOR]*128*_MSAT_sect_count
151
+ if _MSAT_sect_count > 0
152
+ _MSAT_2nd[- 1] = SID_END_OF_CHAIN
153
+ end
154
+
155
+ i = 109
156
+ msat_sect = 0
157
+ sid_num = 0
158
+ while i < _SAT_sect_count
159
+ if (sid_num + 1) % 128 == 0
160
+ #print 'link: ',
161
+ msat_sect += 1
162
+ if msat_sect < @MSAT_sect_2nd.length
163
+ _MSAT_2nd[sid_num] = @MSAT_sect_2nd[msat_sect]
164
+ end
165
+ else
166
+ #print 'sid: ',
167
+ _MSAT_2nd[sid_num] = @SAT_sect[i]
168
+ i += 1
169
+ end
170
+ #print sid_num, MSAT_2nd[sid_num]
171
+ sid_num += 1
172
+ end
173
+
174
+ @packed_MSAT_2nd = _MSAT_2nd.pack('l%d'%(_MSAT_sect_count*128))
175
+
176
+ #print vars()
177
+ #print zip(range(0, sect), SAT)
178
+ #print @book_stream_sect
179
+ #print self.MSAT_sect_2nd
180
+ #print MSAT_2nd
181
+ #print self.SAT_sect
182
+ #print @dir_stream_sect
183
+ end
184
+
185
+ def _build_header
186
+ doc_magic = "\xD0\xCF\x11\xE0\xA1\xB1\x1A\xE1"
187
+ file_uid = "\x00"*16
188
+ rev_num = "\x3E\x00"
189
+ ver_num = "\x03\x00"
190
+ byte_order = "\xFE\xFF"
191
+ log_sect_size = [ 9].pack('v')
192
+ log_short_sect_size = [ 6].pack('v')
193
+ not_used0 = "\x00"*10
194
+ total_sat_sectors = [@SAT_sect.length].pack('V')
195
+ dir_start_sid = [ @dir_stream_sect[0]].pack('V')
196
+ not_used1 = "\x00"*4
197
+ min_stream_size = [ 0x1000].pack('V')
198
+ ssat_start_sid = [ -2].pack('V')
199
+ total_ssat_sectors = [ 0].pack('V')
200
+
201
+ if @MSAT_sect_2nd.length == 0
202
+ msat_start_sid = [ -2].pack('V')
203
+ else
204
+ msat_start_sid = [@MSAT_sect_2nd[0]].pack('V')
205
+ end
206
+
207
+ total_msat_sectors = [@MSAT_sect_2nd.length].pack('V')
208
+
209
+ @header = [ doc_magic,
210
+ file_uid,
211
+ rev_num,
212
+ ver_num,
213
+ byte_order,
214
+ log_sect_size,
215
+ log_short_sect_size,
216
+ not_used0,
217
+ total_sat_sectors,
218
+ dir_start_sid,
219
+ not_used1,
220
+ min_stream_size,
221
+ ssat_start_sid,
222
+ total_ssat_sectors,
223
+ msat_start_sid,
224
+ total_msat_sectors
225
+ ].join('')
226
+ end
227
+
228
+ def save(f, stream)
229
+ # 1. Align stream on 0x1000 boundary (and therefore on sector boundary)
230
+ is_string = f.is_a? String
231
+ f = File.new(f, 'wb') if is_string
232
+ padding = "\x00" * (0x1000 - (stream.length % 0x1000))
233
+ @book_stream_len = stream.length + padding.length
234
+
235
+ _build_directory()
236
+ _build_sat()
237
+ _build_header()
238
+
239
+ f.write(@header)
240
+ f.write(@packed_MSAT_1st)
241
+ f.write(stream)
242
+ f.write(padding)
243
+ f.write(@packed_MSAT_2nd)
244
+ f.write(@packed_SAT)
245
+ f.write(@dir_stream)
246
+ f.close if is_string
247
+ end
248
+
249
+ def binary(stream)
250
+ padding = "\x00" * (0x1000 - (stream.length % 0x1000))
251
+ @book_stream_len = stream.length + padding.length
252
+
253
+ _build_directory()
254
+ _build_sat()
255
+ _build_header()
256
+ "#@header#@packed_MSAT_1st#{stream}#{padding}#@packed_MSAT_2nd#@packed_SAT#@dir_stream"
257
+ end
258
+ end
259
+ end
260
+
261
+ if $0 == __FILE__
262
+ d = XlsDoc()
263
+ d.save('a.aaa', 'b'*17000)
264
+ end
265
+
266
+
267
+
268
+