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
@@ -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