write_xlsx 1.11.1 → 1.12.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: cabafeb058f3742956ec06251cab5ecf67d9b76cd5db3ec659e2e4184d2c1483
4
- data.tar.gz: 95a68e0390bff44e03dc8577b31162c1d1c2cb80d77f3943947e1114327afb01
3
+ metadata.gz: b8f704b9674f7d6de467d512cea780114dccc51ed88d99ad4d57727fdafd32b4
4
+ data.tar.gz: 54df4aec8346b1935bb3b0ba283c11b04f415b3b644308ab8f4cce2f0595d14d
5
5
  SHA512:
6
- metadata.gz: 70a98e3ffc2be69574805f72a63678b2023cc3aef9a1c1a97c5f8a13de80de29c641c573d8c8c9bd7d33da088827a15fb6feb3b4c7591b50ffb26cb321591b49
7
- data.tar.gz: 1d387aa195a7a35f0b664e4ff155ca71de765ad6ae886df66debc9063d1fea8c4c905ab10a7aef735d69813b79faa1d6e7ded96e70ce23a75df56e5f172b1648
6
+ metadata.gz: ecbd4957e8b5ee4d9c77e46da68ddfa0f877dd94166551a20c1b76a782209db761a43a86bae340657181ec8993bdc277ee67b66c4c889bc5ad418e6fe2b953c5
7
+ data.tar.gz: c2a9f0b209de183bbb87ddceb0897dca8708b372d7bb8a5fb7953595440fdaceac60fe4500dafe2ed74585208c6f17b03c554e20c4b5d05af2ec019854b0a259
data/.rubocop.yml CHANGED
@@ -82,6 +82,9 @@ Naming/HeredocDelimiterNaming:
82
82
  Naming/MethodName:
83
83
  Enabled: false
84
84
 
85
+ Naming/MethodParameterName:
86
+ Enabled: false
87
+
85
88
  Naming/VariableNumber:
86
89
  Enabled: false
87
90
 
data/Changes CHANGED
@@ -1,5 +1,14 @@
1
1
  Change history of write_xlsx rubygem.
2
2
 
3
+ 2023-12-26 v1.11.2
4
+ Fix issue where header images in chartsheets weren't displayed.
5
+ Add support for custom table total functions.
6
+ Add chart option to display N/A as empty cells.
7
+ Add support for leader lines to all chart types.
8
+ Fix issue where column formulas were overwritten by table data
9
+ add Worksheet#very_hidden method.
10
+ Add add trendline equation formatting for chart.
11
+
3
12
  2023-08-09 v1.11.1
4
13
  Fixed issue #110. Redefining costant Struct::ColInfo
5
14
 
data/README.md CHANGED
@@ -85,7 +85,7 @@ the first worksheet in an Excel XML spreadsheet called ruby.xlsx:
85
85
  Original Perl module was written by John McNamara(jmcnamara@cpan.org).
86
86
 
87
87
  Converted to ruby by Hideo NAKAMURA(nakamrua.hideo@gmail.com)
88
- Copyright (c) 2012-2023 Hideo NAKAMURA.
88
+ Copyright (c) 2012-2024 Hideo NAKAMURA.
89
89
 
90
90
  See LICENSE.txt for further details.
91
91
 
@@ -218,7 +218,7 @@ shapes_list.each_line do |line|
218
218
  line = line.chomp
219
219
  next unless line =~ /^\w/ # Skip blank lines and comments.
220
220
 
221
- sheet, name = line.split(/\t/)
221
+ sheet, name = line.split("\t")
222
222
  if last_sheet != sheet
223
223
  worksheet = workbook.add_worksheet(sheet)
224
224
  row = 2
@@ -123,8 +123,7 @@ module Writexlsx
123
123
  # Write the <c:legend> element.
124
124
  #
125
125
  def write_legend
126
- position = @legend.position
127
- allowed = %w[right left top bottom]
126
+ allowed = %w[right left top bottom]
128
127
  delete_series = @legend.delete_series || []
129
128
 
130
129
  if @legend.position =~ /^overlay_/
@@ -207,6 +206,19 @@ module Writexlsx
207
206
  def write_first_slice_ang
208
207
  @writer.empty_tag('c:firstSliceAng', [['val', @rotation]])
209
208
  end
209
+
210
+ #
211
+ # Write the <c:showLeaderLines> element. This is for Pie/Doughnut charts.
212
+ # Other chart types only supported leader lines after Excel 2015 via an
213
+ # extension element.
214
+ #
215
+ def write_show_leader_lines
216
+ val = 1
217
+
218
+ attributes = [['val', val]]
219
+
220
+ @writer.empty_tag('c:showLeaderLines', attributes)
221
+ end
210
222
  end
211
223
  end
212
224
  end
@@ -59,10 +59,13 @@ module Writexlsx
59
59
  class Trendline < Chartline
60
60
  attr_reader :name, :forward, :backward, :order, :period
61
61
  attr_reader :intercept, :display_equation, :display_r_squared
62
+ attr_reader :label
62
63
 
63
64
  def initialize(params)
64
65
  super(params)
65
66
 
67
+ @label = trendline_label_properties(params[:label])
68
+
66
69
  @name = params[:name]
67
70
  @forward = params[:forward]
68
71
  @backward = params[:backward]
@@ -76,6 +79,51 @@ module Writexlsx
76
79
 
77
80
  private
78
81
 
82
+ #
83
+ # Convert user defined trendline label properties to the structure required
84
+ # internally.
85
+ #
86
+ def trendline_label_properties(_label)
87
+ return unless _label || _label.is_a?(Hash)
88
+
89
+ # Copy the user supplied properties.
90
+ label = {}
91
+
92
+ # Set the font properties for the label.
93
+ label[:font] = convert_font_args(_label[:font]) if ptrue?(_label[:font])
94
+
95
+ # Set the line properties for the label.
96
+ line = line_properties(_label[:line])
97
+
98
+ # Allow 'border' as a synonym for 'line'.
99
+ line = line_properties(_label[:border]) if ptrue?(_label[:border])
100
+
101
+ # Set the fill properties for the label.
102
+ fill = fill_properties(_label[:fill])
103
+
104
+ # Set the pattern properties for the label.
105
+ pattern = pattern_properties(_label[:pattern])
106
+
107
+ # Set the gradient fill properties for the label.
108
+ gradient = gradient_properties(_label[:gradient])
109
+
110
+ # Pattern fill overrides solid fill.
111
+ fill = nil if ptrue?(pattern)
112
+
113
+ # Gradient fill overrides solid and pattern fills.
114
+ if ptrue?(gradient)
115
+ pattern = nil
116
+ fill = nil
117
+ end
118
+
119
+ label[:line] = line
120
+ label[:fill] = fill
121
+ label[:pattern] = pattern
122
+ label[:gradient] = gradient
123
+
124
+ label
125
+ end
126
+
79
127
  def types
80
128
  {
81
129
  exponential: 'exp',
@@ -568,6 +568,13 @@ module Writexlsx
568
568
  @is_secondary
569
569
  end
570
570
 
571
+ #
572
+ # Set the option for displaying #N/A as an empty cell in a chart.
573
+ #
574
+ def show_na_as_empty_cell
575
+ @show_na_as_empty = true
576
+ end
577
+
571
578
  private
572
579
 
573
580
  def axis_setup
@@ -598,6 +605,7 @@ module Writexlsx
598
605
  @cross_between = 'between'
599
606
  @date_category = false
600
607
  @show_blanks = 'gap'
608
+ @show_na_as_empty = false
601
609
  @show_hidden_data = false
602
610
  @show_crosses = true
603
611
  end
@@ -782,6 +790,9 @@ module Writexlsx
782
790
 
783
791
  # Write the c:dispBlanksAs element.
784
792
  write_disp_blanks_as
793
+
794
+ # Write the c:extLst element.
795
+ write_ext_lst_display_na if @show_na_as_empty
785
796
  end
786
797
  end
787
798
 
@@ -950,12 +961,13 @@ module Writexlsx
950
961
  write_val(series)
951
962
  # Write the c:smooth element.
952
963
  write_c_smooth(series.smooth) if ptrue?(@smooth_allowed)
953
- write_ext_lst(series.inverted_color) if series.inverted_color
964
+ # Write the c:extLst element.
965
+ write_ext_lst_inverted_fill(series.inverted_color) if series.inverted_color
954
966
  end
955
967
  @series_index += 1
956
968
  end
957
969
 
958
- def write_ext_lst(color)
970
+ def write_ext_lst_inverted_fill(color)
959
971
  uri = '{6F2FDCE9-48DA-4B69-8628-5D25D57E5C99}'
960
972
  xmlns_c_14 =
961
973
  'http://schemas.microsoft.com/office/drawing/2007/8/2/chart'
@@ -980,6 +992,31 @@ module Writexlsx
980
992
  end
981
993
  end
982
994
 
995
+ #
996
+ # Write the <c:extLst> element for the display N/A as empty cell option.
997
+ #
998
+ def write_ext_lst_display_na
999
+ uri = '{56B9EC1D-385E-4148-901F-78D8002777C0}'
1000
+ xmlns_c_16 = 'http://schemas.microsoft.com/office/drawing/2017/03/chart'
1001
+
1002
+ attributes1 = [
1003
+ ['uri', uri],
1004
+ ['xmlns:c16r3', xmlns_c_16]
1005
+ ]
1006
+
1007
+ attributes2 = [
1008
+ ['val', 1]
1009
+ ]
1010
+
1011
+ @writer.tag_elements('c:extLst') do
1012
+ @writer.tag_elements('c:ext', attributes1) do
1013
+ @writer.tag_elements('c16r3:dataDisplayOptions16') do
1014
+ @writer.empty_tag('c16r3:dispNaAsBlank', attributes2)
1015
+ end
1016
+ end
1017
+ end
1018
+ end
1019
+
983
1020
  def write_ser_base(series)
984
1021
  # Write the c:idx element.
985
1022
  write_idx(@series_index)
@@ -2122,7 +2159,7 @@ module Writexlsx
2122
2159
  # Write the c:dispEq element.
2123
2160
  write_disp_eq
2124
2161
  # Write the c:trendlineLbl element.
2125
- write_trendline_lbl
2162
+ write_trendline_lbl(trendline)
2126
2163
  end
2127
2164
  end
2128
2165
  end
@@ -2199,12 +2236,18 @@ module Writexlsx
2199
2236
  #
2200
2237
  # Write the <c:trendlineLbl> element.
2201
2238
  #
2202
- def write_trendline_lbl
2239
+ def write_trendline_lbl(trendline)
2203
2240
  @writer.tag_elements('c:trendlineLbl') do
2204
2241
  # Write the c:layout element.
2205
2242
  write_layout
2206
2243
  # Write the c:numFmt element.
2207
2244
  write_trendline_num_fmt
2245
+ # Write the c:spPr element for the label formatting.
2246
+ write_sp_pr(trendline.label)
2247
+ # Write the data label font elements.
2248
+ if trendline.label && ptrue?(trendline.label[:font])
2249
+ write_axis_font(trendline.label[:font])
2250
+ end
2208
2251
  end
2209
2252
  end
2210
2253
 
@@ -2544,11 +2587,25 @@ module Writexlsx
2544
2587
  @writer.data_element('c:separator', data)
2545
2588
  end
2546
2589
 
2547
- #
2548
- # Write the <c:showLeaderLines> element.
2549
- #
2590
+ # Write the <c:showLeaderLines> element. This is different for Pie/Doughnut
2591
+ # charts. Other chart types only supported leader lines after Excel 2015 via
2592
+ # an extension element.
2550
2593
  def write_show_leader_lines
2551
- @writer.empty_tag('c:showLeaderLines', [['val', 1]])
2594
+ uri = '{CE6537A1-D6FC-4f65-9D91-7224C49458BB}'
2595
+ xmlns_c_15 = 'http://schemas.microsoft.com/office/drawing/2012/chart'
2596
+
2597
+ attributes1 = [
2598
+ ['uri', uri],
2599
+ ['xmlns:c15', xmlns_c_15]
2600
+ ]
2601
+
2602
+ attributes2 = [['val', 1]]
2603
+
2604
+ @writer.tag_elements('c:extLst') do
2605
+ @writer.tag_elements('c:ext', attributes1) do
2606
+ @writer.empty_tag('c15:showLeaderLines', attributes2)
2607
+ end
2608
+ end
2552
2609
  end
2553
2610
 
2554
2611
  #
@@ -2862,8 +2919,6 @@ module Writexlsx
2862
2919
  # Write the <a:fillToRect> element.
2863
2920
  #
2864
2921
  def write_a_fill_to_rect(type)
2865
- attributes = []
2866
-
2867
2922
  attributes = if type == 'shape'
2868
2923
  [
2869
2924
  ['l', 50000],
@@ -2885,8 +2940,6 @@ module Writexlsx
2885
2940
  # Write the <a:tileRect> element.
2886
2941
  #
2887
2942
  def write_a_tile_rect(type)
2888
- attributes = []
2889
-
2890
2943
  attributes = if type == 'shape'
2891
2944
  []
2892
2945
  else
@@ -54,6 +54,8 @@ module Writexlsx
54
54
  write_header_footer
55
55
  # Write the drawing element.
56
56
  write_drawings
57
+ # Write the legaacyDrawingHF element.
58
+ write_legacy_drawing_hf
57
59
  # Close the worksheet tag.
58
60
  end
59
61
  end
@@ -145,6 +147,10 @@ module Writexlsx
145
147
  @chart.show_blanks_as(*args)
146
148
  end
147
149
 
150
+ def show_na_as_empty_cell
151
+ @chart.show_na_as_empty_cell(*args)
152
+ end
153
+
148
154
  def show_hidden_data(*args)
149
155
  @chart.show_hidden_data(*args)
150
156
  end
@@ -185,7 +191,10 @@ module Writexlsx
185
191
  end
186
192
 
187
193
  def external_links
188
- [@external_drawing_links]
194
+ [
195
+ @external_drawing_links,
196
+ @external_vml_links
197
+ ]
189
198
  end
190
199
 
191
200
  private
@@ -7,12 +7,16 @@ class ColName
7
7
  include Singleton
8
8
 
9
9
  def initialize
10
- @col_str_table = {}
10
+ @col_str_table = []
11
+ @row_str_table = []
11
12
  end
12
13
 
13
14
  def col_str(col)
14
- @col_str_table[col] = col_str_build(col) unless @col_str_table[col]
15
- @col_str_table[col]
15
+ @col_str_table[col] ||= col_str_build(col)
16
+ end
17
+
18
+ def row_str(row)
19
+ @row_str_table[row] ||= row.to_s
16
20
  end
17
21
 
18
22
  private
@@ -146,10 +146,10 @@ module Writexlsx
146
146
  return
147
147
  end
148
148
 
149
- # Indent is only allowed for horizontal left, right and distributed. If it
150
- # is defined for any other alignment or no alignment has been set then
151
- # default to left alignment.
152
- @text_h_align = 1 if @indent != 0 && ![1, 3, 7].include?(@text_h_align)
149
+ # Indent is only allowed for some alignment properties. If it is defined
150
+ # for any other alignment or no alignment has been set then default to
151
+ # left alignment.
152
+ @text_h_align = 1 if @indent != 0 && ![1, 3, 7].include?(@text_h_align) && ![1, 3, 5].include?(@text_v_align)
153
153
 
154
154
  # Check for properties that are mutually exclusive.
155
155
  @shrink = 0 if @text_wrap != 0
@@ -178,8 +178,8 @@ module Writexlsx
178
178
  align << %w[vertical justify] if @text_v_align == 4
179
179
  align << %w[vertical distributed] if @text_v_align == 5
180
180
 
181
- align << ['indent', @indent] if @indent != 0
182
181
  align << ['textRotation', @rotation] if @rotation != 0
182
+ align << ['indent', @indent] if @indent != 0
183
183
 
184
184
  align << ['wrapText', 1] if @text_wrap != 0
185
185
  align << ['shrinkToFit', 1] if @shrink != 0
@@ -324,7 +324,7 @@ module Writexlsx
324
324
  return colors[color_code.downcase.to_sym] if colors[color_code.downcase.to_sym]
325
325
 
326
326
  # or the default color if string is unrecognised,
327
- return 0x00 if color_code =~ /\D/
327
+ 0x00 if color_code =~ /\D/
328
328
  else
329
329
  # or an index < 8 mapped into the correct range,
330
330
  return color_code + 8 if color_code < 8
@@ -46,7 +46,9 @@ module Writexlsx
46
46
  add_heading_pair(
47
47
  [
48
48
  'Worksheets',
49
- @workbook.worksheets.reject { |s| s.is_chartsheet? }.count
49
+ @workbook.worksheets.reject do |s|
50
+ s.is_chartsheet? || s.very_hidden?
51
+ end.count
50
52
  ]
51
53
  )
52
54
  end
@@ -57,14 +59,16 @@ module Writexlsx
57
59
 
58
60
  def add_worksheet_part_names
59
61
  @workbook.worksheets
60
- .reject { |sheet| sheet.is_chartsheet? }
61
- .each { |sheet| add_part_name(sheet.name) }
62
+ .reject { |sheet| sheet.is_chartsheet? || sheet.very_hidden? }
63
+ .each do |sheet|
64
+ add_part_name(sheet.name)
65
+ end
62
66
  end
63
67
 
64
68
  def add_chartsheet_part_names
65
69
  @workbook.worksheets
66
- .select { |sheet| sheet.is_chartsheet? }
67
- .each { |sheet| add_part_name(sheet.name) }
70
+ .select { |sheet| sheet.is_chartsheet? }
71
+ .each { |sheet| add_part_name(sheet.name) }
68
72
  end
69
73
 
70
74
  def add_part_name(part_name)
@@ -411,7 +411,7 @@ module Writexlsx
411
411
  # Check for a cell reference in A1 notation and substitute row and column
412
412
  user_range = if args[0].to_s =~ (/^\D/) && (args[0] =~ /,/)
413
413
  # Check for a user defined multiple range like B3:K6,B8:K11.
414
- args[0].sub(/^=/, '').gsub(/\s*,\s*/, ' ').gsub(/\$/, '')
414
+ args[0].sub(/^=/, '').gsub(/\s*,\s*/, ' ').gsub("$", '')
415
415
  end
416
416
 
417
417
  if (row_col_array = row_col_notation(args.first))
@@ -769,7 +769,7 @@ module Writexlsx
769
769
  attr = super
770
770
  attr << ['percent', 1] if criteria == '%'
771
771
  attr << ['bottom', 1] if direction
772
- attr << ['rank', (value || 10)]
772
+ attr << ['rank', value || 10]
773
773
  attr
774
774
  end
775
775
  end
@@ -199,6 +199,28 @@ module Writexlsx
199
199
  )
200
200
  end
201
201
 
202
+ def add_richvalue
203
+ add_override(
204
+ '/xl/richData/rdRichValueTypes.xml',
205
+ 'application/vnd.ms-excel.rdrichvaluetypes+xml'
206
+ )
207
+
208
+ add_override(
209
+ '/xl/richData/rdrichvalue.xml',
210
+ 'application/vnd.ms-excel.rdrichvalue+xml'
211
+ )
212
+
213
+ add_override(
214
+ '/xl/richData/rdrichvaluestructure.xml',
215
+ 'application/vnd.ms-excel.rdrichvaluestructure+xml'
216
+ )
217
+
218
+ add_override(
219
+ '/xl/richData/richValueRel.xml',
220
+ 'application/vnd.ms-excel.richvaluerel+xml'
221
+ )
222
+ end
223
+
202
224
  private
203
225
 
204
226
  def change_the_workbook_xml_content_type_from_xlsx_to_xlsm