write_xlsx 1.12.3 → 1.13.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.
- checksums.yaml +4 -4
- data/.rubocop.yml +7 -0
- data/Changes +3 -0
- data/README.md +1 -1
- data/lib/write_xlsx/chartsheet.rb +4 -1
- data/lib/write_xlsx/object_positioning.rb +189 -0
- data/lib/write_xlsx/package/app.rb +1 -1
- data/lib/write_xlsx/page_setup.rb +190 -0
- data/lib/write_xlsx/sheets.rb +3 -3
- data/lib/write_xlsx/utility.rb +57 -9
- data/lib/write_xlsx/version.rb +1 -1
- data/lib/write_xlsx/workbook.rb +34 -34
- data/lib/write_xlsx/worksheet/asset_manager.rb +60 -0
- data/lib/write_xlsx/worksheet/autofilter.rb +388 -0
- data/lib/write_xlsx/worksheet/cell_data.rb +8 -5
- data/lib/write_xlsx/worksheet/cell_data_manager.rb +47 -0
- data/lib/write_xlsx/worksheet/cell_data_store.rb +61 -0
- data/lib/write_xlsx/worksheet/columns.rb +199 -0
- data/lib/write_xlsx/worksheet/comments_support.rb +61 -0
- data/lib/write_xlsx/worksheet/conditional_formats.rb +30 -0
- data/lib/write_xlsx/worksheet/data_writing.rb +990 -0
- data/lib/write_xlsx/worksheet/drawing_methods.rb +308 -0
- data/lib/write_xlsx/worksheet/drawing_preparation.rb +290 -0
- data/lib/write_xlsx/worksheet/drawing_relations.rb +76 -0
- data/lib/write_xlsx/worksheet/drawing_xml_writer.rb +50 -0
- data/lib/write_xlsx/worksheet/formatting.rb +416 -0
- data/lib/write_xlsx/worksheet/initialization.rb +146 -0
- data/lib/write_xlsx/worksheet/panes.rb +64 -0
- data/lib/write_xlsx/worksheet/print_options.rb +72 -0
- data/lib/write_xlsx/worksheet/protection.rb +65 -0
- data/lib/write_xlsx/worksheet/rich_text_helpers.rb +78 -0
- data/lib/write_xlsx/worksheet/row_col_sizing.rb +67 -0
- data/lib/write_xlsx/worksheet/rows.rb +84 -0
- data/lib/write_xlsx/worksheet/selection.rb +41 -0
- data/lib/write_xlsx/worksheet/xml_writer.rb +1241 -0
- data/lib/write_xlsx/worksheet.rb +359 -4530
- metadata +26 -3
- data/lib/write_xlsx/worksheet/page_setup.rb +0 -192
|
@@ -0,0 +1,1241 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Writexlsx
|
|
4
|
+
class Worksheet
|
|
5
|
+
module XmlWriter
|
|
6
|
+
include Utility
|
|
7
|
+
|
|
8
|
+
def assemble_xml_file # :nodoc:
|
|
9
|
+
write_xml_declaration do
|
|
10
|
+
@writer.tag_elements('worksheet', write_worksheet_attributes) do
|
|
11
|
+
write_sheet_pr
|
|
12
|
+
write_dimension
|
|
13
|
+
write_sheet_views
|
|
14
|
+
write_sheet_format_pr
|
|
15
|
+
write_cols
|
|
16
|
+
write_sheet_data
|
|
17
|
+
write_sheet_protection
|
|
18
|
+
write_protected_ranges
|
|
19
|
+
# write_sheet_calc_pr
|
|
20
|
+
write_phonetic_pr if excel2003_style?
|
|
21
|
+
write_auto_filter
|
|
22
|
+
write_merge_cells
|
|
23
|
+
write_conditional_formats
|
|
24
|
+
write_data_validations
|
|
25
|
+
write_hyperlinks
|
|
26
|
+
write_print_options
|
|
27
|
+
write_page_margins
|
|
28
|
+
write_page_setup
|
|
29
|
+
write_header_footer
|
|
30
|
+
write_row_breaks
|
|
31
|
+
write_col_breaks
|
|
32
|
+
write_ignored_errors
|
|
33
|
+
write_drawings
|
|
34
|
+
write_legacy_drawing
|
|
35
|
+
write_legacy_drawing_hf
|
|
36
|
+
write_picture
|
|
37
|
+
write_table_parts
|
|
38
|
+
write_ext_list
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
#
|
|
44
|
+
# Write the <worksheet> element. This is the root element of Worksheet.
|
|
45
|
+
#
|
|
46
|
+
def write_worksheet_attributes # :nodoc:
|
|
47
|
+
schema = 'http://schemas.openxmlformats.org/'
|
|
48
|
+
attributes = [
|
|
49
|
+
['xmlns', "#{schema}spreadsheetml/2006/main"],
|
|
50
|
+
['xmlns:r', "#{schema}officeDocument/2006/relationships"]
|
|
51
|
+
]
|
|
52
|
+
|
|
53
|
+
if @excel_version == 2010
|
|
54
|
+
attributes << ['xmlns:mc', "#{schema}markup-compatibility/2006"]
|
|
55
|
+
attributes << ['xmlns:x14ac', "#{OFFICE_URL}spreadsheetml/2009/9/ac"]
|
|
56
|
+
attributes << ['mc:Ignorable', 'x14ac']
|
|
57
|
+
end
|
|
58
|
+
attributes
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
#
|
|
62
|
+
# Write the cell value <v> element.
|
|
63
|
+
#
|
|
64
|
+
def write_cell_value(value = '') # :nodoc:
|
|
65
|
+
return write_cell_formula('=NA()') if value.is_a?(Float) && value.nan?
|
|
66
|
+
|
|
67
|
+
value ||= ''
|
|
68
|
+
|
|
69
|
+
int_value = value.to_i
|
|
70
|
+
value = int_value if value == int_value
|
|
71
|
+
@writer.data_element('v', value)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
#
|
|
75
|
+
# Write the cell formula <f> element.
|
|
76
|
+
#
|
|
77
|
+
def write_cell_formula(formula = '') # :nodoc:
|
|
78
|
+
@writer.data_element('f', formula)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
#
|
|
82
|
+
# Write the cell array formula <f> element.
|
|
83
|
+
#
|
|
84
|
+
def write_cell_array_formula(formula, range) # :nodoc:
|
|
85
|
+
@writer.data_element(
|
|
86
|
+
'f', formula,
|
|
87
|
+
[
|
|
88
|
+
%w[t array],
|
|
89
|
+
['ref', range]
|
|
90
|
+
]
|
|
91
|
+
)
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
#
|
|
95
|
+
# Write the frozen or split <pane> elements.
|
|
96
|
+
#
|
|
97
|
+
def write_panes # :nodoc:
|
|
98
|
+
return if @panes.empty?
|
|
99
|
+
|
|
100
|
+
if @panes[4] == 2
|
|
101
|
+
write_split_panes
|
|
102
|
+
else
|
|
103
|
+
write_freeze_panes(*@panes)
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
#
|
|
108
|
+
# Write the <pane> element for freeze panes.
|
|
109
|
+
#
|
|
110
|
+
def write_freeze_panes(row, col, top_row, left_col, type) # :nodoc:
|
|
111
|
+
y_split = row
|
|
112
|
+
x_split = col
|
|
113
|
+
top_left_cell = xl_rowcol_to_cell(top_row, left_col)
|
|
114
|
+
|
|
115
|
+
# Move user cell selection to the panes.
|
|
116
|
+
unless @selections.empty?
|
|
117
|
+
_dummy, active_cell, sqref = @selections[0]
|
|
118
|
+
@selections = []
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
active_cell ||= nil
|
|
122
|
+
sqref ||= nil
|
|
123
|
+
active_pane = set_active_pane_and_cell_selections(row, col, row, col, active_cell, sqref)
|
|
124
|
+
|
|
125
|
+
# Set the pane type.
|
|
126
|
+
state = if type == 0
|
|
127
|
+
'frozen'
|
|
128
|
+
elsif type == 1
|
|
129
|
+
'frozenSplit'
|
|
130
|
+
else
|
|
131
|
+
'split'
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
attributes = []
|
|
135
|
+
attributes << ['xSplit', x_split] if x_split > 0
|
|
136
|
+
attributes << ['ySplit', y_split] if y_split > 0
|
|
137
|
+
attributes << ['topLeftCell', top_left_cell]
|
|
138
|
+
attributes << ['activePane', active_pane]
|
|
139
|
+
attributes << ['state', state]
|
|
140
|
+
|
|
141
|
+
@writer.empty_tag('pane', attributes)
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
#
|
|
145
|
+
# Write the <pane> element for split panes.
|
|
146
|
+
#
|
|
147
|
+
# See also, implementers note for split_panes().
|
|
148
|
+
#
|
|
149
|
+
def write_split_panes # :nodoc:
|
|
150
|
+
row, col, top_row, left_col = @panes
|
|
151
|
+
has_selection = false
|
|
152
|
+
y_split = row
|
|
153
|
+
x_split = col
|
|
154
|
+
|
|
155
|
+
# Move user cell selection to the panes.
|
|
156
|
+
unless @selections.empty?
|
|
157
|
+
_dummy, active_cell, sqref = @selections[0]
|
|
158
|
+
@selections = []
|
|
159
|
+
has_selection = true
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
# Convert the row and col to 1/20 twip units with padding.
|
|
163
|
+
y_split = ((20 * y_split) + 300).to_i if y_split > 0
|
|
164
|
+
x_split = calculate_x_split_width(x_split) if x_split > 0
|
|
165
|
+
|
|
166
|
+
# For non-explicit topLeft definitions, estimate the cell offset based
|
|
167
|
+
# on the pixels dimensions. This is only a workaround and doesn't take
|
|
168
|
+
# adjusted cell dimensions into account.
|
|
169
|
+
if top_row == row && left_col == col
|
|
170
|
+
top_row = (0.5 + ((y_split - 300) / 20 / 15)).to_i
|
|
171
|
+
left_col = (0.5 + ((x_split - 390) / 20 / 3 * 4 / 64)).to_i
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
top_left_cell = xl_rowcol_to_cell(top_row, left_col)
|
|
175
|
+
|
|
176
|
+
# If there is no selection set the active cell to the top left cell.
|
|
177
|
+
unless has_selection
|
|
178
|
+
active_cell = top_left_cell
|
|
179
|
+
sqref = top_left_cell
|
|
180
|
+
end
|
|
181
|
+
active_pane = set_active_pane_and_cell_selections(
|
|
182
|
+
row, col, top_row, left_col, active_cell, sqref
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
attributes = []
|
|
186
|
+
attributes << ['xSplit', x_split] if x_split > 0
|
|
187
|
+
attributes << ['ySplit', y_split] if y_split > 0
|
|
188
|
+
attributes << ['topLeftCell', top_left_cell]
|
|
189
|
+
attributes << ['activePane', active_pane] if has_selection
|
|
190
|
+
|
|
191
|
+
@writer.empty_tag('pane', attributes)
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
#
|
|
195
|
+
# Convert column width from user units to pane split width.
|
|
196
|
+
#
|
|
197
|
+
def calculate_x_split_width(width) # :nodoc:
|
|
198
|
+
# Convert to pixels.
|
|
199
|
+
pixels = if width < 1
|
|
200
|
+
int((width * 12) + 0.5)
|
|
201
|
+
else
|
|
202
|
+
((width * MAX_DIGIT_WIDTH) + 0.5).to_i + PADDING
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
# Convert to points.
|
|
206
|
+
points = pixels * 3 / 4
|
|
207
|
+
|
|
208
|
+
# Convert to twips (twentieths of a point).
|
|
209
|
+
twips = points * 20
|
|
210
|
+
|
|
211
|
+
# Add offset/padding.
|
|
212
|
+
twips + 390
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
#
|
|
216
|
+
# Write the <sheetViews> element.
|
|
217
|
+
#
|
|
218
|
+
def write_sheet_views # :nodoc:
|
|
219
|
+
@writer.tag_elements('sheetViews', []) { write_sheet_view }
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
def write_sheet_view # :nodoc:
|
|
223
|
+
attributes = []
|
|
224
|
+
# Hide screen gridlines if required.
|
|
225
|
+
attributes << ['showGridLines', 0] unless @screen_gridlines
|
|
226
|
+
|
|
227
|
+
# Hide the row/column headers.
|
|
228
|
+
attributes << ['showRowColHeaders', 0] if ptrue?(@hide_row_col_headers)
|
|
229
|
+
|
|
230
|
+
# Hide zeroes in cells.
|
|
231
|
+
attributes << ['showZeros', 0] unless show_zeros?
|
|
232
|
+
|
|
233
|
+
# Display worksheet right to left for Hebrew, Arabic and others.
|
|
234
|
+
attributes << ['rightToLeft', 1] if @right_to_left
|
|
235
|
+
|
|
236
|
+
# Show that the sheet tab is selected.
|
|
237
|
+
attributes << ['tabSelected', 1] if @selected
|
|
238
|
+
|
|
239
|
+
# Turn outlines off. Also required in the outlinePr element.
|
|
240
|
+
attributes << ["showOutlineSymbols", 0] if @outline_on
|
|
241
|
+
|
|
242
|
+
# Set the page view/layout mode if required.
|
|
243
|
+
case @page_view
|
|
244
|
+
when 1
|
|
245
|
+
attributes << %w[view pageLayout]
|
|
246
|
+
when 2
|
|
247
|
+
attributes << %w[view pageBreakPreview]
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
# Set the first visible cell.
|
|
251
|
+
attributes << ['topLeftCell', @top_left_cell] if ptrue?(@top_left_cell)
|
|
252
|
+
|
|
253
|
+
# Set the zoom level.
|
|
254
|
+
if @zoom != 100
|
|
255
|
+
attributes << ['zoomScale', @zoom]
|
|
256
|
+
|
|
257
|
+
if @page_view == 1
|
|
258
|
+
attributes << ['zoomScalePageLayoutView', @zoom]
|
|
259
|
+
elsif @page_view == 2
|
|
260
|
+
attributes << ['zoomScaleSheetLayoutView', @zoom]
|
|
261
|
+
elsif ptrue?(@zoom_scale_normal)
|
|
262
|
+
attributes << ['zoomScaleNormal', @zoom]
|
|
263
|
+
end
|
|
264
|
+
end
|
|
265
|
+
|
|
266
|
+
attributes << ['workbookViewId', 0]
|
|
267
|
+
|
|
268
|
+
if @panes.empty? && @selections.empty?
|
|
269
|
+
@writer.empty_tag('sheetView', attributes)
|
|
270
|
+
else
|
|
271
|
+
@writer.tag_elements('sheetView', attributes) do
|
|
272
|
+
write_panes
|
|
273
|
+
write_selections
|
|
274
|
+
end
|
|
275
|
+
end
|
|
276
|
+
end
|
|
277
|
+
|
|
278
|
+
#
|
|
279
|
+
# Write the <cols> element and <col> sub elements.
|
|
280
|
+
#
|
|
281
|
+
def write_cols # :nodoc:
|
|
282
|
+
# Exit unless some column have been formatted.
|
|
283
|
+
return if @col_info.empty?
|
|
284
|
+
|
|
285
|
+
@writer.tag_elements('cols') do
|
|
286
|
+
# Use the first element of the column informatin structure to set
|
|
287
|
+
# the initial/previous properties.
|
|
288
|
+
first_col = @col_info.keys.min
|
|
289
|
+
last_col = first_col
|
|
290
|
+
previous_options = @col_info[first_col]
|
|
291
|
+
deleted_col = first_col
|
|
292
|
+
deleted_col_options = previous_options
|
|
293
|
+
|
|
294
|
+
@col_info.delete(first_col)
|
|
295
|
+
|
|
296
|
+
@col_info.keys.sort.each do |col|
|
|
297
|
+
col_options = @col_info[col]
|
|
298
|
+
|
|
299
|
+
# Check if the column number is contiguous with the previous
|
|
300
|
+
# column and if the properties are the same.
|
|
301
|
+
if (col == last_col + 1) &&
|
|
302
|
+
compare_col_info(col_options, previous_options)
|
|
303
|
+
last_col = col
|
|
304
|
+
else
|
|
305
|
+
# If not contiguous/equal then we write out the current range
|
|
306
|
+
# of columns and start again.
|
|
307
|
+
write_col_info([first_col, last_col, previous_options])
|
|
308
|
+
first_col = col
|
|
309
|
+
last_col = first_col
|
|
310
|
+
previous_options = col_options
|
|
311
|
+
end
|
|
312
|
+
end
|
|
313
|
+
|
|
314
|
+
# We will exit the previous loop with one unhandled column range.
|
|
315
|
+
write_col_info([first_col, last_col, previous_options])
|
|
316
|
+
|
|
317
|
+
# Put back the deleted first column information structure:
|
|
318
|
+
@col_info[deleted_col] = deleted_col_options
|
|
319
|
+
end
|
|
320
|
+
end
|
|
321
|
+
|
|
322
|
+
#
|
|
323
|
+
# Write the <col> element.
|
|
324
|
+
#
|
|
325
|
+
def write_col_info(args) # :nodoc:
|
|
326
|
+
@writer.empty_tag('col', col_info_attributes(args))
|
|
327
|
+
end
|
|
328
|
+
|
|
329
|
+
def col_info_attributes(args)
|
|
330
|
+
min = args[0] || 0 # First formatted column.
|
|
331
|
+
max = args[1] || 0 # Last formatted column.
|
|
332
|
+
width = args[2].width # Col width in user units.
|
|
333
|
+
format = args[2].format # Format index.
|
|
334
|
+
hidden = args[2].hidden || 0 # Hidden flag.
|
|
335
|
+
level = args[2].level || 0 # Outline level.
|
|
336
|
+
collapsed = args[2].collapsed || 0 # Outline Collapsed
|
|
337
|
+
autofit = args[2].autofit || 0 # Best fit for autofit numbers.
|
|
338
|
+
xf_index = format ? format.get_xf_index : 0
|
|
339
|
+
|
|
340
|
+
custom_width = true
|
|
341
|
+
custom_width = false if width.nil? && hidden == 0
|
|
342
|
+
custom_width = false if !width.nil? && (width - 8.43).abs < 0.01
|
|
343
|
+
|
|
344
|
+
width ||= hidden == 0 ? @default_col_width : 0
|
|
345
|
+
|
|
346
|
+
# Convert column width from user units to character width.
|
|
347
|
+
width = if width && width < 1
|
|
348
|
+
(((width * (MAX_DIGIT_WIDTH + PADDING)) + 0.5).to_i / MAX_DIGIT_WIDTH.to_f * 256).to_i / 256.0
|
|
349
|
+
else
|
|
350
|
+
((((width * MAX_DIGIT_WIDTH) + 0.5).to_i + PADDING).to_i / MAX_DIGIT_WIDTH.to_f * 256).to_i / 256.0
|
|
351
|
+
end
|
|
352
|
+
width = width.to_i if width - width.to_i == 0
|
|
353
|
+
|
|
354
|
+
attributes = [
|
|
355
|
+
['min', min + 1],
|
|
356
|
+
['max', max + 1],
|
|
357
|
+
['width', width]
|
|
358
|
+
]
|
|
359
|
+
|
|
360
|
+
attributes << ['style', xf_index] if xf_index != 0
|
|
361
|
+
attributes << ['hidden', 1] if hidden != 0
|
|
362
|
+
attributes << ['bestFit', 1] if autofit != 0
|
|
363
|
+
attributes << ['customWidth', 1] if custom_width
|
|
364
|
+
attributes << ['outlineLevel', level] if level != 0
|
|
365
|
+
attributes << ['collapsed', 1] if collapsed != 0
|
|
366
|
+
attributes
|
|
367
|
+
end
|
|
368
|
+
|
|
369
|
+
#
|
|
370
|
+
# Write the <sheetData> element.
|
|
371
|
+
#
|
|
372
|
+
def write_sheet_data # :nodoc:
|
|
373
|
+
if @dim_rowmin
|
|
374
|
+
@writer.tag_elements('sheetData') { write_rows }
|
|
375
|
+
else
|
|
376
|
+
# If the dimensions aren't defined then there is no data to write.
|
|
377
|
+
@writer.empty_tag('sheetData')
|
|
378
|
+
end
|
|
379
|
+
end
|
|
380
|
+
|
|
381
|
+
#
|
|
382
|
+
# Write out the worksheet data as a series of rows and cells.
|
|
383
|
+
#
|
|
384
|
+
def write_rows # :nodoc:
|
|
385
|
+
calculate_spans
|
|
386
|
+
|
|
387
|
+
(@dim_rowmin..@dim_rowmax).each do |row_num|
|
|
388
|
+
# Skip row if it doesn't contain row formatting or cell data.
|
|
389
|
+
next if not_contain_formatting_or_data?(row_num)
|
|
390
|
+
|
|
391
|
+
span_index = row_num / 16
|
|
392
|
+
span = @row_spans[span_index]
|
|
393
|
+
|
|
394
|
+
# Write the cells if the row contains data.
|
|
395
|
+
if @cell_data_store[row_num]
|
|
396
|
+
args = @set_rows[row_num] || []
|
|
397
|
+
write_row_element(row_num, span, *args) do
|
|
398
|
+
write_cell_column_dimension(row_num)
|
|
399
|
+
end
|
|
400
|
+
else
|
|
401
|
+
# Row attributes only.
|
|
402
|
+
write_empty_row(row_num, span, *@set_rows[row_num])
|
|
403
|
+
end
|
|
404
|
+
end
|
|
405
|
+
end
|
|
406
|
+
|
|
407
|
+
def not_contain_formatting_or_data?(row_num) # :nodoc:
|
|
408
|
+
!@set_rows[row_num] && !@cell_data_store[row_num] && !@comments.has_comment_in_row?(row_num)
|
|
409
|
+
end
|
|
410
|
+
|
|
411
|
+
#
|
|
412
|
+
# Write the <row> element.
|
|
413
|
+
#
|
|
414
|
+
def write_row_element(*args, &block) # :nodoc:
|
|
415
|
+
@writer.tag_elements('row', row_attributes(args), &block)
|
|
416
|
+
end
|
|
417
|
+
|
|
418
|
+
def write_cell_column_dimension(row_num) # :nodoc:
|
|
419
|
+
row = @cell_data_store[row_num]
|
|
420
|
+
row_name = (row_num + 1).to_s
|
|
421
|
+
(@dim_colmin..@dim_colmax).each do |col_num|
|
|
422
|
+
if (cell = row[col_num])
|
|
423
|
+
cell.write_cell(self, row_num, row_name, col_num)
|
|
424
|
+
end
|
|
425
|
+
end
|
|
426
|
+
end
|
|
427
|
+
|
|
428
|
+
#
|
|
429
|
+
# Write and empty <row> element, i.e., attributes only, no cell data.
|
|
430
|
+
#
|
|
431
|
+
def write_empty_row(*args) # :nodoc:
|
|
432
|
+
@writer.empty_tag('row', row_attributes(args))
|
|
433
|
+
end
|
|
434
|
+
|
|
435
|
+
def row_attributes(args)
|
|
436
|
+
r, spans, height, format, hidden, level, collapsed, _empty_row = args
|
|
437
|
+
height ||= @default_row_height
|
|
438
|
+
hidden ||= 0
|
|
439
|
+
level ||= 0
|
|
440
|
+
xf_index = format ? format.get_xf_index : 0
|
|
441
|
+
|
|
442
|
+
attributes = [['r', r + 1]]
|
|
443
|
+
|
|
444
|
+
attributes << ['spans', spans] if spans
|
|
445
|
+
attributes << ['s', xf_index] if ptrue?(xf_index)
|
|
446
|
+
attributes << ['customFormat', 1] if ptrue?(format)
|
|
447
|
+
attributes << ['ht', height] if height != @original_row_height
|
|
448
|
+
attributes << ['hidden', 1] if ptrue?(hidden)
|
|
449
|
+
attributes << ['customHeight', 1] if height != @original_row_height
|
|
450
|
+
attributes << ['outlineLevel', level] if ptrue?(level)
|
|
451
|
+
attributes << ['collapsed', 1] if ptrue?(collapsed)
|
|
452
|
+
|
|
453
|
+
attributes << ['x14ac:dyDescent', '0.25'] if @excel_version == 2010
|
|
454
|
+
attributes
|
|
455
|
+
end
|
|
456
|
+
|
|
457
|
+
#
|
|
458
|
+
# Write the <sheetProtection> element.
|
|
459
|
+
#
|
|
460
|
+
def write_sheet_protection # :nodoc:
|
|
461
|
+
return unless protect?
|
|
462
|
+
|
|
463
|
+
attributes = []
|
|
464
|
+
attributes << ["password", @protect[:password]] if ptrue?(@protect[:password])
|
|
465
|
+
attributes << ["sheet", 1] if ptrue?(@protect[:sheet])
|
|
466
|
+
attributes << ["content", 1] if ptrue?(@protect[:content])
|
|
467
|
+
attributes << ["objects", 1] unless ptrue?(@protect[:objects])
|
|
468
|
+
attributes << ["scenarios", 1] unless ptrue?(@protect[:scenarios])
|
|
469
|
+
attributes << ["formatCells", 0] if ptrue?(@protect[:format_cells])
|
|
470
|
+
attributes << ["formatColumns", 0] if ptrue?(@protect[:format_columns])
|
|
471
|
+
attributes << ["formatRows", 0] if ptrue?(@protect[:format_rows])
|
|
472
|
+
attributes << ["insertColumns", 0] if ptrue?(@protect[:insert_columns])
|
|
473
|
+
attributes << ["insertRows", 0] if ptrue?(@protect[:insert_rows])
|
|
474
|
+
attributes << ["insertHyperlinks", 0] if ptrue?(@protect[:insert_hyperlinks])
|
|
475
|
+
attributes << ["deleteColumns", 0] if ptrue?(@protect[:delete_columns])
|
|
476
|
+
attributes << ["deleteRows", 0] if ptrue?(@protect[:delete_rows])
|
|
477
|
+
|
|
478
|
+
attributes << ["selectLockedCells", 1] unless ptrue?(@protect[:select_locked_cells])
|
|
479
|
+
|
|
480
|
+
attributes << ["sort", 0] if ptrue?(@protect[:sort])
|
|
481
|
+
attributes << ["autoFilter", 0] if ptrue?(@protect[:autofilter])
|
|
482
|
+
attributes << ["pivotTables", 0] if ptrue?(@protect[:pivot_tables])
|
|
483
|
+
|
|
484
|
+
attributes << ["selectUnlockedCells", 1] unless ptrue?(@protect[:select_unlocked_cells])
|
|
485
|
+
|
|
486
|
+
@writer.empty_tag('sheetProtection', attributes)
|
|
487
|
+
end
|
|
488
|
+
|
|
489
|
+
#
|
|
490
|
+
# Write the <protectedRanges> element.
|
|
491
|
+
#
|
|
492
|
+
def write_protected_ranges
|
|
493
|
+
return if @num_protected_ranges == 0
|
|
494
|
+
|
|
495
|
+
@writer.tag_elements('protectedRanges') do
|
|
496
|
+
@protected_ranges.each do |protected_range|
|
|
497
|
+
write_protected_range(*protected_range)
|
|
498
|
+
end
|
|
499
|
+
end
|
|
500
|
+
end
|
|
501
|
+
|
|
502
|
+
#
|
|
503
|
+
# Write the <protectedRange> element.
|
|
504
|
+
#
|
|
505
|
+
def write_protected_range(sqref, name, password)
|
|
506
|
+
attributes = []
|
|
507
|
+
|
|
508
|
+
attributes << ['password', password] if password
|
|
509
|
+
attributes << ['sqref', sqref]
|
|
510
|
+
attributes << ['name', name]
|
|
511
|
+
|
|
512
|
+
@writer.empty_tag('protectedRange', attributes)
|
|
513
|
+
end
|
|
514
|
+
|
|
515
|
+
#
|
|
516
|
+
# Write the <sheetCalcPr> element for the worksheet calculation properties.
|
|
517
|
+
#
|
|
518
|
+
def write_sheet_calc_pr # :nodoc:
|
|
519
|
+
@writer.empty_tag('sheetCalcPr', [['fullCalcOnLoad', 1]])
|
|
520
|
+
end
|
|
521
|
+
|
|
522
|
+
#
|
|
523
|
+
# Write the <phoneticPr> element.
|
|
524
|
+
#
|
|
525
|
+
def write_phonetic_pr # :nodoc:
|
|
526
|
+
attributes = [
|
|
527
|
+
['fontId', 0],
|
|
528
|
+
%w[type noConversion]
|
|
529
|
+
]
|
|
530
|
+
|
|
531
|
+
@writer.empty_tag('phoneticPr', attributes)
|
|
532
|
+
end
|
|
533
|
+
|
|
534
|
+
#
|
|
535
|
+
# Write the <autoFilter> element.
|
|
536
|
+
#
|
|
537
|
+
def write_auto_filter # :nodoc:
|
|
538
|
+
return unless autofilter_ref?
|
|
539
|
+
|
|
540
|
+
attributes = [
|
|
541
|
+
['ref', @autofilter_ref]
|
|
542
|
+
]
|
|
543
|
+
|
|
544
|
+
if filter_on?
|
|
545
|
+
# Autofilter defined active filters.
|
|
546
|
+
@writer.tag_elements('autoFilter', attributes) do
|
|
547
|
+
write_autofilters
|
|
548
|
+
end
|
|
549
|
+
else
|
|
550
|
+
# Autofilter defined without active filters.
|
|
551
|
+
@writer.empty_tag('autoFilter', attributes)
|
|
552
|
+
end
|
|
553
|
+
end
|
|
554
|
+
|
|
555
|
+
#
|
|
556
|
+
# Function to iterate through the columns that form part of an autofilter
|
|
557
|
+
# range and write the appropriate filters.
|
|
558
|
+
#
|
|
559
|
+
def write_autofilters # :nodoc:
|
|
560
|
+
col1, col2 = @filter_range
|
|
561
|
+
|
|
562
|
+
(col1..col2).each do |col|
|
|
563
|
+
# Skip if column doesn't have an active filter.
|
|
564
|
+
next unless @filter_cols[col]
|
|
565
|
+
|
|
566
|
+
# Retrieve the filter tokens and write the autofilter records.
|
|
567
|
+
tokens = @filter_cols[col]
|
|
568
|
+
type = @filter_type[col]
|
|
569
|
+
|
|
570
|
+
# Filters are relative to first column in the autofilter.
|
|
571
|
+
write_filter_column(col - col1, type, *tokens)
|
|
572
|
+
end
|
|
573
|
+
end
|
|
574
|
+
|
|
575
|
+
#
|
|
576
|
+
# Write the <filterColumn> element.
|
|
577
|
+
#
|
|
578
|
+
def write_filter_column(col_id, type, *filters) # :nodoc:
|
|
579
|
+
@writer.tag_elements('filterColumn', [['colId', col_id]]) do
|
|
580
|
+
if type == 1
|
|
581
|
+
# Type == 1 is the new XLSX style filter.
|
|
582
|
+
write_filters(*filters)
|
|
583
|
+
else
|
|
584
|
+
# Type == 0 is the classic "custom" filter.
|
|
585
|
+
write_custom_filters(*filters)
|
|
586
|
+
end
|
|
587
|
+
end
|
|
588
|
+
end
|
|
589
|
+
|
|
590
|
+
#
|
|
591
|
+
# Write the <filters> element.
|
|
592
|
+
#
|
|
593
|
+
def write_filters(*filters) # :nodoc:
|
|
594
|
+
non_blanks = filters.reject { |filter| filter.to_s =~ /^blanks$/i }
|
|
595
|
+
attributes = []
|
|
596
|
+
|
|
597
|
+
attributes = [['blank', 1]] if filters != non_blanks
|
|
598
|
+
|
|
599
|
+
if filters.size == 1 && non_blanks.empty?
|
|
600
|
+
# Special case for blank cells only.
|
|
601
|
+
@writer.empty_tag('filters', attributes)
|
|
602
|
+
else
|
|
603
|
+
# General case.
|
|
604
|
+
@writer.tag_elements('filters', attributes) do
|
|
605
|
+
non_blanks.sort.each { |filter| write_filter(filter) }
|
|
606
|
+
end
|
|
607
|
+
end
|
|
608
|
+
end
|
|
609
|
+
|
|
610
|
+
#
|
|
611
|
+
# Write the <filter> element.
|
|
612
|
+
#
|
|
613
|
+
def write_filter(val) # :nodoc:
|
|
614
|
+
@writer.empty_tag('filter', [['val', val]])
|
|
615
|
+
end
|
|
616
|
+
|
|
617
|
+
#
|
|
618
|
+
# Write the <customFilters> element.
|
|
619
|
+
#
|
|
620
|
+
def write_custom_filters(*tokens) # :nodoc:
|
|
621
|
+
if tokens.size == 2
|
|
622
|
+
# One filter expression only.
|
|
623
|
+
@writer.tag_elements('customFilters') { write_custom_filter(*tokens) }
|
|
624
|
+
else
|
|
625
|
+
# Two filter expressions.
|
|
626
|
+
|
|
627
|
+
# Check if the "join" operand is "and" or "or".
|
|
628
|
+
attributes = if tokens[2] == 0
|
|
629
|
+
[['and', 1]]
|
|
630
|
+
else
|
|
631
|
+
[['and', 0]]
|
|
632
|
+
end
|
|
633
|
+
|
|
634
|
+
# Write the two custom filters.
|
|
635
|
+
@writer.tag_elements('customFilters', attributes) do
|
|
636
|
+
write_custom_filter(tokens[0], tokens[1])
|
|
637
|
+
write_custom_filter(tokens[3], tokens[4])
|
|
638
|
+
end
|
|
639
|
+
end
|
|
640
|
+
end
|
|
641
|
+
|
|
642
|
+
#
|
|
643
|
+
# Write the <customFilter> element.
|
|
644
|
+
#
|
|
645
|
+
def write_custom_filter(operator, val) # :nodoc:
|
|
646
|
+
operators = {
|
|
647
|
+
1 => 'lessThan',
|
|
648
|
+
2 => 'equal',
|
|
649
|
+
3 => 'lessThanOrEqual',
|
|
650
|
+
4 => 'greaterThan',
|
|
651
|
+
5 => 'notEqual',
|
|
652
|
+
6 => 'greaterThanOrEqual',
|
|
653
|
+
22 => 'equal'
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
# Convert the operator from a number to a descriptive string.
|
|
657
|
+
if operators[operator]
|
|
658
|
+
operator = operators[operator]
|
|
659
|
+
else
|
|
660
|
+
raise "Unknown operator = #{operator}\n"
|
|
661
|
+
end
|
|
662
|
+
|
|
663
|
+
# The 'equal' operator is the default attribute and isn't stored.
|
|
664
|
+
attributes = []
|
|
665
|
+
attributes << ['operator', operator] unless operator == 'equal'
|
|
666
|
+
attributes << ['val', val]
|
|
667
|
+
|
|
668
|
+
@writer.empty_tag('customFilter', attributes)
|
|
669
|
+
end
|
|
670
|
+
|
|
671
|
+
#
|
|
672
|
+
# Write the <mergeCells> element.
|
|
673
|
+
#
|
|
674
|
+
def write_merge_cells # :nodoc:
|
|
675
|
+
write_some_elements('mergeCells', @merge) do
|
|
676
|
+
@merge.each { |merged_range| write_merge_cell(merged_range) }
|
|
677
|
+
end
|
|
678
|
+
end
|
|
679
|
+
|
|
680
|
+
def write_some_elements(tag, container, &block)
|
|
681
|
+
return if container.empty?
|
|
682
|
+
|
|
683
|
+
@writer.tag_elements(tag, [['count', container.size]], &block)
|
|
684
|
+
end
|
|
685
|
+
|
|
686
|
+
#
|
|
687
|
+
# Write the <mergeCell> element.
|
|
688
|
+
#
|
|
689
|
+
def write_merge_cell(merged_range) # :nodoc:
|
|
690
|
+
row_min, col_min, row_max, col_max = merged_range
|
|
691
|
+
|
|
692
|
+
# Convert the merge dimensions to a cell range.
|
|
693
|
+
cell_1 = xl_rowcol_to_cell(row_min, col_min)
|
|
694
|
+
cell_2 = xl_rowcol_to_cell(row_max, col_max)
|
|
695
|
+
|
|
696
|
+
@writer.empty_tag('mergeCell', [['ref', "#{cell_1}:#{cell_2}"]])
|
|
697
|
+
end
|
|
698
|
+
|
|
699
|
+
#
|
|
700
|
+
# Write the Worksheet conditional formats.
|
|
701
|
+
#
|
|
702
|
+
def write_conditional_formats # :nodoc:
|
|
703
|
+
@cond_formats.keys.sort.each do |range|
|
|
704
|
+
write_conditional_formatting(range, @cond_formats[range])
|
|
705
|
+
end
|
|
706
|
+
end
|
|
707
|
+
|
|
708
|
+
# conditional formatting XML writing moved to worksheet/conditional_formats.rb
|
|
709
|
+
# see Writexlsx::Worksheet::ConditionalFormats#write_conditional_formatting
|
|
710
|
+
|
|
711
|
+
#
|
|
712
|
+
# Write the <dataValidations> element.
|
|
713
|
+
#
|
|
714
|
+
def write_data_validations # :nodoc:
|
|
715
|
+
write_some_elements('dataValidations', @validations) do
|
|
716
|
+
@validations.each { |validation| validation.write_data_validation(@writer) }
|
|
717
|
+
end
|
|
718
|
+
end
|
|
719
|
+
|
|
720
|
+
#
|
|
721
|
+
# Process any sored hyperlinks in row/col order and write the <hyperlinks>
|
|
722
|
+
# element. The attributes are different for internal and external links.
|
|
723
|
+
#
|
|
724
|
+
def write_hyperlinks # :nodoc:
|
|
725
|
+
return unless @hyperlinks
|
|
726
|
+
|
|
727
|
+
hlink_attributes = []
|
|
728
|
+
@hyperlinks.keys.sort.each do |row_num|
|
|
729
|
+
# Sort the hyperlinks into column order.
|
|
730
|
+
col_nums = @hyperlinks[row_num].keys.sort
|
|
731
|
+
# Iterate over the columns.
|
|
732
|
+
col_nums.each do |col_num|
|
|
733
|
+
# Get the link data for this cell.
|
|
734
|
+
link = @hyperlinks[row_num][col_num]
|
|
735
|
+
|
|
736
|
+
# If the cell isn't a string then we have to add the url as
|
|
737
|
+
# the string to display
|
|
738
|
+
if ptrue?(@cell_data_store) &&
|
|
739
|
+
ptrue?(@cell_data_store[row_num]) &&
|
|
740
|
+
ptrue?(@cell_data_store[row_num][col_num]) &&
|
|
741
|
+
@cell_data_store[row_num][col_num].display_url_string?
|
|
742
|
+
link.display_on
|
|
743
|
+
end
|
|
744
|
+
|
|
745
|
+
if link.respond_to?(:external_hyper_link)
|
|
746
|
+
# External link with rel file relationship.
|
|
747
|
+
@rel_count += 1
|
|
748
|
+
# Links for use by the packager.
|
|
749
|
+
@external_hyper_links << link.external_hyper_link
|
|
750
|
+
end
|
|
751
|
+
hlink_attributes << link.attributes(row_num, col_num, @rel_count)
|
|
752
|
+
end
|
|
753
|
+
end
|
|
754
|
+
|
|
755
|
+
return if hlink_attributes.empty?
|
|
756
|
+
|
|
757
|
+
# Write the hyperlink elements.
|
|
758
|
+
@writer.tag_elements('hyperlinks') do
|
|
759
|
+
hlink_attributes.each do |attributes|
|
|
760
|
+
@writer.empty_tag('hyperlink', attributes)
|
|
761
|
+
end
|
|
762
|
+
end
|
|
763
|
+
end
|
|
764
|
+
|
|
765
|
+
#
|
|
766
|
+
# Write the <printOptions> element.
|
|
767
|
+
#
|
|
768
|
+
def write_print_options # :nodoc:
|
|
769
|
+
@page_setup.write_print_options(@writer)
|
|
770
|
+
end
|
|
771
|
+
|
|
772
|
+
#
|
|
773
|
+
# Write the <pageMargins> element.
|
|
774
|
+
#
|
|
775
|
+
def write_page_margins # :nodoc:
|
|
776
|
+
@page_setup.write_page_margins(@writer)
|
|
777
|
+
end
|
|
778
|
+
|
|
779
|
+
#
|
|
780
|
+
# Write the <pageSetup> element.
|
|
781
|
+
#
|
|
782
|
+
def write_page_setup # :nodoc:
|
|
783
|
+
@page_setup.write_page_setup(@writer)
|
|
784
|
+
end
|
|
785
|
+
|
|
786
|
+
#
|
|
787
|
+
# Write the <headerFooter> element.
|
|
788
|
+
#
|
|
789
|
+
def write_header_footer # :nodoc:
|
|
790
|
+
@page_setup.write_header_footer(@writer, excel2003_style?)
|
|
791
|
+
end
|
|
792
|
+
|
|
793
|
+
#
|
|
794
|
+
# Write the <rowBreaks> element.
|
|
795
|
+
#
|
|
796
|
+
def write_row_breaks # :nodoc:
|
|
797
|
+
write_breaks('rowBreaks')
|
|
798
|
+
end
|
|
799
|
+
|
|
800
|
+
#
|
|
801
|
+
# Write the <colBreaks> element.
|
|
802
|
+
#
|
|
803
|
+
def write_col_breaks # :nodoc:
|
|
804
|
+
write_breaks('colBreaks')
|
|
805
|
+
end
|
|
806
|
+
|
|
807
|
+
def write_breaks(tag) # :nodoc:
|
|
808
|
+
case tag
|
|
809
|
+
when 'rowBreaks'
|
|
810
|
+
page_breaks = sort_pagebreaks(*@page_setup.hbreaks)
|
|
811
|
+
max = 16383
|
|
812
|
+
when 'colBreaks'
|
|
813
|
+
page_breaks = sort_pagebreaks(*@page_setup.vbreaks)
|
|
814
|
+
max = 1048575
|
|
815
|
+
else
|
|
816
|
+
raise "Invalid parameter '#{tag}' in write_breaks."
|
|
817
|
+
end
|
|
818
|
+
count = page_breaks.size
|
|
819
|
+
|
|
820
|
+
return if page_breaks.empty?
|
|
821
|
+
|
|
822
|
+
attributes = [
|
|
823
|
+
['count', count],
|
|
824
|
+
['manualBreakCount', count]
|
|
825
|
+
]
|
|
826
|
+
|
|
827
|
+
@writer.tag_elements(tag, attributes) do
|
|
828
|
+
page_breaks.each { |num| write_brk(num, max) }
|
|
829
|
+
end
|
|
830
|
+
end
|
|
831
|
+
|
|
832
|
+
#
|
|
833
|
+
# Write the <brk> element.
|
|
834
|
+
#
|
|
835
|
+
def write_brk(id, max) # :nodoc:
|
|
836
|
+
attributes = [
|
|
837
|
+
['id', id],
|
|
838
|
+
['max', max],
|
|
839
|
+
['man', 1]
|
|
840
|
+
]
|
|
841
|
+
|
|
842
|
+
@writer.empty_tag('brk', attributes)
|
|
843
|
+
end
|
|
844
|
+
|
|
845
|
+
#
|
|
846
|
+
# Write the <ignoredErrors> element.
|
|
847
|
+
#
|
|
848
|
+
def write_ignored_errors
|
|
849
|
+
return unless @ignore_errors
|
|
850
|
+
|
|
851
|
+
ignore = @ignore_errors
|
|
852
|
+
|
|
853
|
+
@writer.tag_elements('ignoredErrors') do
|
|
854
|
+
{
|
|
855
|
+
number_stored_as_text: 'numberStoredAsText',
|
|
856
|
+
eval_error: 'evalError',
|
|
857
|
+
formula_differs: 'formula',
|
|
858
|
+
formula_range: 'formulaRange',
|
|
859
|
+
formula_unlocked: 'unlockedFormula',
|
|
860
|
+
empty_cell_reference: 'emptyCellReference',
|
|
861
|
+
list_data_validation: 'listDataValidation',
|
|
862
|
+
calculated_column: 'calculatedColumn',
|
|
863
|
+
two_digit_text_year: 'twoDigitTextYear'
|
|
864
|
+
}.each do |key, value|
|
|
865
|
+
write_ignored_error(value, ignore[key]) if ignore[key]
|
|
866
|
+
end
|
|
867
|
+
end
|
|
868
|
+
end
|
|
869
|
+
|
|
870
|
+
#
|
|
871
|
+
# Write the <ignoredError> element.
|
|
872
|
+
#
|
|
873
|
+
def write_ignored_error(type, sqref)
|
|
874
|
+
attributes = [
|
|
875
|
+
['sqref', sqref],
|
|
876
|
+
[type, 1]
|
|
877
|
+
]
|
|
878
|
+
|
|
879
|
+
@writer.empty_tag('ignoredError', attributes)
|
|
880
|
+
end
|
|
881
|
+
|
|
882
|
+
#
|
|
883
|
+
# Write the <tableParts> element.
|
|
884
|
+
#
|
|
885
|
+
def write_table_parts
|
|
886
|
+
return if @assets.tables.empty?
|
|
887
|
+
|
|
888
|
+
@writer.tag_elements('tableParts', [['count', tables_count]]) do
|
|
889
|
+
tables_count.times { increment_rel_id_and_write_r_id('tablePart') }
|
|
890
|
+
end
|
|
891
|
+
end
|
|
892
|
+
|
|
893
|
+
#
|
|
894
|
+
# Write the <tablePart> element.
|
|
895
|
+
#
|
|
896
|
+
def write_table_part(id)
|
|
897
|
+
@writer.empty_tag('tablePart', [r_id_attributes(id)])
|
|
898
|
+
end
|
|
899
|
+
|
|
900
|
+
def increment_rel_id_and_write_r_id(tag)
|
|
901
|
+
@rel_count += 1
|
|
902
|
+
write_r_id(tag, @rel_count)
|
|
903
|
+
end
|
|
904
|
+
|
|
905
|
+
def write_r_id(tag, id)
|
|
906
|
+
@writer.empty_tag(tag, [r_id_attributes(id)])
|
|
907
|
+
end
|
|
908
|
+
|
|
909
|
+
#
|
|
910
|
+
# Write the <extLst> element for data bars and sparklines.
|
|
911
|
+
#
|
|
912
|
+
def write_ext_list # :nodoc:
|
|
913
|
+
return if @data_bars_2010.empty? && @assets.sparklines.empty?
|
|
914
|
+
|
|
915
|
+
@writer.tag_elements('extLst') do
|
|
916
|
+
write_ext_list_data_bars unless @data_bars_2010.empty?
|
|
917
|
+
write_ext_list_sparklines unless @assets.sparklines.empty?
|
|
918
|
+
end
|
|
919
|
+
end
|
|
920
|
+
|
|
921
|
+
#
|
|
922
|
+
# Write the Excel 2010 data_bar subelements.
|
|
923
|
+
#
|
|
924
|
+
def write_ext_list_data_bars
|
|
925
|
+
# Write the ext element.
|
|
926
|
+
write_ext('{78C0D931-6437-407d-A8EE-F0AAD7539E65}') do
|
|
927
|
+
@writer.tag_elements('x14:conditionalFormattings') do
|
|
928
|
+
# Write each of the Excel 2010 conditional formatting data bar elements.
|
|
929
|
+
@data_bars_2010.each do |data_bar|
|
|
930
|
+
# Write the x14:conditionalFormatting element.
|
|
931
|
+
write_conditional_formatting_2010(data_bar)
|
|
932
|
+
end
|
|
933
|
+
end
|
|
934
|
+
end
|
|
935
|
+
end
|
|
936
|
+
|
|
937
|
+
def write_ext(url, &block)
|
|
938
|
+
attributes = [
|
|
939
|
+
['xmlns:x14', "#{OFFICE_URL}spreadsheetml/2009/9/main"],
|
|
940
|
+
['uri', url]
|
|
941
|
+
]
|
|
942
|
+
@writer.tag_elements('ext', attributes, &block)
|
|
943
|
+
end
|
|
944
|
+
|
|
945
|
+
#
|
|
946
|
+
# Write the <x14:conditionalFormatting> element.
|
|
947
|
+
#
|
|
948
|
+
def write_conditional_formatting_2010(data_bar)
|
|
949
|
+
xmlns_xm = 'http://schemas.microsoft.com/office/excel/2006/main'
|
|
950
|
+
|
|
951
|
+
attributes = [['xmlns:xm', xmlns_xm]]
|
|
952
|
+
|
|
953
|
+
@writer.tag_elements('x14:conditionalFormatting', attributes) do
|
|
954
|
+
# Write the '<x14:cfRule element.
|
|
955
|
+
write_x14_cf_rule(data_bar)
|
|
956
|
+
|
|
957
|
+
# Write the x14:dataBar element.
|
|
958
|
+
write_x14_data_bar(data_bar)
|
|
959
|
+
|
|
960
|
+
# Write the x14 max and min data bars.
|
|
961
|
+
write_x14_cfvo(data_bar[:x14_min_type], data_bar[:min_value])
|
|
962
|
+
write_x14_cfvo(data_bar[:x14_max_type], data_bar[:max_value])
|
|
963
|
+
|
|
964
|
+
# Write the x14:borderColor element.
|
|
965
|
+
write_x14_border_color(data_bar[:bar_border_color]) unless ptrue?(data_bar[:bar_no_border])
|
|
966
|
+
|
|
967
|
+
# Write the x14:negativeFillColor element.
|
|
968
|
+
write_x14_negative_fill_color(data_bar[:bar_negative_color]) unless ptrue?(data_bar[:bar_negative_color_same])
|
|
969
|
+
|
|
970
|
+
# Write the x14:negativeBorderColor element.
|
|
971
|
+
if !ptrue?(data_bar[:bar_no_border]) &&
|
|
972
|
+
!ptrue?(data_bar[:bar_negative_border_color_same])
|
|
973
|
+
write_x14_negative_border_color(
|
|
974
|
+
data_bar[:bar_negative_border_color]
|
|
975
|
+
)
|
|
976
|
+
end
|
|
977
|
+
|
|
978
|
+
# Write the x14:axisColor element.
|
|
979
|
+
write_x14_axis_color(data_bar[:bar_axis_color]) if data_bar[:bar_axis_position] != 'none'
|
|
980
|
+
|
|
981
|
+
# Write closing elements.
|
|
982
|
+
@writer.end_tag('x14:dataBar')
|
|
983
|
+
@writer.end_tag('x14:cfRule')
|
|
984
|
+
|
|
985
|
+
# Add the conditional format range.
|
|
986
|
+
@writer.data_element('xm:sqref', data_bar[:range])
|
|
987
|
+
end
|
|
988
|
+
end
|
|
989
|
+
|
|
990
|
+
#
|
|
991
|
+
# Write the <cfvo> element.
|
|
992
|
+
#
|
|
993
|
+
def write_x14_cfvo(type, value)
|
|
994
|
+
attributes = [['type', type]]
|
|
995
|
+
|
|
996
|
+
if %w[min max autoMin autoMax].include?(type)
|
|
997
|
+
@writer.empty_tag('x14:cfvo', attributes)
|
|
998
|
+
else
|
|
999
|
+
@writer.tag_elements('x14:cfvo', attributes) do
|
|
1000
|
+
@writer.data_element('xm:f', value)
|
|
1001
|
+
end
|
|
1002
|
+
end
|
|
1003
|
+
end
|
|
1004
|
+
|
|
1005
|
+
#
|
|
1006
|
+
# Write the <'<x14:cfRule> element.
|
|
1007
|
+
#
|
|
1008
|
+
def write_x14_cf_rule(data_bar)
|
|
1009
|
+
type = 'dataBar'
|
|
1010
|
+
id = data_bar[:guid]
|
|
1011
|
+
|
|
1012
|
+
attributes = [
|
|
1013
|
+
['type', type],
|
|
1014
|
+
['id', id]
|
|
1015
|
+
]
|
|
1016
|
+
|
|
1017
|
+
@writer.start_tag('x14:cfRule', attributes)
|
|
1018
|
+
end
|
|
1019
|
+
|
|
1020
|
+
#
|
|
1021
|
+
# Write the <x14:dataBar> element.
|
|
1022
|
+
#
|
|
1023
|
+
def write_x14_data_bar(data_bar)
|
|
1024
|
+
min_length = 0
|
|
1025
|
+
max_length = 100
|
|
1026
|
+
|
|
1027
|
+
attributes = [
|
|
1028
|
+
['minLength', min_length],
|
|
1029
|
+
['maxLength', max_length]
|
|
1030
|
+
]
|
|
1031
|
+
|
|
1032
|
+
attributes << ['border', 1] unless ptrue?(data_bar[:bar_no_border])
|
|
1033
|
+
attributes << ['gradient', 0] if ptrue?(data_bar[:bar_solid])
|
|
1034
|
+
|
|
1035
|
+
attributes << %w[direction leftToRight] if data_bar[:bar_direction] == 'left'
|
|
1036
|
+
attributes << %w[direction rightToLeft] if data_bar[:bar_direction] == 'right'
|
|
1037
|
+
|
|
1038
|
+
attributes << ['negativeBarColorSameAsPositive', 1] if ptrue?(data_bar[:bar_negative_color_same])
|
|
1039
|
+
|
|
1040
|
+
if !ptrue?(data_bar[:bar_no_border]) &&
|
|
1041
|
+
!ptrue?(data_bar[:bar_negative_border_color_same])
|
|
1042
|
+
attributes << ['negativeBarBorderColorSameAsPositive', 0]
|
|
1043
|
+
end
|
|
1044
|
+
|
|
1045
|
+
attributes << %w[axisPosition middle] if data_bar[:bar_axis_position] == 'middle'
|
|
1046
|
+
|
|
1047
|
+
attributes << %w[axisPosition none] if data_bar[:bar_axis_position] == 'none'
|
|
1048
|
+
|
|
1049
|
+
@writer.start_tag('x14:dataBar', attributes)
|
|
1050
|
+
end
|
|
1051
|
+
|
|
1052
|
+
#
|
|
1053
|
+
# Write the <x14:borderColor> element.
|
|
1054
|
+
#
|
|
1055
|
+
def write_x14_border_color(rgb)
|
|
1056
|
+
attributes = [['rgb', rgb]]
|
|
1057
|
+
|
|
1058
|
+
@writer.empty_tag('x14:borderColor', attributes)
|
|
1059
|
+
end
|
|
1060
|
+
|
|
1061
|
+
#
|
|
1062
|
+
# Write the <x14:negativeFillColor> element.
|
|
1063
|
+
#
|
|
1064
|
+
def write_x14_negative_fill_color(rgb)
|
|
1065
|
+
attributes = [['rgb', rgb]]
|
|
1066
|
+
|
|
1067
|
+
@writer.empty_tag('x14:negativeFillColor', attributes)
|
|
1068
|
+
end
|
|
1069
|
+
|
|
1070
|
+
#
|
|
1071
|
+
# Write the <x14:negativeBorderColor> element.
|
|
1072
|
+
#
|
|
1073
|
+
def write_x14_negative_border_color(rgb)
|
|
1074
|
+
attributes = [['rgb', rgb]]
|
|
1075
|
+
|
|
1076
|
+
@writer.empty_tag('x14:negativeBorderColor', attributes)
|
|
1077
|
+
end
|
|
1078
|
+
|
|
1079
|
+
#
|
|
1080
|
+
# Write the <x14:axisColor> element.
|
|
1081
|
+
#
|
|
1082
|
+
def write_x14_axis_color(rgb)
|
|
1083
|
+
attributes = [['rgb', rgb]]
|
|
1084
|
+
|
|
1085
|
+
@writer.empty_tag('x14:axisColor', attributes)
|
|
1086
|
+
end
|
|
1087
|
+
|
|
1088
|
+
#
|
|
1089
|
+
# Write the sparkline subelements.
|
|
1090
|
+
#
|
|
1091
|
+
def write_ext_list_sparklines
|
|
1092
|
+
# Write the ext element.
|
|
1093
|
+
write_ext('{05C60535-1F16-4fd2-B633-F4F36F0B64E0}') do
|
|
1094
|
+
# Write the x14:sparklineGroups element.
|
|
1095
|
+
write_sparkline_groups
|
|
1096
|
+
end
|
|
1097
|
+
end
|
|
1098
|
+
|
|
1099
|
+
def write_sparkline_groups
|
|
1100
|
+
# Write the x14:sparklineGroups element.
|
|
1101
|
+
@writer.tag_elements('x14:sparklineGroups', sparkline_groups_attributes) do
|
|
1102
|
+
# Write the sparkline elements.
|
|
1103
|
+
@assets.sparklines.reverse.each do |sparkline|
|
|
1104
|
+
sparkline.write_sparkline_group(@writer)
|
|
1105
|
+
end
|
|
1106
|
+
end
|
|
1107
|
+
end
|
|
1108
|
+
|
|
1109
|
+
def sparkline_groups_attributes # :nodoc:
|
|
1110
|
+
[
|
|
1111
|
+
['xmlns:xm', "#{OFFICE_URL}excel/2006/main"]
|
|
1112
|
+
]
|
|
1113
|
+
end
|
|
1114
|
+
|
|
1115
|
+
#
|
|
1116
|
+
# Write the <sheetPr> element for Sheet level properties.
|
|
1117
|
+
#
|
|
1118
|
+
def write_sheet_pr # :nodoc:
|
|
1119
|
+
return unless tab_outline_fit? || vba_codename? || filter_on?
|
|
1120
|
+
|
|
1121
|
+
attributes = []
|
|
1122
|
+
attributes << ['codeName', @vba_codename] if vba_codename?
|
|
1123
|
+
attributes << ['filterMode', 1] if filter_on?
|
|
1124
|
+
|
|
1125
|
+
if tab_outline_fit?
|
|
1126
|
+
@writer.tag_elements('sheetPr', attributes) do
|
|
1127
|
+
write_tab_color
|
|
1128
|
+
write_outline_pr
|
|
1129
|
+
write_page_set_up_pr
|
|
1130
|
+
end
|
|
1131
|
+
else
|
|
1132
|
+
@writer.empty_tag('sheetPr', attributes)
|
|
1133
|
+
end
|
|
1134
|
+
end
|
|
1135
|
+
|
|
1136
|
+
#
|
|
1137
|
+
# Write the <tabColor> element.
|
|
1138
|
+
#
|
|
1139
|
+
def write_tab_color # :nodoc:
|
|
1140
|
+
return unless tab_color?
|
|
1141
|
+
|
|
1142
|
+
@writer.empty_tag(
|
|
1143
|
+
'tabColor',
|
|
1144
|
+
[
|
|
1145
|
+
['rgb', palette_color(@tab_color)]
|
|
1146
|
+
]
|
|
1147
|
+
)
|
|
1148
|
+
end
|
|
1149
|
+
|
|
1150
|
+
#
|
|
1151
|
+
# Write the <outlinePr> element.
|
|
1152
|
+
#
|
|
1153
|
+
def write_outline_pr
|
|
1154
|
+
return unless outline_changed?
|
|
1155
|
+
|
|
1156
|
+
attributes = []
|
|
1157
|
+
attributes << ["applyStyles", 1] if @outline_style
|
|
1158
|
+
attributes << ["summaryBelow", 0] if @outline_below == 0
|
|
1159
|
+
attributes << ["summaryRight", 0] if @outline_right == 0
|
|
1160
|
+
attributes << ["showOutlineSymbols", 0] if @outline_on == 0
|
|
1161
|
+
|
|
1162
|
+
@writer.empty_tag('outlinePr', attributes)
|
|
1163
|
+
end
|
|
1164
|
+
|
|
1165
|
+
#
|
|
1166
|
+
# Write the <pageSetUpPr> element.
|
|
1167
|
+
#
|
|
1168
|
+
def write_page_set_up_pr # :nodoc:
|
|
1169
|
+
@writer.empty_tag('pageSetUpPr', [['fitToPage', 1]]) if fit_page?
|
|
1170
|
+
end
|
|
1171
|
+
|
|
1172
|
+
# Write the <dimension> element. This specifies the range of cells in the
|
|
1173
|
+
# worksheet. As a special case, empty spreadsheets use 'A1' as a range.
|
|
1174
|
+
#
|
|
1175
|
+
def write_dimension # :nodoc:
|
|
1176
|
+
if !@dim_rowmin && !@dim_colmin
|
|
1177
|
+
# If the min dims are undefined then no dimensions have been set
|
|
1178
|
+
# and we use the default 'A1'.
|
|
1179
|
+
ref = 'A1'
|
|
1180
|
+
elsif !@dim_rowmin && @dim_colmin
|
|
1181
|
+
# If the row dims aren't set but the column dims are then they
|
|
1182
|
+
# have been changed via set_column().
|
|
1183
|
+
if @dim_colmin == @dim_colmax
|
|
1184
|
+
# The dimensions are a single cell and not a range.
|
|
1185
|
+
ref = xl_rowcol_to_cell(0, @dim_colmin)
|
|
1186
|
+
else
|
|
1187
|
+
# The dimensions are a cell range.
|
|
1188
|
+
cell_1 = xl_rowcol_to_cell(0, @dim_colmin)
|
|
1189
|
+
cell_2 = xl_rowcol_to_cell(0, @dim_colmax)
|
|
1190
|
+
ref = cell_1 + ':' + cell_2
|
|
1191
|
+
end
|
|
1192
|
+
elsif @dim_rowmin == @dim_rowmax && @dim_colmin == @dim_colmax
|
|
1193
|
+
# The dimensions are a single cell and not a range.
|
|
1194
|
+
ref = xl_rowcol_to_cell(@dim_rowmin, @dim_colmin)
|
|
1195
|
+
else
|
|
1196
|
+
# The dimensions are a cell range.
|
|
1197
|
+
cell_1 = xl_rowcol_to_cell(@dim_rowmin, @dim_colmin)
|
|
1198
|
+
cell_2 = xl_rowcol_to_cell(@dim_rowmax, @dim_colmax)
|
|
1199
|
+
ref = cell_1 + ':' + cell_2
|
|
1200
|
+
end
|
|
1201
|
+
@writer.empty_tag('dimension', [['ref', ref]])
|
|
1202
|
+
end
|
|
1203
|
+
|
|
1204
|
+
#
|
|
1205
|
+
# Write the <selection> elements.
|
|
1206
|
+
#
|
|
1207
|
+
def write_selections # :nodoc:
|
|
1208
|
+
@selections.each { |selection| write_selection(*selection) }
|
|
1209
|
+
end
|
|
1210
|
+
|
|
1211
|
+
#
|
|
1212
|
+
# Write the <selection> element.
|
|
1213
|
+
#
|
|
1214
|
+
def write_selection(pane, active_cell, sqref) # :nodoc:
|
|
1215
|
+
attributes = []
|
|
1216
|
+
attributes << ['pane', pane] if pane
|
|
1217
|
+
attributes << ['activeCell', active_cell] if active_cell
|
|
1218
|
+
attributes << ['sqref', sqref] if sqref
|
|
1219
|
+
|
|
1220
|
+
@writer.empty_tag('selection', attributes)
|
|
1221
|
+
end
|
|
1222
|
+
|
|
1223
|
+
#
|
|
1224
|
+
# Write the <sheetFormatPr> element.
|
|
1225
|
+
#
|
|
1226
|
+
def write_sheet_format_pr # :nodoc:
|
|
1227
|
+
attributes = [
|
|
1228
|
+
['defaultRowHeight', @default_row_height]
|
|
1229
|
+
]
|
|
1230
|
+
attributes << ['customHeight', 1] if @default_row_height != @original_row_height
|
|
1231
|
+
|
|
1232
|
+
attributes << ['zeroHeight', 1] if ptrue?(@default_row_zeroed)
|
|
1233
|
+
|
|
1234
|
+
attributes << ['outlineLevelRow', @outline_row_level] if @outline_row_level > 0
|
|
1235
|
+
attributes << ['outlineLevelCol', @outline_col_level] if @outline_col_level > 0
|
|
1236
|
+
attributes << ['x14ac:dyDescent', '0.25'] if @excel_version == 2010
|
|
1237
|
+
@writer.empty_tag('sheetFormatPr', attributes)
|
|
1238
|
+
end
|
|
1239
|
+
end
|
|
1240
|
+
end
|
|
1241
|
+
end
|