writeexcel 0.6.9 → 0.6.10

Sign up to get free protection for your applications and to get access to all the features.
Files changed (59) hide show
  1. data/README.rdoc +2 -0
  2. data/VERSION +1 -1
  3. data/lib/writeexcel/biffwriter.rb +29 -43
  4. data/lib/writeexcel/cell_range.rb +332 -0
  5. data/lib/writeexcel/chart.rb +50 -51
  6. data/lib/writeexcel/col_info.rb +87 -0
  7. data/lib/writeexcel/comments.rb +456 -0
  8. data/lib/writeexcel/convert_date_time.rb +117 -0
  9. data/lib/writeexcel/data_validations.rb +370 -0
  10. data/lib/writeexcel/debug_info.rb +5 -1
  11. data/lib/writeexcel/embedded_chart.rb +35 -0
  12. data/lib/writeexcel/format.rb +1 -1
  13. data/lib/writeexcel/formula.rb +3 -3
  14. data/lib/writeexcel/helper.rb +3 -0
  15. data/lib/writeexcel/image.rb +61 -1
  16. data/lib/writeexcel/olewriter.rb +2 -8
  17. data/lib/writeexcel/outline.rb +24 -0
  18. data/lib/writeexcel/shared_string_table.rb +153 -0
  19. data/lib/writeexcel/workbook.rb +86 -444
  20. data/lib/writeexcel/worksheet.rb +693 -2029
  21. data/lib/writeexcel/worksheets.rb +25 -0
  22. data/lib/writeexcel/write_file.rb +34 -15
  23. data/test/test_02_merge_formats.rb +0 -4
  24. data/test/test_04_dimensions.rb +0 -4
  25. data/test/test_05_rows.rb +0 -4
  26. data/test/test_06_extsst.rb +3 -6
  27. data/test/test_11_date_time.rb +0 -4
  28. data/test/test_12_date_only.rb +262 -231
  29. data/test/test_13_date_seconds.rb +0 -4
  30. data/test/test_21_escher.rb +71 -84
  31. data/test/test_22_mso_drawing_group.rb +0 -4
  32. data/test/test_23_note.rb +5 -21
  33. data/test/test_24_txo.rb +6 -7
  34. data/test/test_25_position_object.rb +0 -4
  35. data/test/test_26_autofilter.rb +0 -5
  36. data/test/test_27_autofilter.rb +0 -5
  37. data/test/test_28_autofilter.rb +0 -5
  38. data/test/test_29_process_jpg.rb +1 -1
  39. data/test/test_30_validation_dval.rb +4 -7
  40. data/test/test_31_validation_dv_strings.rb +9 -12
  41. data/test/test_32_validation_dv_formula.rb +11 -14
  42. data/test/test_42_set_properties.rb +0 -3
  43. data/test/test_50_name_stored.rb +0 -4
  44. data/test/test_51_name_print_area.rb +0 -4
  45. data/test/test_52_name_print_titles.rb +0 -4
  46. data/test/test_53_autofilter.rb +0 -4
  47. data/test/test_60_chart_generic.rb +42 -46
  48. data/test/test_61_chart_subclasses.rb +7 -11
  49. data/test/test_62_chart_formats.rb +12 -16
  50. data/test/test_63_chart_area_formats.rb +3 -7
  51. data/test/test_biff.rb +0 -4
  52. data/test/test_big_workbook.rb +17 -0
  53. data/test/test_format.rb +0 -3
  54. data/test/test_ole.rb +0 -4
  55. data/test/test_storage_lite.rb +0 -9
  56. data/test/test_workbook.rb +0 -4
  57. data/test/test_worksheet.rb +3 -7
  58. data/writeexcel.gemspec +12 -2
  59. metadata +12 -2
@@ -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.9
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 :byte_order, :data, :datasize
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
- @data = ''
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 = 0 # Little Endian
50
+ @byte_order = false # Little Endian
63
51
  elsif number == teststr.reverse
64
- @byte_order = 1 # Big Endian
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 != 0
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
- # in perl
173
- # $tmp = substr($data, 0, $limit, "");
174
- if data.bytesize > @limit
175
- tmp = data[0, @limit]
176
- data[0, @limit] = ''
177
- else
178
- tmp = data.dup
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