⚠️ PREVIEW — NOT PRODUCTION READYThis project is an early preview. APIs, file-format behavior, performance characteristics, and compatibility guarantees may change without notice. Do not use it for production data without independent validation and backups.
High-performance Rust readers and writers for Excel XLSX and XLSB workbooks.
The crate is a Rust port of @justybase/spreadsheet-tasks and focuses on
streaming-friendly access to workbook data.
This repository is actively evolving and should be treated as experimental. The current implementation includes:
- XLSX and XLSB reading;
- XLSX and XLSB writing;
- forward-only row iteration;
- borrowed forward-only explicit-cell iteration for XLSX and XLSB;
- batch and streaming worksheet writers;
- worksheet updates for XLSX, XLSM, and XLSB;
- date-system handling for Excel's 1900 and 1904 systems;
- parity and cross-runtime regression tests.
The public API is not yet considered stable. Not every Excel feature is implemented, and compatibility should be tested against the exact workbooks your application uses.
Add the crate from a local checkout while the project is in preview:
[dependencies]
justybase-spreadsheet = { path = "../justybase_spreadsheet_rust" }Note: the library target keeps the short name, so imports stay
use spreadsheet::...even though the package isjustybase-spreadsheet.
Create an XLSX workbook:
use spreadsheet::{CellValue, XlsxWriter};
use std::path::Path;
fn main() -> Result<(), Box<dyn std::error::Error>> {
let path = Path::new("output.xlsx");
let mut writer = XlsxWriter::create(path)?;
writer.add_sheet("Sheet1", false);
writer.write_sheet(
vec![vec![CellValue::Text("Alice".into()), CellValue::Integer(30)]],
Some(&["Name".to_string(), "Age".to_string()]),
true,
)?;
writer.finalize()?;
Ok(())
}Read rows from either supported format:
use spreadsheet::{create_reader, SpreadsheetResult};
use std::path::Path;
fn read_workbook(path: &Path) -> SpreadsheetResult<usize> {
let mut reader = create_reader(path)?;
reader.open(path, true)?;
let mut cells = 0;
while reader.read()? {
cells += reader.current_row().len();
}
Ok(cells)
}For convenience, the row-oriented API remains available; avoid collecting the entire workbook unless your application needs random access.
When the lowest allocation rate matters, use the explicit-cell streaming API.
It yields borrowed CellRef values in worksheet order; text is borrowed from
the shared-string table or a reusable reader buffer and is valid until the
next call to next_cell:
use spreadsheet::{create_reader, SpreadsheetResult};
use std::path::Path;
fn read_cells(path: &Path) -> SpreadsheetResult<usize> {
let mut reader = create_reader(path)?;
reader.open(path, true)?;
let mut cells = reader.cell_reader("Sheet1")?;
let mut count = 0;
while let Some(cell) = cells.next_cell()? {
println!("({}, {}) = {:?}", cell.row, cell.column, cell.value);
count += 1;
}
Ok(count)
}This is a forward-only API for explicit cells. It does not materialize a
worksheet-wide XML buffer or a Vec<CellValue> row, and therefore is the
preferred path for large scans. The legacy row API remains available for
compatibility and convenience.
Run the complete test suite:
cargo testRun formatting checks:
cargo fmt --checkRun the read-performance comparison with
calamine:
cargo bench --bench read_comparisonThe benchmark creates one deterministic XLSX and one XLSB fixture. The
full_read group measures the compatible row API against calamine; the
streaming_cell_read group measures the new borrowed explicit-cell API.
The default fixture contains 5,000 rows and keeps the run short. Criterion
reports are written to target/criterion/; the available benchmark profiles
are listed in the repository's
BENCHMARKS.md.
Run the XLSX write comparison against
rust_xlsxwriter:
cargo run --release --example write_comparisonIt prints a text report (median write time, output file size, peak heap,
total allocations) for the batch and streaming writers of this crate against
the default and constant_memory modes of rust_xlsxwriter; optional
arguments are [outDir] [rows] [iters] [columns]. The default is 5,000 rows
and 3 iterations; larger profiles are opt-in. Available checks are listed in
the repository benchmark guide.
End-to-end gate combining the suites above with an independent reader:
cargo fmt --check && cargo test && cargo test --release
cargo bench --bench read_comparison -- --quick # asserts our reader == calamine
cargo run --release --example parity_bench /tmp/parity_check
cargo run --release --example write_comparison /tmp/parity_check
python3 scripts/validate_openpyxl.py \
/tmp/parity_check/spreadsheet-batch.xlsx=5001x7 \
/tmp/parity_check/spreadsheet-streaming.xlsx=5001x7scripts/validate_openpyxl.py value-checks the parity_write fixtures with
openpyxl — a pure-Python reader with no
code shared with this crate — covering cell types, exact values, XML
escaping, number formats, datetimes, hidden sheets and empty sheets, then
verifies the sheet extent of the larger benchmark outputs against
<dimension> (read-only consumers such as pandas size sheets from that
element). It bootstraps a virtualenv under target/ovenv when openpyxl is
not installed. openpyxl reads .xlsx only; .xlsb is covered by the
roundtrip tests and the calamine cross-check.
Licensed under the MIT License. See LICENSE-MIT.