Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -407,7 +407,10 @@ Many (most) parts of the specifications are not implemented, the focus has been
The main unsupported items are:

- no support for writing excel files, this is a read-only library
- no support for reading extra content, such as formatting, excel parameter, encrypted components etc ...
- limited support for reading cell formatting: XLSX cell styles (font, fill,
borders, alignment and number format) can be read with `worksheet_style()`,
but there is no formatting support for the other file formats, or for other
extra content such as excel parameters, encrypted components etc ...
- no support for reading VB for opendocuments

## Credits
Expand Down
4 changes: 4 additions & 0 deletions examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,12 @@ This directory contains some example of Calamine usage.
checks them for errors.
- `xlsx_formula_stream.rs`: Streams XLSX cell values and formula text in one
worksheet pass.
- `xlsx_style_stream.rs`: Streams XLSX cell values and cell styles in one
worksheet pass.
- `read_hyperlinks.rs`: Reads the hyperlinks defined in an XLSX worksheet,
either by sheet name or by sheet index.
- `read_cell_styles.rs`: Reads cell style/formatting information (fonts,
fills, borders, alignment and number formats) from an XLSX worksheet.
- `read_picture_data.rs`: Reads pictures and their metadata from an XLSX file.

### Serialization examples
Expand Down
68 changes: 68 additions & 0 deletions examples/read_cell_styles.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
// SPDX-License-Identifier: MIT
//
// Copyright 2016-2026, Johann Tuffe.

//! Example of reading cell style/formatting information from a worksheet in
//! an XLSX file.

use calamine::{open_workbook, Error, Reader, Xlsx};

fn main() -> Result<(), Error> {
let path = "tests/styles.xlsx";

let mut workbook: Xlsx<_> = open_workbook(path)?;
let sheet_name = workbook.sheet_names()[0].clone();

// Read the styles of all explicitly formatted cells in the worksheet.
let styles = workbook.worksheet_style(&sheet_name)?;

println!(
"'{}': styled range {:?}..={:?}",
sheet_name,
styles.start(),
styles.end(),
);

// Iterate over the cells and print a summary of any visible formatting.
for (row, col, style) in styles.cells() {
if style.is_empty() {
continue;
}

let mut summary = Vec::new();

if let Some(font) = &style.font {
if font.is_bold() {
summary.push("bold".to_string());
}
if font.is_italic() {
summary.push("italic".to_string());
}
if let Some(color) = &font.color {
summary.push(format!("font color {color}"));
}
}

if let Some(fill) = &style.fill {
if let Some(color) = fill.get_color() {
summary.push(format!("fill {color}"));
}
}

if let Some(borders) = &style.borders {
if borders.has_visible_borders() {
summary.push("borders".to_string());
}
}

if let Some(number_format) = &style.number_format {
summary.push(format!("format '{}'", number_format.format_code));
}

if !summary.is_empty() {
println!("({row}, {col}): {}", summary.join(", "));
}
}

Ok(())
}
55 changes: 55 additions & 0 deletions examples/xlsx_style_stream.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
// SPDX-License-Identifier: MIT
//
// Copyright 2016-2026, Johann Tuffe.

//! Demonstrates streaming XLSX cell values and cell styles in one worksheet
//! pass.

use calamine::{open_workbook, Xlsx};

fn main() -> Result<(), Box<dyn std::error::Error>> {
let path = "tests/styles.xlsx";

let mut workbook: Xlsx<_> = open_workbook(path)?;
let mut reader = workbook.worksheet_cells_reader("Sheet 1")?;

// Stream the cell values and their style ids in a single pass. The style
// id resolves to a full style via the workbook style palette.
while let Some((cell, style_id)) = reader.next_cell_with_style_id()? {
let style = &reader.styles()[style_id];

let mut summary = Vec::new();

if let Some(font) = &style.font {
if font.is_bold() {
summary.push("bold".to_string());
}
if font.is_italic() {
summary.push("italic".to_string());
}
}

if let Some(fill) = &style.fill {
if let Some(color) = fill.get_color() {
summary.push(format!("fill {color}"));
}
}

if let Some(number_format) = &style.number_format {
if number_format.format_code != "General" {
summary.push(format!("format '{}'", number_format.format_code));
}
}

let (row, col) = cell.get_position();
println!(
"row={}, col={}, value={:?}, style=[{}]",
row + 1,
col + 1,
cell.get_value(),
summary.join(", ")
);
}

Ok(())
}
12 changes: 11 additions & 1 deletion src/auto.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ use crate::vba::VbaProject;
use crate::Picture;
use crate::{
open_workbook, open_workbook_from_rs, Data, DataRef, HeaderRow, Metadata, Ods, Range, Reader,
ReaderRef, Xls, Xlsb, Xlsx,
ReaderRef, StyleRange, Xls, Xlsb, Xlsx,
};

use std::fs::File;
Expand Down Expand Up @@ -147,6 +147,16 @@ where
}
}

/// Get the cell styles for the worksheet with the given name.
fn worksheet_style(&mut self, name: &str) -> Result<StyleRange, Self::Error> {
match self {
Sheets::Xls(e) => e.worksheet_style(name).map_err(Error::Xls),
Sheets::Xlsx(e) => e.worksheet_style(name).map_err(Error::Xlsx),
Sheets::Xlsb(e) => e.worksheet_style(name).map_err(Error::Xlsb),
Sheets::Ods(e) => e.worksheet_style(name).map_err(Error::Ods),
}
}

fn worksheets(&mut self) -> Vec<(String, Range<Data>)> {
match self {
Sheets::Xls(e) => e.worksheets(),
Expand Down
43 changes: 43 additions & 0 deletions src/formats.rs
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,49 @@ pub fn builtin_format_by_id(id: &[u8]) -> CellFormat {
}
}

// Get the format code string for a builtin number format id.
//
// The ids and format codes are defined in ECMA-376 section 18.8.30. Returns
// `None` for ids that are not built in, such as custom formats and the reserved and
// locale-specific formats.
pub(crate) fn builtin_format_code_by_id(id: u32) -> Option<&'static str> {
match id {
0 => Some("General"),
1 => Some("0"),
2 => Some("0.00"),
3 => Some("#,##0"),
4 => Some("#,##0.00"),
9 => Some("0%"),
10 => Some("0.00%"),
11 => Some("0.00E+00"),
12 => Some("# ?/?"),
13 => Some("# ??/??"),
14 => Some("mm-dd-yy"),
15 => Some("d-mmm-yy"),
16 => Some("d-mmm"),
17 => Some("mmm-yy"),
18 => Some("h:mm AM/PM"),
19 => Some("h:mm:ss AM/PM"),
20 => Some("h:mm"),
21 => Some("h:mm:ss"),
22 => Some("m/d/yy h:mm"),
37 => Some("#,##0 ;(#,##0)"),
38 => Some("#,##0 ;[Red](#,##0)"),
39 => Some("#,##0.00;(#,##0.00)"),
40 => Some("#,##0.00;[Red](#,##0.00)"),
41 => Some("_(* #,##0_);_(* (#,##0);_(* \"-\"_);_(@_)"),
42 => Some("_($* #,##0_);_($* (#,##0);_($* \"-\"_);_(@_)"),
43 => Some("_(* #,##0.00_);_(* (#,##0.00);_(* \"-\"??_);_(@_)"),
44 => Some("_($* #,##0.00_);_($* (#,##0.00);_($* \"-\"??_);_(@_)"),
45 => Some("mm:ss"),
46 => Some("[h]:mm:ss"),
47 => Some("mmss.0"),
48 => Some("##0.0E+0"),
49 => Some("@"),
_ => None,
}
}

/// Check if code corresponds to builtin date format
///
/// See `is_builtin_date_format_id`
Expand Down
16 changes: 16 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ mod cfb;
mod datatype;
mod formats;
mod ods;
mod style;
mod xls;
mod xlsb;
mod xlsx;
Expand All @@ -111,6 +112,11 @@ pub use crate::de::{
};
pub use crate::errors::Error;
pub use crate::ods::{Ods, OdsError};
pub use crate::style::{
Alignment, Border, BorderStyle, Borders, Color, Fill, FillPattern, Font, FontStyle, FontWeight,
HorizontalAlignment, NumberFormat, Protection, Style, StyleRange, StyleRangeCells,
TextRotation, UnderlineStyle, VerticalAlignment,
};
pub use crate::xls::{Xls, XlsError, XlsOptions};
pub use crate::xlsb::{Xlsb, XlsbError};
pub use crate::xlsx::{
Expand Down Expand Up @@ -337,6 +343,16 @@ where
/// Read worksheet formula in corresponding worksheet path
fn worksheet_formula(&mut self, _: &str) -> Result<Range<String>, Self::Error>;

/// Get the cell styles for the worksheet with the given name.
///
/// Returns a [`StyleRange`] holding the styles of all cells in the
/// worksheet that have an explicit (non-default) format. Style reading is
/// currently only supported for XLSX files. The other file formats
/// currently return an empty range.
fn worksheet_style(&mut self, _name: &str) -> Result<StyleRange, Self::Error> {
Ok(StyleRange::empty())
}

/// Get all sheet names of this workbook, in workbook order
///
/// # Examples
Expand Down
Loading
Loading