Module: Xlsxrb

Defined in:
lib/xlsxrb.rb,
lib/xlsxrb/ooxml.rb,
lib/xlsxrb/version.rb,
lib/xlsxrb/elements.rb,
lib/xlsxrb/ooxml/utils.rb,
lib/xlsxrb/elements/row.rb,
lib/xlsxrb/ooxml/reader.rb,
lib/xlsxrb/ooxml/writer.rb,
lib/xlsxrb/elements/cell.rb,
lib/xlsxrb/style_builder.rb,
lib/xlsxrb/elements/types.rb,
lib/xlsxrb/elements/column.rb,
lib/xlsxrb/ooxml/xml_parser.rb,
lib/xlsxrb/ooxml/zip_reader.rb,
lib/xlsxrb/ooxml/zip_writer.rb,
lib/xlsxrb/elements/workbook.rb,
lib/xlsxrb/ooxml/xml_builder.rb,
lib/xlsxrb/elements/worksheet.rb,
lib/xlsxrb/ooxml/styles_parser.rb,
lib/xlsxrb/ooxml/zip_generator.rb,
lib/xlsxrb/ooxml/workbook_parser.rb,
lib/xlsxrb/ooxml/workbook_writer.rb,
lib/xlsxrb/ooxml/worksheet_parser.rb,
lib/xlsxrb/ooxml/worksheet_writer.rb,
lib/xlsxrb/ooxml/shared_strings_parser.rb,
sig/generated/xlsxrb/ooxml.rbs,
sig/generated/xlsxrb/version.rbs,
sig/generated/xlsxrb/elements.rbs,
sig/generated/xlsxrb/style_builder.rbs

Overview

rbs_inline: enabled

Defined Under Namespace

Modules: Elements, Ooxml Classes: ChartBuilder, Error, ParseError, StreamSheet, StreamWriter, StyleBuilder, ValidationError, WorkbookBuilder, WorksheetBuilder, ZipError

Constant Summary collapse

TRACER =
OpenTelemetry.tracer_provider.tracer("xlsxrb", Xlsxrb::VERSION)
VERSION =

Returns:

  • (::String)
"0.1.5"

Class Method Summary collapse

Class Method Details

.build(strict_excel_mode: true) {|builder| ... } ⇒ Elements::Workbook

Builds an Elements::Workbook in memory using a DSL.

: (?strict_excel_mode: bool) ?{ (WorkbookBuilder) -> void } -> Elements::Workbook

Yields:

  • (builder)

Yield Parameters:

Returns:

Raises:



511
512
513
514
515
516
517
518
519
# File 'lib/xlsxrb.rb', line 511

def self.build(strict_excel_mode: true)
  raise Error, "block is required" unless block_given?

  Xlsxrb.in_span("Xlsxrb.build") do
    builder = WorkbookBuilder.new(strict_excel_mode: strict_excel_mode)
    yield builder
    builder.build
  end
end

.build_raw_cell_from_value(row_index, col_index, value, sst, sst_index) ⇒ Object

Builds a raw cell hash from a value for streaming writes. : (untyped row_index, untyped col_index, untyped value, untyped sst, untyped sst_index) -> untyped



2675
2676
2677
2678
2679
2680
2681
2682
2683
2684
2685
2686
2687
2688
2689
2690
2691
2692
2693
2694
2695
2696
2697
2698
2699
2700
2701
2702
2703
2704
2705
# File 'lib/xlsxrb.rb', line 2675

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

.foreach(source) {|sheet| ... } ⇒ Enumerator, void

Streaming read: yields StreamSheet objects one at a time for each sheet.

: (String | IO source) ?{ (StreamSheet) -> void } -> (Enumerator[StreamSheet, void] | void)

Parameters:

  • source (String, IO)

    File path or IO object.

Yields:

  • (sheet)

    Yields each sheet.

Yield Parameters:

Returns:

  • (Enumerator)

    If no block is given.

  • (void)


457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
# File 'lib/xlsxrb.rb', line 457

def self.foreach(source)
  return enum_for(:foreach, source) unless block_given?

  attributes = source.is_a?(String) ? { "filepath" => source } : {}
  Xlsxrb.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"])

    workbook_sheets.each do |sheet_info|
      target = rels[sheet_info[: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?

      yield StreamSheet.new(sheet_info[:name], sheet_xml, shared_strings)
    end
  end
end

.formula(expression, cached_value: nil) ⇒ Elements::Formula

Creates a Formula object for use in row values.

: (String expression, ?cached_value: String | Numeric | bool | nil) -> Elements::Formula

Parameters:

  • expression (String)

    The formula text (e.g. "SUM(A1:A10)").

  • cached_value (Object, nil) (defaults to: nil)

    Optional cached result. If nil, Excel will calculate on open.

Returns:



299
300
301
302
303
304
305
# File 'lib/xlsxrb.rb', line 299

def self.formula(expression, cached_value: nil)
  Elements::Formula.new(
    expression: expression,
    cached_value: cached_value,
    calculate_always: cached_value.nil? || nil
  )
end

.generate(target, strict_excel_mode: true) {|stream_writer| ... } ⇒ void

This method returns an undefined value.

Streaming write: yields a StreamWriter context for building XLSX on-the-fly.

: (String | IO target, ?strict_excel_mode: bool) ?{ (StreamWriter) -> void } -> void

Parameters:

  • target (String, IO)

    File path or IO object.

Yields:

  • (stream_writer)

Yield Parameters:

Raises:



488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
# File 'lib/xlsxrb.rb', line 488

def self.generate(target, strict_excel_mode: true)
  raise Error, "target is required" if target.nil?
  raise Error, "block is required" unless block_given?

  attributes = target.is_a?(String) ? { "filepath" => target } : {}
  Xlsxrb.in_span("Xlsxrb.generate", attributes: attributes) do
    stream_writer = StreamWriter.new(target, strict_excel_mode: strict_excel_mode)
    begin
      yield stream_writer
      stream_writer.close
    ensure
      stream_writer.cleanup!
    end
  end
end

.in_span(name, attributes: nil) ⇒ Object



44
45
46
47
48
49
50
51
52
# File 'lib/xlsxrb.rb', line 44

def self.in_span(name, attributes: nil, &)
  if defined?(Ractor) && Ractor.current != Ractor.main
    yield
  elsif attributes
    TRACER.in_span(name, attributes: attributes, &)
  else
    TRACER.in_span(name, &)
  end
end

.modify(source, target = nil) {|workbook| ... } ⇒ void

This method returns an undefined value.

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 update_sheet). If no target is given, the source is overwritten.

: (String | IO source, ?(String | IO)? target) ?{ (Elements::Workbook) -> Elements::Workbook } -> void

Examples:

Xlsxrb.modify("template.xlsx", "output.xlsx") do |wb|
  wb.update_sheet(0) do |sheet|
    sheet.update_cell("B1", value: "Updated")
         .update_cell("B2", value: 100)
  end
end

Parameters:

  • source (String, IO)

    The source file path or IO object.

  • target (String, IO, nil) (defaults to: nil)

    The target file path or IO object. If nil, overwrites source.

Yields:

  • (workbook)

    Yields the parsed workbook.

Yield Parameters:

Yield Returns:

Raises:



414
415
416
417
418
419
420
421
422
423
424
# File 'lib/xlsxrb.rb', line 414

def self.modify(source, target = nil)
  raise Error, "source is required" if source.nil?
  raise Error, "block is required" unless block_given?

  workbook = read(source)
  result_workbook = yield workbook
  result_workbook = workbook unless result_workbook.is_a?(Elements::Workbook)

  write_target = target || source
  write(write_target, result_workbook)
end

.read(source) ⇒ Elements::Workbook

Reads an XLSX file into an Elements::Workbook.

: (String | IO source) -> Elements::Workbook

Parameters:

  • source (String, IO)

    File path or IO object.

Returns:



313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
# File 'lib/xlsxrb.rb', line 313

def self.read(source)
  attributes = source.is_a?(String) ? { "filepath" => source } : {}
  Xlsxrb.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

.rich_text(*runs, text: nil, **font_props) ⇒ Elements::RichText

Helper to easily create RichText objects. Supports both Xlsxrb.rich_text({ text: "A" }, { text: "B" }) and Xlsxrb.rich_text(text: "Hi", bold: true)

: (*Hash[Symbol, String | Integer | bool | nil] runs, ?text: String?, **String | Integer | bool | nil font_props) -> Elements::RichText

Parameters:

  • runs (Array<Hash>)

    Optional rich text runs.

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

    Simple text.

  • font_props (Hash)

    Font styling options (e.g., bold: true).

Returns:



64
65
66
67
# File 'lib/xlsxrb.rb', line 64

def self.rich_text(*runs, text: nil, **font_props)
  runs = [{ text: text, font: font_props }] if text
  Elements::RichText.new(runs: runs)
end

.write(target, workbook) ⇒ void

This method returns an undefined value.

Writes an Elements::Workbook to an XLSX file.

: (String | IO target, Elements::Workbook workbook) -> void

Parameters:

  • target (String, IO)

    File path or IO object.

  • workbook (Elements::Workbook)

    The workbook to write.

Raises:



342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
# File 'lib/xlsxrb.rb', line 342

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 } : {}
  Xlsxrb.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],
      workbook_properties: wb_facade[:workbook_properties]
    )
  end
end