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.rbs,
sig/generated/xlsxrb/ooxml.rbs,
sig/generated/xlsxrb/version.rbs,
sig/generated/xlsxrb/elements.rbs,
sig/generated/xlsxrb/ooxml/utils.rbs,
sig/generated/xlsxrb/elements/row.rbs,
sig/generated/xlsxrb/ooxml/reader.rbs,
sig/generated/xlsxrb/ooxml/writer.rbs,
sig/generated/xlsxrb/elements/cell.rbs,
sig/generated/xlsxrb/style_builder.rbs,
sig/generated/xlsxrb/elements/types.rbs,
sig/generated/xlsxrb/elements/column.rbs,
sig/generated/xlsxrb/ooxml/xml_parser.rbs,
sig/generated/xlsxrb/ooxml/zip_reader.rbs,
sig/generated/xlsxrb/ooxml/zip_writer.rbs,
sig/generated/xlsxrb/elements/workbook.rbs,
sig/generated/xlsxrb/ooxml/xml_builder.rbs,
sig/generated/xlsxrb/elements/worksheet.rbs,
sig/generated/xlsxrb/ooxml/styles_parser.rbs,
sig/generated/xlsxrb/ooxml/zip_generator.rbs,
sig/generated/xlsxrb/ooxml/workbook_parser.rbs,
sig/generated/xlsxrb/ooxml/workbook_writer.rbs,
sig/generated/xlsxrb/ooxml/worksheet_parser.rbs,
sig/generated/xlsxrb/ooxml/worksheet_writer.rbs,
sig/generated/xlsxrb/ooxml/shared_strings_parser.rbs

Overview

Ruby XLSX read/write library.

Defined Under Namespace

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

Constant Summary collapse

TRACER =

Returns:

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

Returns:

  • (::String)
"0.1.6"

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 } -> untyped

Parameters:

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

Yields:

  • (builder)

Yield Parameters:

Returns:



575
576
577
578
579
580
581
582
583
# File 'lib/xlsxrb.rb', line 575

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

Parameters:

  • row_index (Object)
  • col_index (Object)
  • value (Object)
  • sst (Object)
  • sst_index (Object)

Returns:

  • (Object)


2876
2877
2878
2879
2880
2881
2882
2883
2884
2885
2886
2887
2888
2889
2890
2891
2892
2893
2894
2895
2896
2897
2898
2899
2900
2901
2902
2903
2904
2905
2906
2907
2908
2909
2910
2911
2912
# File 'lib/xlsxrb.rb', line 2876

def self.build_raw_cell_from_value(row_index, col_index, value, sst, sst_index)
  # simplecov:disable
  # Edge case / untested delegation block
  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)
  # simplecov:enable
  when NilClass
    # empty cell
  end

  # simplecov:disable
  # Edge case / untested delegation block
  result
  # simplecov:enable
end

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

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

: (untyped source) ?{ (StreamSheet) -> void } -> untyped

Parameters:

  • source (String, IO)

    File path or IO object.

Yields:

  • (sheet)

    Yields each sheet.

Yield Parameters:

Returns:

  • (Enumerator)

    If no block is given.

  • (void)


521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
# File 'lib/xlsxrb.rb', line 521

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) -> untyped

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.

  • cached_value: (String, Numeric, bool, nil) (defaults to: nil)

Returns:



349
350
351
352
353
354
355
# File 'lib/xlsxrb.rb', line 349

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.

: (untyped target, ?strict_excel_mode: bool) ?{ (Xlsxrb::StreamWriter) -> void } -> void

Parameters:

  • target (String, IO)

    File path or IO object.

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

Yields:

  • (stream_writer)

Yield Parameters:



552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
# File 'lib/xlsxrb.rb', line 552

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) ⇒ void

This method returns an undefined value.

Parameters:

  • name (Object)
  • attributes: (Object) (defaults to: nil)


47
48
49
50
51
52
53
54
55
56
57
58
# File 'lib/xlsxrb.rb', line 47

def self.in_span(name, attributes: nil, &)
  if defined?(Ractor) && Ractor.current != Ractor.main
    # simplecov:disable
    # Test suite runs in the main Ractor. This branch is for multi-threaded usage via Ractors.
    yield
    # simplecov:enable
  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.

: (untyped source, ?untyped target) ?{ (untyped) -> untyped } -> 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:



467
468
469
470
471
472
473
474
475
476
477
# File 'lib/xlsxrb.rb', line 467

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.

: (untyped source) -> untyped

Parameters:

  • source (String, IO)

    File path or IO object.

Returns:



363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
# File 'lib/xlsxrb.rb', line 363

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)

: (*untyped runs, ?text: String?, **untyped font_props) -> untyped

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).

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

Returns:



70
71
72
73
# File 'lib/xlsxrb.rb', line 70

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.

: (untyped target, untyped workbook) -> void

Parameters:

  • target (String, IO)

    File path or IO object.

  • workbook (Elements::Workbook)

    The workbook to write.



392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
# File 'lib/xlsxrb.rb', line 392

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|
        # simplecov:disable
        # Edge case / untested delegation block
        { index: col.index, width: col.width, hidden: col.hidden, custom_width: col.custom_width, outline_level: col.outline_level }
        # simplecov:enable
      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