write_xlsx 0.58.0 → 0.59.0

Sign up to get free protection for your applications and to get access to all the features.
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
@@ -44,7 +44,17 @@ module Writexlsx
44
44
  #
45
45
  def add_package_relationship(type, target)
46
46
  type = Package_schema + type
47
- target = target + '.xml'
47
+ target = target
48
+
49
+ @rels.push([type, target])
50
+ end
51
+
52
+ #
53
+ # Add container relationship to XLSX .rels xml files. Uses MS schema.
54
+ #
55
+ def add_ms_package_relationship(type, target)
56
+ schema = 'http://schemas.microsoft.com/office/2006/relationships'
57
+ type = schema + type
48
58
 
49
59
  @rels.push([type, target])
50
60
  end
@@ -8,11 +8,47 @@ module Writexlsx
8
8
  class Table
9
9
  include Writexlsx::Utility
10
10
 
11
- attr_writer :properties
11
+ class ColumnData
12
+ attr_reader :id
13
+ attr_accessor :name, :format, :formula
14
+ attr_accessor :total_string, :total_function
15
+
16
+ def initialize(id, param = {})
17
+ @id = id
18
+ @name = "Column#{id}"
19
+ @total_string = ''
20
+ @total_function = ''
21
+ @formula = ''
22
+ @format = nil
23
+ @user_data = param[id-1] if param
24
+ end
25
+ end
26
+
27
+ attr_reader :id
28
+
29
+ def initialize(worksheet, id, *args)
30
+ @worksheet = worksheet
31
+ @writer = Package::XMLWriterSimple.new
32
+ @id = id
33
+
34
+ @row1, @row2, @col1, @col2, @param = handle_args(*args)
35
+ @columns = []
36
+ @col_formats = []
37
+
38
+ # Set the data range rows (without the header and footer).
39
+ @first_data_row = @row1
40
+ @first_data_row += 1 if ptrue?(@param[:header_row])
41
+ @last_data_row = @row2
42
+ @last_data_row -= 1 if @param[:total_row]
12
43
 
13
- def initialize
14
- @writer = Package::XMLWriterSimple.new
15
- @properties = {}
44
+ set_the_table_options
45
+ set_the_table_style
46
+ set_the_table_name
47
+ set_the_table_and_autofilter_ranges
48
+ set_the_autofilter_range
49
+
50
+ add_the_table_columns
51
+ write_the_cell_data_if_supplied
16
52
  end
17
53
 
18
54
  def set_xml_writer(filename)
@@ -41,33 +77,237 @@ module Writexlsx
41
77
  @writer.close
42
78
  end
43
79
 
44
- #
45
- # Set the document properties.
46
- #
47
- def set_properties(properties)
48
- @properties = properties
80
+ def add_the_table_columns
81
+ col_id = 0
82
+ (@col1..@col2).each do |col_num|
83
+ # Set up the default column data.
84
+ col_data = Package::Table::ColumnData.new(col_id + 1, @param[:columns])
85
+
86
+ overrite_the_defaults_with_any_use_defined_values(col_id, col_data, col_num)
87
+
88
+ # Store the column data.
89
+ @columns << col_data
90
+
91
+ write_the_column_headers_to_the_worksheet(col_num, col_data)
92
+
93
+ col_id += 1
94
+ end # Table columns.
95
+ end
96
+
97
+ def overrite_the_defaults_with_any_use_defined_values(col_id, col_data, col_num)
98
+ if @param[:columns]
99
+ # Check if there are user defined values for this column.
100
+ if user_data = @param[:columns][col_id]
101
+ # Map user defined values to internal values.
102
+ if user_data[:header] && !user_data[:header].empty?
103
+ col_data.name = user_data[:header]
104
+ end
105
+ # Handle the column formula.
106
+ handle_the_column_formula(
107
+ col_data, col_num, user_data[:formula], user_data[:format]
108
+ )
109
+
110
+ # Handle the function for the total row.
111
+ if user_data[:total_function]
112
+ handle_the_function_for_the_table_row(
113
+ @row2, col_data, col_num,
114
+ user_data[:total_function],
115
+ user_data[:format]
116
+ )
117
+ elsif user_data[:total_string]
118
+ total_label_only(
119
+ @row2, col_num, col_data, user_data[:total_string], user_data[:format]
120
+ )
121
+ end
122
+
123
+ # Get the dxf format index.
124
+ if user_data[:format]
125
+ col_data.format = user_data[:format].get_dxf_index
126
+ end
127
+
128
+ # Store the column format for writing the cell data.
129
+ # It doesn't matter if it is undefined.
130
+ @col_formats[col_id] = user_data[:format]
131
+ end
132
+ end
133
+ end
134
+
135
+ def write_the_column_headers_to_the_worksheet(col_num, col_data)
136
+ if @param[:header_row] != 0
137
+ @worksheet.write_string(@row1, col_num, col_data.name)
138
+ end
139
+ end
140
+
141
+ def write_the_cell_data_if_supplied
142
+ return unless @param[:data]
143
+
144
+ data = @param[:data]
145
+ i = 0 # For indexing the row data.
146
+ (@first_data_row..@last_data_row).each do |row|
147
+ next unless data[i]
148
+
149
+ j = 0 # For indexing the col data.
150
+ (@col1..@col2).each do |col|
151
+ token = data[i][j]
152
+ @worksheet.write(row, col, token, @col_formats[j]) if token
153
+ j += 1
154
+ end
155
+ i += 1
156
+ end
49
157
  end
50
158
 
51
159
  private
52
160
 
53
- #
54
- # Write the XML declaration.
55
- #
56
- def write_xml_declaration
57
- @writer.xml_decl('UTF-8', 1)
161
+ def handle_args(*args)
162
+ # Check for a cell reference in A1 notation and substitute row and column
163
+ row1, col1, row2, col2, param = row_col_notation(args)
164
+
165
+ # Check for a valid number of args.
166
+ raise "Not enough parameters to add_table()" if [row1, col1, row2, col2].include?(nil)
167
+
168
+ # Check that row and col are valid without storing the values.
169
+ check_dimensions_and_update_max_min_values(row1, col1, 1, 1)
170
+ check_dimensions_and_update_max_min_values(row2, col2, 1, 1)
171
+
172
+ # Swap last row/col for first row/col as necessary.
173
+ row1, row2 = row2, row1 if row1 > row2
174
+ col1, col2 = col2, col1 if col1 > col2
175
+
176
+ # The final hash contains the validation parameters.
177
+ param ||= {}
178
+
179
+ # Turn on Excel's defaults.
180
+ param[:banded_rows] ||= 1
181
+ param[:header_row] ||= 1
182
+ param[:autofilter] ||= 1
183
+
184
+ # If the header row if off the default is to turn autofilter off.
185
+ param[:autofilter] = 0 if param[:header_row] == 0
186
+
187
+ check_parameter(param, valid_table_parameter, 'add_table')
188
+
189
+ [row1, row2, col1, col2, param]
190
+ end
191
+
192
+ # List of valid input parameters.
193
+ def valid_table_parameter
194
+ [
195
+ :autofilter,
196
+ :banded_columns,
197
+ :banded_rows,
198
+ :columns,
199
+ :data,
200
+ :first_column,
201
+ :header_row,
202
+ :last_column,
203
+ :name,
204
+ :style,
205
+ :total_row
206
+ ]
207
+ end
208
+
209
+ def handle_the_column_formula(col_data, col_num, formula, format)
210
+ return unless formula
211
+
212
+ # Remove the leading = from formula.
213
+ formula.sub!(/^=/, '')
214
+ # Covert Excel 2010 "@" ref to 2007 "#This Row".
215
+ formula.gsub!(/@/,'[#This Row],')
216
+
217
+ col_data.formula = formula
218
+
219
+ (@first_data_row..@last_data_row).each do |row|
220
+ @worksheet.write_formula(row, col_num, formula, format)
221
+ end
222
+ end
223
+
224
+ def handle_the_function_for_the_table_row(row2, col_data, col_num, total_function, format)
225
+ function = total_function
226
+
227
+ # Massage the function name.
228
+ function = function.downcase
229
+ function.gsub!(/_/, '')
230
+ function.gsub!(/\s/,'')
231
+
232
+ function = 'countNums' if function == 'countnums'
233
+ function = 'stdDev' if function == 'stddev'
234
+
235
+ col_data.total_function = function
236
+
237
+ formula = table_function_to_formula(function, col_data.name)
238
+ @worksheet.write_formula(row2, col_num, formula, format)
58
239
  end
59
240
 
60
241
  #
61
- # Write the <autoFilter> element.
242
+ # Convert a table total function to a worksheet formula.
62
243
  #
63
- def write_auto_filter
64
- autofilter = @properties[:_autofilter]
244
+ def table_function_to_formula(function, col_name)
245
+ subtotals = {
246
+ :average => 101,
247
+ :countNums => 102,
248
+ :count => 103,
249
+ :max => 104,
250
+ :min => 105,
251
+ :stdDev => 107,
252
+ :sum => 109,
253
+ :var => 110
254
+ }
255
+
256
+ unless func_num = subtotals[function.to_sym]
257
+ raise "Unsupported function '#{function}' in add_table()"
258
+ end
259
+ "SUBTOTAL(#{func_num},[#{col_name}])"
260
+ end
65
261
 
66
- return unless autofilter
262
+ # Total label only (not a function).
263
+ def total_label_only(row2, col_num, col_data, total_string, format)
264
+ col_data.total_string = total_string
67
265
 
68
- attributes = ['ref', autofilter]
266
+ @worksheet.write_string(row2, col_num, total_string, format)
267
+ end
69
268
 
70
- @writer.empty_tag('autoFilter', attributes)
269
+ def set_the_table_options
270
+ @show_first_col = ptrue?(@param[:first_column]) ? 1 : 0
271
+ @show_last_col = ptrue?(@param[:last_column]) ? 1 : 0
272
+ @show_row_stripes = ptrue?(@param[:banded_rows]) ? 1 : 0
273
+ @show_col_stripes = ptrue?(@param[:banded_columns]) ? 1 : 0
274
+ @header_row_count = ptrue?(@param[:header_row]) ? 1 : 0
275
+ @totals_row_shown = ptrue?(@param[:total_row]) ? 1 : 0
276
+ end
277
+
278
+ def set_the_table_style
279
+ if @param[:style]
280
+ @style = @param[:style]
281
+ # Remove whitespace from style name.
282
+ @style.gsub!(/\s/, '')
283
+ else
284
+ @style = "TableStyleMedium9"
285
+ end
286
+ end
287
+
288
+ def set_the_table_name
289
+ if @param[:name]
290
+ @name = @param[:name]
291
+ else
292
+ # Set a default name.
293
+ @name = "Table#{id}"
294
+ end
295
+ end
296
+
297
+ def set_the_table_and_autofilter_ranges
298
+ @range = xl_range(@row1, @row2, @col1, @col2)
299
+ @a_range = xl_range(@row1, @last_data_row, @col1, @col2)
300
+ end
301
+
302
+ def set_the_autofilter_range
303
+ @autofilter = @a_range if ptrue?(@param[:autofilter])
304
+ end
305
+
306
+ #
307
+ # Write the XML declaration.
308
+ #
309
+ def write_xml_declaration
310
+ @writer.xml_decl('UTF-8', 1)
71
311
  end
72
312
 
73
313
  #
@@ -76,26 +316,20 @@ module Writexlsx
76
316
  def write_table
77
317
  schema = 'http://schemas.openxmlformats.org/'
78
318
  xmlns = "#{schema}spreadsheetml/2006/main"
79
- id = @properties[:id]
80
- name = @properties[:_name]
81
- display_name = @properties[:_name]
82
- ref = @properties[:_range]
83
- totals_row_shown = @properties[:_totals_row_shown]
84
- header_row_count = @properties[:_header_row_count]
85
319
 
86
320
  attributes = [
87
321
  'xmlns', xmlns,
88
322
  'id', id,
89
- 'name', name,
90
- 'displayName', display_name,
91
- 'ref', ref
323
+ 'name', @name,
324
+ 'displayName', @name,
325
+ 'ref', @range
92
326
  ]
93
327
 
94
- unless ptrue?(header_row_count)
328
+ unless ptrue?(@header_row_count)
95
329
  attributes << 'headerRowCount' << 0
96
330
  end
97
331
 
98
- if ptrue?(totals_row_shown)
332
+ if ptrue?(@totals_row_shown)
99
333
  attributes << 'totalsRowCount' << 1
100
334
  else
101
335
  attributes << 'totalsRowShown' << 0
@@ -107,11 +341,9 @@ module Writexlsx
107
341
  # Write the <autoFilter> element.
108
342
  #
109
343
  def write_auto_filter
110
- autofilter = @properties[:_autofilter]
111
-
112
- return unless ptrue?(autofilter)
344
+ return unless ptrue?(@autofilter)
113
345
 
114
- attributes = ['ref', autofilter]
346
+ attributes = ['ref', @autofilter]
115
347
 
116
348
  @writer.empty_tag('autoFilter', attributes)
117
349
  end
@@ -120,14 +352,12 @@ module Writexlsx
120
352
  # Write the <tableColumns> element.
121
353
  #
122
354
  def write_table_columns
123
- columns = @properties[:_columns]
124
-
125
- count = columns.size
355
+ count = @columns.size
126
356
 
127
357
  attributes = ['count', count]
128
358
 
129
359
  @writer.tag_elements('tableColumns', attributes) do
130
- columns.each {|col_data| write_table_column(col_data)}
360
+ @columns.each {|col_data| write_table_column(col_data)}
131
361
  end
132
362
  end
133
363
 
@@ -136,24 +366,24 @@ module Writexlsx
136
366
  #
137
367
  def write_table_column(col_data)
138
368
  attributes = [
139
- 'id', col_data[:_id],
140
- 'name', col_data[:_name]
369
+ 'id', col_data.id,
370
+ 'name', col_data.name
141
371
  ]
142
372
 
143
- if ptrue?(col_data[:_total_string])
144
- attributes << :totalsRowLabel << col_data[:_total_string]
145
- elsif ptrue?(col_data[:_total_function])
146
- attributes << :totalsRowFunction << col_data[:_total_function]
373
+ if ptrue?(col_data.total_string)
374
+ attributes << :totalsRowLabel << col_data.total_string
375
+ elsif ptrue?(col_data.total_function)
376
+ attributes << :totalsRowFunction << col_data.total_function
147
377
  end
148
378
 
149
- if col_data[:_format]
150
- attributes << :dataDxfId << col_data[:_format]
379
+ if col_data.format
380
+ attributes << :dataDxfId << col_data.format
151
381
  end
152
382
 
153
- if ptrue?(col_data[:_formula])
383
+ if ptrue?(col_data.formula)
154
384
  @writer.tag_elements('tableColumn', attributes) do
155
385
  # Write the calculatedColumnFormula element.
156
- write_calculated_column_formula(col_data[:_formula])
386
+ write_calculated_column_formula(col_data.formula)
157
387
  end
158
388
  else
159
389
  @writer.empty_tag('tableColumn', attributes)
@@ -164,20 +394,12 @@ module Writexlsx
164
394
  # Write the <tableStyleInfo> element.
165
395
  #
166
396
  def write_table_style_info
167
- props = @properties
168
-
169
- name = props[:_style]
170
- show_first_column = props[:_show_first_col]
171
- show_last_column = props[:_show_last_col]
172
- show_row_stripes = props[:_show_row_stripes]
173
- show_column_stripes = props[:_show_col_stripes]
174
-
175
397
  attributes = [
176
- 'name', name,
177
- 'showFirstColumn', show_first_column,
178
- 'showLastColumn', show_last_column,
179
- 'showRowStripes', show_row_stripes,
180
- 'showColumnStripes', show_column_stripes
398
+ 'name', @style,
399
+ 'showFirstColumn', @show_first_col,
400
+ 'showLastColumn', @show_last_col,
401
+ 'showRowStripes', @show_row_stripes,
402
+ 'showColumnStripes', @show_col_stripes
181
403
  ]
182
404
 
183
405
  @writer.empty_tag('tableStyleInfo', attributes)
@@ -91,6 +91,140 @@ module Writexlsx
91
91
  "=#{sheetname}!#{range1}:#{range2}"
92
92
  end
93
93
 
94
+ def check_dimensions(row, col)
95
+ if !row || row >= ROW_MAX || !col || col >= COL_MAX
96
+ raise WriteXLSXDimensionError
97
+ end
98
+ 0
99
+ end
100
+
101
+ #
102
+ # convert_date_time(date_time_string)
103
+ #
104
+ # The function takes a date and time in ISO8601 "yyyy-mm-ddThh:mm:ss.ss" format
105
+ # and converts it to a decimal number representing a valid Excel date.
106
+ #
107
+ # Dates and times in Excel are represented by real numbers. The integer part of
108
+ # the number stores the number of days since the epoch and the fractional part
109
+ # stores the percentage of the day in seconds. The epoch can be either 1900 or
110
+ # 1904.
111
+ #
112
+ # Parameter: Date and time string in one of the following formats:
113
+ # yyyy-mm-ddThh:mm:ss.ss # Standard
114
+ # yyyy-mm-ddT # Date only
115
+ # Thh:mm:ss.ss # Time only
116
+ #
117
+ # Returns:
118
+ # A decimal number representing a valid Excel date, or
119
+ # nil if the date is invalid.
120
+ #
121
+ def convert_date_time(date_time_string) #:nodoc:
122
+ date_time = date_time_string
123
+
124
+ days = 0 # Number of days since epoch
125
+ seconds = 0 # Time expressed as fraction of 24h hours in seconds
126
+
127
+ # Strip leading and trailing whitespace.
128
+ date_time.sub!(/^\s+/, '')
129
+ date_time.sub!(/\s+$/, '')
130
+
131
+ # Check for invalid date char.
132
+ return nil if date_time =~ /[^0-9T:\-\.Z]/
133
+
134
+ # Check for "T" after date or before time.
135
+ return nil unless date_time =~ /\dT|T\d/
136
+
137
+ # Strip trailing Z in ISO8601 date.
138
+ date_time.sub!(/Z$/, '')
139
+
140
+ # Split into date and time.
141
+ date, time = date_time.split(/T/)
142
+
143
+ # We allow the time portion of the input DateTime to be optional.
144
+ if time
145
+ # Match hh:mm:ss.sss+ where the seconds are optional
146
+ if time =~ /^(\d\d):(\d\d)(:(\d\d(\.\d+)?))?/
147
+ hour = $1.to_i
148
+ min = $2.to_i
149
+ sec = $4.to_f || 0
150
+ else
151
+ return nil # Not a valid time format.
152
+ end
153
+
154
+ # Some boundary checks
155
+ return nil if hour >= 24
156
+ return nil if min >= 60
157
+ return nil if sec >= 60
158
+
159
+ # Excel expresses seconds as a fraction of the number in 24 hours.
160
+ seconds = (hour * 60* 60 + min * 60 + sec) / (24.0 * 60 * 60)
161
+ end
162
+
163
+ # We allow the date portion of the input DateTime to be optional.
164
+ return seconds if date == ''
165
+
166
+ # Match date as yyyy-mm-dd.
167
+ if date =~ /^(\d\d\d\d)-(\d\d)-(\d\d)$/
168
+ year = $1.to_i
169
+ month = $2.to_i
170
+ day = $3.to_i
171
+ else
172
+ return nil # Not a valid date format.
173
+ end
174
+
175
+ # Set the epoch as 1900 or 1904. Defaults to 1900.
176
+ # Special cases for Excel.
177
+ unless date_1904?
178
+ return seconds if date == '1899-12-31' # Excel 1900 epoch
179
+ return seconds if date == '1900-01-00' # Excel 1900 epoch
180
+ return 60 + seconds if date == '1900-02-29' # Excel false leapday
181
+ end
182
+
183
+
184
+ # We calculate the date by calculating the number of days since the epoch
185
+ # and adjust for the number of leap days. We calculate the number of leap
186
+ # days by normalising the year in relation to the epoch. Thus the year 2000
187
+ # becomes 100 for 4 and 100 year leapdays and 400 for 400 year leapdays.
188
+ #
189
+ epoch = date_1904? ? 1904 : 1900
190
+ offset = date_1904? ? 4 : 0
191
+ norm = 300
192
+ range = year - epoch
193
+
194
+ # Set month days and check for leap year.
195
+ mdays = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
196
+ leap = 0
197
+ leap = 1 if year % 4 == 0 && year % 100 != 0 || year % 400 == 0
198
+ mdays[1] = 29 if leap != 0
199
+
200
+ # Some boundary checks
201
+ return nil if year < epoch or year > 9999
202
+ return nil if month < 1 or month > 12
203
+ return nil if day < 1 or day > mdays[month - 1]
204
+
205
+ # Accumulate the number of days since the epoch.
206
+ days = day # Add days for current month
207
+ (0 .. month-2).each do |m|
208
+ days += mdays[m] # Add days for past months
209
+ end
210
+ days += range * 365 # Add days for past years
211
+ days += ((range) / 4) # Add leapdays
212
+ days -= ((range + offset) /100) # Subtract 100 year leapdays
213
+ days += ((range + offset + norm)/400) # Add 400 year leapdays
214
+ days -= leap # Already counted above
215
+
216
+ # Adjust for Excel erroneously treating 1900 as a leap year.
217
+ days += 1 if !date_1904? and days > 59
218
+
219
+ date_time = sprintf("%0.10f", days + seconds)
220
+ date_time = date_time.sub(/\.?0+$/, '') if date_time =~ /\./
221
+ if date_time =~ /\./
222
+ date_time.to_f
223
+ else
224
+ date_time.to_i
225
+ end
226
+ end
227
+
94
228
  def absolute_char(absolute)
95
229
  absolute ? '$' : ''
96
230
  end
@@ -115,6 +249,15 @@ module Writexlsx
115
249
  $stderr.puts("Warning: calling deprecated method #{method}. This method will be removed in a future release.")
116
250
  end
117
251
 
252
+ # Check for a cell reference in A1 notation and substitute row and column
253
+ def row_col_notation(args) # :nodoc:
254
+ if args[0] =~ /^\D/
255
+ substitute_cellref(*args)
256
+ else
257
+ args
258
+ end
259
+ end
260
+
118
261
  #
119
262
  # Substitute an Excel cell reference in A1 notation for zero based row and
120
263
  # column values in an argument list.
@@ -159,6 +302,15 @@ module Writexlsx
159
302
  end
160
303
  end
161
304
 
305
+ #
306
+ # Write the <color> element.
307
+ #
308
+ def write_color(writer, name, value) #:nodoc:
309
+ attributes = [name, value]
310
+
311
+ writer.empty_tag('color', attributes)
312
+ end
313
+
162
314
  #
163
315
  # return perl's boolean result
164
316
  #
@@ -178,5 +330,32 @@ module Writexlsx
178
330
  end
179
331
  true
180
332
  end
333
+
334
+ #
335
+ # Check that row and col are valid and store max and min values for use in
336
+ # other methods/elements.
337
+ #
338
+ # The ignore_row/ignore_col flags is used to indicate that we wish to
339
+ # perform the dimension check without storing the value.
340
+ #
341
+ # The ignore flags are use by set_row() and data_validate.
342
+ #
343
+ def check_dimensions_and_update_max_min_values(row, col, ignore_row = 0, ignore_col = 0) #:nodoc:
344
+ check_dimensions(row, col)
345
+ store_row_max_min_values(row) if ignore_row == 0
346
+ store_col_max_min_values(col) if ignore_col == 0
347
+
348
+ 0
349
+ end
350
+
351
+ def store_row_max_min_values(row)
352
+ @dim_rowmin = row if !@dim_rowmin || (row < @dim_rowmin)
353
+ @dim_rowmax = row if !@dim_rowmax || (row > @dim_rowmax)
354
+ end
355
+
356
+ def store_col_max_min_values(col)
357
+ @dim_colmin = col if !@dim_colmin || (col < @dim_colmin)
358
+ @dim_colmax = col if !@dim_colmax || (col > @dim_colmax)
359
+ end
181
360
  end
182
361
  end
@@ -1,5 +1,5 @@
1
1
  require 'write_xlsx/workbook'
2
2
 
3
3
  class WriteXLSX < Writexlsx::Workbook
4
- VERSION = "0.58.0"
4
+ VERSION = "0.59.0"
5
5
  end
@@ -25,6 +25,7 @@ module Writexlsx
25
25
  attr_reader :image_types, :images
26
26
  attr_reader :shared_strings
27
27
  attr_accessor :table_count
28
+ attr_reader :vba_project
28
29
  #
29
30
  # A new Excel workbook is created using the new() constructor
30
31
  # which accepts either a filename or an IO object as a parameter.
@@ -756,6 +757,13 @@ module Writexlsx
756
757
  @doc_properties = params.dup
757
758
  end
758
759
 
760
+ #
761
+ # Add a vbaProject binary to the XLSX file.
762
+ #
763
+ def add_vba_project(vba_project)
764
+ @vba_project = vba_project
765
+ end
766
+
759
767
  #
760
768
  # Change the RGB components of the elements in the colour palette.
761
769
  #
@@ -1066,7 +1074,12 @@ module Writexlsx
1066
1074
  'lastEdited', 4,
1067
1075
  'lowestEdited', 4,
1068
1076
  'rupBuild', 4505
1069
- ]
1077
+ ]
1078
+
1079
+ if @vba_project
1080
+ attributes << :codeName << '{37E998C4-C9E5-D4B9-71C8-EB1FF731991C}'
1081
+ end
1082
+
1070
1083
  @writer.empty_tag('fileVersion', attributes)
1071
1084
  end
1072
1085