xlsxrb 0.1.16 → 0.1.17
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/CHANGELOG.md +64 -0
- data/README.md +2 -2
- data/lib/xlsxrb/chart_builder.rb +41 -2
- data/lib/xlsxrb/dsl_helpers.rb +522 -6
- data/lib/xlsxrb/elements/cell.rb +34 -9
- data/lib/xlsxrb/elements/column.rb +2 -2
- data/lib/xlsxrb/elements/types.rb +1 -0
- data/lib/xlsxrb/elements/workbook.rb +48 -0
- data/lib/xlsxrb/elements/worksheet.rb +158 -11
- data/lib/xlsxrb/ooxml/reader/listeners/core_listeners.rb +22 -1
- data/lib/xlsxrb/ooxml/reader/listeners/drawing_listeners.rb +187 -24
- data/lib/xlsxrb/ooxml/reader/listeners/feature_listeners.rb +108 -1
- data/lib/xlsxrb/ooxml/reader/listeners/style_listeners.rb +16 -11
- data/lib/xlsxrb/ooxml/reader.rb +88 -0
- data/lib/xlsxrb/ooxml/styles_parser.rb +19 -11
- data/lib/xlsxrb/ooxml/workbook_parser.rb +18 -3
- data/lib/xlsxrb/ooxml/workbook_writer.rb +266 -46
- data/lib/xlsxrb/ooxml/worksheet_parser.rb +18 -4
- data/lib/xlsxrb/ooxml/worksheet_writer.rb +289 -88
- data/lib/xlsxrb/ooxml/writer/drawing_xml.rb +579 -441
- data/lib/xlsxrb/ooxml/writer/features_xml.rb +26 -9
- data/lib/xlsxrb/ooxml/writer/styles_xml.rb +23 -9
- data/lib/xlsxrb/ooxml/writer.rb +268 -31
- data/lib/xlsxrb/ooxml/zip_writer.rb +15 -5
- data/lib/xlsxrb/stream_sheet.rb +157 -3
- data/lib/xlsxrb/stream_writer.rb +946 -74
- data/lib/xlsxrb/style_builder.rb +59 -6
- data/lib/xlsxrb/utils.rb +65 -0
- data/lib/xlsxrb/version.rb +1 -1
- data/lib/xlsxrb/workbook_builder.rb +197 -14
- data/lib/xlsxrb/worksheet_builder.rb +1137 -44
- data/lib/xlsxrb.rb +302 -47
- data/sig/generated/xlsxrb/chart_builder.rbs +31 -3
- data/sig/generated/xlsxrb/dsl_helpers.rbs +119 -4
- data/sig/generated/xlsxrb/elements/cell.rbs +20 -3
- data/sig/generated/xlsxrb/elements/column.rbs +3 -3
- data/sig/generated/xlsxrb/elements/types.rbs +2 -0
- data/sig/generated/xlsxrb/elements/workbook.rbs +37 -0
- data/sig/generated/xlsxrb/elements/worksheet.rbs +82 -2
- data/sig/generated/xlsxrb/ooxml/reader/listeners/core_listeners.rbs +2 -0
- data/sig/generated/xlsxrb/ooxml/reader/listeners/drawing_listeners.rbs +74 -2
- data/sig/generated/xlsxrb/ooxml/reader/listeners/feature_listeners.rbs +36 -0
- data/sig/generated/xlsxrb/ooxml/reader/listeners/style_listeners.rbs +3 -0
- data/sig/generated/xlsxrb/ooxml/reader.rbs +17 -0
- data/sig/generated/xlsxrb/ooxml/styles_parser.rbs +3 -0
- data/sig/generated/xlsxrb/ooxml/workbook_parser.rbs +8 -0
- data/sig/generated/xlsxrb/ooxml/workbook_writer.rbs +8 -4
- data/sig/generated/xlsxrb/ooxml/worksheet_parser.rbs +1 -1
- data/sig/generated/xlsxrb/ooxml/worksheet_writer.rbs +12 -1
- data/sig/generated/xlsxrb/ooxml/writer/drawing_xml.rbs +16 -0
- data/sig/generated/xlsxrb/ooxml/writer.rbs +42 -8
- data/sig/generated/xlsxrb/ooxml/zip_writer.rbs +8 -2
- data/sig/generated/xlsxrb/stream_sheet.rbs +101 -1
- data/sig/generated/xlsxrb/stream_writer.rbs +592 -23
- data/sig/generated/xlsxrb/style_builder.rbs +24 -6
- data/sig/generated/xlsxrb/utils.rbs +20 -0
- data/sig/generated/xlsxrb/workbook_builder.rbs +87 -5
- data/sig/generated/xlsxrb/worksheet_builder.rbs +554 -10
- data/sig/generated/xlsxrb.rbs +77 -16
- metadata +1 -1
|
@@ -54,6 +54,8 @@ module Xlsxrb
|
|
|
54
54
|
return if @started
|
|
55
55
|
|
|
56
56
|
@started = true
|
|
57
|
+
@sheet_properties = sheet_properties
|
|
58
|
+
@sheet_format = sheet_format
|
|
57
59
|
@builder.declaration
|
|
58
60
|
@builder.open_tag("worksheet", { xmlns: SSML_NS, "xmlns:r": DOC_REL_NS })
|
|
59
61
|
|
|
@@ -130,10 +132,15 @@ module Xlsxrb
|
|
|
130
132
|
cell_type_val = nil
|
|
131
133
|
end
|
|
132
134
|
|
|
135
|
+
cell_style_id = resolve_style_id(cell_style_id, nil)
|
|
133
136
|
col_style = @column_styles && col_idx ? @column_styles[col_idx] : nil
|
|
134
|
-
|
|
137
|
+
col_style = resolve_style_id(col_style, nil)
|
|
138
|
+
row_style = resolve_style_id(attrs[:style_index], nil)
|
|
139
|
+
style_id = cell_style_id.nil? ? (row_style || col_style) : cell_style_id
|
|
135
140
|
style_id = nil unless style_id.is_a?(Integer)
|
|
136
141
|
|
|
142
|
+
raise ArgumentError, "Non-finite Float #{value} is not supported in XLSX" if value.is_a?(Float) && !value.finite?
|
|
143
|
+
|
|
137
144
|
# Fast path for common unstyled cells
|
|
138
145
|
if !formula && !style_id && !cell_type_val
|
|
139
146
|
case value
|
|
@@ -204,6 +211,8 @@ module Xlsxrb
|
|
|
204
211
|
formula_expr = formula_expr[1..] if formula_expr.is_a?(String) && formula_expr.start_with?("=")
|
|
205
212
|
end
|
|
206
213
|
|
|
214
|
+
raise ArgumentError, "Non-finite Float #{xml_val} is not supported in XLSX" if xml_val.is_a?(Float) && !xml_val.finite?
|
|
215
|
+
|
|
207
216
|
if formula_expr
|
|
208
217
|
case xml_val
|
|
209
218
|
when true
|
|
@@ -257,43 +266,11 @@ module Xlsxrb
|
|
|
257
266
|
|
|
258
267
|
if type == "inlineStr"
|
|
259
268
|
if xml_val.is_a?(Xlsxrb::Elements::RichText)
|
|
260
|
-
buf
|
|
261
|
-
xml_val.runs.each do |run|
|
|
262
|
-
font = run[:font]
|
|
263
|
-
if font && !font.empty?
|
|
264
|
-
buf << "<r><rPr>"
|
|
265
|
-
buf << "<b/>" if font[:bold]
|
|
266
|
-
buf << "<i/>" if font[:italic]
|
|
267
|
-
buf << "<strike/>" if font[:strike]
|
|
268
|
-
if font[:underline]
|
|
269
|
-
if font[:underline] == true
|
|
270
|
-
buf << "<u/>"
|
|
271
|
-
else
|
|
272
|
-
buf << '<u val="' << font[:underline].to_s << '"/>'
|
|
273
|
-
end
|
|
274
|
-
end
|
|
275
|
-
buf << '<vertAlign val="' << font[:vert_align].to_s << '"/>' if font[:vert_align]
|
|
276
|
-
buf << '<sz val="' << font[:sz].to_s << '"/>' if font[:sz]
|
|
277
|
-
if font[:color]
|
|
278
|
-
buf << '<color rgb="' << font[:color].to_s << '"/>'
|
|
279
|
-
elsif font[:theme]
|
|
280
|
-
tint_attr = font[:tint] ? " tint=\"#{font[:tint]}\"" : ""
|
|
281
|
-
buf << '<color theme="' << font[:theme].to_s << '"' << tint_attr << "/>"
|
|
282
|
-
end
|
|
283
|
-
buf << '<rFont val="' << escape_xml(font[:name]) << '"/>' if font[:name]
|
|
284
|
-
buf << '<family val="' << font[:family].to_s << '"/>' if font[:family]
|
|
285
|
-
buf << '<scheme val="' << font[:scheme].to_s << '"/>' if font[:scheme]
|
|
286
|
-
buf << "</rPr><t>"
|
|
287
|
-
else
|
|
288
|
-
buf << "<r><t>"
|
|
289
|
-
end
|
|
290
|
-
buf << escape_xml(run[:text]) << "</t></r>"
|
|
291
|
-
end
|
|
292
|
-
buf << "</is></c>"
|
|
269
|
+
write_inline_rich_text(buf, xml_val)
|
|
293
270
|
else
|
|
294
271
|
buf << "><is><t>" << escape_xml(xml_val.to_s) << "</t></is></c>"
|
|
295
272
|
end
|
|
296
|
-
elsif formula_expr
|
|
273
|
+
elsif formula_expr || (formula_type && formula_type.to_sym == :shared && !formula_si.nil?)
|
|
297
274
|
f_attrs = +""
|
|
298
275
|
if formula_type
|
|
299
276
|
case formula_type.to_sym
|
|
@@ -307,7 +284,11 @@ module Xlsxrb
|
|
|
307
284
|
end
|
|
308
285
|
end
|
|
309
286
|
f_attrs << ' ca="1"' if formula_ca
|
|
310
|
-
|
|
287
|
+
if formula_expr && !formula_expr.empty?
|
|
288
|
+
buf << "><f#{f_attrs}>" << escape_xml(formula_expr) << "</f>"
|
|
289
|
+
else
|
|
290
|
+
buf << "><f#{f_attrs}/>"
|
|
291
|
+
end
|
|
311
292
|
if xml_val.nil?
|
|
312
293
|
buf << "<v>0</v></c>"
|
|
313
294
|
else
|
|
@@ -356,6 +337,8 @@ module Xlsxrb
|
|
|
356
337
|
val_str = value >= 0 && value < 65_536 ? INTEGER_STRINGS[value] : value.to_s
|
|
357
338
|
buf << '<c r="' << col_ref << row_num_str << '"><v>' << val_str << "</v></c>"
|
|
358
339
|
when Float
|
|
340
|
+
raise ArgumentError, "Non-finite Float #{value} is not supported in XLSX" unless value.finite?
|
|
341
|
+
|
|
359
342
|
buf << '<c r="' << col_ref << row_num_str << '"><v>' << value.to_s << "</v></c>"
|
|
360
343
|
when String
|
|
361
344
|
raise ArgumentError, "Cell text length #{value.length} exceeds Excel limit of 32,767 characters" if @strict_excel_mode && value.length > 32_767
|
|
@@ -389,6 +372,8 @@ module Xlsxrb
|
|
|
389
372
|
"<c r=\"#{col_ref}#{row_num_str}\"><v>#{Xlsxrb::Ooxml::Utils.datetime_to_serial(value, date1904: @date1904)}</v></c>"
|
|
390
373
|
end
|
|
391
374
|
when Xlsxrb::Elements::Formula
|
|
375
|
+
raise ArgumentError, "Non-finite Float #{value.cached_value} is not supported in XLSX" if value.cached_value.is_a?(Float) && !value.cached_value.finite?
|
|
376
|
+
|
|
392
377
|
formula_expr = value.expression
|
|
393
378
|
formula_expr = formula_expr[1..] if formula_expr.start_with?("=")
|
|
394
379
|
f_attrs = +""
|
|
@@ -420,20 +405,27 @@ module Xlsxrb
|
|
|
420
405
|
else
|
|
421
406
|
v_val = value.cached_value.is_a?(String) ? escape_xml(value.cached_value) : value.cached_value
|
|
422
407
|
end
|
|
423
|
-
|
|
408
|
+
f_tag = if formula_expr && !formula_expr.empty?
|
|
409
|
+
"<f#{f_attrs}>#{escape_xml(formula_expr)}</f>"
|
|
410
|
+
else
|
|
411
|
+
"<f#{f_attrs}/>"
|
|
412
|
+
end
|
|
413
|
+
buf << "<c r=\"#{col_ref}#{row_num_str}\"#{c_type}>#{f_tag}<v>#{v_val}</v></c>"
|
|
424
414
|
when Hash
|
|
425
|
-
if value.key?(:formula)
|
|
415
|
+
if value.key?(:formula) || value.key?(:expression) || (value[:type]&.to_sym == :shared && (value.key?(:shared_index) || value.key?(:si)))
|
|
426
416
|
formula_expr = value[:formula] || value[:expression]
|
|
427
|
-
formula_expr = formula_expr[1..] if formula_expr.start_with?("=")
|
|
417
|
+
formula_expr = formula_expr[1..] if formula_expr.is_a?(String) && formula_expr.start_with?("=")
|
|
428
418
|
f_attrs = +""
|
|
429
|
-
|
|
430
|
-
|
|
419
|
+
f_type = value[:type] || (:shared if value.key?(:shared_index) || value.key?(:si))
|
|
420
|
+
if f_type
|
|
421
|
+
case f_type.to_sym
|
|
431
422
|
when :array
|
|
432
423
|
f_attrs << ' t="array"'
|
|
433
424
|
f_attrs << %( ref="#{value[:ref]}") if value[:ref]
|
|
434
425
|
when :shared
|
|
435
426
|
f_attrs << ' t="shared"'
|
|
436
|
-
|
|
427
|
+
si = value[:si] || value[:shared_index]
|
|
428
|
+
f_attrs << %( si="#{si}") if si
|
|
437
429
|
f_attrs << %( ref="#{value[:ref]}") if value[:ref]
|
|
438
430
|
end
|
|
439
431
|
end
|
|
@@ -445,6 +437,8 @@ module Xlsxrb
|
|
|
445
437
|
else
|
|
446
438
|
0
|
|
447
439
|
end
|
|
440
|
+
raise ArgumentError, "Non-finite Float #{xml_val} is not supported in XLSX" if xml_val.is_a?(Float) && !xml_val.finite?
|
|
441
|
+
|
|
448
442
|
c_type = ""
|
|
449
443
|
case xml_val
|
|
450
444
|
when true
|
|
@@ -458,19 +452,31 @@ module Xlsxrb
|
|
|
458
452
|
xml_val = xml_val.code
|
|
459
453
|
end
|
|
460
454
|
xml_val = escape_xml(xml_val) if xml_val.is_a?(String)
|
|
461
|
-
|
|
455
|
+
f_tag = if formula_expr && !formula_expr.empty?
|
|
456
|
+
"<f#{f_attrs}>#{escape_xml(formula_expr)}</f>"
|
|
457
|
+
else
|
|
458
|
+
"<f#{f_attrs}/>"
|
|
459
|
+
end
|
|
460
|
+
buf << "<c r=\"#{col_ref}#{row_num_str}\"#{c_type}>#{f_tag}<v>#{xml_val}</v></c>"
|
|
462
461
|
end
|
|
463
462
|
when Xlsxrb::Elements::CellError
|
|
464
463
|
buf << "<c r=\"#{col_ref}#{row_num_str}\" t=\"e\"><v>#{value.code}</v></c>"
|
|
465
464
|
when BigDecimal
|
|
466
465
|
buf << "<c r=\"#{col_ref}#{row_num_str}\"><v>#{value.to_s("F")}</v></c>"
|
|
467
466
|
else
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
467
|
+
if sst_index
|
|
468
|
+
idx = (sst_index[value] ||= begin
|
|
469
|
+
sst << value
|
|
470
|
+
sst.size - 1
|
|
471
|
+
end)
|
|
472
|
+
idx_str = idx < 65_536 ? INTEGER_STRINGS[idx] : idx.to_s
|
|
473
|
+
buf << "<c r=\"#{col_ref}#{row_num_str}\" t=\"s\"><v>#{idx_str}</v></c>"
|
|
474
|
+
elsif value.is_a?(Xlsxrb::Elements::RichText)
|
|
475
|
+
buf << '<c r="' << col_ref << row_num_str << '" t="inlineStr"'
|
|
476
|
+
write_inline_rich_text(buf, value)
|
|
477
|
+
else
|
|
478
|
+
buf << "<c r=\"#{col_ref}#{row_num_str}\" t=\"inlineStr\"><is><t>#{escape_xml(value.to_s)}</t></is></c>"
|
|
479
|
+
end
|
|
474
480
|
end
|
|
475
481
|
end
|
|
476
482
|
|
|
@@ -537,6 +543,8 @@ module Xlsxrb
|
|
|
537
543
|
next
|
|
538
544
|
end
|
|
539
545
|
|
|
546
|
+
raise ArgumentError, "Non-finite Float #{value} is not supported in XLSX" if value.is_a?(Float) && !value.finite?
|
|
547
|
+
|
|
540
548
|
# Fast path: unstyled numbers, booleans, and simple strings (majority of cells)
|
|
541
549
|
if style_id.nil? && !value.is_a?(Xlsxrb::Elements::Formula) && !value.is_a?(Hash)
|
|
542
550
|
case value
|
|
@@ -670,12 +678,14 @@ module Xlsxrb
|
|
|
670
678
|
type = "e"
|
|
671
679
|
end
|
|
672
680
|
|
|
681
|
+
raise ArgumentError, "Non-finite Float #{xml_val} is not supported in XLSX" if xml_val.is_a?(Float) && !xml_val.finite?
|
|
682
|
+
|
|
673
683
|
formula_expr = formula_expr[1..] if formula_expr&.start_with?("=")
|
|
674
684
|
|
|
675
685
|
buf << '<c r="' << col_ref << row_num_str << '"'
|
|
676
686
|
buf << ' s="' << style_id.to_s << '"' if style_id
|
|
677
687
|
buf << ' t="' << type << '"' if type
|
|
678
|
-
if formula_expr
|
|
688
|
+
if formula_expr || (formula_type && formula_type.to_sym == :shared && !formula_si.nil?)
|
|
679
689
|
f_attrs = +""
|
|
680
690
|
if formula_type
|
|
681
691
|
case formula_type.to_sym
|
|
@@ -689,7 +699,11 @@ module Xlsxrb
|
|
|
689
699
|
end
|
|
690
700
|
end
|
|
691
701
|
f_attrs << ' ca="1"' if formula_ca
|
|
692
|
-
|
|
702
|
+
if formula_expr && !formula_expr.empty?
|
|
703
|
+
buf << "><f#{f_attrs}>" << escape_xml(formula_expr) << "</f>"
|
|
704
|
+
else
|
|
705
|
+
buf << "><f#{f_attrs}/>"
|
|
706
|
+
end
|
|
693
707
|
if xml_val.nil?
|
|
694
708
|
buf << "<v>0</v></c>"
|
|
695
709
|
else
|
|
@@ -698,7 +712,11 @@ module Xlsxrb
|
|
|
698
712
|
buf << "<v>" << v_str << "</v></c>"
|
|
699
713
|
end
|
|
700
714
|
elsif type == "inlineStr"
|
|
701
|
-
|
|
715
|
+
if xml_val.is_a?(Xlsxrb::Elements::RichText)
|
|
716
|
+
write_inline_rich_text(buf, xml_val)
|
|
717
|
+
else
|
|
718
|
+
buf << "><is><t>" << escape_xml(xml_val.to_s) << "</t></is></c>"
|
|
719
|
+
end
|
|
702
720
|
else
|
|
703
721
|
buf << "><v>" << xml_val.to_s << "</v></c>"
|
|
704
722
|
end
|
|
@@ -715,13 +733,13 @@ module Xlsxrb
|
|
|
715
733
|
|
|
716
734
|
# Write the worksheet footer. Call once after all rows.
|
|
717
735
|
# Options for post-sheetData elements (in OOXML order):
|
|
718
|
-
def finish(drawing_rid: nil, sheet_protection: nil, protected_ranges: nil, auto_filter: nil,
|
|
736
|
+
def finish(drawing_rid: nil, picture_rid: nil, sheet_protection: nil, protected_ranges: nil, auto_filter: nil,
|
|
719
737
|
filter_columns: nil, sort_state: nil, merge_cells: nil,
|
|
720
738
|
conditional_formats: nil, data_validations: nil,
|
|
721
739
|
hyperlinks: nil, print_options: nil, page_margins: nil,
|
|
722
740
|
page_setup: nil, header_footer: nil, row_breaks: nil,
|
|
723
|
-
col_breaks: nil, cell_watches: nil, tables: nil, table_start_rid: nil,
|
|
724
|
-
legacy_drawing_rid: nil, sparkline_groups: nil)
|
|
741
|
+
col_breaks: nil, cell_watches: nil, ignored_errors: nil, tables: nil, table_start_rid: nil,
|
|
742
|
+
legacy_drawing_rid: nil, sparkline_groups: nil, phonetic_pr: nil, sheet_calc_pr: nil)
|
|
725
743
|
return if @finished
|
|
726
744
|
|
|
727
745
|
start unless @started
|
|
@@ -733,10 +751,13 @@ module Xlsxrb
|
|
|
733
751
|
@builder.close_tag("sheetData")
|
|
734
752
|
|
|
735
753
|
# Elements must appear in OOXML specification order after sheetData
|
|
754
|
+
calc_pr = sheet_calc_pr || (@sheet_properties if @sheet_properties && !@sheet_properties[:full_calc_on_load].nil?)
|
|
755
|
+
write_sheet_calc_pr(calc_pr) if calc_pr
|
|
736
756
|
write_sheet_protection(sheet_protection) if sheet_protection
|
|
737
757
|
write_protected_ranges(protected_ranges) if protected_ranges && !protected_ranges.empty?
|
|
738
758
|
write_auto_filter(auto_filter, filter_columns, sort_state) if auto_filter
|
|
739
759
|
write_merge_cells(merge_cells) if merge_cells && !merge_cells.empty?
|
|
760
|
+
write_phonetic_pr(phonetic_pr) if phonetic_pr
|
|
740
761
|
write_conditional_formatting(conditional_formats) if conditional_formats && !conditional_formats.empty?
|
|
741
762
|
write_data_validations(data_validations) if data_validations && !data_validations.empty?
|
|
742
763
|
write_hyperlinks(hyperlinks) if hyperlinks && !hyperlinks.empty?
|
|
@@ -747,8 +768,10 @@ module Xlsxrb
|
|
|
747
768
|
write_row_breaks(row_breaks) if row_breaks && !row_breaks.empty?
|
|
748
769
|
write_col_breaks(col_breaks) if col_breaks && !col_breaks.empty?
|
|
749
770
|
write_cell_watches(cell_watches) if cell_watches && !cell_watches.empty?
|
|
771
|
+
write_ignored_errors(ignored_errors) if ignored_errors && !ignored_errors.empty?
|
|
750
772
|
@builder.empty_tag("drawing", { "r:id": drawing_rid }) if drawing_rid
|
|
751
773
|
@builder.empty_tag("legacyDrawing", { "r:id": legacy_drawing_rid }) if legacy_drawing_rid
|
|
774
|
+
@builder.empty_tag("picture", { "r:id": picture_rid }) if picture_rid
|
|
752
775
|
write_table_parts(tables, table_start_rid) if tables && !tables.empty?
|
|
753
776
|
write_sparklines(sparkline_groups) if sparkline_groups && !sparkline_groups.empty?
|
|
754
777
|
@builder.close_tag("worksheet")
|
|
@@ -764,6 +787,8 @@ module Xlsxrb
|
|
|
764
787
|
return mapped if mapped
|
|
765
788
|
end
|
|
766
789
|
|
|
790
|
+
return 0 if [:default, :none, "default", "none"].include?(style)
|
|
791
|
+
|
|
767
792
|
style.is_a?(Integer) ? style : nil
|
|
768
793
|
end
|
|
769
794
|
|
|
@@ -771,15 +796,29 @@ module Xlsxrb
|
|
|
771
796
|
|
|
772
797
|
def write_sheet_properties(props)
|
|
773
798
|
attrs = {}
|
|
774
|
-
|
|
799
|
+
attrs[:codeName] = props[:code_name].to_s if props[:code_name]
|
|
800
|
+
tc_attrs = build_tab_color_attrs(props)
|
|
801
|
+
|
|
802
|
+
outline_props = props[:outline_properties] || props[:outline_pr] || props
|
|
803
|
+
apply_styles = props.fetch(:apply_styles, outline_props[:apply_styles])
|
|
804
|
+
summary_below = props.fetch(:summary_below, props.fetch(:outline_below, outline_props[:summary_below]))
|
|
805
|
+
summary_right = props.fetch(:summary_right, props.fetch(:outline_right, outline_props[:summary_right]))
|
|
806
|
+
show_outline_symbols = props.fetch(:show_outline_symbols, props.fetch(:show_symbols, outline_props[:show_outline_symbols]))
|
|
807
|
+
has_outline = !apply_styles.nil? || !summary_below.nil? || !summary_right.nil? || !show_outline_symbols.nil?
|
|
808
|
+
|
|
809
|
+
has_children = tc_attrs || !props[:fit_to_page].nil? || has_outline
|
|
775
810
|
if has_children
|
|
776
811
|
@builder.open_tag("sheetPr", attrs)
|
|
777
|
-
@builder.empty_tag("tabColor",
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
812
|
+
@builder.empty_tag("tabColor", tc_attrs) if tc_attrs
|
|
813
|
+
|
|
814
|
+
if has_outline
|
|
815
|
+
outline_attrs = {}
|
|
816
|
+
outline_attrs[:applyStyles] = apply_styles ? "1" : "0" unless apply_styles.nil?
|
|
817
|
+
outline_attrs[:summaryBelow] = summary_below ? "1" : "0" unless summary_below.nil?
|
|
818
|
+
outline_attrs[:summaryRight] = summary_right ? "1" : "0" unless summary_right.nil?
|
|
819
|
+
outline_attrs[:showOutlineSymbols] = show_outline_symbols ? "1" : "0" unless show_outline_symbols.nil?
|
|
820
|
+
@builder.empty_tag("outlinePr", outline_attrs) unless outline_attrs.empty?
|
|
821
|
+
end
|
|
783
822
|
|
|
784
823
|
unless props[:fit_to_page].nil?
|
|
785
824
|
@builder.empty_tag("pageSetUpPr", { fitToPage: props[:fit_to_page] ? "1" : "0" })
|
|
@@ -791,6 +830,29 @@ module Xlsxrb
|
|
|
791
830
|
end
|
|
792
831
|
end
|
|
793
832
|
|
|
833
|
+
def build_tab_color_attrs(props)
|
|
834
|
+
tc = props[:tab_color]
|
|
835
|
+
if tc.is_a?(Hash)
|
|
836
|
+
attrs = {}
|
|
837
|
+
attrs[:auto] = "1" if tc[:auto]
|
|
838
|
+
attrs[:indexed] = tc[:indexed].to_s if tc[:indexed]
|
|
839
|
+
attrs[:rgb] = tc[:rgb].to_s.delete_prefix("#") if tc[:rgb]
|
|
840
|
+
attrs[:theme] = tc[:theme].to_s if tc[:theme]
|
|
841
|
+
attrs[:tint] = tc[:tint].to_s if tc[:tint]
|
|
842
|
+
attrs.empty? ? nil : attrs
|
|
843
|
+
elsif tc.is_a?(String) || tc.is_a?(Symbol)
|
|
844
|
+
{ rgb: tc.to_s.delete_prefix("#") }
|
|
845
|
+
elsif props[:tab_color_theme]
|
|
846
|
+
attrs = { theme: props[:tab_color_theme].to_s }
|
|
847
|
+
attrs[:tint] = props[:tab_color_tint].to_s if props[:tab_color_tint]
|
|
848
|
+
attrs
|
|
849
|
+
elsif props[:tab_color_indexed]
|
|
850
|
+
{ indexed: props[:tab_color_indexed].to_s }
|
|
851
|
+
elsif props[:tab_color_auto]
|
|
852
|
+
{ auto: "1" }
|
|
853
|
+
end
|
|
854
|
+
end
|
|
855
|
+
|
|
794
856
|
def write_sheet_views(freeze_pane: nil, split_pane: nil, selection: nil, sheet_view: nil)
|
|
795
857
|
@builder.open_tag("sheetViews")
|
|
796
858
|
sv_attrs = { workbookViewId: "0" }
|
|
@@ -1009,7 +1071,7 @@ module Xlsxrb
|
|
|
1009
1071
|
snake = t_str.gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
|
|
1010
1072
|
.gsub(/([a-z\d])([A-Z])/, '\1_\2')
|
|
1011
1073
|
.downcase.to_sym
|
|
1012
|
-
type = snake if %i[cell_is expression color_scale data_bar icon_set above_average top10 duplicate_values unique_values contains_text not_contains_text begins_with ends_with contains_blanks not_contains_blanks time_period].include?(snake)
|
|
1074
|
+
type = snake if %i[cell_is expression color_scale data_bar icon_set above_average top10 duplicate_values unique_values contains_text not_contains_text begins_with ends_with contains_blanks not_contains_blanks contains_errors not_contains_errors time_period].include?(snake)
|
|
1013
1075
|
end
|
|
1014
1076
|
|
|
1015
1077
|
cf_type = case type
|
|
@@ -1028,12 +1090,17 @@ module Xlsxrb
|
|
|
1028
1090
|
when :ends_with then "endsWith"
|
|
1029
1091
|
when :contains_blanks then "containsBlanks"
|
|
1030
1092
|
when :not_contains_blanks then "notContainsBlanks"
|
|
1093
|
+
when :contains_errors then "containsErrors"
|
|
1094
|
+
when :not_contains_errors then "notContainsErrors"
|
|
1031
1095
|
when :time_period then "timePeriod"
|
|
1032
1096
|
else type.to_s
|
|
1033
1097
|
end
|
|
1034
1098
|
|
|
1035
1099
|
r_attrs = { type: cf_type, priority: (rule[:priority] || (idx + 1)).to_s }
|
|
1036
|
-
|
|
1100
|
+
if rule[:operator]
|
|
1101
|
+
op = rule[:operator]
|
|
1102
|
+
r_attrs[:operator] = DslHelpers::CF_OPERATORS[op.to_sym] || op.to_s
|
|
1103
|
+
end
|
|
1037
1104
|
r_attrs[:dxfId] = rule[:format_id].to_s if rule[:format_id]
|
|
1038
1105
|
r_attrs[:stopIfTrue] = "1" if rule[:stop_if_true]
|
|
1039
1106
|
r_attrs[:aboveAverage] = "0" if rule[:above_average] == false
|
|
@@ -1042,23 +1109,22 @@ module Xlsxrb
|
|
|
1042
1109
|
r_attrs[:percent] = "1" if rule[:percent]
|
|
1043
1110
|
r_attrs[:bottom] = "1" if rule[:bottom]
|
|
1044
1111
|
r_attrs[:text] = rule[:text].to_s if rule[:text]
|
|
1045
|
-
|
|
1112
|
+
if rule[:time_period]
|
|
1113
|
+
tp = rule[:time_period]
|
|
1114
|
+
r_attrs[:timePeriod] = DslHelpers::CF_TIME_PERIODS[tp.to_sym] || tp.to_s
|
|
1115
|
+
end
|
|
1046
1116
|
r_attrs[:stdDev] = rule[:std_dev].to_s if rule[:std_dev]
|
|
1047
1117
|
|
|
1048
1118
|
case type
|
|
1049
1119
|
when :color_scale
|
|
1050
1120
|
@builder.open_tag("cfRule", r_attrs)
|
|
1051
1121
|
cs = rule[:color_scale] || {}
|
|
1052
|
-
cfvos = cs[:cfvo] || [
|
|
1053
|
-
{ type: "min" },
|
|
1054
|
-
{ type: "percent", val: "50" },
|
|
1055
|
-
{ type: "max" }
|
|
1056
|
-
]
|
|
1057
1122
|
colors = cs[:colors] || [
|
|
1058
1123
|
"FFF8696B", # Red
|
|
1059
1124
|
"FFFFEB84", # Yellow
|
|
1060
1125
|
"FF63BE7B" # Green
|
|
1061
1126
|
]
|
|
1127
|
+
cfvos = cs[:cfvo] || (colors.size == 2 ? [{ type: "min" }, { type: "max" }] : [{ type: "min" }, { type: "percent", val: "50" }, { type: "max" }])
|
|
1062
1128
|
@builder.tag("colorScale") do |b|
|
|
1063
1129
|
cfvos.each do |cfvo|
|
|
1064
1130
|
cfvo_attrs = { type: cfvo[:type] }
|
|
@@ -1115,11 +1181,28 @@ module Xlsxrb
|
|
|
1115
1181
|
@builder.open_tag("cfRule", r_attrs)
|
|
1116
1182
|
is = rule[:icon_set] || {}
|
|
1117
1183
|
icon_style = is[:icon_set] || rule[:icon_style] || "3Arrows"
|
|
1118
|
-
cfvos = is[:cfvo] ||
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1184
|
+
cfvos = is[:cfvo] || if icon_style.to_s.start_with?("4")
|
|
1185
|
+
[
|
|
1186
|
+
{ type: "percent", val: "0" },
|
|
1187
|
+
{ type: "percent", val: "25" },
|
|
1188
|
+
{ type: "percent", val: "50" },
|
|
1189
|
+
{ type: "percent", val: "75" }
|
|
1190
|
+
]
|
|
1191
|
+
elsif icon_style.to_s.start_with?("5")
|
|
1192
|
+
[
|
|
1193
|
+
{ type: "percent", val: "0" },
|
|
1194
|
+
{ type: "percent", val: "20" },
|
|
1195
|
+
{ type: "percent", val: "40" },
|
|
1196
|
+
{ type: "percent", val: "60" },
|
|
1197
|
+
{ type: "percent", val: "80" }
|
|
1198
|
+
]
|
|
1199
|
+
else
|
|
1200
|
+
[
|
|
1201
|
+
{ type: "percent", val: "0" },
|
|
1202
|
+
{ type: "percent", val: "33" },
|
|
1203
|
+
{ type: "percent", val: "67" }
|
|
1204
|
+
]
|
|
1205
|
+
end
|
|
1123
1206
|
is_attrs = { iconSet: icon_style }
|
|
1124
1207
|
is_attrs[:reverse] = is[:reverse] ? "1" : "0" unless is[:reverse].nil?
|
|
1125
1208
|
is_attrs[:percent] = is[:percent] ? "1" : "0" unless is[:percent].nil?
|
|
@@ -1167,13 +1250,25 @@ module Xlsxrb
|
|
|
1167
1250
|
dv_attrs[:type] = dv_type.to_s if dv_type
|
|
1168
1251
|
dv_attrs[:operator] = dv[:operator].to_s if dv[:operator]
|
|
1169
1252
|
dv_attrs[:allowBlank] = "1" if dv[:allow_blank]
|
|
1170
|
-
|
|
1171
|
-
|
|
1253
|
+
show_input = if dv.key?(:show_input_message)
|
|
1254
|
+
dv[:show_input_message]
|
|
1255
|
+
else
|
|
1256
|
+
!dv[:prompt].nil? || !dv[:prompt_title].nil?
|
|
1257
|
+
end
|
|
1258
|
+
dv_attrs[:showInputMessage] = "1" if show_input
|
|
1259
|
+
|
|
1260
|
+
show_error = if dv.key?(:show_error_message)
|
|
1261
|
+
dv[:show_error_message]
|
|
1262
|
+
else
|
|
1263
|
+
!dv[:error].nil? || !dv[:error_title].nil?
|
|
1264
|
+
end
|
|
1265
|
+
dv_attrs[:showErrorMessage] = "1" if show_error
|
|
1266
|
+
|
|
1172
1267
|
dv_attrs[:errorStyle] = dv[:error_style].to_s if dv[:error_style]
|
|
1173
|
-
dv_attrs[:errorTitle] = dv[:error_title] if dv[:error_title]
|
|
1174
|
-
dv_attrs[:error] = dv[:error] if dv[:error]
|
|
1175
|
-
dv_attrs[:promptTitle] = dv[:prompt_title] if dv[:prompt_title]
|
|
1176
|
-
dv_attrs[:prompt] = dv[:prompt] if dv[:prompt]
|
|
1268
|
+
dv_attrs[:errorTitle] = dv[:error_title].to_s.gsub(/\r\n|\r|\n/, "_x000a_") if dv[:error_title]
|
|
1269
|
+
dv_attrs[:error] = dv[:error].to_s.gsub(/\r\n|\r|\n/, "_x000a_") if dv[:error]
|
|
1270
|
+
dv_attrs[:promptTitle] = dv[:prompt_title].to_s.gsub(/\r\n|\r|\n/, "_x000a_") if dv[:prompt_title]
|
|
1271
|
+
dv_attrs[:prompt] = dv[:prompt].to_s.gsub(/\r\n|\r|\n/, "_x000a_") if dv[:prompt]
|
|
1177
1272
|
dv_attrs[:showDropDown] = "1" if dv[:show_drop_down]
|
|
1178
1273
|
dv_attrs[:imeMode] = dv[:ime_mode].to_s if dv[:ime_mode]
|
|
1179
1274
|
|
|
@@ -1286,6 +1381,41 @@ module Xlsxrb
|
|
|
1286
1381
|
@builder.close_tag("cellWatches")
|
|
1287
1382
|
end
|
|
1288
1383
|
|
|
1384
|
+
def write_ignored_errors(errors)
|
|
1385
|
+
@builder.open_tag("ignoredErrors")
|
|
1386
|
+
errors.each do |ie|
|
|
1387
|
+
attrs = { sqref: ie[:sqref].to_s }
|
|
1388
|
+
attrs[:evalError] = "1" if ie[:eval_error]
|
|
1389
|
+
attrs[:twoDigitTextYear] = "1" if ie[:two_digit_text_year]
|
|
1390
|
+
attrs[:numberStoredAsText] = "1" if ie[:number_stored_as_text]
|
|
1391
|
+
attrs[:formula] = "1" if ie[:formula]
|
|
1392
|
+
attrs[:formulaRange] = "1" if ie[:formula_range]
|
|
1393
|
+
attrs[:unlockedFormula] = "1" if ie[:unlocked_formula]
|
|
1394
|
+
attrs[:emptyCellReference] = "1" if ie[:empty_cell_reference]
|
|
1395
|
+
attrs[:listDataValidation] = "1" if ie[:list_data_validation]
|
|
1396
|
+
attrs[:calculatedColumn] = "1" if ie[:calculated_column]
|
|
1397
|
+
@builder.empty_tag("ignoredError", attrs)
|
|
1398
|
+
end
|
|
1399
|
+
@builder.close_tag("ignoredErrors")
|
|
1400
|
+
end
|
|
1401
|
+
|
|
1402
|
+
def write_phonetic_pr(props)
|
|
1403
|
+
attrs = { fontId: (props[:font_id] || props["font_id"] || 0).to_s }
|
|
1404
|
+
type = props[:type] || props["type"]
|
|
1405
|
+
attrs[:type] = type.to_s if type
|
|
1406
|
+
align = props[:alignment] || props["alignment"]
|
|
1407
|
+
attrs[:alignment] = align.to_s if align
|
|
1408
|
+
@builder.empty_tag("phoneticPr", attrs)
|
|
1409
|
+
end
|
|
1410
|
+
|
|
1411
|
+
def write_sheet_calc_pr(props)
|
|
1412
|
+
return unless props
|
|
1413
|
+
|
|
1414
|
+
attrs = {}
|
|
1415
|
+
attrs[:fullCalcOnLoad] = props[:full_calc_on_load] ? "1" : "0" unless props[:full_calc_on_load].nil?
|
|
1416
|
+
@builder.empty_tag("sheetCalcPr", attrs) unless attrs.empty?
|
|
1417
|
+
end
|
|
1418
|
+
|
|
1289
1419
|
def write_table_parts(tables, start_rid)
|
|
1290
1420
|
@builder.open_tag("tableParts", { count: tables.size.to_s })
|
|
1291
1421
|
tables.each_with_index do |_tbl, idx|
|
|
@@ -1367,24 +1497,27 @@ module Xlsxrb
|
|
|
1367
1497
|
# --- Columns ---
|
|
1368
1498
|
|
|
1369
1499
|
def write_columns(columns)
|
|
1500
|
+
default_width = @sheet_format ? (@sheet_format[:default_col_width] || @sheet_format["default_col_width"]) : nil
|
|
1370
1501
|
@builder.open_tag("cols")
|
|
1371
1502
|
columns.each do |col|
|
|
1372
1503
|
if col.is_a?(Elements::Column)
|
|
1373
1504
|
min = col.unmapped_data[:min] || (col.index + 1)
|
|
1374
1505
|
max = col.unmapped_data[:max] || (col.index + 1)
|
|
1375
|
-
|
|
1506
|
+
style_id = col.style_index
|
|
1507
|
+
width = col.width || (style_id ? default_width : nil)
|
|
1376
1508
|
hidden = col.hidden
|
|
1377
|
-
custom_width = col.custom_width ||
|
|
1509
|
+
custom_width = col.custom_width || !width.nil?
|
|
1378
1510
|
outline_level = col.outline_level
|
|
1379
|
-
|
|
1511
|
+
collapsed = col.unmapped_data[:collapsed]
|
|
1380
1512
|
else
|
|
1381
1513
|
min = col[:min] || ((col[:index] || 0) + 1)
|
|
1382
1514
|
max = col[:max] || ((col[:index] || 0) + 1)
|
|
1383
|
-
|
|
1515
|
+
style_id = col[:style_index] || col[:style]
|
|
1516
|
+
width = col[:width] || (style_id ? default_width : nil)
|
|
1384
1517
|
hidden = col[:hidden]
|
|
1385
|
-
custom_width = col[:custom_width] ||
|
|
1518
|
+
custom_width = col[:custom_width] || !width.nil?
|
|
1386
1519
|
outline_level = col[:outline_level]
|
|
1387
|
-
|
|
1520
|
+
collapsed = col[:collapsed]
|
|
1388
1521
|
end
|
|
1389
1522
|
attrs = {
|
|
1390
1523
|
min: min.to_s,
|
|
@@ -1392,8 +1525,9 @@ module Xlsxrb
|
|
|
1392
1525
|
}
|
|
1393
1526
|
attrs[:width] = width.to_s if width
|
|
1394
1527
|
attrs[:hidden] = "1" if hidden
|
|
1395
|
-
attrs[:customWidth] = "1" if custom_width
|
|
1528
|
+
attrs[:customWidth] = "1" if custom_width && width
|
|
1396
1529
|
attrs[:outlineLevel] = outline_level.to_s if outline_level
|
|
1530
|
+
attrs[:collapsed] = "1" if collapsed
|
|
1397
1531
|
attrs[:style] = style_id.to_s if style_id.is_a?(Integer)
|
|
1398
1532
|
@builder.empty_tag("col", attrs)
|
|
1399
1533
|
end
|
|
@@ -1491,6 +1625,73 @@ module Xlsxrb
|
|
|
1491
1625
|
Xlsxrb::Elements::Cell.column_letter(index)
|
|
1492
1626
|
end
|
|
1493
1627
|
|
|
1628
|
+
#: (String buf, Elements::RichText rich_text) -> void
|
|
1629
|
+
def write_inline_rich_text(buf, rich_text)
|
|
1630
|
+
buf << "><is>"
|
|
1631
|
+
rich_text.runs.each do |run|
|
|
1632
|
+
font = run[:font]
|
|
1633
|
+
if font && !font.empty?
|
|
1634
|
+
buf << "<r><rPr>"
|
|
1635
|
+
buf << "<b/>" if font[:bold]
|
|
1636
|
+
buf << "<i/>" if font[:italic]
|
|
1637
|
+
buf << "<strike/>" if font[:strike]
|
|
1638
|
+
if font[:underline]
|
|
1639
|
+
u_val = font[:underline]
|
|
1640
|
+
if u_val == true || u_val.to_s == "single"
|
|
1641
|
+
buf << "<u/>"
|
|
1642
|
+
else
|
|
1643
|
+
mapped_u = StyleBuilder::UNDERLINE_MAP[u_val.to_sym] || u_val
|
|
1644
|
+
buf << '<u val="' << mapped_u.to_s << '"/>'
|
|
1645
|
+
end
|
|
1646
|
+
end
|
|
1647
|
+
va_val = font[:vert_align] || font[:vertical_align]
|
|
1648
|
+
va_val ||= "superscript" if font[:superscript]
|
|
1649
|
+
va_val ||= "subscript" if font[:subscript]
|
|
1650
|
+
if va_val
|
|
1651
|
+
mapped_va = StyleBuilder::VERT_ALIGN_MAP[va_val.to_sym] || va_val
|
|
1652
|
+
buf << '<vertAlign val="' << mapped_va.to_s << '"/>'
|
|
1653
|
+
end
|
|
1654
|
+
font_sz = font[:sz] || font[:size]
|
|
1655
|
+
buf << '<sz val="' << font_sz.to_s << '"/>' if font_sz
|
|
1656
|
+
if font[:color]
|
|
1657
|
+
if font[:color].is_a?(Hash)
|
|
1658
|
+
c_hash = font[:color]
|
|
1659
|
+
if c_hash[:rgb]
|
|
1660
|
+
norm = StyleBuilder.normalize_color(c_hash[:rgb], alpha: true) || c_hash[:rgb].to_s
|
|
1661
|
+
buf << '<color rgb="' << norm << '"/>'
|
|
1662
|
+
elsif c_hash[:theme]
|
|
1663
|
+
tint_attr = c_hash[:tint] ? %( tint="#{c_hash[:tint]}") : ""
|
|
1664
|
+
buf << '<color theme="' << c_hash[:theme].to_s << '"' << tint_attr << "/>"
|
|
1665
|
+
elsif c_hash[:indexed]
|
|
1666
|
+
buf << '<color indexed="' << c_hash[:indexed].to_s << '"/>'
|
|
1667
|
+
elsif c_hash[:auto]
|
|
1668
|
+
buf << '<color auto="1"/>'
|
|
1669
|
+
end
|
|
1670
|
+
else
|
|
1671
|
+
raw_color = font[:color]
|
|
1672
|
+
norm_color = StyleBuilder.normalize_color(raw_color, alpha: true) || raw_color.to_s
|
|
1673
|
+
buf << '<color rgb="' << norm_color << '"/>'
|
|
1674
|
+
end
|
|
1675
|
+
elsif font[:theme]
|
|
1676
|
+
tint_attr = font[:tint] ? %( tint="#{font[:tint]}") : ""
|
|
1677
|
+
buf << '<color theme="' << font[:theme].to_s << '"' << tint_attr << "/>"
|
|
1678
|
+
elsif font[:indexed]
|
|
1679
|
+
buf << '<color indexed="' << font[:indexed].to_s << '"/>'
|
|
1680
|
+
elsif font[:auto]
|
|
1681
|
+
buf << '<color auto="1"/>'
|
|
1682
|
+
end
|
|
1683
|
+
buf << '<rFont val="' << escape_xml(font[:name]) << '"/>' if font[:name]
|
|
1684
|
+
buf << '<family val="' << font[:family].to_s << '"/>' if font[:family]
|
|
1685
|
+
buf << '<scheme val="' << font[:scheme].to_s << '"/>' if font[:scheme]
|
|
1686
|
+
buf << "</rPr><t>"
|
|
1687
|
+
else
|
|
1688
|
+
buf << "<r><t>"
|
|
1689
|
+
end
|
|
1690
|
+
buf << escape_xml(run[:text].to_s) << "</t></r>"
|
|
1691
|
+
end
|
|
1692
|
+
buf << "</is></c>"
|
|
1693
|
+
end
|
|
1694
|
+
|
|
1494
1695
|
INVALID_XML_CHARS_RE = /[^\x09\x0A\x0D\x20-\uD7FF\uE000-\uFFFD\u{10000}-\u{10FFFF}]/
|
|
1495
1696
|
ESCAPE_RE = /[&<>"']/
|
|
1496
1697
|
ESCAPE_MAP = { "&" => "&", "<" => "<", ">" => ">", '"' => """, "'" => "'" }.freeze
|