Class: Xlsxrb::WorkbookBuilder

Inherits:
Object
  • Object
show all
Defined in:
lib/xlsxrb.rb,
sig/generated/xlsxrb.rbs

Overview

DSL context for Xlsxrb.build.

Instance Method Summary collapse

Constructor Details

#initialize(strict_excel_mode: true) ⇒ WorkbookBuilder

: (?strict_excel_mode: bool) -> void

Parameters:

  • strict_excel_mode: (Boolean) (defaults to: true)


714
715
716
717
718
719
720
721
722
723
724
# File 'lib/xlsxrb.rb', line 714

def initialize(strict_excel_mode: true)
  @strict_excel_mode = strict_excel_mode
  @sheets = []
  @sheet_builders = [] # Keep track of sheet builders for style processing
  @defined_names = []
  @core_properties = {}
  @app_properties = {}
  @custom_properties = []
  @workbook_protection = nil
  @workbook_properties = { update_links: "never" }
end

Instance Method Details

#absolute_range(range) ⇒ Object

: (untyped range) -> untyped

Parameters:

  • range (Object)

Returns:

  • (Object)


899
900
901
# File 'lib/xlsxrb.rb', line 899

def absolute_range(range)
  range.gsub(/([A-Z]+)(\d+)/, '$\1$\2')
end

#app_property(name, value) ⇒ void

This method returns an undefined value.

Set an app document property.

: (Symbol name, String | Integer | Time value) -> void

Parameters:

  • name (Symbol)

    The property name.

  • value (String, Integer, Time)

    The property value.



841
842
843
# File 'lib/xlsxrb.rb', line 841

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

#buildElements::Workbook

Builds and returns the in-memory Elements::Workbook.

: () -> Elements::Workbook

Returns:



874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
# File 'lib/xlsxrb.rb', line 874

def build
  raise ArgumentError, "Workbook must contain at least one sheet (Excel limitation)" if @strict_excel_mode && @sheets.empty?

  # Process styles from all sheets and collect style definitions
  processed_sheets, styles_definition = process_styles(@sheets)

  # Store workbook-level metadata in unmapped_data
  wb_meta = {}
  wb_meta[:defined_names] = resolve_defined_names(@defined_names, processed_sheets) unless @defined_names.empty?
  wb_meta[:core_properties] = @core_properties unless @core_properties.empty?
  wb_meta[:app_properties] = @app_properties unless @app_properties.empty?
  wb_meta[:custom_properties] = @custom_properties unless @custom_properties.empty?
  wb_meta[:workbook_protection] = @workbook_protection if @workbook_protection
  wb_meta[:workbook_properties] = @workbook_properties unless @workbook_properties.empty?

  Elements::Workbook.new(
    sheets: processed_sheets,
    styles: styles_definition,
    unmapped_data: wb_meta.empty? ? {} : { facade: wb_meta }
  )
end

#core_property(name, value) ⇒ void

This method returns an undefined value.

Set a core document property.

: (Symbol name, String | Integer | Time value) -> void

Parameters:

  • name (Symbol)

    The property name.

  • value (String, Integer, Time)

    The property value.



830
831
832
# File 'lib/xlsxrb.rb', line 830

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

#custom_property(name, value, type: :string) ⇒ void

This method returns an undefined value.

Add a custom document property.

: (String name, String | Integer | Float | bool | Time value, ?type: ::Symbol) -> void

Parameters:

  • name (String)

    The property name.

  • value (String, Integer, Float, Boolean, Time)

    The property value.

  • type (Symbol) (defaults to: :string)

    The type of property (:string, :number, :bool, :date).

  • type: (::Symbol) (defaults to: :string)


865
866
867
# File 'lib/xlsxrb.rb', line 865

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

#defined_name(name, value, sheet: nil, hidden: false) ⇒ void

This method returns an undefined value.

Add a defined name.

: (String name, String value, ?sheet: String?, ?hidden: bool) -> void

Parameters:

  • name (String)

    The defined name.

  • value (String)

    The formula or value.

  • sheet (String, nil) (defaults to: nil)

    Local sheet name.

  • hidden (Boolean) (defaults to: false)

    Whether the defined name is hidden.

  • sheet: (String, nil) (defaults to: nil)
  • hidden: (Boolean) (defaults to: false)


775
776
777
778
779
# File 'lib/xlsxrb.rb', line 775

def defined_name(name, value, sheet: nil, hidden: false)
  entry = { name: name, value: value, hidden: hidden }
  entry[:local_sheet_name] = sheet if sheet
  @defined_names << entry
end

#extract_styles_from_writer(writer) ⇒ { fonts: untyped, fills: untyped, borders: untyped, xf_entries: untyped, num_fmts: untyped }

: (untyped writer) -> { fonts: untyped, fills: untyped, borders: untyped, xf_entries: untyped, num_fmts: untyped }

Parameters:

  • writer (Object)

Returns:

  • ({ fonts: untyped, fills: untyped, borders: untyped, xf_entries: untyped, num_fmts: untyped })


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

def extract_styles_from_writer(writer)
  # Extract style definitions from the writer that can be reused
  # This captures the fonts, fills, borders, and xf entries that were created
  {
    fonts: writer.fonts.dup,
    fills: writer.fills.dup,
    borders: writer.borders.dup,
    xf_entries: writer.xf_entries.dup,
    num_fmts: writer.num_fmts.dup
  }
end

This method returns an undefined value.

Set the print area for a sheet.

: (String range, ?sheet: String?) -> void

Parameters:

  • range (String)

    The range string (e.g. "A1:B10").

  • sheet (String, nil) (defaults to: nil)

    The sheet name.

  • sheet: (String, nil) (defaults to: nil)


788
789
790
791
792
793
# File 'lib/xlsxrb.rb', line 788

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

This method returns an undefined value.

Set print titles for a sheet.

: (?rows: String?, ?cols: String?, ?sheet: String?) -> void

Parameters:

  • rows (String, nil) (defaults to: nil)

    Rows to repeat (e.g. "1:2").

  • cols (String, nil) (defaults to: nil)

    Columns to repeat (e.g. "A:B").

  • sheet (String, nil) (defaults to: nil)

    The sheet name.

  • rows: (String, nil) (defaults to: nil)
  • cols: (String, nil) (defaults to: nil)
  • sheet: (String, nil) (defaults to: nil)


803
804
805
806
807
808
809
810
811
# File 'lib/xlsxrb.rb', line 803

def print_titles(rows: nil, cols: nil, sheet: nil)
  sheet_name = sheet || @sheets.last&.name || "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 }
  defined_name("_xlnm.Print_Titles", value, sheet: sheet_name)
end

#process_styles(sheets) ⇒ ::Array[untyped | ::Hash[untyped, untyped]], ::Array[untyped]

: (untyped sheets) -> (::Array[untyped | ::Hash[untyped, untyped]] | ::Array)

Parameters:

  • sheets (Object)

Returns:

  • (::Array[untyped | ::Hash[untyped, untyped]], ::Array[untyped])


918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
# File 'lib/xlsxrb.rb', line 918

def process_styles(sheets)
  # Collect all unique StyleBuilders from all sheets
  all_style_builders = {}
  @sheet_builders.each do |sheet_builder|
    sheet_builder.styles.each do |style_name, style_builder|
      all_style_builders[style_name] = style_builder
    end
  end

  return [sheets, {}] if all_style_builders.empty?

  # Create a temporary writer to register styles and get numeric IDs
  temp_writer = Ooxml::Writer.new
  style_name_to_id = {}
  all_style_builders.each do |name, builder|
    style_id = builder.register_with(temp_writer)
    style_name_to_id[name] = style_id
  end

  # Capture the style definitions from the temporary writer
  styles_definition = extract_styles_from_writer(temp_writer)

  # Update all cells with their resolved style IDs
  updated_sheets = sheets.map do |sheet|
    new_rows = sheet.rows.map do |row|
      new_cells = row.cells.map do |cell|
        # If style_index is a string (style name), resolve it to a numeric ID
        if cell.style_index.is_a?(String) && style_name_to_id.key?(cell.style_index)
          Elements::Cell.new(
            row_index: cell.row_index,
            column_index: cell.column_index,
            value: cell.value,
            formula: cell.formula,
            style_index: style_name_to_id[cell.style_index],
            unmapped_data: cell.unmapped_data,
            errors: cell.errors
          )
        else
          cell
        end
      end
      Elements::Row.new(
        index: row.index,
        cells: new_cells,
        height: row.height,
        hidden: row.hidden,
        custom_height: row.custom_height,
        outline_level: row.outline_level,
        unmapped_data: row.unmapped_data,
        errors: row.errors
      )
    end
    Elements::Worksheet.new(
      name: sheet.name,
      rows: new_rows,
      columns: sheet.columns,
      charts: sheet.charts,
      unmapped_data: sheet.unmapped_data,
      errors: sheet.errors
    )
  end

  [updated_sheets, styles_definition]
end

#properties(core: nil, app: nil) ⇒ void

This method returns an undefined value.

Set multiple core and/or app properties.

: (?core: Hash[Symbol, String | Integer | Time]?, ?app: Hash[Symbol, String | Integer | Time]?) -> void

Parameters:

  • core (Hash, nil) (defaults to: nil)

    Core properties.

  • app (Hash, nil) (defaults to: nil)

    App properties.

  • core: (Hash[Symbol, String | Integer | Time], nil) (defaults to: nil)
  • app: (Hash[Symbol, String | Integer | Time], nil) (defaults to: nil)


852
853
854
855
# File 'lib/xlsxrb.rb', line 852

def properties(core: nil, app: nil)
  core&.each { |k, v| core_property(k, v) }
  app&.each { |k, v| app_property(k, v) }
end

#protect_workbook(**opts) ⇒ void

This method returns an undefined value.

Set workbook protection.

: (**String | Integer | bool | nil opts) -> void

Parameters:

  • opts (Hash)

    Protection options.



819
820
821
# File 'lib/xlsxrb.rb', line 819

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

#resolve_defined_names(names, sheets) ⇒ Object

: (untyped names, untyped sheets) -> untyped

Parameters:

  • names (Object)
  • sheets (Object)

Returns:

  • (Object)


904
905
906
907
908
909
910
911
912
913
914
915
# File 'lib/xlsxrb.rb', line 904

def resolve_defined_names(names, sheets)
  sheet_names = sheets.map(&:name)
  names.map do |dn|
    resolved = dn.dup
    if dn[:local_sheet_name]
      idx = sheet_names.index(dn[:local_sheet_name])
      resolved[:local_sheet_id] = idx if idx
      resolved.delete(:local_sheet_name)
    end
    resolved
  end
end

#sheet(name = nil, **opts) {|sheet_builder| ... } ⇒ void Also known as: []

This method returns an undefined value.

Add a new sheet.

: (?String? name, **untyped opts) ?{ (WorksheetBuilder) -> void } -> untyped

Parameters:

  • name (String, nil) (defaults to: nil)

    The name of the sheet.

  • opts (Hash)

    Sheet properties.

Yields:

  • (sheet_builder)

Yield Parameters:



750
751
752
753
754
755
756
757
758
759
760
761
# File 'lib/xlsxrb.rb', line 750

def sheet(name = nil, **opts)
  name ||= "Sheet#{@sheets.size + 1}"
  raise ArgumentError, "Sheet name '#{name}' must be <= 31 characters (Excel limitation)" if @strict_excel_mode && name.length > 31
  raise ArgumentError, "Sheet name '#{name}' contains invalid characters (ECMA-376 OOXML specification)" if name.match?(%r{[\[\]*?/\\]})
  raise ArgumentError, "Sheet name '#{name}' is already used. Excel requires unique sheet names." if @strict_excel_mode && @sheets.map { |s| s.respond_to?(:name) ? s.name.downcase : s.to_s.downcase }.include?(name.downcase)

  sheet_builder = WorksheetBuilder.new(name, strict_excel_mode: @strict_excel_mode)
  opts.each { |k, v| sheet_builder.sheet_properties(k, v) }
  yield sheet_builder if block_given?
  @sheet_builders << sheet_builder
  @sheets << sheet_builder.build
end

#workbook_property(name, value) ⇒ void

Note:

SECURITY WARNING: If you set :update_links to anything other than "never", you may expose end-users to malicious external reference vulnerabilities (e.g., CSV/DDE Injection) when they open the generated Excel file. Ensure you fully trust the exported data.

This method returns an undefined value.

Set a workbook property.

: (Symbol name, String | Integer | bool value) -> (String | Integer | bool)

Parameters:

  • name (Symbol)

    The property name (e.g. :update_links).

  • value (String, Integer, Boolean)

    The property value.



737
738
739
# File 'lib/xlsxrb.rb', line 737

def workbook_property(name, value)
  @workbook_properties[name] = value
end