Xlsxrb

A Ruby library for reading and writing XLSX files with streaming support.

Motivation

The Ruby ecosystem already has great XLSX libraries. Each is well-designed for its purpose:

Library Read Write Model Write String Storage Rich Formatting
roo ✅ ❌ Streaming N/A (Read-only) ⚠️ (Formulas, Basic styles)
creek ✅ ❌ Streaming N/A (Read-only) ❌ (Raw cell values)
xsv ✅ ❌ Streaming N/A (Read-only) ❌ (Fast plain text)
caxlsx / axlsx ❌ ✅ In-Memory Inline (opt: SST) ✅ (Charts, Styles)
xlsxtream ❌ ✅ Streaming Inline (opt: SST) ❌ (Plain data only)
fast_excel ❌ ✅ Streaming (C Ext) SST (opt: Inline) ⚠️ (Basic styles)
rubyXL âś… âś… In-Memory Inline / Direct âś… (DOM editing)
xlsxrb âś… âś… Streaming / In-Memory SST âś… (Full Features)

Each of these libraries makes deliberate tradeoffs, and they do so thoughtfully: * Memory & Execution Model (Streaming vs In-Memory): Streaming libraries write or read rows sequentially on-the-fly to maintain a constant, low-memory footprint regardless of row count. In-memory libraries build complete document object trees, offering flexible random access and cell updates at the cost of high RAM usage on large sheets. * String Storage Architecture (SST vs Inline Strings): * SST (Shared String Table): De-duplicates strings into a central dictionary (xl/sharedStrings.xml), referencing them by numeric IDs in cell entries (<c t="s"><v>0</v></c>). This is standard Microsoft Excel behavior, producing significantly smaller raw XML documents (50–100% smaller) and reducing Excel’s memory footprint when opening spreadsheets. * Inline Strings: Writes text directly into cell payloads (<c t="inlineStr"><is><t>...</t></is></c>). Bypassing the dictionary enables blazing-fast raw throughput for simple data exports, but inflates uncompressed XML size and limits advanced formatting (e.g. styling, cell merges, charts).

Traditionally, attempting to build a “complete package” that offers both reading and writing, rich features, high performance, strict compatibility, and comprehensive documentation presents an inherent open-source challenge: the cumulative maintenance overhead often exceeds the capacity of individual human maintainers.

xlsxrb is born from a different premise. We believe that Advanced Agentic AI (AI Coders) can help manage this maintenance demand. By utilizing AI agents to automate rigorous E2E testing, visual regression testing, specification compliance checks, and documentation updates, we can reconcile these competing engineering requirements. This allows us to build and continuously maintain a feature-rich, high-performance, and deeply compatible “all-in-one” XLSX library that remains sustainable for the long run.

Design Principles

Installation

bundle add xlsxrb

Or without Bundler:

gem install xlsxrb

On Ruby 4+, some components used by xlsxrb and its test suite are shipped as bundled gems rather than built-in default libraries. When using Bundler, those bundled gems are resolved and installed in the usual way.

Interactive Playground (WebAssembly)

You can try xlsxrb directly in your browser without installing anything!

👉 Try the Live Demo / Interactive Playground

We have integrated an interactive WebAssembly-powered playground into our RDoc documentation. You can edit the code examples, run them in the browser sandbox, and download the generated .xlsx spreadsheets immediately.

To launch the playground locally: 1. Generate the WebAssembly bundle and interactive RDoc: bash bundle exec rake doc 2. Start the local preview server: bash bundle exec rake doc:preview 3. Open http://localhost:8000 in your browser, hover over any code block, and click the “Live Preview” or “Download XLSX” buttons!

Usage

xlsxrb supports both low-memory Streaming (recommended for large files) and full In-Memory document manipulation (for random-access cell modifications or updating existing sheets).

For visual demonstrations of various features, check the Visual Examples Gallery.

Streaming Write

Generate large files efficiently by writing data directly to the file stream:

require "xlsxrb"

Xlsxrb.generate("large_output.xlsx") do |stream_writer|
  stream_writer.sheet("Sales Data") do |sheet|
    sheet.row(["Date", "Amount", "Status"])
    sheet.row([Date.today, 100, true])
    sheet.column(0, width: 15.5)
  end
end

Streaming Read

Read rows one at a time without loading the entire file into memory:

require "xlsxrb"

Xlsxrb.foreach("large_file.xlsx") do |sheet|
  sheet.each_row do |row|
    puts "Row #{row.index}: #{row.cells.map(&:value).join(', ')}"
  end
end

In-Memory Building & Modifying

xlsxrb provides a powerful, immutable-by-default API for modifying existing Excel files or building templates in-memory.

Modifying an Existing File

You can update specific cells or sheets using the functional Xlsxrb.modify API, which yields the parsed Elements::Workbook.

require "xlsxrb"

# Create a template.xlsx for this example
Xlsxrb.build { |builder| builder.sheet("Invoice") }.write("template.xlsx")

Xlsxrb.modify("template.xlsx", "output.xlsx") do |workbook|
  workbook.update_sheet("Invoice") do |sheet|
    # Update specific cells (returns updated sheet)
    sheet = sheet.update_cell("C4", value: "INV-10042")
    sheet = sheet.update_cell("C5", value: Date.today)
    
    # Or append new rows
    sheet.with(rows: sheet.rows + [
      Xlsxrb::Elements::Row.new(index: sheet.rows.size, cells: [])
    ])
  end
end

Hash & Range Styling (Syntactic Sugar)

You can directly apply inline styles or use Ranges for multiple columns without boilerplate:

Xlsxrb.build do |builder|
  # Use [] accessor for sheets
  builder["Report"].row(
    ["ID", "Name", "Score", "Rank"],
    # Apply 'header' style to first two columns, and bold inline style to the third
    styles: { 0..1 => "header", 2 => { font: { bold: true, color: "red" } } }
  )
  
  # Set multiple column widths at once using Ranges
  builder["Report"].column("A".."D", width: 15.0)
end

IDE Autocompletion & Ruby LSP Support

xlsxrb bundles a native Ruby LSP Add-on (RubyLsp::Xlsxrb::Addon) and full RBS signatures, enabling zero-configuration method autocompletion and rich Markdown documentation in VS Code and other LSP-enabled editors.

Whether you use standard descriptive block variable names (|stream_writer|, |sheet|, |workbook|) or short names (|wb|, |s|), your editor will automatically provide complete method suggestions and parameter hints:

Xlsxrb.generate("output.xlsx") do |stream_writer| # or |wb|
  stream_writer.sheet("Data") do |sheet|           # or |s|
    sheet.row(["Product", "Price"], styles: :bold)
    sheet.auto_filter("A1:B100")
  end
end

Feature Support & ECMA-376 Compliance

xlsxrb is designed for full interoperability and strict compliance with the ECMA-376 (Office Open XML) Transitional specification. It supports nearly all major spreadsheet features required for business reports:

For detailed specification references and policies, see SPEC_SOURCES.md.

Benchmarks

The following benchmarks measure the time, peak memory, and GC count required to process a 1,000,000 cells (100,000 rows Ă— 10 columns) spreadsheet across popular Ruby Excel libraries. Each test is executed across 3 independent runs in isolated subprocesses; median values are reported along with the mean execution time.

Write Performance (1,000,000 cells)

Library Model Write String Storage Time (Median) Time (Mean) Peak Memory GC Count
xlsxtream 3.1.0 Streaming Inline String 1.23 s 1.25 s 18.1 MB 1061.0
xlsxrb (Streaming) Streaming SST (Shared) 1.62 s 1.62 s 94.5 MB 39.0
fast_excel 0.5.0 © Streaming SST (Shared) 2.03 s 2.03 s 147.9 MB 263.0
xlsxrb (In-Memory) In-Memory SST (Shared) 4.06 s 4.06 s 280.3 MB 32.0
caxlsx 4.5.0 In-Memory Inline String 5.36 s 5.35 s 188.7 MB 23.0
rubyXL 3.4.38 In-Memory Inline String 38.02 s 38.03 s 2166.0 MB 104.0

Note: All libraries are evaluated in their default, out-of-the-box configuration. Under the same Microsoft Excel-standard Shared String Table (SST) architecture, Pure Ruby xlsxrb (Streaming: 1.62s, In-Memory: 4.06s) writes 1,000,000 cells faster than the C-extension fast_excel (2.03s) and in-memory gems like caxlsx (5.36s).

Read Performance (1,000,000 cells)

Library Model Time (Median) Time (Mean) Peak Memory GC Count
xlsxrb (Streaming) Streaming 3.38 s 3.37 s 90.6 MB 40.0
xlsxrb (In-Memory) In-Memory 5.75 s 5.94 s 250.9 MB 55.0
creek 2.6.3 Streaming 7.90 s 7.93 s 835.7 MB 481.0
roo 3.0.0 Streaming 10.34 s 10.28 s 139.0 MB 107.0
xsv 1.4.1 Streaming 16.39 s 17.28 s 75.4 MB 2215.0
rubyXL 3.4.38 In-Memory 35.45 s 35.38 s 2281.3 MB 146.0

Running the Benchmarks Locally (Reproducibility)

The benchmark suite leverages bundler/inline to automatically manage and download all peer ecosystem gems without modifying the project’s core Gemfile or requiring manual global gem install steps. Each library is executed in an isolated subprocess (Bundler.with_unbundled_env) across multiple runs with standard business dataset rows (integers, strings, floats, booleans, dates) to ensure clean memory and GC measurements without cross-contamination.

To run the complete benchmark suite:

ruby benchmark.rb 100000 10

Security (Protection against CSV/Excel Injection)

Unlike CSV files which lack type definitions and force Excel to guess types (often inadvertently executing strings starting with =), .xlsx files generated by xlsxrb are strictly typed.

When you pass a Ruby String to xlsxrb, it explicitly writes it as a String (t="s") into the OOXML file. Therefore, even if a string starts with =, Excel will never evaluate it as a formula. To write a formula, you must explicitly use Xlsxrb::Elements::Formula.new. This design completely mitigates CSV/Formula Injection vulnerabilities by default without requiring additional sanitization.

As an extra layer of “defense in depth”, xlsxrb configures the workbook to never automatically update external links when opened (updateLinks="never"). This is intentionally set to never by default to prevent Excel from silently reaching out to external resources or executing DDE (Dynamic Data Exchange) links, which is a known vector for malware.

If you absolutely need external links to update automatically, you can explicitly override this (though it is highly discouraged due to security risks):

Xlsxrb.generate("file.xlsx") do |wb|
  # WARNING: Enabling this can expose users to malicious external reference vulnerabilities!
  wb.workbook_property(:update_links, "always") 
  # ...
end

Testing & Quality Assurance

To support reliability, compliance with the ECMA-376 specification, and consistent updates, xlsxrb is backed by a highly rigorous, enterprise-grade Quality Assurance (QA) and testing architecture.

Multi-Tier Testing Strategy

Strict Interoperability & Rendering

Performance & Types

For a comprehensive breakdown of our QA matrix, see docs/QUALITY_ASSURANCE.md. For details on running tests locally, see docs/DEVELOPMENT.md.

Development

We welcome contributions! The project is configured with a ready-to-use Dev Container to streamline local environment setup.

For contribution guidelines, E2E testing policies, and the step-by-step development workflow (including how to run the Dev Container from your terminal), please refer to docs/DEVELOPMENT.md.

To install this gem onto your local machine, run bundle exec rake install. To release a new version, update the version number in version.rb, and then run bundle exec rake release, which will create a git tag for the version, push git commits and the created tag, and push the .gem file to rubygems.org.

Contributing

Bug reports and pull requests are welcome on GitHub at github.com/niku/xlsxrb. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the code of conduct.

License

The gem is available as open source under the terms of the MIT License.

Code of Conduct

Everyone interacting in the Xlsxrb project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the code of conduct.