Class: Xlsxrb::StreamWriter

Inherits:
Object
  • Object
show all
Defined in:
lib/xlsxrb.rb

Overview

DSL context for Xlsxrb.generate streaming writes.

Instance Method Summary collapse

Constructor Details

#initialize(target) ⇒ StreamWriter

Returns a new instance of StreamWriter.



784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
# File 'lib/xlsxrb.rb', line 784

def initialize(target)
  @target = target
  @sst = []
  @sst_index = {}
  @sheets = []
  @current_sheet = nil
  @current_row_index = 0
  @tempfiles = []
  @current_tempfile = nil
  @current_row_writer = nil
  @current_columns = []
  @current_charts = []
  @current_hyperlinks = []
  @current_auto_filter = nil
  @current_filter_columns = {}
  @current_sort_state = nil
  @current_data_validations = []
  @current_conditional_formats = []
  @current_tables = []
  @current_comments = []
  @current_merge_cells = []
  @current_freeze_pane = nil
  @current_split_pane = nil
  @current_selection = nil
  @current_page_margins = nil
  @current_page_setup = {}
  @current_header_footer = {}
  @current_print_options = {}
  @current_sheet_protection = nil
  @current_images = []
  @current_shapes = []
  @current_sheet_properties = {}
  @current_sheet_view = {}
  @current_row_breaks = []
  @current_col_breaks = []
  @styles = {} # { style_name => StyleBuilder }
  @style_writer = Ooxml::Writer.new
  @style_name_to_id = {}
  # Workbook-level settings
  @defined_names = []
  @core_properties = {}
  @app_properties = {}
  @custom_properties = []
  @workbook_protection = nil
end

Instance Method Details

#add_chart(**options, &block) ⇒ Object

Add a chart to the current sheet.



926
927
928
929
930
931
932
933
934
935
936
# File 'lib/xlsxrb.rb', line 926

def add_chart(**options, &block)
  add_sheet if @current_sheet.nil?

  if block_given?
    builder = ChartBuilder.new
    block.call(builder)
    options = builder.options.merge(options)
  end

  @current_charts << options
end

#add_col_break(col_index) ⇒ Object



1127
1128
1129
1130
# File 'lib/xlsxrb.rb', line 1127

def add_col_break(col_index)
  add_sheet if @current_sheet.nil?
  @current_col_breaks << col_index
end

#add_comment(cell, text, author: "Author") ⇒ Object

--- Comments ---



1010
1011
1012
1013
# File 'lib/xlsxrb.rb', line 1010

def add_comment(cell, text, author: "Author")
  add_sheet if @current_sheet.nil?
  @current_comments << { cell: cell, text: text, author: author }
end

#add_conditional_format(sqref, **opts) ⇒ Object

--- Conditional Formatting ---



978
979
980
981
# File 'lib/xlsxrb.rb', line 978

def add_conditional_format(sqref, **opts)
  add_sheet if @current_sheet.nil?
  @current_conditional_formats << opts.merge(sqref: sqref)
end

#add_custom_property(name, value, type: :string) ⇒ Object

Add a custom document property.



1179
1180
1181
# File 'lib/xlsxrb.rb', line 1179

def add_custom_property(name, value, type: :string)
  @custom_properties << { name: name, value: value, type: type }
end

#add_data_validation(sqref, **opts) ⇒ Object

--- Data Validation ---



971
972
973
974
# File 'lib/xlsxrb.rb', line 971

def add_data_validation(sqref, **opts)
  add_sheet if @current_sheet.nil?
  @current_data_validations << opts.merge(sqref: sqref)
end

#add_defined_name(name, value, sheet: nil, hidden: false) ⇒ Object

Add a defined name.



1135
1136
1137
1138
1139
1140
1141
1142
# File 'lib/xlsxrb.rb', line 1135

def add_defined_name(name, value, sheet: nil, hidden: false)
  entry = { name: name, value: value, hidden: hidden }
  if sheet
    # local_sheet_id will be resolved at close time
    entry[:local_sheet_name] = sheet
  end
  @defined_names << entry
end

#add_filter_column(col_id, filter) ⇒ Object

rubocop:enable Naming/AccessorMethodName



959
960
961
962
# File 'lib/xlsxrb.rb', line 959

def add_filter_column(col_id, filter)
  add_sheet if @current_sheet.nil?
  @current_filter_columns[col_id] = filter
end

--- Hyperlinks ---



940
941
942
943
944
945
946
947
948
# File 'lib/xlsxrb.rb', line 940

def add_hyperlink(cell, url = nil, display: nil, tooltip: nil, location: nil)
  add_sheet if @current_sheet.nil?
  link = { cell: cell }
  link[:url] = url if url
  link[:display] = display if display
  link[:tooltip] = tooltip if tooltip
  link[:location] = location if location
  @current_hyperlinks << link
end

#add_image(file_data, ext: "png", from_col: 0, from_row: 0, to_col: 5, to_row: 10, **opts) ⇒ Object

--- Images ---



1091
1092
1093
1094
1095
1096
# File 'lib/xlsxrb.rb', line 1091

def add_image(file_data, ext: "png", from_col: 0, from_row: 0, to_col: 5, to_row: 10, **opts)
  add_sheet if @current_sheet.nil?
  img = { file_data: file_data, ext: ext, from_col: from_col, from_row: from_row, to_col: to_col, to_row: to_row }
  img.merge!(opts)
  @current_images << img
end

#add_pivot_table(source_ref, row_fields:, data_fields:, col_fields: [], dest_ref: "E1", name: nil, field_names: nil, items: nil) ⇒ Object

--- Pivot Tables ---



997
998
999
1000
1001
1002
1003
1004
1005
1006
# File 'lib/xlsxrb.rb', line 997

def add_pivot_table(source_ref, row_fields:, data_fields:, col_fields: [], dest_ref: "E1", name: nil, field_names: nil, items: nil)
  add_sheet if @current_sheet.nil?
  @current_pivot_tables ||= []
  @current_pivot_tables << {
    source_ref: source_ref, row_fields: row_fields,
    data_fields: data_fields, col_fields: col_fields,
    dest_ref: dest_ref, name: name,
    field_names: field_names, items: items
  }
end

#add_row(values, styles: nil, height: nil, hidden: false, custom_height: false, outline_level: nil) ⇒ Object

Add a row of values. values is an Array.

styles

Hash mapping column indices to style names, or Array of style names for each column



891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
# File 'lib/xlsxrb.rb', line 891

def add_row(values, styles: nil, height: nil, hidden: false, custom_height: false, outline_level: nil)
  add_sheet if @current_sheet.nil?

  row_index = @current_row_index
  @current_row_index += 1

  @current_cells ||= {}
  row_num = row_index + 1
  values.each_with_index do |val, col_idx|
    next if val.nil?

    addr = "#{Elements::Cell.column_letter(col_idx)}#{row_num}"
    @current_cells[addr] = val
  end

  attrs = nil
  if height || hidden || outline_level
    attrs = {}
    attrs[:height] = height if height
    attrs[:hidden] = true if hidden
    attrs[:custom_height] = custom_height || !height.nil?
    attrs[:outline_level] = outline_level if outline_level
  end

  @current_row_writer.write_row_values(row_index, values, styles: styles, style_map: @style_name_to_id, sst: @sst, sst_index: @sst_index, attrs: attrs)
end

#add_row_break(row_num) ⇒ Object

--- Row / Column Breaks ---



1122
1123
1124
1125
# File 'lib/xlsxrb.rb', line 1122

def add_row_break(row_num)
  add_sheet if @current_sheet.nil?
  @current_row_breaks << row_num
end

#add_shape(preset: "rect", text: nil, from_col: 0, from_row: 0, to_col: 5, to_row: 5, **opts) ⇒ Object

--- Shapes ---



1100
1101
1102
1103
1104
1105
1106
# File 'lib/xlsxrb.rb', line 1100

def add_shape(preset: "rect", text: nil, from_col: 0, from_row: 0, to_col: 5, to_row: 5, **opts)
  add_sheet if @current_sheet.nil?
  shape = { preset: preset, text: text, from_col: from_col, from_row: from_row, to_col: to_col, to_row: to_row }
  shape[:name] = opts.delete(:name) || "Shape #{@current_shapes.size + 1}"
  shape.merge!(opts)
  @current_shapes << shape
end

#add_sheet(name = nil) {|_self| ... } ⇒ Object

Start or switch to a named sheet.

Yields:

  • (_self)

Yield Parameters:



844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
# File 'lib/xlsxrb.rb', line 844

def add_sheet(name = nil)
  flush_current_sheet
  name ||= "Sheet#{@sheets.size + 1}"
  @current_sheet = name
  @current_row_index = 0
  @current_tempfile = Tempfile.new(["xlsxrb_rows", ".xml"])
  @current_tempfile.binmode
  @current_row_writer = Ooxml::WorksheetWriter.new(@current_tempfile)
  @current_row_writer.instance_variable_set(:@started, true)

  @current_columns = []
  @current_charts = []
  @current_hyperlinks = []
  @current_auto_filter = nil
  @current_filter_columns = {}
  @current_sort_state = nil
  @current_data_validations = []
  @current_conditional_formats = []
  @current_tables = []
  @current_pivot_tables = []
  @current_sparkline_groups = []
  @current_comments = []
  @current_merge_cells = []
  @current_freeze_pane = nil
  @current_split_pane = nil
  @current_selection = nil
  @current_page_margins = nil
  @current_page_setup = {}
  @current_header_footer = {}
  @current_print_options = {}
  @current_sheet_protection = nil
  @current_images = []
  @current_shapes = []
  @current_sheet_properties = {}
  @current_sheet_view = {}
  @current_row_breaks = []
  @current_col_breaks = []
  @current_cells = {}

  return unless block_given?

  yield self
  flush_current_sheet
end

#add_sparkline_group(sparklines:, type: nil, **opts) ⇒ Object

--- Sparklines ---



1017
1018
1019
1020
1021
1022
1023
# File 'lib/xlsxrb.rb', line 1017

def add_sparkline_group(sparklines:, type: nil, **opts)
  add_sheet if @current_sheet.nil?
  group = { sparklines: sparklines }
  group[:type] = type if type
  group.merge!(opts)
  @current_sparkline_groups << group
end

#add_style(name, **opts, &block) ⇒ Object

Define a named style that can be applied to cells.



831
832
833
834
835
836
837
838
839
840
841
# File 'lib/xlsxrb.rb', line 831

def add_style(name, **opts, &block)
  style_builder = StyleBuilder.new(name)
  style_builder.apply_options!(**opts) unless opts.empty?
  block.call(style_builder) if block_given?
  @styles[name] = style_builder

  # Register immediately
  @style_name_to_id[name] = style_builder.register_with(@style_writer)

  style_builder
end

#add_table(ref, columns:, name: nil, display_name: nil, style: nil, **opts) ⇒ Object

--- Tables ---



985
986
987
988
989
990
991
992
993
# File 'lib/xlsxrb.rb', line 985

def add_table(ref, columns:, name: nil, display_name: nil, style: nil, **opts)
  add_sheet if @current_sheet.nil?
  tbl = { ref: ref, columns: columns }
  tbl[:name] = name if name
  tbl[:display_name] = display_name if display_name
  tbl[:style] = style if style
  tbl.merge!(opts)
  @current_tables << tbl
end

#closeObject



1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
# File 'lib/xlsxrb.rb', line 1183

def close
  TRACER.in_span("StreamWriter#close") do
    flush_current_sheet

    styles_definition = {
      fonts: @style_writer.instance_variable_get(:@fonts).dup,
      fills: @style_writer.instance_variable_get(:@fills).dup,
      borders: @style_writer.instance_variable_get(:@borders).dup,
      xf_entries: @style_writer.instance_variable_get(:@xf_entries).dup,
      num_fmts: @style_writer.instance_variable_get(:@num_fmts).dup
    }

    resolved_names = resolve_defined_names(@defined_names, @sheets)

    Ooxml::WorkbookWriter.write(
      @target,
      sheets: @sheets,
      shared_strings: @sst,
      styles: styles_definition,
      defined_names: resolved_names.empty? ? nil : resolved_names,
      core_properties: @core_properties.empty? ? nil : @core_properties,
      app_properties: @app_properties.empty? ? nil : @app_properties,
      custom_properties: @custom_properties.empty? ? nil : @custom_properties,
      workbook_protection: @workbook_protection
    )
  end
ensure
  @tempfiles.each do |tmp|
    tmp.close
    tmp.unlink
  end
end

#merge_cells(range) ⇒ Object

--- Merge Cells ---



1027
1028
1029
1030
# File 'lib/xlsxrb.rb', line 1027

def merge_cells(range)
  add_sheet if @current_sheet.nil?
  @current_merge_cells << range
end

#set_app_property(name, value) ⇒ Object

Set an app document property.



1174
1175
1176
# File 'lib/xlsxrb.rb', line 1174

def set_app_property(name, value)
  @app_properties[name] = value
end

#set_auto_filter(range) ⇒ Object

rubocop:disable Naming/AccessorMethodName



953
954
955
956
# File 'lib/xlsxrb.rb', line 953

def set_auto_filter(range)
  add_sheet if @current_sheet.nil?
  @current_auto_filter = range
end

#set_column(index, width: nil, hidden: false, custom_width: false, outline_level: nil) ⇒ Object

Set column width for a 0-based column index.



919
920
921
922
923
# File 'lib/xlsxrb.rb', line 919

def set_column(index, width: nil, hidden: false, custom_width: false, outline_level: nil)
  add_sheet if @current_sheet.nil?

  @current_columns << { index: index, width: width, hidden: hidden, custom_width: custom_width || !width.nil?, outline_level: outline_level }
end

#set_core_property(name, value) ⇒ Object

Set a core document property.



1169
1170
1171
# File 'lib/xlsxrb.rb', line 1169

def set_core_property(name, value)
  @core_properties[name] = value
end

#set_freeze_pane(row: 0, col: 0) ⇒ Object

--- Freeze / Split Panes ---



1034
1035
1036
1037
# File 'lib/xlsxrb.rb', line 1034

def set_freeze_pane(row: 0, col: 0)
  add_sheet if @current_sheet.nil?
  @current_freeze_pane = { row: row, col: col }
end


1062
1063
1064
1065
# File 'lib/xlsxrb.rb', line 1062

def set_header_footer(**opts)
  add_sheet if @current_sheet.nil?
  @current_header_footer.merge!(opts)
end

#set_page_margins(left: nil, right: nil, top: nil, bottom: nil, header: nil, footer: nil) ⇒ Object

--- Page Setup / Margins / Print ---



1052
1053
1054
1055
# File 'lib/xlsxrb.rb', line 1052

def set_page_margins(left: nil, right: nil, top: nil, bottom: nil, header: nil, footer: nil)
  add_sheet if @current_sheet.nil?
  @current_page_margins = { left: left, right: right, top: top, bottom: bottom, header: header, footer: footer }.compact
end

#set_page_setup(**opts) ⇒ Object



1057
1058
1059
1060
# File 'lib/xlsxrb.rb', line 1057

def set_page_setup(**opts)
  add_sheet if @current_sheet.nil?
  @current_page_setup.merge!(opts)
end

#set_print_area(range, sheet: nil) ⇒ Object

Set the print area for the current or named sheet.



1145
1146
1147
1148
1149
1150
# File 'lib/xlsxrb.rb', line 1145

def set_print_area(range, sheet: nil)
  sheet_name = sheet || @current_sheet || "Sheet1"
  value = "'#{sheet_name}'!#{absolute_range(range)}"
  @defined_names.reject! { |dn| dn[:name] == "_xlnm.Print_Area" && dn[:local_sheet_name] == sheet_name }
  add_defined_name("_xlnm.Print_Area", value, sheet: sheet_name)
end

#set_print_option(name, value) ⇒ Object



1067
1068
1069
1070
# File 'lib/xlsxrb.rb', line 1067

def set_print_option(name, value)
  add_sheet if @current_sheet.nil?
  @current_print_options[name] = value
end

#set_print_titles(rows: nil, cols: nil, sheet: nil) ⇒ Object

Set print titles for the current or named sheet.



1153
1154
1155
1156
1157
1158
1159
1160
1161
# File 'lib/xlsxrb.rb', line 1153

def set_print_titles(rows: nil, cols: nil, sheet: nil)
  sheet_name = sheet || @current_sheet || "Sheet1"
  parts = []
  parts << "'#{sheet_name}'!$#{cols.sub(":", ":$")}" if cols
  parts << "'#{sheet_name}'!$#{rows.sub(":", ":$")}" if rows
  value = parts.join(",")
  @defined_names.reject! { |dn| dn[:name] == "_xlnm.Print_Titles" && dn[:local_sheet_name] == sheet_name }
  add_defined_name("_xlnm.Print_Titles", value, sheet: sheet_name)
end

#set_selection(active_cell, sqref: nil, pane: nil) ⇒ Object



1044
1045
1046
1047
1048
# File 'lib/xlsxrb.rb', line 1044

def set_selection(active_cell, sqref: nil, pane: nil)
  add_sheet if @current_sheet.nil?
  @current_selection = { active_cell: active_cell, sqref: sqref || active_cell }
  @current_selection[:pane] = pane if pane
end

#set_sheet_property(name, value) ⇒ Object

--- Sheet Properties ---



1110
1111
1112
1113
# File 'lib/xlsxrb.rb', line 1110

def set_sheet_property(name, value)
  add_sheet if @current_sheet.nil?
  @current_sheet_properties[name] = value
end

#set_sheet_protection(**opts) ⇒ Object

--- Sheet Protection ---



1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
# File 'lib/xlsxrb.rb', line 1074

def set_sheet_protection(**opts)
  add_sheet if @current_sheet.nil?
  normalized = opts.dup
  plain_password = normalized[:password]
  needs_hash = plain_password.is_a?(String) && !plain_password.empty? &&
               normalized[:algorithm_name].nil? && normalized[:hash_value].nil? &&
               normalized[:salt_value].nil? && normalized[:spin_count].nil? &&
               !plain_password.match?(/\A[0-9A-Fa-f]{4}\z/)
  if needs_hash
    normalized.delete(:password)
    normalized.merge!(Xlsxrb::Ooxml::Utils.hash_password(plain_password))
  end
  @current_sheet_protection = normalized
end

#set_sheet_view(name, value) ⇒ Object



1115
1116
1117
1118
# File 'lib/xlsxrb.rb', line 1115

def set_sheet_view(name, value)
  add_sheet if @current_sheet.nil?
  @current_sheet_view[name] = value
end

#set_sort_state(ref, sort_conditions, **opts) ⇒ Object



964
965
966
967
# File 'lib/xlsxrb.rb', line 964

def set_sort_state(ref, sort_conditions, **opts)
  add_sheet if @current_sheet.nil?
  @current_sort_state = { ref: ref, sort_conditions: sort_conditions }.merge(opts)
end

#set_split_pane(x_split: 0, y_split: 0, top_left_cell: nil) ⇒ Object



1039
1040
1041
1042
# File 'lib/xlsxrb.rb', line 1039

def set_split_pane(x_split: 0, y_split: 0, top_left_cell: nil)
  add_sheet if @current_sheet.nil?
  @current_split_pane = { x_split: x_split, y_split: y_split, top_left_cell: top_left_cell }
end

#set_workbook_protection(**opts) ⇒ Object

Set workbook protection.



1164
1165
1166
# File 'lib/xlsxrb.rb', line 1164

def set_workbook_protection(**opts)
  @workbook_protection = opts
end