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 | Streaming (low memory) |
|---|---|---|---|
| roo | ✅ | ❌ | ✅ |
| creek | ✅ | ❌ | ✅ |
| xsv | ✅ | ❌ | ✅ |
| caxlsx / axlsx | ❌ | ✅ | ❌ |
| xlsxtream | ❌ | ✅ | ✅ |
| rubyXL | ✅ | ✅ | ❌ |
| fast_excel | ❌ | ✅ | ✅ |
Each of these libraries makes deliberate tradeoffs, and they do so thoughtfully. Some focus exclusively on highly efficient reading or writing by streaming data, while others provide a rich API for complex, in-memory document modifications.
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
- Minimal Dependencies (Zero Core Logic Dependencies): This library avoids heavy third-party XLSX/XML/ZIP gems, building all core parsing and writing features purely on the Ruby standard library and bundled gems (
zlib,rexml, etc.). The only runtime dependency isopentelemetry-api, which provides zero-overhead observability. If you do not configure an OpenTelemetry SDK in your application, it acts as a lightweight no-op, keeping the runtime footprint extremely small. - Streaming Support: Both reading and writing are designed to handle large files efficiently by streaming data, keeping memory usage low and predictable.
- Memory-Efficient XML Parsing: For reading operations, the library uses a custom byte-level streaming parser for worksheet rows (with targeted SAX parsing where appropriate) instead of DOM-based parsing, so entire XML documents are never loaded into memory. This enables true streaming capability for large spreadsheets.
- Strict Microsoft Excel & OpenXML Interoperability: It is designed to closely follow the Microsoft Office implementation of the ISO 29500 standard. We ensure absolute bidirectional compatibility (both reading and writing) with Microsoft Excel by continuously validating files against the official Microsoft Open XML SDK.
- AI-Agent Assisted Maintenance (Managing the Engineering Tradeoff): Building a library that is specification-compliant, rich in features, highly compatible, well-documented, and extremely fast typically presents a substantial maintenance challenge.
xlsxrbaddresses this inherent constraint by leveraging Advanced Agentic AI (AI Coders) to automate testing, feature expansion, and compatibility verification. This AI-assisted development process supports the project's long-term sustainability and high software quality. - Modern Ruby 4.0+: Built for the future with Ruby 4.0 or higher.
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:
- Generate the WebAssembly bundle and interactive RDoc:
bundle exec rake doc - Start the local preview server:
bundle exec rake doc:preview - 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.
Quick Start: Streaming (Recommended)
Streaming Write
Generate large files efficiently by writing data directly to the file stream:
require "xlsxrb"
Xlsxrb.generate("large_output.xlsx") do |wb|
wb.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 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 Workbook.
require "xlsxrb"
# Create a dummy template.xlsx for this example
Xlsxrb.build { |w| w.sheet("Invoice") }.write("template.xlsx")
Xlsxrb.modify("template.xlsx", "output.xlsx") do |wb|
wb.update_sheet("Invoice") do |sheet|
# Update a specific cell
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 |wb|
# Use [] accessor for sheets
wb["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
wb["Report"].column("A".."D", width: 15.0)
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:
- Cells & Layout: Formulas, Hyperlinks, Merge Cells, Freeze & Split Panes, Page Setup (margins, headers/footers, scaling, gridlines).
- Data & Controls: Auto Filters, Data Validations (dropdowns, range limits), Sheet Protection.
- Formatting & Styling: Rich Text, Cell Tables, Conditional Formatting (color scales, data bars, icon sets).
- Graphics & Charts: Embedded Images, Shapes & Drawings, Sparklines, Charts (Line, Bar, Pie, Area, Radar, Scatter).
- Workbook Level: Defined Names, Print Areas, Workbook Protection, and Document Metadata (core, app, custom properties).
For detailed specification references and policies, see SPEC_SOURCES.md.
Benchmarks
The following benchmarks measure the time and memory required to process a 1,000,000 cells (100,000 rows × 10 columns) spreadsheet, demonstrating xlsxrb's memory consumption and processing times.
Write Performance (1,000,000 cells)
| Library | Time | Peak Memory | GC Count |
|---|---|---|---|
| xlsxtream (Streaming) | 0.12 s | 64.9 MB | 9.0 |
| fast_excel (Streaming) | 1.34 s | 64.5 MB | 28.0 |
| caxlsx (In-Memory) | 2.64 s | 142.7 MB | 16.0 |
| xlsxrb (Streaming) | 3.32 s | 216.9 MB | 65.0 |
| xlsxrb (In-Memory) | 6.23 s | 431.2 MB | 68.0 |
| rubyXL (In-Memory) | 37.16 s | 2105.9 MB | 90.0 |
Read Performance (1,000,000 cells)
| Library | Time | Peak Memory | GC Count |
|---|---|---|---|
| xlsxrb (Streaming) | 5.38 s | 101.8 MB | 1429.0 |
| creek (Streaming) | 7.05 s | 706.2 MB | 3985.0 |
| roo (Streaming) | 7.24 s | 128.1 MB | 279.0 |
| xlsxrb (In-Memory) | 8.62 s | 996.1 MB | 28.0 |
| xsv (Streaming) | 14.41 s | 93.5 MB | 998.0 |
| rubyXL (In-Memory) | 24.58 s | 1856.8 MB | 127.0 |
Running the Benchmarks Locally
The benchmark data is gathered using the bundled benchmark.rb script, which runs each library's code in isolated subprocesses to ensure accurate memory and GC measurements.
To run the benchmark locally for 1,000,000 cells (100,000 rows):
ruby benchmark.rb 100000
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.
External Link Updates (update_links)
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
- Round-Trip Testing: Unit tests verify that every generated sheet can be reliably parsed back by the reader with identical content and styling.
- Contract Consistency: Ensures semantic output consistency between the Streaming (
Xlsxrb.generate) and In-Memory (Xlsxrb.build) APIs. - Property-Based Testing (PBT): Automatically generates random data to catch edge cases (e.g., huge numbers, special characters) preventing unexpected crashes.
- Concurrency Validation: Thread and Ractor safety checks to guarantee no global variable pollution during parallel execution.
- Security & DoS Protection: Hardened against malicious files, including memory exhaustion (ZIP Bombs) and infinite parsing loops.
Strict Interoperability & Rendering
- Official Open XML SDK Validation (E2E): Every generated spreadsheet is structurally validated against the official Microsoft Open XML SDK to prevent file corruption warnings in Microsoft Excel.
- Visual Regression Testing (VRT): Spreadsheets are rendered via a headless LibreOffice Calc engine and compared pixel-by-pixel against visual baselines to catch subtle rendering regressions.
Performance & Types
- Continuous Benchmarking: Memory usage and processing speeds are profiled in CI on large datasets to prevent performance regressions and OOM leaks.
- Runtime Type Validation: Strong dynamic typing using
RBS::Testto ensure the library's types are perfectly sound at runtime.
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 https://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.