Skip to main content

cellrune/
lib.rs

1//! Bounded XLSX/XLSM reading, deterministic calculation, editing, and writing.
2//!
3//! `CellRune` separates source data from recalculated results:
4//!
5//! 1. [`read_xlsx_path`], [`read_xlsx_bytes`], or [`read_xlsx`] creates an immutable
6//!    [`WorkbookSnapshot`].
7//! 2. [`calculate_workbook`] creates a separate owned [`CalculationSnapshot`] without changing
8//!    the source workbook or its saved XLSX results.
9//! 3. Each formula result is either a typed [`CalculationCellResult::Value`] or a structured
10//!    [`CalculationCellResult::Unavailable`] issue.
11//!
12//! # Quick start
13//!
14//! ```no_run
15//! use cellrune::{
16//!     CalculationCellResult, CalculationOptions, FiniteNumber, ReadOptions, calculate_workbook,
17//!     read_xlsx_path,
18//! };
19//!
20//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
21//! let workbook = read_xlsx_path("input.xlsx", ReadOptions::default())?;
22//!
23//! let options = CalculationOptions::default()
24//!     .with_today_serial(FiniteNumber::new(46_225.0)?);
25//! let calculation = calculate_workbook(&workbook, options);
26//!
27//! if let Some(sheet) = workbook.sheet_by_name("Sheet1") {
28//!     let source_cell = sheet.cell_by_a1("A1")?;
29//!     let _ = source_cell;
30//! }
31//!
32//! for (cell, result) in calculation.cells() {
33//!     match result {
34//!         CalculationCellResult::Value(value) => println!("{cell:?}: {value:?}"),
35//!         CalculationCellResult::Unavailable(issue) => {
36//!             eprintln!("{cell:?}: {}", issue.code().as_str());
37//!         }
38//!     }
39//! }
40//! # Ok(())
41//! # }
42//! ```
43//!
44//! # Failure model
45//!
46//! `CellRune` keeps failures at their owning boundary:
47//!
48//! - [`XlsxReadError`] means no trustworthy workbook snapshot could be produced. Its
49//!   [`XlsxReadError::code`] is stable and machine-readable.
50//! - [`Diagnostic`] records a compatibility caveat on a successfully read workbook.
51//! - [`CalculationIssue`] explains why one formula has no recalculated value. Unsupported engine
52//!   capabilities are not converted into Excel errors and cannot be hidden by `IFERROR`.
53//! - [`CellValue::Error`] represents an actual spreadsheet error value.
54//! - [`ValidationError`] rejects invalid caller-provided model values and addresses.
55//!
56//! Reading never executes macros or follows external links. Calculation is explicit and does not
57//! mutate or implicitly write a workbook. Recalculated result materialization is a separate
58//! explicit operation on package-backed documents.
59//!
60//! # Numeric contract
61//!
62//! Calculated numbers are not guaranteed to be bit-identical to Excel's.
63//! [`docs/NUMERICS.md`](https://github.com/emulette/cellrune/blob/main/docs/NUMERICS.md)
64//! records every known deliberate difference, the Excel build each statement was
65//! measured against, and which function families remain unmeasured. Compare results with a
66//! tolerance rather than for equality.
67//!
68//! Two behaviors are selectable through [`CalculationOptions`], and both default to matching
69//! Excel rather than to what releases up to 0.1.2 did:
70//!
71//! - [`ArithmeticSemantics`] decides whether Excel's narrow near-zero correction is applied to a
72//!   decimal/rational cancellation, or every IEEE-754 residue is preserved.
73//! - [`FinancialSolverSemantics`] decides whether `IRR`, `XIRR`, and `RATE` stop at the iteration
74//!   budget Microsoft documents, or search longer and return values where Excel reports `#NUM!`.
75//!
76//! Select [`ArithmeticSemantics::Ieee754`] and [`FinancialSolverSemantics::ExtendedSearch`] to
77//! restore the 0.1.2 behavior.
78
79#![forbid(unsafe_code)]
80#![deny(missing_docs)]
81
82mod address;
83mod calculation;
84mod cell;
85mod defined_name;
86mod diagnostic;
87mod draft;
88mod error;
89mod formula;
90mod presentation;
91mod table;
92mod workbook;
93mod xlsx;
94
95pub(crate) fn case_insensitive_eq(left: &str, right: &str) -> bool {
96    left.chars()
97        .flat_map(char::to_lowercase)
98        .eq(right.chars().flat_map(char::to_lowercase))
99}
100
101pub use address::{CellAddress, CellRange, Column, EXCEL_MAX_COLUMNS, EXCEL_MAX_ROWS, Row};
102pub use calculation::{
103    ApplyChangesError, ArithmeticSemantics, CalculationCellId, CalculationCellResult,
104    CalculationDecisionReason, CalculationDelta, CalculationDeltaCell, CalculationDeltaPage,
105    CalculationExecutionMode, CalculationIssue, CalculationIssueCode, CalculationLimits,
106    CalculationOptions, CalculationOptionsError, CalculationSnapshot, CancellationToken,
107    CompletedCalculation, CompletedWorkbookTransaction, DefinedNameAnalysis,
108    DefinedNameAnalysisError, DefinedNameAnalysisErrorKind, DefinedNameAnalysisLimitKind,
109    DefinedNameAnalysisOptions, DefinedNameAnalysisOptionsError, DefinedNameDynamicKind,
110    DefinedNameExternalReference, DefinedNameExternalTargetKind, DefinedNameInvalidReason,
111    DefinedNameReferenceArea, DefinedNameSheetSpan, DefinedNameUnsupportedReason,
112    FinancialSolverSemantics, FormulaCapability, FormulaCapabilityEntry, FormulaCapabilityReport,
113    FunctionCatalogEntry, FunctionSupport, FunctionUsageEntry, FunctionUsageReport,
114    InstallDeltaBasisReason, MaterializedCalculationCell, MaterializedResultOrigin,
115    PreparedCalculation, PreparedEditBatch, PreparedWorkbookTransaction, RecalculationMode,
116    SessionError, SessionErrorCode, SessionLimits, TransactionAffectedFormula,
117    TransactionDetailItem, TransactionDetailSection, TransactionImpactCause,
118    TransactionImpactCoverage, TransactionImpactPage, TransactionInstallResultChange,
119    TransactionIssueChange, TransactionIssueChangeKind, TransactionPageCursor,
120    TransactionResultChange, WorkbookCalculationSession, WorkbookTransactionReceipt,
121    WorkbookTransactionReport, analyze_defined_name, analyze_defined_name_cancellable,
122    analyze_defined_name_with_options, calculate_workbook, scan_formula_capabilities,
123    scan_formula_capabilities_with_options, scan_function_usage, scan_function_usage_with_options,
124    supported_function_catalog,
125};
126pub use cell::{
127    Cell, CellContent, CellValue, ExcelError, FiniteNumber, NumberFormat, NumberFormatKind,
128};
129pub use defined_name::{DefinedName, DefinedNameScope};
130pub use diagnostic::{
131    Diagnostic, DiagnosticCode, DiagnosticSeverity, InputHash, OutputHash, Provenance,
132    ProviderIdentity, SourceId, SourceLocation,
133};
134pub use draft::{EditBatch, EditReceipt, WorkbookChange, WorkbookDraft};
135pub use error::{ValidationError, ValidationErrorCode};
136pub use formula::{
137    FormulaCell, FormulaDialect, FormulaMetadata, FormulaText, SavedResult, SavedResultIssue,
138    SharedFormulaRole,
139};
140pub use presentation::{
141    CellPhonetics, ColumnPhoneticVisibility, DocumentPresentation, FrozenPane, PhoneticAlignment,
142    PhoneticProperties, PhoneticRun, PhoneticTextRange, PhoneticType, PhoneticWriteOptions,
143    ResolvedPhoneticRun,
144};
145pub(crate) use presentation::{CellPresentation, PhoneticAnnotation};
146pub use table::{
147    Table, TableAutoFilter, TableCalendarType, TableColorFilter, TableColumn, TableColumnId,
148    TableColumnName, TableCustomFilter, TableCustomFilterOperator, TableCustomFilters,
149    TableDateGroupItem, TableDateTimeGrouping, TableDateTimeValue, TableDynamicFilter,
150    TableDynamicFilterType, TableFilterColumn, TableFilterCriteria, TableFilterItem, TableFormula,
151    TableIconFilter, TableIconSet, TableId, TableName, TableNumericValue, TableSortBy,
152    TableSortCondition, TableSortMethod, TableSortState, TableStyleInfo, TableTopFilter, TableType,
153    TableValueFilters, TotalsRowFunction,
154};
155pub use workbook::{
156    CalculationHints, CalculationMode, DateSystem, Sheet, SheetId, SheetName, SheetVisibility,
157    WorkbookFingerprint, WorkbookSnapshot, WorkbookSource, WorkbookSourceKind,
158};
159pub use xlsx::{
160    OpenOptions, PackageSummary, ReadLimits, ReadOptions, ReadOptionsError, RecalculatedWorkbook,
161    RecalculationWriteOptions, RecalculationWritePolicy, WriteLimits, WriteOptions,
162    WriteOptionsError, WriteProvenance, WriteReport, XlsxDocument, XlsxDocumentKind, XlsxErrorCode,
163    XlsxReadError, XlsxWriteError, XlsxWriteErrorCode, inspect_package, open_xlsx_document,
164    open_xlsx_document_bytes, open_xlsx_document_path, read_xlsx, read_xlsx_bytes, read_xlsx_path,
165    write_preserved_xlsx_bytes, write_recalculated_xlsx, write_recalculated_xlsx_bytes,
166    write_recalculated_xlsx_path, write_xlsx_draft, write_xlsx_draft_bytes, write_xlsx_draft_path,
167};