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