writeexcel 0.6.9 → 0.6.10
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/README.rdoc +2 -0
- data/VERSION +1 -1
- data/lib/writeexcel/biffwriter.rb +29 -43
- data/lib/writeexcel/cell_range.rb +332 -0
- data/lib/writeexcel/chart.rb +50 -51
- data/lib/writeexcel/col_info.rb +87 -0
- data/lib/writeexcel/comments.rb +456 -0
- data/lib/writeexcel/convert_date_time.rb +117 -0
- data/lib/writeexcel/data_validations.rb +370 -0
- data/lib/writeexcel/debug_info.rb +5 -1
- data/lib/writeexcel/embedded_chart.rb +35 -0
- data/lib/writeexcel/format.rb +1 -1
- data/lib/writeexcel/formula.rb +3 -3
- data/lib/writeexcel/helper.rb +3 -0
- data/lib/writeexcel/image.rb +61 -1
- data/lib/writeexcel/olewriter.rb +2 -8
- data/lib/writeexcel/outline.rb +24 -0
- data/lib/writeexcel/shared_string_table.rb +153 -0
- data/lib/writeexcel/workbook.rb +86 -444
- data/lib/writeexcel/worksheet.rb +693 -2029
- data/lib/writeexcel/worksheets.rb +25 -0
- data/lib/writeexcel/write_file.rb +34 -15
- data/test/test_02_merge_formats.rb +0 -4
- data/test/test_04_dimensions.rb +0 -4
- data/test/test_05_rows.rb +0 -4
- data/test/test_06_extsst.rb +3 -6
- data/test/test_11_date_time.rb +0 -4
- data/test/test_12_date_only.rb +262 -231
- data/test/test_13_date_seconds.rb +0 -4
- data/test/test_21_escher.rb +71 -84
- data/test/test_22_mso_drawing_group.rb +0 -4
- data/test/test_23_note.rb +5 -21
- data/test/test_24_txo.rb +6 -7
- data/test/test_25_position_object.rb +0 -4
- data/test/test_26_autofilter.rb +0 -5
- data/test/test_27_autofilter.rb +0 -5
- data/test/test_28_autofilter.rb +0 -5
- data/test/test_29_process_jpg.rb +1 -1
- data/test/test_30_validation_dval.rb +4 -7
- data/test/test_31_validation_dv_strings.rb +9 -12
- data/test/test_32_validation_dv_formula.rb +11 -14
- data/test/test_42_set_properties.rb +0 -3
- data/test/test_50_name_stored.rb +0 -4
- data/test/test_51_name_print_area.rb +0 -4
- data/test/test_52_name_print_titles.rb +0 -4
- data/test/test_53_autofilter.rb +0 -4
- data/test/test_60_chart_generic.rb +42 -46
- data/test/test_61_chart_subclasses.rb +7 -11
- data/test/test_62_chart_formats.rb +12 -16
- data/test/test_63_chart_area_formats.rb +3 -7
- data/test/test_biff.rb +0 -4
- data/test/test_big_workbook.rb +17 -0
- data/test/test_format.rb +0 -3
- data/test/test_ole.rb +0 -4
- data/test/test_storage_lite.rb +0 -9
- data/test/test_workbook.rb +0 -4
- data/test/test_worksheet.rb +3 -7
- data/writeexcel.gemspec +12 -2
- metadata +12 -2
data/README.rdoc
CHANGED
@@ -76,6 +76,8 @@ You must save source file in UTF8 and run ruby with -Ku option or set $KCODE='u'
|
|
76
76
|
when use urf8 string data.
|
77
77
|
|
78
78
|
== Recent Changes
|
79
|
+
v0.6.10
|
80
|
+
* Bug fix. method missing split_string_setup() in shared_string_table.rb. see https://github.com/cxn03651/writeexcel/pull/13
|
79
81
|
|
80
82
|
v0.6.9
|
81
83
|
* Bug fix. When sheetname is cell's A1 notation such as 'D1', Worksheet#autofilter causes exception.
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.6.
|
1
|
+
0.6.10
|
@@ -20,7 +20,7 @@ class BIFFWriter < WriteFile #:nodoc:
|
|
20
20
|
BIFF_Version = 0x0600
|
21
21
|
BigEndian = [1].pack("I") == [1].pack("N")
|
22
22
|
|
23
|
-
attr_reader :
|
23
|
+
attr_reader :data, :datasize
|
24
24
|
|
25
25
|
######################################################################
|
26
26
|
# The args here aren't used by BIFFWriter, but they are needed by its
|
@@ -28,21 +28,9 @@ class BIFFWriter < WriteFile #:nodoc:
|
|
28
28
|
######################################################################
|
29
29
|
|
30
30
|
def initialize
|
31
|
+
super
|
31
32
|
set_byte_order
|
32
|
-
@
|
33
|
-
@datasize = 0
|
34
|
-
@limit = 8224
|
35
|
-
@ignore_continue = 0
|
36
|
-
|
37
|
-
# Open a tmp file to store the majority of the Worksheet data. If this fails,
|
38
|
-
# for example due to write permissions, store the data in memory. This can be
|
39
|
-
# slow for large files.
|
40
|
-
@filehandle = Tempfile.new('writeexcel')
|
41
|
-
@filehandle.binmode
|
42
|
-
|
43
|
-
# failed. store temporary data in memory.
|
44
|
-
@using_tmpfile = @filehandle ? true : false
|
45
|
-
|
33
|
+
@ignore_continue = false
|
46
34
|
end
|
47
35
|
|
48
36
|
###############################################################################
|
@@ -59,9 +47,9 @@ def set_byte_order
|
|
59
47
|
number = hexdata.pack("C8")
|
60
48
|
|
61
49
|
if number == teststr
|
62
|
-
@byte_order =
|
50
|
+
@byte_order = false # Little Endian
|
63
51
|
elsif number == teststr.reverse
|
64
|
-
@byte_order =
|
52
|
+
@byte_order = true # Big Endian
|
65
53
|
else
|
66
54
|
# Give up. I'll fix this in a later version.
|
67
55
|
raise( "Required floating point format not supported " +
|
@@ -160,37 +148,23 @@ def store_eof
|
|
160
148
|
# option to bypass this function.
|
161
149
|
#
|
162
150
|
def add_continue(data)
|
163
|
-
record = 0x003C # Record identifier
|
164
|
-
|
165
151
|
# Skip this if another method handles the continue blocks.
|
166
|
-
return data if @ignore_continue
|
152
|
+
return data if @ignore_continue
|
153
|
+
|
154
|
+
record = 0x003C # Record identifier
|
155
|
+
header = [record, @limit].pack("vv")
|
167
156
|
|
168
157
|
# The first 2080/8224 bytes remain intact. However, we have to change
|
169
158
|
# the length field of the record.
|
170
159
|
#
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
data = ''
|
180
|
-
end
|
181
|
-
|
182
|
-
tmp[2, 2] = [@limit-4].pack('v')
|
183
|
-
|
184
|
-
# Strip out chunks of 2080/8224 bytes +4 for the header.
|
185
|
-
while (data.bytesize > @limit)
|
186
|
-
header = [record, @limit].pack("vv")
|
187
|
-
tmp += header + data[0, @limit]
|
188
|
-
data[0, @limit] = ''
|
189
|
-
end
|
190
|
-
|
191
|
-
# Mop up the last of the data
|
192
|
-
header = [record, data.bytesize].pack("vv")
|
193
|
-
tmp += header + data
|
160
|
+
data_array = split_by_length(data, @limit)
|
161
|
+
first_data = data_array.shift
|
162
|
+
last_data = data_array.pop || ''
|
163
|
+
first_data[2, 2] = [@limit-4].pack('v')
|
164
|
+
first_data <<
|
165
|
+
data_array.join(header) <<
|
166
|
+
[record, last_data.bytesize].pack('vv') <<
|
167
|
+
last_data
|
194
168
|
end
|
195
169
|
|
196
170
|
###############################################################################
|
@@ -234,4 +208,16 @@ def cleanup # :nodoc:
|
|
234
208
|
def inspect # :nodoc:
|
235
209
|
to_s
|
236
210
|
end
|
211
|
+
|
212
|
+
private
|
213
|
+
|
214
|
+
def split_by_length(data, length)
|
215
|
+
array = []
|
216
|
+
s = 0
|
217
|
+
while s < data.length
|
218
|
+
array << data[s, length]
|
219
|
+
s += length
|
220
|
+
end
|
221
|
+
array
|
222
|
+
end
|
237
223
|
end
|
@@ -0,0 +1,332 @@
|
|
1
|
+
module Writeexcel
|
2
|
+
|
3
|
+
class Worksheet < BIFFWriter
|
4
|
+
class CellRange
|
5
|
+
attr_accessor :row_min, :row_max, :col_min, :col_max
|
6
|
+
|
7
|
+
def initialize(worksheet)
|
8
|
+
@worksheet = worksheet
|
9
|
+
end
|
10
|
+
|
11
|
+
def increment_row_max
|
12
|
+
@row_max += 1 if @row_max
|
13
|
+
end
|
14
|
+
|
15
|
+
def increment_col_max
|
16
|
+
@col_max += 1 if @col_max
|
17
|
+
end
|
18
|
+
|
19
|
+
def row(val)
|
20
|
+
@row_min = val if !@row_min || (val < row_min)
|
21
|
+
@row_max = val if !@row_max || (val > row_max)
|
22
|
+
end
|
23
|
+
|
24
|
+
def col(val)
|
25
|
+
@col_min = val if !@col_min || (val < col_min)
|
26
|
+
@col_max = val if !@col_max || (val > col_max)
|
27
|
+
end
|
28
|
+
|
29
|
+
#
|
30
|
+
# assemble the NAME record in the long format that is used for storing the repeat
|
31
|
+
# rows and columns when both are specified. This share a lot of code with
|
32
|
+
# name_record_short() but we use a separate method to keep the code clean.
|
33
|
+
# Code abstraction for reuse can be carried too far, and I should know. ;-)
|
34
|
+
#
|
35
|
+
# type
|
36
|
+
# ext_ref # TODO
|
37
|
+
#
|
38
|
+
def name_record_long(type, ext_ref) #:nodoc:
|
39
|
+
record = 0x0018 # Record identifier
|
40
|
+
length = 0x002a # Number of bytes to follow
|
41
|
+
|
42
|
+
grbit = 0x0020 # Option flags
|
43
|
+
chkey = 0x00 # Keyboard shortcut
|
44
|
+
cch = 0x01 # Length of text name
|
45
|
+
cce = 0x001a # Length of text definition
|
46
|
+
unknown01 = 0x0000 #
|
47
|
+
ixals = @worksheet.index + 1 # Sheet index
|
48
|
+
unknown02 = 0x00 #
|
49
|
+
cch_cust_menu = 0x00 # Length of cust menu text
|
50
|
+
cch_description = 0x00 # Length of description text
|
51
|
+
cch_helptopic = 0x00 # Length of help topic text
|
52
|
+
cch_statustext = 0x00 # Length of status bar text
|
53
|
+
rgch = type # Built-in name type
|
54
|
+
|
55
|
+
unknown03 = 0x29
|
56
|
+
unknown04 = 0x0017
|
57
|
+
unknown05 = 0x3b
|
58
|
+
|
59
|
+
header = [record, length].pack("vv")
|
60
|
+
data = [grbit].pack("v")
|
61
|
+
data += [chkey].pack("C")
|
62
|
+
data += [cch].pack("C")
|
63
|
+
data += [cce].pack("v")
|
64
|
+
data += [unknown01].pack("v")
|
65
|
+
data += [ixals].pack("v")
|
66
|
+
data += [unknown02].pack("C")
|
67
|
+
data += [cch_cust_menu].pack("C")
|
68
|
+
data += [cch_description].pack("C")
|
69
|
+
data += [cch_helptopic].pack("C")
|
70
|
+
data += [cch_statustext].pack("C")
|
71
|
+
data += [rgch].pack("C")
|
72
|
+
|
73
|
+
# Column definition
|
74
|
+
data += [unknown03].pack("C")
|
75
|
+
data += [unknown04].pack("v")
|
76
|
+
data += [unknown05].pack("C")
|
77
|
+
data += [ext_ref].pack("v")
|
78
|
+
data += [0x0000].pack("v")
|
79
|
+
data += [0xffff].pack("v")
|
80
|
+
data += [@col_min].pack("v")
|
81
|
+
data += [@col_max].pack("v")
|
82
|
+
|
83
|
+
# Row definition
|
84
|
+
data += [unknown05].pack("C")
|
85
|
+
data += [ext_ref].pack("v")
|
86
|
+
data += [@row_min].pack("v")
|
87
|
+
data += [@row_max].pack("v")
|
88
|
+
data += [0x00].pack("v")
|
89
|
+
data += [0xff].pack("v")
|
90
|
+
# End of data
|
91
|
+
data += [0x10].pack("C")
|
92
|
+
|
93
|
+
[header, data]
|
94
|
+
end
|
95
|
+
|
96
|
+
#
|
97
|
+
# assemble the NAME record in the short format that is used for storing the print
|
98
|
+
# area, repeat rows only and repeat columns only.
|
99
|
+
#
|
100
|
+
# type
|
101
|
+
# ext_ref # TODO
|
102
|
+
# hidden # Name is hidden
|
103
|
+
#
|
104
|
+
def name_record_short(type, ext_ref, hidden = nil) #:nodoc:
|
105
|
+
record = 0x0018 # Record identifier
|
106
|
+
length = 0x001b # Number of bytes to follow
|
107
|
+
|
108
|
+
grbit = 0x0020 # Option flags
|
109
|
+
chkey = 0x00 # Keyboard shortcut
|
110
|
+
cch = 0x01 # Length of text name
|
111
|
+
cce = 0x000b # Length of text definition
|
112
|
+
unknown01 = 0x0000 #
|
113
|
+
ixals = @worksheet.index + 1 # Sheet index
|
114
|
+
unknown02 = 0x00 #
|
115
|
+
cch_cust_menu = 0x00 # Length of cust menu text
|
116
|
+
cch_description = 0x00 # Length of description text
|
117
|
+
cch_helptopic = 0x00 # Length of help topic text
|
118
|
+
cch_statustext = 0x00 # Length of status bar text
|
119
|
+
rgch = type # Built-in name type
|
120
|
+
unknown03 = 0x3b #
|
121
|
+
|
122
|
+
grbit = 0x0021 if hidden
|
123
|
+
|
124
|
+
rowmin = row_min
|
125
|
+
rowmax = row_max
|
126
|
+
rowmin, rowmax = 0x0000, 0xffff unless row_min
|
127
|
+
|
128
|
+
colmin = col_min
|
129
|
+
colmax = col_max
|
130
|
+
colmin, colmax = 0x00, 0xff unless col_min
|
131
|
+
|
132
|
+
header = [record, length].pack("vv")
|
133
|
+
data = [grbit].pack("v")
|
134
|
+
data += [chkey].pack("C")
|
135
|
+
data += [cch].pack("C")
|
136
|
+
data += [cce].pack("v")
|
137
|
+
data += [unknown01].pack("v")
|
138
|
+
data += [ixals].pack("v")
|
139
|
+
data += [unknown02].pack("C")
|
140
|
+
data += [cch_cust_menu].pack("C")
|
141
|
+
data += [cch_description].pack("C")
|
142
|
+
data += [cch_helptopic].pack("C")
|
143
|
+
data += [cch_statustext].pack("C")
|
144
|
+
data += [rgch].pack("C")
|
145
|
+
data += [unknown03].pack("C")
|
146
|
+
data += [ext_ref].pack("v")
|
147
|
+
|
148
|
+
data += [rowmin].pack("v")
|
149
|
+
data += [rowmax].pack("v")
|
150
|
+
data += [colmin].pack("v")
|
151
|
+
data += [colmax].pack("v")
|
152
|
+
|
153
|
+
[header, data]
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
class CellDimension < CellRange
|
158
|
+
def row_min
|
159
|
+
@row_min || 0
|
160
|
+
end
|
161
|
+
|
162
|
+
def col_min
|
163
|
+
@col_min || 0
|
164
|
+
end
|
165
|
+
|
166
|
+
def row_max
|
167
|
+
@row_max || 0
|
168
|
+
end
|
169
|
+
|
170
|
+
def col_max
|
171
|
+
@col_max || 0
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
class PrintRange < CellRange
|
176
|
+
def name_record_short(ext_ref, hidden)
|
177
|
+
super(0x06, ext_ref, hidden) # 0x06 NAME type = Print_Area
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
class TitleRange < CellRange
|
182
|
+
def name_record_long(ext_ref)
|
183
|
+
super(0x07, ext_ref) # 0x07 NAME type = Print_Titles
|
184
|
+
end
|
185
|
+
|
186
|
+
def name_record_short(ext_ref, hidden)
|
187
|
+
super(0x07, ext_ref, hidden) # 0x07 NAME type = Print_Titles
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
class FilterRange < CellRange
|
192
|
+
def name_record_short(ext_ref, hidden)
|
193
|
+
super(0x0D, ext_ref, hidden) # 0x0D NAME type = Filter Database
|
194
|
+
end
|
195
|
+
|
196
|
+
def count
|
197
|
+
if @col_min && @col_max
|
198
|
+
1 + @col_max - @col_min
|
199
|
+
else
|
200
|
+
0
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
204
|
+
def inside?(col)
|
205
|
+
@col_min <= col && col <= @col_max
|
206
|
+
end
|
207
|
+
|
208
|
+
def store
|
209
|
+
record = 0x00EC # Record identifier
|
210
|
+
|
211
|
+
spid = @worksheet.object_ids.spid
|
212
|
+
|
213
|
+
# Number of objects written so far.
|
214
|
+
num_objects = @worksheet.images_size + @worksheet.charts_size
|
215
|
+
|
216
|
+
(0 .. count-1).each do |i|
|
217
|
+
if i == 0 && num_objects
|
218
|
+
spid, data = write_parent_msodrawing_record(count, @worksheet.comments_size, spid, vertices(i))
|
219
|
+
else
|
220
|
+
spid, data = write_child_msodrawing_record(spid, vertices(i))
|
221
|
+
end
|
222
|
+
length = data.bytesize
|
223
|
+
header = [record, length].pack("vv")
|
224
|
+
append(header, data)
|
225
|
+
|
226
|
+
store_obj_filter(num_objects + i + 1, col_min + i)
|
227
|
+
end
|
228
|
+
spid
|
229
|
+
end
|
230
|
+
|
231
|
+
private
|
232
|
+
|
233
|
+
def write_parent_msodrawing_record(num_filters, num_comments, spid, vertices)
|
234
|
+
# Write the parent MSODRAWIING record.
|
235
|
+
dg_length = 168 + 96 * (num_filters - 1)
|
236
|
+
spgr_length = 144 + 96 * (num_filters - 1)
|
237
|
+
|
238
|
+
dg_length += 128 * num_comments
|
239
|
+
spgr_length += 128 * num_comments
|
240
|
+
|
241
|
+
data = store_parent_mso_record(dg_length, spgr_length, spid)
|
242
|
+
spid += 1
|
243
|
+
data += store_child_mso_record(spid, *vertices)
|
244
|
+
spid += 1
|
245
|
+
[spid, data]
|
246
|
+
end
|
247
|
+
|
248
|
+
def write_child_msodrawing_record(spid, vertices)
|
249
|
+
data = store_child_mso_record(spid, *vertices)
|
250
|
+
spid += 1
|
251
|
+
[spid, data]
|
252
|
+
end
|
253
|
+
|
254
|
+
def store_parent_mso_record(dg_length, spgr_length, spid)
|
255
|
+
@worksheet.__send__("store_parent_mso_record", dg_length, spgr_length, spid)
|
256
|
+
end
|
257
|
+
|
258
|
+
def store_child_mso_record(spid, *vertices)
|
259
|
+
@worksheet.__send__("store_child_mso_record", spid, *vertices)
|
260
|
+
end
|
261
|
+
|
262
|
+
def vertices(i)
|
263
|
+
[
|
264
|
+
col_min + i, 0,
|
265
|
+
row_min, 0,
|
266
|
+
col_min + i + 1, 0,
|
267
|
+
row_min + 1, 0
|
268
|
+
]
|
269
|
+
end
|
270
|
+
|
271
|
+
#
|
272
|
+
# Write the OBJ record that is part of filter records.
|
273
|
+
# obj_id # Object ID number.
|
274
|
+
# col
|
275
|
+
#
|
276
|
+
def store_obj_filter(obj_id, col) #:nodoc:
|
277
|
+
record = 0x005D # Record identifier
|
278
|
+
length = 0x0046 # Bytes to follow
|
279
|
+
|
280
|
+
obj_type = 0x0014 # Object type (combo box).
|
281
|
+
data = '' # Record data.
|
282
|
+
|
283
|
+
sub_record = 0x0000 # Sub-record identifier.
|
284
|
+
sub_length = 0x0000 # Length of sub-record.
|
285
|
+
sub_data = '' # Data of sub-record.
|
286
|
+
options = 0x2101
|
287
|
+
reserved = 0x0000
|
288
|
+
|
289
|
+
# Add ftCmo (common object data) subobject
|
290
|
+
sub_record = 0x0015 # ftCmo
|
291
|
+
sub_length = 0x0012
|
292
|
+
sub_data = [obj_type, obj_id, options, reserved, reserved, reserved].pack('vvvVVV')
|
293
|
+
data = [sub_record, sub_length].pack('vv') + sub_data
|
294
|
+
|
295
|
+
# Add ftSbs Scroll bar subobject
|
296
|
+
sub_record = 0x000C # ftSbs
|
297
|
+
sub_length = 0x0014
|
298
|
+
sub_data = ['0000000000000000640001000A00000010000100'].pack('H*')
|
299
|
+
data += [sub_record, sub_length].pack('vv') + sub_data
|
300
|
+
|
301
|
+
# Add ftLbsData (List box data) subobject
|
302
|
+
sub_record = 0x0013 # ftLbsData
|
303
|
+
sub_length = 0x1FEE # Special case (undocumented).
|
304
|
+
|
305
|
+
# If the filter is active we set one of the undocumented flags.
|
306
|
+
|
307
|
+
if @worksheet.instance_variable_get(:@filter_cols)[col]
|
308
|
+
sub_data = ['000000000100010300000A0008005700'].pack('H*')
|
309
|
+
else
|
310
|
+
sub_data = ['00000000010001030000020008005700'].pack('H*')
|
311
|
+
end
|
312
|
+
|
313
|
+
data += [sub_record, sub_length].pack('vv') + sub_data
|
314
|
+
|
315
|
+
# Add ftEnd (end of object) subobject
|
316
|
+
sub_record = 0x0000 # ftNts
|
317
|
+
sub_length = 0x0000
|
318
|
+
data += [sub_record, sub_length].pack('vv')
|
319
|
+
|
320
|
+
# Pack the record.
|
321
|
+
header = [record, length].pack('vv')
|
322
|
+
|
323
|
+
append(header, data)
|
324
|
+
end
|
325
|
+
|
326
|
+
def append(*args)
|
327
|
+
@worksheet.append(*args)
|
328
|
+
end
|
329
|
+
end
|
330
|
+
end
|
331
|
+
|
332
|
+
end
|