calamine

repository·master·Indexed 25 days ago

https://github.com/tafia/calamine

A pure Rust library for reading and deserializing spreadsheet formats, including Excel (xls, xlsx, xlsm, xlsb, xla, xlam) and OpenDocument (ods) files. It provides tools for basic cell reading, streaming XLSX values and formulas, extracting hyperlinks and pictures, and deserializing rows into Rust structs using Serde. The library is read-only and supports automatic workbook type detection via open_workbook_auto.

Tokens
15.8K
Snippets
27
Records
90
Agent score
80%

What's inside calamine

  1. Understand calamine limitations and unsupported features

    master

    The calamine library is focused on reading cell values and vba code. It is a read-only library.

    Unsupported features include:

    • Writing Excel files (read-only only).
    • Reading extra content such as formatting, Excel parameters, or encrypted components.
    • Reading VB for OpenDocument formats.
  2. Enable Calamine crate features

    master

    Calamine supports optional features that are disabled by default. You can enable them using the -F flag with cargo add:

    • chrono: Adds support for Chrono date/time types to the API.
    • dates: A deprecated backwards compatible synonym for the chrono feature.
    • picture: Adds support for reading raw data for pictures in spreadsheets.
    cargo add calamine -F chrono
  3. Handle Excel 1900 vs 1904 date epochs

    master

    Excel supports two different date systems. When parsing serial numbers, you must specify the correct epoch to ensure dates are calculated correctly:

    1. 1900 Epoch: The standard system where 0.0 corresponds to 1899-12-31. Use false for the is_1904_epoch parameter in ExcelDateTime::new.
    2. 1904 Epoch: An alternative system where 0.0 corresponds to 1904-01-01. Use true for the is_1904_epoch parameter in ExcelDateTime::new.

    This distinction affects both full DateTime values and Date-only values.

  4. Use the Sheets enum for runtime file type handling

    master

    The Sheets<RS> enum is a wrapper used when the file type is not known at static time. It allows you to treat different spreadsheet formats (Xls, Xlsx, Xlsb, Ods) uniformly by implementing the Reader and ReaderRef traits.

    Supported variants:

    • Xls(Xls<RS>)
    • Xlsx(Xlsx<RS>)
    • Xlsb(Xlsb<RS>)
    • Ods(Ods<RS>)

    Because it implements the Reader trait, you can call common methods like .worksheet_range(), .metadata(), and .with_header_row() directly on a Sheets instance without manually matching the enum variants.

  5. Enable optional crate features

    master

    Calamine supports optional features that are disabled by default. To enable them, add them to your Cargo.toml or use the cargo add command:

    • chrono: Adds support for Chrono date/time types.
    • dates: A deprecated synonym for chrono.
    • picture: Adds support for reading raw data for pictures in spreadsheets.
  6. Load and use Excel Tables

    master

    Excel Tables (named ranges with common formatting) are not loaded automatically to minimize overhead. You must explicitly call load_tables() before attempting to access them.

    After loading, you can:

    • List all table names in the workbook with table_names().
    • List table names within a specific sheet with table_names_in_sheet(sheet_name).
    • Retrieve an owned copy of a table's data using table_by_name(table_name).
    • Retrieve a borrowed/referenced copy of a table's data (more efficient for large tables) using table_by_name_ref(table_name).

    Both table_by_name and table_by_name_ref return a Table object containing the table's name, sheet name, columns, and a Range of data.

    use calamine::{open_workbook, Data, Error, Xlsx};
    
    fn main() -> Result<(), Error> {
        let path = "tests/table-multiple.xlsx";
    
        // Open the workbook.
        let mut workbook: Xlsx<_> = open_workbook(path)?;
    
        // Load the tables in the workbook.
        workbook.load_tables()?;
    
        // Get the table by name. 
        // This returns an owned copy of the worksheet data.
        let table = workbook.table_by_name("Inventory")?;
    
        // Get the data range of the table. The data type is `&Range<Data>`.
        let data_range = table.data();
    
        // Access cell values using the Range API.
        assert_eq!(
            data_range.get((0, 1)),
            Some(&Data::String("Apple".to_string()))
        );
    
        Ok(())
    }
  7. Work with Pivot Tables in XLSX

    master

    To access Pivot Table data, you must first retrieve the workbook's pivot table metadata using pivot_tables(). This is a required step because Pivot Table data relies on metadata stored in PivotTableRef.

    Once you have the PivotTables collection, you can iterate over the cached data for a specific table using pivot_table_data(), providing the worksheet name and the pivot table name.

    use calamine::{open_workbook, Error, Xlsx};
    
    fn main() -> Result<(), Error> {
        let path = "tests/pivots.xlsx";
    
        // Open the workbook.
        let mut workbook: Xlsx<_> = open_workbook(path)?;
    
        // Must retrieve necessary metadata before reading Pivot Table data.
        let pivot_tables = workbook.pivot_tables()?;
    
        // Get the Pivot Table data by referencing the pivot table name and the worksheet it resides.
        for row in workbook.pivot_table_data(&pivot_tables, "PivotSheet1", "PivotTable1")? {
                 // Do something.
        }
    
        Ok(())
    }
  8. Configure Serde deserialization with RangeDeserializerBuilder

    master

    Use RangeDeserializerBuilder to configure how a spreadsheet Range is deserialized into Rust types using Serde. You can control whether the first row is treated as a header and specify which headers to include.

    Header Options

    • No headers: Use .has_headers(false) to treat every row as data (including the first row).
    • All headers: Use .has_headers(true) (default) to treat the first row as a header row. This allows deserializing into structs or maps using header names.
    • Custom headers: Use .with_headers(&["name1", "name2"]) to only include specific columns based on their header names.
    • Automatic struct headers: Use .with_deserialize_headers::<T>() where T is your target struct. This automatically extracts the required header names from the struct's field names.
    use calamine::{open_workbook, Error, Xlsx, Reader, RangeDeserializerBuilder};
    
    fn main() -> Result<(), Error> {
        let path = "path/to/file.xlsx";
        let mut workbook: Xlsx<_> = open_workbook(path)?;
        let range = workbook.worksheet_range("Sheet1")?;
    
        // Example 1: No headers (treat first row as data)
        let mut iter = RangeDeserializerBuilder::new()
            .has_headers(false)
            .from_range(&range)?;
    
        // Example 2: Custom headers (only 'value' and 'label' columns)
        let mut iter = RangeDeserializerBuilder::with_headers(&["value", "label"])
            .from_range(&range)?;
    
        // Example 3: Automatic headers from a struct
        #[derive(serde::Deserialize)]
        struct Record {
            label: String,
            value: f64,
        }
        let mut iter = RangeDeserializerBuilder::with_deserialize_headers::<Record>()
            .from_range(&range)?;
    
        if let Some(result) = iter.next() {
            let record: Record = result.map_err(|e| ...)?;
            Ok(())
        }
        Ok(())
    }