class Xlsxrb::WorkbookBuilder
DSL context for building an in-memory {Elements::Workbook} in {Xlsxrb.build}.
@example Build an in-memory workbook with multiple sheets
workbook = Xlsxrb.build do |builder| builder.sheet("Sales") do |sheet| sheet.row(["Product", "Revenue"]) sheet.row(["Widget", 1000]) end builder.core_property(:creator, "Reporting System") end
@api public
Public Class Methods
(?strict_excel_mode: bool) → void
Source
# File lib/xlsxrb/workbook_builder.rb, line 23 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
@param strict_excel_mode [Boolean] Whether to enforce Microsoft Excel specifications.
Public Instance Methods
(Symbol name, String | Integer | Time value) → void
Source
# File lib/xlsxrb/workbook_builder.rb, line 210 def app_property(name, value) @app_properties[name] = value end
Sets an extended application metadata property.
@param name [Symbol] Application property name (:company, :manager, :app_version, etc.). @param value [String, Integer, Time] Property value. @return [void] @api public
() → Elements::Workbook
Source
# File lib/xlsxrb/workbook_builder.rb, line 246 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) # Auto-register print_area and print_titles defined names from sheet builders if not explicitly defined @sheet_builders.each do |sb| print_area(sb.print_area, sheet: sb.name) if sb.print_area && @defined_names.none? { |dn| dn[:name] == "_xlnm.Print_Area" && dn[:local_sheet_name] == sb.name } print_titles(rows: sb.print_titles[:rows], cols: sb.print_titles[:cols], sheet: sb.name) if sb.print_titles && @defined_names.none? { |dn| dn[:name] == "_xlnm.Print_Titles" && dn[:local_sheet_name] == sb.name } end # Store workbook-level metadata in unmapped_data wb_meta = {} dns = resolve_defined_names(@defined_names, processed_sheets) wb_meta[:defined_names] = dns unless dns.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 }, defined_names: dns ) end
Builds and returns the compiled {Elements::Workbook}.
@return [Elements::Workbook] @raise [ArgumentError] If workbook contains zero sheets in strict mode. @api public
(Symbol name, String | Integer | Time value) → void
Source
# File lib/xlsxrb/workbook_builder.rb, line 199 def core_property(name, value) @core_properties[name] = value end
Sets a Dublin Core document metadata property.
@param name [Symbol] Core property name (:creator, :title, :subject, :description, etc.). @param value [String, Integer, Time] Property value. @return [void] @api public
(String name, String | Integer | Float | bool | Time value, ?type: ::Symbol) → void
Source
# File lib/xlsxrb/workbook_builder.rb, line 236 def custom_property(name, value, type: :string) @custom_properties << { name: name, value: value, type: type } end
Adds a custom document property.
@param name [String] Property name. @param value [String, Integer, Float, Boolean, Time] Property value. @param type [Symbol] Value type (:string, :number, :bool, :date). @return [void] @api public
(String name, String value, ?sheet: String?, ?hidden: bool) → void
Source
# File lib/xlsxrb/workbook_builder.rb, line 90 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
Adds a defined name (named range or formula constant) to the workbook.
@param name [String] Defined name (e.g. “TaxRate”). @param value [String] Formula expression or range reference (e.g. “Sheet1!$B$2”). @param sheet [String, nil] Optional sheet name for sheet-scoped defined names. @param hidden [Boolean] Whether the defined name is hidden from Excel’s UI. @return [void] @api public
(String range, ?sheet: String?) → void
Source
# File lib/xlsxrb/workbook_builder.rb, line 103 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
Sets the print area for a sheet.
@param range [String] Cell range (e.g. “A1:G50”). @param sheet [String, nil] Target sheet name (defaults to latest or “Sheet1”). @return [void] @api public
(?rows: String?, ?cols: String?, ?sheet: String?) → void
Source
# File lib/xlsxrb/workbook_builder.rb, line 118 def print_titles(rows: nil, cols: nil, sheet: nil) sheet_name = sheet || @sheets.last&.name || "Sheet1" existing = @defined_names.find { |dn| dn[:name] == "_xlnm.Print_Titles" && dn[:local_sheet_name] == sheet_name } if existing && (rows.nil? || cols.nil?) cols = Regexp.last_match(1).delete("$") if cols.nil? && existing[:value] =~ /'[^']*'!\$([A-Za-z]+:\$[A-Za-z]+)/ rows = Regexp.last_match(1).delete("$") if rows.nil? && existing[:value] =~ /'[^']*'!\$([0-9]+:\$[0-9]+)/ end 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
Sets repeating print titles (rows and/or columns) for pagination.
@param rows [String, nil] Repeating row range (e.g. “1:2”). @param cols [String, nil] Repeating column range (e.g. “A:B”). @param sheet [String, nil] Target sheet name. @return [void] @api public
(?core: Hash[Symbol, String | Integer | Time]?, ?app: Hash[Symbol, String | Integer | Time]?, ?custom: Hash[String | Symbol, untyped]?) → void
Source
# File lib/xlsxrb/workbook_builder.rb, line 222 def properties(core: nil, app: nil, custom: nil) core&.each { |k, v| core_property(k, v) } app&.each { |k, v| app_property(k, v) } custom&.each { |k, v| custom_property(k.to_s, v) } end
Sets multiple core, app, and/or custom properties simultaneously.
@param core [Hash{Symbol => Object}, nil] Core properties map. @param app [Hash{Symbol => Object}, nil] App properties map. @param custom [Hash{String, Symbol => Object}, nil] Custom properties map. @return [void] @api public
(**String | Integer | bool | nil opts) → void
Source
# File lib/xlsxrb/workbook_builder.rb, line 188 def protect_workbook(**opts) @workbook_protection = opts end
Sets workbook structure and window protection.
@param opts [Hash] Protection options (e.g. lock_structure: true, password: “secret”). @return [void] @api public
(Integer | String first, ?(Integer | String)? last, ?sheet: String?) → void
Source
# File lib/xlsxrb/workbook_builder.rb, line 165 def repeat_columns(first, last = nil, sheet: nil) cols_str = if first.is_a?(String) first.include?(":") ? first : "#{first}:#{last || first}" else first_name = Utils.col_index_to_name(first) last_name = if last.is_a?(Integer) Utils.col_index_to_name(last) elsif last last.to_s else first_name end "#{first_name}:#{last_name}" end print_titles(cols: cols_str, sheet: sheet) end
Sets repeating title columns for pagination on the target or active sheet.
@example Repeat columns A through C
wb.repeat_columns("A", "C")
@param first [Integer, String] First column index (0-based) or column name/range. @param last [Integer, String, nil] Optional last column index/name. @param sheet [String, nil] Target sheet name. @return [void] @api public
(Integer | String first, ?(Integer | String)? last, ?sheet: String?) → void
Source
# File lib/xlsxrb/workbook_builder.rb, line 144 def repeat_rows(first, last = nil, sheet: nil) rows_str = if first.is_a?(String) first.include?(":") ? first : "#{first}:#{last || first}" else last ||= first "#{first + 1}:#{last + 1}" end print_titles(rows: rows_str, sheet: sheet) end
Sets repeating title rows for pagination on the target or active sheet.
@example Repeat first row
wb.repeat_rows(0)
@param first [Integer, String] First row index (0-based) or range string (e.g. “1:2”). @param last [Integer, String, nil] Optional last row index (0-based). @param sheet [String, nil] Target sheet name. @return [void] @api public
(?String? name, **untyped opts) ?{ (WorksheetBuilder) → void } → untyped
Source
# File lib/xlsxrb/workbook_builder.rb, line 60 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) state = opts.delete(:state) hidden = opts.delete(:hidden) state ||= :hidden if hidden state ||= :visible sheet_builder = WorksheetBuilder.new(name, strict_excel_mode: @strict_excel_mode, state: state) 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
Adds a new worksheet to the workbook.
@param name [String, nil] Sheet name (max 31 chars, no forbidden chars ‘[ ] * ? / `). @param opts [Hash] Sheet properties. @yield [sheet_builder] @yieldparam sheet_builder [Xlsxrb::WorksheetBuilder] @return [Elements::Worksheet] @raise [ArgumentError] If sheet name violates Excel limits in strict mode. @api public
(Symbol name, String | Integer | bool value) → (String | Integer | bool)
Source
# File lib/xlsxrb/workbook_builder.rb, line 46 def workbook_property(name, value) @workbook_properties[name] = value end
Sets a workbook-level property.
@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.
@param name [Symbol] Property name (e.g. :update_links). @param value [String, Integer, Boolean] Property value. @return [void] @api public