write_xlsx 0.58.0 → 0.59.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 (37) hide show
  1. data/README.rdoc +7 -1
  2. data/bin/extract_vba.rb +29 -0
  3. data/examples/add_vba_project.rb +36 -0
  4. data/examples/vbaProject.bin +0 -0
  5. data/lib/write_xlsx/chart.rb +22 -37
  6. data/lib/write_xlsx/package/conditional_format.rb +593 -0
  7. data/lib/write_xlsx/package/content_types.rb +17 -0
  8. data/lib/write_xlsx/package/packager.rb +26 -6
  9. data/lib/write_xlsx/package/relationships.rb +11 -1
  10. data/lib/write_xlsx/package/table.rb +284 -62
  11. data/lib/write_xlsx/utility.rb +179 -0
  12. data/lib/write_xlsx/version.rb +1 -1
  13. data/lib/write_xlsx/workbook.rb +14 -1
  14. data/lib/write_xlsx/worksheet.rb +667 -875
  15. data/test/package/table/test_table01.rb +1 -2
  16. data/test/package/table/test_table02.rb +1 -2
  17. data/test/package/table/test_table03.rb +1 -2
  18. data/test/package/table/test_table04.rb +1 -2
  19. data/test/package/table/test_table05.rb +1 -2
  20. data/test/package/table/test_table06.rb +1 -2
  21. data/test/package/table/test_table07.rb +1 -2
  22. data/test/package/table/test_table08.rb +1 -2
  23. data/test/package/table/test_table09.rb +1 -2
  24. data/test/package/table/test_table10.rb +1 -2
  25. data/test/package/table/test_table11.rb +1 -2
  26. data/test/package/table/test_table12.rb +1 -2
  27. data/test/package/table/test_write_auto_filter.rb +10 -3
  28. data/test/package/table/test_write_table_column.rb +9 -2
  29. data/test/package/table/test_write_table_style_info.rb +12 -11
  30. data/test/package/table/test_write_xml_declaration.rb +6 -1
  31. data/test/perl_output/add_vba_project.xlsm +0 -0
  32. data/test/regression/test_macro01.rb +29 -0
  33. data/test/regression/xlsx_files/macro01.xlsm +0 -0
  34. data/test/regression/xlsx_files/vbaProject01.bin +0 -0
  35. data/test/test_example_match.rb +22 -0
  36. data/test/vbaProject.bin +0 -0
  37. metadata +18 -3
@@ -0,0 +1,593 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ module Writexlsx
4
+ module Package
5
+ class ConditionalFormat
6
+ include Writexlsx::Utility
7
+
8
+ def self.factory(worksheet, *args)
9
+ range, param =
10
+ Package::ConditionalFormat.new(worksheet, nil, nil).
11
+ range_param_for_conditional_formatting(*args)
12
+
13
+ case param[:type]
14
+ when 'cellIs'
15
+ CellIsFormat.new(worksheet, range, param)
16
+ when 'aboveAverage'
17
+ AboveAverageFormat.new(worksheet, range, param)
18
+ when 'top10'
19
+ Top10Format.new(worksheet, range, param)
20
+ when 'containsText', 'notContainsText', 'beginsWith', 'endsWith'
21
+ TextOrWithFormat.new(worksheet, range, param)
22
+ when 'timePeriod'
23
+ TimePeriodFormat.new(worksheet, range, param)
24
+ when 'containsBlanks', 'notContainsBlanks', 'containsErrors', 'notContainsErrors'
25
+ BlanksOrErrorsFormat.new(worksheet, range, param)
26
+ when 'colorScale'
27
+ ColorScaleFormat.new(worksheet, range, param)
28
+ when 'dataBar'
29
+ DataBarFormat.new(worksheet, range, param)
30
+ when 'expression'
31
+ ExpressionFormat.new(worksheet, range, param)
32
+ else # when 'duplicateValues', 'uniqueValues'
33
+ ConditionalFormat.new(worksheet, range, param)
34
+ end
35
+ end
36
+
37
+ attr_reader :range
38
+
39
+ def initialize(worksheet, range, param)
40
+ @worksheet, @range, @param = worksheet, range, param
41
+ @writer = @worksheet.writer
42
+ end
43
+
44
+ def write_cf_rule
45
+ @writer.empty_tag('cfRule', attributes)
46
+ end
47
+
48
+ def write_cf_rule_formula_tag(tag = formula)
49
+ @writer.tag_elements('cfRule', attributes) do
50
+ write_formula_tag(tag)
51
+ end
52
+ end
53
+
54
+ def write_formula_tag(data) #:nodoc:
55
+ data = data.sub(/^=/, '') if data.respond_to?(:sub)
56
+ @writer.data_element('formula', data)
57
+ end
58
+
59
+ #
60
+ # Write the <cfvo> element.
61
+ #
62
+ def write_cfvo(type, val)
63
+ @writer.empty_tag('cfvo', ['type', type, 'val', val])
64
+ end
65
+
66
+ def attributes
67
+ attr = ['type' , type]
68
+ attr << 'dxfId' << format if format
69
+ attr << 'priority' << priority
70
+ attr
71
+ end
72
+
73
+ def type
74
+ @param[:type]
75
+ end
76
+
77
+ def format
78
+ @param[:format]
79
+ end
80
+
81
+ def priority
82
+ @param[:priority]
83
+ end
84
+
85
+ def criteria
86
+ @param[:criteria]
87
+ end
88
+
89
+ def maximum
90
+ @param[:maximum]
91
+ end
92
+
93
+ def minimum
94
+ @param[:minimum]
95
+ end
96
+
97
+ def value
98
+ @param[:value]
99
+ end
100
+
101
+ def direction
102
+ @param[:direction]
103
+ end
104
+
105
+ def formula
106
+ @param[:formula]
107
+ end
108
+
109
+ def min_type
110
+ @param[:min_type]
111
+ end
112
+ def min_value
113
+ @param[:min_value]
114
+ end
115
+
116
+ def min_color
117
+ @param[:min_color]
118
+ end
119
+
120
+ def mid_type
121
+ @param[:mid_type]
122
+ end
123
+
124
+ def mid_value
125
+ @param[:mid_value]
126
+ end
127
+
128
+ def mid_color
129
+ @param[:mid_color]
130
+ end
131
+
132
+ def max_type
133
+ @param[:max_type]
134
+ end
135
+
136
+ def max_value
137
+ @param[:max_value]
138
+ end
139
+
140
+ def max_color
141
+ @param[:max_color]
142
+ end
143
+
144
+ def bar_color
145
+ @param[:bar_color]
146
+ end
147
+
148
+ def range_param_for_conditional_formatting(*args) # :nodoc:
149
+ range_start_cell_for_conditional_formatting(*args)
150
+ param_for_conditional_formatting(*args)
151
+
152
+ handling_of_text_criteria if @param[:type] == 'text'
153
+ handling_of_time_period_criteria if @param[:type] == 'timePeriod'
154
+ handling_of_blanks_error_types
155
+
156
+ [@range, @param]
157
+ end
158
+
159
+ private
160
+
161
+ def handling_of_text_criteria
162
+ case @param[:criteria]
163
+ when 'containsText'
164
+ @param[:type] = 'containsText';
165
+ @param[:formula] =
166
+ %Q!NOT(ISERROR(SEARCH("#{@param[:value]}",#{@start_cell})))!
167
+ when 'notContains'
168
+ @param[:type] = 'notContainsText';
169
+ @param[:formula] =
170
+ %Q!ISERROR(SEARCH("#{@param[:value]}",#{@start_cell}))!
171
+ when 'beginsWith'
172
+ @param[:type] = 'beginsWith'
173
+ @param[:formula] =
174
+ %Q!LEFT(#{@start_cell},#{@param[:value].size})="#{@param[:value]}"!
175
+ when 'endsWith'
176
+ @param[:type] = 'endsWith'
177
+ @param[:formula] =
178
+ %Q!RIGHT(#{@start_cell},#{@param[:value].size})="#{@param[:value]}"!
179
+ else
180
+ raise "Invalid text criteria '#{@param[:criteria]} in conditional_formatting()"
181
+ end
182
+ end
183
+
184
+ def handling_of_time_period_criteria
185
+ case @param[:criteria]
186
+ when 'yesterday'
187
+ @param[:formula] = "FLOOR(#{@start_cell},1)=TODAY()-1"
188
+ when 'today'
189
+ @param[:formula] = "FLOOR(#{@start_cell},1)=TODAY()"
190
+ when 'tomorrow'
191
+ @param[:formula] = "FLOOR(#{@start_cell},1)=TODAY()+1"
192
+ when 'last7Days'
193
+ @param[:formula] =
194
+ "AND(TODAY()-FLOOR(#{@start_cell},1)<=6,FLOOR(#{@start_cell},1)<=TODAY())"
195
+ when 'lastWeek'
196
+ @param[:formula] =
197
+ "AND(TODAY()-ROUNDDOWN(#{@start_cell},0)>=(WEEKDAY(TODAY())),TODAY()-ROUNDDOWN(#{@start_cell},0)<(WEEKDAY(TODAY())+7))"
198
+ when 'thisWeek'
199
+ @param[:formula] =
200
+ "AND(TODAY()-ROUNDDOWN(#{@start_cell},0)<=WEEKDAY(TODAY())-1,ROUNDDOWN(#{@start_cell},0)-TODAY()<=7-WEEKDAY(TODAY()))"
201
+ when 'nextWeek'
202
+ @param[:formula] =
203
+ "AND(ROUNDDOWN(#{@start_cell},0)-TODAY()>(7-WEEKDAY(TODAY())),ROUNDDOWN(#{@start_cell},0)-TODAY()<(15-WEEKDAY(TODAY())))"
204
+ when 'lastMonth'
205
+ @param[:formula] =
206
+ "AND(MONTH(#{@start_cell})=MONTH(TODAY())-1,OR(YEAR(#{@start_cell})=YEAR(TODAY()),AND(MONTH(#{@start_cell})=1,YEAR(A1)=YEAR(TODAY())-1)))"
207
+ when 'thisMonth'
208
+ @param[:formula] =
209
+ "AND(MONTH(#{@start_cell})=MONTH(TODAY()),YEAR(#{@start_cell})=YEAR(TODAY()))"
210
+ when 'nextMonth'
211
+ @param[:formula] =
212
+ "AND(MONTH(#{@start_cell})=MONTH(TODAY())+1,OR(YEAR(#{@start_cell})=YEAR(TODAY()),AND(MONTH(#{@start_cell})=12,YEAR(#{@start_cell})=YEAR(TODAY())+1)))"
213
+ else
214
+ raise "Invalid time_period criteria '#{@param[:criteria]}' in conditional_formatting()"
215
+ end
216
+ end
217
+
218
+ def handling_of_blanks_error_types
219
+ # Special handling of blanks/error types.
220
+ case @param[:type]
221
+ when 'containsBlanks'
222
+ @param[:formula] = "LEN(TRIM(#{@start_cell}))=0"
223
+ when 'notContainsBlanks'
224
+ @param[:formula] = "LEN(TRIM(#{@start_cell}))>0"
225
+ when 'containsErrors'
226
+ @param[:formula] = "ISERROR(#{@start_cell})"
227
+ when 'notContainsErrors'
228
+ @param[:formula] = "NOT(ISERROR(#{@start_cell}))"
229
+ when '2_color_scale'
230
+ @param[:type] = 'colorScale'
231
+
232
+ # Color scales don't use any additional formatting.
233
+ @param[:format] = nil
234
+
235
+ # Turn off 3 color parameters.
236
+ @param[:mid_type] = nil
237
+ @param[:mid_color] = nil
238
+
239
+ @param[:min_type] ||= 'min'
240
+ @param[:max_type] ||= 'max'
241
+ @param[:min_value] ||= 0
242
+ @param[:max_value] ||= 0
243
+ @param[:min_color] ||= '#FF7128'
244
+ @param[:max_color] ||= '#FFEF9C'
245
+
246
+ @param[:max_color] = get_palette_color( @param[:max_color] )
247
+ @param[:min_color] = get_palette_color( @param[:min_color] )
248
+ when '3_color_scale'
249
+ @param[:type] = 'colorScale'
250
+
251
+ # Color scales don't use any additional formatting.
252
+ @param[:format] = nil
253
+
254
+ @param[:min_type] ||= 'min'
255
+ @param[:mid_type] ||= 'percentile'
256
+ @param[:max_type] ||= 'max'
257
+ @param[:min_value] ||= 0
258
+ @param[:mid_value] ||= 50
259
+ @param[:max_value] ||= 0
260
+ @param[:min_color] ||= '#F8696B'
261
+ @param[:mid_color] ||= '#FFEB84'
262
+ @param[:max_color] ||= '#63BE7B'
263
+
264
+ @param[:max_color] = get_palette_color(@param[:max_color])
265
+ @param[:mid_color] = get_palette_color(@param[:mid_color])
266
+ @param[:min_color] = get_palette_color(@param[:min_color])
267
+ when 'dataBar'
268
+ # Color scales don't use any additional formatting.
269
+ @param[:format] = nil
270
+
271
+ @param[:min_type] ||= 'min'
272
+ @param[:max_type] ||= 'max'
273
+ @param[:min_value] ||= 0
274
+ @param[:max_value] ||= 0
275
+ @param[:bar_color] ||= '#638EC6'
276
+
277
+ @param[:bar_color] = get_palette_color(@param[:bar_color])
278
+ end
279
+ end
280
+
281
+ def get_palette_color(index)
282
+ @worksheet.get_palette_color(index)
283
+ end
284
+
285
+ def range_start_cell_for_conditional_formatting(*args) # :nodoc:
286
+ row1, row2, col1, col2, user_range, param =
287
+ row_col_param_for_conditional_formatting(*args)
288
+ # If the first and last cell are the same write a single cell.
289
+ if row1 == row2 && col1 == col2
290
+ range = xl_rowcol_to_cell(row1, col1)
291
+ @start_cell = range
292
+ else
293
+ range = xl_range(row1, row2, col1, col2)
294
+ @start_cell = xl_rowcol_to_cell(row1, col1)
295
+ end
296
+
297
+ # Override with user defined multiple range if provided.
298
+ range = user_range if user_range
299
+
300
+ @range = range
301
+ end
302
+
303
+ def row_col_param_for_conditional_formatting(*args)
304
+ # Check for a cell reference in A1 notation and substitute row and column
305
+ if args[0] =~ /^\D/
306
+ # Check for a user defined multiple range like B3:K6,B8:K11.
307
+ user_range = args[0].gsub(/\s*,\s*/, ' ').gsub(/\$/, '') if args[0] =~ /,/
308
+ end
309
+
310
+ row1, col1, row2, col2, param = row_col_notation(args)
311
+ if row2.respond_to?(:keys)
312
+ param = row2
313
+ row2, col2 = row1, col1
314
+ end
315
+ raise WriteXLSXInsufficientArgumentError if [row1, col1, row2, col2, param].include?(nil)
316
+
317
+ # Check that row and col are valid without storing the values.
318
+ check_dimensions(row1, col1)
319
+ check_dimensions(row2, col2)
320
+
321
+ # Swap last row/col for first row/col as necessary
322
+ row1, row2 = row2, row1 if row1 > row2
323
+ col1, col2 = col2, col1 if col1 > col2
324
+
325
+ [row1, row2, col1, col2, user_range, param]
326
+ end
327
+
328
+ def param_for_conditional_formatting(*args) # :nodoc:
329
+ dummy, dummy, dummy, dummy, dummy, @param =
330
+ row_col_param_for_conditional_formatting(*args)
331
+ check_conditional_formatting_parameters(@param)
332
+
333
+ @param[:format] = @param[:format].get_dxf_index if @param[:format]
334
+ @param[:priority] = @worksheet.dxf_priority
335
+ @worksheet.dxf_priority += 1
336
+ end
337
+
338
+ def check_conditional_formatting_parameters(param) # :nodoc:
339
+ # Check for valid input parameters.
340
+ unless (param.keys.uniq - valid_parameter_for_conditional_formatting).empty? &&
341
+ param.has_key?(:type) &&
342
+ valid_type_for_conditional_formatting.has_key?(param[:type].downcase)
343
+ raise WriteXLSXOptionParameterError, "Invalid type : #{param[:type]}"
344
+ end
345
+
346
+ param[:direction] = 'bottom' if param[:type] == 'bottom'
347
+ param[:type] = valid_type_for_conditional_formatting[param[:type].downcase]
348
+
349
+ # Check for valid criteria types.
350
+ if param.has_key?(:criteria) && valid_criteria_type_for_conditional_formatting.has_key?(param[:criteria].downcase)
351
+ param[:criteria] = valid_criteria_type_for_conditional_formatting[param[:criteria].downcase]
352
+ end
353
+
354
+ # Convert date/times value if required.
355
+ if %w[date time cellIs].include?(param[:type])
356
+ param[:type] = 'cellIs'
357
+
358
+ param[:value] = convert_date_time_if_required(param[:value])
359
+ param[:minimum] = convert_date_time_if_required(param[:minimum])
360
+ param[:maximum] = convert_date_time_if_required(param[:maximum])
361
+ end
362
+
363
+ # 'Between' and 'Not between' criteria require 2 values.
364
+ if param[:criteria] == 'between' || param[:criteria] == 'notBetween'
365
+ unless param.has_key?(:minimum) || param.has_key?(:maximum)
366
+ raise WriteXLSXOptionParameterError, "Invalid criteria : #{param[:criteria]}"
367
+ end
368
+ else
369
+ param[:minimum] = nil
370
+ param[:maximum] = nil
371
+ end
372
+
373
+ # Convert date/times value if required.
374
+ if param[:type] == 'date' || param[:type] == 'time'
375
+ unless convert_date_time_value(param, :value) || convert_date_time_value(param, :maximum)
376
+ raise WriteXLSXOptionParameterError
377
+ end
378
+ end
379
+ end
380
+
381
+ def convert_date_time_if_required(val)
382
+ if val =~ /T/
383
+ date_time = convert_date_time(val)
384
+ raise "Invalid date/time value '#{val}' in conditional_formatting()" unless date_time
385
+ date_time
386
+ else
387
+ val
388
+ end
389
+ end
390
+
391
+ # List of valid input parameters for conditional_formatting.
392
+ def valid_parameter_for_conditional_formatting
393
+ [
394
+ :type,
395
+ :format,
396
+ :criteria,
397
+ :value,
398
+ :minimum,
399
+ :maximum,
400
+ :min_type,
401
+ :mid_type,
402
+ :max_type,
403
+ :min_value,
404
+ :mid_value,
405
+ :max_value,
406
+ :min_color,
407
+ :mid_color,
408
+ :max_color,
409
+ :bar_color
410
+ ]
411
+ end
412
+
413
+ # List of valid validation types for conditional_formatting.
414
+ def valid_type_for_conditional_formatting
415
+ {
416
+ 'cell' => 'cellIs',
417
+ 'date' => 'date',
418
+ 'time' => 'time',
419
+ 'average' => 'aboveAverage',
420
+ 'duplicate' => 'duplicateValues',
421
+ 'unique' => 'uniqueValues',
422
+ 'top' => 'top10',
423
+ 'bottom' => 'top10',
424
+ 'text' => 'text',
425
+ 'time_period' => 'timePeriod',
426
+ 'blanks' => 'containsBlanks',
427
+ 'no_blanks' => 'notContainsBlanks',
428
+ 'errors' => 'containsErrors',
429
+ 'no_errors' => 'notContainsErrors',
430
+ '2_color_scale' => '2_color_scale',
431
+ '3_color_scale' => '3_color_scale',
432
+ 'data_bar' => 'dataBar',
433
+ 'formula' => 'expression'
434
+ }
435
+ end
436
+
437
+ # List of valid criteria types for conditional_formatting.
438
+ def valid_criteria_type_for_conditional_formatting
439
+ {
440
+ 'between' => 'between',
441
+ 'not between' => 'notBetween',
442
+ 'equal to' => 'equal',
443
+ '=' => 'equal',
444
+ '==' => 'equal',
445
+ 'not equal to' => 'notEqual',
446
+ '!=' => 'notEqual',
447
+ '<>' => 'notEqual',
448
+ 'greater than' => 'greaterThan',
449
+ '>' => 'greaterThan',
450
+ 'less than' => 'lessThan',
451
+ '<' => 'lessThan',
452
+ 'greater than or equal to' => 'greaterThanOrEqual',
453
+ '>=' => 'greaterThanOrEqual',
454
+ 'less than or equal to' => 'lessThanOrEqual',
455
+ '<=' => 'lessThanOrEqual',
456
+ 'containing' => 'containsText',
457
+ 'not containing' => 'notContains',
458
+ 'begins with' => 'beginsWith',
459
+ 'ends with' => 'endsWith',
460
+ 'yesterday' => 'yesterday',
461
+ 'today' => 'today',
462
+ 'last 7 days' => 'last7Days',
463
+ 'last week' => 'lastWeek',
464
+ 'this week' => 'thisWeek',
465
+ 'next week' => 'nextWeek',
466
+ 'last month' => 'lastMonth',
467
+ 'this month' => 'thisMonth',
468
+ 'next month' => 'nextMonth'
469
+ }
470
+ end
471
+
472
+ def date_1904?
473
+ @worksheet.date_1904?
474
+ end
475
+ end
476
+
477
+ class CellIsFormat < ConditionalFormat
478
+ def attributes
479
+ super << 'operator' << criteria
480
+ end
481
+
482
+ def write_cf_rule
483
+ if minimum && maximum
484
+ @writer.tag_elements('cfRule', attributes) do
485
+ write_formula_tag(minimum)
486
+ write_formula_tag(maximum)
487
+ end
488
+ else
489
+ write_cf_rule_formula_tag(value)
490
+ end
491
+ end
492
+ end
493
+
494
+ class AboveAverageFormat < ConditionalFormat
495
+ def attributes
496
+ attr = super
497
+ attr << 'aboveAverage' << 0 if criteria =~ /below/
498
+ attr << 'equalAverage' << 1 if criteria =~ /equal/
499
+ if criteria =~ /([123]) std dev/
500
+ attr << 'stdDev' << $~[1]
501
+ end
502
+ attr
503
+ end
504
+ end
505
+
506
+ class Top10Format < ConditionalFormat
507
+ def attributes
508
+ attr = super
509
+ attr << 'percent' << 1 if criteria == '%'
510
+ attr << 'bottom' << 1 if direction
511
+ attr << 'rank' << (value || 10)
512
+ attr
513
+ end
514
+ end
515
+
516
+ class TextOrWithFormat < ConditionalFormat
517
+ def attributes
518
+ attr = super
519
+ attr << 'operator' << criteria
520
+ attr << 'text' << value
521
+ attr
522
+ end
523
+
524
+ def write_cf_rule
525
+ write_cf_rule_formula_tag
526
+ end
527
+ end
528
+
529
+ class TimePeriodFormat < ConditionalFormat
530
+ def attributes
531
+ super << 'timePeriod' << criteria
532
+ end
533
+
534
+ def write_cf_rule
535
+ write_cf_rule_formula_tag
536
+ end
537
+ end
538
+
539
+ class BlanksOrErrorsFormat < ConditionalFormat
540
+ def write_cf_rule
541
+ write_cf_rule_formula_tag
542
+ end
543
+ end
544
+
545
+ class ColorScaleFormat < ConditionalFormat
546
+ def write_cf_rule
547
+ @writer.tag_elements('cfRule', attributes) do
548
+ write_color_scale
549
+ end
550
+ end
551
+
552
+ #
553
+ # Write the <colorScale> element.
554
+ #
555
+ def write_color_scale
556
+ @writer.tag_elements('colorScale') do
557
+ write_cfvo(min_type, min_value)
558
+ write_cfvo(mid_type, mid_value) if mid_type
559
+ write_cfvo(max_type, max_value)
560
+ write_color(@writer, 'rgb', min_color)
561
+ write_color(@writer, 'rgb', mid_color) if mid_color
562
+ write_color(@writer, 'rgb', max_color)
563
+ end
564
+ end
565
+ end
566
+
567
+ class DataBarFormat < ConditionalFormat
568
+ def write_cf_rule
569
+ @writer.tag_elements('cfRule', attributes) do
570
+ write_data_bar
571
+ end
572
+ end
573
+
574
+ #
575
+ # Write the <dataBar> element.
576
+ #
577
+ def write_data_bar
578
+ @writer.tag_elements('dataBar') do
579
+ write_cfvo(min_type, min_value)
580
+ write_cfvo(max_type, max_value)
581
+
582
+ write_color(@writer, 'rgb', bar_color)
583
+ end
584
+ end
585
+ end
586
+
587
+ class ExpressionFormat < ConditionalFormat
588
+ def write_cf_rule
589
+ write_cf_rule_formula_tag(criteria)
590
+ end
591
+ end
592
+ end
593
+ end
@@ -137,12 +137,29 @@ module Writexlsx
137
137
  )
138
138
  end
139
139
 
140
+ #
141
+ # Add a vbaProject to the ContentTypes defaults.
142
+ #
143
+ def add_vba_project
144
+ change_the_workbook_xml_content_type_from_xlsx_to_xlsm
145
+ add_default('bin', 'application/vnd.ms-office.vbaProject')
146
+ end
147
+
140
148
  private
141
149
 
142
150
  def write_xml_declaration
143
151
  @writer.xml_decl
144
152
  end
145
153
 
154
+ def change_the_workbook_xml_content_type_from_xlsx_to_xlsm
155
+ @overrides.collect! do |arr|
156
+ if arr[0] == '/xl/workbook.xml'
157
+ arr[1] = 'application/vnd.ms-excel.sheet.macroEnabled.main+xml'
158
+ end
159
+ arr
160
+ end
161
+ end
162
+
146
163
  #
147
164
  # Write out all of the <Default> types.
148
165
  #
@@ -78,6 +78,7 @@ module Writexlsx
78
78
  write_chartsheet_rels_files
79
79
  write_drawing_rels_files
80
80
  add_image_files
81
+ add_vba_project
81
82
  end
82
83
 
83
84
  private
@@ -281,6 +282,9 @@ module Writexlsx
281
282
  # Add the sharedString rel if there is string data in the workbook.
282
283
  content.add_shared_strings unless @workbook.shared_strings_empty?
283
284
 
285
+ # Add vbaProject if present.
286
+ content.add_vba_project if @workbook.vba_project
287
+
284
288
  content.set_xml_writer("#{@package_dir}/[Content_Types].xml")
285
289
  content.assemble_xml_file
286
290
  end
@@ -344,11 +348,8 @@ module Writexlsx
344
348
 
345
349
  FileUtils.mkdir_p("#{dir}/xl/tables")
346
350
 
347
- table_props.each do |table_prop|
348
- table = Package::Table.new
349
-
351
+ table_props.each do |table|
350
352
  table.set_xml_writer("#{dir}/xl/tables/table#{index}.xml")
351
- table.set_properties(table_prop)
352
353
  table.assemble_xml_file
353
354
  index += 1
354
355
  @table_count += 1
@@ -366,7 +367,7 @@ module Writexlsx
366
367
 
367
368
  rels.add_document_relationship('/officeDocument', 'xl/workbook.xml')
368
369
  rels.add_package_relationship('/metadata/core-properties',
369
- 'docProps/core')
370
+ 'docProps/core.xml')
370
371
  rels.add_document_relationship('/extended-properties', 'docProps/app.xml')
371
372
  rels.set_xml_writer("#{@package_dir}/_rels/.rels" )
372
373
  rels.assemble_xml_file
@@ -398,6 +399,12 @@ module Writexlsx
398
399
 
399
400
  # Add the sharedString rel if there is string data in the workbook.
400
401
  rels.add_document_relationship('/sharedStrings', 'sharedStrings.xml') unless @workbook.shared_strings_empty?
402
+
403
+ # Add vbaProject if present.
404
+ if @workbook.vba_project
405
+ rels.add_ms_package_relationship('/vbaProject', 'vbaProject.bin')
406
+ end
407
+
401
408
  rels.set_xml_writer("#{@package_dir}/xl/_rels/workbook.xml.rels")
402
409
  rels.assemble_xml_file
403
410
  end
@@ -508,7 +515,7 @@ module Writexlsx
508
515
 
509
516
 
510
517
  #
511
- # Write the workbook.xml file.
518
+ # Write the /xl/media/image?.xml files.
512
519
  #
513
520
  def add_image_files
514
521
  return if @workbook.images.empty?
@@ -525,6 +532,19 @@ module Writexlsx
525
532
  index += 1
526
533
  end
527
534
  end
535
+
536
+ #
537
+ # Write the vbaProject.bin file.
538
+ #
539
+ def add_vba_project
540
+ dir = @package_dir
541
+ vba_project = @workbook.vba_project
542
+
543
+ return unless vba_project
544
+
545
+ FileUtils.mkdir_p("#{dir}/xl")
546
+ FileUtils.copy(vba_project, "#{dir}/xl/vbaProject.bin")
547
+ end
528
548
  end
529
549
  end
530
550
  end