module Xlsxrb
Ruby XLSX read/write library.
Constants
- TRACER
- VERSION
Public Class Methods
Source
# File lib/xlsxrb.rb, line 241 def self.build(&block) raise Error, "block is required" unless block TRACER.in_span("Xlsxrb.build") do builder = WorkbookBuilder.new block.call(builder) builder.build end end
Builds an Elements::Workbook in memory using a DSL.
# File lib/xlsxrb.rb, line 1396 def self.build_raw_cell_from_value(row_index, col_index, value, sst, sst_index) ref = "#{Elements::Cell.column_letter(col_index)}#{row_index + 1}" result = { ref: ref } case value when Elements::Formula result[:formula] = value.expression result[:formula_ca] = true if value.calculate_always result[:value] = value.cached_value if value.cached_value when String idx = sst_index[value] ||= begin sst << value sst.size - 1 end result[:value] = idx result[:type] = "s" when true, false result[:value] = value result[:type] = "b" when Integer, Float result[:value] = value when Date result[:value] = Xlsxrb::Ooxml::Utils.date_to_serial(value) when Time result[:value] = Xlsxrb::Ooxml::Utils.datetime_to_serial(value) when NilClass # empty cell end result end
Builds a raw cell hash from a value for streaming writes.
# File lib/xlsxrb.rb, line 194 def self.foreach(source, sheet: 0, &block) return enum_for(:foreach, source, sheet: sheet) unless block attributes = source.is_a?(String) ? { "filepath" => source } : {} TRACER.in_span("Xlsxrb.foreach", attributes: attributes) do entries = Ooxml::ZipReader.open(source, &:read_all) shared_strings = Ooxml::SharedStringsParser.parse(entries["xl/sharedStrings.xml"]) workbook_sheets = Ooxml::WorkbookParser.parse(entries["xl/workbook.xml"]) rels = Ooxml::RelationshipsParser.parse(entries["xl/_rels/workbook.xml.rels"]) target_sheet = case sheet when Integer workbook_sheets[sheet] when String workbook_sheets.find { |s| s[:name] == sheet } end next unless target_sheet target = rels[target_sheet[:r_id]] next unless target sheet_path = target.start_with?("/") ? target.delete_prefix("/") : "xl/#{target}" sheet_xml = entries[sheet_path] next if sheet_xml.nil? || sheet_xml.empty? Ooxml::WorksheetParser.each_row(sheet_xml, shared_strings: shared_strings) do |raw_row| row = build_row_from_raw(raw_row) block.call(row) end end end
Streaming read: yields Elements::Row one at a time. source: file path (String) or IO object. Options:
sheet: sheet index (0-based Integer) or name (String). Defaults to 0.
# File lib/xlsxrb.rb, line 80 def self.formula(expression, cached_value: nil) Elements::Formula.new( expression: expression, cached_value: cached_value, calculate_always: cached_value.nil? || nil ) end
Creates a Formula object for use in add_row values. expression: the formula text (e.g. “SUM(A1:A10)”) cached_value: optional cached result. If nil, Excel will calculate on open.
Source
# File lib/xlsxrb.rb, line 228 def self.generate(target, &block) raise Error, "target is required" if target.nil? raise Error, "block is required" unless block attributes = target.is_a?(String) ? { "filepath" => target } : {} TRACER.in_span("Xlsxrb.generate", attributes: attributes) do stream_writer = StreamWriter.new(target) block.call(stream_writer) stream_writer.close end end
Streaming write: yields a StreamWriter context for building XLSX on-the-fly. target: file path (String) or IO object.
# File lib/xlsxrb.rb, line 178 def self.modify(source, target = nil, &block) raise Error, "source is required" if source.nil? raise Error, "block is required" unless block workbook = read(source) result_workbook = block.call(workbook) result_workbook = workbook unless result_workbook.is_a?(Elements::Workbook) write_target = target || source write(write_target, result_workbook) end
Modifies an existing XLSX file. Reads the workbook, passes it to the block, and writes the result. The block receives an Elements::Workbook and must return a modified one (e.g. via with). If no target is given, the source is overwritten.
Example:
Xlsxrb.modify("template.xlsx", "output.xlsx") do |wb| sheet = wb.sheet(0) row0 = sheet.row_at(0) new_cell = Xlsxrb::Elements::Cell.new(row_index: 0, column_index: 1, value: "Updated") new_row = row0.with(cells: row0.cells.map { |c| c.column_index == 1 ? new_cell : c }) new_sheet = sheet.with(rows: sheet.rows.map { |r| r.index == 0 ? new_row : r }) wb.with(sheets: wb.sheets.map.with_index { |s, i| i == 0 ? new_sheet : s }) end
Source
# File lib/xlsxrb.rb, line 90 def self.read(source) attributes = source.is_a?(String) ? { "filepath" => source } : {} TRACER.in_span("Xlsxrb.read", attributes: attributes) do entries = Ooxml::ZipReader.open(source, &:read_all) shared_strings = Ooxml::SharedStringsParser.parse(entries["xl/sharedStrings.xml"]) styles = Ooxml::StylesParser.parse(entries["xl/styles.xml"]) workbook_sheets = Ooxml::WorkbookParser.parse(entries["xl/workbook.xml"]) rels = Ooxml::RelationshipsParser.parse(entries["xl/_rels/workbook.xml.rels"]) sheets = workbook_sheets.map do |sheet_info| target = rels[sheet_info[:r_id]] next nil unless target sheet_path = target.start_with?("/") ? target.delete_prefix("/") : "xl/#{target}" sheet_xml = entries[sheet_path] build_worksheet(sheet_info[:name], sheet_xml, shared_strings, styles) end.compact Elements::Workbook.new(sheets: sheets, shared_strings: shared_strings, styles: styles) end end
Reads an XLSX file into an Elements::Workbook. source: file path (String) or IO object.
Source
# File lib/xlsxrb.rb, line 114 def self.write(target, workbook) raise Error, "target is required" if target.nil? raise Error, "workbook must be an Elements::Workbook" unless workbook.is_a?(Elements::Workbook) attributes = target.is_a?(String) ? { "filepath" => target } : {} TRACER.in_span("Xlsxrb.write", attributes: attributes) do sst = [] sst_index = {} # Collect shared strings and build index without allocating new Hashes sheet_data = workbook.sheets.map do |ws| ws.rows.each do |row| row.cells.each do |cell| val = cell.value if (val.is_a?(String) || val.is_a?(Elements::RichText)) && !sst_index.key?(val) sst << val sst_index[val] = sst.size - 1 end end end columns = ws.columns.map do |col| { index: col.index, width: col.width, hidden: col.hidden, custom_width: col.custom_width, outline_level: col.outline_level } end sd = { name: ws.name, rows: ws.rows, columns: columns } sd[:charts] = ws.charts unless ws.charts.empty? # Extract facade metadata from unmapped_data facade = ws.unmapped_data[:facade] facade&.each { |key, val| sd[key] = val } sd end # Extract workbook-level facade metadata wb_facade = workbook.unmapped_data[:facade] || {} Ooxml::WorkbookWriter.write( target, sheets: sheet_data, shared_strings: sst, shared_strings_index: sst_index, styles: workbook.styles, defined_names: wb_facade[:defined_names], core_properties: wb_facade[:core_properties], app_properties: wb_facade[:app_properties], custom_properties: wb_facade[:custom_properties], workbook_protection: wb_facade[:workbook_protection] ) end end
Writes an Elements::Workbook to an XLSX file. target: file path (String) or IO object.