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.
- data/README.rdoc +7 -1
- data/bin/extract_vba.rb +29 -0
- data/examples/add_vba_project.rb +36 -0
- data/examples/vbaProject.bin +0 -0
- data/lib/write_xlsx/chart.rb +22 -37
- data/lib/write_xlsx/package/conditional_format.rb +593 -0
- data/lib/write_xlsx/package/content_types.rb +17 -0
- data/lib/write_xlsx/package/packager.rb +26 -6
- data/lib/write_xlsx/package/relationships.rb +11 -1
- data/lib/write_xlsx/package/table.rb +284 -62
- data/lib/write_xlsx/utility.rb +179 -0
- data/lib/write_xlsx/version.rb +1 -1
- data/lib/write_xlsx/workbook.rb +14 -1
- data/lib/write_xlsx/worksheet.rb +667 -875
- data/test/package/table/test_table01.rb +1 -2
- data/test/package/table/test_table02.rb +1 -2
- data/test/package/table/test_table03.rb +1 -2
- data/test/package/table/test_table04.rb +1 -2
- data/test/package/table/test_table05.rb +1 -2
- data/test/package/table/test_table06.rb +1 -2
- data/test/package/table/test_table07.rb +1 -2
- data/test/package/table/test_table08.rb +1 -2
- data/test/package/table/test_table09.rb +1 -2
- data/test/package/table/test_table10.rb +1 -2
- data/test/package/table/test_table11.rb +1 -2
- data/test/package/table/test_table12.rb +1 -2
- data/test/package/table/test_write_auto_filter.rb +10 -3
- data/test/package/table/test_write_table_column.rb +9 -2
- data/test/package/table/test_write_table_style_info.rb +12 -11
- data/test/package/table/test_write_xml_declaration.rb +6 -1
- data/test/perl_output/add_vba_project.xlsm +0 -0
- data/test/regression/test_macro01.rb +29 -0
- data/test/regression/xlsx_files/macro01.xlsm +0 -0
- data/test/regression/xlsx_files/vbaProject01.bin +0 -0
- data/test/test_example_match.rb +22 -0
- data/test/vbaProject.bin +0 -0
- 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
|
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
|
-
|
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
|
-
|
14
|
-
|
15
|
-
|
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
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
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
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
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
|
-
#
|
242
|
+
# Convert a table total function to a worksheet formula.
|
62
243
|
#
|
63
|
-
def
|
64
|
-
|
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
|
-
|
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
|
-
|
266
|
+
@worksheet.write_string(row2, col_num, total_string, format)
|
267
|
+
end
|
69
268
|
|
70
|
-
|
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',
|
91
|
-
'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
|
-
|
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
|
-
|
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
|
140
|
-
'name', col_data
|
369
|
+
'id', col_data.id,
|
370
|
+
'name', col_data.name
|
141
371
|
]
|
142
372
|
|
143
|
-
if ptrue?(col_data
|
144
|
-
attributes << :totalsRowLabel << col_data
|
145
|
-
elsif ptrue?(col_data
|
146
|
-
attributes << :totalsRowFunction << col_data
|
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
|
150
|
-
attributes << :dataDxfId << col_data
|
379
|
+
if col_data.format
|
380
|
+
attributes << :dataDxfId << col_data.format
|
151
381
|
end
|
152
382
|
|
153
|
-
if ptrue?(col_data
|
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
|
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',
|
177
|
-
'showFirstColumn',
|
178
|
-
'showLastColumn',
|
179
|
-
'showRowStripes', show_row_stripes,
|
180
|
-
'showColumnStripes',
|
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)
|
data/lib/write_xlsx/utility.rb
CHANGED
@@ -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
|
data/lib/write_xlsx/version.rb
CHANGED
data/lib/write_xlsx/workbook.rb
CHANGED
@@ -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
|
|