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.
Files changed (61) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +64 -0
  3. data/README.md +2 -2
  4. data/lib/xlsxrb/chart_builder.rb +41 -2
  5. data/lib/xlsxrb/dsl_helpers.rb +522 -6
  6. data/lib/xlsxrb/elements/cell.rb +34 -9
  7. data/lib/xlsxrb/elements/column.rb +2 -2
  8. data/lib/xlsxrb/elements/types.rb +1 -0
  9. data/lib/xlsxrb/elements/workbook.rb +48 -0
  10. data/lib/xlsxrb/elements/worksheet.rb +158 -11
  11. data/lib/xlsxrb/ooxml/reader/listeners/core_listeners.rb +22 -1
  12. data/lib/xlsxrb/ooxml/reader/listeners/drawing_listeners.rb +187 -24
  13. data/lib/xlsxrb/ooxml/reader/listeners/feature_listeners.rb +108 -1
  14. data/lib/xlsxrb/ooxml/reader/listeners/style_listeners.rb +16 -11
  15. data/lib/xlsxrb/ooxml/reader.rb +88 -0
  16. data/lib/xlsxrb/ooxml/styles_parser.rb +19 -11
  17. data/lib/xlsxrb/ooxml/workbook_parser.rb +18 -3
  18. data/lib/xlsxrb/ooxml/workbook_writer.rb +266 -46
  19. data/lib/xlsxrb/ooxml/worksheet_parser.rb +18 -4
  20. data/lib/xlsxrb/ooxml/worksheet_writer.rb +289 -88
  21. data/lib/xlsxrb/ooxml/writer/drawing_xml.rb +579 -441
  22. data/lib/xlsxrb/ooxml/writer/features_xml.rb +26 -9
  23. data/lib/xlsxrb/ooxml/writer/styles_xml.rb +23 -9
  24. data/lib/xlsxrb/ooxml/writer.rb +268 -31
  25. data/lib/xlsxrb/ooxml/zip_writer.rb +15 -5
  26. data/lib/xlsxrb/stream_sheet.rb +157 -3
  27. data/lib/xlsxrb/stream_writer.rb +946 -74
  28. data/lib/xlsxrb/style_builder.rb +59 -6
  29. data/lib/xlsxrb/utils.rb +65 -0
  30. data/lib/xlsxrb/version.rb +1 -1
  31. data/lib/xlsxrb/workbook_builder.rb +197 -14
  32. data/lib/xlsxrb/worksheet_builder.rb +1137 -44
  33. data/lib/xlsxrb.rb +302 -47
  34. data/sig/generated/xlsxrb/chart_builder.rbs +31 -3
  35. data/sig/generated/xlsxrb/dsl_helpers.rbs +119 -4
  36. data/sig/generated/xlsxrb/elements/cell.rbs +20 -3
  37. data/sig/generated/xlsxrb/elements/column.rbs +3 -3
  38. data/sig/generated/xlsxrb/elements/types.rbs +2 -0
  39. data/sig/generated/xlsxrb/elements/workbook.rbs +37 -0
  40. data/sig/generated/xlsxrb/elements/worksheet.rbs +82 -2
  41. data/sig/generated/xlsxrb/ooxml/reader/listeners/core_listeners.rbs +2 -0
  42. data/sig/generated/xlsxrb/ooxml/reader/listeners/drawing_listeners.rbs +74 -2
  43. data/sig/generated/xlsxrb/ooxml/reader/listeners/feature_listeners.rbs +36 -0
  44. data/sig/generated/xlsxrb/ooxml/reader/listeners/style_listeners.rbs +3 -0
  45. data/sig/generated/xlsxrb/ooxml/reader.rbs +17 -0
  46. data/sig/generated/xlsxrb/ooxml/styles_parser.rbs +3 -0
  47. data/sig/generated/xlsxrb/ooxml/workbook_parser.rbs +8 -0
  48. data/sig/generated/xlsxrb/ooxml/workbook_writer.rbs +8 -4
  49. data/sig/generated/xlsxrb/ooxml/worksheet_parser.rbs +1 -1
  50. data/sig/generated/xlsxrb/ooxml/worksheet_writer.rbs +12 -1
  51. data/sig/generated/xlsxrb/ooxml/writer/drawing_xml.rbs +16 -0
  52. data/sig/generated/xlsxrb/ooxml/writer.rbs +42 -8
  53. data/sig/generated/xlsxrb/ooxml/zip_writer.rbs +8 -2
  54. data/sig/generated/xlsxrb/stream_sheet.rbs +101 -1
  55. data/sig/generated/xlsxrb/stream_writer.rbs +592 -23
  56. data/sig/generated/xlsxrb/style_builder.rbs +24 -6
  57. data/sig/generated/xlsxrb/utils.rbs +20 -0
  58. data/sig/generated/xlsxrb/workbook_builder.rbs +87 -5
  59. data/sig/generated/xlsxrb/worksheet_builder.rbs +554 -10
  60. data/sig/generated/xlsxrb.rbs +77 -16
  61. metadata +1 -1
@@ -322,13 +322,21 @@ module Xlsxrb
322
322
  rpr << "<i/>" if font[:italic]
323
323
  rpr << "<strike/>" if font[:strike]
324
324
  if font[:underline]
325
- rpr << if font[:underline] == true
326
- "<u/>"
327
- else
328
- %(<u val="#{font[:underline]}"/>)
329
- end
325
+ u_val = font[:underline]
326
+ if u_val == true || u_val.to_s == "single"
327
+ rpr << "<u/>"
328
+ else
329
+ mapped_u = StyleBuilder::UNDERLINE_MAP[u_val.to_sym] || u_val
330
+ rpr << %(<u val="#{mapped_u}"/>)
331
+ end
332
+ end
333
+ va_val = font[:vert_align] || font[:vertical_align]
334
+ va_val ||= "superscript" if font[:superscript]
335
+ va_val ||= "subscript" if font[:subscript]
336
+ if va_val
337
+ mapped_va = StyleBuilder::VERT_ALIGN_MAP[va_val.to_sym] || va_val
338
+ rpr << %(<vertAlign val="#{mapped_va}"/>)
330
339
  end
331
- rpr << %(<vertAlign val="#{font[:vert_align]}"/>) if font[:vert_align]
332
340
  rpr << %(<sz val="#{font[:sz]}"/>) if font[:sz]
333
341
  rpr << emit_color_xml(font)
334
342
  rpr << %(<rFont val="#{xml_escape(font[:name])}"/>) if font[:name]
@@ -378,7 +386,12 @@ module Xlsxrb
378
386
  t_attr = ' t="b"'
379
387
  cached_val_str = "0"
380
388
  end
381
- parts = %(<c r="#{cell_ref}"#{t_attr}#{s_attr}#{ph_attr}><f#{f_attrs}>#{xml_escape(value.expression)}</f>)
389
+ f_tag = if value.expression && !value.expression.empty?
390
+ "<f#{f_attrs}>#{xml_escape(value.expression)}</f>"
391
+ else
392
+ "<f#{f_attrs}/>"
393
+ end
394
+ parts = %(<c r="#{cell_ref}"#{t_attr}#{s_attr}#{ph_attr}>#{f_tag})
382
395
  parts << "<v>#{xml_escape(cached_val_str)}</v>" unless value.cached_value.nil?
383
396
  parts << "</c>"
384
397
  parts
@@ -538,13 +551,17 @@ module Xlsxrb
538
551
  rule_attrs << %( percent="1") if rule[:percent]
539
552
  rule_attrs << %( bottom="1") if rule[:bottom]
540
553
  rule_attrs << %( text="#{xml_escape(rule[:text])}") if rule[:text]
541
- rule_attrs << %( timePeriod="#{rule[:time_period]}") if rule[:time_period]
554
+ if rule[:time_period]
555
+ tp = rule[:time_period]
556
+ tp_str = DslHelpers::CF_TIME_PERIODS[tp.to_sym] || tp.to_s
557
+ rule_attrs << %( timePeriod="#{tp_str}")
558
+ end
542
559
  rule_attrs << %( stdDev="#{rule[:std_dev]}") if rule[:std_dev]
543
560
 
544
561
  case type
545
562
  when :cell_is, :expression, :above_average, :top10, :duplicate_values, :unique_values,
546
563
  :contains_text, :not_contains_text, :begins_with, :ends_with,
547
- :contains_blanks, :not_contains_blanks, :time_period
564
+ :contains_blanks, :not_contains_blanks, :contains_errors, :not_contains_errors, :time_period
548
565
  formulas = rule[:formulas] || [rule[:formula]].compact
549
566
  if formulas.empty?
550
567
  parts << "<cfRule #{rule_attrs}/>"
@@ -231,21 +231,35 @@ module Xlsxrb
231
231
  # : (untyped font) -> untyped
232
232
  def emit_font_xml(font)
233
233
  parts = ["<font>"]
234
- parts << "<b/>" if font[:bold]
235
- parts << "<i/>" if font[:italic]
236
- parts << "<strike/>" if font[:strike]
234
+ if font.key?(:bold)
235
+ parts << (font[:bold] ? "<b/>" : '<b val="0"/>')
236
+ end
237
+ if font.key?(:italic)
238
+ parts << (font[:italic] ? "<i/>" : '<i val="0"/>')
239
+ end
240
+ if font.key?(:strike)
241
+ parts << (font[:strike] ? "<strike/>" : '<strike val="0"/>')
242
+ end
237
243
  parts << "<shadow/>" if font[:shadow]
238
244
  parts << "<outline/>" if font[:outline]
239
245
  parts << "<condense/>" if font[:condense]
240
246
  parts << "<extend/>" if font[:extend]
241
247
  if font[:underline]
242
- parts << if font[:underline] == true
243
- "<u/>"
244
- else
245
- %(<u val="#{font[:underline]}"/>)
246
- end
248
+ u_val = font[:underline]
249
+ if u_val == true || u_val.to_s == "single"
250
+ parts << "<u/>"
251
+ else
252
+ mapped_u = StyleBuilder::UNDERLINE_MAP[u_val.to_sym] || u_val
253
+ parts << %(<u val="#{mapped_u}"/>)
254
+ end
255
+ end
256
+ va_val = font[:vert_align] || font[:vertical_align]
257
+ va_val ||= "superscript" if font[:superscript]
258
+ va_val ||= "subscript" if font[:subscript]
259
+ if va_val
260
+ mapped_va = StyleBuilder::VERT_ALIGN_MAP[va_val.to_sym] || va_val
261
+ parts << %(<vertAlign val="#{mapped_va}"/>)
247
262
  end
248
- parts << %(<vertAlign val="#{font[:vert_align]}"/>) if font[:vert_align]
249
263
  parts << %(<sz val="#{font[:sz]}"/>) if font[:sz]
250
264
  parts << emit_color_xml(font)
251
265
  parts << %(<name val="#{xml_escape(font[:name])}"/>) if font[:name]
@@ -33,10 +33,10 @@ module Xlsxrb
33
33
  MAX_ROW = 1_048_576
34
34
  MAX_COLUMN_INDEX = 16_384 # XFD
35
35
 
36
- attr_reader :fonts, :fills, :borders, :xf_entries, :num_fmts
36
+ attr_reader :fonts, :fills, :borders, :xf_entries, :num_fmts, :dxfs
37
37
 
38
- # : () -> void
39
- def initialize
38
+ # : (?default_font: untyped?) -> void
39
+ def initialize(default_font: nil)
40
40
  @sheets = { "Sheet1" => {} }
41
41
  @column_widths = { "Sheet1" => {} }
42
42
  @column_attrs = { "Sheet1" => {} }
@@ -50,6 +50,7 @@ module Xlsxrb
50
50
  @sort_state = { "Sheet1" => nil }
51
51
  @num_fmts = []
52
52
  @fonts = [{ sz: 11, name: "Calibri" }]
53
+ default_font(default_font) if default_font
53
54
  @fills = [{ pattern: "none" }, { pattern: "gray125" }]
54
55
  @borders = [{ left: nil, right: nil, top: nil, bottom: nil }]
55
56
  @xf_entries = [{ num_fmt_id: 0, font_id: 0, fill_id: 0, border_id: 0 }]
@@ -96,6 +97,9 @@ module Xlsxrb
96
97
  @charts_data = { "Sheet1" => [] }
97
98
  @shapes_data = { "Sheet1" => [] }
98
99
  @comments_data = { "Sheet1" => [] }
100
+ @threaded_comments_data = { "Sheet1" => [] }
101
+ @threaded_comment_count = 0
102
+ @persons_data = []
99
103
  @pivot_tables_data = { "Sheet1" => [] }
100
104
  @extra_entries = {}
101
105
  @extra_ct_defaults = {}
@@ -146,6 +150,7 @@ module Xlsxrb
146
150
  @charts_data[name] = []
147
151
  @shapes_data[name] = []
148
152
  @comments_data[name] = []
153
+ @threaded_comments_data[name] = []
149
154
  @pivot_tables_data[name] = []
150
155
  @sheet_protection[name] = nil
151
156
  @protected_ranges[name] = []
@@ -542,6 +547,36 @@ module Xlsxrb
542
547
  @dxfs.size - 1
543
548
  end
544
549
 
550
+ # Sets the workbook-wide default font (font 0).
551
+ #
552
+ # @param name [String, Hash, nil] Font name or font options Hash.
553
+ # @param size [Float, Integer, nil] Font size in points.
554
+ # @param opts [Hash] Additional font options (e.g. `bold:`, `italic:`, `family:`, `charset:`, `color:`).
555
+ # @return [void]
556
+ # @api public
557
+ #: (?String | Hash[Symbol, untyped]? name, ?Float | Integer | nil size, **untyped opts) -> void
558
+ def default_font(name = nil, size = nil, **opts)
559
+ if name.is_a?(Hash)
560
+ opts = name.merge(opts)
561
+ name = opts[:name]
562
+ end
563
+
564
+ font = @fonts[0] ? @fonts[0].dup : { name: "Calibri", sz: 11 }
565
+ font[:name] = (name || opts[:name]).to_s if name || opts[:name]
566
+ font[:sz] = size || opts[:size] || opts[:sz] if size || opts[:size] || opts[:sz]
567
+ font[:family] = opts[:family] if opts.key?(:family)
568
+ font[:charset] = opts[:charset] if opts.key?(:charset)
569
+ font[:scheme] = opts[:scheme].to_s if opts.key?(:scheme)
570
+ font[:color] = opts[:color] if opts.key?(:color)
571
+ font[:bold] = opts[:bold] if opts.key?(:bold)
572
+ font[:italic] = opts[:italic] if opts.key?(:italic)
573
+ font[:strike] = opts[:strike] if opts.key?(:strike)
574
+ font[:underline] = opts[:underline] if opts.key?(:underline)
575
+
576
+ @fonts[0] = font
577
+ end
578
+ alias set_default_font default_font
579
+
545
580
  # Sets the indexed colors palette (array of ARGB hex strings, e.g. ["FF000000", "FFFFFFFF"]).
546
581
  # rubocop:disable Naming/AccessorMethodName
547
582
  # : (untyped colors) -> untyped
@@ -883,12 +918,19 @@ module Xlsxrb
883
918
  # Adds a conditional formatting rule to the specified range.
884
919
  # Options: type (:cell_is, :expression, :color_scale, :data_bar, :icon_set),
885
920
  # operator, priority, formula/formulas, format_id, color_scale, data_bar, icon_set.
886
- # : (untyped sqref, ?sheet: untyped?, **untyped opts) -> untyped
887
- def add_conditional_format(sqref, sheet: nil, **opts)
921
+ # : (?untyped sqref, ?sheet: untyped?, **untyped opts) ?{ (ConditionalFormatBuilder) -> void } -> untyped
922
+ def add_conditional_format(sqref = nil, sheet: nil, **opts, &)
888
923
  sheet_name = sheet || @sheet_order.first
889
924
  raise ArgumentError, "unknown sheet: #{sheet_name}" unless @conditional_formats.key?(sheet_name)
890
925
 
891
- @conditional_formats[sheet_name] << opts.merge(sqref: sqref)
926
+ normalized_list = DslHelpers.normalize_conditional_format(sqref, **opts, &)
927
+ normalized_list.each do |rule|
928
+ if (rule[:dxf] || rule[:font_color] || rule[:fill_color] || rule[:bold] || rule[:italic]) && rule[:format_id].nil?
929
+ dxf = rule.delete(:dxf) || DslHelpers.extract_dxf_from_rule!(rule)
930
+ rule[:format_id] = @dxfs.index(dxf) || add_dxf(**dxf) if dxf
931
+ end
932
+ @conditional_formats[sheet_name] << rule
933
+ end
892
934
  end
893
935
 
894
936
  # Returns conditional formatting rules for the first (or given) sheet.
@@ -916,7 +958,8 @@ module Xlsxrb
916
958
  %i[header_row_count published comment insert_row insert_row_shift
917
959
  header_row_dxf_id data_dxf_id totals_row_dxf_id
918
960
  header_row_border_dxf_id table_border_dxf_id totals_row_border_dxf_id
919
- header_row_cell_style totals_row_cell_style connection_id table_type].each do |key|
961
+ header_row_cell_style totals_row_cell_style connection_id table_type
962
+ auto_filter show_first_column show_last_column show_row_stripes show_column_stripes].each do |key|
920
963
  tbl[key] = opts[key] if opts.key?(key)
921
964
  end
922
965
  @tables[sheet_name] << tbl
@@ -1278,8 +1321,8 @@ module Xlsxrb
1278
1321
  # Adds a chart to the given sheet.
1279
1322
  # type: :bar, :line, :pie. title: chart title string.
1280
1323
  # data_ref: e.g. "Sheet1!$A$1:$B$4". cat_ref/val_ref for explicit series.
1281
- # : (?type: ::Symbol, ?title: untyped?, ?title_overlay: untyped?, ?title_font: untyped?, ?title_fill_color: untyped?, ?title_no_fill: untyped?, ?title_line_color: untyped?, ?title_line_width: untyped?, ?title_line_dash: untyped?, ?title_rotation: untyped?, ?auto_title_deleted: untyped?, ?cat_ref: untyped?, ?val_ref: untyped?, ?series: untyped?, ?legend: untyped?, ?data_labels: untyped?, ?cat_axis_title: untyped?, ?val_axis_title: untyped?, ?cat_axis_title_font: untyped?, ?cat_axis_title_fill: untyped?, ?cat_axis_title_no_fill: untyped?, ?cat_axis_title_line_color: untyped?, ?cat_axis_title_line_width: untyped?, ?cat_axis_title_line_dash: untyped?, ?cat_axis_title_rotation: untyped?, ?val_axis_title_font: untyped?, ?val_axis_title_fill: untyped?, ?val_axis_title_no_fill: untyped?, ?val_axis_title_line_color: untyped?, ?val_axis_title_line_width: untyped?, ?val_axis_title_line_dash: untyped?, ?val_axis_title_rotation: untyped?, ?cat_axis_tick_lbl_pos: untyped?, ?val_axis_tick_lbl_pos: untyped?, ?cat_axis_major_gridlines: untyped?, ?val_axis_major_gridlines: untyped?, ?cat_axis_minor_gridlines: untyped?, ?val_axis_minor_gridlines: untyped?, ?cat_axis_delete: untyped?, ?val_axis_delete: untyped?, ?cat_axis_orientation: untyped?, ?val_axis_orientation: untyped?, ?cat_axis_num_fmt: untyped?, ?val_axis_num_fmt: untyped?, ?cat_axis_major_tick_mark: untyped?, ?cat_axis_minor_tick_mark: untyped?, ?val_axis_major_tick_mark: untyped?, ?val_axis_minor_tick_mark: untyped?, ?cat_axis_crosses: untyped?, ?val_axis_crosses: untyped?, ?cat_axis_crosses_at: untyped?, ?val_axis_crosses_at: untyped?, ?cat_axis_tick_lbl_skip: untyped?, ?cat_axis_tick_mark_skip: untyped?, ?cat_axis_lbl_offset: untyped?, ?cat_axis_auto: untyped?, ?cat_axis_lbl_algn: untyped?, ?cat_axis_no_multi_lvl_lbl: untyped?, ?val_axis_cross_between: untyped?, ?val_axis_major_unit: untyped?, ?val_axis_minor_unit: untyped?, ?cat_axis_scaling_max: untyped?, ?cat_axis_scaling_min: untyped?, ?val_axis_scaling_max: untyped?, ?val_axis_scaling_min: untyped?, ?cat_axis_log_base: untyped?, ?val_axis_log_base: untyped?, ?val_axis_disp_units: untyped?, ?gap_width: untyped?, ?gap_depth: untyped?, ?overlap: untyped?, ?first_slice_ang: untyped?, ?hole_size: untyped?, ?smooth: untyped?, ?marker: untyped?, ?scatter_style: untyped?, ?radar_style: untyped?, ?bar_shape: untyped?, ?bubble_3d: untyped?, ?bubble_scale: untyped?, ?show_neg_bubbles: untyped?, ?size_represents: untyped?, ?wireframe: untyped?, ?grouping: untyped?, ?bar_dir: untyped?, ?vary_colors: untyped?, ?style: untyped?, ?rounded_corners: untyped?, ?view_3d: untyped?, ?cat_axis_pos: untyped?, ?val_axis_pos: untyped?, ?name: untyped?, ?description: untyped?, ?frame_title: untyped?, ?frame_hidden: untyped?, ?frame_macro: untyped?, ?frame_no_grp: untyped?, ?from_col: ::Integer, ?from_row: ::Integer, ?to_col: ::Integer, ?to_row: ::Integer, ?from_col_off: untyped?, ?from_row_off: untyped?, ?to_col_off: untyped?, ?to_row_off: untyped?, ?edit_as: untyped?, ?published: untyped?, ?locks_with_sheet: untyped?, ?prints_with_sheet: untyped?, ?plot_vis_only: untyped?, ?disp_blanks_as: untyped?, ?show_d_lbls_over_max: untyped?, ?data_table: untyped?, ?plot_area_fill: untyped?, ?plot_area_no_fill: untyped?, ?plot_area_line_color: untyped?, ?plot_area_line_width: untyped?, ?plot_area_line_dash: untyped?, ?plot_area_layout: untyped?, ?floor: untyped?, ?side_wall: untyped?, ?back_wall: untyped?, ?drop_lines: untyped?, ?hi_low_lines: untyped?, ?ser_lines: untyped?, ?up_down_bars: untyped?, ?band_fmts: untyped?, ?of_pie_type: untyped?, ?split_type: untyped?, ?split_pos: untyped?, ?cust_split: untyped?, ?second_pie_size: untyped?, ?cat_axis_label_rotation: untyped?, ?val_axis_label_rotation: untyped?, ?cat_axis_font: untyped?, ?val_axis_font: untyped?, ?cat_axis_line_color: untyped?, ?cat_axis_line_width: untyped?, ?cat_axis_line_dash: untyped?, ?val_axis_line_color: untyped?, ?val_axis_line_width: untyped?, ?val_axis_line_dash: untyped?, ?cat_axis_fill: untyped?, ?cat_axis_no_fill: untyped?, ?val_axis_fill: untyped?, ?val_axis_no_fill: untyped?, ?cat_axis_type: untyped?, ?cat_axis_base_time_unit: untyped?, ?cat_axis_major_time_unit: untyped?, ?cat_axis_minor_time_unit: untyped?, ?cat_axis_major_unit: untyped?, ?cat_axis_minor_unit: untyped?, ?chart_fill: untyped?, ?chart_no_fill: untyped?, ?chart_line_color: untyped?, ?chart_line_width: untyped?, ?chart_line_dash: untyped?, ?protection: untyped?, ?print_settings: untyped?, ?chart_font: untyped?, ?sheet: untyped?) -> untyped
1282
- def add_chart(type: :bar, title: nil, title_overlay: nil, title_font: nil, title_fill_color: nil, title_no_fill: nil, title_line_color: nil, title_line_width: nil, title_line_dash: nil, title_rotation: nil, auto_title_deleted: nil, cat_ref: nil, val_ref: nil, series: nil, legend: nil, data_labels: nil, cat_axis_title: nil, val_axis_title: nil, cat_axis_title_font: nil, cat_axis_title_fill: nil, cat_axis_title_no_fill: nil, cat_axis_title_line_color: nil, cat_axis_title_line_width: nil, cat_axis_title_line_dash: nil, cat_axis_title_rotation: nil, val_axis_title_font: nil, val_axis_title_fill: nil, val_axis_title_no_fill: nil, val_axis_title_line_color: nil, val_axis_title_line_width: nil, val_axis_title_line_dash: nil, val_axis_title_rotation: nil, cat_axis_tick_lbl_pos: nil, val_axis_tick_lbl_pos: nil, cat_axis_major_gridlines: nil, val_axis_major_gridlines: nil, cat_axis_minor_gridlines: nil, val_axis_minor_gridlines: nil, cat_axis_delete: nil, val_axis_delete: nil, cat_axis_orientation: nil, val_axis_orientation: nil, cat_axis_num_fmt: nil, val_axis_num_fmt: nil, cat_axis_major_tick_mark: nil, cat_axis_minor_tick_mark: nil, val_axis_major_tick_mark: nil, val_axis_minor_tick_mark: nil, cat_axis_crosses: nil, val_axis_crosses: nil, cat_axis_crosses_at: nil, val_axis_crosses_at: nil, cat_axis_tick_lbl_skip: nil, cat_axis_tick_mark_skip: nil, cat_axis_lbl_offset: nil, cat_axis_auto: nil, cat_axis_lbl_algn: nil, cat_axis_no_multi_lvl_lbl: nil, val_axis_cross_between: nil, val_axis_major_unit: nil, val_axis_minor_unit: nil, cat_axis_scaling_max: nil, cat_axis_scaling_min: nil, val_axis_scaling_max: nil, val_axis_scaling_min: nil, cat_axis_log_base: nil, val_axis_log_base: nil, val_axis_disp_units: nil, gap_width: nil, gap_depth: nil, overlap: nil, first_slice_ang: nil, hole_size: nil, smooth: nil, marker: nil, scatter_style: nil, radar_style: nil, bar_shape: nil, bubble_3d: nil, bubble_scale: nil, show_neg_bubbles: nil, size_represents: nil, wireframe: nil, grouping: nil, bar_dir: nil, vary_colors: nil, style: nil, rounded_corners: nil, view_3d: nil, cat_axis_pos: nil, val_axis_pos: nil, name: nil, description: nil, frame_title: nil, frame_hidden: nil, frame_macro: nil, frame_no_grp: nil, from_col: 0, from_row: 0, to_col: 10, to_row: 15, from_col_off: nil, from_row_off: nil, to_col_off: nil, to_row_off: nil, edit_as: nil, published: nil, locks_with_sheet: nil, prints_with_sheet: nil, plot_vis_only: nil, disp_blanks_as: nil, show_d_lbls_over_max: nil, data_table: nil, plot_area_fill: nil, plot_area_no_fill: nil, plot_area_line_color: nil, plot_area_line_width: nil, plot_area_line_dash: nil, plot_area_layout: nil, floor: nil, side_wall: nil, back_wall: nil, drop_lines: nil, hi_low_lines: nil, ser_lines: nil, up_down_bars: nil, band_fmts: nil, of_pie_type: nil, split_type: nil, split_pos: nil, cust_split: nil, second_pie_size: nil, cat_axis_label_rotation: nil, val_axis_label_rotation: nil, cat_axis_font: nil, val_axis_font: nil, cat_axis_line_color: nil, cat_axis_line_width: nil, cat_axis_line_dash: nil, val_axis_line_color: nil, val_axis_line_width: nil, val_axis_line_dash: nil, cat_axis_fill: nil, cat_axis_no_fill: nil, val_axis_fill: nil, val_axis_no_fill: nil, cat_axis_type: nil, cat_axis_base_time_unit: nil, cat_axis_major_time_unit: nil, cat_axis_minor_time_unit: nil, cat_axis_major_unit: nil, cat_axis_minor_unit: nil, chart_fill: nil, chart_no_fill: nil, chart_line_color: nil, chart_line_width: nil, chart_line_dash: nil, protection: nil, print_settings: nil, chart_font: nil, sheet: nil)
1324
+ # : (?type: ::Symbol, ?title: untyped?, ?title_overlay: untyped?, ?title_font: untyped?, ?title_fill_color: untyped?, ?title_no_fill: untyped?, ?title_line_color: untyped?, ?title_line_width: untyped?, ?title_line_dash: untyped?, ?title_rotation: untyped?, ?auto_title_deleted: untyped?, ?cat_ref: untyped?, ?val_ref: untyped?, ?series: untyped?, ?legend: untyped?, ?data_labels: untyped?, ?cat_axis_title: untyped?, ?val_axis_title: untyped?, ?cat_axis_title_font: untyped?, ?cat_axis_title_fill: untyped?, ?cat_axis_title_no_fill: untyped?, ?cat_axis_title_line_color: untyped?, ?cat_axis_title_line_width: untyped?, ?cat_axis_title_line_dash: untyped?, ?cat_axis_title_rotation: untyped?, ?val_axis_title_font: untyped?, ?val_axis_title_fill: untyped?, ?val_axis_title_no_fill: untyped?, ?val_axis_title_line_color: untyped?, ?val_axis_title_line_width: untyped?, ?val_axis_title_line_dash: untyped?, ?val_axis_title_rotation: untyped?, ?cat_axis_tick_lbl_pos: untyped?, ?val_axis_tick_lbl_pos: untyped?, ?cat_axis_major_gridlines: untyped?, ?val_axis_major_gridlines: untyped?, ?cat_axis_minor_gridlines: untyped?, ?val_axis_minor_gridlines: untyped?, ?cat_axis_delete: untyped?, ?val_axis_delete: untyped?, ?cat_axis_orientation: untyped?, ?val_axis_orientation: untyped?, ?cat_axis_num_fmt: untyped?, ?val_axis_num_fmt: untyped?, ?cat_axis_major_tick_mark: untyped?, ?cat_axis_minor_tick_mark: untyped?, ?val_axis_major_tick_mark: untyped?, ?val_axis_minor_tick_mark: untyped?, ?cat_axis_crosses: untyped?, ?val_axis_crosses: untyped?, ?cat_axis_crosses_at: untyped?, ?val_axis_crosses_at: untyped?, ?cat_axis_tick_lbl_skip: untyped?, ?cat_axis_tick_mark_skip: untyped?, ?cat_axis_lbl_offset: untyped?, ?cat_axis_auto: untyped?, ?cat_axis_lbl_algn: untyped?, ?cat_axis_no_multi_lvl_lbl: untyped?, ?val_axis_cross_between: untyped?, ?val_axis_major_unit: untyped?, ?val_axis_minor_unit: untyped?, ?cat_axis_scaling_max: untyped?, ?cat_axis_scaling_min: untyped?, ?val_axis_scaling_max: untyped?, ?val_axis_scaling_min: untyped?, ?cat_axis_log_base: untyped?, ?val_axis_log_base: untyped?, ?val_axis_disp_units: untyped?, ?gap_width: untyped?, ?gap_depth: untyped?, ?overlap: untyped?, ?first_slice_ang: untyped?, ?hole_size: untyped?, ?smooth: untyped?, ?marker: untyped?, ?scatter_style: untyped?, ?radar_style: untyped?, ?bar_shape: untyped?, ?bubble_3d: untyped?, ?bubble_scale: untyped?, ?show_neg_bubbles: untyped?, ?size_represents: untyped?, ?wireframe: untyped?, ?grouping: untyped?, ?bar_dir: untyped?, ?vary_colors: untyped?, ?style: untyped?, ?rounded_corners: untyped?, ?view_3d: untyped?, ?cat_axis_pos: untyped?, ?val_axis_pos: untyped?, ?name: untyped?, ?description: untyped?, ?frame_title: untyped?, ?frame_hidden: untyped?, ?frame_macro: untyped?, ?frame_no_grp: untyped?, ?from_col: ::Integer, ?from_row: ::Integer, ?to_col: ::Integer, ?to_row: ::Integer, ?from_col_off: untyped?, ?from_row_off: untyped?, ?to_col_off: untyped?, ?to_row_off: untyped?, ?edit_as: untyped?, ?published: untyped?, ?locks_with_sheet: untyped?, ?prints_with_sheet: untyped?, ?plot_vis_only: untyped?, ?disp_blanks_as: untyped?, ?show_d_lbls_over_max: untyped?, ?data_table: untyped?, ?plot_area_fill: untyped?, ?plot_area_no_fill: untyped?, ?plot_area_line_color: untyped?, ?plot_area_line_width: untyped?, ?plot_area_line_dash: untyped?, ?plot_area_layout: untyped?, ?floor: untyped?, ?side_wall: untyped?, ?back_wall: untyped?, ?drop_lines: untyped?, ?hi_low_lines: untyped?, ?ser_lines: untyped?, ?up_down_bars: untyped?, ?band_fmts: untyped?, ?of_pie_type: untyped?, ?split_type: untyped?, ?split_pos: untyped?, ?cust_split: untyped?, ?second_pie_size: untyped?, ?cat_axis_label_rotation: untyped?, ?val_axis_label_rotation: untyped?, ?cat_axis_font: untyped?, ?val_axis_font: untyped?, ?cat_axis_line_color: untyped?, ?cat_axis_line_width: untyped?, ?cat_axis_line_dash: untyped?, ?val_axis_line_color: untyped?, ?val_axis_line_width: untyped?, ?val_axis_line_dash: untyped?, ?cat_axis_fill: untyped?, ?cat_axis_no_fill: untyped?, ?val_axis_fill: untyped?, ?val_axis_no_fill: untyped?, ?cat_axis_type: untyped?, ?cat_axis_base_time_unit: untyped?, ?cat_axis_major_time_unit: untyped?, ?cat_axis_minor_time_unit: untyped?, ?cat_axis_major_unit: untyped?, ?cat_axis_minor_unit: untyped?, ?chart_fill: untyped?, ?chart_no_fill: untyped?, ?chart_line_color: untyped?, ?chart_line_width: untyped?, ?chart_line_dash: untyped?, ?protection: untyped?, ?print_settings: untyped?, ?chart_font: untyped?, ?secondary_value_axis: untyped?, ?secondary_axis: untyped?, ?category_axis: untyped?, ?value_axis: untyped?, ?sheet: untyped?, **untyped extra_options) -> untyped
1325
+ def add_chart(type: :bar, title: nil, title_overlay: nil, title_font: nil, title_fill_color: nil, title_no_fill: nil, title_line_color: nil, title_line_width: nil, title_line_dash: nil, title_rotation: nil, auto_title_deleted: nil, cat_ref: nil, val_ref: nil, series: nil, legend: nil, data_labels: nil, cat_axis_title: nil, val_axis_title: nil, cat_axis_title_font: nil, cat_axis_title_fill: nil, cat_axis_title_no_fill: nil, cat_axis_title_line_color: nil, cat_axis_title_line_width: nil, cat_axis_title_line_dash: nil, cat_axis_title_rotation: nil, val_axis_title_font: nil, val_axis_title_fill: nil, val_axis_title_no_fill: nil, val_axis_title_line_color: nil, val_axis_title_line_width: nil, val_axis_title_line_dash: nil, val_axis_title_rotation: nil, cat_axis_tick_lbl_pos: nil, val_axis_tick_lbl_pos: nil, cat_axis_major_gridlines: nil, val_axis_major_gridlines: nil, cat_axis_minor_gridlines: nil, val_axis_minor_gridlines: nil, cat_axis_delete: nil, val_axis_delete: nil, cat_axis_orientation: nil, val_axis_orientation: nil, cat_axis_num_fmt: nil, val_axis_num_fmt: nil, cat_axis_major_tick_mark: nil, cat_axis_minor_tick_mark: nil, val_axis_major_tick_mark: nil, val_axis_minor_tick_mark: nil, cat_axis_crosses: nil, val_axis_crosses: nil, cat_axis_crosses_at: nil, val_axis_crosses_at: nil, cat_axis_tick_lbl_skip: nil, cat_axis_tick_mark_skip: nil, cat_axis_lbl_offset: nil, cat_axis_auto: nil, cat_axis_lbl_algn: nil, cat_axis_no_multi_lvl_lbl: nil, val_axis_cross_between: nil, val_axis_major_unit: nil, val_axis_minor_unit: nil, cat_axis_scaling_max: nil, cat_axis_scaling_min: nil, val_axis_scaling_max: nil, val_axis_scaling_min: nil, cat_axis_log_base: nil, val_axis_log_base: nil, val_axis_disp_units: nil, gap_width: nil, gap_depth: nil, overlap: nil, first_slice_ang: nil, hole_size: nil, smooth: nil, marker: nil, scatter_style: nil, radar_style: nil, bar_shape: nil, bubble_3d: nil, bubble_scale: nil, show_neg_bubbles: nil, size_represents: nil, wireframe: nil, grouping: nil, bar_dir: nil, vary_colors: nil, style: nil, rounded_corners: nil, view_3d: nil, cat_axis_pos: nil, val_axis_pos: nil, name: nil, description: nil, frame_title: nil, frame_hidden: nil, frame_macro: nil, frame_no_grp: nil, from_col: 0, from_row: 0, to_col: 10, to_row: 15, from_col_off: nil, from_row_off: nil, to_col_off: nil, to_row_off: nil, edit_as: nil, published: nil, locks_with_sheet: nil, prints_with_sheet: nil, plot_vis_only: nil, disp_blanks_as: nil, show_d_lbls_over_max: nil, data_table: nil, plot_area_fill: nil, plot_area_no_fill: nil, plot_area_line_color: nil, plot_area_line_width: nil, plot_area_line_dash: nil, plot_area_layout: nil, floor: nil, side_wall: nil, back_wall: nil, drop_lines: nil, hi_low_lines: nil, ser_lines: nil, up_down_bars: nil, band_fmts: nil, of_pie_type: nil, split_type: nil, split_pos: nil, cust_split: nil, second_pie_size: nil, cat_axis_label_rotation: nil, val_axis_label_rotation: nil, cat_axis_font: nil, val_axis_font: nil, cat_axis_line_color: nil, cat_axis_line_width: nil, cat_axis_line_dash: nil, val_axis_line_color: nil, val_axis_line_width: nil, val_axis_line_dash: nil, cat_axis_fill: nil, cat_axis_no_fill: nil, val_axis_fill: nil, val_axis_no_fill: nil, cat_axis_type: nil, cat_axis_base_time_unit: nil, cat_axis_major_time_unit: nil, cat_axis_minor_time_unit: nil, cat_axis_major_unit: nil, cat_axis_minor_unit: nil, chart_fill: nil, chart_no_fill: nil, chart_line_color: nil, chart_line_width: nil, chart_line_dash: nil, protection: nil, print_settings: nil, chart_font: nil, secondary_value_axis: nil, secondary_axis: nil, category_axis: nil, value_axis: nil, sheet: nil, **extra_options)
1283
1326
  sheet_name = sheet || @sheet_order.first
1284
1327
  raise ArgumentError, "unknown sheet: #{sheet_name}" unless @charts_data.key?(sheet_name)
1285
1328
 
@@ -1438,6 +1481,73 @@ module Xlsxrb
1438
1481
  chart[:protection] = protection if protection
1439
1482
  chart[:print_settings] = print_settings if print_settings
1440
1483
  chart[:chart_font] = chart_font if chart_font
1484
+ chart[:category_axis] = category_axis if category_axis
1485
+ chart[:value_axis] = value_axis if value_axis
1486
+ chart[:secondary_value_axis] = secondary_value_axis || secondary_axis if secondary_value_axis || secondary_axis
1487
+ chart.merge!(extra_options) if extra_options && !extra_options.empty?
1488
+
1489
+ if chart[:category_axis].is_a?(Hash)
1490
+ ca = chart[:category_axis]
1491
+ chart[:cat_axis_title] ||= ca[:title]
1492
+ chart[:cat_axis_num_fmt] ||= ca[:num_fmt]
1493
+ chart[:cat_axis_pos] ||= ca[:pos]
1494
+ chart[:cat_axis_major_gridlines] = ca[:major_gridlines] if chart[:cat_axis_major_gridlines].nil? && !ca[:major_gridlines].nil?
1495
+ chart[:cat_axis_minor_gridlines] = ca[:minor_gridlines] if chart[:cat_axis_minor_gridlines].nil? && !ca[:minor_gridlines].nil?
1496
+ chart[:cat_axis_font] ||= ca[:font]
1497
+ chart[:cat_axis_line_color] ||= ca[:line_color]
1498
+ chart[:cat_axis_line_width] ||= ca[:line_width]
1499
+ chart[:cat_axis_line_dash] ||= ca[:line_dash]
1500
+ chart[:cat_axis_scaling_min] ||= ca[:min]
1501
+ chart[:cat_axis_scaling_max] ||= ca[:max]
1502
+ chart[:cat_axis_label_rotation] ||= ca[:label_rotation] || ca[:rotation]
1503
+ end
1504
+
1505
+ if chart[:value_axis].is_a?(Hash)
1506
+ va = chart[:value_axis]
1507
+ chart[:val_axis_title] ||= va[:title]
1508
+ chart[:val_axis_num_fmt] ||= va[:num_fmt]
1509
+ chart[:val_axis_pos] ||= va[:pos]
1510
+ chart[:val_axis_major_gridlines] = va[:major_gridlines] if chart[:val_axis_major_gridlines].nil? && !va[:major_gridlines].nil?
1511
+ chart[:val_axis_minor_gridlines] = va[:minor_gridlines] if chart[:val_axis_minor_gridlines].nil? && !va[:minor_gridlines].nil?
1512
+ chart[:val_axis_font] ||= va[:font]
1513
+ chart[:val_axis_line_color] ||= va[:line_color]
1514
+ chart[:val_axis_line_width] ||= va[:line_width]
1515
+ chart[:val_axis_line_dash] ||= va[:line_dash]
1516
+ chart[:val_axis_scaling_min] ||= va[:min]
1517
+ chart[:val_axis_scaling_max] ||= va[:max]
1518
+ chart[:val_axis_major_unit] ||= va[:major_unit]
1519
+ chart[:val_axis_minor_unit] ||= va[:minor_unit]
1520
+ chart[:val_axis_label_rotation] ||= va[:label_rotation] || va[:rotation]
1521
+ end
1522
+
1523
+ if chart[:secondary_value_axis].is_a?(Hash)
1524
+ sva = chart[:secondary_value_axis]
1525
+ chart[:sec_val_axis_title] ||= sva[:title]
1526
+ chart[:sec_val_axis_num_fmt] ||= sva[:num_fmt]
1527
+ chart[:sec_val_axis_pos] ||= sva[:pos] || "r"
1528
+ chart[:sec_val_axis_major_gridlines] = sva[:major_gridlines] unless sva[:major_gridlines].nil?
1529
+ chart[:sec_val_axis_minor_gridlines] = sva[:minor_gridlines] unless sva[:minor_gridlines].nil?
1530
+ chart[:sec_val_axis_font] ||= sva[:font]
1531
+ chart[:sec_val_axis_line_color] ||= sva[:line_color]
1532
+ chart[:sec_val_axis_line_width] ||= sva[:line_width]
1533
+ chart[:sec_val_axis_line_dash] ||= sva[:line_dash]
1534
+ chart[:sec_val_axis_scaling_min] ||= sva[:min]
1535
+ chart[:sec_val_axis_scaling_max] ||= sva[:max]
1536
+ chart[:sec_val_axis_major_unit] ||= sva[:major_unit]
1537
+ chart[:sec_val_axis_minor_unit] ||= sva[:minor_unit]
1538
+ chart[:sec_val_axis_label_rotation] ||= sva[:label_rotation] || sva[:rotation]
1539
+ chart[:sec_val_axis_crosses] ||= sva[:crosses] || "max"
1540
+ chart[:sec_val_axis_crosses_at] ||= sva[:crosses_at]
1541
+ chart[:sec_val_axis_cross_between] ||= sva[:cross_between]
1542
+ chart[:sec_val_axis_delete] = sva[:delete] unless sva[:delete].nil?
1543
+ chart[:sec_val_axis_orientation] ||= sva[:orientation]
1544
+ chart[:sec_val_axis_log_base] ||= sva[:log_base]
1545
+ chart[:sec_val_axis_disp_units] ||= sva[:disp_units]
1546
+ chart[:sec_val_axis_major_tick_mark] ||= sva[:major_tick_mark]
1547
+ chart[:sec_val_axis_minor_tick_mark] ||= sva[:minor_tick_mark]
1548
+ chart[:sec_val_axis_tick_lbl_pos] ||= sva[:tick_lbl_pos]
1549
+ end
1550
+
1441
1551
  @charts_data[sheet_name] << chart
1442
1552
  end
1443
1553
 
@@ -1571,6 +1681,81 @@ module Xlsxrb
1571
1681
  @comments_data[sheet_name] || []
1572
1682
  end
1573
1683
 
1684
+ # Adds a threaded comment to a cell in the given sheet.
1685
+ # : (String | Integer cell_address, String text, ?author: untyped, ?timestamp: untyped, ?id: String?, ?parent_id: String?, ?done: bool?, ?user_id: String?, ?provider_id: String?, ?sheet: untyped?) -> Hash[Symbol, untyped]
1686
+ def add_threaded_comment(cell_address, text, author: "Author", timestamp: nil, id: nil, parent_id: nil, done: false, user_id: nil, provider_id: nil, sheet: nil)
1687
+ sheet_name = sheet || @sheet_order.first
1688
+ raise ArgumentError, "unknown sheet: #{sheet_name}" unless @threaded_comments_data.key?(sheet_name)
1689
+
1690
+ person = if author.is_a?(Hash)
1691
+ add_person(author[:display_name] || author[:name] || "Author", id: author[:id], user_id: author[:user_id], provider_id: author[:provider_id])
1692
+ else
1693
+ add_person(author, user_id: user_id, provider_id: provider_id)
1694
+ end
1695
+
1696
+ dt = case timestamp
1697
+ when Time, DateTime
1698
+ timestamp.to_time.utc.strftime("%Y-%m-%dT%H:%M:%SZ")
1699
+ when String
1700
+ timestamp
1701
+ else
1702
+ Time.now.utc.strftime("%Y-%m-%dT%H:%M:%SZ")
1703
+ end
1704
+
1705
+ comment_id = format_guid(id)
1706
+ parent_guid = parent_id ? format_guid(parent_id) : nil
1707
+
1708
+ entry = {
1709
+ cell: cell_address.to_s.upcase,
1710
+ ref: cell_address.to_s.upcase,
1711
+ text: text.to_s,
1712
+ author: person[:display_name],
1713
+ person_id: person[:id],
1714
+ id: comment_id,
1715
+ parent_id: parent_guid,
1716
+ timestamp: dt,
1717
+ dt: dt,
1718
+ done: done ? true : false
1719
+ }.compact
1720
+ @threaded_comments_data[sheet_name] << entry
1721
+ entry
1722
+ end
1723
+ alias threaded_comment add_threaded_comment
1724
+
1725
+ # Returns threaded comments for the first (or given) sheet.
1726
+ # : (?sheet: untyped?) -> Array[Hash[Symbol, untyped]]
1727
+ def threaded_comments(sheet: nil)
1728
+ sheet_name = sheet || @sheet_order.first
1729
+ @threaded_comments_data[sheet_name] || []
1730
+ end
1731
+
1732
+ # Registers a person in the workbook person list.
1733
+ # : (String name, ?id: String?, ?user_id: String?, ?provider_id: String?) -> Hash[Symbol, untyped]
1734
+ def add_person(name, id: nil, user_id: nil, provider_id: nil)
1735
+ person_name = name.to_s
1736
+ person_id = id ? format_guid(id) : nil
1737
+ existing = @persons_data.find do |p|
1738
+ (person_id && p[:id] == person_id) || p[:display_name] == person_name
1739
+ end
1740
+ return existing if existing
1741
+
1742
+ person = {
1743
+ id: person_id || format_guid(nil),
1744
+ display_name: person_name,
1745
+ user_id: user_id || "AD:#{person_name}",
1746
+ provider_id: provider_id || "AD"
1747
+ }
1748
+ @persons_data << person
1749
+ person
1750
+ end
1751
+
1752
+ # Returns the workbook person list.
1753
+ # : () -> Array[Hash[Symbol, untyped]]
1754
+ def persons
1755
+ @persons_data
1756
+ end
1757
+ alias person_list persons
1758
+
1574
1759
  # Adds a pivot table to the given sheet.
1575
1760
  # source_ref: data source range (e.g. "Sheet1!A1:C4").
1576
1761
  # row_fields: array of 0-based field indices for row axis.
@@ -1717,13 +1902,16 @@ module Xlsxrb
1717
1902
  sheet_charts = @charts_data[sheet_name] || []
1718
1903
  sheet_shapes = @shapes_data[sheet_name] || []
1719
1904
  sheet_comments = @comments_data[sheet_name] || []
1905
+ sheet_threaded_comments = @threaded_comments_data[sheet_name] || []
1720
1906
  sheet_pivots = @pivot_tables_data[sheet_name] || []
1721
1907
  has_drawing = sheet_images.any? || sheet_charts.any? || sheet_shapes.any?
1722
1908
  has_comments = sheet_comments.any?
1909
+ has_tc = sheet_threaded_comments.any?
1723
1910
 
1724
1911
  # Pre-increment counters so rels reference correct paths.
1725
1912
  sheet_drawing_idx = has_drawing ? (@drawing_count += 1) : nil
1726
1913
  sheet_comment_idx = has_comments ? (@comment_count += 1) : nil
1914
+ sheet_tc_idx = has_tc ? (@threaded_comment_count += 1) : nil
1727
1915
  sheet_pivot_start = @pivot_table_count
1728
1916
  sheet_cache_start = @pivot_cache_count
1729
1917
  sheet_pivots.each do
@@ -1736,7 +1924,8 @@ module Xlsxrb
1736
1924
  sheet_rels_parts = build_sheet_rels_parts_v2(
1737
1925
  sheet_name, sheet_tables, table_index,
1738
1926
  sheet_drawing_idx, sheet_comment_idx,
1739
- sheet_pivot_start, sheet_pivots.size
1927
+ sheet_pivot_start, sheet_pivots.size,
1928
+ sheet_tc_idx
1740
1929
  )
1741
1930
 
1742
1931
  # Calculate the legacyDrawing rId if comments exist.
@@ -1805,6 +1994,9 @@ module Xlsxrb
1805
1994
  entries["xl/drawings/vmlDrawing#{sheet_comment_idx}.vml"] = generate_vml_drawing_xml(sheet_comments)
1806
1995
  end
1807
1996
 
1997
+ # Generate threaded comments XML.
1998
+ entries["xl/threadedComments/threadedComment#{sheet_tc_idx}.xml"] = generate_threaded_comments_xml(sheet_threaded_comments) if has_tc
1999
+
1808
2000
  # Generate pivot table + cache entries.
1809
2001
  sheet_pivots.each_with_index do |pt, pi|
1810
2002
  cache_idx = sheet_cache_start + pi + 1
@@ -1836,6 +2028,9 @@ module Xlsxrb
1836
2028
  entries["xl/externalLinks/_rels/externalLink#{link_num}.xml.rels"] = generate_external_link_rels(el)
1837
2029
  end
1838
2030
 
2031
+ # Generate persons entry if any persons registered.
2032
+ entries["xl/persons/person.xml"] = generate_persons_xml(@persons_data) if @persons_data.any?
2033
+
1839
2034
  # Generate workbook rels (needs to know pivot cache count).
1840
2035
  entries["xl/_rels/workbook.xml.rels"] = generate_workbook_rels(has_calc_chain: entries.key?("xl/calcChain.xml"))
1841
2036
 
@@ -1866,6 +2061,8 @@ module Xlsxrb
1866
2061
  ends_with: "endsWith",
1867
2062
  contains_blanks: "containsBlanks",
1868
2063
  not_contains_blanks: "notContainsBlanks",
2064
+ contains_errors: "containsErrors",
2065
+ not_contains_errors: "notContainsErrors",
1869
2066
  time_period: "timePeriod"
1870
2067
  }.freeze
1871
2068
 
@@ -1879,7 +2076,8 @@ module Xlsxrb
1879
2076
  pie3d: "pie3DChart", area3d: "area3DChart", surface: "surfaceChart",
1880
2077
  surface3d: "surface3DChart",
1881
2078
  stock: "stockChart", bubble: "bubbleChart",
1882
- of_pie: "ofPieChart"
2079
+ of_pie: "ofPieChart",
2080
+ combo: "comboChart", column: "barChart"
1883
2081
  }.freeze
1884
2082
 
1885
2083
  NO_AXIS_CHARTS = %w[pieChart doughnutChart pie3DChart ofPieChart].freeze
@@ -1959,6 +2157,14 @@ module Xlsxrb
1959
2157
  overrides["/xl/comments#{c}.xml"] = "application/vnd.openxmlformats-officedocument.spreadsheetml.comments+xml"
1960
2158
  end
1961
2159
 
2160
+ # Threaded comments.
2161
+ (1..@threaded_comment_count.to_i).each do |tc|
2162
+ overrides["/xl/threadedComments/threadedComment#{tc}.xml"] = "application/vnd.ms-excel.threadedcomments+xml"
2163
+ end
2164
+
2165
+ # Persons.
2166
+ overrides["/xl/persons/person.xml"] = "application/vnd.ms-excel.person+xml" if @persons_data.any?
2167
+
1962
2168
  # Pivot tables and cache.
1963
2169
  (1..@pivot_table_count.to_i).each do |p|
1964
2170
  overrides["/xl/pivotTables/pivotTable#{p}.xml"] = "application/vnd.openxmlformats-officedocument.spreadsheetml.pivotTable+xml"
@@ -2247,6 +2453,10 @@ module Xlsxrb
2247
2453
  parts << %(<Relationship Id="rId#{next_rid}" Type="#{DOC_REL_NS}/calcChain" Target="calcChain.xml"/>)
2248
2454
  next_rid += 1
2249
2455
  end
2456
+ if @persons_data.any?
2457
+ parts << %(<Relationship Id="rId#{next_rid}" Type="http://schemas.microsoft.com/office/2017/10/relationships/person" Target="persons/person.xml"/>)
2458
+ next_rid += 1
2459
+ end
2250
2460
  parts << "</Relationships>"
2251
2461
  parts.join
2252
2462
  end
@@ -2292,8 +2502,17 @@ module Xlsxrb
2292
2502
  sp_attrs << 'published="0"' if sheet_props[:published] == false
2293
2503
  sp_attrs << 'enableFormatConditionsCalculation="0"' if sheet_props[:enable_format_conditions_calculation] == false
2294
2504
  sp_children = []
2295
- if sheet_props[:tab_color]
2296
- sp_children << %(<tabColor rgb="#{sheet_props[:tab_color]}"/>)
2505
+ tc = sheet_props[:tab_color]
2506
+ if tc.is_a?(Hash)
2507
+ tc_attrs = []
2508
+ tc_attrs << 'auto="1"' if tc[:auto]
2509
+ tc_attrs << %(indexed="#{tc[:indexed]}") if tc[:indexed]
2510
+ tc_attrs << %(rgb="#{tc[:rgb].to_s.delete_prefix("#")}") if tc[:rgb]
2511
+ tc_attrs << %(theme="#{tc[:theme]}") if tc[:theme]
2512
+ tc_attrs << %(tint="#{tc[:tint]}") if tc[:tint]
2513
+ sp_children << "<tabColor #{tc_attrs.join(" ")}/>" unless tc_attrs.empty?
2514
+ elsif tc
2515
+ sp_children << %(<tabColor rgb="#{tc.to_s.delete_prefix("#")}"/>)
2297
2516
  elsif sheet_props[:tab_color_theme]
2298
2517
  tc_attrs = [%(theme="#{sheet_props[:tab_color_theme]}")]
2299
2518
  tc_attrs << %(tint="#{sheet_props[:tab_color_tint]}") if sheet_props[:tab_color_tint]
@@ -2920,24 +3139,27 @@ module Xlsxrb
2920
3139
  table_attrs << %( tableType="#{xml_escape(tbl[:table_type])}") if tbl[:table_type]
2921
3140
  parts = [
2922
3141
  XML_HEADER,
2923
- "<table #{table_attrs}>",
2924
- %(<autoFilter ref="#{tbl[:ref]}"/>),
2925
- %(<tableColumns count="#{tbl[:columns].size}">)
3142
+ "<table #{table_attrs}>"
2926
3143
  ]
3144
+ parts << %(<autoFilter ref="#{tbl[:ref]}"/>) unless tbl[:auto_filter] == false
3145
+ parts << %(<tableColumns count="#{tbl[:columns].size}">)
2927
3146
  tbl[:columns].each_with_index do |col, i|
2928
3147
  col_name = col.is_a?(Hash) ? col[:name] : col
2929
3148
  col_attrs = %(id="#{i + 1}" name="#{xml_escape(col_name)}")
2930
3149
  if col.is_a?(Hash)
2931
- col_attrs << %( totalsRowFunction="#{col[:totals_row_function]}") if col[:totals_row_function]
3150
+ calc_formula = col[:calculated_column_formula] || col[:data_formula] || col[:formula]
3151
+ tot_formula = col[:totals_row_formula] || col[:total_row_formula] || col[:total_formula]
3152
+ trf = col[:totals_row_function] || ("custom" if tot_formula)
3153
+ col_attrs << %( totalsRowFunction="#{trf}") if trf
2932
3154
  col_attrs << %( totalsRowLabel="#{xml_escape(col[:totals_row_label])}") if col[:totals_row_label]
2933
3155
  col_attrs << %( dataDxfId="#{col[:data_dxf_id]}") if col[:data_dxf_id]
2934
3156
  col_attrs << %( totalsRowDxfId="#{col[:totals_row_dxf_id]}") if col[:totals_row_dxf_id]
2935
3157
  col_attrs << %( headerRowDxfId="#{col[:header_row_dxf_id]}") if col[:header_row_dxf_id]
2936
3158
  col_attrs << %( dataCellStyle="#{xml_escape(col[:data_cell_style])}") if col[:data_cell_style]
2937
- if col[:calculated_column_formula] || col[:totals_row_formula]
3159
+ if calc_formula || tot_formula
2938
3160
  parts << "<tableColumn #{col_attrs}>"
2939
- parts << "<calculatedColumnFormula>#{xml_escape(col[:calculated_column_formula])}</calculatedColumnFormula>" if col[:calculated_column_formula]
2940
- parts << "<totalsRowFormula>#{xml_escape(col[:totals_row_formula])}</totalsRowFormula>" if col[:totals_row_formula]
3161
+ parts << "<calculatedColumnFormula>#{xml_escape(calc_formula.to_s)}</calculatedColumnFormula>" if calc_formula
3162
+ parts << "<totalsRowFormula>#{xml_escape(tot_formula.to_s)}</totalsRowFormula>" if tot_formula
2941
3163
  parts << "</tableColumn>"
2942
3164
  else
2943
3165
  parts << "<tableColumn #{col_attrs}/>"
@@ -2947,16 +3169,30 @@ module Xlsxrb
2947
3169
  end
2948
3170
  end
2949
3171
  parts << "</tableColumns>"
2950
- style = tbl[:style] || {}
3172
+ raw_style = tbl[:style]
3173
+ style = if raw_style.is_a?(Hash)
3174
+ raw_style
3175
+ elsif raw_style
3176
+ { name: raw_style.to_s }
3177
+ else
3178
+ {}
3179
+ end
2951
3180
  style_name = style[:name] || "TableStyleMedium2"
2952
- sfc = style[:show_first_column] ? "1" : "0"
2953
- slc = style[:show_last_column] ? "1" : "0"
2954
- srs = if style.key?(:show_row_stripes)
2955
- style[:show_row_stripes] ? "1" : "0"
2956
- else
2957
- "1"
2958
- end
2959
- scs = style[:show_column_stripes] ? "1" : "0"
3181
+ show_first_column = style.key?(:show_first_column) ? style[:show_first_column] : tbl[:show_first_column]
3182
+ show_last_column = style.key?(:show_last_column) ? style[:show_last_column] : tbl[:show_last_column]
3183
+ show_row_stripes = if style.key?(:show_row_stripes)
3184
+ style[:show_row_stripes]
3185
+ elsif tbl.key?(:show_row_stripes)
3186
+ tbl[:show_row_stripes]
3187
+ else
3188
+ true
3189
+ end
3190
+ show_column_stripes = style.key?(:show_column_stripes) ? style[:show_column_stripes] : tbl[:show_column_stripes]
3191
+
3192
+ sfc = show_first_column ? "1" : "0"
3193
+ slc = show_last_column ? "1" : "0"
3194
+ srs = show_row_stripes ? "1" : "0"
3195
+ scs = show_column_stripes ? "1" : "0"
2960
3196
  parts << %(<tableStyleInfo name="#{xml_escape(style_name)}" showFirstColumn="#{sfc}" showLastColumn="#{slc}" showRowStripes="#{srs}" showColumnStripes="#{scs}"/>)
2961
3197
  parts << "</table>"
2962
3198
  parts.join
@@ -2983,8 +3219,8 @@ module Xlsxrb
2983
3219
  parts.join
2984
3220
  end
2985
3221
 
2986
- # : (untyped sheet_name, untyped sheet_tables, untyped table_start_index, untyped drawing_idx, untyped comment_idx, untyped pivot_start, untyped pivot_count) -> untyped
2987
- def build_sheet_rels_parts_v2(sheet_name, sheet_tables, table_start_index, drawing_idx, comment_idx, pivot_start, pivot_count)
3222
+ # : (untyped sheet_name, untyped sheet_tables, untyped table_start_index, untyped drawing_idx, untyped comment_idx, untyped pivot_start, untyped pivot_count, ?untyped? threaded_comment_idx) -> untyped
3223
+ def build_sheet_rels_parts_v2(sheet_name, sheet_tables, table_start_index, drawing_idx, comment_idx, pivot_start, pivot_count, threaded_comment_idx = nil)
2988
3224
  rels = []
2989
3225
  @hyperlinks[sheet_name].each do |(_cell_ref, link)|
2990
3226
  next unless link[:url]
@@ -2998,6 +3234,7 @@ module Xlsxrb
2998
3234
  rels << { type: "#{DOC_REL_NS}/comments", target: "../comments#{comment_idx}.xml" }
2999
3235
  rels << { type: "#{DOC_REL_NS}/vmlDrawing", target: "../drawings/vmlDrawing#{comment_idx}.vml" }
3000
3236
  end
3237
+ rels << { type: "http://schemas.microsoft.com/office/2017/10/relationships/threadedComment", target: "../threadedComments/threadedComment#{threaded_comment_idx}.xml" } if threaded_comment_idx
3001
3238
  rels << { type: "#{DOC_REL_NS}/drawing", target: "../drawings/drawing#{drawing_idx}.xml" } if drawing_idx
3002
3239
  pivot_count.times do |i|
3003
3240
  pt_idx = pivot_start + i + 1