xlsxrb 0.1.14 → 0.1.16
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 +108 -0
- data/README.md +37 -17
- data/benchmark/micro_ips.rb +70 -0
- data/lib/xlsxrb/elements/styles.rb +427 -15
- data/lib/xlsxrb/elements/workbook.rb +67 -4
- data/lib/xlsxrb/elements/worksheet.rb +117 -7
- data/lib/xlsxrb/ooxml/cfb.rb +10 -0
- data/lib/xlsxrb/ooxml/reader/listeners/core_listeners.rb +56 -38
- data/lib/xlsxrb/ooxml/reader/listeners/feature_listeners.rb +14 -2
- data/lib/xlsxrb/ooxml/reader/listeners/style_listeners.rb +12 -7
- data/lib/xlsxrb/ooxml/reader.rb +167 -139
- data/lib/xlsxrb/ooxml/shared_strings_parser.rb +83 -28
- data/lib/xlsxrb/ooxml/styles_parser.rb +57 -14
- data/lib/xlsxrb/ooxml/utils.rb +59 -2
- data/lib/xlsxrb/ooxml/workbook_parser.rb +63 -4
- data/lib/xlsxrb/ooxml/workbook_writer.rb +232 -59
- data/lib/xlsxrb/ooxml/worksheet_parser.rb +192 -123
- data/lib/xlsxrb/ooxml/worksheet_writer.rb +307 -125
- data/lib/xlsxrb/ooxml/writer/drawing_xml.rb +16 -12
- data/lib/xlsxrb/ooxml/writer/features_xml.rb +1 -1
- data/lib/xlsxrb/ooxml/writer/styles_xml.rb +64 -15
- data/lib/xlsxrb/ooxml/writer.rb +31 -28
- data/lib/xlsxrb/ooxml/xml_builder.rb +22 -2
- data/lib/xlsxrb/ooxml/xml_parser.rb +45 -0
- data/lib/xlsxrb/ooxml/zip_reader.rb +83 -32
- data/lib/xlsxrb/stream_row.rb +4 -3
- data/lib/xlsxrb/stream_sheet.rb +259 -7
- data/lib/xlsxrb/stream_writer.rb +197 -31
- data/lib/xlsxrb/utils.rb +68 -0
- data/lib/xlsxrb/version.rb +1 -1
- data/lib/xlsxrb/workbook_builder.rb +110 -19
- data/lib/xlsxrb/worksheet_builder.rb +526 -15
- data/lib/xlsxrb.rb +889 -90
- data/sig/generated/xlsxrb/elements/styles.rbs +271 -5
- data/sig/generated/xlsxrb/elements/workbook.rbs +54 -3
- data/sig/generated/xlsxrb/elements/worksheet.rbs +63 -2
- data/sig/generated/xlsxrb/ooxml/reader/listeners/style_listeners.rbs +2 -0
- data/sig/generated/xlsxrb/ooxml/reader.rbs +12 -0
- data/sig/generated/xlsxrb/ooxml/shared_strings_parser.rbs +3 -1
- data/sig/generated/xlsxrb/ooxml/styles_parser.rbs +2 -0
- data/sig/generated/xlsxrb/ooxml/utils.rbs +22 -0
- data/sig/generated/xlsxrb/ooxml/workbook_parser.rbs +7 -0
- data/sig/generated/xlsxrb/ooxml/workbook_writer.rbs +13 -1
- data/sig/generated/xlsxrb/ooxml/worksheet_parser.rbs +18 -2
- data/sig/generated/xlsxrb/ooxml/worksheet_writer.rbs +13 -3
- data/sig/generated/xlsxrb/ooxml/writer/styles_xml.rbs +5 -2
- data/sig/generated/xlsxrb/ooxml/xml_builder.rbs +6 -1
- data/sig/generated/xlsxrb/ooxml/zip_reader.rbs +9 -6
- data/sig/generated/xlsxrb/stream_row.rbs +3 -2
- data/sig/generated/xlsxrb/stream_sheet.rbs +132 -6
- data/sig/generated/xlsxrb/stream_writer.rbs +118 -27
- data/sig/generated/xlsxrb/utils.rbs +45 -0
- data/sig/generated/xlsxrb/worksheet_builder.rbs +252 -9
- data/sig/generated/xlsxrb.rbs +84 -10
- metadata +2 -1
|
@@ -93,6 +93,9 @@ module Xlsxrb
|
|
|
93
93
|
selection: sheet[:selection],
|
|
94
94
|
sheet_view: sheet[:sheet_view],
|
|
95
95
|
sheet_properties: sheet[:sheet_properties],
|
|
96
|
+
sheet_format: sheet[:sheet_format],
|
|
97
|
+
protected_ranges: sheet[:protected_ranges],
|
|
98
|
+
cell_watches: sheet[:cell_watches],
|
|
96
99
|
tables: sheet_tables,
|
|
97
100
|
table_start_rid: info[:table_start_rid],
|
|
98
101
|
legacy_drawing_rid: info[:vml_rid],
|
|
@@ -493,6 +496,14 @@ module Xlsxrb
|
|
|
493
496
|
b.empty_tag("workbookProtection", wp_attrs) unless wp_attrs.empty?
|
|
494
497
|
end
|
|
495
498
|
|
|
499
|
+
# Workbook view: which sheet tab is selected when the file is opened
|
|
500
|
+
active_tab = @workbook_properties&.[](:active_tab)
|
|
501
|
+
if active_tab
|
|
502
|
+
b.open_tag("bookViews")
|
|
503
|
+
b.empty_tag("workbookView", { activeTab: active_tab.to_s })
|
|
504
|
+
b.close_tag("bookViews")
|
|
505
|
+
end
|
|
506
|
+
|
|
496
507
|
b.open_tag("sheets")
|
|
497
508
|
@sheets.each_with_index do |sheet, idx|
|
|
498
509
|
s_name = sheet.respond_to?(:name) ? sheet.name : sheet[:name]
|
|
@@ -528,6 +539,9 @@ module Xlsxrb
|
|
|
528
539
|
b.close_tag("definedNames")
|
|
529
540
|
end
|
|
530
541
|
|
|
542
|
+
# Calculation properties: ask Excel to recalculate every formula when the file is opened
|
|
543
|
+
b.empty_tag("calcPr", { fullCalcOnLoad: "1" }) if @workbook_properties&.[](:full_calc_on_load)
|
|
544
|
+
|
|
531
545
|
# Pivot caches
|
|
532
546
|
total_pivots = @sheets.sum { |s| (s[:pivot_tables] || []).size }
|
|
533
547
|
if total_pivots.positive?
|
|
@@ -594,7 +608,11 @@ module Xlsxrb
|
|
|
594
608
|
b.open_tag("styleSheet", { xmlns: SSML_NS })
|
|
595
609
|
|
|
596
610
|
# Number formats
|
|
597
|
-
raw_num_fmts = @styles
|
|
611
|
+
raw_num_fmts = if @styles.respond_to?(:num_fmts)
|
|
612
|
+
@styles.num_fmts
|
|
613
|
+
else
|
|
614
|
+
@styles&.dig(:num_fmts) || []
|
|
615
|
+
end
|
|
598
616
|
num_fmts = if raw_num_fmts.is_a?(Hash)
|
|
599
617
|
raw_num_fmts.map { |k, v| { num_fmt_id: k.to_i, format_code: v.to_s } }
|
|
600
618
|
elsif raw_num_fmts.is_a?(Array)
|
|
@@ -619,7 +637,11 @@ module Xlsxrb
|
|
|
619
637
|
end
|
|
620
638
|
|
|
621
639
|
# Fonts
|
|
622
|
-
fonts = @styles
|
|
640
|
+
fonts = if @styles.respond_to?(:fonts)
|
|
641
|
+
@styles.fonts
|
|
642
|
+
else
|
|
643
|
+
@styles&.dig(:fonts) || []
|
|
644
|
+
end
|
|
623
645
|
fonts = [{}] if fonts.empty? # At least one default font
|
|
624
646
|
b.tag("fonts", { count: fonts.size.to_s }) do |_|
|
|
625
647
|
fonts.each do |font_props|
|
|
@@ -631,14 +653,19 @@ module Xlsxrb
|
|
|
631
653
|
b.empty_tag("u", font_props[:underline] == "single" ? {} : { val: font_props[:underline] })
|
|
632
654
|
end
|
|
633
655
|
b.empty_tag("sz", { val: (font_props[:sz] || 11).to_s }) if font_props[:sz] || fonts == [{}]
|
|
634
|
-
|
|
656
|
+
color_attrs = color_tag_attrs(font_props[:color] || (font_props.key?(:rgb) || font_props.key?(:theme) || font_props.key?(:indexed) ? font_props : nil))
|
|
657
|
+
b.empty_tag("color", color_attrs) if color_attrs
|
|
635
658
|
b.empty_tag("name", { val: font_props[:name] || "Calibri" })
|
|
636
659
|
end
|
|
637
660
|
end
|
|
638
661
|
end
|
|
639
662
|
|
|
640
663
|
# Fills
|
|
641
|
-
fills = @styles
|
|
664
|
+
fills = if @styles.respond_to?(:fills)
|
|
665
|
+
@styles.fills
|
|
666
|
+
else
|
|
667
|
+
@styles&.dig(:fills) || []
|
|
668
|
+
end
|
|
642
669
|
fills = [{ pattern: "none" }, { pattern: "gray125" }] if fills.empty?
|
|
643
670
|
b.tag("fills", { count: fills.size.to_s }) do |_|
|
|
644
671
|
fills.each do |fill_props|
|
|
@@ -647,14 +674,18 @@ module Xlsxrb
|
|
|
647
674
|
b.open_tag("gradientFill", { type: fill_props[:gradient][:type], degree: fill_props[:gradient][:degree]&.to_s }.compact)
|
|
648
675
|
fill_props[:gradient][:stops].each do |stop|
|
|
649
676
|
b.tag("stop", { position: stop[:position].to_s }) do |_|
|
|
650
|
-
|
|
677
|
+
c_attrs = color_tag_attrs(stop[:color] || stop)
|
|
678
|
+
b.empty_tag("color", c_attrs) if c_attrs
|
|
651
679
|
end
|
|
652
680
|
end
|
|
653
681
|
b.close_tag("gradientFill")
|
|
654
682
|
else
|
|
655
683
|
b.tag("patternFill", { patternType: fill_props[:pattern] || "none" }) do |_|
|
|
656
|
-
|
|
657
|
-
b.empty_tag("
|
|
684
|
+
fg_attrs = color_tag_attrs(fill_props[:fg_color] || (fill_props[:fg_color_theme] ? { theme: fill_props[:fg_color_theme], tint: fill_props[:fg_color_tint] } : nil) || (fill_props[:fg_color_indexed] ? { indexed: fill_props[:fg_color_indexed] } : nil))
|
|
685
|
+
b.empty_tag("fgColor", fg_attrs) if fg_attrs
|
|
686
|
+
|
|
687
|
+
bg_attrs = color_tag_attrs(fill_props[:bg_color] || (fill_props[:bg_color_theme] ? { theme: fill_props[:bg_color_theme], tint: fill_props[:bg_color_tint] } : nil) || (fill_props[:bg_color_indexed] ? { indexed: fill_props[:bg_color_indexed] } : nil))
|
|
688
|
+
b.empty_tag("bgColor", bg_attrs) if bg_attrs
|
|
658
689
|
end
|
|
659
690
|
end
|
|
660
691
|
end
|
|
@@ -662,7 +693,11 @@ module Xlsxrb
|
|
|
662
693
|
end
|
|
663
694
|
|
|
664
695
|
# Borders
|
|
665
|
-
borders = @styles
|
|
696
|
+
borders = if @styles.respond_to?(:borders)
|
|
697
|
+
@styles.borders
|
|
698
|
+
else
|
|
699
|
+
@styles&.dig(:borders) || []
|
|
700
|
+
end
|
|
666
701
|
borders = [{}] if borders.empty?
|
|
667
702
|
b.tag("borders", { count: borders.size.to_s }) do |_|
|
|
668
703
|
borders.each do |border_props|
|
|
@@ -674,16 +709,8 @@ module Xlsxrb
|
|
|
674
709
|
side_data = border_props[side]
|
|
675
710
|
if side_data
|
|
676
711
|
b.tag(side.to_s, { style: side_data[:style] }) do |_|
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
c_attrs = {}
|
|
680
|
-
c_attrs[:rgb] = c[:rgb] if c[:rgb]
|
|
681
|
-
c_attrs[:theme] = c[:theme].to_s if c[:theme]
|
|
682
|
-
c_attrs[:tint] = c[:tint].to_s if c[:tint]
|
|
683
|
-
b.empty_tag("color", c_attrs) unless c_attrs.empty?
|
|
684
|
-
elsif c
|
|
685
|
-
b.empty_tag("color", { rgb: c })
|
|
686
|
-
end
|
|
712
|
+
c_attrs = color_tag_attrs(side_data[:color])
|
|
713
|
+
b.empty_tag("color", c_attrs) if c_attrs
|
|
687
714
|
end
|
|
688
715
|
else
|
|
689
716
|
b.empty_tag(side.to_s)
|
|
@@ -699,7 +726,11 @@ module Xlsxrb
|
|
|
699
726
|
end
|
|
700
727
|
|
|
701
728
|
# Cell XFs (formatting)
|
|
702
|
-
xf_entries = @styles
|
|
729
|
+
xf_entries = if @styles.respond_to?(:cell_xfs)
|
|
730
|
+
@styles.cell_xfs
|
|
731
|
+
else
|
|
732
|
+
@styles&.dig(:cell_xfs) || @styles&.dig(:xf_entries) || []
|
|
733
|
+
end
|
|
703
734
|
xf_count = [xf_entries.size, 1].max
|
|
704
735
|
b.tag("cellXfs", { count: xf_count.to_s }) do |_|
|
|
705
736
|
xf_entries.each do |xf|
|
|
@@ -748,7 +779,11 @@ module Xlsxrb
|
|
|
748
779
|
end
|
|
749
780
|
|
|
750
781
|
# Differential formats (for conditional formatting)
|
|
751
|
-
dxfs = @styles
|
|
782
|
+
dxfs = if @styles.respond_to?(:dxfs)
|
|
783
|
+
@styles.dxfs
|
|
784
|
+
else
|
|
785
|
+
@styles&.dig(:dxfs) || []
|
|
786
|
+
end
|
|
752
787
|
unless dxfs.empty?
|
|
753
788
|
b.tag("dxfs", { count: dxfs.size.to_s }) do |_|
|
|
754
789
|
dxfs.each do |dxf|
|
|
@@ -759,6 +794,9 @@ module Xlsxrb
|
|
|
759
794
|
b.empty_tag("numFmt", { numFmtId: nf[:num_fmt_id].to_s, formatCode: nf[:format_code].to_s })
|
|
760
795
|
end
|
|
761
796
|
write_dxf_fill(b, dxf[:fill]) if dxf[:fill]
|
|
797
|
+
write_dxf_alignment(b, dxf[:alignment]) if dxf[:alignment]
|
|
798
|
+
write_dxf_border(b, dxf[:border]) if dxf[:border]
|
|
799
|
+
write_dxf_protection(b, dxf[:protection]) if dxf[:protection]
|
|
762
800
|
end
|
|
763
801
|
end
|
|
764
802
|
end
|
|
@@ -769,8 +807,6 @@ module Xlsxrb
|
|
|
769
807
|
end
|
|
770
808
|
|
|
771
809
|
def preprocess_conditional_formats!
|
|
772
|
-
@styles[:dxfs] ||= []
|
|
773
|
-
|
|
774
810
|
@sheets.each do |sheet|
|
|
775
811
|
rules = sheet[:conditional_formats]
|
|
776
812
|
next if rules.nil? || rules.empty?
|
|
@@ -782,11 +818,15 @@ module Xlsxrb
|
|
|
782
818
|
dxf = normalized.delete(:dxf) || extract_dxf_from_rule!(normalized)
|
|
783
819
|
next normalized unless dxf
|
|
784
820
|
|
|
785
|
-
dxf_id = @styles
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
821
|
+
dxf_id = if @styles.respond_to?(:add_dxf)
|
|
822
|
+
@styles.dxfs.index(dxf) || @styles.add_dxf(dxf)
|
|
823
|
+
else
|
|
824
|
+
@styles[:dxfs] ||= []
|
|
825
|
+
@styles[:dxfs].index(dxf) || begin
|
|
826
|
+
@styles[:dxfs] << dxf
|
|
827
|
+
@styles[:dxfs].size - 1
|
|
828
|
+
end
|
|
829
|
+
end
|
|
790
830
|
normalized[:format_id] = dxf_id
|
|
791
831
|
normalized
|
|
792
832
|
end
|
|
@@ -814,16 +854,24 @@ module Xlsxrb
|
|
|
814
854
|
|
|
815
855
|
def write_dxf_font(builder, font)
|
|
816
856
|
builder.tag("font") do |_|
|
|
817
|
-
|
|
818
|
-
|
|
857
|
+
if font.key?(:bold)
|
|
858
|
+
font[:bold] ? builder.empty_tag("b") : builder.empty_tag("b", { val: "0" })
|
|
859
|
+
end
|
|
860
|
+
if font.key?(:italic)
|
|
861
|
+
font[:italic] ? builder.empty_tag("i") : builder.empty_tag("i", { val: "0" })
|
|
862
|
+
end
|
|
863
|
+
if font.key?(:strike)
|
|
864
|
+
font[:strike] ? builder.empty_tag("strike") : builder.empty_tag("strike", { val: "0" })
|
|
865
|
+
end
|
|
819
866
|
if font[:underline]
|
|
820
|
-
if font[:underline]
|
|
867
|
+
if [true, "single"].include?(font[:underline])
|
|
821
868
|
builder.empty_tag("u")
|
|
822
869
|
else
|
|
823
870
|
builder.empty_tag("u", { val: font[:underline] })
|
|
824
871
|
end
|
|
825
872
|
end
|
|
826
|
-
|
|
873
|
+
color_attrs = color_tag_attrs(font[:color] || font)
|
|
874
|
+
builder.empty_tag("color", color_attrs) if color_attrs
|
|
827
875
|
builder.empty_tag("name", { val: font[:name] }) if font[:name]
|
|
828
876
|
builder.empty_tag("sz", { val: font[:sz].to_s }) if font[:sz]
|
|
829
877
|
end
|
|
@@ -831,11 +879,120 @@ module Xlsxrb
|
|
|
831
879
|
|
|
832
880
|
def write_dxf_fill(builder, fill)
|
|
833
881
|
builder.tag("fill") do |_|
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
882
|
+
pattern_attrs = {}
|
|
883
|
+
pattern_attrs[:patternType] = fill[:pattern] if fill[:pattern]
|
|
884
|
+
builder.tag("patternFill", pattern_attrs) do |_|
|
|
885
|
+
fg_attrs = color_tag_attrs(fill[:fg_color])
|
|
886
|
+
builder.empty_tag("fgColor", fg_attrs) if fg_attrs
|
|
887
|
+
bg_attrs = color_tag_attrs(fill[:bg_color])
|
|
888
|
+
builder.empty_tag("bgColor", bg_attrs) if bg_attrs
|
|
889
|
+
end
|
|
890
|
+
end
|
|
891
|
+
end
|
|
892
|
+
|
|
893
|
+
def write_dxf_alignment(builder, alignment)
|
|
894
|
+
return if alignment.nil? || alignment.empty?
|
|
895
|
+
|
|
896
|
+
align_attrs = {}
|
|
897
|
+
align_attrs[:horizontal] = alignment[:horizontal] if alignment[:horizontal]
|
|
898
|
+
align_attrs[:vertical] = alignment[:vertical] if alignment[:vertical]
|
|
899
|
+
align_attrs[:wrapText] = "1" if alignment[:wrap_text]
|
|
900
|
+
align_attrs[:textRotation] = alignment[:text_rotation].to_s if alignment[:text_rotation]
|
|
901
|
+
align_attrs[:indent] = alignment[:indent].to_s if alignment[:indent]
|
|
902
|
+
align_attrs[:relativeIndent] = alignment[:relative_indent].to_s if alignment[:relative_indent]
|
|
903
|
+
align_attrs[:shrinkToFit] = "1" if alignment[:shrink_to_fit]
|
|
904
|
+
align_attrs[:readingOrder] = alignment[:reading_order].to_s if alignment[:reading_order]
|
|
905
|
+
align_attrs[:justifyLastLine] = "1" if alignment[:justify_last_line]
|
|
906
|
+
builder.empty_tag("alignment", align_attrs) unless align_attrs.empty?
|
|
907
|
+
end
|
|
908
|
+
|
|
909
|
+
def write_dxf_border(builder, border)
|
|
910
|
+
return if border.nil? || border.empty?
|
|
911
|
+
|
|
912
|
+
border_attrs = {}
|
|
913
|
+
border_attrs[:diagonalUp] = "1" if border[:diagonal_up]
|
|
914
|
+
border_attrs[:diagonalDown] = "1" if border[:diagonal_down]
|
|
915
|
+
|
|
916
|
+
builder.tag("border", border_attrs) do |_|
|
|
917
|
+
%i[left right top bottom diagonal].each do |side|
|
|
918
|
+
side_data = border[side]
|
|
919
|
+
next unless side_data
|
|
920
|
+
|
|
921
|
+
tag_attrs = {}
|
|
922
|
+
tag_attrs[:style] = side_data[:style] if side_data[:style]
|
|
923
|
+
c_attrs = color_tag_attrs(side_data[:color])
|
|
924
|
+
if c_attrs
|
|
925
|
+
builder.tag(side.to_s, tag_attrs) do |_|
|
|
926
|
+
builder.empty_tag("color", c_attrs)
|
|
927
|
+
end
|
|
928
|
+
elsif !tag_attrs.empty?
|
|
929
|
+
builder.empty_tag(side.to_s, tag_attrs)
|
|
930
|
+
else
|
|
931
|
+
builder.empty_tag(side.to_s)
|
|
932
|
+
end
|
|
933
|
+
end
|
|
934
|
+
end
|
|
935
|
+
end
|
|
936
|
+
|
|
937
|
+
def write_dxf_protection(builder, protection)
|
|
938
|
+
return if protection.nil? || protection.empty?
|
|
939
|
+
|
|
940
|
+
prot_attrs = {}
|
|
941
|
+
prot_attrs[:locked] = protection[:locked] ? "1" : "0" unless protection[:locked].nil?
|
|
942
|
+
prot_attrs[:hidden] = protection[:hidden] ? "1" : "0" unless protection[:hidden].nil?
|
|
943
|
+
builder.empty_tag("protection", prot_attrs) unless prot_attrs.empty?
|
|
944
|
+
end
|
|
945
|
+
|
|
946
|
+
# : (untyped rgb) -> ::String?
|
|
947
|
+
def format_rgb_hex(rgb)
|
|
948
|
+
return nil unless rgb
|
|
949
|
+
|
|
950
|
+
str = rgb.to_s.sub(/^#/, "").strip
|
|
951
|
+
return nil if str.empty?
|
|
952
|
+
|
|
953
|
+
case str
|
|
954
|
+
when /\A[0-9a-fA-F]{8}\z/
|
|
955
|
+
str.upcase
|
|
956
|
+
when /\A[0-9a-fA-F]{6}\z/
|
|
957
|
+
"FF#{str.upcase}"
|
|
958
|
+
when /\A[0-9a-fA-F]{3}\z/
|
|
959
|
+
r, g, b = str.chars
|
|
960
|
+
"FF#{r}#{r}#{g}#{g}#{b}#{b}".upcase
|
|
961
|
+
else
|
|
962
|
+
resolved = StyleBuilder.resolve_color(rgb)
|
|
963
|
+
case resolved
|
|
964
|
+
when /\A[0-9a-fA-F]{8}\z/
|
|
965
|
+
resolved.upcase
|
|
966
|
+
when /\A[0-9a-fA-F]{6}\z/
|
|
967
|
+
"FF#{resolved.upcase}"
|
|
968
|
+
end
|
|
969
|
+
end
|
|
970
|
+
end
|
|
971
|
+
|
|
972
|
+
# : (untyped spec) -> ::Hash[Symbol, ::String]?
|
|
973
|
+
def color_tag_attrs(spec)
|
|
974
|
+
return nil unless spec
|
|
975
|
+
|
|
976
|
+
spec = spec[:color] if spec.is_a?(Hash) && spec[:color].is_a?(Hash)
|
|
977
|
+
return nil unless spec
|
|
978
|
+
|
|
979
|
+
if spec.is_a?(Hash)
|
|
980
|
+
if spec[:auto]
|
|
981
|
+
{ auto: "1" }
|
|
982
|
+
elsif spec[:rgb] || (spec[:color] && !spec[:color].is_a?(Hash))
|
|
983
|
+
raw_rgb = spec[:rgb] || spec[:color]
|
|
984
|
+
fmt = format_rgb_hex(raw_rgb)
|
|
985
|
+
fmt ? { rgb: fmt } : nil
|
|
986
|
+
elsif spec[:theme]
|
|
987
|
+
attrs = { theme: spec[:theme].to_s }
|
|
988
|
+
attrs[:tint] = spec[:tint].to_s if spec[:tint]
|
|
989
|
+
attrs
|
|
990
|
+
elsif spec[:indexed]
|
|
991
|
+
{ indexed: spec[:indexed].to_s }
|
|
838
992
|
end
|
|
993
|
+
elsif spec.is_a?(String) || spec.is_a?(Symbol)
|
|
994
|
+
fmt = format_rgb_hex(spec)
|
|
995
|
+
fmt ? { rgb: fmt } : nil
|
|
839
996
|
end
|
|
840
997
|
end
|
|
841
998
|
|
|
@@ -846,6 +1003,7 @@ module Xlsxrb
|
|
|
846
1003
|
|
|
847
1004
|
def escape_xml(value)
|
|
848
1005
|
str = value.to_s
|
|
1006
|
+
str = str.dup.force_encoding(Encoding::UTF_8) if str.encoding == Encoding::BINARY
|
|
849
1007
|
str = str.gsub(INVALID_XML_CHARS_RE, "") if str.match?(INVALID_XML_CHARS_RE)
|
|
850
1008
|
str.match?(ESCAPE_RE) ? str.gsub(ESCAPE_RE, ESCAPE_MAP) : str
|
|
851
1009
|
end
|
|
@@ -878,11 +1036,10 @@ module Xlsxrb
|
|
|
878
1036
|
end
|
|
879
1037
|
buf << '<vertAlign val="' << font[:vert_align].to_s << '"/>' if font[:vert_align]
|
|
880
1038
|
buf << '<sz val="' << font[:sz].to_s << '"/>' if font[:sz]
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
buf << '<color theme="' << font[:theme].to_s << '"' << tint_attr << "/>"
|
|
1039
|
+
color_attrs = color_tag_attrs(font[:color] || font)
|
|
1040
|
+
if color_attrs
|
|
1041
|
+
attrs_str = color_attrs.map { |k, v| %(#{k}="#{v}") }.join(" ")
|
|
1042
|
+
buf << "<color #{attrs_str}/>"
|
|
886
1043
|
end
|
|
887
1044
|
buf << '<rFont val="' << escape_xml(font[:name]) << '"/>' if font[:name]
|
|
888
1045
|
buf << '<family val="' << font[:family].to_s << '"/>' if font[:family]
|
|
@@ -891,17 +1048,11 @@ module Xlsxrb
|
|
|
891
1048
|
else
|
|
892
1049
|
buf << "<r><t>"
|
|
893
1050
|
end
|
|
894
|
-
buf << escape_xml(run[:text]) << "</t></r>"
|
|
1051
|
+
buf << escape_xml(run[:text].to_s) << "</t></r>"
|
|
895
1052
|
end
|
|
896
1053
|
buf << "</si>"
|
|
897
1054
|
else
|
|
898
|
-
|
|
899
|
-
needs_space = s.start_with?(" ", "\t", "\n") || s.end_with?(" ", "\t", "\n")
|
|
900
|
-
if needs_space
|
|
901
|
-
buf << '<si><t xml:space="preserve">' << escape_xml(s) << "</t></si>"
|
|
902
|
-
else
|
|
903
|
-
buf << "<si><t>" << escape_xml(s) << "</t></si>"
|
|
904
|
-
end
|
|
1055
|
+
buf << "<si><t>" << escape_xml(str.to_s) << "</t></si>"
|
|
905
1056
|
end
|
|
906
1057
|
|
|
907
1058
|
if buf.bytesize >= 32_768
|
|
@@ -917,14 +1068,14 @@ module Xlsxrb
|
|
|
917
1068
|
end
|
|
918
1069
|
|
|
919
1070
|
def write_worksheet_xml(io, sheet, drawing_rid: nil, sheet_protection: nil,
|
|
920
|
-
|
|
921
|
-
merge_cells: nil, conditional_formats: nil,
|
|
1071
|
+
protected_ranges: nil, auto_filter: nil, filter_columns: nil,
|
|
1072
|
+
sort_state: nil, merge_cells: nil, conditional_formats: nil,
|
|
922
1073
|
data_validations: nil, hyperlinks: nil,
|
|
923
1074
|
print_options: nil, page_margins: nil, page_setup: nil,
|
|
924
1075
|
header_footer: nil, row_breaks: nil, col_breaks: nil,
|
|
925
|
-
|
|
926
|
-
sheet_view: nil, sheet_properties: nil,
|
|
927
|
-
tables: nil, table_start_rid: nil,
|
|
1076
|
+
cell_watches: nil, freeze_pane: nil, split_pane: nil,
|
|
1077
|
+
selection: nil, sheet_view: nil, sheet_properties: nil,
|
|
1078
|
+
sheet_format: nil, tables: nil, table_start_rid: nil,
|
|
928
1079
|
legacy_drawing_rid: nil, sparkline_groups: nil)
|
|
929
1080
|
date1904 = @workbook_properties&.[](:date1904) ? true : false
|
|
930
1081
|
ws = WorksheetWriter.new(io, date1904: date1904)
|
|
@@ -934,7 +1085,8 @@ module Xlsxrb
|
|
|
934
1085
|
freeze_pane: freeze_pane,
|
|
935
1086
|
split_pane: split_pane,
|
|
936
1087
|
selection: selection,
|
|
937
|
-
sheet_view: sheet_view
|
|
1088
|
+
sheet_view: sheet_view,
|
|
1089
|
+
sheet_format: sheet_format
|
|
938
1090
|
)
|
|
939
1091
|
if sheet[:rows_tmp_path]
|
|
940
1092
|
File.open(sheet[:rows_tmp_path], "rb") do |f|
|
|
@@ -961,6 +1113,7 @@ module Xlsxrb
|
|
|
961
1113
|
ws.finish(
|
|
962
1114
|
drawing_rid: drawing_rid,
|
|
963
1115
|
sheet_protection: sheet_protection,
|
|
1116
|
+
protected_ranges: protected_ranges,
|
|
964
1117
|
auto_filter: auto_filter,
|
|
965
1118
|
filter_columns: filter_columns,
|
|
966
1119
|
sort_state: sort_state,
|
|
@@ -974,6 +1127,7 @@ module Xlsxrb
|
|
|
974
1127
|
header_footer: header_footer,
|
|
975
1128
|
row_breaks: row_breaks,
|
|
976
1129
|
col_breaks: col_breaks,
|
|
1130
|
+
cell_watches: cell_watches,
|
|
977
1131
|
tables: tables,
|
|
978
1132
|
table_start_rid: table_start_rid,
|
|
979
1133
|
legacy_drawing_rid: legacy_drawing_rid,
|
|
@@ -1002,8 +1156,25 @@ module Xlsxrb
|
|
|
1002
1156
|
# Table columns
|
|
1003
1157
|
columns = tbl[:columns] || []
|
|
1004
1158
|
b.open_tag("tableColumns", { count: columns.size.to_s })
|
|
1005
|
-
columns.each_with_index do |
|
|
1006
|
-
|
|
1159
|
+
columns.each_with_index do |col, ci|
|
|
1160
|
+
col_name = col.is_a?(Hash) ? col[:name] : col
|
|
1161
|
+
col_attrs = { id: (ci + 1).to_s, name: col_name.to_s }
|
|
1162
|
+
if col.is_a?(Hash)
|
|
1163
|
+
col_attrs[:totalsRowFunction] = col[:totals_row_function].to_s if col[:totals_row_function]
|
|
1164
|
+
col_attrs[:totalsRowLabel] = col[:totals_row_label].to_s if col[:totals_row_label]
|
|
1165
|
+
col_attrs[:dataDxfId] = col[:data_dxf_id].to_s if col[:data_dxf_id]
|
|
1166
|
+
col_attrs[:totalsRowDxfId] = col[:totals_row_dxf_id].to_s if col[:totals_row_dxf_id]
|
|
1167
|
+
col_attrs[:headerRowDxfId] = col[:header_row_dxf_id].to_s if col[:header_row_dxf_id]
|
|
1168
|
+
col_attrs[:dataCellStyle] = col[:data_cell_style].to_s if col[:data_cell_style]
|
|
1169
|
+
end
|
|
1170
|
+
if col.is_a?(Hash) && (col[:calculated_column_formula] || col[:totals_row_formula])
|
|
1171
|
+
b.open_tag("tableColumn", col_attrs)
|
|
1172
|
+
b.tag("calculatedColumnFormula") { |cb| cb.text(col[:calculated_column_formula].to_s) } if col[:calculated_column_formula]
|
|
1173
|
+
b.tag("totalsRowFormula") { |tb| tb.text(col[:totals_row_formula].to_s) } if col[:totals_row_formula]
|
|
1174
|
+
b.close_tag("tableColumn")
|
|
1175
|
+
else
|
|
1176
|
+
b.empty_tag("tableColumn", col_attrs)
|
|
1177
|
+
end
|
|
1007
1178
|
end
|
|
1008
1179
|
b.close_tag("tableColumns")
|
|
1009
1180
|
|
|
@@ -1102,11 +1273,13 @@ module Xlsxrb
|
|
|
1102
1273
|
@custom_properties.each_with_index do |prop, idx|
|
|
1103
1274
|
b.open_tag("property", { fmtid: "{D5CDD505-2E9C-101B-9397-08002B2CF9AE}", pid: (idx + 2).to_s, name: prop[:name] })
|
|
1104
1275
|
case prop[:type]
|
|
1105
|
-
when :number
|
|
1276
|
+
when :number, :int, :i4
|
|
1106
1277
|
b.tag("vt:i4") { |_| b.text(prop[:value].to_s) }
|
|
1107
|
-
when :
|
|
1278
|
+
when :float, :r8
|
|
1279
|
+
b.tag("vt:r8") { |_| b.text(prop[:value].to_s) }
|
|
1280
|
+
when :bool, :boolean
|
|
1108
1281
|
b.tag("vt:bool") { |_| b.text(prop[:value] ? "true" : "false") }
|
|
1109
|
-
when :date
|
|
1282
|
+
when :date, :filetime
|
|
1110
1283
|
b.tag("vt:filetime") { |_| b.text(prop[:value].to_s) }
|
|
1111
1284
|
else
|
|
1112
1285
|
b.tag("vt:lpwstr") { |_| b.text(prop[:value].to_s) }
|